blob: 254d0fda05e1956ab39537fc7663bb443414c520 [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.org16f41802014-02-26 19:05:20 +00008 ['ConstantsModule', 'diff_viewer']
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;
66 $scope.loadingMessage = "Loading results of type '" + $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 */
epoger@google.com62a5ef02013-12-05 18:03:24 +000074 $http.get("/results/" + $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]) {
epoger@google.com2682c902013-12-05 16:05:16 +000078 $scope.loadingMessage =
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +000079 "Server is still loading results; will retry at " +
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000080 $scope.localTimeString(dataHeader[
81 constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE]);
epoger@google.com2682c902013-12-05 16:05:16 +000082 $timeout(
83 function(){location.reload();},
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000084 (dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE]
85 * 1000) - new Date().getTime());
epoger@google.com2682c902013-12-05 16:05:16 +000086 } else {
87 $scope.loadingMessage = "Processing data, please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +000088
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000089 $scope.header = dataHeader;
90 $scope.extraColumnHeaders = data[constants.KEY__EXTRACOLUMNHEADERS];
91 $scope.imagePairs = data[constants.KEY__IMAGEPAIRS];
92 $scope.imageSets = data[constants.KEY__IMAGESETS];
93 $scope.sortColumnSubdict = constants.KEY__DIFFERENCE_DATA;
94 $scope.sortColumnKey = constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF;
epoger@google.com5f2bb002013-10-02 18:57:48 +000095
epoger@google.com2682c902013-12-05 16:05:16 +000096 $scope.showSubmitAdvancedSettings = false;
97 $scope.submitAdvancedSettings = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000098 $scope.submitAdvancedSettings[
99 constants.KEY__EXPECTATIONS__REVIEWED] = true;
100 $scope.submitAdvancedSettings[
101 constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false;
epoger@google.com2682c902013-12-05 16:05:16 +0000102 $scope.submitAdvancedSettings['bug'] = '';
epoger@google.com055e3b52013-10-26 14:31:11 +0000103
epoger@google.com2682c902013-12-05 16:05:16 +0000104 // Create the list of tabs (lists into which the user can file each
105 // test). This may vary, depending on isEditable.
106 $scope.tabs = [
107 'Unfiled', 'Hidden'
108 ];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000109 if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) {
epoger@google.com2682c902013-12-05 16:05:16 +0000110 $scope.tabs = $scope.tabs.concat(
111 ['Pending Approval']);
112 }
113 $scope.defaultTab = $scope.tabs[0];
114 $scope.viewingTab = $scope.defaultTab;
115
116 // Track the number of results on each tab.
117 $scope.numResultsPerTab = {};
118 for (var i = 0; i < $scope.tabs.length; i++) {
119 $scope.numResultsPerTab[$scope.tabs[i]] = 0;
120 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000121 $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length;
epoger@google.com2682c902013-12-05 16:05:16 +0000122
123 // Add index and tab fields to all records.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000124 for (var i = 0; i < $scope.imagePairs.length; i++) {
125 $scope.imagePairs[i].index = i;
126 $scope.imagePairs[i].tab = $scope.defaultTab;
epoger@google.com2682c902013-12-05 16:05:16 +0000127 }
128
129 // Arrays within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000130 $scope.selectedImagePairs = [];
epoger@google.com2682c902013-12-05 16:05:16 +0000131
132 // Sets within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000133 $scope.hiddenResultTypes = {};
134 $scope.hiddenResultTypes[
135 constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true;
136 $scope.hiddenResultTypes[
137 constants.KEY__RESULT_TYPE__NOCOMPARISON] = true;
138 $scope.hiddenResultTypes[
139 constants.KEY__RESULT_TYPE__SUCCEEDED] = true;
140 $scope.allResultTypes = Object.keys(
141 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE]
142 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000143 $scope.hiddenConfigs = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000144 $scope.allConfigs = Object.keys(
145 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG]
146 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000147
148 // Associative array of partial string matches per category.
149 $scope.categoryValueMatch = {};
150 $scope.categoryValueMatch.builder = "";
151 $scope.categoryValueMatch.test = "";
152
epoger@google.com62a5ef02013-12-05 18:03:24 +0000153 // If any defaults were overridden in the URL, get them now.
154 $scope.queryParameters.load();
155
epoger@google.com2682c902013-12-05 16:05:16 +0000156 $scope.updateResults();
157 $scope.loadingMessage = "";
158 $scope.windowTitle = "Current GM Results";
epoger@google.comeb832592013-10-23 15:07:26 +0000159 }
epoger@google.comdcb4e652013-10-11 18:45:33 +0000160 }
161 ).error(
162 function(data, status, header, config) {
163 $scope.loadingMessage = "Failed to load results of type '"
epoger@google.com62a5ef02013-12-05 18:03:24 +0000164 + $scope.resultsToLoad + "'";
epoger@google.com542b65f2013-10-15 20:10:33 +0000165 $scope.windowTitle = "Failed to Load GM Results";
epoger@google.comf9d134d2013-09-27 15:02:44 +0000166 }
167 );
epoger@google.com5f2bb002013-10-02 18:57:48 +0000168
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000169
epoger@google.comad0e5522013-10-24 15:38:27 +0000170 //
epoger@google.com055e3b52013-10-26 14:31:11 +0000171 // Select/Clear/Toggle all tests.
172 //
173
174 /**
175 * Select all currently showing tests.
176 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000177 $scope.selectAllImagePairs = function() {
178 var numImagePairsShowing = $scope.limitedImagePairs.length;
179 for (var i = 0; i < numImagePairsShowing; i++) {
180 var index = $scope.limitedImagePairs[i].index;
181 if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) {
182 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000183 }
184 }
185 }
186
187 /**
188 * Deselect all currently showing tests.
189 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000190 $scope.clearAllImagePairs = function() {
191 var numImagePairsShowing = $scope.limitedImagePairs.length;
192 for (var i = 0; i < numImagePairsShowing; i++) {
193 var index = $scope.limitedImagePairs[i].index;
194 if ($scope.isValueInArray(index, $scope.selectedImagePairs)) {
195 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000196 }
197 }
198 }
199
200 /**
201 * Toggle selection of all currently showing tests.
202 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000203 $scope.toggleAllImagePairs = function() {
204 var numImagePairsShowing = $scope.limitedImagePairs.length;
205 for (var i = 0; i < numImagePairsShowing; i++) {
206 var index = $scope.limitedImagePairs[i].index;
207 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000208 }
209 }
210
211
212 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000213 // Tab operations.
214 //
epoger@google.com5f2bb002013-10-02 18:57:48 +0000215
epoger@google.comad0e5522013-10-24 15:38:27 +0000216 /**
217 * Change the selected tab.
218 *
219 * @param tab (string): name of the tab to select
220 */
epoger@google.comeb832592013-10-23 15:07:26 +0000221 $scope.setViewingTab = function(tab) {
222 $scope.viewingTab = tab;
223 $scope.updateResults();
224 }
225
epoger@google.comeb832592013-10-23 15:07:26 +0000226 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000227 * Move the imagePairs in $scope.selectedImagePairs to a different tab,
228 * and then clear $scope.selectedImagePairs.
epoger@google.comeb832592013-10-23 15:07:26 +0000229 *
230 * @param newTab (string): name of the tab to move the tests to
231 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000232 $scope.moveSelectedImagePairsToTab = function(newTab) {
233 $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
234 $scope.selectedImagePairs = [];
epoger@google.comeb832592013-10-23 15:07:26 +0000235 $scope.updateResults();
236 }
237
238 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000239 * Move a subset of $scope.imagePairs to a different tab.
epoger@google.comeb832592013-10-23 15:07:26 +0000240 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000241 * @param imagePairIndices (array of ints): indices into $scope.imagePairs
epoger@google.comeb832592013-10-23 15:07:26 +0000242 * indicating which test results to move
243 * @param newTab (string): name of the tab to move the tests to
244 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000245 $scope.moveImagePairsToTab = function(imagePairIndices, newTab) {
246 var imagePairIndex;
247 var numImagePairs = imagePairIndices.length;
248 for (var i = 0; i < numImagePairs; i++) {
249 imagePairIndex = imagePairIndices[i];
250 $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--;
251 $scope.imagePairs[imagePairIndex].tab = newTab;
epoger@google.comeb832592013-10-23 15:07:26 +0000252 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000253 $scope.numResultsPerTab[newTab] += numImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000254 }
255
epoger@google.comad0e5522013-10-24 15:38:27 +0000256
257 //
epoger@google.com62a5ef02013-12-05 18:03:24 +0000258 // $scope.queryParameters:
259 // Transfer parameter values between $scope and the URL query string.
260 //
261 $scope.queryParameters = {};
262
263 // load and save functions for parameters of each type
264 // (load a parameter value into $scope from nameValuePairs,
265 // save a parameter value from $scope into nameValuePairs)
266 $scope.queryParameters.copiers = {
267 'simple': {
268 'load': function(nameValuePairs, name) {
269 var value = nameValuePairs[name];
270 if (value) {
271 $scope[name] = value;
272 }
273 },
274 'save': function(nameValuePairs, name) {
275 nameValuePairs[name] = $scope[name];
276 }
277 },
278
279 'categoryValueMatch': {
280 'load': function(nameValuePairs, name) {
281 var value = nameValuePairs[name];
282 if (value) {
283 $scope.categoryValueMatch[name] = value;
284 }
285 },
286 'save': function(nameValuePairs, name) {
287 nameValuePairs[name] = $scope.categoryValueMatch[name];
288 }
289 },
290
291 'set': {
292 'load': function(nameValuePairs, name) {
293 var value = nameValuePairs[name];
294 if (value) {
295 var valueArray = value.split(',');
296 $scope[name] = {};
297 $scope.toggleValuesInSet(valueArray, $scope[name]);
298 }
299 },
300 'save': function(nameValuePairs, name) {
301 nameValuePairs[name] = Object.keys($scope[name]).join(',');
302 }
303 },
304
305 };
306
307 // parameter name -> copier objects to load/save parameter value
308 $scope.queryParameters.map = {
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000309 'resultsToLoad': $scope.queryParameters.copiers.simple,
310 'displayLimitPending': $scope.queryParameters.copiers.simple,
311 'showThumbnailsPending': $scope.queryParameters.copiers.simple,
312 'imageSizePending': $scope.queryParameters.copiers.simple,
313 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
314 'sortColumnKey': $scope.queryParameters.copiers.simple,
epoger@google.com62a5ef02013-12-05 18:03:24 +0000315
316 'hiddenResultTypes': $scope.queryParameters.copiers.set,
317 'hiddenConfigs': $scope.queryParameters.copiers.set,
318 };
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000319 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] =
320 $scope.queryParameters.copiers.categoryValueMatch;
321 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] =
322 $scope.queryParameters.copiers.categoryValueMatch;
epoger@google.com62a5ef02013-12-05 18:03:24 +0000323
324 // Loads all parameters into $scope from the URL query string;
325 // any which are not found within the URL will keep their current value.
326 $scope.queryParameters.load = function() {
327 var nameValuePairs = $location.search();
328 angular.forEach($scope.queryParameters.map,
329 function(copier, paramName) {
330 copier.load(nameValuePairs, paramName);
331 }
332 );
333 };
334
335 // Saves all parameters from $scope into the URL query string.
336 $scope.queryParameters.save = function() {
337 var nameValuePairs = {};
338 angular.forEach($scope.queryParameters.map,
339 function(copier, paramName) {
340 copier.save(nameValuePairs, paramName);
341 }
342 );
343 $location.search(nameValuePairs);
344 };
345
346
347 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000348 // updateResults() and friends.
349 //
350
351 /**
352 * Set $scope.areUpdatesPending (to enable/disable the Update Results
353 * button).
354 *
355 * TODO(epoger): We could reduce the amount of code by just setting the
356 * variable directly (from, e.g., a button's ng-click handler). But when
357 * I tried that, the HTML elements depending on the variable did not get
358 * updated.
359 * It turns out that this is due to variable scoping within an ng-repeat
360 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat
361 *
362 * @param val boolean value to set $scope.areUpdatesPending to
363 */
364 $scope.setUpdatesPending = function(val) {
365 $scope.areUpdatesPending = val;
366 }
367
368 /**
epoger@google.com62a5ef02013-12-05 18:03:24 +0000369 * Update the displayed results, based on filters/settings,
370 * and call $scope.queryParameters.save() so that the new filter results
371 * can be bookmarked.
epoger@google.comad0e5522013-10-24 15:38:27 +0000372 */
epoger@google.com5f2bb002013-10-02 18:57:48 +0000373 $scope.updateResults = function() {
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000374 $scope.renderStartTime = window.performance.now();
375 $log.debug("renderStartTime: " + $scope.renderStartTime);
epoger@google.com5f2bb002013-10-02 18:57:48 +0000376 $scope.displayLimit = $scope.displayLimitPending;
377 // TODO(epoger): Every time we apply a filter, AngularJS creates
378 // another copy of the array. Is there a way we can filter out
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000379 // the imagePairs as they are displayed, rather than storing multiple
epoger@google.com5f2bb002013-10-02 18:57:48 +0000380 // array copies? (For better performance.)
epoger@google.comeb832592013-10-23 15:07:26 +0000381
382 if ($scope.viewingTab == $scope.defaultTab) {
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000383
384 // TODO(epoger): Until we allow the user to reverse sort order,
385 // there are certain columns we want to sort in a different order.
386 var doReverse = (
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000387 ($scope.sortColumnKey ==
388 constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) ||
389 ($scope.sortColumnKey ==
390 constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF));
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000391
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000392 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000393 $filter("orderBy")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000394 $filter("removeHiddenImagePairs")(
395 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000396 $scope.hiddenResultTypes,
397 $scope.hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +0000398 $scope.categoryValueMatch.builder,
399 $scope.categoryValueMatch.test,
epoger@google.comeb832592013-10-23 15:07:26 +0000400 $scope.viewingTab
401 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000402 $scope.getSortColumnValue, doReverse);
403 $scope.limitedImagePairs = $filter("limitTo")(
404 $scope.filteredImagePairs, $scope.displayLimit);
epoger@google.comeb832592013-10-23 15:07:26 +0000405 } else {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000406 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000407 $filter("orderBy")(
408 $filter("filter")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000409 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000410 {tab: $scope.viewingTab},
411 true
412 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000413 $scope.getSortColumnValue);
414 $scope.limitedImagePairs = $scope.filteredImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000415 }
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000416 $scope.showThumbnails = $scope.showThumbnailsPending;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000417 $scope.imageSize = $scope.imageSizePending;
epoger@google.comad0e5522013-10-24 15:38:27 +0000418 $scope.setUpdatesPending(false);
epoger@google.com62a5ef02013-12-05 18:03:24 +0000419 $scope.queryParameters.save();
epoger@google.com5f2bb002013-10-02 18:57:48 +0000420 }
421
epoger@google.comad0e5522013-10-24 15:38:27 +0000422 /**
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000423 * This function is called when the results have been completely rendered
424 * after updateResults().
425 */
426 $scope.resultsUpdatedCallback = function() {
427 $scope.renderEndTime = window.performance.now();
428 $log.debug("renderEndTime: " + $scope.renderEndTime);
429 }
430
431 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000432 * Re-sort the displayed results.
433 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000434 * @param subdict (string): which subdictionary
435 * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA,
436 * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within
437 * @param key (string): sort by value associated with this key in subdict
epoger@google.comad0e5522013-10-24 15:38:27 +0000438 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000439 $scope.sortResultsBy = function(subdict, key) {
440 $scope.sortColumnSubdict = subdict;
441 $scope.sortColumnKey = key;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000442 $scope.updateResults();
443 }
epoger@google.comeb832592013-10-23 15:07:26 +0000444
epoger@google.comf4394d52013-10-29 15:49:40 +0000445 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000446 * For a particular ImagePair, return the value of the column we are
447 * sorting on (according to $scope.sortColumnSubdict and
448 * $scope.sortColumnKey).
449 *
450 * @param imagePair: imagePair to get a column value out of.
451 */
452 $scope.getSortColumnValue = function(imagePair) {
453 if ($scope.sortColumnSubdict in imagePair) {
454 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
455 } else {
456 return undefined;
457 }
458 }
459
460 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000461 * Set $scope.categoryValueMatch[name] = value, and update results.
462 *
463 * @param name
464 * @param value
465 */
466 $scope.setCategoryValueMatch = function(name, value) {
467 $scope.categoryValueMatch[name] = value;
468 $scope.updateResults();
469 }
470
471 /**
472 * Update $scope.hiddenResultTypes so that ONLY this resultType is showing,
473 * and update the visible results.
474 *
475 * @param resultType
476 */
477 $scope.showOnlyResultType = function(resultType) {
478 $scope.hiddenResultTypes = {};
479 // TODO(epoger): Maybe change $scope.allResultTypes to be a Set like
480 // $scope.hiddenResultTypes (rather than an array), so this operation is
481 // simpler (just assign or add allResultTypes to hiddenResultTypes).
482 $scope.toggleValuesInSet($scope.allResultTypes, $scope.hiddenResultTypes);
483 $scope.toggleValueInSet(resultType, $scope.hiddenResultTypes);
484 $scope.updateResults();
485 }
486
487 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000488 * Update $scope.hiddenResultTypes so that ALL resultTypes are showing,
489 * and update the visible results.
490 */
491 $scope.showAllResultTypes = function() {
492 $scope.hiddenResultTypes = {};
493 $scope.updateResults();
494 }
495
496 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000497 * Update $scope.hiddenConfigs so that ONLY this config is showing,
498 * and update the visible results.
499 *
500 * @param config
501 */
502 $scope.showOnlyConfig = function(config) {
503 $scope.hiddenConfigs = {};
504 $scope.toggleValuesInSet($scope.allConfigs, $scope.hiddenConfigs);
505 $scope.toggleValueInSet(config, $scope.hiddenConfigs);
506 $scope.updateResults();
507 }
508
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000509 /**
510 * Update $scope.hiddenConfigs so that ALL configs are showing,
511 * and update the visible results.
512 */
513 $scope.showAllConfigs = function() {
514 $scope.hiddenConfigs = {};
515 $scope.updateResults();
516 }
517
epoger@google.comad0e5522013-10-24 15:38:27 +0000518
519 //
520 // Operations for sending info back to the server.
521 //
522
epoger@google.comeb832592013-10-23 15:07:26 +0000523 /**
524 * Tell the server that the actual results of these particular tests
525 * are acceptable.
526 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000527 * TODO(epoger): This assumes that the original expectations are in
528 * imageSetA, and the actuals are in imageSetB.
529 *
530 * @param imagePairsSubset an array of test results, most likely a subset of
531 * $scope.imagePairs (perhaps with some modifications)
epoger@google.comeb832592013-10-23 15:07:26 +0000532 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000533 $scope.submitApprovals = function(imagePairsSubset) {
epoger@google.comeb832592013-10-23 15:07:26 +0000534 $scope.submitPending = true;
epoger@google.com055e3b52013-10-26 14:31:11 +0000535
536 // Convert bug text field to null or 1-item array.
537 var bugs = null;
538 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
539 if (!isNaN(bugNumber)) {
540 bugs = [bugNumber];
541 }
542
543 // TODO(epoger): This is a suboptimal way to prevent users from
544 // rebaselining failures in alternative renderModes, but it does work.
545 // For a better solution, see
546 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
547 // result type, RenderModeMismatch')
548 var encounteredComparisonConfig = false;
549
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000550 var updatedExpectations = [];
551 for (var i = 0; i < imagePairsSubset.length; i++) {
552 var imagePair = imagePairsSubset[i];
553 var updatedExpectation = {};
554 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] =
555 imagePair[constants.KEY__EXPECTATIONS_DATA];
556 updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] =
557 imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
558 updatedExpectation[constants.KEY__NEW_IMAGE_URL] =
559 imagePair[constants.KEY__IMAGE_B_URL];
560 if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES]
561 [constants.KEY__EXTRACOLUMN__CONFIG]
562 .indexOf('comparison-')) {
epoger@google.com055e3b52013-10-26 14:31:11 +0000563 encounteredComparisonConfig = true;
564 }
565
566 // Advanced settings...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000567 if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) {
568 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {};
epoger@google.com055e3b52013-10-26 14:31:11 +0000569 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000570 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
571 [constants.KEY__EXPECTATIONS__REVIEWED] =
572 $scope.submitAdvancedSettings[
573 constants.KEY__EXPECTATIONS__REVIEWED];
574 if (true == $scope.submitAdvancedSettings[
575 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
576 // if it's false, don't send it at all (just keep the default)
577 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
578 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
579 }
580 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
581 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
epoger@google.com055e3b52013-10-26 14:31:11 +0000582
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000583 updatedExpectations.push(updatedExpectation);
epoger@google.comeb832592013-10-23 15:07:26 +0000584 }
epoger@google.com055e3b52013-10-26 14:31:11 +0000585 if (encounteredComparisonConfig) {
586 alert("Approval failed -- you cannot approve results with config " +
587 "type comparison-*");
588 $scope.submitPending = false;
589 return;
590 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000591 var modificationData = {};
592 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
593 updatedExpectations;
594 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
595 $scope.header[constants.KEY__HEADER__DATAHASH];
596 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
597 $scope.header[constants.KEY__HEADER__TYPE];
epoger@google.comeb832592013-10-23 15:07:26 +0000598 $http({
599 method: "POST",
600 url: "/edits",
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000601 data: modificationData
epoger@google.comeb832592013-10-23 15:07:26 +0000602 }).success(function(data, status, headers, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000603 var imagePairIndicesToMove = [];
604 for (var i = 0; i < imagePairsSubset.length; i++) {
605 imagePairIndicesToMove.push(imagePairsSubset[i].index);
epoger@google.comeb832592013-10-23 15:07:26 +0000606 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000607 $scope.moveImagePairsToTab(imagePairIndicesToMove,
608 "HackToMakeSureThisImagePairDisappears");
epoger@google.comeb832592013-10-23 15:07:26 +0000609 $scope.updateResults();
610 alert("New baselines submitted successfully!\n\n" +
611 "You still need to commit the updated expectations files on " +
612 "the server side to the Skia repo.\n\n" +
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +0000613 "When you click OK, your web UI will reload; after that " +
614 "completes, you will see the updated data (once the server has " +
615 "finished loading the update results into memory!) and you can " +
616 "submit more baselines if you want.");
617 // I don't know why, but if I just call reload() here it doesn't work.
618 // Making a timer call it fixes the problem.
619 $timeout(function(){location.reload();}, 1);
epoger@google.comeb832592013-10-23 15:07:26 +0000620 }).error(function(data, status, headers, config) {
621 alert("There was an error submitting your baselines.\n\n" +
622 "Please see server-side log for details.");
623 $scope.submitPending = false;
624 });
625 }
epoger@google.comad0e5522013-10-24 15:38:27 +0000626
627
628 //
629 // Operations we use to mimic Set semantics, in such a way that
630 // checking for presence within the Set is as fast as possible.
631 // But getting a list of all values within the Set is not necessarily
632 // possible.
633 // TODO(epoger): move into a separate .js file?
634 //
635
636 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000637 * Returns the number of values present within set "set".
638 *
639 * @param set an Object which we use to mimic set semantics
640 */
641 $scope.setSize = function(set) {
642 return Object.keys(set).length;
643 }
644
645 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000646 * Returns true if value "value" is present within set "set".
647 *
648 * @param value a value of any type
649 * @param set an Object which we use to mimic set semantics
650 * (this should make isValueInSet faster than if we used an Array)
651 */
652 $scope.isValueInSet = function(value, set) {
653 return (true == set[value]);
654 }
655
656 /**
657 * If value "value" is already in set "set", remove it; otherwise, add it.
658 *
659 * @param value a value of any type
660 * @param set an Object which we use to mimic set semantics
661 */
662 $scope.toggleValueInSet = function(value, set) {
663 if (true == set[value]) {
664 delete set[value];
665 } else {
666 set[value] = true;
667 }
668 }
669
epoger@google.comf4394d52013-10-29 15:49:40 +0000670 /**
671 * For each value in valueArray, call toggleValueInSet(value, set).
672 *
673 * @param valueArray
674 * @param set
675 */
676 $scope.toggleValuesInSet = function(valueArray, set) {
677 var arrayLength = valueArray.length;
678 for (var i = 0; i < arrayLength; i++) {
679 $scope.toggleValueInSet(valueArray[i], set);
680 }
681 }
682
epoger@google.comad0e5522013-10-24 15:38:27 +0000683
684 //
685 // Array operations; similar to our Set operations, but operate on a
686 // Javascript Array so we *can* easily get a list of all values in the Set.
687 // TODO(epoger): move into a separate .js file?
688 //
689
690 /**
691 * Returns true if value "value" is present within array "array".
692 *
693 * @param value a value of any type
694 * @param array a Javascript Array
695 */
696 $scope.isValueInArray = function(value, array) {
697 return (-1 != array.indexOf(value));
698 }
699
700 /**
701 * If value "value" is already in array "array", remove it; otherwise,
702 * add it.
703 *
704 * @param value a value of any type
705 * @param array a Javascript Array
706 */
707 $scope.toggleValueInArray = function(value, array) {
708 var i = array.indexOf(value);
709 if (-1 == i) {
710 array.push(value);
711 } else {
712 array.splice(i, 1);
713 }
714 }
715
716
717 //
718 // Miscellaneous utility functions.
719 // TODO(epoger): move into a separate .js file?
720 //
721
722 /**
723 * Returns a human-readable (in local time zone) time string for a
724 * particular moment in time.
725 *
726 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
727 */
728 $scope.localTimeString = function(secondsPastEpoch) {
729 var d = new Date(secondsPastEpoch * 1000);
730 return d.toString();
731 }
732
commit-bot@chromium.orgceba0792014-02-05 19:49:17 +0000733 /**
734 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
735 *
736 * @param r (numeric): red channel value, 0-255
737 * @param g (numeric): green channel value, 0-255
738 * @param b (numeric): blue channel value, 0-255
739 */
740 $scope.hexColorString = function(r, g, b) {
741 var rString = r.toString(16);
742 if (r < 16) {
743 rString = "0" + rString;
744 }
745 var gString = g.toString(16);
746 if (g < 16) {
747 gString = "0" + gString;
748 }
749 var bString = b.toString(16);
750 if (b < 16) {
751 bString = "0" + bString;
752 }
753 return '#' + rString + gString + bString;
754 }
755
756 /**
757 * Returns a hex color string (such as "#aabbcc") for the given brightness.
758 *
759 * @param brightnessString (string): 0-255, 0 is completely black
760 *
761 * TODO(epoger): It might be nice to tint the color when it's not completely
762 * black or completely white.
763 */
764 $scope.brightnessStringToHexColor = function(brightnessString) {
765 var v = parseInt(brightnessString);
766 return $scope.hexColorString(v, v, v);
767 }
768
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000769 /**
770 * Returns the last path component of image diff URL for a given ImagePair.
771 *
772 * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this
773 * will be relative to different base URLs.
774 *
775 * We must keep this function in sync with _get_difference_locator() in
776 * ../imagediffdb.py
777 *
778 * @param imagePair: ImagePair to generate image diff URL for
779 */
780 $scope.getImageDiffRelativeUrl = function(imagePair) {
781 var before =
782 imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" +
783 imagePair[constants.KEY__IMAGE_B_URL];
784 return before.replace(/[^\w\-]/g, "_") + ".png";
785 }
786
epoger@google.comf9d134d2013-09-27 15:02:44 +0000787 }
788);