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

Side by Side Diff: appengine/swarming/elements/res/imp/botlist/bot-list.html

Issue 2241413002: Refactor out reusable pieces from new Botlist (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@master
Patch Set: Address comments 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 <!-- 1 <!--
2 Copyright 2016 The LUCI Authors. All rights reserved. 2 Copyright 2016 The LUCI Authors. All rights reserved.
3 Use of this source code is governed under the Apache License, Version 2.0 3 Use of this source code is governed under the Apache License, Version 2.0
4 that can be found in the LICENSE file. 4 that can be found in the LICENSE file.
5 5
6 This in an HTML Import-able file that contains the definition 6 This in an HTML Import-able file that contains the definition
7 of the following elements: 7 of the following elements:
8 8
9 <bot-list> 9 <bot-list>
10 10
11 bot-list creats a dynamic table for viewing swarming bots. Columns can be 11 bot-list creats a dynamic table for viewing swarming bots. Columns can be
12 dynamically filtered and it supports client-side filtering. 12 dynamically filtered and it supports client-side filtering.
13 13
14 This is a top-level element. 14 This is a top-level element.
15 15
16 Properties: 16 Properties:
17 client_id: String, will be set by server-side template evaluation. 17 client_id: String, will be set by server-side template evaluation.
18 18
19 Methods: 19 Methods:
20 None. 20 None.
21 21
22 Events: 22 Events:
23 None. 23 None.
24 --> 24 -->
25 25
26 <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-la yout-classes.html"> 26 <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-la yout-classes.html">
27 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> 27 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
28 28
29 <link rel="import" href="/res/imp/common/dynamic-table.html">
29 <link rel="import" href="/res/imp/common/sort-toggle.html"> 30 <link rel="import" href="/res/imp/common/sort-toggle.html">
30 <link rel="import" href="/res/imp/common/swarming-app.html"> 31 <link rel="import" href="/res/imp/common/swarming-app.html">
31 <link rel="import" href="/res/imp/common/url-param.html"> 32 <link rel="import" href="/res/imp/common/url-param.html">
32 33
33 <link rel="import" href="bot-filters.html"> 34 <link rel="import" href="bot-filters.html">
34 <link rel="import" href="bot-list-data.html"> 35 <link rel="import" href="bot-list-data.html">
35 <link rel="import" href="bot-list-shared.html"> 36 <link rel="import" href="bot-list-shared.html">
36 <link rel="import" href="bot-list-summary.html"> 37 <link rel="import" href="bot-list-summary.html">
37 38
38 <dom-module id="bot-list"> 39 <dom-module id="bot-list">
39 <template> 40 <template>
40 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style"> 41 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style dynamic-table-style">
41 bot-filters, bot-list-summary { 42 bot-filters, bot-list-summary {
42 margin-bottom: 8px; 43 margin-bottom: 8px;
43 margin-right: 10px; 44 margin-right: 10px;
44 } 45 }
45 .bot {
46 margin:5px;
47 max-width:400px;
48 min-height:100px;
49 min-width:300px;
50 }
51 table {
52 border-collapse: collapse;
53 margin-left: 5px;
54 }
55 td, th {
56 border: 1px solid #DDD;
57 padding: 5px;
58 }
59
60 .quarantined, .bad-device { 46 .quarantined, .bad-device {
61 background-color: #ffdddd; 47 background-color: #ffdddd;
62 } 48 }
63 .dead { 49 .dead {
64 background-color: #cccccc; 50 background-color: #cccccc;
65 } 51 }
66
67 th {
68 position: relative;
69 }
70 sort-toggle {
71 position: absolute;
72 right: 0;
73 top: 0.4em;
74 }
75 .bot-list th > span { 52 .bot-list th > span {
76 /* Leave space for sort-toggle*/ 53 /* Leave space for sort-toggle*/
77 padding-right: 30px; 54 padding-right: 30px;
78 } 55 }
79 </style> 56 </style>
80 57
81 <url-param name="sort" 58 <url-param name="sort"
82 value="{{_sortstr}}" 59 value="{{_sortstr}}"
83 default_value="id:asc"> 60 default_value="id:asc">
84 </url-param> 61 </url-param>
(...skipping 19 matching lines...) Expand all
104 81
105 columns="{{_columns}}" 82 columns="{{_columns}}"
106 query_params="{{_query_params}}" 83 query_params="{{_query_params}}"
107 filter="{{_filter}}" 84 filter="{{_filter}}"
108 verbose="{{_verbose}}"> 85 verbose="{{_verbose}}">
109 </bot-filters> 86 </bot-filters>
110 87
111 <bot-list-summary 88 <bot-list-summary
112 columns="[[_columns]]" 89 columns="[[_columns]]"
113 fleet="[[_fleet]]" 90 fleet="[[_fleet]]"
114 filtered_bots="[[_filteredSortedBots]]" 91 filtered_bots="[[_filteredSortedItems]]"
115 sort="[[_sortstr]]" 92 sort="[[_sortstr]]"
116 verbose="[[_verbose]]"> 93 verbose="[[_verbose]]">
117 </bot-list-summary> 94 </bot-list-summary>
118 95
119 </div> 96 </div>
120 97
121 <bot-list-data 98 <bot-list-data
122 auth_headers="[[_auth_headers]]" 99 auth_headers="[[_auth_headers]]"
123 query_params="[[_query_params]]" 100 query_params="[[_query_params]]"
124 101
125 bots="{{_bots}}" 102 bots="{{_items}}"
126 busy="{{_busy}}" 103 busy="{{_busy}}"
127 dimensions="{{_dimensions}}" 104 dimensions="{{_dimensions}}"
128 fleet="{{_fleet}}" 105 fleet="{{_fleet}}"
129 primary_map="{{_primary_map}}" 106 primary_map="{{_primary_map}}"
130 primary_arr="{{_primary_arr}}"> 107 primary_arr="{{_primary_arr}}">
131 </bot-list-data> 108 </bot-list-data>
132 109
133 <table class="bot-list"> 110 <table class="bot-list">
134 <thead on-sort_change="_sortChange"> 111 <thead on-sort_change="_sortChange">
135 <!-- To allow for dynamic columns without having a lot of copy-pasted 112 <!-- To allow for dynamic columns without having a lot of copy-pasted
(...skipping 18 matching lines...) Expand all
154 --> 131 -->
155 <th hidden$="[[_hide('task', _columns.*)]]"> 132 <th hidden$="[[_hide('task', _columns.*)]]">
156 <span>Current Task</span> 133 <span>Current Task</span>
157 <sort-toggle 134 <sort-toggle
158 name="task" 135 name="task"
159 current="[[_sort]]"> 136 current="[[_sort]]">
160 </sort-toggle> 137 </sort-toggle>
161 </th> 138 </th>
162 139
163 <template is="dom-repeat" 140 <template is="dom-repeat"
164 items="[[_plain_columns]]" 141 items="[[_plainColumns]]"
165 as="c"> 142 as="c">
166 <th hidden$="[[_hide(c)]]"> 143 <th hidden$="[[_hide(c)]]">
167 <span>[[_header(c)]]</span> 144 <span>[[_header(c)]]</span>
168 <sort-toggle 145 <sort-toggle
169 name="[[c]]" 146 name="[[c]]"
170 current="[[_sort]]"> 147 current="[[_sort]]">
171 </sort-toggle> 148 </sort-toggle>
172 </th> 149 </th>
173 </template> 150 </template>
174 </tr> 151 </tr>
175 </thead> 152 </thead>
176 <tbody> 153 <tbody>
177 <template id="bot_table" is="dom-repeat" 154 <template id="bot_table" is="dom-repeat"
178 items="[[_filteredSortedBots]]" 155 items="[[_filteredSortedItems]]"
179 as="bot" 156 as="bot"
180 initial-count=50> 157 initial-count=50>
181 158
182 <tr class$="[[_botClass(bot)]]"> 159 <tr class$="[[_botClass(bot)]]">
183 <td> 160 <td>
184 <a class="center" 161 <a class="center"
185 href$="[[_botLink(bot.bot_id)]]" 162 href$="[[_botLink(bot.bot_id)]]"
186 target="_blank"> 163 target="_blank">
187 [[bot.bot_id]] 164 [[bot.bot_id]]
188 </a> 165 </a>
189 </td> 166 </td>
190 <td hidden$="[[_hide('task', _columns.*)]]"> 167 <td hidden$="[[_hide('task', _columns.*)]]">
191 <a href$="[[_taskLink(bot)]]">[[_taskId(bot)]]</a> 168 <a href$="[[_taskLink(bot)]]">[[_taskId(bot)]]</a>
192 </td> 169 </td>
193 170
194 <template is="dom-repeat" 171 <template is="dom-repeat"
195 items="[[_plain_columns]]" 172 items="[[_plainColumns]]"
196 as="c"> 173 as="c">
197 <td hidden$="[[_hide(c)]]"> 174 <td hidden$="[[_hide(c)]]">
198 [[_column(c, bot, _verbose)]] 175 [[_column(c, bot, _verbose)]]
199 </td> 176 </td>
200 </template> 177 </template>
201 178
202 </tr> 179 </tr>
203 <template is="dom-repeat" 180 <template is="dom-repeat"
204 items="[[_devices(bot)]]" 181 items="[[_devices(bot)]]"
205 as="device"> 182 as="device">
206 <tr hidden$="[[_hide('android_devices', _columns.*)]]" 183 <tr hidden$="[[_hide('android_devices', _columns.*)]]"
207 class$="[[_deviceClass(device)]]"> 184 class$="[[_deviceClass(device)]]">
208 <td></td> 185 <td></td>
209 <td hidden$="[[_hide('task', _columns.*)]]"></td> 186 <td hidden$="[[_hide('task', _columns.*)]]"></td>
210 <template is="dom-repeat" 187 <template is="dom-repeat"
211 items="[[_plain_columns]]" 188 items="[[_plainColumns]]"
212 as="c"> 189 as="c">
213 <td hidden$="[[_hide(c)]]"> 190 <td hidden$="[[_hide(c)]]">
214 [[_deviceColumn(c, device, _verbose)]] 191 [[_deviceColumn(c, device, _verbose)]]
215 </td> 192 </td>
216 </template> 193 </template>
217 </tr> 194 </tr>
218 </template> <!--devices repeat--> 195 </template> <!--devices repeat-->
219 </template> <!--bot-table repeat--> 196 </template> <!--bot-table repeat-->
220 </tbody> 197 </tbody>
221 </table> 198 </table>
222 </div> 199 </div>
223 200
224 </swarming-app> 201 </swarming-app>
225 202
226 </template> 203 </template>
227 <script> 204 <script>
228 (function(){ 205 (function(){
229 var special_columns = ["id", "task"]; 206 // see dynamic-table for more information on specialColumns, headerMap,
207 // columnMap, and specialSort
208 var specialColumns = ["id", "task"];
230 209
231 var headerMap = { 210 var headerMap = {
232 // "id" and "task" are special, so they don't go here and have their 211 // "id" and "task" are special, so they don't go here. They have their
233 // headers hard-coded below. 212 // headers hard-coded above.
234 "android_devices": "Android Devices", 213 "android_devices": "Android Devices",
235 "cores": "Cores", 214 "cores": "Cores",
236 "cpu": "CPU", 215 "cpu": "CPU",
237 "device": "Non-android Device", 216 "device": "Non-android Device",
238 "device_os": "Device OS", 217 "device_os": "Device OS",
239 "device_type": "Device Type", 218 "device_type": "Device Type",
240 "disk_space": "Free Space (MB)", 219 "disk_space": "Free Space (MB)",
241 "gpu": "GPU", 220 "gpu": "GPU",
242 "os": "OS", 221 "os": "OS",
243 "pool": "Pool", 222 "pool": "Pool",
244 "status": "Status", 223 "status": "Status",
245 "xcode_version": "XCode Version", 224 "xcode_version": "XCode Version",
246 }; 225 };
247 226
248 // This maps column name to a function that will return the content for a
249 // given bot. These functions are bound to this element, and have access
250 // to all functions defined here and in bot-list-shared. If a column
251 // is not listed here, a sane default will be used (see _column()).
252 var columnMap = { 227 var columnMap = {
253 android_devices: function(bot) { 228 android_devices: function(bot) {
254 var devs = this._attribute(bot, "android_devices", "0"); 229 var devs = this._attribute(bot, "android_devices", "0");
255 if (this._verbose) { 230 if (this._verbose) {
256 return devs.join(" | ") + " devices available"; 231 return devs.join(" | ") + " devices available";
257 } 232 }
258 // max() works on strings as long as they can be coerced to Number. 233 // max() works on strings as long as they can be coerced to Number.
259 return Math.max(...devs) + " devices available"; 234 return Math.max(...devs) + " devices available";
260 }, 235 },
261 device_type: function(bot) { 236 device_type: function(bot) {
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
341 if (device.build) { 316 if (device.build) {
342 return device.build["build.id"]; 317 return device.build["build.id"];
343 } 318 }
344 return "unknown"; 319 return "unknown";
345 }, 320 },
346 status: function(device) { 321 status: function(device) {
347 return device.state; 322 return device.state;
348 } 323 }
349 } 324 }
350 325
351 // specialSort defines any custom sorting rules. By default, a
352 // naturalCompare of the column content is done.
353 var specialSort = { 326 var specialSort = {
354 android_devices: function(dir, botA, botB) { 327 android_devices: function(dir, botA, botB) {
355 // We sort on the number of attached devices. Note that this 328 // We sort on the number of attached devices. Note that this
356 // may not be the same as android_devices, because _devices().length 329 // may not be the same as android_devices, because _devices().length
357 // counts all devices plugged into the bot, whereas android_devices 330 // counts all devices plugged into the bot, whereas android_devices
358 // counts just devices ready for work. 331 // counts just devices ready for work.
359 var botACol = this._devices(botA).length; 332 var botACol = this._devices(botA).length;
360 var botBCol = this._devices(botB).length; 333 var botBCol = this._devices(botB).length;
361 return dir * swarming.naturalCompare(botACol, botBCol); 334 return dir * swarming.naturalCompare(botACol, botBCol);
362 }, 335 },
363 disk_space: function(dir, botA, botB) { 336 disk_space: function(dir, botA, botB) {
364 // We sort based on the raw number of MB of the first disk. 337 // We sort based on the raw number of MB of the first disk.
365 var botACol = botA.disks[0].mb; 338 var botACol = botA.disks[0].mb;
366 var botBCol = botB.disks[0].mb;; 339 var botBCol = botB.disks[0].mb;;
367 return dir * swarming.naturalCompare(botACol, botBCol); 340 return dir * swarming.naturalCompare(botACol, botBCol);
368 }, 341 },
369 }; 342 };
370 343
371 Polymer({ 344 Polymer({
372 is: 'bot-list', 345 is: 'bot-list',
373 behaviors: [SwarmingBehaviors.BotListBehavior], 346 behaviors: [SwarmingBehaviors.BotListBehavior,
347 SwarmingBehaviors.DynamicTableBehavior],
374 348
375 properties: { 349 properties: {
376 350
377 client_id: { 351 client_id: {
378 type: String, 352 type: String,
379 }, 353 },
380 354
381 _bots: { 355 // for dynamic table
356 _columnMap: {
357 type: Object,
358 value: function() {
359 return columnMap;
360 }
361 },
362 _headerMap: {
363 type: Object,
364 value: function() {
365 return headerMap;
366 },
367 },
368 // special columns contain html. non-special (i.e. normal colunns) just
369 // contain text.
370 _specialColumns: {
382 type: Array, 371 type: Array,
372 value: function() {
stephana 2016/08/16 13:28:06 This will always return the same reference to spec
kjlubick 2016/08/16 13:41:25 Good call, will address on next CL.
373 return specialColumns;
374 }
375 },
376 _specialSort: {
377 type: Object,
378 value: function() {
379 return specialSort;
380 }
383 }, 381 },
384 382
385 _columns: {
386 type: Array,
387 },
388
389 _filter: {
390 type: Function,
391 value: function() {
392 return true;
393 },
394 },
395
396 _filteredSortedBots: {
397 type: Array,
398 computed: "_filterAndSort(_bots,_filter.*,_sort.*)"
399 },
400
401 _plain_columns: {
402 type: Array,
403 computed: "_stripSpecial(_columns.*)",
404 },
405
406 // _sort is an Object {name:String, direction:String}.
407 _sort: {
408 type: Object,
409 computed: "_makeObject(_sortstr)",
410 },
411
412 _verbose: {
413 type: Boolean,
414 }
415 }, 383 },
416 384
417 _botClass: function(bot) { 385 _botClass: function(bot) {
418 if (bot.is_dead) { 386 if (bot.is_dead) {
419 return "dead"; 387 return "dead";
420 } 388 }
421 if (bot.quarantined) { 389 if (bot.quarantined) {
422 return "quarantined"; 390 return "quarantined";
423 } 391 }
424 return ""; 392 return "";
425 }, 393 },
426 394
427 _botLink: function(id) { 395 _botLink: function(id) {
428 // TODO(kjlubick) Make this point to /newui/ when appropriate. 396 // TODO(kjlubick) Make this point to /newui/ when appropriate.
429 return "/restricted/bot/"+id; 397 return "/restricted/bot/"+id;
430 }, 398 },
431 399
432 400
433 _column: function(col, bot) {
434 var f = columnMap[col];
435 if (!f) {
436 f = function(bot) {
437 var c = this._attribute(bot, col, "none");
438 if (this._verbose) {
439 return c.join(" | ");
440 }
441 return c[0];
442 }
443 }
444 return f.bind(this)(bot);
445 },
446
447 _androidAliasDevice: function(device) { 401 _androidAliasDevice: function(device) {
448 if (device.notReady) { 402 if (device.notReady) {
449 return UNAUTHENTICATED.toUpperCase(); 403 return UNAUTHENTICATED.toUpperCase();
450 } 404 }
451 return this._androidAlias(this._deviceType(device)); 405 return this._androidAlias(this._deviceType(device));
452 }, 406 },
453 407
454 _deviceColumn: function(col, device) { 408 _deviceColumn: function(col, device) {
455 var f = deviceColumnMap[col]; 409 var f = deviceColumnMap[col];
456 if (!f || !device) { 410 if (!f || !device) {
457 return ""; 411 return "";
458 } 412 }
459 return f.bind(this)(device); 413 return f.bind(this)(device);
460 }, 414 },
461 415
462 _deviceClass: function(device) { 416 _deviceClass: function(device) {
463 if (!device.okay) { 417 if (!device.okay) {
464 return "bad-device"; 418 return "bad-device";
465 } 419 }
466 return ""; 420 return "";
467 }, 421 },
468 422
469 _filterAndSort: function(a,b,c) {
470 // We intentionally sort this._bots (and not a copy) to allow users to
471 // "chain" sorts, that is, sort by one thing and then another, and
472 // have both orderings properly impact the list.
473 swarming.stableSort(this._bots, this._sortBotTable.bind(this));
474 var bots = this._bots;
475 if (this._filter) {
476 bots = bots.filter(this._filter.bind(this));
477 }
478
479 return bots;
480 },
481
482 _header: function(col){
483 return headerMap[col] || col;
484 },
485
486 _hide: function(col) {
487 return this._columns.indexOf(col) === -1;
488 },
489
490 _makeObject: function(sortstr){
491 if (!sortstr) {
492 return undefined;
493 }
494 var pieces = sortstr.split(":");
495 if (pieces.length != 2) {
496 // fail safe
497 return {name: "id", direction:"desc"};
498 }
499 return {
500 name: pieces[0],
501 direction: pieces[1],
502 }
503 },
504
505 _reRender: function(filter, sort) {
506 this.$.bot_table.render();
507 },
508
509 _sortBotTable: function(botA, botB) {
510 if (!this._sort) {
511 return 0;
512 }
513 var dir = 1;
514 if (this._sort.direction === "desc") {
515 dir = -1;
516 }
517 var sort = specialSort[this._sort.name];
518 if (sort) {
519 return sort.bind(this)(dir, botA, botB);
520 }
521 // Default to a natural compare of the columns.
522 var botACol = this._column(this._sort.name, botA);
523 var botBCol = this._column(this._sort.name, botB);
524
525 return dir * swarming.naturalCompare(botACol, botBCol);
526 },
527
528 _sortChange: function(e) {
529 // The event we get from sort-toggle tells us the name of what needs
530 // to be sorting and how to sort it.
531 if (!(e && e.detail && e.detail.name)) {
532 return;
533 }
534 // should trigger the computation of _sort and __filterAndSort
535 this.set("_sortstr", e.detail.name +":"+e.detail.direction);
536 },
537
538 // _stripSpecial removes the special columns and sorts the remaining
539 // columns so they always appear in the same order, regardless of
540 // the order they are added.
541 _stripSpecial: function(){
542 return this._columns.filter(function(c){
543 return special_columns.indexOf(c) === -1;
544 }).sort();
545 },
546
547 _taskLink: function(data) { 423 _taskLink: function(data) {
548 if (data && data.task_id) { 424 if (data && data.task_id) {
549 return "/user/task/" + data.task_id; 425 return "/user/task/" + data.task_id;
550 } 426 }
551 return undefined; 427 return undefined;
552 } 428 }
553 429
554 }); 430 });
555 })(); 431 })();
556 </script> 432 </script>
557 </dom-module> 433 </dom-module>
OLDNEW
« no previous file with comments | « appengine/swarming/elements/build/elements.html ('k') | appengine/swarming/elements/res/imp/botlist/bot-list-data.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698