blob: ab79df8189da0a0b7b4aa120aced0edbab5be626 [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 = {
295 'resultsToLoad': $scope.queryParameters.copiers.simple,
296 'displayLimitPending': $scope.queryParameters.copiers.simple,
297 'imageSizePending': $scope.queryParameters.copiers.simple,
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000298 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
299 'sortColumnKey': $scope.queryParameters.copiers.simple,
epoger@google.com62a5ef02013-12-05 18:03:24 +0000300
301 'hiddenResultTypes': $scope.queryParameters.copiers.set,
302 'hiddenConfigs': $scope.queryParameters.copiers.set,
303 };
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000304 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] =
305 $scope.queryParameters.copiers.categoryValueMatch;
306 $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] =
307 $scope.queryParameters.copiers.categoryValueMatch;
epoger@google.com62a5ef02013-12-05 18:03:24 +0000308
309 // Loads all parameters into $scope from the URL query string;
310 // any which are not found within the URL will keep their current value.
311 $scope.queryParameters.load = function() {
312 var nameValuePairs = $location.search();
313 angular.forEach($scope.queryParameters.map,
314 function(copier, paramName) {
315 copier.load(nameValuePairs, paramName);
316 }
317 );
318 };
319
320 // Saves all parameters from $scope into the URL query string.
321 $scope.queryParameters.save = function() {
322 var nameValuePairs = {};
323 angular.forEach($scope.queryParameters.map,
324 function(copier, paramName) {
325 copier.save(nameValuePairs, paramName);
326 }
327 );
328 $location.search(nameValuePairs);
329 };
330
331
332 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000333 // updateResults() and friends.
334 //
335
336 /**
337 * Set $scope.areUpdatesPending (to enable/disable the Update Results
338 * button).
339 *
340 * TODO(epoger): We could reduce the amount of code by just setting the
341 * variable directly (from, e.g., a button's ng-click handler). But when
342 * I tried that, the HTML elements depending on the variable did not get
343 * updated.
344 * It turns out that this is due to variable scoping within an ng-repeat
345 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat
346 *
347 * @param val boolean value to set $scope.areUpdatesPending to
348 */
349 $scope.setUpdatesPending = function(val) {
350 $scope.areUpdatesPending = val;
351 }
352
353 /**
epoger@google.com62a5ef02013-12-05 18:03:24 +0000354 * Update the displayed results, based on filters/settings,
355 * and call $scope.queryParameters.save() so that the new filter results
356 * can be bookmarked.
epoger@google.comad0e5522013-10-24 15:38:27 +0000357 */
epoger@google.com5f2bb002013-10-02 18:57:48 +0000358 $scope.updateResults = function() {
359 $scope.displayLimit = $scope.displayLimitPending;
360 // TODO(epoger): Every time we apply a filter, AngularJS creates
361 // another copy of the array. Is there a way we can filter out
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000362 // the imagePairs as they are displayed, rather than storing multiple
epoger@google.com5f2bb002013-10-02 18:57:48 +0000363 // array copies? (For better performance.)
epoger@google.comeb832592013-10-23 15:07:26 +0000364
365 if ($scope.viewingTab == $scope.defaultTab) {
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000366
367 // TODO(epoger): Until we allow the user to reverse sort order,
368 // there are certain columns we want to sort in a different order.
369 var doReverse = (
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000370 ($scope.sortColumnKey ==
371 constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) ||
372 ($scope.sortColumnKey ==
373 constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF));
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000374
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000375 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000376 $filter("orderBy")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000377 $filter("removeHiddenImagePairs")(
378 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000379 $scope.hiddenResultTypes,
380 $scope.hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +0000381 $scope.categoryValueMatch.builder,
382 $scope.categoryValueMatch.test,
epoger@google.comeb832592013-10-23 15:07:26 +0000383 $scope.viewingTab
384 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000385 $scope.getSortColumnValue, doReverse);
386 $scope.limitedImagePairs = $filter("limitTo")(
387 $scope.filteredImagePairs, $scope.displayLimit);
epoger@google.comeb832592013-10-23 15:07:26 +0000388 } else {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000389 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000390 $filter("orderBy")(
391 $filter("filter")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000392 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000393 {tab: $scope.viewingTab},
394 true
395 ),
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000396 $scope.getSortColumnValue);
397 $scope.limitedImagePairs = $scope.filteredImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000398 }
epoger@google.com5f2bb002013-10-02 18:57:48 +0000399 $scope.imageSize = $scope.imageSizePending;
epoger@google.comad0e5522013-10-24 15:38:27 +0000400 $scope.setUpdatesPending(false);
epoger@google.com62a5ef02013-12-05 18:03:24 +0000401 $scope.queryParameters.save();
epoger@google.com5f2bb002013-10-02 18:57:48 +0000402 }
403
epoger@google.comad0e5522013-10-24 15:38:27 +0000404 /**
405 * Re-sort the displayed results.
406 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000407 * @param subdict (string): which subdictionary
408 * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA,
409 * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within
410 * @param key (string): sort by value associated with this key in subdict
epoger@google.comad0e5522013-10-24 15:38:27 +0000411 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000412 $scope.sortResultsBy = function(subdict, key) {
413 $scope.sortColumnSubdict = subdict;
414 $scope.sortColumnKey = key;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000415 $scope.updateResults();
416 }
epoger@google.comeb832592013-10-23 15:07:26 +0000417
epoger@google.comf4394d52013-10-29 15:49:40 +0000418 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000419 * For a particular ImagePair, return the value of the column we are
420 * sorting on (according to $scope.sortColumnSubdict and
421 * $scope.sortColumnKey).
422 *
423 * @param imagePair: imagePair to get a column value out of.
424 */
425 $scope.getSortColumnValue = function(imagePair) {
426 if ($scope.sortColumnSubdict in imagePair) {
427 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
428 } else {
429 return undefined;
430 }
431 }
432
433 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000434 * Set $scope.categoryValueMatch[name] = value, and update results.
435 *
436 * @param name
437 * @param value
438 */
439 $scope.setCategoryValueMatch = function(name, value) {
440 $scope.categoryValueMatch[name] = value;
441 $scope.updateResults();
442 }
443
444 /**
445 * Update $scope.hiddenResultTypes so that ONLY this resultType is showing,
446 * and update the visible results.
447 *
448 * @param resultType
449 */
450 $scope.showOnlyResultType = function(resultType) {
451 $scope.hiddenResultTypes = {};
452 // TODO(epoger): Maybe change $scope.allResultTypes to be a Set like
453 // $scope.hiddenResultTypes (rather than an array), so this operation is
454 // simpler (just assign or add allResultTypes to hiddenResultTypes).
455 $scope.toggleValuesInSet($scope.allResultTypes, $scope.hiddenResultTypes);
456 $scope.toggleValueInSet(resultType, $scope.hiddenResultTypes);
457 $scope.updateResults();
458 }
459
460 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000461 * Update $scope.hiddenResultTypes so that ALL resultTypes are showing,
462 * and update the visible results.
463 */
464 $scope.showAllResultTypes = function() {
465 $scope.hiddenResultTypes = {};
466 $scope.updateResults();
467 }
468
469 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000470 * Update $scope.hiddenConfigs so that ONLY this config is showing,
471 * and update the visible results.
472 *
473 * @param config
474 */
475 $scope.showOnlyConfig = function(config) {
476 $scope.hiddenConfigs = {};
477 $scope.toggleValuesInSet($scope.allConfigs, $scope.hiddenConfigs);
478 $scope.toggleValueInSet(config, $scope.hiddenConfigs);
479 $scope.updateResults();
480 }
481
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000482 /**
483 * Update $scope.hiddenConfigs so that ALL configs are showing,
484 * and update the visible results.
485 */
486 $scope.showAllConfigs = function() {
487 $scope.hiddenConfigs = {};
488 $scope.updateResults();
489 }
490
epoger@google.comad0e5522013-10-24 15:38:27 +0000491
492 //
493 // Operations for sending info back to the server.
494 //
495
epoger@google.comeb832592013-10-23 15:07:26 +0000496 /**
497 * Tell the server that the actual results of these particular tests
498 * are acceptable.
499 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000500 * TODO(epoger): This assumes that the original expectations are in
501 * imageSetA, and the actuals are in imageSetB.
502 *
503 * @param imagePairsSubset an array of test results, most likely a subset of
504 * $scope.imagePairs (perhaps with some modifications)
epoger@google.comeb832592013-10-23 15:07:26 +0000505 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000506 $scope.submitApprovals = function(imagePairsSubset) {
epoger@google.comeb832592013-10-23 15:07:26 +0000507 $scope.submitPending = true;
epoger@google.com055e3b52013-10-26 14:31:11 +0000508
509 // Convert bug text field to null or 1-item array.
510 var bugs = null;
511 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
512 if (!isNaN(bugNumber)) {
513 bugs = [bugNumber];
514 }
515
516 // TODO(epoger): This is a suboptimal way to prevent users from
517 // rebaselining failures in alternative renderModes, but it does work.
518 // For a better solution, see
519 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
520 // result type, RenderModeMismatch')
521 var encounteredComparisonConfig = false;
522
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000523 var updatedExpectations = [];
524 for (var i = 0; i < imagePairsSubset.length; i++) {
525 var imagePair = imagePairsSubset[i];
526 var updatedExpectation = {};
527 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] =
528 imagePair[constants.KEY__EXPECTATIONS_DATA];
529 updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] =
530 imagePair[constants.KEY__EXTRA_COLUMN_VALUES];
531 updatedExpectation[constants.KEY__NEW_IMAGE_URL] =
532 imagePair[constants.KEY__IMAGE_B_URL];
533 if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES]
534 [constants.KEY__EXTRACOLUMN__CONFIG]
535 .indexOf('comparison-')) {
epoger@google.com055e3b52013-10-26 14:31:11 +0000536 encounteredComparisonConfig = true;
537 }
538
539 // Advanced settings...
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000540 if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) {
541 updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {};
epoger@google.com055e3b52013-10-26 14:31:11 +0000542 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000543 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
544 [constants.KEY__EXPECTATIONS__REVIEWED] =
545 $scope.submitAdvancedSettings[
546 constants.KEY__EXPECTATIONS__REVIEWED];
547 if (true == $scope.submitAdvancedSettings[
548 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
549 // if it's false, don't send it at all (just keep the default)
550 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
551 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
552 }
553 updatedExpectation[constants.KEY__EXPECTATIONS_DATA]
554 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
epoger@google.com055e3b52013-10-26 14:31:11 +0000555
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000556 updatedExpectations.push(updatedExpectation);
epoger@google.comeb832592013-10-23 15:07:26 +0000557 }
epoger@google.com055e3b52013-10-26 14:31:11 +0000558 if (encounteredComparisonConfig) {
559 alert("Approval failed -- you cannot approve results with config " +
560 "type comparison-*");
561 $scope.submitPending = false;
562 return;
563 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000564 var modificationData = {};
565 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
566 updatedExpectations;
567 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
568 $scope.header[constants.KEY__HEADER__DATAHASH];
569 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
570 $scope.header[constants.KEY__HEADER__TYPE];
epoger@google.comeb832592013-10-23 15:07:26 +0000571 $http({
572 method: "POST",
573 url: "/edits",
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000574 data: modificationData
epoger@google.comeb832592013-10-23 15:07:26 +0000575 }).success(function(data, status, headers, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000576 var imagePairIndicesToMove = [];
577 for (var i = 0; i < imagePairsSubset.length; i++) {
578 imagePairIndicesToMove.push(imagePairsSubset[i].index);
epoger@google.comeb832592013-10-23 15:07:26 +0000579 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000580 $scope.moveImagePairsToTab(imagePairIndicesToMove,
581 "HackToMakeSureThisImagePairDisappears");
epoger@google.comeb832592013-10-23 15:07:26 +0000582 $scope.updateResults();
583 alert("New baselines submitted successfully!\n\n" +
584 "You still need to commit the updated expectations files on " +
585 "the server side to the Skia repo.\n\n" +
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +0000586 "When you click OK, your web UI will reload; after that " +
587 "completes, you will see the updated data (once the server has " +
588 "finished loading the update results into memory!) and you can " +
589 "submit more baselines if you want.");
590 // I don't know why, but if I just call reload() here it doesn't work.
591 // Making a timer call it fixes the problem.
592 $timeout(function(){location.reload();}, 1);
epoger@google.comeb832592013-10-23 15:07:26 +0000593 }).error(function(data, status, headers, config) {
594 alert("There was an error submitting your baselines.\n\n" +
595 "Please see server-side log for details.");
596 $scope.submitPending = false;
597 });
598 }
epoger@google.comad0e5522013-10-24 15:38:27 +0000599
600
601 //
602 // Operations we use to mimic Set semantics, in such a way that
603 // checking for presence within the Set is as fast as possible.
604 // But getting a list of all values within the Set is not necessarily
605 // possible.
606 // TODO(epoger): move into a separate .js file?
607 //
608
609 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000610 * Returns the number of values present within set "set".
611 *
612 * @param set an Object which we use to mimic set semantics
613 */
614 $scope.setSize = function(set) {
615 return Object.keys(set).length;
616 }
617
618 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000619 * Returns true if value "value" is present within set "set".
620 *
621 * @param value a value of any type
622 * @param set an Object which we use to mimic set semantics
623 * (this should make isValueInSet faster than if we used an Array)
624 */
625 $scope.isValueInSet = function(value, set) {
626 return (true == set[value]);
627 }
628
629 /**
630 * If value "value" is already in set "set", remove it; otherwise, add it.
631 *
632 * @param value a value of any type
633 * @param set an Object which we use to mimic set semantics
634 */
635 $scope.toggleValueInSet = function(value, set) {
636 if (true == set[value]) {
637 delete set[value];
638 } else {
639 set[value] = true;
640 }
641 }
642
epoger@google.comf4394d52013-10-29 15:49:40 +0000643 /**
644 * For each value in valueArray, call toggleValueInSet(value, set).
645 *
646 * @param valueArray
647 * @param set
648 */
649 $scope.toggleValuesInSet = function(valueArray, set) {
650 var arrayLength = valueArray.length;
651 for (var i = 0; i < arrayLength; i++) {
652 $scope.toggleValueInSet(valueArray[i], set);
653 }
654 }
655
epoger@google.comad0e5522013-10-24 15:38:27 +0000656
657 //
658 // Array operations; similar to our Set operations, but operate on a
659 // Javascript Array so we *can* easily get a list of all values in the Set.
660 // TODO(epoger): move into a separate .js file?
661 //
662
663 /**
664 * Returns true if value "value" is present within array "array".
665 *
666 * @param value a value of any type
667 * @param array a Javascript Array
668 */
669 $scope.isValueInArray = function(value, array) {
670 return (-1 != array.indexOf(value));
671 }
672
673 /**
674 * If value "value" is already in array "array", remove it; otherwise,
675 * add it.
676 *
677 * @param value a value of any type
678 * @param array a Javascript Array
679 */
680 $scope.toggleValueInArray = function(value, array) {
681 var i = array.indexOf(value);
682 if (-1 == i) {
683 array.push(value);
684 } else {
685 array.splice(i, 1);
686 }
687 }
688
689
690 //
691 // Miscellaneous utility functions.
692 // TODO(epoger): move into a separate .js file?
693 //
694
695 /**
696 * Returns a human-readable (in local time zone) time string for a
697 * particular moment in time.
698 *
699 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
700 */
701 $scope.localTimeString = function(secondsPastEpoch) {
702 var d = new Date(secondsPastEpoch * 1000);
703 return d.toString();
704 }
705
commit-bot@chromium.orgceba0792014-02-05 19:49:17 +0000706 /**
707 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
708 *
709 * @param r (numeric): red channel value, 0-255
710 * @param g (numeric): green channel value, 0-255
711 * @param b (numeric): blue channel value, 0-255
712 */
713 $scope.hexColorString = function(r, g, b) {
714 var rString = r.toString(16);
715 if (r < 16) {
716 rString = "0" + rString;
717 }
718 var gString = g.toString(16);
719 if (g < 16) {
720 gString = "0" + gString;
721 }
722 var bString = b.toString(16);
723 if (b < 16) {
724 bString = "0" + bString;
725 }
726 return '#' + rString + gString + bString;
727 }
728
729 /**
730 * Returns a hex color string (such as "#aabbcc") for the given brightness.
731 *
732 * @param brightnessString (string): 0-255, 0 is completely black
733 *
734 * TODO(epoger): It might be nice to tint the color when it's not completely
735 * black or completely white.
736 */
737 $scope.brightnessStringToHexColor = function(brightnessString) {
738 var v = parseInt(brightnessString);
739 return $scope.hexColorString(v, v, v);
740 }
741
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000742 /**
743 * Returns the last path component of image diff URL for a given ImagePair.
744 *
745 * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this
746 * will be relative to different base URLs.
747 *
748 * We must keep this function in sync with _get_difference_locator() in
749 * ../imagediffdb.py
750 *
751 * @param imagePair: ImagePair to generate image diff URL for
752 */
753 $scope.getImageDiffRelativeUrl = function(imagePair) {
754 var before =
755 imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" +
756 imagePair[constants.KEY__IMAGE_B_URL];
757 return before.replace(/[^\w\-]/g, "_") + ".png";
758 }
759
epoger@google.comf9d134d2013-09-27 15:02:44 +0000760 }
761);