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

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

Powered by Google App Engine
This is Rietveld 408576698