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

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: Make devices column make more sense 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 var a = ANDROID_ALIASES[dt];
20634 if (!a) {
20635 return UNKNOWN;
20636 }
20637 return a;
20638 },
20639
20640 // _applyAlias is the consistent way to modify a string to show its alias.
20641 _applyAlias: function(orig, alias) {
20642 return alias +" ("+orig+")";
20643 },
20644
20645 // _attribute looks first in dimension and then in state for the
20646 // specified attribute. This will always return an array. If there is
20647 // no matching attribute, ["unknown"] will be returned.
20648 _attribute: function(bot, attr, none) {
20649 none = none || UNKNOWN;
20650 return this._dimension(bot, attr) || this._state(bot, attr) || [none];
20651 },
20652
20653 _devices: function(bot) {
20654 var devices = [];
20655 var d = (bot && bot.state && bot.state.devices) || {};
20656 // state.devices is like {Serial:Object}, so we need to keep the serial
20657 for (key in d) {
20658 var o = d[key];
20659 o.serial = key;
20660 o.okay = (o.state === AVAILABLE);
20661 devices.push(o);
20662 }
20663 return devices;
20664 },
20665
20666 // _deviceType returns the codename of a given Android device.
20667 _deviceType: function(device) {
20668 if (!device || !device.build) {
20669 return UNKNOWN;
20670 }
20671 var t = device.build["build.product"] || device.build["product.board"] | |
20672 device.build["product.device"] || UNKNOWN;
20673 return t.toLowerCase();
20674 },
20675
20676 // _dimension returns the given dimension of a bot. If it is defined, it
20677 // is an array of strings.
20678 _dimension: function(bot, dim) {
20679 if (!bot || !bot.dimensions || !dim) {
20680 return undefined;
20681 }
20682 for (var i = 0; i < bot.dimensions.length; i++) {
20683 if (bot.dimensions[i].key === dim) {
20684 return bot.dimensions[i].value;
20685 }
20686 }
20687 return undefined;
20688 },
20689
20690 _gpuAlias: function(gpu) {
20691 var a = GPU_ALIASES[gpu];
20692 if (!a) {
20693 return UNKNOWN;
20694 }
20695 return a;
20696 },
20697
20698 _not: function(a) {
20699 return !a;
20700 },
20701
20702 _or: function() {
20703 var result = false;
20704 // can't use .foreach, as arguments isn't really a function.
20705 for (var i = 0; i < arguments.length; i++) {
20706 result = result || arguments[i];
20707 }
20708 return result;
20709 },
20710
20711 // _state returns the requested attribute from a bot's state.
20712 // For consistency with _dimension, if the attribute is not an array,
20713 // it is put as theonly element in an array.
20714 _state: function(bot, attr) {
20715 if (!bot || !bot.state || !bot.state[attr]) {
20716 return undefined
20717 }
20718 var state = bot.state[attr];
20719 if (Array.isArray(state)) {
20720 return state;
20721 }
20722 return [state];
20723 },
20724
20725 _taskId: function(bot) {
20726 if (bot && bot.task_id) {
20727 return bot.task_id;
20728 }
20729 return "idle";
20730 },
20731
20732 // _unalias will return the base dimension/state with its alias removed
20733 // if it had one. This is handy for sorting and filtering.
20734 _unalias: function(str) {
20735 var match = ALIAS_REGEXP.exec(str);
20736 if (match) {
20737 return match[1];
20738 }
20739 return str;
20740 },
20741 }
20742 })()
20743 </script>
20557 <dom-module id="bot-filters" assetpath="/res/imp/botlist/"> 20744 <dom-module id="bot-filters" assetpath="/res/imp/botlist/">
20558 <template> 20745 <template>
20559 <style is="custom-style" include="iron-flex iron-flex-alignment iron-positio ning"> 20746 <style is="custom-style" include="iron-flex iron-flex-alignment iron-positio ning">
20560 :host { 20747 :host {
20561 display: block; 20748 display: block;
20562 font-family: sans-serif; 20749 font-family: sans-serif;
20563 } 20750 }
20564 #filter { 20751 #filter {
20565 margin:0 5px; 20752 margin:0 5px;
20566 } 20753 }
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
20685 </div> 20872 </div>
20686 20873
20687 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox> 20874 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox>
20688 </div> 20875 </div>
20689 20876
20690 </div> 20877 </div>
20691 20878
20692 </template> 20879 </template>
20693 <script> 20880 <script>
20694 (function(){ 20881 (function(){
20695 var FILTER_SEP = " | "; 20882 var FILTER_SEP = ":";
20696 // filterMap is a map of primary -> function. The function returns a 20883 // filterMap is a map of primary -> function. The function returns a
20697 // boolean "does the bot (first arg) match the second argument". These 20884 // 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 20885 // functions will have "this" be the botlist, and will have access to all
20699 // functions defined in bot-list and bot-list-shared. 20886 // functions defined in bot-list and bot-list-shared.
20700 var filterMap = { 20887 var filterMap = {
20701 cores: function(bot, cores){ 20888 android_devices: function(bot, num) {
20702 var o = this._cores(bot); 20889 var o = this._attribute(bot, "android_devices", "0");
20890 return o.indexOf(num) !== -1;
20891 },
20892 cores: function(bot, cores) {
20893 var o = this._attribute(bot, "cores");
20703 return o.indexOf(cores) !== -1; 20894 return o.indexOf(cores) !== -1;
20704 }, 20895 },
20705 cpu: function(bot, cpu){ 20896 cpu: function(bot, cpu) {
20706 var o = this._dimension(bot, "cpu") || ["none"]; 20897 var o = this._attribute(bot, "cpu");
20707 return o.indexOf(cpu) !== -1; 20898 return o.indexOf(cpu) !== -1;
20708 }, 20899 },
20709 devices: function(bot, device){ 20900 device_os: function(bot, os) {
20710 if (device === "none") { 20901 var o = this._attribute(bot, "device_os", "none");
20711 return this._devices(bot).length === 0; 20902 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 }, 20903 },
20723 gpu: function(bot, gpu){ 20904 device_type: function(bot, dt) {
20724 var o = this._dimension(bot, "gpu") || ["none"]; 20905 var o = this._attribute(bot, "device_type", "none");
20906 return o.indexOf(this._unalias(dt)) !== -1;
20907 },
20908 disk_space: function(bot, space) {
20909 return true;
20910 },
20911 gpu: function(bot, gpu) {
20912 var o = this._attribute(bot, "gpu", "none");
20725 return o.indexOf(this._unalias(gpu)) !== -1; 20913 return o.indexOf(this._unalias(gpu)) !== -1;
20726 }, 20914 },
20727 id: function(bot, id) { 20915 id: function(bot, id) {
20728 return bot.bot_id === id; 20916 return true;
20729 }, 20917 },
20730 os: function(bot, os){ 20918 os: function(bot, os) {
20731 var o = this._dimension(bot, "os") || ["Unknown"]; 20919 var o = this._attribute(bot, "os");
20732 return o.indexOf(os) !== -1; 20920 return o.indexOf(os) !== -1;
20733 }, 20921 },
20734 pool: function(bot, pool){ 20922 pool: function(bot, pool) {
20735 var o = this._dimension(bot, "pool") || ["Unknown"]; 20923 var o = this._attribute(bot, "pool");
20736 return o.indexOf(pool) !== -1; 20924 return o.indexOf(pool) !== -1;
20737 }, 20925 },
20738 status: function(bot, status){ 20926 status: function(bot, status) {
20739 if (status === "quarantined") { 20927 if (status === "quarantined") {
20740 return bot.quarantined; 20928 return bot.quarantined;
20741 } else if (status === "dead") { 20929 } else if (status === "dead") {
20742 return bot.is_dead; 20930 return bot.is_dead;
20743 } else { 20931 } else {
20744 // Status must be "available". 20932 // Status must be "available".
20745 return !bot.quarantined && !bot.is_dead; 20933 return !bot.quarantined && !bot.is_dead;
20746 } 20934 }
20747 }, 20935 },
20748 task: function(bot, task) { 20936 task: function(bot, task) {
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
20781 }; 20969 };
20782 } 20970 }
20783 } 20971 }
20784 return { 20972 return {
20785 idx: -1, 20973 idx: -1,
20786 }; 20974 };
20787 }; 20975 };
20788 20976
20789 Polymer({ 20977 Polymer({
20790 is: "bot-filters", 20978 is: "bot-filters",
20979
20980 behaviors: [SwarmingBehaviors.BotListBehavior],
20981
20791 properties: { 20982 properties: {
20792 // input 20983 // input
20793 primary_map: { 20984 primary_map: {
20794 type: Object, 20985 type: Object,
20795 }, 20986 },
20796 primary_arr: { 20987 primary_arr: {
20797 type: Array, 20988 type: Array,
20798 }, 20989 },
20799 20990
20800 // output 20991 // output
20801 columns: { 20992 columns: {
20802 type: Array, 20993 type: Array,
20803 value: function() { 20994 value: function() {
20804 // TODO(kjlubick) back these up to URL params and load them from 20995 // TODO(kjlubick) back these up to URL params and load them from
20805 // there on reload. 20996 // there on reload.
20806 return ["id","os","task","status"]; 20997 return ["id","os","task","status"];
20807 }, 20998 },
20808 notify: true, 20999 notify: true,
20809 }, 21000 },
21001 dimensions: {
21002 type: Array,
21003 computed: "_extractDimensions(DIMENSIONS.*,_filters.*)",
21004 notify: true,
21005 },
20810 filter: { 21006 filter: {
20811 type: Object, 21007 type: Object,
20812 computed: "_makeFilter(_filters.*)", 21008 computed: "_makeFilter(_filters.*)",
20813 notify: true, 21009 notify: true,
20814 }, 21010 },
20815 verbose: { 21011 verbose: {
20816 type: Boolean, 21012 type: Boolean,
20817 value: false, 21013 value: false,
20818 notify: true, 21014 notify: true,
20819 }, 21015 },
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after
21025 }, 21221 },
21026 21222
21027 _afterBold: function(item, query) { 21223 _afterBold: function(item, query) {
21028 var match = matchPartCaseInsensitive(item, query); 21224 var match = matchPartCaseInsensitive(item, query);
21029 if (match.idx === -1) { 21225 if (match.idx === -1) {
21030 return ""; 21226 return "";
21031 } 21227 }
21032 return item.substring(match.idx + match.part.length); 21228 return item.substring(match.idx + match.part.length);
21033 }, 21229 },
21034 21230
21231 _extractDimensions: function() {
21232 var dims = []
21233 this._filters.forEach(function(f) {
21234 var split = f.split(FILTER_SEP, 1)
21235 var col = split[0];
21236 if (this.DIMENSIONS.indexOf(col) !== -1) {
21237 var rest = f.substring(col.length + FILTER_SEP.length);
21238 dims.push(col + FILTER_SEP + this._unalias(rest))
21239 };
21240 }.bind(this));
21241 return dims;
21242 }
21243
21035 }); 21244 });
21036 })(); 21245 })();
21037 </script> 21246 </script>
21038 </dom-module><script> 21247 </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> 21248 <template>
21193 <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth _headers]]" handle-as="json" last-response="{{_list}}" loading="{{_busy1}}"> 21249 <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> 21250 </iron-ajax>
21195 21251
21196 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_ headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy2}}"> 21252 <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" header s="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{ {_busy2}}">
21253 </iron-ajax>
21254
21255 <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> 21256 </iron-ajax>
21198 </template> 21257 </template>
21199 <script> 21258 <script>
21200 (function(){ 21259 (function(){
21201 // TODO(kjlubick): Add more of these as well as things from state 21260
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({ 21261 Polymer({
21208 is: 'bot-list-data', 21262 is: 'bot-list-data',
21209 21263
21210 behaviors: [SwarmingBehaviors.BotListBehavior], 21264 behaviors: [SwarmingBehaviors.BotListBehavior],
21211 21265
21212 properties: { 21266 properties: {
21213 // inputs 21267 // inputs
21214 auth_headers: { 21268 auth_headers: {
21215 type: Object, 21269 type: Object,
21216 observer: "signIn", 21270 observer: "signIn",
21217 }, 21271 },
21272 dimensions: {
21273 type: Array,
21274 },
21218 21275
21219 //outputs 21276 //outputs
21220 bots: { 21277 bots: {
21221 type: Array, 21278 type: Array,
21222 computed: "_bots(_list)", 21279 computed: "_bots(_list)",
21223 notify: true, 21280 notify: true,
21224 }, 21281 },
21225 busy: { 21282 busy: {
21226 type: Boolean, 21283 type: Boolean,
21227 computed: "_or(_busy1,_busy2)", 21284 computed: "_or(_busy1,_busy2,_busy3)",
21228 notify: true, 21285 notify: true,
21229 }, 21286 },
21230 fleet: { 21287 fleet: {
21231 type: Object, 21288 type: Object,
21232 computed: "_fleet(_count)", 21289 computed: "_fleet(_count)",
21233 notify: true, 21290 notify: true,
21234 }, 21291 },
21235 primary_map: { 21292 primary_map: {
21236 type:Object, 21293 type:Object,
21237 computed: "_primaryMap(bots)", 21294 computed: "_primaryMap(_dimensions)",
21238 notify: true, 21295 notify: true,
21239 }, 21296 },
21240 primary_arr: { 21297 primary_arr: {
21241 type:Array, 21298 type: Array,
21242 value: function() { 21299 // DIMENSIONS and BOT_PROPERTIES are inherited from BotListBehavior
21243 return DIMENSIONS.concat(BOT_PROPERTIES); 21300 computed: "_primaryArr(DIMENSIONS, BOT_PROPERTIES)",
21244 },
21245 notify: true, 21301 notify: true,
21246 }, 21302 },
21247 21303
21248 // private 21304 // private
21249 _count: { 21305 _count: {
21250 type: Object, 21306 type: Object,
21251 }, 21307 },
21308 _dimensions: {
21309 type: Object,
21310 },
21252 _list: { 21311 _list: {
21253 type: Object, 21312 type: Object,
21254 }, 21313 },
21255 }, 21314 },
21256 21315
21257 signIn: function(){ 21316 signIn: function(){
21258 this.$.botlist.generateRequest(); 21317 this.$.botlist.auto = true;
21259 this.$.fleet.generateRequest(); 21318 this.$.dimensions.auto = true;
21319 this.$.fleet.auto = true;
21320 },
21321
21322 _botlistParams: function() {
21323 if (!this.dimensions) {
21324 return {};
21325 }
21326 return {
21327 dimensions: this.dimensions,
21328 };
21260 }, 21329 },
21261 21330
21262 _bots: function(){ 21331 _bots: function(){
21263 if (!this._list || !this._list.items) { 21332 if (!this._list || !this._list.items) {
21264 return []; 21333 return [];
21265 } 21334 }
21266 this._list.items.forEach(function(o){ 21335 // Do any preprocessing here
21267 o.state = JSON.parse(o.state); 21336 this._list.items.forEach(function(bot){
21337 // Parse the state, which is a JSON string. This contains a lot of
21338 // interesting information like details about the devices attached.
21339 bot.state = JSON.parse(bot.state);
21340 // get the disks in an easier to deal with format, sorted by size.
21341 var disks = bot.state["disks"];
21342 var keys = Object.keys(disks);
21343 if (!keys || !keys.length) {
21344 bot.disks = [{"id": "unknown", "mb": 0}];
21345 } else {
21346 bot.disks = [];
21347 for (var i = 0; i < keys.length; i++) {
21348 bot.disks.push({"id":keys[i], "mb":disks[keys[i]].free_mb});
21349 }
21350 // Sort these so the biggest disk comes first.
21351 bot.disks.sort(function(a, b) {
21352 return b.mb - a.mb;
21353 });
21354 }
21355
21268 }); 21356 });
21269 return this._list.items; 21357 return this._list.items;
21270 }, 21358 },
21271 21359
21272 _fleet: function() { 21360 _fleet: function() {
21273 if (!this._count) { 21361 if (!this._count) {
21274 return {}; 21362 return {};
21275 } 21363 }
21276 return { 21364 return {
21277 alive: this._count.count || -1, 21365 alive: this._count.count || -1,
21278 busy: this._count.busy || -1, 21366 busy: this._count.busy || -1,
21279 idle: this._count.count && this._count.busy && 21367 idle: this._count.count && this._count.busy &&
21280 this._count.count - this._count.busy, 21368 this._count.count - this._count.busy,
21281 dead: this._count.dead || -1, 21369 dead: this._count.dead || -1,
21282 quarantined: this._count.quarantined || -1, 21370 quarantined: this._count.quarantined || -1,
21283 } 21371 }
21284 }, 21372 },
21285 21373
21286 _primaryMap: function(bots){ 21374 _primaryArr: function(dimensions, properties) {
21375 return dimensions.concat(properties);
21376 },
21377 _primaryMap: function(dimensions){
21287 // map will keep track of dimensions that we have seen at least once. 21378 // 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 21379 // This will then basically get turned into an array to be used for
21289 // filtering. 21380 // filtering.
21290 var map = {}; 21381 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 21382
21304 // Add Android devices and their aliases 21383 var pMap = {};
21305 this._devices(b).forEach(function(d){ 21384 dimensions.forEach(function(d){
21306 var dt = this._deviceType(d); 21385 if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) {
21307 var alias = this._androidAlias(d); 21386 // value is an array of the values the
21308 if (dt !== "unknown") { 21387 pMap[d.key] = d.value;
21309 dt = this._applyAlias(dt,alias); 21388 } else if (d.key === "gpu") {
21310 } 21389 var gpus = [];
21311 map["devices"][dt] = true; 21390 d.value.forEach(function(g){
21312 }.bind(this)); 21391 var alias = this._gpuAlias(g);
21313 map["devices"]["none"] = true; 21392 if (alias !== "unknown") {
21314 21393 gpus.push(this._applyAlias(g, alias));
21315 // Add GPUs and their aliases 21394 } else {
21316 var gpus = this._dimension(b, "gpu") || []; 21395 gpus.push(g);
21317 gpus.forEach(function(g){ 21396 }
21318 var alias = this._gpuAlias(g); 21397 }.bind(this));
21319 if (alias !== "UNKNOWN") { 21398 pMap["gpu"] = gpus;
21320 map["gpu"][this._applyAlias(g, alias)] = true; 21399 } else if (d.key === "device_type") {
21321 } else { 21400 var devs = [];
21322 map["gpu"][g] = true; 21401 d.value.forEach(function(dt){
21323 } 21402 var alias = this._androidAlias(dt);
21324 21403 if (alias !== "unknown") {
21325 }.bind(this)); 21404 devs.push(this._applyAlias(dt, alias));
21405 } else {
21406 devs.push(dt);
21407 }
21408 }.bind(this));
21409 pMap["device_type"] = devs;
21410 } else {
21411 console.log("Unknown alias type: ", d);
21412 }
21326 }.bind(this)); 21413 }.bind(this));
21327 21414
21328 // Turn the Map<Object,Map<Boolean>> into a Map<Object,Array<String>> 21415 // Add some options that might not show up.
21329 // with all of the secondary elements sorted appropriately. 21416 pMap["android_devices"].push("0");
21330 var pMap = {}; 21417 pMap["device_os"].push("none");
21331 for (key in map){ 21418 pMap["device_type"].push("none");
21332 pMap[key] = Object.keys(map[key]).sort(swarming.naturalCompare); 21419
21333 } 21420 pMap["id"] = [];
21334 21421
21335 // Create custom filter options 21422 // Create custom filter options
21423 pMap["disk_space"] = [];
21336 pMap["task"] = ["busy", "idle"]; 21424 pMap["task"] = ["busy", "idle"];
21337 pMap["status"] = ["available", "dead", "quarantined"]; 21425 pMap["status"] = ["available", "dead", "quarantined"];
21426
21427 // No need to sort any of this, bot-filters sorts secondary items
21428 // automatically, especially when the user types a query.
21338 return pMap; 21429 return pMap;
21339 }, 21430 },
21340 21431
21341 }); 21432 });
21342 })(); 21433 })();
21343 </script> 21434 </script>
21344 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/"> 21435 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/">
21345 <template> 21436 <template>
21346 <style include="swarming-app-style"> 21437 <style include="swarming-app-style">
21347 :host { 21438 :host {
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after
21518 </style> 21609 </style>
21519 21610
21520 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu sy="[[_busy]]" name="Swarming Bot List"> 21611 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu sy="[[_busy]]" name="Swarming Bot List">
21521 21612
21522 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> 21613 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
21523 21614
21524 <div hidden$="[[_not(_signed_in)]]"> 21615 <div hidden$="[[_not(_signed_in)]]">
21525 21616
21526 <div class="horizontal layout"> 21617 <div class="horizontal layout">
21527 21618
21528 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar r]]" columns="{{_columns}}" filter="{{_filter}}" verbose="{{_verbose}}"> 21619 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar r]]" columns="{{_columns}}" dimensions="{{_dimensions}}" filter="{{_filter}}" ve rbose="{{_verbose}}">
21529 </bot-filters> 21620 </bot-filters>
21530 21621
21531 <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedB ots]]"> 21622 <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedB ots]]">
21532 </bot-list-summary> 21623 </bot-list-summary>
21533 21624
21534 </div> 21625 </div>
21535 21626
21536 <bot-list-data auth_headers="[[_auth_headers]]" bots="{{_bots}}" busy="{ {_busy}}" fleet="{{_fleet}}" primary_map="{{_primary_map}}" primary_arr="{{_prim ary_arr}}"> 21627 <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> 21628 </bot-list-data>
21538 21629
21539 <table class="bot-list"> 21630 <table class="bot-list">
21540 <thead on-sort_change="_sortChange"> 21631 <thead on-sort_change="_sortChange">
21541 21632
21542 <tr> 21633 <tr>
21543 <th> 21634 <th>
21544 <span>Bot Id</span> 21635 <span>Bot Id</span>
21545 <sort-toggle name="id" current="[[_sort]]"> 21636 <sort-toggle name="id" current="[[_sort]]">
21546 </sort-toggle> 21637 </sort-toggle>
(...skipping 28 matching lines...) Expand all
21575 </td> 21666 </td>
21576 21667
21577 <template is="dom-repeat" items="[[_plain_columns]]" as="c"> 21668 <template is="dom-repeat" items="[[_plain_columns]]" as="c">
21578 <td hidden$="[[_hide(c)]]"> 21669 <td hidden$="[[_hide(c)]]">
21579 [[_column(c, bot, _verbose)]] 21670 [[_column(c, bot, _verbose)]]
21580 </td> 21671 </td>
21581 </template> 21672 </template>
21582 21673
21583 </tr> 21674 </tr>
21584 <template is="dom-repeat" items="[[_devices(bot)]]" as="device"> 21675 <template is="dom-repeat" items="[[_devices(bot)]]" as="device">
21585 <tr hidden$="[[_hide('devices', _columns.*)]]" class$="[[_device Class(device)]]"> 21676 <tr hidden$="[[_hide('android_devices', _columns.*)]]" class$="[ [_deviceClass(device)]]">
21586 <td></td> 21677 <td></td>
21587 <td hidden$="[[_hide('task', _columns.*)]]"></td> 21678 <td hidden$="[[_hide('task', _columns.*)]]"></td>
21588 <template is="dom-repeat" items="[[_plain_columns]]" as="c"> 21679 <template is="dom-repeat" items="[[_plain_columns]]" as="c">
21589 <td hidden$="[[_hide(c)]]"> 21680 <td hidden$="[[_hide(c)]]">
21590 [[_deviceColumn(c, device, _verbose)]] 21681 [[_deviceColumn(c, device, _verbose)]]
21591 </td> 21682 </td>
21592 </template> 21683 </template>
21593 </tr> 21684 </tr>
21594 </template> 21685 </template>
21595 </template> 21686 </template>
21596 </tbody> 21687 </tbody>
21597 </table> 21688 </table>
21598 </div> 21689 </div>
21599 21690
21600 </swarming-app> 21691 </swarming-app>
21601 21692
21602 </template> 21693 </template>
21603 <script> 21694 <script>
21604 (function(){ 21695 (function(){
21605 var special_columns = ["id", "task"]; 21696 var special_columns = ["id", "task"];
21606 21697
21607 var headerMap = { 21698 var headerMap = {
21608 // "id" and "task" are special, so they don't go here and have their 21699 // "id" and "task" are special, so they don't go here and have their
21609 // headers hard-coded below. 21700 // headers hard-coded below.
21701 "android_devices": "Android Devices",
21610 "cores": "Cores", 21702 "cores": "Cores",
21611 "cpu": "CPU", 21703 "cpu": "CPU",
21612 "devices": "Devices", 21704 "device_os": "Device OS",
21705 "device_type": "Device Type",
21706 "disk_space": "Free Space (MB)",
21613 "gpu": "GPU", 21707 "gpu": "GPU",
21614 "os": "OS", 21708 "os": "OS",
21615 "pool": "Pool", 21709 "pool": "Pool",
21616 "status": "Status", 21710 "status": "Status",
21617 }; 21711 };
21618 21712
21619 // This maps column name to a function that will return the content for a 21713 // 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 21714 // given bot. These functions are bound to this element, and have access
21621 // to all functions defined here and in bot-list-shared. 21715 // to all functions defined here and in bot-list-shared.
21622 var columnMap = { 21716 var columnMap = {
21717 android_devices: function(bot) {
21718 var devs = this._attribute(bot, "android_devices", "0");
21719 if (this._verbose) {
21720 return devs.join(" | ") + " devices available";
21721 }
21722 // max() works on strings as long as they can be coerced to Number.
21723 return Math.max(...devs) + " devices available";
21724 },
21623 cores: function(bot){ 21725 cores: function(bot){
21624 var cores = this._cores(bot); 21726 var cores = this._attribute(bot, "cores");
21625 if (this._verbose){ 21727 if (this._verbose){
21626 return cores.join(" | "); 21728 return cores.join(" | ");
21627 } 21729 }
21628 return cores[0]; 21730 return cores[0];
21629 }, 21731 },
21630 cpu: function(bot){ 21732 cpu: function(bot){
21631 var cpus = this._dimension(bot, 'cpu') || ['Unknown']; 21733 var cpus = this._attribute(bot, "cpu");
21632 if (this._verbose){ 21734 if (this._verbose){
21633 return cpus.join(" | "); 21735 return cpus.join(" | ");
21634 } 21736 }
21635 return cpus[0]; 21737 return cpus[0];
21636 }, 21738 },
21637 devices: function(bot){ 21739 device_os: function(bot){
21638 return this._devices(bot).length + " devices attached"; 21740 var os = this._attribute(bot, "device_os", "none");
21741 if (this._verbose) {
21742 return os.join(" | ");
21743 }
21744 return os[0];
21745 },
21746 device_type: function(bot){
21747 var dt = this._attribute(bot, "device_type", "none");
21748 if (this._verbose) {
21749 return dt.join(" | ");
21750 }
21751 return dt[0];
21752 },
21753 disk_space: function(bot) {
21754 var aliased = [];
21755 bot.disks.forEach(function(disk){
21756 var alias = swarming.humanBytes(disk.mb, swarming.MB);
21757 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias));
21758 }.bind(this));
21759 if (this._verbose) {
21760 return aliased.join(" | ");
21761 }
21762 return aliased[0];
21639 }, 21763 },
21640 gpu: function(bot){ 21764 gpu: function(bot){
21641 var gpus = this._dimension(bot, 'gpu') 21765 var gpus = this._attribute(bot, "gpu", "none")
21642 if (!gpus) {
21643 return "none";
21644 }
21645 var verbose = [] 21766 var verbose = []
21646 var named = []; 21767 var named = [];
21647 // non-verbose mode has only the top level GPU info "e.g. NVidia" 21768 // 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. 21769 // which is found by looking for gpu ids w/o a colon.
21649 gpus.forEach(function(g){ 21770 gpus.forEach(function(g){
21650 var alias = this._gpuAlias(g); 21771 var alias = this._gpuAlias(g);
21651 if (alias === "UNKNOWN") { 21772 if (alias === "unknown") {
21652 verbose.push(g); 21773 verbose.push(g);
21653 if (g.indexOf(":") === -1) { 21774 if (g.indexOf(":") === -1) {
21654 named.push(g); 21775 named.push(g);
21655 } 21776 }
21656 return; 21777 return;
21657 } 21778 }
21658 verbose.push(this._applyAlias(g, alias)); 21779 verbose.push(this._applyAlias(g, alias));
21659 if (g.indexOf(":") === -1) { 21780 if (g.indexOf(":") === -1) {
21660 named.push(this._applyAlias(g, alias)); 21781 named.push(this._applyAlias(g, alias));
21661 } 21782 }
21662 }.bind(this)) 21783 }.bind(this))
21663 if (this._verbose) { 21784 if (this._verbose) {
21664 return verbose.join(" | "); 21785 return verbose.join(" | ");
21665 } 21786 }
21666 return named.join(" | "); 21787 return named.join(" | ");
21667 }, 21788 },
21668 id: function(bot) { 21789 id: function(bot) {
21669 return bot.bot_id; 21790 return bot.bot_id;
21670 }, 21791 },
21671 os: function(bot) { 21792 os: function(bot) {
21672 var os = this._dimension(bot, 'os') || ['Unknown']; 21793 var os = this._attribute(bot, "os");
21673 if (this._verbose){ 21794 if (this._verbose){
21674 return os.join(" | "); 21795 return os.join(" | ");
21675 } 21796 }
21676 return os[0]; 21797 return os[0];
21677 }, 21798 },
21678 pool: function(bot) { 21799 pool: function(bot) {
21679 var pool = this._dimension(bot, 'pool') || ['Unknown']; 21800 var pool = this._attribute(bot, "pool");
21680 return pool.join(" | "); 21801 return pool.join(" | ");
21681 }, 21802 },
21682 status: function(bot) { 21803 status: function(bot) {
21683 // If a bot is both dead and quarantined, show the deadness over the 21804 // If a bot is both dead and quarantined, show the deadness over the
21684 // quarentinedness. 21805 // quarentinedness.
21685 if (bot.is_dead) { 21806 if (bot.is_dead) {
21686 return "Dead: " + bot.is_dead; 21807 return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) +
21808 " ago";
21687 } 21809 }
21688 if (bot.quarantined) { 21810 if (bot.quarantined) {
21689 return "Quarantined: " + bot.quarantined; 21811 return "Quarantined: " + this._attribute(bot, "quarantined");
21690 } 21812 }
21691 return "Alive"; 21813 return "Alive";
21692 }, 21814 },
21693 task: function(bot){ 21815 task: function(bot){
21694 return this._taskId(bot); 21816 return this._taskId(bot);
21695 }, 21817 },
21696 }; 21818 };
21697 21819
21820 var deviceColumnMap = {
21821 android_devices: function(device) {
21822 var str = this._androidAliasDevice(device);
21823 if (device.okay) {
21824 str = this._applyAlias(this._deviceType(device), str);
21825 }
21826 str += " S/N:";
21827 str += device.serial;
21828 return str;
21829 },
21830 device_os: function(device) {
21831 if (device.build) {
21832 return device.build["build.id"];
21833 }
21834 return "unknown";
21835 },
21836 status: function(device) {
21837 return device.state;
21838 }
21839 }
21840
21841 // specialSort defines any custom sorting rules. By default, a
21842 // naturalCompare of the column content is done.
21843 var specialSort = {
21844 device_type: function(dir, botA, botB) {
21845 // We sort on the number of attached devices. Note that this
21846 // may not be the same as android_devices, because _devices().length
21847 // counts all devices plugged into the bot, whereas android_devices
21848 // counts just devices ready for work.
21849 var botACol = this._devices(botA).length;
21850 var botBCol = this._devices(botB).length;
21851 return dir * swarming.naturalCompare(botACol, botBCol);
21852 },
21853 disk_space: function(dir, botA, botB) {
21854 // We sort based on the raw number of MB of the first disk.
21855 var botACol = botA.disks[0].mb;
21856 var botBCol = botB.disks[0].mb;;
21857 return dir * swarming.naturalCompare(botACol, botBCol);
21858 },
21859 };
21860
21698 Polymer({ 21861 Polymer({
21699 is: 'bot-list', 21862 is: 'bot-list',
21700 behaviors: [SwarmingBehaviors.BotListBehavior], 21863 behaviors: [SwarmingBehaviors.BotListBehavior],
21701 21864
21702 properties: { 21865 properties: {
21703 21866
21704 _bots: { 21867 _bots: {
21705 type: Array, 21868 type: Array,
21706 }, 21869 },
21707 21870
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
21755 _botLink: function(id) { 21918 _botLink: function(id) {
21756 // TODO(kjlubick) Make this point to /newui/ when appropriate. 21919 // TODO(kjlubick) Make this point to /newui/ when appropriate.
21757 return "/restricted/bot/"+id; 21920 return "/restricted/bot/"+id;
21758 }, 21921 },
21759 21922
21760 21923
21761 _column: function(col, bot) { 21924 _column: function(col, bot) {
21762 return columnMap[col].bind(this)(bot); 21925 return columnMap[col].bind(this)(bot);
21763 }, 21926 },
21764 21927
21928 _androidAliasDevice: function(device) {
21929 if (device.notReady) {
21930 return UNAUTHENTICATED.toUpperCase();
21931 }
21932 return this._androidAlias(this._deviceType(device));
21933 },
21934
21765 _deviceColumn: function(col, device) { 21935 _deviceColumn: function(col, device) {
21766 if (col === "devices") { 21936 var f = deviceColumnMap[col];
21767 var str = this._androidAlias(device); 21937 if (!f || !device) {
21768 if (device.okay) { 21938 return "";
21769 str = this._applyAlias(this._deviceType(device), str);
21770 }
21771 str += " S/N:";
21772 str += device.serial;
21773 return str;
21774 } 21939 }
21775 if (col === "status") { 21940 return f.bind(this)(device);
21776 return device.state;
21777 }
21778 return "";
21779 }, 21941 },
21780 21942
21781 _deviceClass: function(device) { 21943 _deviceClass: function(device) {
21782 if (!device.okay) { 21944 if (!device.okay) {
21783 return "bad-device"; 21945 return "bad-device";
21784 } 21946 }
21785 return ""; 21947 return "";
21786 }, 21948 },
21787 21949
21788 _filterAndSort: function(a,b,c) { 21950 _filterAndSort: function(a,b,c) {
(...skipping 22 matching lines...) Expand all
21811 }, 21973 },
21812 21974
21813 _sortBotTable: function(botA, botB) { 21975 _sortBotTable: function(botA, botB) {
21814 if (!this._sort) { 21976 if (!this._sort) {
21815 return 0; 21977 return 0;
21816 } 21978 }
21817 var dir = 1; 21979 var dir = 1;
21818 if (this._sort.direction === "desc") { 21980 if (this._sort.direction === "desc") {
21819 dir = -1; 21981 dir = -1;
21820 } 21982 }
21983 var sort = specialSort[this._sort.name];
21984 if (sort) {
21985 return sort.bind(this)(dir, botA, botB);
21986 }
21987 // Default to a natural compare of the columns.
21821 var botACol = this._column(this._sort.name, botA); 21988 var botACol = this._column(this._sort.name, botA);
21822 var botBCol = this._column(this._sort.name, botB); 21989 var botBCol = this._column(this._sort.name, botB);
21823 21990
21824 return dir * swarming.naturalCompare(botACol, botBCol); 21991 return dir * swarming.naturalCompare(botACol, botBCol);
21825 }, 21992 },
21826 21993
21827 _sortChange: function(e) { 21994 _sortChange: function(e) {
21828 // The event we get from sort-toggle tells us the name of what needs 21995 // The event we get from sort-toggle tells us the name of what needs
21829 // to be sorting and how to sort it. 21996 // to be sorting and how to sort it.
21830 if (!(e && e.detail && e.detail.name)) { 21997 if (!(e && e.detail && e.detail.name)) {
(...skipping 16 matching lines...) Expand all
21847 if (data && data.task_id) { 22014 if (data && data.task_id) {
21848 return "/user/task/" + data.task_id; 22015 return "/user/task/" + data.task_id;
21849 } 22016 }
21850 return undefined; 22017 return undefined;
21851 } 22018 }
21852 22019
21853 }); 22020 });
21854 })(); 22021 })();
21855 </script> 22022 </script>
21856 </dom-module></div></body></html> 22023 </dom-module></div></body></html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698