blob: dc33bd0a93a971b01cf303e86627953ee8d0589f [file] [log] [blame]
epoger@google.comf9d134d2013-09-27 15:02:44 +00001/*
2 * Loader:
epoger@google.comafaad3d2013-09-30 15:06:25 +00003 * Reads GM result reports written out by results.py, and imports
commit-bot@chromium.org16f41802014-02-26 19:05:20 +00004 * them into $scope.extraColumnHeaders and $scope.imagePairs .
epoger@google.comf9d134d2013-09-27 15:02:44 +00005 */
6var Loader = angular.module(
7 'Loader',
commit-bot@chromium.org6e3ee332014-03-10 19:12:53 +00008 ['ConstantsModule']
epoger@google.comf9d134d2013-09-27 15:02:44 +00009);
epoger@google.com5f2bb002013-10-02 18:57:48 +000010
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +000011Loader.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
epoger@google.com5f2bb002013-10-02 18:57:48 +000026// 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.)
30Loader.filter(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000031 'removeHiddenImagePairs',
32 function(constants) {
33 return function(unfilteredImagePairs, hiddenResultTypes, hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +000034 builderSubstring, testSubstring, viewingTab) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000035 var filteredImagePairs = [];
36 for (var i = 0; i < unfilteredImagePairs.length; i++) {
37 var imagePair = unfilteredImagePairs[i];
38 var extraColumnValues = imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
epoger@google.com055e3b52013-10-26 14:31:11 +000039 // For performance, we examine the "set" objects directly rather
40 // than calling $scope.isValueInSet().
41 // Besides, I don't think we have access to $scope in here...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000042 if (!(true == hiddenResultTypes[extraColumnValues[
43 constants.KEY__EXTRACOLUMN__RESULT_TYPE]]) &&
44 !(true == hiddenConfigs[extraColumnValues[
45 constants.KEY__EXTRACOLUMN__CONFIG]]) &&
46 !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__BUILDER]
47 .indexOf(builderSubstring)) &&
48 !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__TEST]
49 .indexOf(testSubstring)) &&
50 (viewingTab == imagePair.tab)) {
51 filteredImagePairs.push(imagePair);
epoger@google.com9fb6c8a2013-10-09 18:05:58 +000052 }
epoger@google.com5f2bb002013-10-02 18:57:48 +000053 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000054 return filteredImagePairs;
epoger@google.com5f2bb002013-10-02 18:57:48 +000055 };
56 }
57);
58
epoger@google.comad0e5522013-10-24 15:38:27 +000059
epoger@google.comf9d134d2013-09-27 15:02:44 +000060Loader.controller(
61 'Loader.Controller',
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +000062 function($scope, $http, $filter, $location, $log, $timeout, constants) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000063 $scope.constants = constants;
epoger@google.com542b65f2013-10-15 20:10:33 +000064 $scope.windowTitle = "Loading GM Results...";
epoger@google.com62a5ef02013-12-05 18:03:24 +000065 $scope.resultsToLoad = $location.search().resultsToLoad;
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +000066 $scope.loadingMessage = "please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +000067
epoger@google.comad0e5522013-10-24 15:38:27 +000068 /**
69 * On initial page load, load a full dictionary of results.
70 * Once the dictionary is loaded, unhide the page elements so they can
71 * render the data.
72 */
commit-bot@chromium.org7498d952014-03-13 14:56:29 +000073 $http.get($scope.resultsToLoad).success(
epoger@google.comdcb4e652013-10-11 18:45:33 +000074 function(data, status, header, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000075 var dataHeader = data[constants.KEY__HEADER];
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +000076 if (dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] !=
77 constants.REBASELINE_SERVER_SCHEMA_VERSION_NUMBER) {
78 $scope.loadingMessage = "ERROR: Got JSON file with schema version "
79 + dataHeader[constants.KEY__HEADER__SCHEMA_VERSION]
80 + " but expected schema version "
81 + constants.REBASELINE_SERVER_SCHEMA_VERSION_NUMBER;
82 } else if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) {
commit-bot@chromium.orged191072014-03-10 18:05:15 +000083 // Apply the server's requested reload delay to local time,
84 // so we will wait the right number of seconds regardless of clock
85 // skew between client and server.
86 var reloadDelayInSeconds =
87 dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] -
88 dataHeader[constants.KEY__HEADER__TIME_UPDATED];
89 var timeNow = new Date().getTime();
90 var timeToReload = timeNow + reloadDelayInSeconds * 1000;
epoger@google.com2682c902013-12-05 16:05:16 +000091 $scope.loadingMessage =
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +000092 "server is still loading results; will retry at " +
commit-bot@chromium.orged191072014-03-10 18:05:15 +000093 $scope.localTimeString(timeToReload / 1000);
epoger@google.com2682c902013-12-05 16:05:16 +000094 $timeout(
95 function(){location.reload();},
commit-bot@chromium.orged191072014-03-10 18:05:15 +000096 timeToReload - timeNow);
epoger@google.com2682c902013-12-05 16:05:16 +000097 } else {
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +000098 $scope.loadingMessage = "processing data, please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +000099
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000100 $scope.header = dataHeader;
101 $scope.extraColumnHeaders = data[constants.KEY__EXTRACOLUMNHEADERS];
102 $scope.imagePairs = data[constants.KEY__IMAGEPAIRS];
103 $scope.imageSets = data[constants.KEY__IMAGESETS];
104 $scope.sortColumnSubdict = constants.KEY__DIFFERENCE_DATA;
commit-bot@chromium.orge93a4c02014-04-11 21:45:41 +0000105 $scope.sortColumnKey = constants.KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000106
epoger@google.com2682c902013-12-05 16:05:16 +0000107 $scope.showSubmitAdvancedSettings = false;
108 $scope.submitAdvancedSettings = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000109 $scope.submitAdvancedSettings[
110 constants.KEY__EXPECTATIONS__REVIEWED] = true;
111 $scope.submitAdvancedSettings[
112 constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false;
epoger@google.com2682c902013-12-05 16:05:16 +0000113 $scope.submitAdvancedSettings['bug'] = '';
epoger@google.com055e3b52013-10-26 14:31:11 +0000114
epoger@google.com2682c902013-12-05 16:05:16 +0000115 // Create the list of tabs (lists into which the user can file each
116 // test). This may vary, depending on isEditable.
117 $scope.tabs = [
118 'Unfiled', 'Hidden'
119 ];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000120 if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) {
epoger@google.com2682c902013-12-05 16:05:16 +0000121 $scope.tabs = $scope.tabs.concat(
122 ['Pending Approval']);
123 }
124 $scope.defaultTab = $scope.tabs[0];
125 $scope.viewingTab = $scope.defaultTab;
126
127 // Track the number of results on each tab.
128 $scope.numResultsPerTab = {};
129 for (var i = 0; i < $scope.tabs.length; i++) {
130 $scope.numResultsPerTab[$scope.tabs[i]] = 0;
131 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000132 $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length;
epoger@google.com2682c902013-12-05 16:05:16 +0000133
134 // Add index and tab fields to all records.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000135 for (var i = 0; i < $scope.imagePairs.length; i++) {
136 $scope.imagePairs[i].index = i;
137 $scope.imagePairs[i].tab = $scope.defaultTab;
epoger@google.com2682c902013-12-05 16:05:16 +0000138 }
139
140 // Arrays within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000141 $scope.selectedImagePairs = [];
epoger@google.com2682c902013-12-05 16:05:16 +0000142
143 // Sets within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000144 $scope.hiddenResultTypes = {};
145 $scope.hiddenResultTypes[
146 constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true;
147 $scope.hiddenResultTypes[
148 constants.KEY__RESULT_TYPE__NOCOMPARISON] = true;
149 $scope.hiddenResultTypes[
150 constants.KEY__RESULT_TYPE__SUCCEEDED] = true;
151 $scope.allResultTypes = Object.keys(
152 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE]
153 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000154 $scope.hiddenConfigs = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000155 $scope.allConfigs = Object.keys(
156 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG]
157 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000158
159 // Associative array of partial string matches per category.
160 $scope.categoryValueMatch = {};
161 $scope.categoryValueMatch.builder = "";
162 $scope.categoryValueMatch.test = "";
163
epoger@google.com62a5ef02013-12-05 18:03:24 +0000164 // If any defaults were overridden in the URL, get them now.
165 $scope.queryParameters.load();
166
commit-bot@chromium.orgb42105a2014-03-20 15:27:34 +0000167 // Any image URLs which are relative should be relative to the JSON
168 // file's source directory; absolute URLs should be left alone.
169 var baseUrlKey = constants.KEY__IMAGESETS__FIELD__BASE_URL;
170 angular.forEach(
171 $scope.imageSets,
172 function(imageSet) {
173 var baseUrl = imageSet[baseUrlKey];
174 if ((baseUrl.substring(0, 1) != '/') &&
175 (baseUrl.indexOf('://') == -1)) {
176 imageSet[baseUrlKey] = $scope.resultsToLoad + '/../' + baseUrl;
177 }
178 }
179 );
180
epoger@google.com2682c902013-12-05 16:05:16 +0000181 $scope.updateResults();
182 $scope.loadingMessage = "";
183 $scope.windowTitle = "Current GM Results";
epoger@google.comeb832592013-10-23 15:07:26 +0000184 }
epoger@google.comdcb4e652013-10-11 18:45:33 +0000185 }
186 ).error(
187 function(data, status, header, config) {
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000188 $scope.loadingMessage = "FAILED to load.";
epoger@google.com542b65f2013-10-15 20:10:33 +0000189 $scope.windowTitle = "Failed to Load GM Results";
epoger@google.comf9d134d2013-09-27 15:02:44 +0000190 }
191 );
epoger@google.com5f2bb002013-10-02 18:57:48 +0000192
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000193
epoger@google.comad0e5522013-10-24 15:38:27 +0000194 //
epoger@google.com055e3b52013-10-26 14:31:11 +0000195 // Select/Clear/Toggle all tests.
196 //
197
198 /**
199 * Select all currently showing tests.
200 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000201 $scope.selectAllImagePairs = function() {
202 var numImagePairsShowing = $scope.limitedImagePairs.length;
203 for (var i = 0; i < numImagePairsShowing; i++) {
204 var index = $scope.limitedImagePairs[i].index;
205 if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) {
206 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000207 }
208 }
209 }
210
211 /**
212 * Deselect all currently showing tests.
213 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000214 $scope.clearAllImagePairs = function() {
215 var numImagePairsShowing = $scope.limitedImagePairs.length;
216 for (var i = 0; i < numImagePairsShowing; i++) {
217 var index = $scope.limitedImagePairs[i].index;
218 if ($scope.isValueInArray(index, $scope.selectedImagePairs)) {
219 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000220 }
221 }
222 }
223
224 /**
225 * Toggle selection of all currently showing tests.
226 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000227 $scope.toggleAllImagePairs = function() {
228 var numImagePairsShowing = $scope.limitedImagePairs.length;
229 for (var i = 0; i < numImagePairsShowing; i++) {
230 var index = $scope.limitedImagePairs[i].index;
231 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000232 }
233 }
234
235
236 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000237 // Tab operations.
238 //
epoger@google.com5f2bb002013-10-02 18:57:48 +0000239
epoger@google.comad0e5522013-10-24 15:38:27 +0000240 /**
241 * Change the selected tab.
242 *
243 * @param tab (string): name of the tab to select
244 */
epoger@google.comeb832592013-10-23 15:07:26 +0000245 $scope.setViewingTab = function(tab) {
246 $scope.viewingTab = tab;
247 $scope.updateResults();
248 }
249
epoger@google.comeb832592013-10-23 15:07:26 +0000250 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000251 * Move the imagePairs in $scope.selectedImagePairs to a different tab,
252 * and then clear $scope.selectedImagePairs.
epoger@google.comeb832592013-10-23 15:07:26 +0000253 *
254 * @param newTab (string): name of the tab to move the tests to
255 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000256 $scope.moveSelectedImagePairsToTab = function(newTab) {
257 $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
258 $scope.selectedImagePairs = [];
epoger@google.comeb832592013-10-23 15:07:26 +0000259 $scope.updateResults();
260 }
261
262 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000263 * Move a subset of $scope.imagePairs to a different tab.
epoger@google.comeb832592013-10-23 15:07:26 +0000264 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000265 * @param imagePairIndices (array of ints): indices into $scope.imagePairs
epoger@google.comeb832592013-10-23 15:07:26 +0000266 * indicating which test results to move
267 * @param newTab (string): name of the tab to move the tests to
268 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000269 $scope.moveImagePairsToTab = function(imagePairIndices, newTab) {
270 var imagePairIndex;
271 var numImagePairs = imagePairIndices.length;
272 for (var i = 0; i < numImagePairs; i++) {
273 imagePairIndex = imagePairIndices[i];
274 $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--;
275 $scope.imagePairs[imagePairIndex].tab = newTab;
epoger@google.comeb832592013-10-23 15:07:26 +0000276 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000277 $scope.numResultsPerTab[newTab] += numImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000278 }
279
epoger@google.comad0e5522013-10-24 15:38:27 +0000280
281 //
epoger@google.com62a5ef02013-12-05 18:03:24 +0000282 // $scope.queryParameters:
283 // Transfer parameter values between $scope and the URL query string.
284 //
285 $scope.queryParameters = {};
286
287 // load and save functions for parameters of each type
288 // (load a parameter value into $scope from nameValuePairs,
289 // save a parameter value from $scope into nameValuePairs)
290 $scope.queryParameters.copiers = {
291 'simple': {
292 'load': function(nameValuePairs, name) {
293 var value = nameValuePairs[name];
294 if (value) {
295 $scope[name] = value;
296 }
297 },
298 'save': function(nameValuePairs, name) {
299 nameValuePairs[name] = $scope[name];
300 }
301 },
302
303 'categoryValueMatch': {
304 'load': function(nameValuePairs, name) {
305 var value = nameValuePairs[name];
306 if (value) {
307 $scope.categoryValueMatch[name] = value;
308 }
309 },
310 'save': function(nameValuePairs, name) {
311 nameValuePairs[name] = $scope.categoryValueMatch[name];
312 }
313 },
314
315 'set': {
316 'load': function(nameValuePairs, name) {
317 var value = nameValuePairs[name];
318 if (value) {
319 var valueArray = value.split(',');
320 $scope[name] = {};
321 $scope.toggleValuesInSet(valueArray, $scope[name]);
322 }
323 },
324 'save': function(nameValuePairs, name) {
325 nameValuePairs[name] = Object.keys($scope[name]).join(',');
326 }
327 },
328
329 };
330
331 // parameter name -> copier objects to load/save parameter value
332 $scope.queryParameters.map = {
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000333 'resultsToLoad': $scope.queryParameters.copiers.simple,
334 'displayLimitPending': $scope.queryParameters.copiers.simple,
335 'showThumbnailsPending': $scope.queryParameters.copiers.simple,
336 'imageSizePending': $scope.queryParameters.copiers.simple,
337 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
338 'sortColumnKey': $scope.queryParameters.copiers.simple,
epoger@google.com62a5ef02013-12-05 18:03:24 +0000339
340 'hiddenResultTypes': $scope.queryParameters.copiers.set,
341 'hiddenConfigs': $scope.queryParameters.copiers.set,
342 };
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000343 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] =
344 $scope.queryParameters.copiers.categoryValueMatch;
345 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] =
346 $scope.queryParameters.copiers.categoryValueMatch;
epoger@google.com62a5ef02013-12-05 18:03:24 +0000347
348 // Loads all parameters into $scope from the URL query string;
349 // any which are not found within the URL will keep their current value.
350 $scope.queryParameters.load = function() {
351 var nameValuePairs = $location.search();
352 angular.forEach($scope.queryParameters.map,
353 function(copier, paramName) {
354 copier.load(nameValuePairs, paramName);
355 }
356 );
357 };
358
359 // Saves all parameters from $scope into the URL query string.
360 $scope.queryParameters.save = function() {
361 var nameValuePairs = {};
362 angular.forEach($scope.queryParameters.map,
363 function(copier, paramName) {
364 copier.save(nameValuePairs, paramName);
365 }
366 );
367 $location.search(nameValuePairs);
368 };
369
370
371 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000372 // updateResults() and friends.
373 //
374
375 /**
376 * Set $scope.areUpdatesPending (to enable/disable the Update Results
377 * button).
378 *
379 * TODO(epoger): We could reduce the amount of code by just setting the
380 * variable directly (from, e.g., a button's ng-click handler). But when
381 * I tried that, the HTML elements depending on the variable did not get
382 * updated.
383 * It turns out that this is due to variable scoping within an ng-repeat
384 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat
385 *
386 * @param val boolean value to set $scope.areUpdatesPending to
387 */
388 $scope.setUpdatesPending = function(val) {
389 $scope.areUpdatesPending = val;
390 }
391
392 /**
epoger@google.com62a5ef02013-12-05 18:03:24 +0000393 * Update the displayed results, based on filters/settings,
394 * and call $scope.queryParameters.save() so that the new filter results
395 * can be bookmarked.
epoger@google.comad0e5522013-10-24 15:38:27 +0000396 */
epoger@google.com5f2bb002013-10-02 18:57:48 +0000397 $scope.updateResults = function() {
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000398 $scope.renderStartTime = window.performance.now();
399 $log.debug("renderStartTime: " + $scope.renderStartTime);
epoger@google.com5f2bb002013-10-02 18:57:48 +0000400 $scope.displayLimit = $scope.displayLimitPending;
401 // TODO(epoger): Every time we apply a filter, AngularJS creates
402 // another copy of the array. Is there a way we can filter out
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000403 // the imagePairs as they are displayed, rather than storing multiple
epoger@google.com5f2bb002013-10-02 18:57:48 +0000404 // array copies? (For better performance.)
epoger@google.comeb832592013-10-23 15:07:26 +0000405
406 if ($scope.viewingTab == $scope.defaultTab) {
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000407
408 // TODO(epoger): Until we allow the user to reverse sort order,
409 // there are certain columns we want to sort in a different order.
410 var doReverse = (
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000411 ($scope.sortColumnKey ==
412 constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) ||
413 ($scope.sortColumnKey ==
commit-bot@chromium.orge93a4c02014-04-11 21:45:41 +0000414 constants.KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF));
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000415
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000416 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000417 $filter("orderBy")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000418 $filter("removeHiddenImagePairs")(
419 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000420 $scope.hiddenResultTypes,
421 $scope.hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +0000422 $scope.categoryValueMatch.builder,
423 $scope.categoryValueMatch.test,
epoger@google.comeb832592013-10-23 15:07:26 +0000424 $scope.viewingTab
425 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000426 $scope.getSortColumnValue, doReverse);
427 $scope.limitedImagePairs = $filter("limitTo")(
428 $scope.filteredImagePairs, $scope.displayLimit);
epoger@google.comeb832592013-10-23 15:07:26 +0000429 } else {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000430 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000431 $filter("orderBy")(
432 $filter("filter")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000433 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000434 {tab: $scope.viewingTab},
435 true
436 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000437 $scope.getSortColumnValue);
438 $scope.limitedImagePairs = $scope.filteredImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000439 }
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000440 $scope.showThumbnails = $scope.showThumbnailsPending;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000441 $scope.imageSize = $scope.imageSizePending;
epoger@google.comad0e5522013-10-24 15:38:27 +0000442 $scope.setUpdatesPending(false);
epoger@google.com62a5ef02013-12-05 18:03:24 +0000443 $scope.queryParameters.save();
epoger@google.com5f2bb002013-10-02 18:57:48 +0000444 }
445
epoger@google.comad0e5522013-10-24 15:38:27 +0000446 /**
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000447 * This function is called when the results have been completely rendered
448 * after updateResults().
449 */
450 $scope.resultsUpdatedCallback = function() {
451 $scope.renderEndTime = window.performance.now();
452 $log.debug("renderEndTime: " + $scope.renderEndTime);
453 }
454
455 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000456 * Re-sort the displayed results.
457 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000458 * @param subdict (string): which subdictionary
459 * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA,
460 * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within
461 * @param key (string): sort by value associated with this key in subdict
epoger@google.comad0e5522013-10-24 15:38:27 +0000462 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000463 $scope.sortResultsBy = function(subdict, key) {
464 $scope.sortColumnSubdict = subdict;
465 $scope.sortColumnKey = key;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000466 $scope.updateResults();
467 }
epoger@google.comeb832592013-10-23 15:07:26 +0000468
epoger@google.comf4394d52013-10-29 15:49:40 +0000469 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000470 * For a particular ImagePair, return the value of the column we are
471 * sorting on (according to $scope.sortColumnSubdict and
472 * $scope.sortColumnKey).
473 *
474 * @param imagePair: imagePair to get a column value out of.
475 */
476 $scope.getSortColumnValue = function(imagePair) {
477 if ($scope.sortColumnSubdict in imagePair) {
478 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
479 } else {
480 return undefined;
481 }
482 }
483
484 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000485 * Set $scope.categoryValueMatch[name] = value, and update results.
486 *
487 * @param name
488 * @param value
489 */
490 $scope.setCategoryValueMatch = function(name, value) {
491 $scope.categoryValueMatch[name] = value;
492 $scope.updateResults();
493 }
494
495 /**
496 * Update $scope.hiddenResultTypes so that ONLY this resultType is showing,
497 * and update the visible results.
498 *
499 * @param resultType
500 */
501 $scope.showOnlyResultType = function(resultType) {
502 $scope.hiddenResultTypes = {};
503 // TODO(epoger): Maybe change $scope.allResultTypes to be a Set like
504 // $scope.hiddenResultTypes (rather than an array), so this operation is
505 // simpler (just assign or add allResultTypes to hiddenResultTypes).
506 $scope.toggleValuesInSet($scope.allResultTypes, $scope.hiddenResultTypes);
507 $scope.toggleValueInSet(resultType, $scope.hiddenResultTypes);
508 $scope.updateResults();
509 }
510
511 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000512 * Update $scope.hiddenResultTypes so that ALL resultTypes are showing,
513 * and update the visible results.
514 */
515 $scope.showAllResultTypes = function() {
516 $scope.hiddenResultTypes = {};
517 $scope.updateResults();
518 }
519
520 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000521 * Update $scope.hiddenConfigs so that ONLY this config is showing,
522 * and update the visible results.
523 *
524 * @param config
525 */
526 $scope.showOnlyConfig = function(config) {
527 $scope.hiddenConfigs = {};
528 $scope.toggleValuesInSet($scope.allConfigs, $scope.hiddenConfigs);
529 $scope.toggleValueInSet(config, $scope.hiddenConfigs);
530 $scope.updateResults();
531 }
532
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000533 /**
534 * Update $scope.hiddenConfigs so that ALL configs are showing,
535 * and update the visible results.
536 */
537 $scope.showAllConfigs = function() {
538 $scope.hiddenConfigs = {};
539 $scope.updateResults();
540 }
541
epoger@google.comad0e5522013-10-24 15:38:27 +0000542
543 //
544 // Operations for sending info back to the server.
545 //
546
epoger@google.comeb832592013-10-23 15:07:26 +0000547 /**
548 * Tell the server that the actual results of these particular tests
549 * are acceptable.
550 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000551 * TODO(epoger): This assumes that the original expectations are in
552 * imageSetA, and the actuals are in imageSetB.
553 *
554 * @param imagePairsSubset an array of test results, most likely a subset of
555 * $scope.imagePairs (perhaps with some modifications)
epoger@google.comeb832592013-10-23 15:07:26 +0000556 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000557 $scope.submitApprovals = function(imagePairsSubset) {
epoger@google.comeb832592013-10-23 15:07:26 +0000558 $scope.submitPending = true;
epoger@google.com055e3b52013-10-26 14:31:11 +0000559
560 // Convert bug text field to null or 1-item array.
561 var bugs = null;
562 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
563 if (!isNaN(bugNumber)) {
564 bugs = [bugNumber];
565 }
566
567 // TODO(epoger): This is a suboptimal way to prevent users from
568 // rebaselining failures in alternative renderModes, but it does work.
569 // For a better solution, see
570 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
571 // result type, RenderModeMismatch')
572 var encounteredComparisonConfig = false;
573
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000574 var updatedExpectations = [];
575 for (var i = 0; i < imagePairsSubset.length; i++) {
576 var imagePair = imagePairsSubset[i];
577 var updatedExpectation = {};
578 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] =
579 imagePair[constants.KEY__EXPECTATIONS_DATA];
580 updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] =
581 imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
582 updatedExpectation[constants.KEY__NEW_IMAGE_URL] =
583 imagePair[constants.KEY__IMAGE_B_URL];
584 if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES]
585 [constants.KEY__EXTRACOLUMN__CONFIG]
586 .indexOf('comparison-')) {
epoger@google.com055e3b52013-10-26 14:31:11 +0000587 encounteredComparisonConfig = true;
588 }
589
590 // Advanced settings...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000591 if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) {
592 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {};
epoger@google.com055e3b52013-10-26 14:31:11 +0000593 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000594 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
595 [constants.KEY__EXPECTATIONS__REVIEWED] =
596 $scope.submitAdvancedSettings[
597 constants.KEY__EXPECTATIONS__REVIEWED];
598 if (true == $scope.submitAdvancedSettings[
599 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
600 // if it's false, don't send it at all (just keep the default)
601 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
602 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
603 }
604 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
605 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
epoger@google.com055e3b52013-10-26 14:31:11 +0000606
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000607 updatedExpectations.push(updatedExpectation);
epoger@google.comeb832592013-10-23 15:07:26 +0000608 }
epoger@google.com055e3b52013-10-26 14:31:11 +0000609 if (encounteredComparisonConfig) {
610 alert("Approval failed -- you cannot approve results with config " +
611 "type comparison-*");
612 $scope.submitPending = false;
613 return;
614 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000615 var modificationData = {};
616 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
617 updatedExpectations;
618 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
619 $scope.header[constants.KEY__HEADER__DATAHASH];
620 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
621 $scope.header[constants.KEY__HEADER__TYPE];
epoger@google.comeb832592013-10-23 15:07:26 +0000622 $http({
623 method: "POST",
624 url: "/edits",
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000625 data: modificationData
epoger@google.comeb832592013-10-23 15:07:26 +0000626 }).success(function(data, status, headers, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000627 var imagePairIndicesToMove = [];
628 for (var i = 0; i < imagePairsSubset.length; i++) {
629 imagePairIndicesToMove.push(imagePairsSubset[i].index);
epoger@google.comeb832592013-10-23 15:07:26 +0000630 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000631 $scope.moveImagePairsToTab(imagePairIndicesToMove,
632 "HackToMakeSureThisImagePairDisappears");
epoger@google.comeb832592013-10-23 15:07:26 +0000633 $scope.updateResults();
634 alert("New baselines submitted successfully!\n\n" +
635 "You still need to commit the updated expectations files on " +
636 "the server side to the Skia repo.\n\n" +
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +0000637 "When you click OK, your web UI will reload; after that " +
638 "completes, you will see the updated data (once the server has " +
639 "finished loading the update results into memory!) and you can " +
640 "submit more baselines if you want.");
641 // I don't know why, but if I just call reload() here it doesn't work.
642 // Making a timer call it fixes the problem.
643 $timeout(function(){location.reload();}, 1);
epoger@google.comeb832592013-10-23 15:07:26 +0000644 }).error(function(data, status, headers, config) {
645 alert("There was an error submitting your baselines.\n\n" +
646 "Please see server-side log for details.");
647 $scope.submitPending = false;
648 });
649 }
epoger@google.comad0e5522013-10-24 15:38:27 +0000650
651
652 //
653 // Operations we use to mimic Set semantics, in such a way that
654 // checking for presence within the Set is as fast as possible.
655 // But getting a list of all values within the Set is not necessarily
656 // possible.
657 // TODO(epoger): move into a separate .js file?
658 //
659
660 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000661 * Returns the number of values present within set "set".
662 *
663 * @param set an Object which we use to mimic set semantics
664 */
665 $scope.setSize = function(set) {
666 return Object.keys(set).length;
667 }
668
669 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000670 * Returns true if value "value" is present within set "set".
671 *
672 * @param value a value of any type
673 * @param set an Object which we use to mimic set semantics
674 * (this should make isValueInSet faster than if we used an Array)
675 */
676 $scope.isValueInSet = function(value, set) {
677 return (true == set[value]);
678 }
679
680 /**
681 * If value "value" is already in set "set", remove it; otherwise, add it.
682 *
683 * @param value a value of any type
684 * @param set an Object which we use to mimic set semantics
685 */
686 $scope.toggleValueInSet = function(value, set) {
687 if (true == set[value]) {
688 delete set[value];
689 } else {
690 set[value] = true;
691 }
692 }
693
epoger@google.comf4394d52013-10-29 15:49:40 +0000694 /**
695 * For each value in valueArray, call toggleValueInSet(value, set).
696 *
697 * @param valueArray
698 * @param set
699 */
700 $scope.toggleValuesInSet = function(valueArray, set) {
701 var arrayLength = valueArray.length;
702 for (var i = 0; i < arrayLength; i++) {
703 $scope.toggleValueInSet(valueArray[i], set);
704 }
705 }
706
epoger@google.comad0e5522013-10-24 15:38:27 +0000707
708 //
709 // Array operations; similar to our Set operations, but operate on a
710 // Javascript Array so we *can* easily get a list of all values in the Set.
711 // TODO(epoger): move into a separate .js file?
712 //
713
714 /**
715 * Returns true if value "value" is present within array "array".
716 *
717 * @param value a value of any type
718 * @param array a Javascript Array
719 */
720 $scope.isValueInArray = function(value, array) {
721 return (-1 != array.indexOf(value));
722 }
723
724 /**
725 * If value "value" is already in array "array", remove it; otherwise,
726 * add it.
727 *
728 * @param value a value of any type
729 * @param array a Javascript Array
730 */
731 $scope.toggleValueInArray = function(value, array) {
732 var i = array.indexOf(value);
733 if (-1 == i) {
734 array.push(value);
735 } else {
736 array.splice(i, 1);
737 }
738 }
739
740
741 //
742 // Miscellaneous utility functions.
743 // TODO(epoger): move into a separate .js file?
744 //
745
746 /**
747 * Returns a human-readable (in local time zone) time string for a
748 * particular moment in time.
749 *
750 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
751 */
752 $scope.localTimeString = function(secondsPastEpoch) {
753 var d = new Date(secondsPastEpoch * 1000);
754 return d.toString();
755 }
756
commit-bot@chromium.orgceba0792014-02-05 19:49:17 +0000757 /**
758 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
759 *
760 * @param r (numeric): red channel value, 0-255
761 * @param g (numeric): green channel value, 0-255
762 * @param b (numeric): blue channel value, 0-255
763 */
764 $scope.hexColorString = function(r, g, b) {
765 var rString = r.toString(16);
766 if (r < 16) {
767 rString = "0" + rString;
768 }
769 var gString = g.toString(16);
770 if (g < 16) {
771 gString = "0" + gString;
772 }
773 var bString = b.toString(16);
774 if (b < 16) {
775 bString = "0" + bString;
776 }
777 return '#' + rString + gString + bString;
778 }
779
780 /**
781 * Returns a hex color string (such as "#aabbcc") for the given brightness.
782 *
783 * @param brightnessString (string): 0-255, 0 is completely black
784 *
785 * TODO(epoger): It might be nice to tint the color when it's not completely
786 * black or completely white.
787 */
788 $scope.brightnessStringToHexColor = function(brightnessString) {
789 var v = parseInt(brightnessString);
790 return $scope.hexColorString(v, v, v);
791 }
792
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000793 /**
794 * Returns the last path component of image diff URL for a given ImagePair.
795 *
796 * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this
797 * will be relative to different base URLs.
798 *
799 * We must keep this function in sync with _get_difference_locator() in
800 * ../imagediffdb.py
801 *
802 * @param imagePair: ImagePair to generate image diff URL for
803 */
804 $scope.getImageDiffRelativeUrl = function(imagePair) {
805 var before =
806 imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" +
807 imagePair[constants.KEY__IMAGE_B_URL];
808 return before.replace(/[^\w\-]/g, "_") + ".png";
809 }
810
epoger@google.comf9d134d2013-09-27 15:02:44 +0000811 }
812);