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

Side by Side Diff: gm/rebaseline_server/static/loader.js

Issue 856103002: Revert "Revert "delete old things!"" (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 5 years, 11 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 | « gm/rebaseline_server/static/live-view.html ('k') | gm/rebaseline_server/static/new/bower.json » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Loader:
3 * Reads GM result reports written out by results.py, and imports
4 * them into $scope.extraColumnHeaders and $scope.imagePairs .
5 */
6 var Loader = angular.module(
7 'Loader',
8 ['ConstantsModule']
9 );
10
11 Loader.directive(
12 'resultsUpdatedCallbackDirective',
13 ['$timeout',
14 function($timeout) {
15 return function(scope, element, attrs) {
16 if (scope.$last) {
17 $timeout(function() {
18 scope.resultsUpdatedCallback();
19 });
20 }
21 };
22 }
23 ]
24 );
25
26 // TODO(epoger): Combine ALL of our filtering operations (including
27 // truncation) into this one filter, so that runs most efficiently?
28 // (We would have to make sure truncation still took place after
29 // sorting, though.)
30 Loader.filter(
31 'removeHiddenImagePairs',
32 function(constants) {
33 return function(unfilteredImagePairs, filterableColumnNames, showingColumnVa lues,
34 viewingTab) {
35 var filteredImagePairs = [];
36 for (var i = 0; i < unfilteredImagePairs.length; i++) {
37 var imagePair = unfilteredImagePairs[i];
38 var extraColumnValues = imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMN S];
39 var allColumnValuesAreVisible = true;
40 // Loop over all columns, and if any of them contain values not found in
41 // showingColumnValues[columnName], don't include this imagePair.
42 //
43 // We use this same filtering mechanism regardless of whether each colum n
44 // has USE_FREEFORM_FILTER set or not; if that flag is set, then we will
45 // have already used the freeform text entry block to populate
46 // showingColumnValues[columnName].
47 for (var j = 0; j < filterableColumnNames.length; j++) {
48 var columnName = filterableColumnNames[j];
49 var columnValue = extraColumnValues[columnName];
50 if (!showingColumnValues[columnName][columnValue]) {
51 allColumnValuesAreVisible = false;
52 break;
53 }
54 }
55 if (allColumnValuesAreVisible && (viewingTab == imagePair.tab)) {
56 filteredImagePairs.push(imagePair);
57 }
58 }
59 return filteredImagePairs;
60 };
61 }
62 );
63
64 /**
65 * Limit the input imagePairs to some max number, and merge identical rows
66 * (adjacent rows which have the same (imageA, imageB) pair).
67 *
68 * @param unfilteredImagePairs imagePairs to filter
69 * @param maxPairs maximum number of pairs to output, or <0 for no limit
70 * @param mergeIdenticalRows if true, merge identical rows by setting
71 * ROWSPAN>1 on the first merged row, and ROWSPAN=0 for the rest
72 */
73 Loader.filter(
74 'mergeAndLimit',
75 function(constants) {
76 return function(unfilteredImagePairs, maxPairs, mergeIdenticalRows) {
77 var numPairs = unfilteredImagePairs.length;
78 if ((maxPairs > 0) && (maxPairs < numPairs)) {
79 numPairs = maxPairs;
80 }
81 var filteredImagePairs = [];
82 if (!mergeIdenticalRows || (numPairs == 1)) {
83 // Take a shortcut if we're not merging identical rows.
84 // We still need to set ROWSPAN to 1 for each row, for the HTML viewer.
85 for (var i = numPairs-1; i >= 0; i--) {
86 var imagePair = unfilteredImagePairs[i];
87 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1;
88 filteredImagePairs[i] = imagePair;
89 }
90 } else if (numPairs > 1) {
91 // General case--there are at least 2 rows, so we may need to merge some .
92 // Work from the bottom up, so we can keep a running total of how many
93 // rows should be merged, and set ROWSPAN of the top row accordingly.
94 var imagePair = unfilteredImagePairs[numPairs-1];
95 var nextRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] ;
96 var nextRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] ;
97 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1;
98 filteredImagePairs[numPairs-1] = imagePair;
99 for (var i = numPairs-2; i >= 0; i--) {
100 imagePair = unfilteredImagePairs[i];
101 var thisRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_UR L];
102 var thisRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_UR L];
103 if ((thisRowImageAUrl == nextRowImageAUrl) &&
104 (thisRowImageBUrl == nextRowImageBUrl)) {
105 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] =
106 filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] + 1;
107 filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] = 0;
108 } else {
109 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1;
110 nextRowImageAUrl = thisRowImageAUrl;
111 nextRowImageBUrl = thisRowImageBUrl;
112 }
113 filteredImagePairs[i] = imagePair;
114 }
115 } else {
116 // No results.
117 }
118 return filteredImagePairs;
119 };
120 }
121 );
122
123
124 Loader.controller(
125 'Loader.Controller',
126 function($scope, $http, $filter, $location, $log, $timeout, constants) {
127 $scope.readyToDisplay = false;
128 $scope.constants = constants;
129 $scope.windowTitle = "Loading GM Results...";
130 $scope.resultsToLoad = $location.search().resultsToLoad;
131 $scope.loadingMessage = "please wait...";
132
133 var currSortAsc = true;
134
135
136 /**
137 * On initial page load, load a full dictionary of results.
138 * Once the dictionary is loaded, unhide the page elements so they can
139 * render the data.
140 */
141 $http.get($scope.resultsToLoad).success(
142 function(data, status, header, config) {
143 var dataHeader = data[constants.KEY__ROOT__HEADER];
144 if (dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] !=
145 constants.VALUE__HEADER__SCHEMA_VERSION) {
146 $scope.loadingMessage = "ERROR: Got JSON file with schema version "
147 + dataHeader[constants.KEY__HEADER__SCHEMA_VERSION]
148 + " but expected schema version "
149 + constants.VALUE__HEADER__SCHEMA_VERSION;
150 } else if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) {
151 // Apply the server's requested reload delay to local time,
152 // so we will wait the right number of seconds regardless of clock
153 // skew between client and server.
154 var reloadDelayInSeconds =
155 dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] -
156 dataHeader[constants.KEY__HEADER__TIME_UPDATED];
157 var timeNow = new Date().getTime();
158 var timeToReload = timeNow + reloadDelayInSeconds * 1000;
159 $scope.loadingMessage =
160 "server is still loading results; will retry at " +
161 $scope.localTimeString(timeToReload / 1000);
162 $timeout(
163 function(){location.reload();},
164 timeToReload - timeNow);
165 } else {
166 $scope.loadingMessage = "processing data, please wait...";
167
168 $scope.header = dataHeader;
169 $scope.extraColumnHeaders = data[constants.KEY__ROOT__EXTRACOLUMNHEADE RS];
170 $scope.orderedColumnNames = data[constants.KEY__ROOT__EXTRACOLUMNORDER ];
171 $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS];
172 $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS];
173
174 // set the default sort column and make it ascending.
175 $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES;
176 $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF;
177 currSortAsc = true;
178
179 $scope.showSubmitAdvancedSettings = false;
180 $scope.submitAdvancedSettings = {};
181 $scope.submitAdvancedSettings[
182 constants.KEY__EXPECTATIONS__REVIEWED] = true;
183 $scope.submitAdvancedSettings[
184 constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false;
185 $scope.submitAdvancedSettings['bug'] = '';
186
187 // Create the list of tabs (lists into which the user can file each
188 // test). This may vary, depending on isEditable.
189 $scope.tabs = [
190 'Unfiled', 'Hidden'
191 ];
192 if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) {
193 $scope.tabs = $scope.tabs.concat(
194 ['Pending Approval']);
195 }
196 $scope.defaultTab = $scope.tabs[0];
197 $scope.viewingTab = $scope.defaultTab;
198
199 // Track the number of results on each tab.
200 $scope.numResultsPerTab = {};
201 for (var i = 0; i < $scope.tabs.length; i++) {
202 $scope.numResultsPerTab[$scope.tabs[i]] = 0;
203 }
204 $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length;
205
206 // Add index and tab fields to all records.
207 for (var i = 0; i < $scope.imagePairs.length; i++) {
208 $scope.imagePairs[i].index = i;
209 $scope.imagePairs[i].tab = $scope.defaultTab;
210 }
211
212 // Arrays within which the user can toggle individual elements.
213 $scope.selectedImagePairs = [];
214
215 // Set up filters.
216 //
217 // filterableColumnNames is a list of all column names we can filter o n.
218 // allColumnValues[columnName] is a list of all known values
219 // for a given column.
220 // showingColumnValues[columnName] is a set indicating which values
221 // in a given column would cause us to show a row, rather than hiding it.
222 //
223 // columnStringMatch[columnName] is a string used as a pattern to gene rate
224 // showingColumnValues[columnName] for columns we filter using free-fo rm text.
225 // It is ignored for any columns with USE_FREEFORM_FILTER == false.
226 $scope.filterableColumnNames = [];
227 $scope.allColumnValues = {};
228 $scope.showingColumnValues = {};
229 $scope.columnStringMatch = {};
230
231 angular.forEach(
232 Object.keys($scope.extraColumnHeaders),
233 function(columnName) {
234 var columnHeader = $scope.extraColumnHeaders[columnName];
235 if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE] ) {
236 $scope.filterableColumnNames.push(columnName);
237 $scope.allColumnValues[columnName] = $scope.columnSliceOf2DArray (
238 columnHeader[constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_C OUNTS], 0);
239 $scope.showingColumnValues[columnName] = {};
240 $scope.toggleValuesInSet($scope.allColumnValues[columnName],
241 $scope.showingColumnValues[columnName]) ;
242 $scope.columnStringMatch[columnName] = "";
243 }
244 }
245 );
246
247 // TODO(epoger): Special handling for RESULT_TYPE column:
248 // by default, show only KEY__RESULT_TYPE__FAILED results
249 $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE] = {};
250 $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE][
251 constants.KEY__RESULT_TYPE__FAILED] = true;
252
253 // Set up mapping for URL parameters.
254 // parameter name -> copier object to load/save parameter value
255 $scope.queryParameters.map = {
256 'resultsToLoad': $scope.queryParameters.copiers.simple,
257 'displayLimitPending': $scope.queryParameters.copiers.simple,
258 'showThumbnailsPending': $scope.queryParameters.copiers.simple,
259 'mergeIdenticalRowsPending': $scope.queryParameters.copiers.simple,
260 'imageSizePending': $scope.queryParameters.copiers.simple,
261 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
262 'sortColumnKey': $scope.queryParameters.copiers.simple,
263 };
264 // Some parameters are handled differently based on whether they USE_F REEFORM_FILTER.
265 angular.forEach(
266 $scope.filterableColumnNames,
267 function(columnName) {
268 if ($scope.extraColumnHeaders[columnName]
269 [constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) {
270 $scope.queryParameters.map[columnName] =
271 $scope.queryParameters.copiers.columnStringMatch;
272 } else {
273 $scope.queryParameters.map[columnName] =
274 $scope.queryParameters.copiers.showingColumnValuesSet;
275 }
276 }
277 );
278
279 // If any defaults were overridden in the URL, get them now.
280 $scope.queryParameters.load();
281
282 // Any image URLs which are relative should be relative to the JSON
283 // file's source directory; absolute URLs should be left alone.
284 var baseUrlKey = constants.KEY__IMAGESETS__FIELD__BASE_URL;
285 angular.forEach(
286 $scope.imageSets,
287 function(imageSet) {
288 var baseUrl = imageSet[baseUrlKey];
289 if ((baseUrl.substring(0, 1) != '/') &&
290 (baseUrl.indexOf('://') == -1)) {
291 imageSet[baseUrlKey] = $scope.resultsToLoad + '/../' + baseUrl;
292 }
293 }
294 );
295
296 $scope.readyToDisplay = true;
297 $scope.updateResults();
298 $scope.loadingMessage = "";
299 $scope.windowTitle = "Current GM Results";
300
301 $timeout( function() {
302 make_results_header_sticky();
303 });
304 }
305 }
306 ).error(
307 function(data, status, header, config) {
308 $scope.loadingMessage = "FAILED to load.";
309 $scope.windowTitle = "Failed to Load GM Results";
310 }
311 );
312
313
314 //
315 // Select/Clear/Toggle all tests.
316 //
317
318 /**
319 * Select all currently showing tests.
320 */
321 $scope.selectAllImagePairs = function() {
322 var numImagePairsShowing = $scope.limitedImagePairs.length;
323 for (var i = 0; i < numImagePairsShowing; i++) {
324 var index = $scope.limitedImagePairs[i].index;
325 if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) {
326 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
327 }
328 }
329 };
330
331 /**
332 * Deselect all currently showing tests.
333 */
334 $scope.clearAllImagePairs = function() {
335 var numImagePairsShowing = $scope.limitedImagePairs.length;
336 for (var i = 0; i < numImagePairsShowing; i++) {
337 var index = $scope.limitedImagePairs[i].index;
338 if ($scope.isValueInArray(index, $scope.selectedImagePairs)) {
339 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
340 }
341 }
342 };
343
344 /**
345 * Toggle selection of all currently showing tests.
346 */
347 $scope.toggleAllImagePairs = function() {
348 var numImagePairsShowing = $scope.limitedImagePairs.length;
349 for (var i = 0; i < numImagePairsShowing; i++) {
350 var index = $scope.limitedImagePairs[i].index;
351 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
352 }
353 };
354
355 /**
356 * Toggle selection state of a subset of the currently showing tests.
357 *
358 * @param startIndex index within $scope.limitedImagePairs of the first
359 * test to toggle selection state of
360 * @param num number of tests (in a contiguous block) to toggle
361 */
362 $scope.toggleSomeImagePairs = function(startIndex, num) {
363 var numImagePairsShowing = $scope.limitedImagePairs.length;
364 for (var i = startIndex; i < startIndex + num; i++) {
365 var index = $scope.limitedImagePairs[i].index;
366 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
367 }
368 };
369
370
371 //
372 // Tab operations.
373 //
374
375 /**
376 * Change the selected tab.
377 *
378 * @param tab (string): name of the tab to select
379 */
380 $scope.setViewingTab = function(tab) {
381 $scope.viewingTab = tab;
382 $scope.updateResults();
383 };
384
385 /**
386 * Move the imagePairs in $scope.selectedImagePairs to a different tab,
387 * and then clear $scope.selectedImagePairs.
388 *
389 * @param newTab (string): name of the tab to move the tests to
390 */
391 $scope.moveSelectedImagePairsToTab = function(newTab) {
392 $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
393 $scope.selectedImagePairs = [];
394 $scope.updateResults();
395 };
396
397 /**
398 * Move a subset of $scope.imagePairs to a different tab.
399 *
400 * @param imagePairIndices (array of ints): indices into $scope.imagePairs
401 * indicating which test results to move
402 * @param newTab (string): name of the tab to move the tests to
403 */
404 $scope.moveImagePairsToTab = function(imagePairIndices, newTab) {
405 var imagePairIndex;
406 var numImagePairs = imagePairIndices.length;
407 for (var i = 0; i < numImagePairs; i++) {
408 imagePairIndex = imagePairIndices[i];
409 $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--;
410 $scope.imagePairs[imagePairIndex].tab = newTab;
411 }
412 $scope.numResultsPerTab[newTab] += numImagePairs;
413 };
414
415
416 //
417 // $scope.queryParameters:
418 // Transfer parameter values between $scope and the URL query string.
419 //
420 $scope.queryParameters = {};
421
422 // load and save functions for parameters of each type
423 // (load a parameter value into $scope from nameValuePairs,
424 // save a parameter value from $scope into nameValuePairs)
425 $scope.queryParameters.copiers = {
426 'simple': {
427 'load': function(nameValuePairs, name) {
428 var value = nameValuePairs[name];
429 if (value) {
430 $scope[name] = value;
431 }
432 },
433 'save': function(nameValuePairs, name) {
434 nameValuePairs[name] = $scope[name];
435 }
436 },
437
438 'columnStringMatch': {
439 'load': function(nameValuePairs, name) {
440 var value = nameValuePairs[name];
441 if (value) {
442 $scope.columnStringMatch[name] = value;
443 }
444 },
445 'save': function(nameValuePairs, name) {
446 nameValuePairs[name] = $scope.columnStringMatch[name];
447 }
448 },
449
450 'showingColumnValuesSet': {
451 'load': function(nameValuePairs, name) {
452 var value = nameValuePairs[name];
453 if (value) {
454 var valueArray = value.split(',');
455 $scope.showingColumnValues[name] = {};
456 $scope.toggleValuesInSet(valueArray, $scope.showingColumnValues[name ]);
457 }
458 },
459 'save': function(nameValuePairs, name) {
460 nameValuePairs[name] = Object.keys($scope.showingColumnValues[name]).j oin(',');
461 }
462 },
463
464 };
465
466 // Loads all parameters into $scope from the URL query string;
467 // any which are not found within the URL will keep their current value.
468 $scope.queryParameters.load = function() {
469 var nameValuePairs = $location.search();
470
471 // If urlSchemaVersion is not specified, we assume the current version.
472 var urlSchemaVersion = constants.URL_VALUE__SCHEMA_VERSION__CURRENT;
473 if (constants.URL_KEY__SCHEMA_VERSION in nameValuePairs) {
474 urlSchemaVersion = nameValuePairs[constants.URL_KEY__SCHEMA_VERSION];
475 } else if ('hiddenResultTypes' in nameValuePairs) {
476 // The combination of:
477 // - absence of an explicit urlSchemaVersion, and
478 // - presence of the old 'hiddenResultTypes' field
479 // tells us that the URL is from the original urlSchemaVersion.
480 // See https://codereview.chromium.org/367173002/
481 urlSchemaVersion = 0;
482 }
483 $scope.urlSchemaVersionLoaded = urlSchemaVersion;
484
485 if (urlSchemaVersion != constants.URL_VALUE__SCHEMA_VERSION__CURRENT) {
486 nameValuePairs = $scope.upconvertUrlNameValuePairs(nameValuePairs, urlSc hemaVersion);
487 }
488 angular.forEach($scope.queryParameters.map,
489 function(copier, paramName) {
490 copier.load(nameValuePairs, paramName);
491 }
492 );
493 };
494
495 // Saves all parameters from $scope into the URL query string.
496 $scope.queryParameters.save = function() {
497 var nameValuePairs = {};
498 nameValuePairs[constants.URL_KEY__SCHEMA_VERSION] = constants.URL_VALUE__S CHEMA_VERSION__CURRENT;
499 angular.forEach($scope.queryParameters.map,
500 function(copier, paramName) {
501 copier.save(nameValuePairs, paramName);
502 }
503 );
504 $location.search(nameValuePairs);
505 };
506
507 /**
508 * Converts URL name/value pairs that were stored by a previous urlSchemaVer sion
509 * to the currently needed format.
510 *
511 * @param oldNValuePairs name/value pairs found in the loaded URL
512 * @param oldUrlSchemaVersion which version of the schema was used to genera te that URL
513 *
514 * @returns nameValuePairs as needed by the current URL parser
515 */
516 $scope.upconvertUrlNameValuePairs = function(oldNameValuePairs, oldUrlSchema Version) {
517 var newNameValuePairs = {};
518 angular.forEach(oldNameValuePairs,
519 function(value, name) {
520 if (oldUrlSchemaVersion < 1) {
521 if ('hiddenConfigs' == name) {
522 name = 'config';
523 var valueSet = {};
524 $scope.toggleValuesInSet(value.split(','), valueSet) ;
525 $scope.toggleValuesInSet(
526 $scope.allColumnValues[constants.KEY__EXTRACOLUM NS__CONFIG],
527 valueSet);
528 value = Object.keys(valueSet).join(',');
529 } else if ('hiddenResultTypes' == name) {
530 name = 'resultType';
531 var valueSet = {};
532 $scope.toggleValuesInSet(value.split(','), valueSet) ;
533 $scope.toggleValuesInSet(
534 $scope.allColumnValues[constants.KEY__EXTRACOLUM NS__RESULT_TYPE],
535 valueSet);
536 value = Object.keys(valueSet).join(',');
537 }
538 }
539
540 newNameValuePairs[name] = value;
541 }
542 );
543 return newNameValuePairs;
544 }
545
546
547 //
548 // updateResults() and friends.
549 //
550
551 /**
552 * Set $scope.areUpdatesPending (to enable/disable the Update Results
553 * button).
554 *
555 * TODO(epoger): We could reduce the amount of code by just setting the
556 * variable directly (from, e.g., a button's ng-click handler). But when
557 * I tried that, the HTML elements depending on the variable did not get
558 * updated.
559 * It turns out that this is due to variable scoping within an ng-repeat
560 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assi gnment-expression-invoked-by-ng-click-within-ng-repeat
561 *
562 * @param val boolean value to set $scope.areUpdatesPending to
563 */
564 $scope.setUpdatesPending = function(val) {
565 $scope.areUpdatesPending = val;
566 }
567
568 /**
569 * Update the displayed results, based on filters/settings,
570 * and call $scope.queryParameters.save() so that the new filter results
571 * can be bookmarked.
572 */
573 $scope.updateResults = function() {
574 $scope.renderStartTime = window.performance.now();
575 $log.debug("renderStartTime: " + $scope.renderStartTime);
576 $scope.displayLimit = $scope.displayLimitPending;
577 $scope.mergeIdenticalRows = $scope.mergeIdenticalRowsPending;
578
579 // For each USE_FREEFORM_FILTER column, populate showingColumnValues.
580 // This is more efficient than applying the freeform filter within the
581 // tight loop in removeHiddenImagePairs.
582 angular.forEach(
583 $scope.filterableColumnNames,
584 function(columnName) {
585 var columnHeader = $scope.extraColumnHeaders[columnName];
586 if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTE R]) {
587 var columnStringMatch = $scope.columnStringMatch[columnName];
588 var showingColumnValues = {};
589 angular.forEach(
590 $scope.allColumnValues[columnName],
591 function(columnValue) {
592 if (-1 != columnValue.indexOf(columnStringMatch)) {
593 showingColumnValues[columnValue] = true;
594 }
595 }
596 );
597 $scope.showingColumnValues[columnName] = showingColumnValues;
598 }
599 }
600 );
601
602 // TODO(epoger): Every time we apply a filter, AngularJS creates
603 // another copy of the array. Is there a way we can filter out
604 // the imagePairs as they are displayed, rather than storing multiple
605 // array copies? (For better performance.)
606 if ($scope.viewingTab == $scope.defaultTab) {
607 var doReverse = !currSortAsc;
608
609 $scope.filteredImagePairs =
610 $filter("orderBy")(
611 $filter("removeHiddenImagePairs")(
612 $scope.imagePairs,
613 $scope.filterableColumnNames,
614 $scope.showingColumnValues,
615 $scope.viewingTab
616 ),
617 // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue],
618 $scope.getSortColumnValue,
619 doReverse);
620 $scope.limitedImagePairs = $filter("mergeAndLimit")(
621 $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdentica lRows);
622 } else {
623 $scope.filteredImagePairs =
624 $filter("orderBy")(
625 $filter("filter")(
626 $scope.imagePairs,
627 {tab: $scope.viewingTab},
628 true
629 ),
630 // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]);
631 $scope.getSortColumnValue);
632 $scope.limitedImagePairs = $filter("mergeAndLimit")(
633 $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows);
634 }
635 $scope.showThumbnails = $scope.showThumbnailsPending;
636 $scope.imageSize = $scope.imageSizePending;
637 $scope.setUpdatesPending(false);
638 $scope.queryParameters.save();
639 }
640
641 /**
642 * This function is called when the results have been completely rendered
643 * after updateResults().
644 */
645 $scope.resultsUpdatedCallback = function() {
646 $scope.renderEndTime = window.performance.now();
647 $log.debug("renderEndTime: " + $scope.renderEndTime);
648 };
649
650 /**
651 * Re-sort the displayed results.
652 *
653 * @param subdict (string): which KEY__IMAGEPAIRS__* subdictionary
654 * the sort column key is within, or 'none' if the sort column
655 * key is one of KEY__IMAGEPAIRS__*
656 * @param key (string): sort by value associated with this key in subdict
657 */
658 $scope.sortResultsBy = function(subdict, key) {
659 // if we are already sorting by this column then toggle between asc/desc
660 if ((subdict === $scope.sortColumnSubdict) && ($scope.sortColumnKey === ke y)) {
661 currSortAsc = !currSortAsc;
662 } else {
663 $scope.sortColumnSubdict = subdict;
664 $scope.sortColumnKey = key;
665 currSortAsc = true;
666 }
667 $scope.updateResults();
668 };
669
670 /**
671 * Returns ASC or DESC (from constants) if currently the data
672 * is sorted by the provided column.
673 *
674 * @param colName: name of the column for which we need to get the class.
675 */
676
677 $scope.sortedByColumnsCls = function (colName) {
678 if ($scope.sortColumnKey !== colName) {
679 return '';
680 }
681
682 var result = (currSortAsc) ? constants.ASC : constants.DESC;
683 console.log("sort class:", result);
684 return result;
685 };
686
687 /**
688 * For a particular ImagePair, return the value of the column we are
689 * sorting on (according to $scope.sortColumnSubdict and
690 * $scope.sortColumnKey).
691 *
692 * @param imagePair: imagePair to get a column value out of.
693 */
694 $scope.getSortColumnValue = function(imagePair) {
695 if ($scope.sortColumnSubdict in imagePair) {
696 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
697 } else if ($scope.sortColumnKey in imagePair) {
698 return imagePair[$scope.sortColumnKey];
699 } else {
700 return undefined;
701 }
702 };
703
704 /**
705 * For a particular ImagePair, return the value we use for the
706 * second-order sort (tiebreaker when multiple rows have
707 * the same getSortColumnValue()).
708 *
709 * We join the imageA and imageB urls for this value, so that we merge
710 * adjacent rows as much as possible.
711 *
712 * @param imagePair: imagePair to get a column value out of.
713 */
714 $scope.getSecondOrderSortValue = function(imagePair) {
715 return imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" +
716 imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
717 };
718
719 /**
720 * Set $scope.columnStringMatch[name] = value, and update results.
721 *
722 * @param name
723 * @param value
724 */
725 $scope.setColumnStringMatch = function(name, value) {
726 $scope.columnStringMatch[name] = value;
727 $scope.updateResults();
728 };
729
730 /**
731 * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatc h[columnName]
732 * so that ONLY entries with this columnValue are showing, and update the vi sible results.
733 * (We update both of those, so we cover both freeform and checkbox filtered columns.)
734 *
735 * @param columnName
736 * @param columnValue
737 */
738 $scope.showOnlyColumnValue = function(columnName, columnValue) {
739 $scope.columnStringMatch[columnName] = columnValue;
740 $scope.showingColumnValues[columnName] = {};
741 $scope.toggleValueInSet(columnValue, $scope.showingColumnValues[columnName ]);
742 $scope.updateResults();
743 };
744
745 /**
746 * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatc h[columnName]
747 * so that ALL entries are showing, and update the visible results.
748 * (We update both of those, so we cover both freeform and checkbox filtered columns.)
749 *
750 * @param columnName
751 */
752 $scope.showAllColumnValues = function(columnName) {
753 $scope.columnStringMatch[columnName] = "";
754 $scope.showingColumnValues[columnName] = {};
755 $scope.toggleValuesInSet($scope.allColumnValues[columnName],
756 $scope.showingColumnValues[columnName]);
757 $scope.updateResults();
758 };
759
760
761 //
762 // Operations for sending info back to the server.
763 //
764
765 /**
766 * Tell the server that the actual results of these particular tests
767 * are acceptable.
768 *
769 * TODO(epoger): This assumes that the original expectations are in
770 * imageSetA, and the actuals are in imageSetB.
771 *
772 * @param imagePairsSubset an array of test results, most likely a subset of
773 * $scope.imagePairs (perhaps with some modifications)
774 */
775 $scope.submitApprovals = function(imagePairsSubset) {
776 $scope.submitPending = true;
777
778 // Convert bug text field to null or 1-item array.
779 var bugs = null;
780 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
781 if (!isNaN(bugNumber)) {
782 bugs = [bugNumber];
783 }
784
785 // TODO(epoger): This is a suboptimal way to prevent users from
786 // rebaselining failures in alternative renderModes, but it does work.
787 // For a better solution, see
788 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
789 // result type, RenderModeMismatch')
790 var encounteredComparisonConfig = false;
791
792 var updatedExpectations = [];
793 for (var i = 0; i < imagePairsSubset.length; i++) {
794 var imagePair = imagePairsSubset[i];
795 var updatedExpectation = {};
796 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] =
797 imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS];
798 updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] =
799 imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS];
800 // IMAGE_B_URL contains the actual image (which is now the expectation)
801 updatedExpectation[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] =
802 imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
803 if (0 == updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]
804 [constants.KEY__EXTRACOLUMNS__CONFIG]
805 .indexOf('comparison-')) {
806 encounteredComparisonConfig = true;
807 }
808
809 // Advanced settings...
810 if (null == updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]) {
811 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = {};
812 }
813 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]
814 [constants.KEY__EXPECTATIONS__REVIEWED] =
815 $scope.submitAdvancedSettings[
816 constants.KEY__EXPECTATIONS__REVIEWED];
817 if (true == $scope.submitAdvancedSettings[
818 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
819 // if it's false, don't send it at all (just keep the default)
820 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]
821 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
822 }
823 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]
824 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
825
826 updatedExpectations.push(updatedExpectation);
827 }
828 if (encounteredComparisonConfig) {
829 alert("Approval failed -- you cannot approve results with config " +
830 "type comparison-*");
831 $scope.submitPending = false;
832 return;
833 }
834 var modificationData = {};
835 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
836 updatedExpectations;
837 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
838 $scope.header[constants.KEY__HEADER__DATAHASH];
839 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
840 $scope.header[constants.KEY__HEADER__TYPE];
841 $http({
842 method: "POST",
843 url: "/edits",
844 data: modificationData
845 }).success(function(data, status, headers, config) {
846 var imagePairIndicesToMove = [];
847 for (var i = 0; i < imagePairsSubset.length; i++) {
848 imagePairIndicesToMove.push(imagePairsSubset[i].index);
849 }
850 $scope.moveImagePairsToTab(imagePairIndicesToMove,
851 "HackToMakeSureThisImagePairDisappears");
852 $scope.updateResults();
853 alert("New baselines submitted successfully!\n\n" +
854 "You still need to commit the updated expectations files on " +
855 "the server side to the Skia repo.\n\n" +
856 "When you click OK, your web UI will reload; after that " +
857 "completes, you will see the updated data (once the server has " +
858 "finished loading the update results into memory!) and you can " +
859 "submit more baselines if you want.");
860 // I don't know why, but if I just call reload() here it doesn't work.
861 // Making a timer call it fixes the problem.
862 $timeout(function(){location.reload();}, 1);
863 }).error(function(data, status, headers, config) {
864 alert("There was an error submitting your baselines.\n\n" +
865 "Please see server-side log for details.");
866 $scope.submitPending = false;
867 });
868 };
869
870
871 //
872 // Operations we use to mimic Set semantics, in such a way that
873 // checking for presence within the Set is as fast as possible.
874 // But getting a list of all values within the Set is not necessarily
875 // possible.
876 // TODO(epoger): move into a separate .js file?
877 //
878
879 /**
880 * Returns the number of values present within set "set".
881 *
882 * @param set an Object which we use to mimic set semantics
883 */
884 $scope.setSize = function(set) {
885 return Object.keys(set).length;
886 };
887
888 /**
889 * Returns true if value "value" is present within set "set".
890 *
891 * @param value a value of any type
892 * @param set an Object which we use to mimic set semantics
893 * (this should make isValueInSet faster than if we used an Array)
894 */
895 $scope.isValueInSet = function(value, set) {
896 return (true == set[value]);
897 };
898
899 /**
900 * If value "value" is already in set "set", remove it; otherwise, add it.
901 *
902 * @param value a value of any type
903 * @param set an Object which we use to mimic set semantics
904 */
905 $scope.toggleValueInSet = function(value, set) {
906 if (true == set[value]) {
907 delete set[value];
908 } else {
909 set[value] = true;
910 }
911 };
912
913 /**
914 * For each value in valueArray, call toggleValueInSet(value, set).
915 *
916 * @param valueArray
917 * @param set
918 */
919 $scope.toggleValuesInSet = function(valueArray, set) {
920 var arrayLength = valueArray.length;
921 for (var i = 0; i < arrayLength; i++) {
922 $scope.toggleValueInSet(valueArray[i], set);
923 }
924 };
925
926
927 //
928 // Array operations; similar to our Set operations, but operate on a
929 // Javascript Array so we *can* easily get a list of all values in the Set.
930 // TODO(epoger): move into a separate .js file?
931 //
932
933 /**
934 * Returns true if value "value" is present within array "array".
935 *
936 * @param value a value of any type
937 * @param array a Javascript Array
938 */
939 $scope.isValueInArray = function(value, array) {
940 return (-1 != array.indexOf(value));
941 };
942
943 /**
944 * If value "value" is already in array "array", remove it; otherwise,
945 * add it.
946 *
947 * @param value a value of any type
948 * @param array a Javascript Array
949 */
950 $scope.toggleValueInArray = function(value, array) {
951 var i = array.indexOf(value);
952 if (-1 == i) {
953 array.push(value);
954 } else {
955 array.splice(i, 1);
956 }
957 };
958
959
960 //
961 // Miscellaneous utility functions.
962 // TODO(epoger): move into a separate .js file?
963 //
964
965 /**
966 * Returns a single "column slice" of a 2D array.
967 *
968 * For example, if array is:
969 * [[A0, A1],
970 * [B0, B1],
971 * [C0, C1]]
972 * and index is 0, this this will return:
973 * [A0, B0, C0]
974 *
975 * @param array a Javascript Array
976 * @param column (numeric): index within each row array
977 */
978 $scope.columnSliceOf2DArray = function(array, column) {
979 var slice = [];
980 var numRows = array.length;
981 for (var row = 0; row < numRows; row++) {
982 slice.push(array[row][column]);
983 }
984 return slice;
985 };
986
987 /**
988 * Returns a human-readable (in local time zone) time string for a
989 * particular moment in time.
990 *
991 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
992 */
993 $scope.localTimeString = function(secondsPastEpoch) {
994 var d = new Date(secondsPastEpoch * 1000);
995 return d.toString();
996 };
997
998 /**
999 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
1000 *
1001 * @param r (numeric): red channel value, 0-255
1002 * @param g (numeric): green channel value, 0-255
1003 * @param b (numeric): blue channel value, 0-255
1004 */
1005 $scope.hexColorString = function(r, g, b) {
1006 var rString = r.toString(16);
1007 if (r < 16) {
1008 rString = "0" + rString;
1009 }
1010 var gString = g.toString(16);
1011 if (g < 16) {
1012 gString = "0" + gString;
1013 }
1014 var bString = b.toString(16);
1015 if (b < 16) {
1016 bString = "0" + bString;
1017 }
1018 return '#' + rString + gString + bString;
1019 };
1020
1021 /**
1022 * Returns a hex color string (such as "#aabbcc") for the given brightness.
1023 *
1024 * @param brightnessString (string): 0-255, 0 is completely black
1025 *
1026 * TODO(epoger): It might be nice to tint the color when it's not completely
1027 * black or completely white.
1028 */
1029 $scope.brightnessStringToHexColor = function(brightnessString) {
1030 var v = parseInt(brightnessString);
1031 return $scope.hexColorString(v, v, v);
1032 };
1033
1034 }
1035 );
OLDNEW
« no previous file with comments | « gm/rebaseline_server/static/live-view.html ('k') | gm/rebaseline_server/static/new/bower.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698