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

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

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

Powered by Google App Engine
This is Rietveld 408576698