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

Side by Side Diff: appengine/swarming/elements/build/elements.html

Issue 2266133002: Add filter to task-list (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@extract-filters
Patch Set: Fix default sort Created 4 years, 3 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 14592 matching lines...) Expand 10 before | Expand all | Expand 10 after
14603 }, 14603 },
14604 auth_headers: { 14604 auth_headers: {
14605 type: Object, 14605 type: Object,
14606 notify: true, 14606 notify: true,
14607 }, 14607 },
14608 signed_in: { 14608 signed_in: {
14609 type: Boolean, 14609 type: Boolean,
14610 value: false, 14610 value: false,
14611 notify:true, 14611 notify:true,
14612 }, 14612 },
14613 permissions: {
14614 type: Object,
14615 value: function() {
14616 // TODO(kjlubick): Make this call the whoami endpoint after signing in.
14617 return {
14618 can_cancel_task: true,
14619 }
14620 },
14621 notify: true,
14622 },
14613 14623
14614 busy: { 14624 busy: {
14615 type: Boolean, 14625 type: Boolean,
14616 }, 14626 },
14617 name: { 14627 name: {
14618 type: String, 14628 type: String,
14619 }, 14629 },
14620 }, 14630 },
14621 14631
14622 }); 14632 });
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
14676 }, 14686 },
14677 14687
14678 }); 14688 });
14679 </script> 14689 </script>
14680 </dom-module><script> 14690 </dom-module><script>
14681 window.SwarmingBehaviors = window.SwarmingBehaviors || {}; 14691 window.SwarmingBehaviors = window.SwarmingBehaviors || {};
14682 (function(){ 14692 (function(){
14683 // This behavior wraps up all the shared swarming functionality. 14693 // This behavior wraps up all the shared swarming functionality.
14684 SwarmingBehaviors.CommonBehavior = { 14694 SwarmingBehaviors.CommonBehavior = {
14685 14695
14696 // _getJsonAsync makes an XHR to a url, parses the response as JSON
14697 // and sticks the resulting object into the property with the name given
14698 // by "bindTo". If busy is defined, the property with that name will be
14699 // set to true while the request is in flight and false afterwards.
14700 // request headers (e.g. authentication) and query params will be used if
14701 // provided. Query params is an object like {String:Array<String>}. On
14702 // error, bindTo will be set to false. It is not set to undefined
14703 // because computed values in Polymer don't fire if a property is
14704 // undefined. Clients should check that bindTo is not falsey.
14705 _getJsonAsync: function(bindTo, url, busy, headers, params) {
14706 if (!bindTo || !url) {
14707 console.log("Need at least a polymer element to bind to and a url");
14708 return;
14709 }
14710 if (busy) {
14711 this.set(busy, true);
14712 }
14713 url = url + "?" + sk.query.fromParamSet(params);
14714 sk.request("GET", url, "", headers).then(JSON.parse).then(function(json) {
14715 this.set(bindTo, json);
14716 if (busy) {
14717 this.set(busy, false);
14718 }
14719 }.bind(this)).catch(function(reason){
14720 console.log("Reason for failure of request to " + url, reason);
14721 this.set(bindTo, false);
14722 if (busy) {
14723 this.set(busy, false);
14724 }
14725 }.bind(this));
14726 },
14727
14686 _not: function(a) { 14728 _not: function(a) {
14687 return !a; 14729 return !a;
14688 }, 14730 },
14689 14731
14690 _or: function() { 14732 _or: function() {
14691 var result = false; 14733 var result = false;
14692 // can't use .foreach, as arguments isn't really an Array. 14734 // can't use .foreach, as arguments isn't really an Array.
14693 for (var i = 0; i < arguments.length; i++) { 14735 for (var i = 0; i < arguments.length; i++) {
14694 result = result || arguments[i]; 14736 result = result || arguments[i];
14695 } 14737 }
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after
14850 _stripSpecial: function(){ 14892 _stripSpecial: function(){
14851 return this._columns.filter(function(c) { 14893 return this._columns.filter(function(c) {
14852 return this._specialColumns.indexOf(c) === -1; 14894 return this._specialColumns.indexOf(c) === -1;
14853 }.bind(this)).sort(); 14895 }.bind(this)).sort();
14854 }, 14896 },
14855 14897
14856 // Common columns shared between tasklist and botlist 14898 // Common columns shared between tasklist and botlist
14857 _commonColumns: function() { 14899 _commonColumns: function() {
14858 // return a fresh object so all elements have their own copy 14900 // return a fresh object so all elements have their own copy
14859 return { 14901 return {
14860 android_devices: function(bot) {
14861 var devs = this._attribute(bot, "android_devices", "0");
14862 if (this._verbose) {
14863 return devs.join(" | ") + " devices available";
14864 }
14865 // max() works on strings as long as they can be coerced to Number.
14866 return Math.max(...devs) + " devices available";
14867 },
14868 device_type: function(bot) { 14902 device_type: function(bot) {
14869 var dt = this._attribute(bot, "device_type", "none"); 14903 var dt = this._attribute(bot, "device_type", "none");
14870 dt = dt[0]; 14904 dt = dt[0];
14871 var alias = swarming.alias.android(dt); 14905 var alias = swarming.alias.android(dt);
14872 if (alias === "unknown") { 14906 if (alias === "unknown") {
14873 return dt; 14907 return dt;
14874 } 14908 }
14875 return swarming.alias.apply(dt, alias); 14909 return swarming.alias.apply(dt, alias);
14876 }, 14910 },
14877 gpu: function(bot){ 14911 gpu: function(bot){
(...skipping 9248 matching lines...) Expand 10 before | Expand all | Expand 10 after
24126 "device_type": "Device Type", 24160 "device_type": "Device Type",
24127 "disk_space": "Free Space (MB)", 24161 "disk_space": "Free Space (MB)",
24128 "gpu": "GPU", 24162 "gpu": "GPU",
24129 "os": "OS", 24163 "os": "OS",
24130 "pool": "Pool", 24164 "pool": "Pool",
24131 "status": "Status", 24165 "status": "Status",
24132 "xcode_version": "XCode Version", 24166 "xcode_version": "XCode Version",
24133 }; 24167 };
24134 24168
24135 var columnMap = { 24169 var columnMap = {
24170 android_devices: function(bot) {
24171 var devs = this._attribute(bot, "android_devices", "0");
24172 if (this._verbose) {
24173 return devs.join(" | ") + " devices available";
24174 }
24175 // max() works on strings as long as they can be coerced to Number.
24176 return Math.max(...devs) + " devices available";
24177 },
24136 disk_space: function(bot) { 24178 disk_space: function(bot) {
24137 var aliased = []; 24179 var aliased = [];
24138 bot.disks.forEach(function(disk){ 24180 bot.disks.forEach(function(disk){
24139 var alias = sk.human.bytes(disk.mb, swarming.MB); 24181 var alias = sk.human.bytes(disk.mb, swarming.MB);
24140 aliased.push(swarming.alias.apply(disk.mb, disk.id + " "+ alias)); 24182 aliased.push(swarming.alias.apply(disk.mb, disk.id + " "+ alias));
24141 }.bind(this)); 24183 }.bind(this));
24142 if (this._verbose) { 24184 if (this._verbose) {
24143 return aliased.join(" | "); 24185 return aliased.join(" | ");
24144 } 24186 }
24145 return aliased[0]; 24187 return aliased[0];
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
24291 _taskLink: function(data) { 24333 _taskLink: function(data) {
24292 if (data && data.task_id) { 24334 if (data && data.task_id) {
24293 return "/user/task/" + data.task_id; 24335 return "/user/task/" + data.task_id;
24294 } 24336 }
24295 return undefined; 24337 return undefined;
24296 } 24338 }
24297 24339
24298 }); 24340 });
24299 })(); 24341 })();
24300 </script> 24342 </script>
24301 </dom-module><dom-module id="task-filters" assetpath="/res/imp/tasklist/"> 24343 </dom-module><script>
24344 /** @polymerBehavior Polymer.PaperButtonBehavior */
24345 Polymer.PaperButtonBehaviorImpl = {
24346 properties: {
24347 /**
24348 * The z-depth of this element, from 0-5. Setting to 0 will remove the
24349 * shadow, and each increasing number greater than 0 will be "deeper"
24350 * than the last.
24351 *
24352 * @attribute elevation
24353 * @type number
24354 * @default 1
24355 */
24356 elevation: {
24357 type: Number,
24358 reflectToAttribute: true,
24359 readOnly: true
24360 }
24361 },
24362
24363 observers: [
24364 '_calculateElevation(focused, disabled, active, pressed, receivedFocusFrom Keyboard)',
24365 '_computeKeyboardClass(receivedFocusFromKeyboard)'
24366 ],
24367
24368 hostAttributes: {
24369 role: 'button',
24370 tabindex: '0',
24371 animated: true
24372 },
24373
24374 _calculateElevation: function() {
24375 var e = 1;
24376 if (this.disabled) {
24377 e = 0;
24378 } else if (this.active || this.pressed) {
24379 e = 4;
24380 } else if (this.receivedFocusFromKeyboard) {
24381 e = 3;
24382 }
24383 this._setElevation(e);
24384 },
24385
24386 _computeKeyboardClass: function(receivedFocusFromKeyboard) {
24387 this.toggleClass('keyboard-focus', receivedFocusFromKeyboard);
24388 },
24389
24390 /**
24391 * In addition to `IronButtonState` behavior, when space key goes down,
24392 * create a ripple down effect.
24393 *
24394 * @param {!KeyboardEvent} event .
24395 */
24396 _spaceKeyDownHandler: function(event) {
24397 Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event);
24398 // Ensure that there is at most one ripple when the space key is held down .
24399 if (this.hasRipple() && this.getRipple().ripples.length < 1) {
24400 this._ripple.uiDownAction();
24401 }
24402 },
24403
24404 /**
24405 * In addition to `IronButtonState` behavior, when space key goes up,
24406 * create a ripple up effect.
24407 *
24408 * @param {!KeyboardEvent} event .
24409 */
24410 _spaceKeyUpHandler: function(event) {
24411 Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event);
24412 if (this.hasRipple()) {
24413 this._ripple.uiUpAction();
24414 }
24415 }
24416 };
24417
24418 /** @polymerBehavior */
24419 Polymer.PaperButtonBehavior = [
24420 Polymer.IronButtonState,
24421 Polymer.IronControlState,
24422 Polymer.PaperRippleBehavior,
24423 Polymer.PaperButtonBehaviorImpl
24424 ];
24425 </script>
24426 <style is="custom-style">
24427
24428 :root {
24429
24430 --shadow-transition: {
24431 transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
24432 };
24433
24434 --shadow-none: {
24435 box-shadow: none;
24436 };
24437
24438 /* from http://codepen.io/shyndman/pen/c5394ddf2e8b2a5c9185904b57421cdb */
24439
24440 --shadow-elevation-2dp: {
24441 box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
24442 0 1px 5px 0 rgba(0, 0, 0, 0.12),
24443 0 3px 1px -2px rgba(0, 0, 0, 0.2);
24444 };
24445
24446 --shadow-elevation-3dp: {
24447 box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
24448 0 1px 8px 0 rgba(0, 0, 0, 0.12),
24449 0 3px 3px -2px rgba(0, 0, 0, 0.4);
24450 };
24451
24452 --shadow-elevation-4dp: {
24453 box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14),
24454 0 1px 10px 0 rgba(0, 0, 0, 0.12),
24455 0 2px 4px -1px rgba(0, 0, 0, 0.4);
24456 };
24457
24458 --shadow-elevation-6dp: {
24459 box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14),
24460 0 1px 18px 0 rgba(0, 0, 0, 0.12),
24461 0 3px 5px -1px rgba(0, 0, 0, 0.4);
24462 };
24463
24464 --shadow-elevation-8dp: {
24465 box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
24466 0 3px 14px 2px rgba(0, 0, 0, 0.12),
24467 0 5px 5px -3px rgba(0, 0, 0, 0.4);
24468 };
24469
24470 --shadow-elevation-12dp: {
24471 box-shadow: 0 12px 16px 1px rgba(0, 0, 0, 0.14),
24472 0 4px 22px 3px rgba(0, 0, 0, 0.12),
24473 0 6px 7px -4px rgba(0, 0, 0, 0.4);
24474 };
24475
24476 --shadow-elevation-16dp: {
24477 box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14),
24478 0 6px 30px 5px rgba(0, 0, 0, 0.12),
24479 0 8px 10px -5px rgba(0, 0, 0, 0.4);
24480 };
24481
24482 }
24483
24484 </style>
24485 <dom-module id="paper-material-shared-styles" assetpath="/res/imp/bower_componen ts/paper-material/">
24302 <template> 24486 <template>
24303 <style> 24487 <style>
24304 :host { 24488 :host {
24305 display: block; 24489 display: block;
24490 position: relative;
24491 }
24492
24493 :host([elevation="1"]) {
24494 @apply(--shadow-elevation-2dp);
24495 }
24496
24497 :host([elevation="2"]) {
24498 @apply(--shadow-elevation-4dp);
24499 }
24500
24501 :host([elevation="3"]) {
24502 @apply(--shadow-elevation-6dp);
24503 }
24504
24505 :host([elevation="4"]) {
24506 @apply(--shadow-elevation-8dp);
24507 }
24508
24509 :host([elevation="5"]) {
24510 @apply(--shadow-elevation-16dp);
24306 } 24511 }
24307 </style> 24512 </style>
24513 </template>
24514 </dom-module>
24515
24516
24517 <dom-module id="paper-material" assetpath="/res/imp/bower_components/paper-mater ial/">
24518 <template>
24519 <style include="paper-material-shared-styles"></style>
24520 <style>
24521 :host([animated]) {
24522 @apply(--shadow-transition);
24523 }
24524 </style>
24525
24526 <content></content>
24527 </template>
24528 </dom-module>
24529 <script>
24530 Polymer({
24531 is: 'paper-material',
24532
24533 properties: {
24534 /**
24535 * The z-depth of this element, from 0-5. Setting to 0 will remove the
24536 * shadow, and each increasing number greater than 0 will be "deeper"
24537 * than the last.
24538 *
24539 * @attribute elevation
24540 * @type number
24541 * @default 1
24542 */
24543 elevation: {
24544 type: Number,
24545 reflectToAttribute: true,
24546 value: 1
24547 },
24548
24549 /**
24550 * Set this to true to animate the shadow when setting a new
24551 * `elevation` value.
24552 *
24553 * @attribute animated
24554 * @type boolean
24555 * @default false
24556 */
24557 animated: {
24558 type: Boolean,
24559 reflectToAttribute: true,
24560 value: false
24561 }
24562 }
24563 });
24564 </script>
24565
24566
24567 <dom-module id="paper-button" assetpath="/res/imp/bower_components/paper-button/ ">
24568 <template strip-whitespace="">
24569 <style include="paper-material">
24570 :host {
24571 @apply(--layout-inline);
24572 @apply(--layout-center-center);
24573 position: relative;
24574 box-sizing: border-box;
24575 min-width: 5.14em;
24576 margin: 0 0.29em;
24577 background: transparent;
24578 -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
24579 -webkit-tap-highlight-color: transparent;
24580 font: inherit;
24581 text-transform: uppercase;
24582 outline-width: 0;
24583 border-radius: 3px;
24584 -moz-user-select: none;
24585 -ms-user-select: none;
24586 -webkit-user-select: none;
24587 user-select: none;
24588 cursor: pointer;
24589 z-index: 0;
24590 padding: 0.7em 0.57em;
24591
24592 @apply(--paper-font-common-base);
24593 @apply(--paper-button);
24594 }
24595
24596 :host([raised].keyboard-focus) {
24597 font-weight: bold;
24598 @apply(--paper-button-raised-keyboard-focus);
24599 }
24600
24601 :host(:not([raised]).keyboard-focus) {
24602 font-weight: bold;
24603 @apply(--paper-button-flat-keyboard-focus);
24604 }
24605
24606 :host([disabled]) {
24607 background: #eaeaea;
24608 color: #a8a8a8;
24609 cursor: auto;
24610 pointer-events: none;
24611
24612 @apply(--paper-button-disabled);
24613 }
24614
24615 paper-ripple {
24616 color: var(--paper-button-ink-color);
24617 }
24618 </style>
24619
24620 <content></content>
24621 </template>
24622
24623 <script>
24624 Polymer({
24625 is: 'paper-button',
24626
24627 behaviors: [
24628 Polymer.PaperButtonBehavior
24629 ],
24630
24631 properties: {
24632 /**
24633 * If true, the button should be styled with a shadow.
24634 */
24635 raised: {
24636 type: Boolean,
24637 reflectToAttribute: true,
24638 value: false,
24639 observer: '_calculateElevation'
24640 }
24641 },
24642
24643 _calculateElevation: function() {
24644 if (!this.raised) {
24645 this._setElevation(0);
24646 } else {
24647 Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
24648 }
24649 }
24650
24651 /**
24652 Fired when the animation finishes.
24653 This is useful if you want to wait until
24654 the ripple animation finishes to perform some action.
24655
24656 @event transitionend
24657 Event param: {{node: Object}} detail Contains the animated node.
24658 */
24659 });
24660 </script>
24661 </dom-module>
24662 <dom-module id="task-filters" assetpath="/res/imp/tasklist/">
24663 <template>
24664 <style is="custom-style" include="iron-flex iron-flex-alignment iron-positio ning query-column-filter-style">
24665
24666 </style>
24667
24668 <url-param name="filters" value="{{_filters}}" default_values="[]" multi="">
24669 </url-param>
24670 <url-param name="columns" value="{{columns}}" default_values="[&quot;name&qu ot;,&quot;state&quot;,&quot;created_ts&quot;,&quot;user&quot;]" multi="">
24671 </url-param>
24672 <url-param name="query" value="{{_query}}" default_value="">
24673 </url-param>
24674 <url-param name="limit" default_value="200" value="{{limit}}">
24675 </url-param>
24676
24677 <div class="container horizontal layout">
24678
24679
24680 <div class="narrow-down-selector">
24681 <div>
24682 <paper-input id="filter" label="Search columns and filters" placeholde r="gpu nvidia" value="{{_query}}">
24683 </paper-input>
24684 </div>
24685
24686 <div class="selector side-by-side" title="This shows all task tags and o ther interesting task properties. Mark the check box to add as a column. Select the row to see filter options.">
24687 <iron-selector attr-for-selected="label" selected="{{_primarySelected} }">
24688 <template is="dom-repeat" items="[[_primaryItems]]" as="item">
24689 <div class="selectable item horizontal layout" label="[[item]]">
24690
24691 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(ite m,_query)]]</span>[[_afterBold(item,_query)]]</span>
24692 <span class="flex"></span>
24693 <paper-checkbox noink="" disabled$="[[_cantToggleColumn(item)]]" checked="[[_columnState(item,columns.*)]]" on-change="_toggleColumn">
24694 </paper-checkbox>
24695 </div>
24696 </template>
24697 </iron-selector>
24698 </div>
24699
24700 <div class="selector side-by-side" title="These are all options (if any) that the task list can be filtered on.">
24701 <template is="dom-repeat" id="secondaryList" items="[[_secondaryItems] ]" as="item">
24702 <div class="item horizontal layout" label="[[item]]">
24703
24704 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(item, _query)]]</span>[[_afterBold(item,_query)]]</span>
24705 <span class="flex"></span>
24706 <iron-icon class="icons" icon="icons:arrow-forward" hidden="[[_can tAddFilter(_primarySelected,item,_filters.*)]]" on-tap="_addFilter">
24707 </iron-icon>
24708 </div>
24709 </template>
24710 </div>
24711
24712 <div class="selector side-by-side" title="These tag filters are AND'd to gether and applied to all tasks.">
24713 <template is="dom-repeat" items="[[_filters]]" as="fil">
24714 <div class="item horizontal layout" label="[[fil]]">
24715 <span>[[fil]]</span>
24716 <span class="flex"></span>
24717 <iron-icon class="icons" icon="icons:remove-circle-outline" hidden ="[[_cantRemoveFilter(fil,_filters.*)]]" on-tap="_removeFilter">
24718 </iron-icon>
24719 </div>
24720 </template>
24721 </div>
24722
24723 <div class="side-by-side">
24724 <paper-input id="limit" label="Limit Results" auto-validate="" min="0" max="1000" pattern="[0-9]+" value="{{limit}}">
24725 </paper-input>
24726 </div>
24727 </div>
24728
24729 </div>
24308 24730
24309 </template> 24731 </template>
24310 <script> 24732 <script>
24311 (function(){ 24733 (function(){
24734 // see query-column-filter for more documentation on these properties.
24735 var filterMap = {
24736 state: function(task, s) {
24737 var state = this._attribute(task, "state")[0];
24738 if (s === state) {
24739 return true;
24740 }
24741 if (s === "PENDING_RUNNING") {
24742 return state === "PENDING" || state === "RUNNING";
24743 }
24744 var failure = this._attribute(task, "failure", false)[0];
24745 if (s === "COMPLETED_SUCCESS") {
24746 return state === "COMPLETED" && !failure;
24747 }
24748 if (s === "COMPLETED_FAILURE") {
24749 return state === "COMPLETED" && failure;
24750 }
24751 var tryNum = this._attribute(task, "try_number", "-1")[0];
24752 if (s === "DEDUPED") {
24753 return state === "COMPLETED" && tryNum === "0";
24754 }
24755 },
24756 };
24312 Polymer({ 24757 Polymer({
24313 is: 'task-filters', 24758 is: 'task-filters',
24314 24759
24760 behaviors: [SwarmingBehaviors.QueryColumnFilter],
24761
24315 properties: { 24762 properties: {
24316 // output 24763 // output
24317 columns: { 24764 columns: {
24318 type: Array, 24765 type: Array,
24319 value: function() {
24320 return ["name", "state", "user"];
24321 },
24322 notify: true,
24323 },
24324 filter: {
24325 type: Function,
24326 value: function() {
24327 return function(){
24328 return true;
24329 };
24330 },
24331 notify: true, 24766 notify: true,
24332 }, 24767 },
24333 query_params: { 24768 query_params: {
24334 type: Object, 24769 type: Object,
24770 computed: "_extractQueryParams(_filters.*, limit)",
24335 notify: true, 24771 notify: true,
24336 }, 24772 },
24337 verbose: { 24773
24338 type: Boolean, 24774 // for QueryColumnFilter
24339 value: true, 24775 _filterMap: {
24340 notify: true, 24776 type: Object,
24341 }, 24777 value: function() {
24778 var base = this._commonFilters();
24779 for (var attr in filterMap) {
24780 base[attr] = filterMap[attr];
24781 }
24782 return base;
24783 },
24784 }
24785 },
24786
24787 _cantToggleColumn: function(col) {
24788 // Don't allow the name column to be removed, as the task list is
24789 // basically meaningless without it.
24790 return !col || col === "name" ;
24791 },
24792
24793 _extractQueryParams: function() {
24794 var params = {};
24795 var tags = [];
24796 this._filters.forEach(function(f) {
24797 var split = f.split(this.FILTER_SEP, 1)
24798 var col = split[0];
24799 var rest = f.substring(col.length + this.FILTER_SEP.length);
24800 if (col === "state") {
24801 params["state"] = [rest];
24802 } else {
24803 tags.push(col + this.FILTER_SEP + swarming.alias.unapply(rest))
24804 }
24805 }.bind(this));
24806 params["tags"] = tags;
24807 var lim = Math.floor(this.limit)
24808 if (Number.isInteger(lim)) {
24809 // Clamp the limit
24810 lim = Math.max(lim, 1);
24811 lim = Math.min(1000, lim);
24812 params["limit"] = [lim];
24813 // not !== because limit could be the string "900"
24814 if (this.limit != lim) {
24815 this.set("limit", lim);
24816 }
24817 }
24818 return params;
24342 } 24819 }
24343 }); 24820 });
24344 })(); 24821 })();
24345 </script> 24822 </script>
24346 </dom-module><dom-module id="task-list-data" assetpath="/res/imp/tasklist/"> 24823 </dom-module><dom-module id="task-list-data" assetpath="/res/imp/tasklist/">
24347 <template>
24348 <iron-ajax id="tasklist" url="/_ah/api/swarming/v1/tasks/list" headers="[[au th_headers]]" params="[[query_params]]" handle-as="json" last-response="{{_list} }" loading="{{_busy1}}">
24349 </iron-ajax>
24350
24351
24352 </template>
24353 <script> 24824 <script>
24354 (function(){ 24825 (function(){
24355 Polymer({ 24826 Polymer({
24356 is: 'task-list-data', 24827 is: 'task-list-data',
24357 24828
24358 behaviors: [SwarmingBehaviors.CommonBehavior], 24829 behaviors: [
24830 SwarmingBehaviors.CommonBehavior,
24831 ],
24359 24832
24360 properties: { 24833 properties: {
24361 // inputs 24834 // inputs
24362 auth_headers: { 24835 auth_headers: {
24363 type: Object, 24836 type: Object,
24364 observer: "signIn", 24837 observer: "signIn",
24365 }, 24838 },
24366 query_params: { 24839 query_params: {
24367 type: Object, 24840 type: Object,
24841 observer: "_request",
24368 }, 24842 },
24369 24843
24370 //outputs 24844 // outputs
24371 busy: { 24845 busy: {
24372 type: Boolean, 24846 type: Boolean,
24373 computed: "_or(_busy1)", 24847 computed: "_or(_busy1,_busy2,_busy3)",
24848 notify: true,
24849 },
24850 primary_map: {
24851 type: Object,
24852 computed: "_primaryMap(_tags,_dimensions,tasks)",
24853 notify: true,
24854 },
24855 primary_arr: {
24856 type: Array,
24857 computed: "_primaryArr(primary_map)",
24374 notify: true, 24858 notify: true,
24375 }, 24859 },
24376 tasks: { 24860 tasks: {
24377 type: Array, 24861 type: Array,
24378 computed: "_tasks(_list)", 24862 computed: "_tasks(_list)",
24379 notify: true, 24863 notify: true,
24864 },
24865
24866 // private
24867 _busy1: {
24868 type: Boolean,
24869 value: false
24870 },
24871 _busy2: {
24872 type: Boolean,
24873 value: false
24874 },
24875 _busy3: {
24876 type: Boolean,
24877 value: false
24878 },
24879 _dimensions: {
24880 type: Object,
24881 },
24882 _list: {
24883 type: Object,
24884 },
24885 _tags: {
24886 type: Object,
24887 },
24888 },
24889
24890 signIn: function(){
24891 this._getJsonAsync("_tags", "/_ah/api/swarming/v1/tasks/tags",
24892 "_busy2", this.auth_headers);
24893 this._getJsonAsync("_dimensions","/_ah/api/swarming/v1/bots/dimensions",
24894 "_busy3", this.auth_headers);
24895
24896 this._request();
24897 },
24898
24899 _primaryArr: function(map) {
24900 var arr = Object.keys(map);
24901 arr.sort();
24902 return arr;
24903 },
24904
24905 _primaryMap: function(tags, dims, tasks) {
24906 tags = (tags && tags.tasks_tags) || [];
24907 dims = (dims && dims.bots_dimensions) || [];
24908 tasks = tasks || [];
24909 var map = {};
24910 // We combine all the tags reported by the tags endpoint, all known
24911 // dimensions from the dimensions endpoint, and the tags seen in the
24912 // returned tasks, just in case they didn't show up in the first two.
24913 // This way a user can filter by what the data actually has and can
24914 // discover new tags to filter by.
24915 tags.forEach(function(t) {
24916 if (!map[t.key]) {
24917 map[t.key] = {};
24918 }
24919 var values = t.value || [];
24920 values.forEach(function(v) {
24921 map[t.key][v] = true;
24922 })
24923 });
24924
24925 dims.forEach(function(d) {
24926 var vals = d.value;
24927 if (!map[d.key]) {
24928 map[d.key] = {};
24929 }
24930 vals.forEach(function(v) {
24931 map[d.key][v] = true;
24932 })
24933 });
24934
24935 tasks.forEach(function(t) {
24936 Object.keys(t.tagMap).forEach(function(k) {
24937 var v = t.tagMap[k];
24938 if (!map[k]) {
24939 map[k] = {};
24940 }
24941 map[k][v] = true;
24942 });
24943 });
24944
24945 // Turn the Map<Object,Map<Boolean>> into a Map<Object,Array<String>>
24946 // with all of the aliases applied.
24947 var pMap = {};
24948 for (key in map) {
24949 var values = Object.keys(map[key]);
24950 if (swarming.alias.DIMENSIONS_WITH_ALIASES.indexOf(key) === -1) {
24951 pMap[key] = values;
24952 } else if (key === "gpu") {
24953 var gpus = [];
24954 values.forEach(function(g){
24955 var alias = swarming.alias.gpu(g);
24956 if (alias !== "unknown") {
24957 gpus.push(swarming.alias.apply(g, alias));
24958 } else {
24959 gpus.push(g);
24960 }
24961 }.bind(this));
24962 pMap["gpu"] = gpus;
24963 } else if (key === "device_type") {
24964 var devs = [];
24965 values.forEach(function(dt){
24966 var alias = swarming.alias.android(dt);
24967 if (alias !== "unknown") {
24968 devs.push(swarming.alias.apply(dt, alias));
24969 } else {
24970 devs.push(dt);
24971 }
24972 }.bind(this));
24973 pMap["device_type"] = devs;
24974 } else {
24975 console.log("Unknown alias type: ", d);
24976 }
24380 } 24977 }
24978
24979 // Add some options that might not show up.
24980 pMap["android_devices"].push("0");
24981 pMap["device_os"].push("none");
24982 pMap["device_type"].push("none");
24983 pMap["user"].push("none");
24984
24985 // Custom filter options
24986 pMap["name"] = [];
24987 pMap["state"] = ["PENDING", "RUNNING", "PENDING_RUNNING", "COMPLETED",
24988 "COMPLETED_SUCCESS","COMPLETED_FAILURE", "EXPIRED", "TIMED_OUT",
24989 "BOT_DIED", "CANCELED", "ALL", "DEDUPED"];
24990 pMap["abandoned_ts"] = [];
24991 pMap["completed_ts"] = [];
24992 pMap["costs_usd"] = [];
24993 pMap["created_ts"] = [];
24994 pMap["deduped_from"] = [];
24995 pMap["duration"] = [];
24996 pMap["modified_ts"] = [];
24997 pMap["server_versions"] = [];
24998 pMap["started_ts"] = [];
24999
25000 return pMap;
24381 }, 25001 },
24382 signIn: function(){ 25002
24383 // Auto on iron-ajax means to automatically re-make the request if 25003 _request: function() {
24384 // the url or the query params change. Auto does not trigger if the 25004 // wait until the user has logged in before requesting this.
24385 // [auth] headers change, so we wait until the user is signed in 25005 if (!this.auth_headers) {
24386 // before making any requests. 25006 return;
24387 this.$.tasklist.auto = true; 25007 }
25008 this._getJsonAsync("_list", "/_ah/api/swarming/v1/tasks/list",
25009 "_busy1", this.auth_headers, this.query_params);
24388 }, 25010 },
24389 25011
24390 _tasks: function() { 25012 _tasks: function() {
24391 if (!this._list || !this._list.items) { 25013 if (!this._list || !this._list.items) {
24392 return []; 25014 return [];
24393 } 25015 }
24394 // Do any preprocessing here 25016 // Do any preprocessing here
25017 this._list.items.forEach(function(t) {
25018 var tagMap = {};
25019 t.tags.forEach(function(tag) {
25020 var split = tag.split(":", 1)
25021 var key = split[0];
25022 var rest = tag.substring(key.length + 1);
25023 tagMap[key] = rest;
25024 });
25025 t.tagMap = tagMap;
25026 });
24395 return this._list.items; 25027 return this._list.items;
24396 } 25028 }
24397 }); 25029 });
24398 })(); 25030 })();
24399 </script> 25031 </script>
24400 </dom-module> 25032 </dom-module>
24401 <dom-module id="task-list" assetpath="/res/imp/tasklist/"> 25033 <dom-module id="task-list" assetpath="/res/imp/tasklist/">
24402 <template> 25034 <template>
24403 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style dynamic-table-style"> 25035 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style dynamic-table-style">
24404 25036 task-filters {
25037 margin-bottom: 8px;
25038 margin-right: 10px;
25039 }
24405 .task-list th > span { 25040 .task-list th > span {
24406 /* Leave space for sort-toggle*/ 25041 /* Leave space for sort-toggle*/
24407 padding-right: 30px; 25042 padding-right: 30px;
24408 } 25043 }
25044
25045 /* These colors are from buildbot */
25046 .failed {
25047 background-color: #ffdddd;
25048 }
25049 .died {
25050 background-color: #cccccc;
25051 }
25052 .exception {
25053 background-color: #edd2ff;
25054 }
25055 .pending {
25056 background-color: #fffc6c;
25057 }
24409 </style> 25058 </style>
24410 25059
24411 <url-param name="sort" value="{{_sortstr}}" default_value="id:asc"> 25060 <url-param name="sort" value="{{_sortstr}}" default_value="id:asc">
24412 </url-param> 25061 </url-param>
24413 25062
24414 <swarming-app client_id="[[client_id]]" auth_headers="{{_auth_headers}}" sig ned_in="{{_signed_in}}" busy="[[_busy]]" name="Swarming Task List"> 25063 <swarming-app client_id="[[client_id]]" auth_headers="{{_auth_headers}}" per missions="{{_permissions}}" signed_in="{{_signed_in}}" busy="[[_busy]]" name="Sw arming Task List">
24415 25064
24416 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> 25065 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
24417 25066
24418 <div hidden$="[[_not(_signed_in)]]"> 25067 <div hidden$="[[_not(_signed_in)]]">
24419 <task-list-data auth_headers="[[_auth_headers]]" query_params="[[_query_ params]]" tasks="{{_items}}" busy="{{_busy}}"> 25068 <task-list-data auth_headers="[[_auth_headers]]" query_params="[[_query_ params]]" tasks="{{_items}}" busy="{{_busy}}" primary_map="{{_primary_map}}" pri mary_arr="{{_primary_arr}}">
24420 </task-list-data> 25069 </task-list-data>
24421 25070
25071 <paper-toast id="toast"></paper-toast>
25072
24422 <div class="horizontal layout"> 25073 <div class="horizontal layout">
24423 25074
24424 <task-filters columns="{{_columns}}" query_params="{{_query_params}}" filter="{{_filter}}" verbose="{{_verbose}}"> 25075 <task-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_a rr]]" columns="{{_columns}}" query_params="{{_query_params}}" filter="{{_filter} }">
24425 </task-filters> 25076 </task-filters>
24426 25077
24427 </div> 25078 </div>
24428 25079
24429 <table class="task-list"> 25080 <table class="task-list">
24430 <thead on-sort_change="_sortChange"> 25081 <thead on-sort_change="_sortChange">
24431 25082
24432 <tr> 25083 <tr>
24433 <th> 25084 <th>
24434 <span>Task Name</span> 25085 <span>Task Name</span>
24435 <sort-toggle name="name" current="[[_sort]]"> 25086 <sort-toggle name="name" current="[[_sort]]">
24436 </sort-toggle> 25087 </sort-toggle>
24437 </th> 25088 </th>
24438 25089
24439 <th hidden$="[[_hide('state', _columns.*)]]"> 25090 <th hidden$="[[_hide('state', _columns.*)]]">
24440 <span>Status</span> 25091 <span>State</span>
24441 <sort-toggle name="state" current="[[_sort]]"> 25092 <sort-toggle name="state" current="[[_sort]]">
24442 </sort-toggle> 25093 </sort-toggle>
24443 </th> 25094 </th>
24444 25095
25096 <th hidden$="[[_hide('deduped_from', _columns.*)]]">
25097 <span>Deduped from</span>
25098 <sort-toggle name="deduped_from" current="[[_sort]]">
25099 </sort-toggle>
25100 </th>
25101
24445 <template is="dom-repeat" items="[[_plainColumns]]" as="c"> 25102 <template is="dom-repeat" items="[[_plainColumns]]" as="c">
24446 <th hidden$="[[_hide(c)]]"> 25103 <th hidden$="[[_hide(c)]]">
24447 <span>[[_header(c)]]</span> 25104 <span>[[_header(c)]]</span>
24448 <sort-toggle name="[[c]]" current="[[_sort]]"> 25105 <sort-toggle name="[[c]]" current="[[_sort]]">
24449 </sort-toggle> 25106 </sort-toggle>
24450 </th> 25107 </th>
24451 </template> 25108 </template>
24452 </tr> 25109 </tr>
24453 </thead> 25110 </thead>
24454 <tbody> 25111 <tbody>
24455 <template id="tasks_table" is="dom-repeat" items="[[_filteredSortedI tems]]" as="task" initial-count="50"> 25112 <template id="tasks_table" is="dom-repeat" items="[[_filteredSortedI tems]]" as="task" initial-count="50">
24456 25113
24457 <tr class$="[[_taskClass(task)]]"> 25114 <tr class$="[[_taskClass(task)]]">
24458 <td> 25115 <td>
24459 <a class="center" href$="[[_taskLink(task)]]" target="_blank"> 25116 <a class="center" href$="[[_taskLink(task.task_id)]]" target=" _blank">
24460 [[task.name]] 25117 [[task.name]]
24461 </a> 25118 </a>
24462 </td> 25119 </td>
24463 <td hidden$="[[_hide('state', _columns.*)]]"> 25120 <td hidden$="[[_hide('state', _columns.*)]]">
24464 [[_column('state', task, _verbose)]] 25121 [[_column('state', task)]]
24465 25122 <paper-button raised="" hidden$="[[_cannotCancel(task,_permiss ions)]]" on-tap="_cancelTask">
25123 Cancel
25124 </paper-button>
25125 </td>
25126 <td hidden$="[[_hide('deduped_from', _columns.*)]]">
25127 <a class="center" href$="[[_taskLink(task.deduped_from)]]" tar get="_blank">
25128 [[_column('deduped_from',task)]]
25129 </a>
24466 </td> 25130 </td>
24467 25131
24468 <template is="dom-repeat" items="[[_plainColumns]]" as="c"> 25132 <template is="dom-repeat" items="[[_plainColumns]]" as="c">
24469 <td hidden$="[[_hide(c)]]"> 25133 <td hidden$="[[_hide(c)]]">
24470 [[_column(c, task, _verbose)]] 25134 [[_column(c, task)]]
24471 </td> 25135 </td>
24472 </template> 25136 </template>
24473 25137
24474 </tr> 25138 </tr>
24475 </template> 25139 </template>
24476 </tbody> 25140 </tbody>
24477 </table> 25141 </table>
24478 </div> 25142 </div>
24479 25143
24480 </swarming-app> 25144 </swarming-app>
24481 25145
24482 </template> 25146 </template>
24483 <script> 25147 <script>
24484 (function(){ 25148 (function(){
24485 var specialColumns = ["name", "state"]; 25149 var specialColumns = ["deduped_from", "name", "state"];
24486 var columnMap = {}; 25150 var columnMap = {
25151 costs_usd: function(task) {
25152 return this._attribute(task, "costs_usd", 0)[0];
25153 },
25154 state: function(task) {
25155 var state = this._attribute(task, "state")[0];
25156 if (state === "COMPLETED") {
25157
25158 if (this._attribute(task, "failure", false)[0]) {
25159 return "COMPLETED (FAILURE)";
25160 }
25161 var tryNum = this._attribute(task, "try_number", "-1")[0];
25162 if (tryNum === "0") {
25163 return "COMPLETED (DEDUPED)";
25164 }
25165 return "COMPLETED (SUCCESS)";
25166 }
25167 return state;
25168 },
25169 };
24487 var headerMap = { 25170 var headerMap = {
24488 "user": "Requesting User", 25171 "user": "Requesting User",
24489 }; 25172 };
24490 var specialSort = {}; 25173 var specialSort = {};
24491 25174
24492 Polymer({ 25175 Polymer({
24493 is: 'task-list', 25176 is: 'task-list',
24494 behaviors: [ 25177 behaviors: [
24495 SwarmingBehaviors.DynamicTableBehavior, 25178 SwarmingBehaviors.DynamicTableBehavior,
24496 ], 25179 ],
24497 25180
24498 properties: { 25181 properties: {
24499 client_id: { 25182 client_id: {
24500 type: String, 25183 type: String,
24501 }, 25184 },
24502 25185
24503 // For dynamic table. 25186 // For dynamic table.
24504 _columnMap: { 25187 _columnMap: {
24505 type: Object, 25188 type: Object,
24506 value: columnMap, 25189 value: function() {
25190 var base = this._commonColumns();
25191 for (var attr in columnMap) {
25192 base[attr] = columnMap[attr];
25193 }
25194 return base;
25195 },
24507 }, 25196 },
24508 _headerMap: { 25197 _headerMap: {
24509 type: Object, 25198 type: Object,
24510 value: headerMap, 25199 value: headerMap,
24511 }, 25200 },
24512 _specialColumns: { 25201 _specialColumns: {
24513 type: Array, 25202 type: Array,
24514 value: specialColumns, 25203 value: specialColumns,
24515 }, 25204 },
24516 _specialSort: { 25205 _specialSort: {
24517 type: Object, 25206 type: Object,
24518 value: specialSort, 25207 value: specialSort,
24519 }, 25208 },
24520 }, 25209 },
24521 25210
24522 _attribute: function(task, col, def) { 25211 _attribute: function(task, col, def) {
24523 var retVal = task[col] || [def]; 25212 if (def === undefined) {
25213 def = "none";
25214 }
25215 var retVal = this._tag(task, col) || task[col] || [def];
24524 if (!Array.isArray(retVal)) { 25216 if (!Array.isArray(retVal)) {
24525 return [retVal]; 25217 return [retVal];
24526 } 25218 }
24527 return retVal; 25219 return retVal;
24528 }, 25220 },
24529 25221
24530 _taskLink: function(task) { 25222 _cannotCancel: function(task, permissions) {
25223 return !(permissions && permissions.can_cancel_task &&
25224 this._column("state", task) === "PENDING");
25225 },
25226
25227 _cancelTask: function(e) {
25228 var task = e.model.task;
25229 if (!task || !task.task_id) {
25230 console.log("Missing task info", task);
25231 return
25232 }
25233 var id = task.task_id
25234
25235 // Keep toast displayed until we hear back from the cancel.
25236 this.$.toast.duration = 0;
25237 this.$.toast.text="Canceling task " + id;
25238 this.$.toast.open();
25239 var url = "/_ah/api/swarming/v1/task/" + id +"/cancel";
25240 sk.request("POST", url, undefined, this._auth_headers).then(function(res ponse) {
25241 this.$.toast.close();
25242 this.$.toast.show({
25243 text: "Request sent. Response: "+response,
25244 duration: 3000,
25245 });
25246 }.bind(this)).catch(function(reason) {
25247 console.log("Cancellation failed", reason);
25248 this.$.toast.close();
25249 this.$.toast.show({
25250 text: "Request failed. Reason: "+reason,
25251 duration: 3000,
25252 });
25253 }.bind(this));
25254 },
25255
25256 _tag: function(task, col) {
25257 if (!task || !task.tagMap) {
25258 return undefined;
25259 }
25260 return task.tagMap[col];
25261 },
25262
25263 _taskLink: function(taskId) {
25264 if (!taskId) {
25265 return undefined;
25266 }
24531 // TODO(kjlubick) Make this point to /newui/ when appropriate. 25267 // TODO(kjlubick) Make this point to /newui/ when appropriate.
24532 return "/restricted/task/"+task.task_id; 25268 return "/restricted/task/"+taskId;
24533 }, 25269 },
24534 25270
24535 _taskClass: function(task) { 25271 _taskClass: function(task) {
24536 // TODO(kjlubick): Color tasks? 25272 var state = this._column("state", task);
25273 if (state === "CANCELED" ||state === "TIMED_OUT" || state === "EXPIRED") {
25274 return "exception";
25275 }
25276 if (state === "BOT_DIED") {
25277 return "died";
25278 }
25279 if (state === "COMPLETED (FAILURE)") {
25280 return "failed";
25281 }
25282 if (state === "RUNNING" || state === "PENDING") {
25283 return "pending";
25284 }
24537 return ""; 25285 return "";
24538 } 25286 }
24539 25287
24540 }); 25288 });
24541 })(); 25289 })();
24542 </script> 25290 </script>
24543 </dom-module></div></body></html> 25291 </dom-module></div></body></html>
OLDNEW
« no previous file with comments | « appengine/swarming/elements/Makefile ('k') | appengine/swarming/elements/res/imp/botlist/bot-list.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698