blob: 645724c194a5d649b3214d5c12fe3ac0fcc157cb [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.org7498d952014-03-13 14:56:29 +000066 $scope.loadingMessage = "Loading results from '" + $scope.resultsToLoad +
epoger@google.comdcb4e652013-10-11 18:45:33 +000067 "', please wait...";
68
epoger@google.comad0e5522013-10-24 15:38:27 +000069 /**
70 * On initial page load, load a full dictionary of results.
71 * Once the dictionary is loaded, unhide the page elements so they can
72 * render the data.
73 */
commit-bot@chromium.org7498d952014-03-13 14:56:29 +000074 $http.get($scope.resultsToLoad).success(
epoger@google.comdcb4e652013-10-11 18:45:33 +000075 function(data, status, header, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000076 var dataHeader = data[constants.KEY__HEADER];
77 if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) {
commit-bot@chromium.orged191072014-03-10 18:05:15 +000078 // Apply the server's requested reload delay to local time,
79 // so we will wait the right number of seconds regardless of clock
80 // skew between client and server.
81 var reloadDelayInSeconds =
82 dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] -
83 dataHeader[constants.KEY__HEADER__TIME_UPDATED];
84 var timeNow = new Date().getTime();
85 var timeToReload = timeNow + reloadDelayInSeconds * 1000;
epoger@google.com2682c902013-12-05 16:05:16 +000086 $scope.loadingMessage =
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +000087 "Server is still loading results; will retry at " +
commit-bot@chromium.orged191072014-03-10 18:05:15 +000088 $scope.localTimeString(timeToReload / 1000);
epoger@google.com2682c902013-12-05 16:05:16 +000089 $timeout(
90 function(){location.reload();},
commit-bot@chromium.orged191072014-03-10 18:05:15 +000091 timeToReload - timeNow);
epoger@google.com2682c902013-12-05 16:05:16 +000092 } else {
93 $scope.loadingMessage = "Processing data, please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +000094
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000095 $scope.header = dataHeader;
96 $scope.extraColumnHeaders = data[constants.KEY__EXTRACOLUMNHEADERS];
97 $scope.imagePairs = data[constants.KEY__IMAGEPAIRS];
98 $scope.imageSets = data[constants.KEY__IMAGESETS];
99 $scope.sortColumnSubdict = constants.KEY__DIFFERENCE_DATA;
100 $scope.sortColumnKey = constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000101
epoger@google.com2682c902013-12-05 16:05:16 +0000102 $scope.showSubmitAdvancedSettings = false;
103 $scope.submitAdvancedSettings = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000104 $scope.submitAdvancedSettings[
105 constants.KEY__EXPECTATIONS__REVIEWED] = true;
106 $scope.submitAdvancedSettings[
107 constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false;
epoger@google.com2682c902013-12-05 16:05:16 +0000108 $scope.submitAdvancedSettings['bug'] = '';
epoger@google.com055e3b52013-10-26 14:31:11 +0000109
epoger@google.com2682c902013-12-05 16:05:16 +0000110 // Create the list of tabs (lists into which the user can file each
111 // test). This may vary, depending on isEditable.
112 $scope.tabs = [
113 'Unfiled', 'Hidden'
114 ];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000115 if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) {
epoger@google.com2682c902013-12-05 16:05:16 +0000116 $scope.tabs = $scope.tabs.concat(
117 ['Pending Approval']);
118 }
119 $scope.defaultTab = $scope.tabs[0];
120 $scope.viewingTab = $scope.defaultTab;
121
122 // Track the number of results on each tab.
123 $scope.numResultsPerTab = {};
124 for (var i = 0; i < $scope.tabs.length; i++) {
125 $scope.numResultsPerTab[$scope.tabs[i]] = 0;
126 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000127 $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length;
epoger@google.com2682c902013-12-05 16:05:16 +0000128
129 // Add index and tab fields to all records.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000130 for (var i = 0; i < $scope.imagePairs.length; i++) {
131 $scope.imagePairs[i].index = i;
132 $scope.imagePairs[i].tab = $scope.defaultTab;
epoger@google.com2682c902013-12-05 16:05:16 +0000133 }
134
135 // Arrays within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000136 $scope.selectedImagePairs = [];
epoger@google.com2682c902013-12-05 16:05:16 +0000137
138 // Sets within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000139 $scope.hiddenResultTypes = {};
140 $scope.hiddenResultTypes[
141 constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true;
142 $scope.hiddenResultTypes[
143 constants.KEY__RESULT_TYPE__NOCOMPARISON] = true;
144 $scope.hiddenResultTypes[
145 constants.KEY__RESULT_TYPE__SUCCEEDED] = true;
146 $scope.allResultTypes = Object.keys(
147 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE]
148 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000149 $scope.hiddenConfigs = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000150 $scope.allConfigs = Object.keys(
151 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG]
152 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000153
154 // Associative array of partial string matches per category.
155 $scope.categoryValueMatch = {};
156 $scope.categoryValueMatch.builder = "";
157 $scope.categoryValueMatch.test = "";
158
epoger@google.com62a5ef02013-12-05 18:03:24 +0000159 // If any defaults were overridden in the URL, get them now.
160 $scope.queryParameters.load();
161
epoger@google.com2682c902013-12-05 16:05:16 +0000162 $scope.updateResults();
163 $scope.loadingMessage = "";
164 $scope.windowTitle = "Current GM Results";
epoger@google.comeb832592013-10-23 15:07:26 +0000165 }
epoger@google.comdcb4e652013-10-11 18:45:33 +0000166 }
167 ).error(
168 function(data, status, header, config) {
commit-bot@chromium.org7498d952014-03-13 14:56:29 +0000169 $scope.loadingMessage = "Failed to load results from '"
epoger@google.com62a5ef02013-12-05 18:03:24 +0000170 + $scope.resultsToLoad + "'";
epoger@google.com542b65f2013-10-15 20:10:33 +0000171 $scope.windowTitle = "Failed to Load GM Results";
epoger@google.comf9d134d2013-09-27 15:02:44 +0000172 }
173 );
epoger@google.com5f2bb002013-10-02 18:57:48 +0000174
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000175
epoger@google.comad0e5522013-10-24 15:38:27 +0000176 //
epoger@google.com055e3b52013-10-26 14:31:11 +0000177 // Select/Clear/Toggle all tests.
178 //
179
180 /**
181 * Select all currently showing tests.
182 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000183 $scope.selectAllImagePairs = function() {
184 var numImagePairsShowing = $scope.limitedImagePairs.length;
185 for (var i = 0; i < numImagePairsShowing; i++) {
186 var index = $scope.limitedImagePairs[i].index;
187 if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) {
188 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000189 }
190 }
191 }
192
193 /**
194 * Deselect all currently showing tests.
195 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000196 $scope.clearAllImagePairs = function() {
197 var numImagePairsShowing = $scope.limitedImagePairs.length;
198 for (var i = 0; i < numImagePairsShowing; i++) {
199 var index = $scope.limitedImagePairs[i].index;
200 if ($scope.isValueInArray(index, $scope.selectedImagePairs)) {
201 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000202 }
203 }
204 }
205
206 /**
207 * Toggle selection of all currently showing tests.
208 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000209 $scope.toggleAllImagePairs = function() {
210 var numImagePairsShowing = $scope.limitedImagePairs.length;
211 for (var i = 0; i < numImagePairsShowing; i++) {
212 var index = $scope.limitedImagePairs[i].index;
213 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000214 }
215 }
216
217
218 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000219 // Tab operations.
220 //
epoger@google.com5f2bb002013-10-02 18:57:48 +0000221
epoger@google.comad0e5522013-10-24 15:38:27 +0000222 /**
223 * Change the selected tab.
224 *
225 * @param tab (string): name of the tab to select
226 */
epoger@google.comeb832592013-10-23 15:07:26 +0000227 $scope.setViewingTab = function(tab) {
228 $scope.viewingTab = tab;
229 $scope.updateResults();
230 }
231
epoger@google.comeb832592013-10-23 15:07:26 +0000232 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000233 * Move the imagePairs in $scope.selectedImagePairs to a different tab,
234 * and then clear $scope.selectedImagePairs.
epoger@google.comeb832592013-10-23 15:07:26 +0000235 *
236 * @param newTab (string): name of the tab to move the tests to
237 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000238 $scope.moveSelectedImagePairsToTab = function(newTab) {
239 $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
240 $scope.selectedImagePairs = [];
epoger@google.comeb832592013-10-23 15:07:26 +0000241 $scope.updateResults();
242 }
243
244 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000245 * Move a subset of $scope.imagePairs to a different tab.
epoger@google.comeb832592013-10-23 15:07:26 +0000246 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000247 * @param imagePairIndices (array of ints): indices into $scope.imagePairs
epoger@google.comeb832592013-10-23 15:07:26 +0000248 * indicating which test results to move
249 * @param newTab (string): name of the tab to move the tests to
250 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000251 $scope.moveImagePairsToTab = function(imagePairIndices, newTab) {
252 var imagePairIndex;
253 var numImagePairs = imagePairIndices.length;
254 for (var i = 0; i < numImagePairs; i++) {
255 imagePairIndex = imagePairIndices[i];
256 $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--;
257 $scope.imagePairs[imagePairIndex].tab = newTab;
epoger@google.comeb832592013-10-23 15:07:26 +0000258 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000259 $scope.numResultsPerTab[newTab] += numImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000260 }
261
epoger@google.comad0e5522013-10-24 15:38:27 +0000262
263 //
epoger@google.com62a5ef02013-12-05 18:03:24 +0000264 // $scope.queryParameters:
265 // Transfer parameter values between $scope and the URL query string.
266 //
267 $scope.queryParameters = {};
268
269 // load and save functions for parameters of each type
270 // (load a parameter value into $scope from nameValuePairs,
271 // save a parameter value from $scope into nameValuePairs)
272 $scope.queryParameters.copiers = {
273 'simple': {
274 'load': function(nameValuePairs, name) {
275 var value = nameValuePairs[name];
276 if (value) {
277 $scope[name] = value;
278 }
279 },
280 'save': function(nameValuePairs, name) {
281 nameValuePairs[name] = $scope[name];
282 }
283 },
284
285 'categoryValueMatch': {
286 'load': function(nameValuePairs, name) {
287 var value = nameValuePairs[name];
288 if (value) {
289 $scope.categoryValueMatch[name] = value;
290 }
291 },
292 'save': function(nameValuePairs, name) {
293 nameValuePairs[name] = $scope.categoryValueMatch[name];
294 }
295 },
296
297 'set': {
298 'load': function(nameValuePairs, name) {
299 var value = nameValuePairs[name];
300 if (value) {
301 var valueArray = value.split(',');
302 $scope[name] = {};
303 $scope.toggleValuesInSet(valueArray, $scope[name]);
304 }
305 },
306 'save': function(nameValuePairs, name) {
307 nameValuePairs[name] = Object.keys($scope[name]).join(',');
308 }
309 },
310
311 };
312
313 // parameter name -> copier objects to load/save parameter value
314 $scope.queryParameters.map = {
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000315 'resultsToLoad': $scope.queryParameters.copiers.simple,
316 'displayLimitPending': $scope.queryParameters.copiers.simple,
317 'showThumbnailsPending': $scope.queryParameters.copiers.simple,
318 'imageSizePending': $scope.queryParameters.copiers.simple,
319 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
320 'sortColumnKey': $scope.queryParameters.copiers.simple,
epoger@google.com62a5ef02013-12-05 18:03:24 +0000321
322 'hiddenResultTypes': $scope.queryParameters.copiers.set,
323 'hiddenConfigs': $scope.queryParameters.copiers.set,
324 };
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000325 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] =
326 $scope.queryParameters.copiers.categoryValueMatch;
327 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] =
328 $scope.queryParameters.copiers.categoryValueMatch;
epoger@google.com62a5ef02013-12-05 18:03:24 +0000329
330 // Loads all parameters into $scope from the URL query string;
331 // any which are not found within the URL will keep their current value.
332 $scope.queryParameters.load = function() {
333 var nameValuePairs = $location.search();
334 angular.forEach($scope.queryParameters.map,
335 function(copier, paramName) {
336 copier.load(nameValuePairs, paramName);
337 }
338 );
339 };
340
341 // Saves all parameters from $scope into the URL query string.
342 $scope.queryParameters.save = function() {
343 var nameValuePairs = {};
344 angular.forEach($scope.queryParameters.map,
345 function(copier, paramName) {
346 copier.save(nameValuePairs, paramName);
347 }
348 );
349 $location.search(nameValuePairs);
350 };
351
352
353 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000354 // updateResults() and friends.
355 //
356
357 /**
358 * Set $scope.areUpdatesPending (to enable/disable the Update Results
359 * button).
360 *
361 * TODO(epoger): We could reduce the amount of code by just setting the
362 * variable directly (from, e.g., a button's ng-click handler). But when
363 * I tried that, the HTML elements depending on the variable did not get
364 * updated.
365 * It turns out that this is due to variable scoping within an ng-repeat
366 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat
367 *
368 * @param val boolean value to set $scope.areUpdatesPending to
369 */
370 $scope.setUpdatesPending = function(val) {
371 $scope.areUpdatesPending = val;
372 }
373
374 /**
epoger@google.com62a5ef02013-12-05 18:03:24 +0000375 * Update the displayed results, based on filters/settings,
376 * and call $scope.queryParameters.save() so that the new filter results
377 * can be bookmarked.
epoger@google.comad0e5522013-10-24 15:38:27 +0000378 */
epoger@google.com5f2bb002013-10-02 18:57:48 +0000379 $scope.updateResults = function() {
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000380 $scope.renderStartTime = window.performance.now();
381 $log.debug("renderStartTime: " + $scope.renderStartTime);
epoger@google.com5f2bb002013-10-02 18:57:48 +0000382 $scope.displayLimit = $scope.displayLimitPending;
383 // TODO(epoger): Every time we apply a filter, AngularJS creates
384 // another copy of the array. Is there a way we can filter out
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000385 // the imagePairs as they are displayed, rather than storing multiple
epoger@google.com5f2bb002013-10-02 18:57:48 +0000386 // array copies? (For better performance.)
epoger@google.comeb832592013-10-23 15:07:26 +0000387
388 if ($scope.viewingTab == $scope.defaultTab) {
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000389
390 // TODO(epoger): Until we allow the user to reverse sort order,
391 // there are certain columns we want to sort in a different order.
392 var doReverse = (
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000393 ($scope.sortColumnKey ==
394 constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) ||
395 ($scope.sortColumnKey ==
396 constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF));
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000397
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000398 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000399 $filter("orderBy")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000400 $filter("removeHiddenImagePairs")(
401 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000402 $scope.hiddenResultTypes,
403 $scope.hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +0000404 $scope.categoryValueMatch.builder,
405 $scope.categoryValueMatch.test,
epoger@google.comeb832592013-10-23 15:07:26 +0000406 $scope.viewingTab
407 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000408 $scope.getSortColumnValue, doReverse);
409 $scope.limitedImagePairs = $filter("limitTo")(
410 $scope.filteredImagePairs, $scope.displayLimit);
epoger@google.comeb832592013-10-23 15:07:26 +0000411 } else {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000412 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000413 $filter("orderBy")(
414 $filter("filter")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000415 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000416 {tab: $scope.viewingTab},
417 true
418 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000419 $scope.getSortColumnValue);
420 $scope.limitedImagePairs = $scope.filteredImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000421 }
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000422 $scope.showThumbnails = $scope.showThumbnailsPending;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000423 $scope.imageSize = $scope.imageSizePending;
epoger@google.comad0e5522013-10-24 15:38:27 +0000424 $scope.setUpdatesPending(false);
epoger@google.com62a5ef02013-12-05 18:03:24 +0000425 $scope.queryParameters.save();
epoger@google.com5f2bb002013-10-02 18:57:48 +0000426 }
427
epoger@google.comad0e5522013-10-24 15:38:27 +0000428 /**
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000429 * This function is called when the results have been completely rendered
430 * after updateResults().
431 */
432 $scope.resultsUpdatedCallback = function() {
433 $scope.renderEndTime = window.performance.now();
434 $log.debug("renderEndTime: " + $scope.renderEndTime);
435 }
436
437 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000438 * Re-sort the displayed results.
439 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000440 * @param subdict (string): which subdictionary
441 * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA,
442 * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within
443 * @param key (string): sort by value associated with this key in subdict
epoger@google.comad0e5522013-10-24 15:38:27 +0000444 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000445 $scope.sortResultsBy = function(subdict, key) {
446 $scope.sortColumnSubdict = subdict;
447 $scope.sortColumnKey = key;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000448 $scope.updateResults();
449 }
epoger@google.comeb832592013-10-23 15:07:26 +0000450
epoger@google.comf4394d52013-10-29 15:49:40 +0000451 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000452 * For a particular ImagePair, return the value of the column we are
453 * sorting on (according to $scope.sortColumnSubdict and
454 * $scope.sortColumnKey).
455 *
456 * @param imagePair: imagePair to get a column value out of.
457 */
458 $scope.getSortColumnValue = function(imagePair) {
459 if ($scope.sortColumnSubdict in imagePair) {
460 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
461 } else {
462 return undefined;
463 }
464 }
465
466 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000467 * Set $scope.categoryValueMatch[name] = value, and update results.
468 *
469 * @param name
470 * @param value
471 */
472 $scope.setCategoryValueMatch = function(name, value) {
473 $scope.categoryValueMatch[name] = value;
474 $scope.updateResults();
475 }
476
477 /**
478 * Update $scope.hiddenResultTypes so that ONLY this resultType is showing,
479 * and update the visible results.
480 *
481 * @param resultType
482 */
483 $scope.showOnlyResultType = function(resultType) {
484 $scope.hiddenResultTypes = {};
485 // TODO(epoger): Maybe change $scope.allResultTypes to be a Set like
486 // $scope.hiddenResultTypes (rather than an array), so this operation is
487 // simpler (just assign or add allResultTypes to hiddenResultTypes).
488 $scope.toggleValuesInSet($scope.allResultTypes, $scope.hiddenResultTypes);
489 $scope.toggleValueInSet(resultType, $scope.hiddenResultTypes);
490 $scope.updateResults();
491 }
492
493 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000494 * Update $scope.hiddenResultTypes so that ALL resultTypes are showing,
495 * and update the visible results.
496 */
497 $scope.showAllResultTypes = function() {
498 $scope.hiddenResultTypes = {};
499 $scope.updateResults();
500 }
501
502 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000503 * Update $scope.hiddenConfigs so that ONLY this config is showing,
504 * and update the visible results.
505 *
506 * @param config
507 */
508 $scope.showOnlyConfig = function(config) {
509 $scope.hiddenConfigs = {};
510 $scope.toggleValuesInSet($scope.allConfigs, $scope.hiddenConfigs);
511 $scope.toggleValueInSet(config, $scope.hiddenConfigs);
512 $scope.updateResults();
513 }
514
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000515 /**
516 * Update $scope.hiddenConfigs so that ALL configs are showing,
517 * and update the visible results.
518 */
519 $scope.showAllConfigs = function() {
520 $scope.hiddenConfigs = {};
521 $scope.updateResults();
522 }
523
epoger@google.comad0e5522013-10-24 15:38:27 +0000524
525 //
526 // Operations for sending info back to the server.
527 //
528
epoger@google.comeb832592013-10-23 15:07:26 +0000529 /**
530 * Tell the server that the actual results of these particular tests
531 * are acceptable.
532 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000533 * TODO(epoger): This assumes that the original expectations are in
534 * imageSetA, and the actuals are in imageSetB.
535 *
536 * @param imagePairsSubset an array of test results, most likely a subset of
537 * $scope.imagePairs (perhaps with some modifications)
epoger@google.comeb832592013-10-23 15:07:26 +0000538 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000539 $scope.submitApprovals = function(imagePairsSubset) {
epoger@google.comeb832592013-10-23 15:07:26 +0000540 $scope.submitPending = true;
epoger@google.com055e3b52013-10-26 14:31:11 +0000541
542 // Convert bug text field to null or 1-item array.
543 var bugs = null;
544 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
545 if (!isNaN(bugNumber)) {
546 bugs = [bugNumber];
547 }
548
549 // TODO(epoger): This is a suboptimal way to prevent users from
550 // rebaselining failures in alternative renderModes, but it does work.
551 // For a better solution, see
552 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
553 // result type, RenderModeMismatch')
554 var encounteredComparisonConfig = false;
555
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000556 var updatedExpectations = [];
557 for (var i = 0; i < imagePairsSubset.length; i++) {
558 var imagePair = imagePairsSubset[i];
559 var updatedExpectation = {};
560 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] =
561 imagePair[constants.KEY__EXPECTATIONS_DATA];
562 updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] =
563 imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
564 updatedExpectation[constants.KEY__NEW_IMAGE_URL] =
565 imagePair[constants.KEY__IMAGE_B_URL];
566 if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES]
567 [constants.KEY__EXTRACOLUMN__CONFIG]
568 .indexOf('comparison-')) {
epoger@google.com055e3b52013-10-26 14:31:11 +0000569 encounteredComparisonConfig = true;
570 }
571
572 // Advanced settings...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000573 if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) {
574 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {};
epoger@google.com055e3b52013-10-26 14:31:11 +0000575 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000576 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
577 [constants.KEY__EXPECTATIONS__REVIEWED] =
578 $scope.submitAdvancedSettings[
579 constants.KEY__EXPECTATIONS__REVIEWED];
580 if (true == $scope.submitAdvancedSettings[
581 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
582 // if it's false, don't send it at all (just keep the default)
583 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
584 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
585 }
586 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
587 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
epoger@google.com055e3b52013-10-26 14:31:11 +0000588
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000589 updatedExpectations.push(updatedExpectation);
epoger@google.comeb832592013-10-23 15:07:26 +0000590 }
epoger@google.com055e3b52013-10-26 14:31:11 +0000591 if (encounteredComparisonConfig) {
592 alert("Approval failed -- you cannot approve results with config " +
593 "type comparison-*");
594 $scope.submitPending = false;
595 return;
596 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000597 var modificationData = {};
598 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
599 updatedExpectations;
600 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
601 $scope.header[constants.KEY__HEADER__DATAHASH];
602 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
603 $scope.header[constants.KEY__HEADER__TYPE];
epoger@google.comeb832592013-10-23 15:07:26 +0000604 $http({
605 method: "POST",
606 url: "/edits",
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000607 data: modificationData
epoger@google.comeb832592013-10-23 15:07:26 +0000608 }).success(function(data, status, headers, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000609 var imagePairIndicesToMove = [];
610 for (var i = 0; i < imagePairsSubset.length; i++) {
611 imagePairIndicesToMove.push(imagePairsSubset[i].index);
epoger@google.comeb832592013-10-23 15:07:26 +0000612 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000613 $scope.moveImagePairsToTab(imagePairIndicesToMove,
614 "HackToMakeSureThisImagePairDisappears");
epoger@google.comeb832592013-10-23 15:07:26 +0000615 $scope.updateResults();
616 alert("New baselines submitted successfully!\n\n" +
617 "You still need to commit the updated expectations files on " +
618 "the server side to the Skia repo.\n\n" +
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +0000619 "When you click OK, your web UI will reload; after that " +
620 "completes, you will see the updated data (once the server has " +
621 "finished loading the update results into memory!) and you can " +
622 "submit more baselines if you want.");
623 // I don't know why, but if I just call reload() here it doesn't work.
624 // Making a timer call it fixes the problem.
625 $timeout(function(){location.reload();}, 1);
epoger@google.comeb832592013-10-23 15:07:26 +0000626 }).error(function(data, status, headers, config) {
627 alert("There was an error submitting your baselines.\n\n" +
628 "Please see server-side log for details.");
629 $scope.submitPending = false;
630 });
631 }
epoger@google.comad0e5522013-10-24 15:38:27 +0000632
633
634 //
635 // Operations we use to mimic Set semantics, in such a way that
636 // checking for presence within the Set is as fast as possible.
637 // But getting a list of all values within the Set is not necessarily
638 // possible.
639 // TODO(epoger): move into a separate .js file?
640 //
641
642 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000643 * Returns the number of values present within set "set".
644 *
645 * @param set an Object which we use to mimic set semantics
646 */
647 $scope.setSize = function(set) {
648 return Object.keys(set).length;
649 }
650
651 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000652 * Returns true if value "value" is present within set "set".
653 *
654 * @param value a value of any type
655 * @param set an Object which we use to mimic set semantics
656 * (this should make isValueInSet faster than if we used an Array)
657 */
658 $scope.isValueInSet = function(value, set) {
659 return (true == set[value]);
660 }
661
662 /**
663 * If value "value" is already in set "set", remove it; otherwise, add it.
664 *
665 * @param value a value of any type
666 * @param set an Object which we use to mimic set semantics
667 */
668 $scope.toggleValueInSet = function(value, set) {
669 if (true == set[value]) {
670 delete set[value];
671 } else {
672 set[value] = true;
673 }
674 }
675
epoger@google.comf4394d52013-10-29 15:49:40 +0000676 /**
677 * For each value in valueArray, call toggleValueInSet(value, set).
678 *
679 * @param valueArray
680 * @param set
681 */
682 $scope.toggleValuesInSet = function(valueArray, set) {
683 var arrayLength = valueArray.length;
684 for (var i = 0; i < arrayLength; i++) {
685 $scope.toggleValueInSet(valueArray[i], set);
686 }
687 }
688
epoger@google.comad0e5522013-10-24 15:38:27 +0000689
690 //
691 // Array operations; similar to our Set operations, but operate on a
692 // Javascript Array so we *can* easily get a list of all values in the Set.
693 // TODO(epoger): move into a separate .js file?
694 //
695
696 /**
697 * Returns true if value "value" is present within array "array".
698 *
699 * @param value a value of any type
700 * @param array a Javascript Array
701 */
702 $scope.isValueInArray = function(value, array) {
703 return (-1 != array.indexOf(value));
704 }
705
706 /**
707 * If value "value" is already in array "array", remove it; otherwise,
708 * add it.
709 *
710 * @param value a value of any type
711 * @param array a Javascript Array
712 */
713 $scope.toggleValueInArray = function(value, array) {
714 var i = array.indexOf(value);
715 if (-1 == i) {
716 array.push(value);
717 } else {
718 array.splice(i, 1);
719 }
720 }
721
722
723 //
724 // Miscellaneous utility functions.
725 // TODO(epoger): move into a separate .js file?
726 //
727
728 /**
729 * Returns a human-readable (in local time zone) time string for a
730 * particular moment in time.
731 *
732 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
733 */
734 $scope.localTimeString = function(secondsPastEpoch) {
735 var d = new Date(secondsPastEpoch * 1000);
736 return d.toString();
737 }
738
commit-bot@chromium.orgceba0792014-02-05 19:49:17 +0000739 /**
740 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
741 *
742 * @param r (numeric): red channel value, 0-255
743 * @param g (numeric): green channel value, 0-255
744 * @param b (numeric): blue channel value, 0-255
745 */
746 $scope.hexColorString = function(r, g, b) {
747 var rString = r.toString(16);
748 if (r < 16) {
749 rString = "0" + rString;
750 }
751 var gString = g.toString(16);
752 if (g < 16) {
753 gString = "0" + gString;
754 }
755 var bString = b.toString(16);
756 if (b < 16) {
757 bString = "0" + bString;
758 }
759 return '#' + rString + gString + bString;
760 }
761
762 /**
763 * Returns a hex color string (such as "#aabbcc") for the given brightness.
764 *
765 * @param brightnessString (string): 0-255, 0 is completely black
766 *
767 * TODO(epoger): It might be nice to tint the color when it's not completely
768 * black or completely white.
769 */
770 $scope.brightnessStringToHexColor = function(brightnessString) {
771 var v = parseInt(brightnessString);
772 return $scope.hexColorString(v, v, v);
773 }
774
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000775 /**
776 * Returns the last path component of image diff URL for a given ImagePair.
777 *
778 * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this
779 * will be relative to different base URLs.
780 *
781 * We must keep this function in sync with _get_difference_locator() in
782 * ../imagediffdb.py
783 *
784 * @param imagePair: ImagePair to generate image diff URL for
785 */
786 $scope.getImageDiffRelativeUrl = function(imagePair) {
787 var before =
788 imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" +
789 imagePair[constants.KEY__IMAGE_B_URL];
790 return before.replace(/[^\w\-]/g, "_") + ".png";
791 }
792
epoger@google.comf9d134d2013-09-27 15:02:44 +0000793 }
794);