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

Side by Side Diff: appengine/swarming/elements/res/imp/tasklist/task-list.html

Issue 2408743002: Move elements/ to ui/ (Closed)
Patch Set: rebase again Created 4 years, 2 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
(Empty)
1 <!--
2 Copyright 2016 The LUCI Authors. All rights reserved.
3 Use of this source code is governed under the Apache License, Version 2.0
4 that can be found in the LICENSE file.
5
6 This in an HTML Import-able file that contains the definition
7 of the following elements:
8
9 <task-list>
10
11 task-list creats a dynamic table for viewing swarming tasks. Columns can be
12 dynamically filtered and it supports client-side filtering.
13
14 This is a top-level element.
15
16 Properties:
17 client_id: String, Oauth 2.0 client id. It will be set by server-side
18 template evaluation.
19
20 Methods:
21 None.
22
23 Events:
24 None.
25 -->
26
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-button/paper-button.htm l">
30 <link rel="import" href="/res/imp/bower_components/paper-dialog/paper-dialog.htm l">
31 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
32
33 <link rel="import" href="/res/imp/common/dynamic-table-behavior.html">
34 <link rel="import" href="/res/imp/common/error-toast.html">
35 <link rel="import" href="/res/imp/common/pageable-data.html">
36 <link rel="import" href="/res/imp/common/sort-toggle.html">
37 <link rel="import" href="/res/imp/common/swarming-app.html">
38 <link rel="import" href="/res/imp/common/task-behavior.html">
39 <link rel="import" href="/res/imp/common/url-param.html">
40
41 <link rel="import" href="task-filters.html">
42 <link rel="import" href="task-list-data.html">
43
44 <dom-module id="task-list">
45 <template>
46 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style dynamic-table-style task-style">
47 task-filters {
48 margin-bottom: 8px;
49 margin-right: 10px;
50 }
51 .task-list th > span {
52 /* Leave space for sort-toggle*/
53 padding-right: 30px;
54 }
55 </style>
56
57 <url-param name="s"
58 value="{{_sortstr}}"
59 default_value="created_ts:desc">
60 </url-param>
61
62 <swarming-app
63 client_id="[[client_id]]"
64 auth_headers="{{_auth_headers}}"
65 permissions="{{_permissions}}"
66 signed_in="{{_signed_in}}"
67 busy="[[_or(_busy1,_busy2)]]"
68 name="Swarming Task List">
69
70 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
71
72 <div hidden$="[[_not(_signed_in)]]">
73 <task-list-data
74 id="data"
75 auth_headers="[[_auth_headers]]"
76 query_params="[[_query_params]]"
77 tasks="[[_items]]"
78 busy="{{_busy1)}}"
79 primary_map="{{_primary_map}}"
80 primary_arr="{{_primary_arr}}">
81 </task-list-data>
82
83 <div class="horizontal layout">
84 <task-filters
85 primary_map="[[_primary_map]]"
86 primary_arr="[[_primary_arr]]"
87 columns="{{_columns}}"
88 query_params="{{_query_params}}"
89 filter="{{_filter}}">
90 </task-filters>
91 </div>
92
93 <table class="task-list">
94 <thead on-sort_change="_sortChange">
95 <!-- To allow for dynamic columns without having a lot of copy-pasted
96 code, we break columns up into "special" and "plain" columns. Special
97 columns require some sort of HTML output (e.g. anchor tags) and plain
98 columns just output text. The plain columns use Polymer functions to
99 insert their text [_header(), _column(), _deviceColumn()]. Polymer
100 functions do not allow HTML (to avoid XSS), so special columns, like i d
101 and task are inserted in a fixed order.
102 -->
103 <tr>
104 <th>
105 <span>Task Name</span>
106 <sort-toggle
107 name="name"
108 current="[[_sort]]">
109 </sort-toggle>
110 </th>
111 <!-- This wonky syntax is the proper way to listen to changes on a n
112 array (we are listening to all subproperties). The element returne d is
113 not of much use, so we'll ignore it in _hide() and use this._colum ns.
114 -->
115 <th hidden$="[[_hide('state', _columns.*)]]">
116 <span>State</span>
117 <sort-toggle
118 name="state"
119 current="[[_sort]]">
120 </sort-toggle>
121 </th>
122
123 <th hidden$="[[_hide('bot', _columns.*)]]">
124 <span>Bot Assigned</span>
125 <sort-toggle
126 name="bot"
127 current="[[_sort]]">
128 </sort-toggle>
129 </th>
130
131 <th hidden$="[[_hide('deduped_from', _columns.*)]]">
132 <span>Deduped from</span>
133 <sort-toggle
134 name="deduped_from"
135 current="[[_sort]]">
136 </sort-toggle>
137 </th>
138
139 <th hidden$="[[_hide('source_revision', _columns.*)]]">
140 <span>Source Revision</span>
141 <sort-toggle
142 name="source_revision"
143 current="[[_sort]]">
144 </sort-toggle>
145 </th>
146
147 <template
148 is="dom-repeat"
149 items="[[_plainColumns]]"
150 as="c">
151 <th hidden$="[[_hide(c)]]">
152 <span>[[_header(c)]]</span>
153 <sort-toggle
154 name="[[c]]"
155 current="[[_sort]]">
156 </sort-toggle>
157 </th>
158 </template>
159 </tr>
160 </thead>
161 <tbody>
162 <template
163 id="tasks_table"
164 is="dom-repeat"
165 items="[[_filteredSortedItems]]"
166 as="task"
167 initial-count=50>
168
169 <tr class$="[[_taskClass(task)]]">
170 <td>
171 <a
172 class="center"
173 href$="[[_taskLink(task.task_id)]]"
174 target="_blank">
175 [[task.name]]
176 </a>
177 </td>
178 <td hidden$="[[_hide('state', _columns.*)]]">
179 [[_column('state', task)]]
180 <paper-button
181 raised
182 hidden$="[[_cannotCancel(task,_permissions)]]"
183 on-tap="_promptCancel">
184 Cancel
185 </paper-button>
186 </td>
187 <td hidden$="[[_hide('bot', _columns.*)]]">
188 <a
189 class="center"
190 href$="[[_botLink(task.bot_id)]]"
191 target="_blank">
192 [[_column('bot',task)]]
193 </a>
194 </td>
195 <td hidden$="[[_hide('deduped_from', _columns.*)]]">
196 <a
197 class="center"
198 href$="[[_taskLink(task.deduped_from)]]"
199 target="_blank">
200 [[_column('deduped_from',task)]]
201 </a>
202 </td>
203
204 <td hidden$="[[_hide('source_revision', _columns.*)]]">
205 <a
206 class="center"
207 href$="[[_sourceLink(task)]]"
208 target="_blank">
209 [[_column('source_revision',task)]]
210 </a>
211 </td>
212
213 <template
214 is="dom-repeat"
215 items="[[_plainColumns]]"
216 as="c">
217 <td hidden$="[[_hide(c)]]">
218 [[_column(c, task)]]
219 </td>
220 </template>
221
222 </tr>
223 </template> <!--tasks_table repeat-->
224 </tbody>
225 </table>
226
227 <pageable-data
228 id="page_tasks"
229 busy="{{_busy2}}"
230 label="Show more tasks"
231 output="{{_items}}"
232 parse="[[_parseTasks]]">
233 </pageable-data>
234 </div>
235 </swarming-app>
236
237 <paper-dialog id="prompt" modal on-iron-overlay-closed="_promptClosed">
238 <h2>Are you sure?</h2>
239 <div>Are you sure you want to [[_dialogPrompt]]?</div>
240 <div class="buttons">
241 <paper-button dialog-dismiss autofocus>No</paper-button>
242 <paper-button dialog-confirm>Yes</paper-button>
243 </div>
244 </paper-dialog>
245
246 <error-toast></error-toast>
247
248 </template>
249 <script>
250 (function(){
251 var specialColumns = ["deduped_from", "name", "state", "bot", "source_revisi on"];
252
253 // Given a time attribute like "abandoned_ts", humanTime returns a function
254 // that returns the human-friendly version of that attribute. The human
255 // friendly time was created in task-list-data.
256 function humanTime(attr) {
257 return function(task) {
258 return this._attribute(task, "human_" + attr)[0];
259 }
260 }
261 var columnMap = {
262 abandoned_ts: humanTime("abandoned_ts"),
263 bot: function(task) {
264 return this._attribute(task, "bot_id")[0];
265 },
266 completed_ts: humanTime("completed_ts"),
267 costs_usd: function(task) {
268 return this._attribute(task, "costs_usd", 0)[0];
269 },
270 created_ts: humanTime("created_ts"),
271 duration: humanTime("duration"),
272 modified_ts: humanTime("modified_ts"),
273 source_revision: function(task) {
274 var r = this._attribute(task, "source_revision")[0];
275 return r.substring(0,8);
276 },
277 started_ts: humanTime("started_ts"),
278 state: function(task) {
279 var state = this._attribute(task, "state")[0];
280 if (state === "COMPLETED") {
281
282 if (this._attribute(task, "failure", false)[0]) {
283 return "COMPLETED (FAILURE)";
284 }
285 var tryNum = this._attribute(task, "try_number", "-1")[0];
286 if (tryNum === "0") {
287 return "COMPLETED (DEDUPED)";
288 }
289 return "COMPLETED (SUCCESS)";
290 }
291 return state;
292 },
293 };
294 var headerMap = {
295 "user": "Requesting User",
296 };
297
298 // Given a time attribute like "abandoned_ts", sortableTime returns a functi on
299 // that compares the tasks based on the attribute. This is used for sorting .
300 function sortableTime(attr) {
301 // sort times based on the string they come with, formatted like
302 // "2016-08-16T13:12:40.606300" which sorts correctly. Locale time
303 // (used in the columns), does not.
304 return function(dir, a, b) {
305 var aCol = this._attribute(a, attr, "0")[0];
306 var bCol = this._attribute(b, attr, "0")[0];
307
308 return dir * (aCol - bCol);
309 }
310 }
311 var specialSort = {
312 abandoned_ts: sortableTime("abandoned_ts"),
313 completed_ts: sortableTime("completed_ts"),
314 created_ts: sortableTime("created_ts"),
315 duration: sortableTime("duration"),
316 modified_ts: sortableTime("modified_ts"),
317 started_ts: sortableTime("started_ts"),
318 };
319
320 Polymer({
321 is: 'task-list',
322 behaviors: [
323 SwarmingBehaviors.DynamicTableBehavior,
324 SwarmingBehaviors.TaskBehavior,
325 ],
326
327 properties: {
328 client_id: {
329 type: String,
330 },
331
332 _busy1: {
333 type: Boolean,
334 value: false
335 },
336 _busy2: {
337 type: Boolean,
338 value: false
339 },
340 _parseTasks: {
341 type: Function,
342 value: function() {
343 return this.$.data.parseTasks.bind(this);
344 }
345 },
346 // The task id to cancel if the prompt is accepted.
347 _toCancel: {
348 type: String,
349 },
350
351 // For dynamic table.
352 _columnMap: {
353 type: Object,
354 value: function() {
355 var base = this._commonColumns();
356 for (var attr in columnMap) {
357 base[attr] = columnMap[attr];
358 }
359 return base;
360 },
361 },
362 _headerMap: {
363 type: Object,
364 value: headerMap,
365 },
366 _specialColumns: {
367 type: Array,
368 value: specialColumns,
369 },
370 _specialSort: {
371 type: Object,
372 value: specialSort,
373 },
374 },
375
376 observers:["reload(_query_params,_auth_headers)"],
377
378 _attribute: function(task, col, def) {
379 if (def === undefined) {
380 def = "none";
381 }
382 var retVal = this._tag(task, col) || task[col] || [def];
383 if (!Array.isArray(retVal)) {
384 return [retVal];
385 }
386 return retVal;
387 },
388
389 _cannotCancel: function(task, permissions) {
390 return !(permissions && permissions.cancel_task &&
391 this._column("state", task) === "PENDING");
392 },
393
394 _cancelTask: function() {
395 var url = "/_ah/api/swarming/v1/task/" + this._toCancel +"/cancel";
396 swarming.postWithToast(url, "Canceling task " + this._toCancel, this._au th_headers);
397 this.set("_toCancel", "");
398 },
399
400 _promptClosed: function(e) {
401 if (e.detail.confirmed) {
402 this._cancelTask();
403 }
404 },
405
406 _promptCancel: function(e) {
407 var task = e.model.task;
408 if (!task || !task.task_id) {
409 console.log("Missing task info", task);
410 return
411 }
412 this.set("_toCancel", task.task_id);
413 this.set("_dialogPrompt", 'cancel task "'+ task.name +'"');
414 this.$.prompt.open();
415 },
416
417 reload: function() {
418 if (!this._auth_headers || !this._query_params) {
419 return;
420 }
421 var url = "/_ah/api/swarming/v1/tasks/list?" + sk.query.fromParamSet(thi s._query_params);
422 this.$.page_tasks.load(url,this._auth_headers);
423 },
424
425 _sourceLink: function(task) {
426 var rev = this._attribute(task, "source_revision")[0];
427 var repo = this._attribute(task, "source_repo")[0];
428 if (rev === "none" || repo === "none") {
429 return false;
430 }
431 return repo.replace("%s", rev);
432 },
433
434 _tag: function(task, col) {
435 if (!task || !task.tagMap) {
436 return undefined;
437 }
438 return task.tagMap[col];
439 },
440
441 _taskClass: function(task) {
442 return this.stateClass(this._column("state", task));
443 }
444 });
445 })();
446 </script>
447 </dom-module>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698