Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(839)

Side by Side Diff: appengine/swarming/elements/build/elements.html

Issue 2211163003: Update new botlist to use dimensions endpoint (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@limiting
Patch Set: put demo data in luci-py wiki Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 <!DOCTYPE html><html><head><!-- 1 <!DOCTYPE html><html><head><!--
2 @license 2 @license
3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt 4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
7 Code distributed by Google as part of the polymer project is also 7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
9 --><!-- 9 --><!--
10 @license 10 @license
(...skipping 20536 matching lines...) Expand 10 before | Expand all | Expand 10 after
20547 <script> 20547 <script>
20548 Polymer({ 20548 Polymer({
20549 is: 'paper-input', 20549 is: 'paper-input',
20550 20550
20551 behaviors: [ 20551 behaviors: [
20552 Polymer.IronFormElementBehavior, 20552 Polymer.IronFormElementBehavior,
20553 Polymer.PaperInputBehavior 20553 Polymer.PaperInputBehavior
20554 ] 20554 ]
20555 }); 20555 });
20556 </script> 20556 </script>
20557 <script>
20558
20559 window.SwarmingBehaviors = window.SwarmingBehaviors || {};
20560 (function(){
20561 var ANDROID_ALIASES = {
20562 "bullhead": "Nexus 5X",
20563 "flo": "Nexus 7",
20564 "flounder": "Nexus 9",
20565 "hammerhead": "Nexus 5",
20566 "mako": "Nexus 4",
20567 "shamu": "Nexus 6",
20568 };
20569 // Taken from http://developer.android.com/reference/android/os/BatteryManag er.html
20570 var BATTERY_HEALTH_UNKNOWN = 1;
20571 var BATTERY_HEALTH_GOOD = 2;
20572 var BATTERY_STATUS_CHARGING = 2;
20573
20574 var UNAUTHENTICATED = "unauthenticated";
20575 var AVAILABLE = "available";
20576 var UNKNOWN = "unknown";
20577
20578 var GPU_ALIASES = {
20579 "1002": "AMD",
20580 "1002:6779": "AMD Radeon HD 6450/7450/8450",
20581 "1002:6821": "AMD Radeon HD 8870M",
20582 "1002:9830": "AMD Radeon HD 8400",
20583 "102b": "Matrox",
20584 "102b:0522": "Matrox MGA G200e",
20585 "102b:0532": "Matrox MGA G200eW",
20586 "102b:0534": "Matrox G200eR2",
20587 "10de": "NVIDIA",
20588 "10de:08aa": "NVIDIA GeForce 320M",
20589 "10de:0fe9": "NVIDIA GeForce GT 750M Mac Edition",
20590 "10de:104a": "NVIDIA GeForce GT 610",
20591 "10de:11c0": "NVIDIA GeForce GTX 660",
20592 "10de:1244": "NVIDIA GeForce GTX 550 Ti",
20593 "10de:1401": "NVIDIA GeForce GTX 960",
20594 "8086": "Intel",
20595 "8086:041a": "Intel Xeon Integrated",
20596 "8086:0a2e": "Intel Haswell Integrated",
20597 "8086:0d26": "Intel Crystal Well Integrated",
20598 }
20599
20600 // For consistency, all aliases are displayed like:
20601 // Nexus 5X (bullhead)
20602 // This regex matches a string like "ALIAS (ORIG)", with ORIG as group 1.
20603 var ALIAS_REGEXP = /.+ \((.*)\)/;
20604
20605 // This behavior wraps up all the shared bot-list functionality.
20606 SwarmingBehaviors.BotListBehavior = {
20607
20608 properties: {
20609 // TODO(kjlubick): Add more of these things from state, as they
20610 // needed/useful/requested.
20611 DIMENSIONS: {
20612 type: Array,
20613 value: function(){
20614 return ["android_devices", "cores", "cpu", "device_type",
20615 "device_os", "gpu", "id", "os", "pool"];
20616 },
20617 },
20618 DIMENSIONS_WITH_ALIASES: {
20619 type: Array,
20620 value: function(){
20621 return ["device_type", "gpu"];
20622 },
20623 },
20624 BOT_PROPERTIES: {
20625 type: Array,
20626 value: function() {
20627 return ["disk_space", "task", "status"];
20628 }
20629 },
20630 },
20631
20632 _androidAlias: function(dt) {
20633 return ANDROID_ALIASES[dt] || UNKNOWN;
20634 },
20635
20636 // _applyAlias is the consistent way to modify a string to show its alias.
20637 _applyAlias: function(orig, alias) {
20638 return alias +" ("+orig+")";
20639 },
20640
20641 // _attribute looks first in dimension and then in state for the
20642 // specified attribute. This will always return an array. If there is
20643 // no matching attribute, ["unknown"] will be returned.
20644 _attribute: function(bot, attr, none) {
20645 none = none || UNKNOWN;
20646 return this._dimension(bot, attr) || this._state(bot, attr) || [none];
20647 },
20648
20649 _devices: function(bot) {
20650 var devices = [];
20651 var d = (bot && bot.state && bot.state.devices) || {};
20652 // state.devices is like {Serial:Object}, so we need to keep the serial
20653 for (key in d) {
20654 var o = d[key];
20655 o.serial = key;
20656 o.okay = (o.state === AVAILABLE);
20657 devices.push(o);
20658 }
20659 return devices;
20660 },
20661
20662 // _deviceType returns the codename of a given Android device.
20663 _deviceType: function(device) {
20664 if (!device || !device.build) {
20665 return UNKNOWN;
20666 }
20667 var t = device.build["build.product"] || device.build["product.board"] | |
20668 device.build["product.device"] || UNKNOWN;
20669 return t.toLowerCase();
20670 },
20671
20672 // _dimension returns the given dimension of a bot. If it is defined, it
20673 // is an array of strings.
20674 _dimension: function(bot, dim) {
20675 if (!bot || !bot.dimensions || !dim) {
20676 return undefined;
20677 }
20678 for (var i = 0; i < bot.dimensions.length; i++) {
20679 if (bot.dimensions[i].key === dim) {
20680 return bot.dimensions[i].value;
20681 }
20682 }
20683 return undefined;
20684 },
20685
20686 _gpuAlias: function(gpu) {
20687 return GPU_ALIASES[gpu] || UNKNOWN;
20688 },
20689
20690 _not: function(a) {
20691 return !a;
20692 },
20693
20694 _or: function() {
20695 var result = false;
20696 // can't use .foreach, as arguments isn't really a function.
20697 for (var i = 0; i < arguments.length; i++) {
20698 result = result || arguments[i];
20699 }
20700 return result;
20701 },
20702
20703 // _state returns the requested attribute from a bot's state.
20704 // For consistency with _dimension, if the attribute is not an array,
20705 // it is put as the only element in an array.
20706 _state: function(bot, attr) {
20707 if (!bot || !bot.state || !bot.state[attr]) {
20708 return undefined
20709 }
20710 var state = bot.state[attr];
20711 if (Array.isArray(state)) {
20712 return state;
20713 }
20714 return [state];
20715 },
20716
20717 _taskId: function(bot) {
20718 if (bot && bot.task_id) {
20719 return bot.task_id;
20720 }
20721 return "idle";
20722 },
20723
20724 // _unalias will return the base dimension/state with its alias removed
20725 // if it had one. This is handy for sorting and filtering.
20726 _unalias: function(str) {
20727 var match = ALIAS_REGEXP.exec(str);
20728 if (match) {
20729 return match[1];
20730 }
20731 return str;
20732 },
20733 }
20734 })()
20735 </script>
20557 <dom-module id="bot-filters" assetpath="/res/imp/botlist/"> 20736 <dom-module id="bot-filters" assetpath="/res/imp/botlist/">
20558 <template> 20737 <template>
20559 <style is="custom-style" include="iron-flex iron-flex-alignment iron-positio ning"> 20738 <style is="custom-style" include="iron-flex iron-flex-alignment iron-positio ning">
20560 :host { 20739 :host {
20561 display: block; 20740 display: block;
20562 font-family: sans-serif; 20741 font-family: sans-serif;
20563 } 20742 }
20564 #filter { 20743 #filter {
20565 margin:0 5px; 20744 margin:0 5px;
20566 } 20745 }
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
20685 </div> 20864 </div>
20686 20865
20687 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox> 20866 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox>
20688 </div> 20867 </div>
20689 20868
20690 </div> 20869 </div>
20691 20870
20692 </template> 20871 </template>
20693 <script> 20872 <script>
20694 (function(){ 20873 (function(){
20695 var FILTER_SEP = " | "; 20874 var FILTER_SEP = ":";
20696 // filterMap is a map of primary -> function. The function returns a 20875 // filterMap is a map of primary -> function. The function returns a
20697 // boolean "does the bot (first arg) match the second argument". These 20876 // boolean "does the bot (first arg) match the second argument". These
20698 // functions will have "this" be the botlist, and will have access to all 20877 // functions will have "this" be the botlist, and will have access to all
20699 // functions defined in bot-list and bot-list-shared. 20878 // functions defined in bot-list and bot-list-shared.
20700 var filterMap = { 20879 var filterMap = {
20701 cores: function(bot, cores){ 20880 android_devices: function(bot, num) {
20702 var o = this._cores(bot); 20881 var o = this._attribute(bot, "android_devices", "0");
20882 return o.indexOf(num) !== -1;
20883 },
20884 cores: function(bot, cores) {
20885 var o = this._attribute(bot, "cores");
20703 return o.indexOf(cores) !== -1; 20886 return o.indexOf(cores) !== -1;
20704 }, 20887 },
20705 cpu: function(bot, cpu){ 20888 cpu: function(bot, cpu) {
20706 var o = this._dimension(bot, "cpu") || ["none"]; 20889 var o = this._attribute(bot, "cpu");
20707 return o.indexOf(cpu) !== -1; 20890 return o.indexOf(cpu) !== -1;
20708 }, 20891 },
20709 devices: function(bot, device){ 20892 device_os: function(bot, os) {
20710 if (device === "none") { 20893 var o = this._attribute(bot, "device_os", "none");
20711 return this._devices(bot).length === 0; 20894 return o.indexOf(os) !== -1;
20712 }
20713 // extract the deviceType, if it is not "unknown".
20714 device = this._unalias(device);
20715 var found = false;
20716 this._devices(bot).forEach(function(d) {
20717 if (this._deviceType(d) === device) {
20718 found = true;
20719 }
20720 }.bind(this));
20721 return found;
20722 }, 20895 },
20723 gpu: function(bot, gpu){ 20896 device_type: function(bot, dt) {
20724 var o = this._dimension(bot, "gpu") || ["none"]; 20897 var o = this._attribute(bot, "device_type", "none");
20898 return o.indexOf(this._unalias(dt)) !== -1;
20899 },
20900 disk_space: function(bot, space) {
20901 return true;
20902 },
20903 gpu: function(bot, gpu) {
20904 var o = this._attribute(bot, "gpu", "none");
20725 return o.indexOf(this._unalias(gpu)) !== -1; 20905 return o.indexOf(this._unalias(gpu)) !== -1;
20726 }, 20906 },
20727 id: function(bot, id) { 20907 id: function(bot, id) {
20728 return bot.bot_id === id; 20908 return true;
20729 }, 20909 },
20730 os: function(bot, os){ 20910 os: function(bot, os) {
20731 var o = this._dimension(bot, "os") || ["Unknown"]; 20911 var o = this._attribute(bot, "os");
20732 return o.indexOf(os) !== -1; 20912 return o.indexOf(os) !== -1;
20733 }, 20913 },
20734 pool: function(bot, pool){ 20914 pool: function(bot, pool) {
20735 var o = this._dimension(bot, "pool") || ["Unknown"]; 20915 var o = this._attribute(bot, "pool");
20736 return o.indexOf(pool) !== -1; 20916 return o.indexOf(pool) !== -1;
20737 }, 20917 },
20738 status: function(bot, status){ 20918 status: function(bot, status) {
20739 if (status === "quarantined") { 20919 if (status === "quarantined") {
20740 return bot.quarantined; 20920 return bot.quarantined;
20741 } else if (status === "dead") { 20921 } else if (status === "dead") {
20742 return bot.is_dead; 20922 return bot.is_dead;
20743 } else { 20923 } else {
20744 // Status must be "available". 20924 // Status must be "available".
20745 return !bot.quarantined && !bot.is_dead; 20925 return !bot.quarantined && !bot.is_dead;
20746 } 20926 }
20747 }, 20927 },
20748 task: function(bot, task) { 20928 task: function(bot, task) {
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
20781 }; 20961 };
20782 } 20962 }
20783 } 20963 }
20784 return { 20964 return {
20785 idx: -1, 20965 idx: -1,
20786 }; 20966 };
20787 }; 20967 };
20788 20968
20789 Polymer({ 20969 Polymer({
20790 is: "bot-filters", 20970 is: "bot-filters",
20971
20972 behaviors: [SwarmingBehaviors.BotListBehavior],
20973
20791 properties: { 20974 properties: {
20792 // input 20975 // input
20793 primary_map: { 20976 primary_map: {
20794 type: Object, 20977 type: Object,
20795 }, 20978 },
20796 primary_arr: { 20979 primary_arr: {
20797 type: Array, 20980 type: Array,
20798 }, 20981 },
20799 20982
20800 // output 20983 // output
20801 columns: { 20984 columns: {
20802 type: Array, 20985 type: Array,
20803 value: function() { 20986 value: function() {
20804 // TODO(kjlubick) back these up to URL params and load them from 20987 // TODO(kjlubick) back these up to URL params and load them from
20805 // there on reload. 20988 // there on reload.
20806 return ["id","os","task","status"]; 20989 return ["id","os","task","status"];
20807 }, 20990 },
20808 notify: true, 20991 notify: true,
20809 }, 20992 },
20993 dimensions: {
20994 type: Array,
20995 computed: "_extractDimensions(DIMENSIONS.*,_filters.*)",
20996 notify: true,
20997 },
20810 filter: { 20998 filter: {
20811 type: Object, 20999 type: Object,
20812 computed: "_makeFilter(_filters.*)", 21000 computed: "_makeFilter(_filters.*)",
20813 notify: true, 21001 notify: true,
20814 }, 21002 },
20815 verbose: { 21003 verbose: {
20816 type: Boolean, 21004 type: Boolean,
20817 value: false, 21005 value: false,
20818 notify: true, 21006 notify: true,
20819 }, 21007 },
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after
21025 }, 21213 },
21026 21214
21027 _afterBold: function(item, query) { 21215 _afterBold: function(item, query) {
21028 var match = matchPartCaseInsensitive(item, query); 21216 var match = matchPartCaseInsensitive(item, query);
21029 if (match.idx === -1) { 21217 if (match.idx === -1) {
21030 return ""; 21218 return "";
21031 } 21219 }
21032 return item.substring(match.idx + match.part.length); 21220 return item.substring(match.idx + match.part.length);
21033 }, 21221 },
21034 21222
21223 _extractDimensions: function() {
21224 var dims = []
21225 this._filters.forEach(function(f) {
21226 var split = f.split(FILTER_SEP, 1)
21227 var col = split[0];
21228 if (this.DIMENSIONS.indexOf(col) !== -1) {
21229 var rest = f.substring(col.length + FILTER_SEP.length);
21230 dims.push(col + FILTER_SEP + this._unalias(rest))
21231 };
21232 }.bind(this));
21233 return dims;
21234 }
21235
21035 }); 21236 });
21036 })(); 21237 })();
21037 </script> 21238 </script>
21038 </dom-module><script> 21239 </dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/">
21039
21040 window.SwarmingBehaviors = window.SwarmingBehaviors || {};
21041 (function(){
21042 var ANDROID_ALIASES = {
21043 "bullhead": "Nexus 5X",
21044 "flo": "Nexus 7",
21045 "hammerhead": "Nexus 5",
21046 "mako": "Nexus 4",
21047 "shamu": "Nexus 6",
21048 };
21049 // Taken from http://developer.android.com/reference/android/os/BatteryManag er.html
21050 var BATTERY_HEALTH_UNKNOWN = 1;
21051 var BATTERY_HEALTH_GOOD = 2;
21052 var BATTERY_STATUS_CHARGING = 2;
21053
21054 var UNAUTHENTICATED = "unauthenticated";
21055 var AVAILABLE = "available";
21056
21057 var GPU_ALIASES = {
21058 "1002": "AMD",
21059 "1002:6779": "AMD Radeon HD 6450/7450/8450",
21060 "1002:6821": "AMD Radeon HD 8870M",
21061 "102b": "Matrox",
21062 "102b:0522": "Matrox MGA G200e",
21063 "102b:0532": "Matrox MGA G200eW",
21064 "102b:0534": "Matrox G200eR2",
21065 "10de": "NVIDIA",
21066 "10de:08aa": "NVIDIA GeForce 320M",
21067 "10de:0fe9": "NVIDIA GeForce GT 750M Mac Edition",
21068 "10de:104a": "NVIDIA GeForce GT 610",
21069 "10de:11c0": "NVIDIA GeForce GTX 660",
21070 "10de:1244": "NVIDIA GeForce GTX 550 Ti",
21071 "10de:1401": "NVIDIA GeForce GTX 960",
21072 "8086": "Intel",
21073 "8086:041a": "Intel Xeon Integrated",
21074 "8086:0a2e": "Intel Haswell Integrated",
21075 "8086:0d26": "Intel Crystal Well Integrated",
21076 }
21077
21078 // For consistency, all aliases are displayed like:
21079 // Nexus 5X (bullhead)
21080 // This regex matches a string like "ALIAS (ORIG)", with ORIG as group 1.
21081 var ALIAS_REGEXP = /.+ \((.*)\)/;
21082
21083 // This behavior wraps up all the shared bot-list functionality.
21084 SwarmingBehaviors.BotListBehavior = {
21085
21086 _androidAlias: function(device) {
21087 if (device.notReady) {
21088 return UNAUTHENTICATED.toUpperCase();
21089 }
21090 var t = this._deviceType(device);
21091 var a = ANDROID_ALIASES[t];
21092 if (!a) {
21093 return "UNKNOWN";
21094 }
21095 return a;
21096 },
21097
21098 // _applyAlias is the consistent way to modify a string to show its alias.
21099 _applyAlias: function(orig, alias) {
21100 return alias +" ("+orig+")";
21101 },
21102
21103 _cores: function(bot) {
21104 // For whatever reason, sometimes cores are in dimensions and sometimes
21105 // they are in state, but never both.
21106 var c = (bot && bot.state && bot.state.cores);
21107 if (c && c.length > 0) {
21108 return c;
21109 }
21110 c = this._dimension(bot, "cores") || ["Unknown"];
21111 return c;
21112 },
21113
21114 _devices: function(bot) {
21115 var devices = [];
21116 var d = (bot && bot.state && bot.state.devices) || {};
21117 // state.devices is like {Serial:Object}, so we need to keep the serial
21118 for (key in d) {
21119 var o = d[key];
21120 o.serial = key;
21121 o.okay = (o.state === AVAILABLE);
21122 devices.push(o);
21123 }
21124 return devices;
21125 },
21126
21127 // _deviceType returns the codename of a given Android device.
21128 _deviceType: function(device) {
21129 if (!device || !device.build) {
21130 return "unknown";
21131 }
21132 var t = device.build["build.product"] || device.build["product.board"] | |
21133 device.build["product.device"] || "unknown";
21134 return t.toLowerCase();
21135 },
21136
21137 // _dimension returns the given dimension of a bot. If it is defined, it
21138 // is typically an array of strings.
21139 _dimension: function(bot, dim) {
21140 if (!bot || !bot.dimensions || !dim) {
21141 return undefined;
21142 }
21143 for (var i = 0; i < bot.dimensions.length; i++) {
21144 if (bot.dimensions[i].key === dim) {
21145 return bot.dimensions[i].value;
21146 }
21147 }
21148 return undefined;
21149 },
21150
21151 _gpuAlias: function(gpu) {
21152 var a = GPU_ALIASES[gpu];
21153 if (!a) {
21154 return "UNKNOWN";
21155 }
21156 return a;
21157 },
21158
21159 _not: function(a) {
21160 return !a;
21161 },
21162
21163 _or: function() {
21164 var result = false;
21165 // can't use .foreach, as arguments isn't really a function.
21166 for (var i = 0; i < arguments.length; i++) {
21167 result = result || arguments[i];
21168 }
21169 return result;
21170 },
21171
21172 _taskId: function(bot) {
21173 if (bot && bot.task_id) {
21174 return bot.task_id;
21175 }
21176 return "idle";
21177 },
21178
21179 // _unalias will return the base dimension/state with its alias removed
21180 // if it had one. This is handy for sorting and filtering.
21181 _unalias: function(str) {
21182 var match = ALIAS_REGEXP.exec(str);
21183 if (match) {
21184 return match[1];
21185 }
21186 return str;
21187 },
21188 }
21189 })()
21190 </script>
21191 <dom-module id="bot-list-data" assetpath="/res/imp/botlist/">
21192 <template> 21240 <template>
21193 <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth _headers]]" handle-as="json" last-response="{{_list}}" loading="{{_busy1}}"> 21241 <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth _headers]]" params="[[_botlistParams(dimensions.*)]]" handle-as="json" last-resp onse="{{_list}}" loading="{{_busy1}}">
21194 </iron-ajax> 21242 </iron-ajax>
21195 21243
21196 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_ headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy2}}"> 21244 <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" header s="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{ {_busy2}}">
21245 </iron-ajax>
21246
21247 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_ headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy3}}">
21197 </iron-ajax> 21248 </iron-ajax>
21198 </template> 21249 </template>
21199 <script> 21250 <script>
21200 (function(){ 21251 (function(){
21201 // TODO(kjlubick): Add more of these as well as things from state 21252
21202 // i.e. disk space remaining.
21203 var DIMENSIONS = ["cores", "cpu", "id", "os", "pool"];
21204 // "gpu" and "devices" are added separately because we need to
21205 // deal with aliases.
21206 var BOT_PROPERTIES = ["gpu", "devices", "task", "status"];
21207 Polymer({ 21253 Polymer({
21208 is: 'bot-list-data', 21254 is: 'bot-list-data',
21209 21255
21210 behaviors: [SwarmingBehaviors.BotListBehavior], 21256 behaviors: [SwarmingBehaviors.BotListBehavior],
21211 21257
21212 properties: { 21258 properties: {
21213 // inputs 21259 // inputs
21214 auth_headers: { 21260 auth_headers: {
21215 type: Object, 21261 type: Object,
21216 observer: "signIn", 21262 observer: "signIn",
21217 }, 21263 },
21264 dimensions: {
21265 type: Array,
21266 },
21218 21267
21219 //outputs 21268 //outputs
21220 bots: { 21269 bots: {
21221 type: Array, 21270 type: Array,
21222 computed: "_bots(_list)", 21271 computed: "_bots(_list)",
21223 notify: true, 21272 notify: true,
21224 }, 21273 },
21225 busy: { 21274 busy: {
21226 type: Boolean, 21275 type: Boolean,
21227 computed: "_or(_busy1,_busy2)", 21276 computed: "_or(_busy1,_busy2,_busy3)",
21228 notify: true, 21277 notify: true,
21229 }, 21278 },
21230 fleet: { 21279 fleet: {
21231 type: Object, 21280 type: Object,
21232 computed: "_fleet(_count)", 21281 computed: "_fleet(_count)",
21233 notify: true, 21282 notify: true,
21234 }, 21283 },
21235 primary_map: { 21284 primary_map: {
21236 type:Object, 21285 type:Object,
21237 computed: "_primaryMap(bots)", 21286 computed: "_primaryMap(_dimensions)",
21238 notify: true, 21287 notify: true,
21239 }, 21288 },
21240 primary_arr: { 21289 primary_arr: {
21241 type:Array, 21290 type: Array,
21242 value: function() { 21291 // DIMENSIONS and BOT_PROPERTIES are inherited from BotListBehavior
21243 return DIMENSIONS.concat(BOT_PROPERTIES); 21292 computed: "_primaryArr(DIMENSIONS, BOT_PROPERTIES)",
21244 },
21245 notify: true, 21293 notify: true,
21246 }, 21294 },
21247 21295
21248 // private 21296 // private
21249 _count: { 21297 _count: {
21250 type: Object, 21298 type: Object,
21251 }, 21299 },
21300 _dimensions: {
21301 type: Object,
21302 },
21252 _list: { 21303 _list: {
21253 type: Object, 21304 type: Object,
21254 }, 21305 },
21255 }, 21306 },
21256 21307
21257 signIn: function(){ 21308 signIn: function(){
21258 this.$.botlist.generateRequest(); 21309 this.$.botlist.auto = true;
21259 this.$.fleet.generateRequest(); 21310 this.$.dimensions.auto = true;
21311 this.$.fleet.auto = true;
21312 },
21313
21314 _botlistParams: function() {
21315 if (!this.dimensions) {
21316 return {};
21317 }
21318 return {
21319 dimensions: this.dimensions,
21320 };
21260 }, 21321 },
21261 21322
21262 _bots: function(){ 21323 _bots: function(){
21263 if (!this._list || !this._list.items) { 21324 if (!this._list || !this._list.items) {
21264 return []; 21325 return [];
21265 } 21326 }
21266 this._list.items.forEach(function(o){ 21327 // Do any preprocessing here
21267 o.state = JSON.parse(o.state); 21328 this._list.items.forEach(function(bot){
21329 // Parse the state, which is a JSON string. This contains a lot of
21330 // interesting information like details about the devices attached.
21331 bot.state = JSON.parse(bot.state);
21332 // get the disks in an easier to deal with format, sorted by size.
21333 var disks = bot.state["disks"];
21334 var keys = Object.keys(disks);
21335 if (!keys || !keys.length) {
21336 bot.disks = [{"id": "unknown", "mb": 0}];
21337 } else {
21338 bot.disks = [];
21339 for (var i = 0; i < keys.length; i++) {
21340 bot.disks.push({"id":keys[i], "mb":disks[keys[i]].free_mb});
21341 }
21342 // Sort these so the biggest disk comes first.
21343 bot.disks.sort(function(a, b) {
21344 return b.mb - a.mb;
21345 });
21346 }
21347
21268 }); 21348 });
21269 return this._list.items; 21349 return this._list.items;
21270 }, 21350 },
21271 21351
21272 _fleet: function() { 21352 _fleet: function() {
21273 if (!this._count) { 21353 if (!this._count) {
21274 return {}; 21354 return {};
21275 } 21355 }
21276 return { 21356 return {
21277 alive: this._count.count || -1, 21357 alive: this._count.count || -1,
21278 busy: this._count.busy || -1, 21358 busy: this._count.busy || -1,
21279 idle: this._count.count && this._count.busy && 21359 idle: this._count.count && this._count.busy &&
21280 this._count.count - this._count.busy, 21360 this._count.count - this._count.busy,
21281 dead: this._count.dead || -1, 21361 dead: this._count.dead || -1,
21282 quarantined: this._count.quarantined || -1, 21362 quarantined: this._count.quarantined || -1,
21283 } 21363 }
21284 }, 21364 },
21285 21365
21286 _primaryMap: function(bots){ 21366 _primaryArr: function(dimensions, properties) {
21367 return dimensions.concat(properties);
21368 },
21369
21370 _primaryMap: function(dimensions){
21287 // map will keep track of dimensions that we have seen at least once. 21371 // map will keep track of dimensions that we have seen at least once.
21288 // This will then basically get turned into an array to be used for 21372 // This will then basically get turned into an array to be used for
21289 // filtering. 21373 // filtering.
21290 var map = {}; 21374 dimensions = dimensions.bots_dimensions;
21291 DIMENSIONS.forEach(function(p){
21292 map[p] = {};
21293 });
21294 map["devices"] = {};
21295 map["gpu"] = {};
21296 bots.forEach(function(b){
21297 DIMENSIONS.forEach(function(d){
21298 var dims = this._dimension(b, d) || [];
21299 dims.forEach(function(e){
21300 map[d][e] = true;
21301 });
21302 }.bind(this));
21303 21375
21304 // Add Android devices and their aliases 21376 var pMap = {};
21305 this._devices(b).forEach(function(d){ 21377 dimensions.forEach(function(d){
21306 var dt = this._deviceType(d); 21378 if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) {
21307 var alias = this._androidAlias(d); 21379 // value is an array of all seen values for the dimension d.key
21308 if (dt !== "unknown") { 21380 pMap[d.key] = d.value;
21309 dt = this._applyAlias(dt,alias); 21381 } else if (d.key === "gpu") {
21310 } 21382 var gpus = [];
21311 map["devices"][dt] = true; 21383 d.value.forEach(function(g){
21312 }.bind(this)); 21384 var alias = this._gpuAlias(g);
21313 map["devices"]["none"] = true; 21385 if (alias !== "unknown") {
21314 21386 gpus.push(this._applyAlias(g, alias));
21315 // Add GPUs and their aliases 21387 } else {
21316 var gpus = this._dimension(b, "gpu") || []; 21388 gpus.push(g);
21317 gpus.forEach(function(g){ 21389 }
21318 var alias = this._gpuAlias(g); 21390 }.bind(this));
21319 if (alias !== "UNKNOWN") { 21391 pMap["gpu"] = gpus;
21320 map["gpu"][this._applyAlias(g, alias)] = true; 21392 } else if (d.key === "device_type") {
21321 } else { 21393 var devs = [];
21322 map["gpu"][g] = true; 21394 d.value.forEach(function(dt){
21323 } 21395 var alias = this._androidAlias(dt);
21324 21396 if (alias !== "unknown") {
21325 }.bind(this)); 21397 devs.push(this._applyAlias(dt, alias));
21398 } else {
21399 devs.push(dt);
21400 }
21401 }.bind(this));
21402 pMap["device_type"] = devs;
21403 } else {
21404 console.log("Unknown alias type: ", d);
21405 }
21326 }.bind(this)); 21406 }.bind(this));
21327 21407
21328 // Turn the Map<Object,Map<Boolean>> into a Map<Object,Array<String>> 21408 // Add some options that might not show up.
21329 // with all of the secondary elements sorted appropriately. 21409 pMap["android_devices"].push("0");
21330 var pMap = {}; 21410 pMap["device_os"].push("none");
21331 for (key in map){ 21411 pMap["device_type"].push("none");
21332 pMap[key] = Object.keys(map[key]).sort(swarming.naturalCompare); 21412
21333 } 21413 pMap["id"] = [];
21334 21414
21335 // Create custom filter options 21415 // Create custom filter options
21416 pMap["disk_space"] = [];
21336 pMap["task"] = ["busy", "idle"]; 21417 pMap["task"] = ["busy", "idle"];
21337 pMap["status"] = ["available", "dead", "quarantined"]; 21418 pMap["status"] = ["available", "dead", "quarantined"];
21419
21420 // No need to sort any of this, bot-filters sorts secondary items
21421 // automatically, especially when the user types a query.
21338 return pMap; 21422 return pMap;
21339 }, 21423 },
21340 21424
21341 }); 21425 });
21342 })(); 21426 })();
21343 </script> 21427 </script>
21344 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/"> 21428 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/">
21345 <template> 21429 <template>
21346 <style include="swarming-app-style"> 21430 <style include="swarming-app-style">
21347 :host { 21431 :host {
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after
21518 </style> 21602 </style>
21519 21603
21520 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu sy="[[_busy]]" name="Swarming Bot List"> 21604 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu sy="[[_busy]]" name="Swarming Bot List">
21521 21605
21522 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> 21606 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
21523 21607
21524 <div hidden$="[[_not(_signed_in)]]"> 21608 <div hidden$="[[_not(_signed_in)]]">
21525 21609
21526 <div class="horizontal layout"> 21610 <div class="horizontal layout">
21527 21611
21528 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar r]]" columns="{{_columns}}" filter="{{_filter}}" verbose="{{_verbose}}"> 21612 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar r]]" columns="{{_columns}}" dimensions="{{_dimensions}}" filter="{{_filter}}" ve rbose="{{_verbose}}">
21529 </bot-filters> 21613 </bot-filters>
21530 21614
21531 <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedB ots]]"> 21615 <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedB ots]]">
21532 </bot-list-summary> 21616 </bot-list-summary>
21533 21617
21534 </div> 21618 </div>
21535 21619
21536 <bot-list-data auth_headers="[[_auth_headers]]" bots="{{_bots}}" busy="{ {_busy}}" fleet="{{_fleet}}" primary_map="{{_primary_map}}" primary_arr="{{_prim ary_arr}}"> 21620 <bot-list-data auth_headers="[[_auth_headers]]" dimensions="[[_dimension s]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_primar y_map}}" primary_arr="{{_primary_arr}}">
21537 </bot-list-data> 21621 </bot-list-data>
21538 21622
21539 <table class="bot-list"> 21623 <table class="bot-list">
21540 <thead on-sort_change="_sortChange"> 21624 <thead on-sort_change="_sortChange">
21541 21625
21542 <tr> 21626 <tr>
21543 <th> 21627 <th>
21544 <span>Bot Id</span> 21628 <span>Bot Id</span>
21545 <sort-toggle name="id" current="[[_sort]]"> 21629 <sort-toggle name="id" current="[[_sort]]">
21546 </sort-toggle> 21630 </sort-toggle>
(...skipping 28 matching lines...) Expand all
21575 </td> 21659 </td>
21576 21660
21577 <template is="dom-repeat" items="[[_plain_columns]]" as="c"> 21661 <template is="dom-repeat" items="[[_plain_columns]]" as="c">
21578 <td hidden$="[[_hide(c)]]"> 21662 <td hidden$="[[_hide(c)]]">
21579 [[_column(c, bot, _verbose)]] 21663 [[_column(c, bot, _verbose)]]
21580 </td> 21664 </td>
21581 </template> 21665 </template>
21582 21666
21583 </tr> 21667 </tr>
21584 <template is="dom-repeat" items="[[_devices(bot)]]" as="device"> 21668 <template is="dom-repeat" items="[[_devices(bot)]]" as="device">
21585 <tr hidden$="[[_hide('devices', _columns.*)]]" class$="[[_device Class(device)]]"> 21669 <tr hidden$="[[_hide('android_devices', _columns.*)]]" class$="[ [_deviceClass(device)]]">
21586 <td></td> 21670 <td></td>
21587 <td hidden$="[[_hide('task', _columns.*)]]"></td> 21671 <td hidden$="[[_hide('task', _columns.*)]]"></td>
21588 <template is="dom-repeat" items="[[_plain_columns]]" as="c"> 21672 <template is="dom-repeat" items="[[_plain_columns]]" as="c">
21589 <td hidden$="[[_hide(c)]]"> 21673 <td hidden$="[[_hide(c)]]">
21590 [[_deviceColumn(c, device, _verbose)]] 21674 [[_deviceColumn(c, device, _verbose)]]
21591 </td> 21675 </td>
21592 </template> 21676 </template>
21593 </tr> 21677 </tr>
21594 </template> 21678 </template>
21595 </template> 21679 </template>
21596 </tbody> 21680 </tbody>
21597 </table> 21681 </table>
21598 </div> 21682 </div>
21599 21683
21600 </swarming-app> 21684 </swarming-app>
21601 21685
21602 </template> 21686 </template>
21603 <script> 21687 <script>
21604 (function(){ 21688 (function(){
21605 var special_columns = ["id", "task"]; 21689 var special_columns = ["id", "task"];
21606 21690
21607 var headerMap = { 21691 var headerMap = {
21608 // "id" and "task" are special, so they don't go here and have their 21692 // "id" and "task" are special, so they don't go here and have their
21609 // headers hard-coded below. 21693 // headers hard-coded below.
21694 "android_devices": "Android Devices",
21610 "cores": "Cores", 21695 "cores": "Cores",
21611 "cpu": "CPU", 21696 "cpu": "CPU",
21612 "devices": "Devices", 21697 "device_os": "Device OS",
21698 "device_type": "Device Type",
21699 "disk_space": "Free Space (MB)",
21613 "gpu": "GPU", 21700 "gpu": "GPU",
21614 "os": "OS", 21701 "os": "OS",
21615 "pool": "Pool", 21702 "pool": "Pool",
21616 "status": "Status", 21703 "status": "Status",
21617 }; 21704 };
21618 21705
21619 // This maps column name to a function that will return the content for a 21706 // This maps column name to a function that will return the content for a
21620 // given bot. These functions are bound to this element, and have access 21707 // given bot. These functions are bound to this element, and have access
21621 // to all functions defined here and in bot-list-shared. 21708 // to all functions defined here and in bot-list-shared.
21622 var columnMap = { 21709 var columnMap = {
21710 android_devices: function(bot) {
21711 var devs = this._attribute(bot, "android_devices", "0");
21712 if (this._verbose) {
21713 return devs.join(" | ") + " devices available";
21714 }
21715 // max() works on strings as long as they can be coerced to Number.
21716 return Math.max(...devs) + " devices available";
21717 },
21623 cores: function(bot){ 21718 cores: function(bot){
21624 var cores = this._cores(bot); 21719 var cores = this._attribute(bot, "cores");
21625 if (this._verbose){ 21720 if (this._verbose){
21626 return cores.join(" | "); 21721 return cores.join(" | ");
21627 } 21722 }
21628 return cores[0]; 21723 return cores[0];
21629 }, 21724 },
21630 cpu: function(bot){ 21725 cpu: function(bot){
21631 var cpus = this._dimension(bot, 'cpu') || ['Unknown']; 21726 var cpus = this._attribute(bot, "cpu");
21632 if (this._verbose){ 21727 if (this._verbose){
21633 return cpus.join(" | "); 21728 return cpus.join(" | ");
21634 } 21729 }
21635 return cpus[0]; 21730 return cpus[0];
21636 }, 21731 },
21637 devices: function(bot){ 21732 device_os: function(bot){
21638 return this._devices(bot).length + " devices attached"; 21733 var os = this._attribute(bot, "device_os", "none");
21734 if (this._verbose) {
21735 return os.join(" | ");
21736 }
21737 return os[0];
21738 },
21739 device_type: function(bot){
21740 var dt = this._attribute(bot, "device_type", "none");
21741 if (this._verbose) {
21742 return dt.join(" | ");
21743 }
21744 return dt[0];
21745 },
21746 disk_space: function(bot) {
21747 var aliased = [];
21748 bot.disks.forEach(function(disk){
21749 var alias = swarming.humanBytes(disk.mb, swarming.MB);
21750 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias));
21751 }.bind(this));
21752 if (this._verbose) {
21753 return aliased.join(" | ");
21754 }
21755 return aliased[0];
21639 }, 21756 },
21640 gpu: function(bot){ 21757 gpu: function(bot){
21641 var gpus = this._dimension(bot, 'gpu') 21758 var gpus = this._attribute(bot, "gpu", "none")
21642 if (!gpus) {
21643 return "none";
21644 }
21645 var verbose = [] 21759 var verbose = []
21646 var named = []; 21760 var named = [];
21647 // non-verbose mode has only the top level GPU info "e.g. NVidia" 21761 // non-verbose mode has only the top level GPU info "e.g. NVidia"
21648 // which is found by looking for gpu ids w/o a colon. 21762 // which is found by looking for gpu ids w/o a colon.
21649 gpus.forEach(function(g){ 21763 gpus.forEach(function(g){
21650 var alias = this._gpuAlias(g); 21764 var alias = this._gpuAlias(g);
21651 if (alias === "UNKNOWN") { 21765 if (alias === "unknown") {
21652 verbose.push(g); 21766 verbose.push(g);
21653 if (g.indexOf(":") === -1) { 21767 if (g.indexOf(":") === -1) {
21654 named.push(g); 21768 named.push(g);
21655 } 21769 }
21656 return; 21770 return;
21657 } 21771 }
21658 verbose.push(this._applyAlias(g, alias)); 21772 verbose.push(this._applyAlias(g, alias));
21659 if (g.indexOf(":") === -1) { 21773 if (g.indexOf(":") === -1) {
21660 named.push(this._applyAlias(g, alias)); 21774 named.push(this._applyAlias(g, alias));
21661 } 21775 }
21662 }.bind(this)) 21776 }.bind(this))
21663 if (this._verbose) { 21777 if (this._verbose) {
21664 return verbose.join(" | "); 21778 return verbose.join(" | ");
21665 } 21779 }
21666 return named.join(" | "); 21780 return named.join(" | ");
21667 }, 21781 },
21668 id: function(bot) { 21782 id: function(bot) {
21669 return bot.bot_id; 21783 return bot.bot_id;
21670 }, 21784 },
21671 os: function(bot) { 21785 os: function(bot) {
21672 var os = this._dimension(bot, 'os') || ['Unknown']; 21786 var os = this._attribute(bot, "os");
21673 if (this._verbose){ 21787 if (this._verbose){
21674 return os.join(" | "); 21788 return os.join(" | ");
21675 } 21789 }
21676 return os[0]; 21790 return os[0];
21677 }, 21791 },
21678 pool: function(bot) { 21792 pool: function(bot) {
21679 var pool = this._dimension(bot, 'pool') || ['Unknown']; 21793 var pool = this._attribute(bot, "pool");
21680 return pool.join(" | "); 21794 return pool.join(" | ");
21681 }, 21795 },
21682 status: function(bot) { 21796 status: function(bot) {
21683 // If a bot is both dead and quarantined, show the deadness over the 21797 // If a bot is both dead and quarantined, show the deadness over the
21684 // quarentinedness. 21798 // quarentinedness.
21685 if (bot.is_dead) { 21799 if (bot.is_dead) {
21686 return "Dead: " + bot.is_dead; 21800 return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) +
21801 " ago";
21687 } 21802 }
21688 if (bot.quarantined) { 21803 if (bot.quarantined) {
21689 return "Quarantined: " + bot.quarantined; 21804 return "Quarantined: " + this._attribute(bot, "quarantined");
21690 } 21805 }
21691 return "Alive"; 21806 return "Alive";
21692 }, 21807 },
21693 task: function(bot){ 21808 task: function(bot){
21694 return this._taskId(bot); 21809 return this._taskId(bot);
21695 }, 21810 },
21696 }; 21811 };
21697 21812
21813 var deviceColumnMap = {
21814 android_devices: function(device) {
21815 var str = this._androidAliasDevice(device);
21816 if (device.okay) {
21817 str = this._applyAlias(this._deviceType(device), str);
21818 }
21819 str += " S/N:";
21820 str += device.serial;
21821 return str;
21822 },
21823 device_os: function(device) {
21824 if (device.build) {
21825 return device.build["build.id"];
21826 }
21827 return "unknown";
21828 },
21829 status: function(device) {
21830 return device.state;
21831 }
21832 }
21833
21834 // specialSort defines any custom sorting rules. By default, a
21835 // naturalCompare of the column content is done.
21836 var specialSort = {
21837 device_type: function(dir, botA, botB) {
21838 // We sort on the number of attached devices. Note that this
21839 // may not be the same as android_devices, because _devices().length
21840 // counts all devices plugged into the bot, whereas android_devices
21841 // counts just devices ready for work.
21842 var botACol = this._devices(botA).length;
21843 var botBCol = this._devices(botB).length;
21844 return dir * swarming.naturalCompare(botACol, botBCol);
21845 },
21846 disk_space: function(dir, botA, botB) {
21847 // We sort based on the raw number of MB of the first disk.
21848 var botACol = botA.disks[0].mb;
21849 var botBCol = botB.disks[0].mb;;
21850 return dir * swarming.naturalCompare(botACol, botBCol);
21851 },
21852 };
21853
21698 Polymer({ 21854 Polymer({
21699 is: 'bot-list', 21855 is: 'bot-list',
21700 behaviors: [SwarmingBehaviors.BotListBehavior], 21856 behaviors: [SwarmingBehaviors.BotListBehavior],
21701 21857
21702 properties: { 21858 properties: {
21703 21859
21704 _bots: { 21860 _bots: {
21705 type: Array, 21861 type: Array,
21706 }, 21862 },
21707 21863
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
21755 _botLink: function(id) { 21911 _botLink: function(id) {
21756 // TODO(kjlubick) Make this point to /newui/ when appropriate. 21912 // TODO(kjlubick) Make this point to /newui/ when appropriate.
21757 return "/restricted/bot/"+id; 21913 return "/restricted/bot/"+id;
21758 }, 21914 },
21759 21915
21760 21916
21761 _column: function(col, bot) { 21917 _column: function(col, bot) {
21762 return columnMap[col].bind(this)(bot); 21918 return columnMap[col].bind(this)(bot);
21763 }, 21919 },
21764 21920
21921 _androidAliasDevice: function(device) {
21922 if (device.notReady) {
21923 return UNAUTHENTICATED.toUpperCase();
21924 }
21925 return this._androidAlias(this._deviceType(device));
21926 },
21927
21765 _deviceColumn: function(col, device) { 21928 _deviceColumn: function(col, device) {
21766 if (col === "devices") { 21929 var f = deviceColumnMap[col];
21767 var str = this._androidAlias(device); 21930 if (!f || !device) {
21768 if (device.okay) { 21931 return "";
21769 str = this._applyAlias(this._deviceType(device), str);
21770 }
21771 str += " S/N:";
21772 str += device.serial;
21773 return str;
21774 } 21932 }
21775 if (col === "status") { 21933 return f.bind(this)(device);
21776 return device.state;
21777 }
21778 return "";
21779 }, 21934 },
21780 21935
21781 _deviceClass: function(device) { 21936 _deviceClass: function(device) {
21782 if (!device.okay) { 21937 if (!device.okay) {
21783 return "bad-device"; 21938 return "bad-device";
21784 } 21939 }
21785 return ""; 21940 return "";
21786 }, 21941 },
21787 21942
21788 _filterAndSort: function(a,b,c) { 21943 _filterAndSort: function(a,b,c) {
(...skipping 22 matching lines...) Expand all
21811 }, 21966 },
21812 21967
21813 _sortBotTable: function(botA, botB) { 21968 _sortBotTable: function(botA, botB) {
21814 if (!this._sort) { 21969 if (!this._sort) {
21815 return 0; 21970 return 0;
21816 } 21971 }
21817 var dir = 1; 21972 var dir = 1;
21818 if (this._sort.direction === "desc") { 21973 if (this._sort.direction === "desc") {
21819 dir = -1; 21974 dir = -1;
21820 } 21975 }
21976 var sort = specialSort[this._sort.name];
21977 if (sort) {
21978 return sort.bind(this)(dir, botA, botB);
21979 }
21980 // Default to a natural compare of the columns.
21821 var botACol = this._column(this._sort.name, botA); 21981 var botACol = this._column(this._sort.name, botA);
21822 var botBCol = this._column(this._sort.name, botB); 21982 var botBCol = this._column(this._sort.name, botB);
21823 21983
21824 return dir * swarming.naturalCompare(botACol, botBCol); 21984 return dir * swarming.naturalCompare(botACol, botBCol);
21825 }, 21985 },
21826 21986
21827 _sortChange: function(e) { 21987 _sortChange: function(e) {
21828 // The event we get from sort-toggle tells us the name of what needs 21988 // The event we get from sort-toggle tells us the name of what needs
21829 // to be sorting and how to sort it. 21989 // to be sorting and how to sort it.
21830 if (!(e && e.detail && e.detail.name)) { 21990 if (!(e && e.detail && e.detail.name)) {
(...skipping 16 matching lines...) Expand all
21847 if (data && data.task_id) { 22007 if (data && data.task_id) {
21848 return "/user/task/" + data.task_id; 22008 return "/user/task/" + data.task_id;
21849 } 22009 }
21850 return undefined; 22010 return undefined;
21851 } 22011 }
21852 22012
21853 }); 22013 });
21854 })(); 22014 })();
21855 </script> 22015 </script>
21856 </dom-module></div></body></html> 22016 </dom-module></div></body></html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698