| 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 <task-list> | 9 <task-list> |
| 10 | 10 |
| 11 task-list creats a dynamic table for viewing swarming tasks. Columns can be | 11 task-list creats a dynamic table for viewing swarming tasks. 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, Oauth 2.0 client id. It will be set by server-side | 17 client_id: String, Oauth 2.0 client id. It will be set by server-side |
| 18 template evaluation. | 18 template evaluation. |
| 19 | 19 |
| 20 Methods: | 20 Methods: |
| 21 None. | 21 None. |
| 22 | 22 |
| 23 Events: | 23 Events: |
| 24 None. | 24 None. |
| 25 --> | 25 --> |
| 26 | 26 |
| 27 <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/iron-flex-layout/iron-flex-la
yout-classes.html"> |
| 28 <link rel="import" href="/res/imp/bower_components/paper-button/paper-button.htm
l"> |
| 29 <link rel="import" href="/res/imp/bower_components/paper-toast/paper-toast.html"
> |
| 28 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> | 30 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> |
| 29 | 31 |
| 30 <link rel="import" href="/res/imp/common/dynamic-table-behavior.html"> | 32 <link rel="import" href="/res/imp/common/dynamic-table-behavior.html"> |
| 31 <link rel="import" href="/res/imp/common/sort-toggle.html"> | 33 <link rel="import" href="/res/imp/common/sort-toggle.html"> |
| 32 <link rel="import" href="/res/imp/common/swarming-app.html"> | 34 <link rel="import" href="/res/imp/common/swarming-app.html"> |
| 33 <link rel="import" href="/res/imp/common/url-param.html"> | 35 <link rel="import" href="/res/imp/common/url-param.html"> |
| 34 | 36 |
| 35 <link rel="import" href="task-filters.html"> | 37 <link rel="import" href="task-filters.html"> |
| 36 <link rel="import" href="task-list-data.html"> | 38 <link rel="import" href="task-list-data.html"> |
| 37 | 39 |
| 38 <dom-module id="task-list"> | 40 <dom-module id="task-list"> |
| 39 <template> | 41 <template> |
| 40 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-
style dynamic-table-style"> | 42 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-
style dynamic-table-style"> |
| 41 | 43 task-filters { |
| 44 margin-bottom: 8px; |
| 45 margin-right: 10px; |
| 46 } |
| 42 .task-list th > span { | 47 .task-list th > span { |
| 43 /* Leave space for sort-toggle*/ | 48 /* Leave space for sort-toggle*/ |
| 44 padding-right: 30px; | 49 padding-right: 30px; |
| 45 } | 50 } |
| 51 |
| 52 /* These colors are from buildbot */ |
| 53 .failed { |
| 54 background-color: #ffdddd; |
| 55 } |
| 56 .died { |
| 57 background-color: #cccccc; |
| 58 } |
| 59 .exception { |
| 60 background-color: #edd2ff; |
| 61 } |
| 62 .pending { |
| 63 background-color: #fffc6c; |
| 64 } |
| 46 </style> | 65 </style> |
| 47 | 66 |
| 48 <url-param name="sort" | 67 <url-param name="sort" |
| 49 value="{{_sortstr}}" | 68 value="{{_sortstr}}" |
| 50 default_value="id:asc"> | 69 default_value="created_ts:desc"> |
| 51 </url-param> | 70 </url-param> |
| 52 | 71 |
| 53 <swarming-app | 72 <swarming-app |
| 54 client_id="[[client_id]]" | 73 client_id="[[client_id]]" |
| 55 auth_headers="{{_auth_headers}}" | 74 auth_headers="{{_auth_headers}}" |
| 75 permissions="{{_permissions}}" |
| 56 signed_in="{{_signed_in}}" | 76 signed_in="{{_signed_in}}" |
| 57 busy="[[_busy]]" | 77 busy="[[_busy]]" |
| 58 name="Swarming Task List"> | 78 name="Swarming Task List"> |
| 59 | 79 |
| 60 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> | 80 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> |
| 61 | 81 |
| 62 <div hidden$="[[_not(_signed_in)]]"> | 82 <div hidden$="[[_not(_signed_in)]]"> |
| 63 <task-list-data | 83 <task-list-data |
| 64 auth_headers="[[_auth_headers]]" | 84 auth_headers="[[_auth_headers]]" |
| 65 query_params="[[_query_params]]" | 85 query_params="[[_query_params]]" |
| 66 tasks="{{_items}}" | 86 tasks="{{_items}}" |
| 67 busy="{{_busy}}"> | 87 busy="{{_busy}}" |
| 88 primary_map="{{_primary_map}}" |
| 89 primary_arr="{{_primary_arr}}"> |
| 68 </task-list-data> | 90 </task-list-data> |
| 69 | 91 |
| 92 <paper-toast id="toast"></paper-toast> |
| 93 |
| 70 <div class="horizontal layout"> | 94 <div class="horizontal layout"> |
| 71 | 95 |
| 72 <task-filters | 96 <task-filters |
| 97 primary_map="[[_primary_map]]" |
| 98 primary_arr="[[_primary_arr]]" |
| 73 columns="{{_columns}}" | 99 columns="{{_columns}}" |
| 74 query_params="{{_query_params}}" | 100 query_params="{{_query_params}}" |
| 75 filter="{{_filter}}" | 101 filter="{{_filter}}"> |
| 76 verbose="{{_verbose}}"> | |
| 77 </task-filters> | 102 </task-filters> |
| 78 | 103 |
| 79 </div> | 104 </div> |
| 80 | 105 |
| 81 <table class="task-list"> | 106 <table class="task-list"> |
| 82 <thead on-sort_change="_sortChange"> | 107 <thead on-sort_change="_sortChange"> |
| 83 <!-- To allow for dynamic columns without having a lot of copy-pasted | 108 <!-- To allow for dynamic columns without having a lot of copy-pasted |
| 84 code, we break columns up into "special" and "plain" columns. Special | 109 code, we break columns up into "special" and "plain" columns. Special |
| 85 columns require some sort of HTML output (e.g. anchor tags) and plain | 110 columns require some sort of HTML output (e.g. anchor tags) and plain |
| 86 columns just output text. The plain columns use Polymer functions to | 111 columns just output text. The plain columns use Polymer functions to |
| 87 insert their text [_header(), _column(), _deviceColumn()]. Polymer | 112 insert their text [_header(), _column(), _deviceColumn()]. Polymer |
| 88 functions do not allow HTML (to avoid XSS), so special columns, like i
d | 113 functions do not allow HTML (to avoid XSS), so special columns, like i
d |
| 89 and task are inserted in a fixed order. | 114 and task are inserted in a fixed order. |
| 90 --> | 115 --> |
| 91 <tr> | 116 <tr> |
| 92 <th> | 117 <th> |
| 93 <span>Task Name</span> | 118 <span>Task Name</span> |
| 94 <sort-toggle | 119 <sort-toggle |
| 95 name="name" | 120 name="name" |
| 96 current="[[_sort]]"> | 121 current="[[_sort]]"> |
| 97 </sort-toggle> | 122 </sort-toggle> |
| 98 </th> | 123 </th> |
| 99 <!-- This wonky syntax is the proper way to listen to changes on a
n | 124 <!-- This wonky syntax is the proper way to listen to changes on a
n |
| 100 array (we are listening to all subproperties). The element returne
d is | 125 array (we are listening to all subproperties). The element returne
d is |
| 101 not of much use, so we'll ignore it in _hide() and use this._colum
ns. | 126 not of much use, so we'll ignore it in _hide() and use this._colum
ns. |
| 102 --> | 127 --> |
| 103 <th hidden$="[[_hide('state', _columns.*)]]"> | 128 <th hidden$="[[_hide('state', _columns.*)]]"> |
| 104 <span>Status</span> | 129 <span>State</span> |
| 105 <sort-toggle | 130 <sort-toggle |
| 106 name="state" | 131 name="state" |
| 107 current="[[_sort]]"> | 132 current="[[_sort]]"> |
| 108 </sort-toggle> | 133 </sort-toggle> |
| 109 </th> | 134 </th> |
| 110 | 135 |
| 136 <th hidden$="[[_hide('deduped_from', _columns.*)]]"> |
| 137 <span>Deduped from</span> |
| 138 <sort-toggle |
| 139 name="deduped_from" |
| 140 current="[[_sort]]"> |
| 141 </sort-toggle> |
| 142 </th> |
| 143 |
| 111 <template | 144 <template |
| 112 is="dom-repeat" | 145 is="dom-repeat" |
| 113 items="[[_plainColumns]]" | 146 items="[[_plainColumns]]" |
| 114 as="c"> | 147 as="c"> |
| 115 <th hidden$="[[_hide(c)]]"> | 148 <th hidden$="[[_hide(c)]]"> |
| 116 <span>[[_header(c)]]</span> | 149 <span>[[_header(c)]]</span> |
| 117 <sort-toggle | 150 <sort-toggle |
| 118 name="[[c]]" | 151 name="[[c]]" |
| 119 current="[[_sort]]"> | 152 current="[[_sort]]"> |
| 120 </sort-toggle> | 153 </sort-toggle> |
| 121 </th> | 154 </th> |
| 122 </template> | 155 </template> |
| 123 </tr> | 156 </tr> |
| 124 </thead> | 157 </thead> |
| 125 <tbody> | 158 <tbody> |
| 126 <template | 159 <template |
| 127 id="tasks_table" | 160 id="tasks_table" |
| 128 is="dom-repeat" | 161 is="dom-repeat" |
| 129 items="[[_filteredSortedItems]]" | 162 items="[[_filteredSortedItems]]" |
| 130 as="task" | 163 as="task" |
| 131 initial-count=50> | 164 initial-count=50> |
| 132 | 165 |
| 133 <tr class$="[[_taskClass(task)]]"> | 166 <tr class$="[[_taskClass(task)]]"> |
| 134 <td> | 167 <td> |
| 135 <a | 168 <a |
| 136 class="center" | 169 class="center" |
| 137 href$="[[_taskLink(task)]]" | 170 href$="[[_taskLink(task.task_id)]]" |
| 138 target="_blank"> | 171 target="_blank"> |
| 139 [[task.name]] | 172 [[task.name]] |
| 140 </a> | 173 </a> |
| 141 </td> | 174 </td> |
| 142 <td hidden$="[[_hide('state', _columns.*)]]"> | 175 <td hidden$="[[_hide('state', _columns.*)]]"> |
| 143 [[_column('state', task, _verbose)]] | 176 [[_column('state', task)]] |
| 144 <!-- TODO(kjlubick): Add button to stop pending.--> | 177 <paper-button |
| 178 raised |
| 179 hidden$="[[_cannotCancel(task,_permissions)]]" |
| 180 on-tap="_cancelTask"> |
| 181 Cancel |
| 182 </paper-button> |
| 183 </td> |
| 184 <td hidden$="[[_hide('deduped_from', _columns.*)]]"> |
| 185 <a |
| 186 class="center" |
| 187 href$="[[_taskLink(task.deduped_from)]]" |
| 188 target="_blank"> |
| 189 [[_column('deduped_from',task)]] |
| 190 </a> |
| 145 </td> | 191 </td> |
| 146 | 192 |
| 147 <template | 193 <template |
| 148 is="dom-repeat" | 194 is="dom-repeat" |
| 149 items="[[_plainColumns]]" | 195 items="[[_plainColumns]]" |
| 150 as="c"> | 196 as="c"> |
| 151 <td hidden$="[[_hide(c)]]"> | 197 <td hidden$="[[_hide(c)]]"> |
| 152 [[_column(c, task, _verbose)]] | 198 [[_column(c, task)]] |
| 153 </td> | 199 </td> |
| 154 </template> | 200 </template> |
| 155 | 201 |
| 156 </tr> | 202 </tr> |
| 157 </template> <!--tasks_table repeat--> | 203 </template> <!--tasks_table repeat--> |
| 158 </tbody> | 204 </tbody> |
| 159 </table> | 205 </table> |
| 160 </div> | 206 </div> |
| 161 | 207 |
| 162 </swarming-app> | 208 </swarming-app> |
| 163 | 209 |
| 164 </template> | 210 </template> |
| 165 <script> | 211 <script> |
| 166 (function(){ | 212 (function(){ |
| 167 var specialColumns = ["name", "state"]; | 213 var specialColumns = ["deduped_from", "name", "state"]; |
| 168 var columnMap = {}; | 214 var columnMap = { |
| 215 costs_usd: function(task) { |
| 216 return this._attribute(task, "costs_usd", 0)[0]; |
| 217 }, |
| 218 state: function(task) { |
| 219 var state = this._attribute(task, "state")[0]; |
| 220 if (state === "COMPLETED") { |
| 221 |
| 222 if (this._attribute(task, "failure", false)[0]) { |
| 223 return "COMPLETED (FAILURE)"; |
| 224 } |
| 225 var tryNum = this._attribute(task, "try_number", "-1")[0]; |
| 226 if (tryNum === "0") { |
| 227 return "COMPLETED (DEDUPED)"; |
| 228 } |
| 229 return "COMPLETED (SUCCESS)"; |
| 230 } |
| 231 return state; |
| 232 }, |
| 233 }; |
| 169 var headerMap = { | 234 var headerMap = { |
| 170 "user": "Requesting User", | 235 "user": "Requesting User", |
| 171 }; | 236 }; |
| 172 var specialSort = {}; | 237 var specialSort = {}; |
| 173 | 238 |
| 174 Polymer({ | 239 Polymer({ |
| 175 is: 'task-list', | 240 is: 'task-list', |
| 176 behaviors: [ | 241 behaviors: [ |
| 177 SwarmingBehaviors.DynamicTableBehavior, | 242 SwarmingBehaviors.DynamicTableBehavior, |
| 178 ], | 243 ], |
| 179 | 244 |
| 180 properties: { | 245 properties: { |
| 181 client_id: { | 246 client_id: { |
| 182 type: String, | 247 type: String, |
| 183 }, | 248 }, |
| 184 | 249 |
| 185 // For dynamic table. | 250 // For dynamic table. |
| 186 _columnMap: { | 251 _columnMap: { |
| 187 type: Object, | 252 type: Object, |
| 188 value: columnMap, | 253 value: function() { |
| 254 var base = this._commonColumns(); |
| 255 for (var attr in columnMap) { |
| 256 base[attr] = columnMap[attr]; |
| 257 } |
| 258 return base; |
| 259 }, |
| 189 }, | 260 }, |
| 190 _headerMap: { | 261 _headerMap: { |
| 191 type: Object, | 262 type: Object, |
| 192 value: headerMap, | 263 value: headerMap, |
| 193 }, | 264 }, |
| 194 _specialColumns: { | 265 _specialColumns: { |
| 195 type: Array, | 266 type: Array, |
| 196 value: specialColumns, | 267 value: specialColumns, |
| 197 }, | 268 }, |
| 198 _specialSort: { | 269 _specialSort: { |
| 199 type: Object, | 270 type: Object, |
| 200 value: specialSort, | 271 value: specialSort, |
| 201 }, | 272 }, |
| 202 }, | 273 }, |
| 203 | 274 |
| 204 _attribute: function(task, col, def) { | 275 _attribute: function(task, col, def) { |
| 205 var retVal = task[col] || [def]; | 276 if (def === undefined) { |
| 277 def = "none"; |
| 278 } |
| 279 var retVal = this._tag(task, col) || task[col] || [def]; |
| 206 if (!Array.isArray(retVal)) { | 280 if (!Array.isArray(retVal)) { |
| 207 return [retVal]; | 281 return [retVal]; |
| 208 } | 282 } |
| 209 return retVal; | 283 return retVal; |
| 210 }, | 284 }, |
| 211 | 285 |
| 212 _taskLink: function(task) { | 286 _cannotCancel: function(task, permissions) { |
| 287 return !(permissions && permissions.can_cancel_task && |
| 288 this._column("state", task) === "PENDING"); |
| 289 }, |
| 290 |
| 291 _cancelTask: function(e) { |
| 292 var task = e.model.task; |
| 293 if (!task || !task.task_id) { |
| 294 console.log("Missing task info", task); |
| 295 return |
| 296 } |
| 297 var id = task.task_id |
| 298 |
| 299 // Keep toast displayed until we hear back from the cancel. |
| 300 this.$.toast.duration = 0; |
| 301 this.$.toast.text="Canceling task " + id; |
| 302 this.$.toast.open(); |
| 303 var url = "/_ah/api/swarming/v1/task/" + id +"/cancel"; |
| 304 sk.request("POST", url, undefined, this._auth_headers).then(function(res
ponse) { |
| 305 this.$.toast.close(); |
| 306 this.$.toast.show({ |
| 307 text: "Request sent. Response: "+response, |
| 308 duration: 3000, |
| 309 }); |
| 310 }.bind(this)).catch(function(reason) { |
| 311 console.log("Cancellation failed", reason); |
| 312 this.$.toast.close(); |
| 313 this.$.toast.show({ |
| 314 text: "Request failed. Reason: "+reason, |
| 315 duration: 3000, |
| 316 }); |
| 317 }.bind(this)); |
| 318 }, |
| 319 |
| 320 _tag: function(task, col) { |
| 321 if (!task || !task.tagMap) { |
| 322 return undefined; |
| 323 } |
| 324 return task.tagMap[col]; |
| 325 }, |
| 326 |
| 327 _taskLink: function(taskId) { |
| 328 if (!taskId) { |
| 329 return undefined; |
| 330 } |
| 213 // TODO(kjlubick) Make this point to /newui/ when appropriate. | 331 // TODO(kjlubick) Make this point to /newui/ when appropriate. |
| 214 return "/restricted/task/"+task.task_id; | 332 return "/restricted/task/"+taskId; |
| 215 }, | 333 }, |
| 216 | 334 |
| 217 _taskClass: function(task) { | 335 _taskClass: function(task) { |
| 218 // TODO(kjlubick): Color tasks? | 336 var state = this._column("state", task); |
| 337 if (state === "CANCELED" ||state === "TIMED_OUT" || state === "EXPIRED")
{ |
| 338 return "exception"; |
| 339 } |
| 340 if (state === "BOT_DIED") { |
| 341 return "died"; |
| 342 } |
| 343 if (state === "COMPLETED (FAILURE)") { |
| 344 return "failed"; |
| 345 } |
| 346 if (state === "RUNNING" || state === "PENDING") { |
| 347 return "pending"; |
| 348 } |
| 219 return ""; | 349 return ""; |
| 220 } | 350 } |
| 221 | 351 |
| 222 }); | 352 }); |
| 223 })(); | 353 })(); |
| 224 </script> | 354 </script> |
| 225 </dom-module> | 355 </dom-module> |
| OLD | NEW |