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

Side by Side Diff: dashboard/dashboard/elements/test-picker.html

Issue 2767433002: Start using /list_tests to populate subtest menus in test-picker (Closed)
Patch Set: done Created 3 years, 8 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> 1 <!DOCTYPE html>
2 <!-- 2 <!--
3 Copyright 2016 The Chromium Authors. All rights reserved. 3 Copyright 2016 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be 4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file. 5 found in the LICENSE file.
6 --> 6 -->
7 7
8 <link rel="import" href="/components/iron-flex-layout/iron-flex-layout-classes.h tml"> 8 <link rel="import" href="/components/iron-flex-layout/iron-flex-layout-classes.h tml">
9 <link rel="import" href="/components/paper-button/paper-button.html"> 9 <link rel="import" href="/components/paper-button/paper-button.html">
10 <link rel="import" href="/components/paper-icon-button/paper-icon-button.html"> 10 <link rel="import" href="/components/paper-icon-button/paper-icon-button.html">
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 on-click="onAddButtonClicked" 74 on-click="onAddButtonClicked"
75 disabled$="{{!enableAddSeries}}">Add</paper-button> 75 disabled$="{{!enableAddSeries}}">Add</paper-button>
76 </div> 76 </div>
77 77
78 <div id="suite-description"></div> 78 <div id="suite-description"></div>
79 </template> 79 </template>
80 80
81 <script> 81 <script>
82 'use strict'; 82 'use strict';
83 83
84 // TODO(eakuefner): https://github.com/catapult-project/catapult/issues/3441
sullivan 2017/04/14 00:37:30 Should add a comment as well: generalize this requ
eakuefner 2017/04/14 17:34:26 Done.
85 tr.exportTo('d', function() {
86 class Subtests {
87 constructor() {
88 this.subtests_ = new Map();
89 }
90
91 // TODO(eakuefner): implemement cancellation by wrapping
92 async getSubtestsForPath(path) {
93 if (this.subtests_.has(path)) {
94 return await this.subtests_.get(path);
sullivan 2017/04/14 00:37:30 I'm confused. subtests_ is a Map. this.subtests_.g
eakuefner 2017/04/14 17:34:26 Oops, great catch! I actually meant for the promis
95 }
96 const params = {
97 type: 'pattern'
98 };
99 params.p = `${path}/*`;
100 const fullSubtests = await simple_xhr.asPromise(
101 '/list_tests', params);
102 // TODO(eakuefner): Standardize logic for dealing with test paths on
103 // the client side. See
104 // https://github.com/catapult-project/catapult/issues/3443.
105 const subtests = [];
106 for (const fullSubtest of fullSubtests) {
107 subtests.push({
108 name: fullSubtest.substring(fullSubtest.lastIndexOf('/') + 1)});
109 }
110 this.subtests_[path] = subtests;
111 return subtests;
112 }
113
114 prepopulate(obj) {
115 for (const [path, subtests] of Object.entries(obj)) {
116 this.subtests_.set(path, Promise.resolve(subtests));
117 }
118 }
119 }
120 return {Subtests};
121 });
122
84 Polymer({ 123 Polymer({
85 124
86 is: 'test-picker', 125 is: 'test-picker',
87 properties: { 126 properties: {
88 SUBTEST_LABEL: { 127 SUBTEST_LABEL: {
89 type: String, 128 type: String,
90 value: 'Subtest', 129 value: 'Subtest',
91 }, 130 },
92 DEPRECATED_TAG: { 131 DEPRECATED_TAG: {
93 type: String, 132 type: String,
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
127 disabled: false, 166 disabled: false,
128 }, 167 },
129 { 168 {
130 datalist: [], 169 datalist: [],
131 placeholder: 'Bot', 170 placeholder: 'Bot',
132 disabled: true, 171 disabled: true,
133 } 172 }
134 ]) 173 ])
135 }, 174 },
136 175
176 subtests: {
177 type: Object,
178 value: () => new d.Subtests()
179 },
180
137 xsrfToken: { notify: true } 181 xsrfToken: { notify: true }
138 182
139 }, 183 },
140 184
141 computeAnd: (x, y) => x && y, 185 computeAnd: (x, y) => x && y,
142 186
143 ready: function() { 187 ready: function() {
144 this.pageStateLoading = true; 188 this.pageStateLoading = true;
145 this.hasChart = false; 189 this.hasChart = false;
146 this.enableAddSeries = false; 190 this.enableAddSeries = false;
147 this.selectedSuite = null; 191 this.selectedSuite = null;
148 this.suiteDescription = null; 192 this.suiteDescription = null;
193 this.updatingSubtestMenus = false;
149 this.set('selectionModels.0.datalist', this.getSuiteItems()); 194 this.set('selectionModels.0.datalist', this.getSuiteItems());
150 }, 195 },
151 196
152 testSuitesChanged: function() { 197 testSuitesChanged: function() {
153 this.set('selectionModels.0.datalist', this.getSuiteItems()); 198 this.set('selectionModels.0.datalist', this.getSuiteItems());
154 this.getSelectionMenu(0).items = this.selectionModels[0].datalist; 199 this.getSelectionMenu(0).items = this.selectionModels[0].datalist;
155 }, 200 },
156 201
157 /** 202 /**
158 * Gets a list of menu items for test suites. 203 * Gets a list of menu items for test suites.
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
220 } 265 }
221 return botMenuItems; 266 return botMenuItems;
222 }, 267 },
223 268
224 /** 269 /**
225 * Handles dropdown menu select; updates the subsequent menu accordingly. 270 * Handles dropdown menu select; updates the subsequent menu accordingly.
226 */ 271 */
227 onDropdownSelect: function(event) { 272 onDropdownSelect: function(event) {
228 var model = event.model; 273 var model = event.model;
229 var boxIndex = model.index; 274 var boxIndex = model.index;
275 if (this.updatingSubtestMenus) return;
230 if (boxIndex === undefined) { 276 if (boxIndex === undefined) {
231 return; 277 return;
232 } else if (boxIndex == 0) { 278 } else if (boxIndex == 0) {
233 this.updateTestSuiteDescription(); 279 this.updateTestSuiteDescription();
234 this.updateBotMenu(); 280 this.updateBotMenu();
235 } else if (boxIndex == 1) {
236 this.sendSubtestRequest();
237 } else { 281 } else {
238 // Update all the next dropdown menus for subtests. 282 // Update all the next dropdown menus for subtests.
239 this.updateSubtestMenus(boxIndex + 1); 283 this.updateSubtestMenus(boxIndex + 1);
240 } 284 }
241 }, 285 },
242 286
243 updateTestSuiteDescription: function() { 287 updateTestSuiteDescription: function() {
244 // Display the test suite description if there is one. 288 // Display the test suite description if there is one.
245 var descriptionElement = this.$['suite-description']; 289 var descriptionElement = this.$['suite-description'];
246 var suite = this.getSelectionMenu(0).selectedName; 290 var suite = this.getSelectionMenu(0).selectedName;
247 if (!this.testSuites[suite]) { 291 if (!this.testSuites[suite]) {
248 Polymer.dom(descriptionElement).innerHTML = ''; 292 Polymer.dom(descriptionElement).innerHTML = '';
249 return; 293 return;
250 } 294 }
251 295
252 var description = this.testSuites[suite]['des']; 296 var description = this.testSuites[suite]['des'];
253 if (description) { 297 if (description) {
254 var descriptionHTML = '<b>' + suite + '</b>: '; 298 var descriptionHTML = '<b>' + suite + '</b>: ';
255 descriptionHTML += this.convertMarkdownLinks(description); 299 descriptionHTML += this.convertMarkdownLinks(description);
256 Polymer.dom(descriptionElement).innerHTML = descriptionHTML; 300 Polymer.dom(descriptionElement).innerHTML = descriptionHTML;
257 } else { 301 } else {
258 Polymer.dom(descriptionElement).innerHTML = ''; 302 Polymer.dom(descriptionElement).innerHTML = '';
259 } 303 }
260 }, 304 },
261 305
262 /** 306 /**
263 * Updates bot dropdown menu with bot items. 307 * Updates bot dropdown menu with bot items.
264 */ 308 */
265 updateBotMenu: function() { 309 updateBotMenu: async function() {
266 var menu = this.getSelectionMenu(1); 310 var menu = this.getSelectionMenu(1);
267 var botItems = this.getBotItems(); 311 var botItems = this.getBotItems();
268 menu.set('items', botItems); 312 menu.set('items', botItems);
269 menu.set('disabled', botItems.length === 0); 313 menu.set('disabled', botItems.length === 0);
270 this.subtestDict = null; 314 this.subtestDict = null;
271 // If there's a selection, send a subtest request. 315 // If there's a selection, update the subtest menus.
272 if (menu.selectedItem) { 316 if (menu.selectedItem) {
273 this.sendSubtestRequest(); 317 this.updatingSubtestMenus = true;
318 await this.updateSubtestMenus(2);
319 this.updatingSubtestMenus = false;
274 } else { 320 } else {
275 // Clear all subtest menus. 321 // Clear all subtest menus.
276 this.splice('selectionModels', 2); 322 this.splice('selectionModels', 2);
277 } 323 }
278 this.updateAddButtonState(); 324 this.updateAddButtonState();
279 }, 325 },
280 326
281 /** 327 /**
282 * Sends a request for subtestDict base on selected test suite and bot.
283 */
284 sendSubtestRequest: function() {
285 if (this.subtestXhr) {
286 this.subtestXhr.abort();
287 this.subtestXhr = null;
288 }
289 var bot = this.getCheckedBot();
290 // If no bot is selected, just leave the current subtests.
291 if (bot === null) {
292 return;
293 }
294 var suite = this.getCheckedSuite();
295 if (!suite) {
296 return;
297 }
298
299 this.loading = true;
300
301 var params = {
302 type: 'sub_tests',
303 suite: suite,
304 bots: bot,
305 xsrf_token: this.xsrfToken
306 };
307 this.subtestXhr = simple_xhr.send(
308 '/list_tests',
309 params,
310 function(response) {
311 this.loading = false;
312 this.subtestDict = response;
313 // Start at first subtest menu.
314 this.updateSubtestMenus(2);
315 }.bind(this),
316 function(error) {
317 // TODO: Display error.
318 this.loading = false;
319 }.bind(this)
320 );
321 },
322
323 /**
324 * Updates all subtest menus starting at 'startIndex'. 328 * Updates all subtest menus starting at 'startIndex'.
325 */ 329 */
326 updateSubtestMenus: function(startIndex) { 330 updateSubtestMenus: async function(startIndex) {
327 var subtestDict = this.getSubtestAtIndex(startIndex); 331 let subtests = await this.subtests.getSubtestsForPath(
332 this.getCurrentSelectedPathUpTo(startIndex));
328 333
329 // Update existing subtest menu. 334 // Update existing subtest menu.
330 for (var i = startIndex; i < this.selectionModels.length; i++) { 335 for (var i = startIndex; i < this.selectionModels.length; i++) {
331 // Remove the rest of the menu if no subtestDict. 336 // Remove the rest of the menu if no subtests.
332 if (!subtestDict || Object.keys(subtestDict).length == 0) { 337 if (subtests.length === 0) {
333 this.splice('selectionModels', i); 338 this.splice('selectionModels', i);
334 this.updateAddButtonState(); 339 this.updateAddButtonState();
335 return; 340 return;
336 } 341 }
337 var subtestItems = this.getSubtestItems(subtestDict); 342 const menu = this.getSelectionMenu(i);
338 var menu = this.getSelectionMenu(i); 343 this.updatingSubtestMenus = true;
339 menu.set('items', subtestItems); 344 menu.set('items', subtests);
345 this.updatingSubtestMenus = false;
340 346
341 // If there are selected item, update next menu. 347 // If there is a selected item, update the next menu.
342 if (menu.selectedItem) { 348 if (menu.selectedItem) {
343 subtestDict = subtestDict[menu.selectedName]['sub_tests']; 349 const selectedPath = this.getCurrentSelectedPathUpTo(i + 1, false);
350 if (selectedPath !== undefined) {
351 subtests = await this.subtests.getSubtestsForPath(selectedPath);
352 } else {
353 subtests = [];
354 }
344 } else { 355 } else {
345 subtestDict = null; 356 subtests = [];
346 } 357 }
347 } 358 }
348 359
349 // Check if we still need to add a subtest menu. 360 // If we reached the last iteration but still have subtests, that means
350 if (subtestDict && Object.keys(subtestDict).length > 0) { 361 // that the last extant subtest selection still has subtests and we need
351 var subtestItems = this.getSubtestItems(subtestDict); 362 // to put those in a new menu.
363 if (subtests.length > 0) {
352 this.push('selectionModels', { 364 this.push('selectionModels', {
353 placeholder: this.SUBTEST_LABEL, 365 placeholder: this.SUBTEST_LABEL,
354 datalist: subtestItems, 366 datalist: subtests,
355 disabled: false, 367 disabled: false,
356 }); 368 });
369 this.updatingSubtestMenus = true;
357 Polymer.dom.flush(); 370 Polymer.dom.flush();
358 var menu = this.getSelectionMenu(this.selectionModels.length - 1); 371 const menu = this.getSelectionMenu(this.selectionModels.length - 1);
359 menu.set('items', subtestItems); 372 menu.set('items', subtests);
373 this.updatingSubtestMenus = false;
360 } 374 }
361 375
362 this.updateAddButtonState(); 376 this.updateAddButtonState();
363 }, 377 },
364 378
365 updateAddButtonState: async function() { 379 updateAddButtonState: async function() {
366 this.enableAddSeries = ( 380 this.enableAddSeries = (
367 (await this.getCurrentSelection()) instanceof Array); 381 (await this.getCurrentSelection()) instanceof Array);
368 }, 382 },
369 383
370 getSubtestAtIndex: function(index) {
371 var subtestDict = this.subtestDict;
372 for (var i = 2; i < index; i++) {
373 var test = this.getSelectionMenu(i).selectedName;
374 if (test in subtestDict) {
375 subtestDict = subtestDict[test]['sub_tests'];
376 } else {
377 return null;
378 }
379 }
380 return subtestDict;
381 },
382
383 getSubtestItems: function(subtestDict) {
384 var subtestItems = [];
385 var subtestNames = Object.keys(subtestDict).sort();
386 for (var i = 0; i < subtestNames.length; i++) {
387 var name = subtestNames[i];
388 subtestItems.push({
389 name: name,
390 tag: (subtestDict[name]['deprecated'] ? this.DEPRECATED_TAG : '')
391 });
392 }
393 return subtestItems;
394 },
395
396 getCheckedBot: function() { 384 getCheckedBot: function() {
397 var botMenu = this.getSelectionMenu(1); 385 var botMenu = this.getSelectionMenu(1);
398 if (botMenu.selectedItem) { 386 if (botMenu.selectedItem) {
399 let item = botMenu.selectedItem; 387 let item = botMenu.selectedItem;
400 return item['group'] + '/' + item['name']; 388 return item['group'] + '/' + item['name'];
401 } 389 }
402 return null; 390 return null;
403 }, 391 },
404 392
405 getCheckedSuite: function() { 393 getCheckedSuite: function() {
(...skipping 24 matching lines...) Expand all
430 * Fires add event on 'Add' button clicked. 418 * Fires add event on 'Add' button clicked.
431 */ 419 */
432 onAddButtonClicked: function(event, detail) { 420 onAddButtonClicked: function(event, detail) {
433 this.fire('add'); 421 this.fire('add');
434 }, 422 },
435 423
436 /** 424 /**
437 * Gets the current selection from the menus. 425 * Gets the current selection from the menus.
438 */ 426 */
439 getCurrentSelection: async function() { 427 getCurrentSelection: async function() {
440 var level = 0; 428 const path = this.getCurrentSelectedPathUpTo(-1, true);
441 var parts = [];
442 while (true) {
443 var menu = this.getSelectionMenu(level);
444 if (!menu || !menu.selectedItem) {
445 // A selection is only valid if it specifies at least one subtest
446 // component, which is the third level.
447 if (level <= 2) return null;
448 break;
449 } else {
450 // We want to collect all the subtest components so we can form
451 // the full test path after this loop is done.
452 if (level >= 2) parts.push(menu.selectedItem.name);
453 }
454 level += 1;
455 }
456
457 var suite = this.getSelectionMenu(0).selectedItem.name;
458 var bot = this.getCheckedBot();
459 parts.unshift(suite);
460 parts.unshift(bot);
461
462 var path = parts.join('/');
463 429
464 // If the paths are the same, this means that the selected path has 430 // If the paths are the same, this means that the selected path has
465 // already been confirmed as valid by the previous request to 431 // already been confirmed as valid by the previous request to
466 // /list_tests. 432 // /list_tests.
467 // TODO(eakuefner): Update the naming of these variables to make this 433 // TODO(eakuefner): Update the naming of these variables to make this
468 // possible to understand without the comment. 434 // possible to understand without the comment.
469 if (this.currentSelectedPath_ === path) { 435 if (this.currentSelectedPath_ === path) {
470 return this.currentSelectedTests_; 436 return this.currentSelectedTests_;
471 } 437 }
472 438
473 this.loading = true; 439 this.loading = true;
474 let selectedTests; 440 let selectedTests;
475 let unselectedTests; 441 let unselectedTests;
476 try { 442 try {
477 [selectedTests, unselectedTests] = await Promise.all([ 443 [selectedTests, unselectedTests] = await Promise.all([
478 this.getSubtestsForPath(path, true), 444 this.getSubtestsForPath(path, true),
479 this.getSubtestsForPath(path, false)]); 445 this.getSubtestsForPath(path, false)]);
480 } catch (e) { 446 } catch (e) {
481 // TODO(eakuefner): Improve this error handling.
482 this.loading = false; 447 this.loading = false;
483 return null; 448 return null;
484 } 449 }
485 this.loading = false; 450 this.loading = false;
486 this.currentSelectedPath_ = path; 451 this.currentSelectedPath_ = path;
487 this.currentSelectedTests_ = selectedTests; 452 this.currentSelectedTests_ = selectedTests;
488 this.currentUnselectedTests_ = unselectedTests; 453 this.currentUnselectedTests_ = unselectedTests;
489 this.updateSubtestMenus(2); 454 this.updatingSubtestMenus = true;
455 await this.updateSubtestMenus(2);
456 this.updatingSubtestMenus = false;
490 return selectedTests; 457 return selectedTests;
491 }, 458 },
492 459
460 getCurrentSelectedPathUpTo: function(maxLevel, onlyValid) {
461 let level = 0;
462 const parts = [];
463 while (true) {
464 if (maxLevel !== -1 && level >= maxLevel) {
465 break;
466 }
467 const menu = this.getSelectionMenu(level);
468 if (onlyValid && (!menu || !menu.selectedItem)) {
469 // A selection is only valid if it specifies at least one subtest
470 // component, which is the third level.
471 if (level <= 2) return undefined;
472 break;
473 } else {
474 // We want to collect all the subtest components so we can form
475 // the full test path after this loop is done.
476 if (level >= 2) parts.push(menu.selectedItem.name);
477 }
478 level += 1;
479 }
480
481 const suite = this.getSelectionMenu(0).selectedItem.name;
482 const bot = this.getCheckedBot();
483 parts.unshift(suite);
484 parts.unshift(bot);
485
486 if (parts.length < maxLevel) return undefined;
487 return parts.join('/');
488 },
489
493 getCurrentSelectedPath: function() { 490 getCurrentSelectedPath: function() {
494 return this.currentSelectedPath_; 491 return this.currentSelectedPath_;
495 }, 492 },
496 493
497 getCurrentUnselected: function() { 494 getCurrentUnselected: function() {
498 return this.currentUnselectedTests_; 495 return this.currentUnselectedTests_;
499 }, 496 },
500 497
501 getSubtestsForPath: async function(path, returnSelected) { 498 getSubtestsForPath: async function(path, returnSelected) {
502 // TODO(eakuefner): Reimplement cancellation and memoize. 499 // TODO(eakuefner): Reimplement cancellation and memoize.
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
551 * Converts a link in markdown format to a HTML link (anchor elements). 548 * Converts a link in markdown format to a HTML link (anchor elements).
552 * @param {string} text A link in markdown format. 549 * @param {string} text A link in markdown format.
553 * @return {string} A hyperlink string. 550 * @return {string} A hyperlink string.
554 */ 551 */
555 convertMarkdownLinks: function(text) { 552 convertMarkdownLinks: function(text) {
556 return text.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>'); 553 return text.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>');
557 } 554 }
558 }); 555 });
559 </script> 556 </script>
560 </dom-module> 557 </dom-module>
OLDNEW
« no previous file with comments | « no previous file | dashboard/dashboard/elements/test-picker-test.html » ('j') | dashboard/dashboard/elements/test-picker-test.html » ('J')

Powered by Google App Engine
This is Rietveld 408576698