Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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> |
| OLD | NEW |