blob: 9926193fc2564bdc494ec32f00f8cbc47172d8e0 [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
11// TODO(epoger): Combine ALL of our filtering operations (including
12// truncation) into this one filter, so that runs most efficiently?
13// (We would have to make sure truncation still took place after
14// sorting, though.)
15Loader.filter(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000016 'removeHiddenImagePairs',
17 function(constants) {
18 return function(unfilteredImagePairs, hiddenResultTypes, hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +000019 builderSubstring, testSubstring, viewingTab) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000020 var filteredImagePairs = [];
21 for (var i = 0; i < unfilteredImagePairs.length; i++) {
22 var imagePair = unfilteredImagePairs[i];
23 var extraColumnValues = imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
epoger@google.com055e3b52013-10-26 14:31:11 +000024 // For performance, we examine the "set" objects directly rather
25 // than calling $scope.isValueInSet().
26 // Besides, I don't think we have access to $scope in here...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000027 if (!(true == hiddenResultTypes[extraColumnValues[
28 constants.KEY__EXTRACOLUMN__RESULT_TYPE]]) &&
29 !(true == hiddenConfigs[extraColumnValues[
30 constants.KEY__EXTRACOLUMN__CONFIG]]) &&
31 !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__BUILDER]
32 .indexOf(builderSubstring)) &&
33 !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__TEST]
34 .indexOf(testSubstring)) &&
35 (viewingTab == imagePair.tab)) {
36 filteredImagePairs.push(imagePair);
epoger@google.com9fb6c8a2013-10-09 18:05:58 +000037 }
epoger@google.com5f2bb002013-10-02 18:57:48 +000038 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000039 return filteredImagePairs;
epoger@google.com5f2bb002013-10-02 18:57:48 +000040 };
41 }
42);
43
epoger@google.comad0e5522013-10-24 15:38:27 +000044
epoger@google.comf9d134d2013-09-27 15:02:44 +000045Loader.controller(
46 'Loader.Controller',
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000047 function($scope, $http, $filter, $location, $timeout, constants) {
48 $scope.constants = constants;
epoger@google.com542b65f2013-10-15 20:10:33 +000049 $scope.windowTitle = "Loading GM Results...";
epoger@google.com62a5ef02013-12-05 18:03:24 +000050 $scope.resultsToLoad = $location.search().resultsToLoad;
51 $scope.loadingMessage = "Loading results of type '" + $scope.resultsToLoad +
epoger@google.comdcb4e652013-10-11 18:45:33 +000052 "', please wait...";
53
epoger@google.comad0e5522013-10-24 15:38:27 +000054 /**
55 * On initial page load, load a full dictionary of results.
56 * Once the dictionary is loaded, unhide the page elements so they can
57 * render the data.
58 */
epoger@google.com62a5ef02013-12-05 18:03:24 +000059 $http.get("/results/" + $scope.resultsToLoad).success(
epoger@google.comdcb4e652013-10-11 18:45:33 +000060 function(data, status, header, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000061 var dataHeader = data[constants.KEY__HEADER];
62 if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) {
epoger@google.com2682c902013-12-05 16:05:16 +000063 $scope.loadingMessage =
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +000064 "Server is still loading results; will retry at " +
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000065 $scope.localTimeString(dataHeader[
66 constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE]);
epoger@google.com2682c902013-12-05 16:05:16 +000067 $timeout(
68 function(){location.reload();},
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000069 (dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE]
70 * 1000) - new Date().getTime());
epoger@google.com2682c902013-12-05 16:05:16 +000071 } else {
72 $scope.loadingMessage = "Processing data, please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +000073
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000074 $scope.header = dataHeader;
75 $scope.extraColumnHeaders = data[constants.KEY__EXTRACOLUMNHEADERS];
76 $scope.imagePairs = data[constants.KEY__IMAGEPAIRS];
77 $scope.imageSets = data[constants.KEY__IMAGESETS];
78 $scope.sortColumnSubdict = constants.KEY__DIFFERENCE_DATA;
79 $scope.sortColumnKey = constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF;
epoger@google.com2682c902013-12-05 16:05:16 +000080 $scope.showTodos = false;
epoger@google.com5f2bb002013-10-02 18:57:48 +000081
epoger@google.com2682c902013-12-05 16:05:16 +000082 $scope.showSubmitAdvancedSettings = false;
83 $scope.submitAdvancedSettings = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000084 $scope.submitAdvancedSettings[
85 constants.KEY__EXPECTATIONS__REVIEWED] = true;
86 $scope.submitAdvancedSettings[
87 constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false;
epoger@google.com2682c902013-12-05 16:05:16 +000088 $scope.submitAdvancedSettings['bug'] = '';
epoger@google.com055e3b52013-10-26 14:31:11 +000089
epoger@google.com2682c902013-12-05 16:05:16 +000090 // Create the list of tabs (lists into which the user can file each
91 // test). This may vary, depending on isEditable.
92 $scope.tabs = [
93 'Unfiled', 'Hidden'
94 ];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000095 if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) {
epoger@google.com2682c902013-12-05 16:05:16 +000096 $scope.tabs = $scope.tabs.concat(
97 ['Pending Approval']);
98 }
99 $scope.defaultTab = $scope.tabs[0];
100 $scope.viewingTab = $scope.defaultTab;
101
102 // Track the number of results on each tab.
103 $scope.numResultsPerTab = {};
104 for (var i = 0; i < $scope.tabs.length; i++) {
105 $scope.numResultsPerTab[$scope.tabs[i]] = 0;
106 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000107 $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length;
epoger@google.com2682c902013-12-05 16:05:16 +0000108
109 // Add index and tab fields to all records.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000110 for (var i = 0; i < $scope.imagePairs.length; i++) {
111 $scope.imagePairs[i].index = i;
112 $scope.imagePairs[i].tab = $scope.defaultTab;
epoger@google.com2682c902013-12-05 16:05:16 +0000113 }
114
115 // Arrays within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000116 $scope.selectedImagePairs = [];
epoger@google.com2682c902013-12-05 16:05:16 +0000117
118 // Sets within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000119 $scope.hiddenResultTypes = {};
120 $scope.hiddenResultTypes[
121 constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true;
122 $scope.hiddenResultTypes[
123 constants.KEY__RESULT_TYPE__NOCOMPARISON] = true;
124 $scope.hiddenResultTypes[
125 constants.KEY__RESULT_TYPE__SUCCEEDED] = true;
126 $scope.allResultTypes = Object.keys(
127 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE]
128 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000129 $scope.hiddenConfigs = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000130 $scope.allConfigs = Object.keys(
131 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG]
132 [constants.KEY__VALUES_AND_COUNTS]);
epoger@google.com2682c902013-12-05 16:05:16 +0000133
134 // Associative array of partial string matches per category.
135 $scope.categoryValueMatch = {};
136 $scope.categoryValueMatch.builder = "";
137 $scope.categoryValueMatch.test = "";
138
epoger@google.com62a5ef02013-12-05 18:03:24 +0000139 // If any defaults were overridden in the URL, get them now.
140 $scope.queryParameters.load();
141
epoger@google.com2682c902013-12-05 16:05:16 +0000142 $scope.updateResults();
143 $scope.loadingMessage = "";
144 $scope.windowTitle = "Current GM Results";
epoger@google.comeb832592013-10-23 15:07:26 +0000145 }
epoger@google.comdcb4e652013-10-11 18:45:33 +0000146 }
147 ).error(
148 function(data, status, header, config) {
149 $scope.loadingMessage = "Failed to load results of type '"
epoger@google.com62a5ef02013-12-05 18:03:24 +0000150 + $scope.resultsToLoad + "'";
epoger@google.com542b65f2013-10-15 20:10:33 +0000151 $scope.windowTitle = "Failed to Load GM Results";
epoger@google.comf9d134d2013-09-27 15:02:44 +0000152 }
153 );
epoger@google.com5f2bb002013-10-02 18:57:48 +0000154
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000155
epoger@google.comad0e5522013-10-24 15:38:27 +0000156 //
epoger@google.com055e3b52013-10-26 14:31:11 +0000157 // Select/Clear/Toggle all tests.
158 //
159
160 /**
161 * Select all currently showing tests.
162 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000163 $scope.selectAllImagePairs = function() {
164 var numImagePairsShowing = $scope.limitedImagePairs.length;
165 for (var i = 0; i < numImagePairsShowing; i++) {
166 var index = $scope.limitedImagePairs[i].index;
167 if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) {
168 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000169 }
170 }
171 }
172
173 /**
174 * Deselect all currently showing tests.
175 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000176 $scope.clearAllImagePairs = function() {
177 var numImagePairsShowing = $scope.limitedImagePairs.length;
178 for (var i = 0; i < numImagePairsShowing; i++) {
179 var index = $scope.limitedImagePairs[i].index;
180 if ($scope.isValueInArray(index, $scope.selectedImagePairs)) {
181 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000182 }
183 }
184 }
185
186 /**
187 * Toggle selection of all currently showing tests.
188 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000189 $scope.toggleAllImagePairs = function() {
190 var numImagePairsShowing = $scope.limitedImagePairs.length;
191 for (var i = 0; i < numImagePairsShowing; i++) {
192 var index = $scope.limitedImagePairs[i].index;
193 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000194 }
195 }
196
197
198 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000199 // Tab operations.
200 //
epoger@google.com5f2bb002013-10-02 18:57:48 +0000201
epoger@google.comad0e5522013-10-24 15:38:27 +0000202 /**
203 * Change the selected tab.
204 *
205 * @param tab (string): name of the tab to select
206 */
epoger@google.comeb832592013-10-23 15:07:26 +0000207 $scope.setViewingTab = function(tab) {
208 $scope.viewingTab = tab;
209 $scope.updateResults();
210 }
211
epoger@google.comeb832592013-10-23 15:07:26 +0000212 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000213 * Move the imagePairs in $scope.selectedImagePairs to a different tab,
214 * and then clear $scope.selectedImagePairs.
epoger@google.comeb832592013-10-23 15:07:26 +0000215 *
216 * @param newTab (string): name of the tab to move the tests to
217 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000218 $scope.moveSelectedImagePairsToTab = function(newTab) {
219 $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
220 $scope.selectedImagePairs = [];
epoger@google.comeb832592013-10-23 15:07:26 +0000221 $scope.updateResults();
222 }
223
224 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000225 * Move a subset of $scope.imagePairs to a different tab.
epoger@google.comeb832592013-10-23 15:07:26 +0000226 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000227 * @param imagePairIndices (array of ints): indices into $scope.imagePairs
epoger@google.comeb832592013-10-23 15:07:26 +0000228 * indicating which test results to move
229 * @param newTab (string): name of the tab to move the tests to
230 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000231 $scope.moveImagePairsToTab = function(imagePairIndices, newTab) {
232 var imagePairIndex;
233 var numImagePairs = imagePairIndices.length;
234 for (var i = 0; i < numImagePairs; i++) {
235 imagePairIndex = imagePairIndices[i];
236 $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--;
237 $scope.imagePairs[imagePairIndex].tab = newTab;
epoger@google.comeb832592013-10-23 15:07:26 +0000238 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000239 $scope.numResultsPerTab[newTab] += numImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000240 }
241
epoger@google.comad0e5522013-10-24 15:38:27 +0000242
243 //
epoger@google.com62a5ef02013-12-05 18:03:24 +0000244 // $scope.queryParameters:
245 // Transfer parameter values between $scope and the URL query string.
246 //
247 $scope.queryParameters = {};
248
249 // load and save functions for parameters of each type
250 // (load a parameter value into $scope from nameValuePairs,
251 // save a parameter value from $scope into nameValuePairs)
252 $scope.queryParameters.copiers = {
253 'simple': {
254 'load': function(nameValuePairs, name) {
255 var value = nameValuePairs[name];
256 if (value) {
257 $scope[name] = value;
258 }
259 },
260 'save': function(nameValuePairs, name) {
261 nameValuePairs[name] = $scope[name];
262 }
263 },
264
265 'categoryValueMatch': {
266 'load': function(nameValuePairs, name) {
267 var value = nameValuePairs[name];
268 if (value) {
269 $scope.categoryValueMatch[name] = value;
270 }
271 },
272 'save': function(nameValuePairs, name) {
273 nameValuePairs[name] = $scope.categoryValueMatch[name];
274 }
275 },
276
277 'set': {
278 'load': function(nameValuePairs, name) {
279 var value = nameValuePairs[name];
280 if (value) {
281 var valueArray = value.split(',');
282 $scope[name] = {};
283 $scope.toggleValuesInSet(valueArray, $scope[name]);
284 }
285 },
286 'save': function(nameValuePairs, name) {
287 nameValuePairs[name] = Object.keys($scope[name]).join(',');
288 }
289 },
290
291 };
292
293 // parameter name -> copier objects to load/save parameter value
294 $scope.queryParameters.map = {
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000295 'resultsToLoad': $scope.queryParameters.copiers.simple,
296 'displayLimitPending': $scope.queryParameters.copiers.simple,
297 'showThumbnailsPending': $scope.queryParameters.copiers.simple,
298 'imageSizePending': $scope.queryParameters.copiers.simple,
299 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
300 'sortColumnKey': $scope.queryParameters.copiers.simple,
epoger@google.com62a5ef02013-12-05 18:03:24 +0000301
302 'hiddenResultTypes': $scope.queryParameters.copiers.set,
303 'hiddenConfigs': $scope.queryParameters.copiers.set,
304 };
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000305 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] =
306 $scope.queryParameters.copiers.categoryValueMatch;
307 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] =
308 $scope.queryParameters.copiers.categoryValueMatch;
epoger@google.com62a5ef02013-12-05 18:03:24 +0000309
310 // Loads all parameters into $scope from the URL query string;
311 // any which are not found within the URL will keep their current value.
312 $scope.queryParameters.load = function() {
313 var nameValuePairs = $location.search();
314 angular.forEach($scope.queryParameters.map,
315 function(copier, paramName) {
316 copier.load(nameValuePairs, paramName);
317 }
318 );
319 };
320
321 // Saves all parameters from $scope into the URL query string.
322 $scope.queryParameters.save = function() {
323 var nameValuePairs = {};
324 angular.forEach($scope.queryParameters.map,
325 function(copier, paramName) {
326 copier.save(nameValuePairs, paramName);
327 }
328 );
329 $location.search(nameValuePairs);
330 };
331
332
333 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000334 // updateResults() and friends.
335 //
336
337 /**
338 * Set $scope.areUpdatesPending (to enable/disable the Update Results
339 * button).
340 *
341 * TODO(epoger): We could reduce the amount of code by just setting the
342 * variable directly (from, e.g., a button's ng-click handler). But when
343 * I tried that, the HTML elements depending on the variable did not get
344 * updated.
345 * It turns out that this is due to variable scoping within an ng-repeat
346 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat
347 *
348 * @param val boolean value to set $scope.areUpdatesPending to
349 */
350 $scope.setUpdatesPending = function(val) {
351 $scope.areUpdatesPending = val;
352 }
353
354 /**
epoger@google.com62a5ef02013-12-05 18:03:24 +0000355 * Update the displayed results, based on filters/settings,
356 * and call $scope.queryParameters.save() so that the new filter results
357 * can be bookmarked.
epoger@google.comad0e5522013-10-24 15:38:27 +0000358 */
epoger@google.com5f2bb002013-10-02 18:57:48 +0000359 $scope.updateResults = function() {
360 $scope.displayLimit = $scope.displayLimitPending;
361 // TODO(epoger): Every time we apply a filter, AngularJS creates
362 // another copy of the array. Is there a way we can filter out
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000363 // the imagePairs as they are displayed, rather than storing multiple
epoger@google.com5f2bb002013-10-02 18:57:48 +0000364 // array copies? (For better performance.)
epoger@google.comeb832592013-10-23 15:07:26 +0000365
366 if ($scope.viewingTab == $scope.defaultTab) {
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000367
368 // TODO(epoger): Until we allow the user to reverse sort order,
369 // there are certain columns we want to sort in a different order.
370 var doReverse = (
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000371 ($scope.sortColumnKey ==
372 constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) ||
373 ($scope.sortColumnKey ==
374 constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF));
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000375
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000376 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000377 $filter("orderBy")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000378 $filter("removeHiddenImagePairs")(
379 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000380 $scope.hiddenResultTypes,
381 $scope.hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +0000382 $scope.categoryValueMatch.builder,
383 $scope.categoryValueMatch.test,
epoger@google.comeb832592013-10-23 15:07:26 +0000384 $scope.viewingTab
385 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000386 $scope.getSortColumnValue, doReverse);
387 $scope.limitedImagePairs = $filter("limitTo")(
388 $scope.filteredImagePairs, $scope.displayLimit);
epoger@google.comeb832592013-10-23 15:07:26 +0000389 } else {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000390 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000391 $filter("orderBy")(
392 $filter("filter")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000393 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000394 {tab: $scope.viewingTab},
395 true
396 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000397 $scope.getSortColumnValue);
398 $scope.limitedImagePairs = $scope.filteredImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000399 }
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000400 $scope.showThumbnails = $scope.showThumbnailsPending;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000401 $scope.imageSize = $scope.imageSizePending;
epoger@google.comad0e5522013-10-24 15:38:27 +0000402 $scope.setUpdatesPending(false);
epoger@google.com62a5ef02013-12-05 18:03:24 +0000403 $scope.queryParameters.save();
epoger@google.com5f2bb002013-10-02 18:57:48 +0000404 }
405
epoger@google.comad0e5522013-10-24 15:38:27 +0000406 /**
407 * Re-sort the displayed results.
408 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000409 * @param subdict (string): which subdictionary
410 * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA,
411 * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within
412 * @param key (string): sort by value associated with this key in subdict
epoger@google.comad0e5522013-10-24 15:38:27 +0000413 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000414 $scope.sortResultsBy = function(subdict, key) {
415 $scope.sortColumnSubdict = subdict;
416 $scope.sortColumnKey = key;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000417 $scope.updateResults();
418 }
epoger@google.comeb832592013-10-23 15:07:26 +0000419
epoger@google.comf4394d52013-10-29 15:49:40 +0000420 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000421 * For a particular ImagePair, return the value of the column we are
422 * sorting on (according to $scope.sortColumnSubdict and
423 * $scope.sortColumnKey).
424 *
425 * @param imagePair: imagePair to get a column value out of.
426 */
427 $scope.getSortColumnValue = function(imagePair) {
428 if ($scope.sortColumnSubdict in imagePair) {
429 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
430 } else {
431 return undefined;
432 }
433 }
434
435 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000436 * Set $scope.categoryValueMatch[name] = value, and update results.
437 *
438 * @param name
439 * @param value
440 */
441 $scope.setCategoryValueMatch = function(name, value) {
442 $scope.categoryValueMatch[name] = value;
443 $scope.updateResults();
444 }
445
446 /**
447 * Update $scope.hiddenResultTypes so that ONLY this resultType is showing,
448 * and update the visible results.
449 *
450 * @param resultType
451 */
452 $scope.showOnlyResultType = function(resultType) {
453 $scope.hiddenResultTypes = {};
454 // TODO(epoger): Maybe change $scope.allResultTypes to be a Set like
455 // $scope.hiddenResultTypes (rather than an array), so this operation is
456 // simpler (just assign or add allResultTypes to hiddenResultTypes).
457 $scope.toggleValuesInSet($scope.allResultTypes, $scope.hiddenResultTypes);
458 $scope.toggleValueInSet(resultType, $scope.hiddenResultTypes);
459 $scope.updateResults();
460 }
461
462 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000463 * Update $scope.hiddenResultTypes so that ALL resultTypes are showing,
464 * and update the visible results.
465 */
466 $scope.showAllResultTypes = function() {
467 $scope.hiddenResultTypes = {};
468 $scope.updateResults();
469 }
470
471 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000472 * Update $scope.hiddenConfigs so that ONLY this config is showing,
473 * and update the visible results.
474 *
475 * @param config
476 */
477 $scope.showOnlyConfig = function(config) {
478 $scope.hiddenConfigs = {};
479 $scope.toggleValuesInSet($scope.allConfigs, $scope.hiddenConfigs);
480 $scope.toggleValueInSet(config, $scope.hiddenConfigs);
481 $scope.updateResults();
482 }
483
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000484 /**
485 * Update $scope.hiddenConfigs so that ALL configs are showing,
486 * and update the visible results.
487 */
488 $scope.showAllConfigs = function() {
489 $scope.hiddenConfigs = {};
490 $scope.updateResults();
491 }
492
epoger@google.comad0e5522013-10-24 15:38:27 +0000493
494 //
495 // Operations for sending info back to the server.
496 //
497
epoger@google.comeb832592013-10-23 15:07:26 +0000498 /**
499 * Tell the server that the actual results of these particular tests
500 * are acceptable.
501 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000502 * TODO(epoger): This assumes that the original expectations are in
503 * imageSetA, and the actuals are in imageSetB.
504 *
505 * @param imagePairsSubset an array of test results, most likely a subset of
506 * $scope.imagePairs (perhaps with some modifications)
epoger@google.comeb832592013-10-23 15:07:26 +0000507 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000508 $scope.submitApprovals = function(imagePairsSubset) {
epoger@google.comeb832592013-10-23 15:07:26 +0000509 $scope.submitPending = true;
epoger@google.com055e3b52013-10-26 14:31:11 +0000510
511 // Convert bug text field to null or 1-item array.
512 var bugs = null;
513 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
514 if (!isNaN(bugNumber)) {
515 bugs = [bugNumber];
516 }
517
518 // TODO(epoger): This is a suboptimal way to prevent users from
519 // rebaselining failures in alternative renderModes, but it does work.
520 // For a better solution, see
521 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
522 // result type, RenderModeMismatch')
523 var encounteredComparisonConfig = false;
524
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000525 var updatedExpectations = [];
526 for (var i = 0; i < imagePairsSubset.length; i++) {
527 var imagePair = imagePairsSubset[i];
528 var updatedExpectation = {};
529 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] =
530 imagePair[constants.KEY__EXPECTATIONS_DATA];
531 updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] =
532 imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
533 updatedExpectation[constants.KEY__NEW_IMAGE_URL] =
534 imagePair[constants.KEY__IMAGE_B_URL];
535 if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES]
536 [constants.KEY__EXTRACOLUMN__CONFIG]
537 .indexOf('comparison-')) {
epoger@google.com055e3b52013-10-26 14:31:11 +0000538 encounteredComparisonConfig = true;
539 }
540
541 // Advanced settings...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000542 if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) {
543 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {};
epoger@google.com055e3b52013-10-26 14:31:11 +0000544 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000545 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
546 [constants.KEY__EXPECTATIONS__REVIEWED] =
547 $scope.submitAdvancedSettings[
548 constants.KEY__EXPECTATIONS__REVIEWED];
549 if (true == $scope.submitAdvancedSettings[
550 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
551 // if it's false, don't send it at all (just keep the default)
552 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
553 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
554 }
555 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
556 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
epoger@google.com055e3b52013-10-26 14:31:11 +0000557
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000558 updatedExpectations.push(updatedExpectation);
epoger@google.comeb832592013-10-23 15:07:26 +0000559 }
epoger@google.com055e3b52013-10-26 14:31:11 +0000560 if (encounteredComparisonConfig) {
561 alert("Approval failed -- you cannot approve results with config " +
562 "type comparison-*");
563 $scope.submitPending = false;
564 return;
565 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000566 var modificationData = {};
567 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
568 updatedExpectations;
569 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
570 $scope.header[constants.KEY__HEADER__DATAHASH];
571 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
572 $scope.header[constants.KEY__HEADER__TYPE];
epoger@google.comeb832592013-10-23 15:07:26 +0000573 $http({
574 method: "POST",
575 url: "/edits",
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000576 data: modificationData
epoger@google.comeb832592013-10-23 15:07:26 +0000577 }).success(function(data, status, headers, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000578 var imagePairIndicesToMove = [];
579 for (var i = 0; i < imagePairsSubset.length; i++) {
580 imagePairIndicesToMove.push(imagePairsSubset[i].index);
epoger@google.comeb832592013-10-23 15:07:26 +0000581 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000582 $scope.moveImagePairsToTab(imagePairIndicesToMove,
583 "HackToMakeSureThisImagePairDisappears");
epoger@google.comeb832592013-10-23 15:07:26 +0000584 $scope.updateResults();
585 alert("New baselines submitted successfully!\n\n" +
586 "You still need to commit the updated expectations files on " +
587 "the server side to the Skia repo.\n\n" +
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +0000588 "When you click OK, your web UI will reload; after that " +
589 "completes, you will see the updated data (once the server has " +
590 "finished loading the update results into memory!) and you can " +
591 "submit more baselines if you want.");
592 // I don't know why, but if I just call reload() here it doesn't work.
593 // Making a timer call it fixes the problem.
594 $timeout(function(){location.reload();}, 1);
epoger@google.comeb832592013-10-23 15:07:26 +0000595 }).error(function(data, status, headers, config) {
596 alert("There was an error submitting your baselines.\n\n" +
597 "Please see server-side log for details.");
598 $scope.submitPending = false;
599 });
600 }
epoger@google.comad0e5522013-10-24 15:38:27 +0000601
602
603 //
604 // Operations we use to mimic Set semantics, in such a way that
605 // checking for presence within the Set is as fast as possible.
606 // But getting a list of all values within the Set is not necessarily
607 // possible.
608 // TODO(epoger): move into a separate .js file?
609 //
610
611 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000612 * Returns the number of values present within set "set".
613 *
614 * @param set an Object which we use to mimic set semantics
615 */
616 $scope.setSize = function(set) {
617 return Object.keys(set).length;
618 }
619
620 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000621 * Returns true if value "value" is present within set "set".
622 *
623 * @param value a value of any type
624 * @param set an Object which we use to mimic set semantics
625 * (this should make isValueInSet faster than if we used an Array)
626 */
627 $scope.isValueInSet = function(value, set) {
628 return (true == set[value]);
629 }
630
631 /**
632 * If value "value" is already in set "set", remove it; otherwise, add it.
633 *
634 * @param value a value of any type
635 * @param set an Object which we use to mimic set semantics
636 */
637 $scope.toggleValueInSet = function(value, set) {
638 if (true == set[value]) {
639 delete set[value];
640 } else {
641 set[value] = true;
642 }
643 }
644
epoger@google.comf4394d52013-10-29 15:49:40 +0000645 /**
646 * For each value in valueArray, call toggleValueInSet(value, set).
647 *
648 * @param valueArray
649 * @param set
650 */
651 $scope.toggleValuesInSet = function(valueArray, set) {
652 var arrayLength = valueArray.length;
653 for (var i = 0; i < arrayLength; i++) {
654 $scope.toggleValueInSet(valueArray[i], set);
655 }
656 }
657
epoger@google.comad0e5522013-10-24 15:38:27 +0000658
659 //
660 // Array operations; similar to our Set operations, but operate on a
661 // Javascript Array so we *can* easily get a list of all values in the Set.
662 // TODO(epoger): move into a separate .js file?
663 //
664
665 /**
666 * Returns true if value "value" is present within array "array".
667 *
668 * @param value a value of any type
669 * @param array a Javascript Array
670 */
671 $scope.isValueInArray = function(value, array) {
672 return (-1 != array.indexOf(value));
673 }
674
675 /**
676 * If value "value" is already in array "array", remove it; otherwise,
677 * add it.
678 *
679 * @param value a value of any type
680 * @param array a Javascript Array
681 */
682 $scope.toggleValueInArray = function(value, array) {
683 var i = array.indexOf(value);
684 if (-1 == i) {
685 array.push(value);
686 } else {
687 array.splice(i, 1);
688 }
689 }
690
691
692 //
693 // Miscellaneous utility functions.
694 // TODO(epoger): move into a separate .js file?
695 //
696
697 /**
698 * Returns a human-readable (in local time zone) time string for a
699 * particular moment in time.
700 *
701 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
702 */
703 $scope.localTimeString = function(secondsPastEpoch) {
704 var d = new Date(secondsPastEpoch * 1000);
705 return d.toString();
706 }
707
commit-bot@chromium.orgceba0792014-02-05 19:49:17 +0000708 /**
709 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
710 *
711 * @param r (numeric): red channel value, 0-255
712 * @param g (numeric): green channel value, 0-255
713 * @param b (numeric): blue channel value, 0-255
714 */
715 $scope.hexColorString = function(r, g, b) {
716 var rString = r.toString(16);
717 if (r < 16) {
718 rString = "0" + rString;
719 }
720 var gString = g.toString(16);
721 if (g < 16) {
722 gString = "0" + gString;
723 }
724 var bString = b.toString(16);
725 if (b < 16) {
726 bString = "0" + bString;
727 }
728 return '#' + rString + gString + bString;
729 }
730
731 /**
732 * Returns a hex color string (such as "#aabbcc") for the given brightness.
733 *
734 * @param brightnessString (string): 0-255, 0 is completely black
735 *
736 * TODO(epoger): It might be nice to tint the color when it's not completely
737 * black or completely white.
738 */
739 $scope.brightnessStringToHexColor = function(brightnessString) {
740 var v = parseInt(brightnessString);
741 return $scope.hexColorString(v, v, v);
742 }
743
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000744 /**
745 * Returns the last path component of image diff URL for a given ImagePair.
746 *
747 * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this
748 * will be relative to different base URLs.
749 *
750 * We must keep this function in sync with _get_difference_locator() in
751 * ../imagediffdb.py
752 *
753 * @param imagePair: ImagePair to generate image diff URL for
754 */
755 $scope.getImageDiffRelativeUrl = function(imagePair) {
756 var before =
757 imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" +
758 imagePair[constants.KEY__IMAGE_B_URL];
759 return before.replace(/[^\w\-]/g, "_") + ".png";
760 }
761
epoger@google.comf9d134d2013-09-27 15:02:44 +0000762 }
763);