blob: 296689bde23fff581e364b805b4f77fa8f7ccdd5 [file] [log] [blame]
epoger@google.comf9d134d2013-09-27 15:02:44 +00001/*
2 * Loader:
epoger@google.comafaad3d2013-09-30 15:06:25 +00003 * Reads GM result reports written out by results.py, and imports
commit-bot@chromium.org16f41802014-02-26 19:05:20 +00004 * them into $scope.extraColumnHeaders and $scope.imagePairs .
epoger@google.comf9d134d2013-09-27 15:02:44 +00005 */
6var Loader = angular.module(
7 'Loader',
commit-bot@chromium.org6e3ee332014-03-10 19:12:53 +00008 ['ConstantsModule']
epoger@google.comf9d134d2013-09-27 15:02:44 +00009);
epoger@google.com5f2bb002013-10-02 18:57:48 +000010
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +000011Loader.directive(
12 'resultsUpdatedCallbackDirective',
13 ['$timeout',
14 function($timeout) {
15 return function(scope, element, attrs) {
16 if (scope.$last) {
17 $timeout(function() {
18 scope.resultsUpdatedCallback();
19 });
20 }
21 };
22 }
23 ]
24);
25
epoger@google.com5f2bb002013-10-02 18:57:48 +000026// TODO(epoger): Combine ALL of our filtering operations (including
27// truncation) into this one filter, so that runs most efficiently?
28// (We would have to make sure truncation still took place after
29// sorting, though.)
30Loader.filter(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000031 'removeHiddenImagePairs',
32 function(constants) {
33 return function(unfilteredImagePairs, hiddenResultTypes, hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +000034 builderSubstring, testSubstring, viewingTab) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000035 var filteredImagePairs = [];
36 for (var i = 0; i < unfilteredImagePairs.length; i++) {
37 var imagePair = unfilteredImagePairs[i];
commit-bot@chromium.org68a38152014-05-12 20:40:29 +000038 var extraColumnValues = imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS];
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[
commit-bot@chromium.org68a38152014-05-12 20:40:29 +000043 constants.KEY__EXTRACOLUMNS__RESULT_TYPE]]) &&
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000044 !(true == hiddenConfigs[extraColumnValues[
commit-bot@chromium.org68a38152014-05-12 20:40:29 +000045 constants.KEY__EXTRACOLUMNS__CONFIG]]) &&
46 !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMNS__BUILDER]
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000047 .indexOf(builderSubstring)) &&
commit-bot@chromium.org68a38152014-05-12 20:40:29 +000048 !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMNS__TEST]
commit-bot@chromium.org16f41802014-02-26 19:05:20 +000049 .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
epoger6b281202014-06-11 14:01:35 -070059/**
60 * Limit the input imagePairs to some max number, and merge identical rows
61 * (adjacent rows which have the same (imageA, imageB) pair).
62 *
63 * @param unfilteredImagePairs imagePairs to filter
64 * @param maxPairs maximum number of pairs to output, or <0 for no limit
65 * @param mergeIdenticalRows if true, merge identical rows by setting
66 * ROWSPAN>1 on the first merged row, and ROWSPAN=0 for the rest
67 */
68Loader.filter(
69 'mergeAndLimit',
70 function(constants) {
71 return function(unfilteredImagePairs, maxPairs, mergeIdenticalRows) {
72 var numPairs = unfilteredImagePairs.length;
73 if ((maxPairs > 0) && (maxPairs < numPairs)) {
74 numPairs = maxPairs;
75 }
76 var filteredImagePairs = [];
77 if (!mergeIdenticalRows || (numPairs == 1)) {
78 // Take a shortcut if we're not merging identical rows.
79 // We still need to set ROWSPAN to 1 for each row, for the HTML viewer.
80 for (var i = numPairs-1; i >= 0; i--) {
81 var imagePair = unfilteredImagePairs[i];
82 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1;
83 filteredImagePairs[i] = imagePair;
84 }
85 } else if (numPairs > 1) {
86 // General case--there are at least 2 rows, so we may need to merge some.
87 // Work from the bottom up, so we can keep a running total of how many
88 // rows should be merged, and set ROWSPAN of the top row accordingly.
89 var imagePair = unfilteredImagePairs[numPairs-1];
90 var nextRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL];
91 var nextRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
92 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1;
93 filteredImagePairs[numPairs-1] = imagePair;
94 for (var i = numPairs-2; i >= 0; i--) {
95 imagePair = unfilteredImagePairs[i];
96 var thisRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL];
97 var thisRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
98 if ((thisRowImageAUrl == nextRowImageAUrl) &&
99 (thisRowImageBUrl == nextRowImageBUrl)) {
100 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] =
101 filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] + 1;
102 filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] = 0;
103 } else {
104 imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1;
105 nextRowImageAUrl = thisRowImageAUrl;
106 nextRowImageBUrl = thisRowImageBUrl;
107 }
108 filteredImagePairs[i] = imagePair;
109 }
110 } else {
111 // No results.
112 }
113 return filteredImagePairs;
114 };
115 }
116);
117
epoger@google.comad0e5522013-10-24 15:38:27 +0000118
epoger@google.comf9d134d2013-09-27 15:02:44 +0000119Loader.controller(
120 'Loader.Controller',
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000121 function($scope, $http, $filter, $location, $log, $timeout, constants) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000122 $scope.constants = constants;
epoger@google.com542b65f2013-10-15 20:10:33 +0000123 $scope.windowTitle = "Loading GM Results...";
epoger@google.com62a5ef02013-12-05 18:03:24 +0000124 $scope.resultsToLoad = $location.search().resultsToLoad;
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000125 $scope.loadingMessage = "please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +0000126
epoger@google.comad0e5522013-10-24 15:38:27 +0000127 /**
128 * On initial page load, load a full dictionary of results.
129 * Once the dictionary is loaded, unhide the page elements so they can
130 * render the data.
131 */
commit-bot@chromium.org7498d952014-03-13 14:56:29 +0000132 $http.get($scope.resultsToLoad).success(
epoger@google.comdcb4e652013-10-11 18:45:33 +0000133 function(data, status, header, config) {
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000134 var dataHeader = data[constants.KEY__ROOT__HEADER];
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000135 if (dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] !=
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000136 constants.VALUE__HEADER__SCHEMA_VERSION) {
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000137 $scope.loadingMessage = "ERROR: Got JSON file with schema version "
138 + dataHeader[constants.KEY__HEADER__SCHEMA_VERSION]
139 + " but expected schema version "
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000140 + constants.VALUE__HEADER__SCHEMA_VERSION;
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000141 } else if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) {
commit-bot@chromium.orged191072014-03-10 18:05:15 +0000142 // Apply the server's requested reload delay to local time,
143 // so we will wait the right number of seconds regardless of clock
144 // skew between client and server.
145 var reloadDelayInSeconds =
146 dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] -
147 dataHeader[constants.KEY__HEADER__TIME_UPDATED];
148 var timeNow = new Date().getTime();
149 var timeToReload = timeNow + reloadDelayInSeconds * 1000;
epoger@google.com2682c902013-12-05 16:05:16 +0000150 $scope.loadingMessage =
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000151 "server is still loading results; will retry at " +
commit-bot@chromium.orged191072014-03-10 18:05:15 +0000152 $scope.localTimeString(timeToReload / 1000);
epoger@google.com2682c902013-12-05 16:05:16 +0000153 $timeout(
154 function(){location.reload();},
commit-bot@chromium.orged191072014-03-10 18:05:15 +0000155 timeToReload - timeNow);
epoger@google.com2682c902013-12-05 16:05:16 +0000156 } else {
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000157 $scope.loadingMessage = "processing data, please wait...";
epoger@google.comdcb4e652013-10-11 18:45:33 +0000158
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000159 $scope.header = dataHeader;
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000160 $scope.extraColumnHeaders = data[constants.KEY__ROOT__EXTRACOLUMNHEADERS];
161 $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS];
162 $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS];
163 $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES;
164 $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000165
epoger@google.com2682c902013-12-05 16:05:16 +0000166 $scope.showSubmitAdvancedSettings = false;
167 $scope.submitAdvancedSettings = {};
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000168 $scope.submitAdvancedSettings[
169 constants.KEY__EXPECTATIONS__REVIEWED] = true;
170 $scope.submitAdvancedSettings[
171 constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false;
epoger@google.com2682c902013-12-05 16:05:16 +0000172 $scope.submitAdvancedSettings['bug'] = '';
epoger@google.com055e3b52013-10-26 14:31:11 +0000173
epoger@google.com2682c902013-12-05 16:05:16 +0000174 // Create the list of tabs (lists into which the user can file each
175 // test). This may vary, depending on isEditable.
176 $scope.tabs = [
177 'Unfiled', 'Hidden'
178 ];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000179 if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) {
epoger@google.com2682c902013-12-05 16:05:16 +0000180 $scope.tabs = $scope.tabs.concat(
181 ['Pending Approval']);
182 }
183 $scope.defaultTab = $scope.tabs[0];
184 $scope.viewingTab = $scope.defaultTab;
185
186 // Track the number of results on each tab.
187 $scope.numResultsPerTab = {};
188 for (var i = 0; i < $scope.tabs.length; i++) {
189 $scope.numResultsPerTab[$scope.tabs[i]] = 0;
190 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000191 $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length;
epoger@google.com2682c902013-12-05 16:05:16 +0000192
193 // Add index and tab fields to all records.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000194 for (var i = 0; i < $scope.imagePairs.length; i++) {
195 $scope.imagePairs[i].index = i;
196 $scope.imagePairs[i].tab = $scope.defaultTab;
epoger@google.com2682c902013-12-05 16:05:16 +0000197 }
198
199 // Arrays within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000200 $scope.selectedImagePairs = [];
epoger@google.com2682c902013-12-05 16:05:16 +0000201
202 // Sets within which the user can toggle individual elements.
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000203 $scope.hiddenResultTypes = {};
204 $scope.hiddenResultTypes[
205 constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true;
206 $scope.hiddenResultTypes[
207 constants.KEY__RESULT_TYPE__NOCOMPARISON] = true;
208 $scope.hiddenResultTypes[
209 constants.KEY__RESULT_TYPE__SUCCEEDED] = true;
commit-bot@chromium.org97f0b082014-05-08 21:15:20 +0000210 $scope.allResultTypes = $scope.columnSliceOf2DArray(
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000211 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMNS__RESULT_TYPE]
commit-bot@chromium.org97f0b082014-05-08 21:15:20 +0000212 [constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS],
213 0);
epoger@google.com2682c902013-12-05 16:05:16 +0000214 $scope.hiddenConfigs = {};
commit-bot@chromium.org97f0b082014-05-08 21:15:20 +0000215 $scope.allConfigs = $scope.columnSliceOf2DArray(
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000216 $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMNS__CONFIG]
commit-bot@chromium.org97f0b082014-05-08 21:15:20 +0000217 [constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS],
218 0);
epoger@google.com2682c902013-12-05 16:05:16 +0000219
220 // Associative array of partial string matches per category.
221 $scope.categoryValueMatch = {};
222 $scope.categoryValueMatch.builder = "";
223 $scope.categoryValueMatch.test = "";
224
epoger@google.com62a5ef02013-12-05 18:03:24 +0000225 // If any defaults were overridden in the URL, get them now.
226 $scope.queryParameters.load();
227
commit-bot@chromium.orgb42105a2014-03-20 15:27:34 +0000228 // Any image URLs which are relative should be relative to the JSON
229 // file's source directory; absolute URLs should be left alone.
230 var baseUrlKey = constants.KEY__IMAGESETS__FIELD__BASE_URL;
231 angular.forEach(
232 $scope.imageSets,
233 function(imageSet) {
234 var baseUrl = imageSet[baseUrlKey];
235 if ((baseUrl.substring(0, 1) != '/') &&
236 (baseUrl.indexOf('://') == -1)) {
237 imageSet[baseUrlKey] = $scope.resultsToLoad + '/../' + baseUrl;
238 }
239 }
240 );
241
epoger@google.com2682c902013-12-05 16:05:16 +0000242 $scope.updateResults();
243 $scope.loadingMessage = "";
244 $scope.windowTitle = "Current GM Results";
epoger@google.comeb832592013-10-23 15:07:26 +0000245 }
epoger@google.comdcb4e652013-10-11 18:45:33 +0000246 }
247 ).error(
248 function(data, status, header, config) {
commit-bot@chromium.orgd1c85d22014-03-17 14:22:02 +0000249 $scope.loadingMessage = "FAILED to load.";
epoger@google.com542b65f2013-10-15 20:10:33 +0000250 $scope.windowTitle = "Failed to Load GM Results";
epoger@google.comf9d134d2013-09-27 15:02:44 +0000251 }
252 );
epoger@google.com5f2bb002013-10-02 18:57:48 +0000253
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000254
epoger@google.comad0e5522013-10-24 15:38:27 +0000255 //
epoger@google.com055e3b52013-10-26 14:31:11 +0000256 // Select/Clear/Toggle all tests.
257 //
258
259 /**
260 * Select all currently showing tests.
261 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000262 $scope.selectAllImagePairs = function() {
263 var numImagePairsShowing = $scope.limitedImagePairs.length;
264 for (var i = 0; i < numImagePairsShowing; i++) {
265 var index = $scope.limitedImagePairs[i].index;
266 if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) {
267 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000268 }
269 }
270 }
271
272 /**
273 * Deselect all currently showing tests.
274 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000275 $scope.clearAllImagePairs = function() {
276 var numImagePairsShowing = $scope.limitedImagePairs.length;
277 for (var i = 0; i < numImagePairsShowing; i++) {
278 var index = $scope.limitedImagePairs[i].index;
279 if ($scope.isValueInArray(index, $scope.selectedImagePairs)) {
280 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000281 }
282 }
283 }
284
285 /**
286 * Toggle selection of all currently showing tests.
287 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000288 $scope.toggleAllImagePairs = function() {
289 var numImagePairsShowing = $scope.limitedImagePairs.length;
290 for (var i = 0; i < numImagePairsShowing; i++) {
291 var index = $scope.limitedImagePairs[i].index;
292 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
epoger@google.com055e3b52013-10-26 14:31:11 +0000293 }
294 }
295
epoger6b281202014-06-11 14:01:35 -0700296 /**
297 * Toggle selection state of a subset of the currently showing tests.
298 *
299 * @param startIndex index within $scope.limitedImagePairs of the first
300 * test to toggle selection state of
301 * @param num number of tests (in a contiguous block) to toggle
302 */
303 $scope.toggleSomeImagePairs = function(startIndex, num) {
304 var numImagePairsShowing = $scope.limitedImagePairs.length;
305 for (var i = startIndex; i < startIndex + num; i++) {
306 var index = $scope.limitedImagePairs[i].index;
307 $scope.toggleValueInArray(index, $scope.selectedImagePairs);
308 }
309 }
310
epoger@google.com055e3b52013-10-26 14:31:11 +0000311
312 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000313 // Tab operations.
314 //
epoger@google.com5f2bb002013-10-02 18:57:48 +0000315
epoger@google.comad0e5522013-10-24 15:38:27 +0000316 /**
317 * Change the selected tab.
318 *
319 * @param tab (string): name of the tab to select
320 */
epoger@google.comeb832592013-10-23 15:07:26 +0000321 $scope.setViewingTab = function(tab) {
322 $scope.viewingTab = tab;
323 $scope.updateResults();
324 }
325
epoger@google.comeb832592013-10-23 15:07:26 +0000326 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000327 * Move the imagePairs in $scope.selectedImagePairs to a different tab,
328 * and then clear $scope.selectedImagePairs.
epoger@google.comeb832592013-10-23 15:07:26 +0000329 *
330 * @param newTab (string): name of the tab to move the tests to
331 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000332 $scope.moveSelectedImagePairsToTab = function(newTab) {
333 $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
334 $scope.selectedImagePairs = [];
epoger@google.comeb832592013-10-23 15:07:26 +0000335 $scope.updateResults();
336 }
337
338 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000339 * Move a subset of $scope.imagePairs to a different tab.
epoger@google.comeb832592013-10-23 15:07:26 +0000340 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000341 * @param imagePairIndices (array of ints): indices into $scope.imagePairs
epoger@google.comeb832592013-10-23 15:07:26 +0000342 * indicating which test results to move
343 * @param newTab (string): name of the tab to move the tests to
344 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000345 $scope.moveImagePairsToTab = function(imagePairIndices, newTab) {
346 var imagePairIndex;
347 var numImagePairs = imagePairIndices.length;
348 for (var i = 0; i < numImagePairs; i++) {
349 imagePairIndex = imagePairIndices[i];
350 $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--;
351 $scope.imagePairs[imagePairIndex].tab = newTab;
epoger@google.comeb832592013-10-23 15:07:26 +0000352 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000353 $scope.numResultsPerTab[newTab] += numImagePairs;
epoger@google.comeb832592013-10-23 15:07:26 +0000354 }
355
epoger@google.comad0e5522013-10-24 15:38:27 +0000356
357 //
epoger@google.com62a5ef02013-12-05 18:03:24 +0000358 // $scope.queryParameters:
359 // Transfer parameter values between $scope and the URL query string.
360 //
361 $scope.queryParameters = {};
362
363 // load and save functions for parameters of each type
364 // (load a parameter value into $scope from nameValuePairs,
365 // save a parameter value from $scope into nameValuePairs)
366 $scope.queryParameters.copiers = {
367 'simple': {
368 'load': function(nameValuePairs, name) {
369 var value = nameValuePairs[name];
370 if (value) {
371 $scope[name] = value;
372 }
373 },
374 'save': function(nameValuePairs, name) {
375 nameValuePairs[name] = $scope[name];
376 }
377 },
378
379 'categoryValueMatch': {
380 'load': function(nameValuePairs, name) {
381 var value = nameValuePairs[name];
382 if (value) {
383 $scope.categoryValueMatch[name] = value;
384 }
385 },
386 'save': function(nameValuePairs, name) {
387 nameValuePairs[name] = $scope.categoryValueMatch[name];
388 }
389 },
390
391 'set': {
392 'load': function(nameValuePairs, name) {
393 var value = nameValuePairs[name];
394 if (value) {
395 var valueArray = value.split(',');
396 $scope[name] = {};
397 $scope.toggleValuesInSet(valueArray, $scope[name]);
398 }
399 },
400 'save': function(nameValuePairs, name) {
401 nameValuePairs[name] = Object.keys($scope[name]).join(',');
402 }
403 },
404
405 };
406
407 // parameter name -> copier objects to load/save parameter value
408 $scope.queryParameters.map = {
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000409 'resultsToLoad': $scope.queryParameters.copiers.simple,
410 'displayLimitPending': $scope.queryParameters.copiers.simple,
411 'showThumbnailsPending': $scope.queryParameters.copiers.simple,
epoger6b281202014-06-11 14:01:35 -0700412 'mergeIdenticalRowsPending': $scope.queryParameters.copiers.simple,
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000413 'imageSizePending': $scope.queryParameters.copiers.simple,
414 'sortColumnSubdict': $scope.queryParameters.copiers.simple,
415 'sortColumnKey': $scope.queryParameters.copiers.simple,
epoger@google.com62a5ef02013-12-05 18:03:24 +0000416
417 'hiddenResultTypes': $scope.queryParameters.copiers.set,
418 'hiddenConfigs': $scope.queryParameters.copiers.set,
419 };
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000420 $scope.queryParameters.map[constants.KEY__EXTRACOLUMNS__BUILDER] =
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000421 $scope.queryParameters.copiers.categoryValueMatch;
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000422 $scope.queryParameters.map[constants.KEY__EXTRACOLUMNS__TEST] =
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000423 $scope.queryParameters.copiers.categoryValueMatch;
epoger@google.com62a5ef02013-12-05 18:03:24 +0000424
425 // Loads all parameters into $scope from the URL query string;
426 // any which are not found within the URL will keep their current value.
427 $scope.queryParameters.load = function() {
428 var nameValuePairs = $location.search();
429 angular.forEach($scope.queryParameters.map,
430 function(copier, paramName) {
431 copier.load(nameValuePairs, paramName);
432 }
433 );
434 };
435
436 // Saves all parameters from $scope into the URL query string.
437 $scope.queryParameters.save = function() {
438 var nameValuePairs = {};
439 angular.forEach($scope.queryParameters.map,
440 function(copier, paramName) {
441 copier.save(nameValuePairs, paramName);
442 }
443 );
444 $location.search(nameValuePairs);
445 };
446
447
448 //
epoger@google.comad0e5522013-10-24 15:38:27 +0000449 // updateResults() and friends.
450 //
451
452 /**
453 * Set $scope.areUpdatesPending (to enable/disable the Update Results
454 * button).
455 *
456 * TODO(epoger): We could reduce the amount of code by just setting the
457 * variable directly (from, e.g., a button's ng-click handler). But when
458 * I tried that, the HTML elements depending on the variable did not get
459 * updated.
460 * It turns out that this is due to variable scoping within an ng-repeat
461 * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat
462 *
463 * @param val boolean value to set $scope.areUpdatesPending to
464 */
465 $scope.setUpdatesPending = function(val) {
466 $scope.areUpdatesPending = val;
467 }
468
469 /**
epoger@google.com62a5ef02013-12-05 18:03:24 +0000470 * Update the displayed results, based on filters/settings,
471 * and call $scope.queryParameters.save() so that the new filter results
472 * can be bookmarked.
epoger@google.comad0e5522013-10-24 15:38:27 +0000473 */
epoger@google.com5f2bb002013-10-02 18:57:48 +0000474 $scope.updateResults = function() {
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000475 $scope.renderStartTime = window.performance.now();
476 $log.debug("renderStartTime: " + $scope.renderStartTime);
epoger@google.com5f2bb002013-10-02 18:57:48 +0000477 $scope.displayLimit = $scope.displayLimitPending;
epoger6b281202014-06-11 14:01:35 -0700478 $scope.mergeIdenticalRows = $scope.mergeIdenticalRowsPending;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000479 // TODO(epoger): Every time we apply a filter, AngularJS creates
480 // another copy of the array. Is there a way we can filter out
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000481 // the imagePairs as they are displayed, rather than storing multiple
epoger@google.com5f2bb002013-10-02 18:57:48 +0000482 // array copies? (For better performance.)
epoger@google.comeb832592013-10-23 15:07:26 +0000483
484 if ($scope.viewingTab == $scope.defaultTab) {
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000485
486 // TODO(epoger): Until we allow the user to reverse sort order,
487 // there are certain columns we want to sort in a different order.
488 var doReverse = (
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000489 ($scope.sortColumnKey ==
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000490 constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS) ||
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000491 ($scope.sortColumnKey ==
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000492 constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF));
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000493
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000494 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000495 $filter("orderBy")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000496 $filter("removeHiddenImagePairs")(
497 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000498 $scope.hiddenResultTypes,
499 $scope.hiddenConfigs,
epoger@google.comf4394d52013-10-29 15:49:40 +0000500 $scope.categoryValueMatch.builder,
501 $scope.categoryValueMatch.test,
epoger@google.comeb832592013-10-23 15:07:26 +0000502 $scope.viewingTab
503 ),
epoger6b281202014-06-11 14:01:35 -0700504 [$scope.getSortColumnValue, $scope.getSecondOrderSortValue],
505 doReverse);
506 $scope.limitedImagePairs = $filter("mergeAndLimit")(
507 $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdenticalRows);
epoger@google.comeb832592013-10-23 15:07:26 +0000508 } else {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000509 $scope.filteredImagePairs =
epoger@google.comeb832592013-10-23 15:07:26 +0000510 $filter("orderBy")(
511 $filter("filter")(
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000512 $scope.imagePairs,
epoger@google.comeb832592013-10-23 15:07:26 +0000513 {tab: $scope.viewingTab},
514 true
515 ),
epoger6b281202014-06-11 14:01:35 -0700516 [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]);
517 $scope.limitedImagePairs = $filter("mergeAndLimit")(
518 $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows);
epoger@google.comeb832592013-10-23 15:07:26 +0000519 }
commit-bot@chromium.org61f16062014-03-05 19:46:17 +0000520 $scope.showThumbnails = $scope.showThumbnailsPending;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000521 $scope.imageSize = $scope.imageSizePending;
epoger@google.comad0e5522013-10-24 15:38:27 +0000522 $scope.setUpdatesPending(false);
epoger@google.com62a5ef02013-12-05 18:03:24 +0000523 $scope.queryParameters.save();
epoger@google.com5f2bb002013-10-02 18:57:48 +0000524 }
525
epoger@google.comad0e5522013-10-24 15:38:27 +0000526 /**
commit-bot@chromium.orgfadc7492014-03-10 15:36:06 +0000527 * This function is called when the results have been completely rendered
528 * after updateResults().
529 */
530 $scope.resultsUpdatedCallback = function() {
531 $scope.renderEndTime = window.performance.now();
532 $log.debug("renderEndTime: " + $scope.renderEndTime);
533 }
534
535 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000536 * Re-sort the displayed results.
537 *
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000538 * @param subdict (string): which KEY__IMAGEPAIRS__* subdictionary
epoger6b281202014-06-11 14:01:35 -0700539 * the sort column key is within, or 'none' if the sort column
540 * key is one of KEY__IMAGEPAIRS__*
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000541 * @param key (string): sort by value associated with this key in subdict
epoger@google.comad0e5522013-10-24 15:38:27 +0000542 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000543 $scope.sortResultsBy = function(subdict, key) {
544 $scope.sortColumnSubdict = subdict;
545 $scope.sortColumnKey = key;
epoger@google.com5f2bb002013-10-02 18:57:48 +0000546 $scope.updateResults();
547 }
epoger@google.comeb832592013-10-23 15:07:26 +0000548
epoger@google.comf4394d52013-10-29 15:49:40 +0000549 /**
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000550 * For a particular ImagePair, return the value of the column we are
551 * sorting on (according to $scope.sortColumnSubdict and
552 * $scope.sortColumnKey).
553 *
554 * @param imagePair: imagePair to get a column value out of.
555 */
556 $scope.getSortColumnValue = function(imagePair) {
557 if ($scope.sortColumnSubdict in imagePair) {
558 return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey];
epoger6b281202014-06-11 14:01:35 -0700559 } else if ($scope.sortColumnKey in imagePair) {
560 return imagePair[$scope.sortColumnKey];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000561 } else {
562 return undefined;
563 }
564 }
565
566 /**
epoger6b281202014-06-11 14:01:35 -0700567 * For a particular ImagePair, return the value we use for the
568 * second-order sort (tiebreaker when multiple rows have
569 * the same getSortColumnValue()).
570 *
571 * We join the imageA and imageB urls for this value, so that we merge
572 * adjacent rows as much as possible.
573 *
574 * @param imagePair: imagePair to get a column value out of.
575 */
576 $scope.getSecondOrderSortValue = function(imagePair) {
577 return imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" +
578 imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
579 }
580
581 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000582 * Set $scope.categoryValueMatch[name] = value, and update results.
583 *
584 * @param name
585 * @param value
586 */
587 $scope.setCategoryValueMatch = function(name, value) {
588 $scope.categoryValueMatch[name] = value;
589 $scope.updateResults();
590 }
591
592 /**
593 * Update $scope.hiddenResultTypes so that ONLY this resultType is showing,
594 * and update the visible results.
595 *
596 * @param resultType
597 */
598 $scope.showOnlyResultType = function(resultType) {
599 $scope.hiddenResultTypes = {};
600 // TODO(epoger): Maybe change $scope.allResultTypes to be a Set like
601 // $scope.hiddenResultTypes (rather than an array), so this operation is
602 // simpler (just assign or add allResultTypes to hiddenResultTypes).
603 $scope.toggleValuesInSet($scope.allResultTypes, $scope.hiddenResultTypes);
604 $scope.toggleValueInSet(resultType, $scope.hiddenResultTypes);
605 $scope.updateResults();
606 }
607
608 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000609 * Update $scope.hiddenResultTypes so that ALL resultTypes are showing,
610 * and update the visible results.
611 */
612 $scope.showAllResultTypes = function() {
613 $scope.hiddenResultTypes = {};
614 $scope.updateResults();
615 }
616
617 /**
epoger@google.comf4394d52013-10-29 15:49:40 +0000618 * Update $scope.hiddenConfigs so that ONLY this config is showing,
619 * and update the visible results.
620 *
621 * @param config
622 */
623 $scope.showOnlyConfig = function(config) {
624 $scope.hiddenConfigs = {};
625 $scope.toggleValuesInSet($scope.allConfigs, $scope.hiddenConfigs);
626 $scope.toggleValueInSet(config, $scope.hiddenConfigs);
627 $scope.updateResults();
628 }
629
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000630 /**
631 * Update $scope.hiddenConfigs so that ALL configs are showing,
632 * and update the visible results.
633 */
634 $scope.showAllConfigs = function() {
635 $scope.hiddenConfigs = {};
636 $scope.updateResults();
637 }
638
epoger@google.comad0e5522013-10-24 15:38:27 +0000639
640 //
641 // Operations for sending info back to the server.
642 //
643
epoger@google.comeb832592013-10-23 15:07:26 +0000644 /**
645 * Tell the server that the actual results of these particular tests
646 * are acceptable.
647 *
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000648 * TODO(epoger): This assumes that the original expectations are in
649 * imageSetA, and the actuals are in imageSetB.
650 *
651 * @param imagePairsSubset an array of test results, most likely a subset of
652 * $scope.imagePairs (perhaps with some modifications)
epoger@google.comeb832592013-10-23 15:07:26 +0000653 */
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000654 $scope.submitApprovals = function(imagePairsSubset) {
epoger@google.comeb832592013-10-23 15:07:26 +0000655 $scope.submitPending = true;
epoger@google.com055e3b52013-10-26 14:31:11 +0000656
657 // Convert bug text field to null or 1-item array.
658 var bugs = null;
659 var bugNumber = parseInt($scope.submitAdvancedSettings['bug']);
660 if (!isNaN(bugNumber)) {
661 bugs = [bugNumber];
662 }
663
664 // TODO(epoger): This is a suboptimal way to prevent users from
665 // rebaselining failures in alternative renderModes, but it does work.
666 // For a better solution, see
667 // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new
668 // result type, RenderModeMismatch')
669 var encounteredComparisonConfig = false;
670
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000671 var updatedExpectations = [];
672 for (var i = 0; i < imagePairsSubset.length; i++) {
673 var imagePair = imagePairsSubset[i];
674 var updatedExpectation = {};
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000675 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] =
676 imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS];
677 updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] =
678 imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS];
679 // IMAGE_B_URL contains the actual image (which is now the expectation)
680 updatedExpectation[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] =
681 imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
682 if (0 == updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]
683 [constants.KEY__EXTRACOLUMNS__CONFIG]
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000684 .indexOf('comparison-')) {
epoger@google.com055e3b52013-10-26 14:31:11 +0000685 encounteredComparisonConfig = true;
686 }
687
688 // Advanced settings...
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000689 if (null == updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]) {
690 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = {};
epoger@google.com055e3b52013-10-26 14:31:11 +0000691 }
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000692 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000693 [constants.KEY__EXPECTATIONS__REVIEWED] =
694 $scope.submitAdvancedSettings[
695 constants.KEY__EXPECTATIONS__REVIEWED];
696 if (true == $scope.submitAdvancedSettings[
697 constants.KEY__EXPECTATIONS__IGNOREFAILURE]) {
698 // if it's false, don't send it at all (just keep the default)
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000699 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000700 [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true;
701 }
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000702 updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000703 [constants.KEY__EXPECTATIONS__BUGS] = bugs;
epoger@google.com055e3b52013-10-26 14:31:11 +0000704
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000705 updatedExpectations.push(updatedExpectation);
epoger@google.comeb832592013-10-23 15:07:26 +0000706 }
epoger@google.com055e3b52013-10-26 14:31:11 +0000707 if (encounteredComparisonConfig) {
708 alert("Approval failed -- you cannot approve results with config " +
709 "type comparison-*");
710 $scope.submitPending = false;
711 return;
712 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000713 var modificationData = {};
714 modificationData[constants.KEY__EDITS__MODIFICATIONS] =
715 updatedExpectations;
716 modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] =
717 $scope.header[constants.KEY__HEADER__DATAHASH];
718 modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] =
719 $scope.header[constants.KEY__HEADER__TYPE];
epoger@google.comeb832592013-10-23 15:07:26 +0000720 $http({
721 method: "POST",
722 url: "/edits",
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000723 data: modificationData
epoger@google.comeb832592013-10-23 15:07:26 +0000724 }).success(function(data, status, headers, config) {
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000725 var imagePairIndicesToMove = [];
726 for (var i = 0; i < imagePairsSubset.length; i++) {
727 imagePairIndicesToMove.push(imagePairsSubset[i].index);
epoger@google.comeb832592013-10-23 15:07:26 +0000728 }
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000729 $scope.moveImagePairsToTab(imagePairIndicesToMove,
730 "HackToMakeSureThisImagePairDisappears");
epoger@google.comeb832592013-10-23 15:07:26 +0000731 $scope.updateResults();
732 alert("New baselines submitted successfully!\n\n" +
733 "You still need to commit the updated expectations files on " +
734 "the server side to the Skia repo.\n\n" +
commit-bot@chromium.org50ad8e42013-12-17 18:06:13 +0000735 "When you click OK, your web UI will reload; after that " +
736 "completes, you will see the updated data (once the server has " +
737 "finished loading the update results into memory!) and you can " +
738 "submit more baselines if you want.");
739 // I don't know why, but if I just call reload() here it doesn't work.
740 // Making a timer call it fixes the problem.
741 $timeout(function(){location.reload();}, 1);
epoger@google.comeb832592013-10-23 15:07:26 +0000742 }).error(function(data, status, headers, config) {
743 alert("There was an error submitting your baselines.\n\n" +
744 "Please see server-side log for details.");
745 $scope.submitPending = false;
746 });
747 }
epoger@google.comad0e5522013-10-24 15:38:27 +0000748
749
750 //
751 // Operations we use to mimic Set semantics, in such a way that
752 // checking for presence within the Set is as fast as possible.
753 // But getting a list of all values within the Set is not necessarily
754 // possible.
755 // TODO(epoger): move into a separate .js file?
756 //
757
758 /**
commit-bot@chromium.org6f0ba472014-02-06 20:22:25 +0000759 * Returns the number of values present within set "set".
760 *
761 * @param set an Object which we use to mimic set semantics
762 */
763 $scope.setSize = function(set) {
764 return Object.keys(set).length;
765 }
766
767 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000768 * Returns true if value "value" is present within set "set".
769 *
770 * @param value a value of any type
771 * @param set an Object which we use to mimic set semantics
772 * (this should make isValueInSet faster than if we used an Array)
773 */
774 $scope.isValueInSet = function(value, set) {
775 return (true == set[value]);
776 }
777
778 /**
779 * If value "value" is already in set "set", remove it; otherwise, add it.
780 *
781 * @param value a value of any type
782 * @param set an Object which we use to mimic set semantics
783 */
784 $scope.toggleValueInSet = function(value, set) {
785 if (true == set[value]) {
786 delete set[value];
787 } else {
788 set[value] = true;
789 }
790 }
791
epoger@google.comf4394d52013-10-29 15:49:40 +0000792 /**
793 * For each value in valueArray, call toggleValueInSet(value, set).
794 *
795 * @param valueArray
796 * @param set
797 */
798 $scope.toggleValuesInSet = function(valueArray, set) {
799 var arrayLength = valueArray.length;
800 for (var i = 0; i < arrayLength; i++) {
801 $scope.toggleValueInSet(valueArray[i], set);
802 }
803 }
804
epoger@google.comad0e5522013-10-24 15:38:27 +0000805
806 //
807 // Array operations; similar to our Set operations, but operate on a
808 // Javascript Array so we *can* easily get a list of all values in the Set.
809 // TODO(epoger): move into a separate .js file?
810 //
811
812 /**
813 * Returns true if value "value" is present within array "array".
814 *
815 * @param value a value of any type
816 * @param array a Javascript Array
817 */
818 $scope.isValueInArray = function(value, array) {
819 return (-1 != array.indexOf(value));
820 }
821
822 /**
823 * If value "value" is already in array "array", remove it; otherwise,
824 * add it.
825 *
826 * @param value a value of any type
827 * @param array a Javascript Array
828 */
829 $scope.toggleValueInArray = function(value, array) {
830 var i = array.indexOf(value);
831 if (-1 == i) {
832 array.push(value);
833 } else {
834 array.splice(i, 1);
835 }
836 }
837
838
839 //
840 // Miscellaneous utility functions.
841 // TODO(epoger): move into a separate .js file?
842 //
843
844 /**
commit-bot@chromium.org97f0b082014-05-08 21:15:20 +0000845 * Returns a single "column slice" of a 2D array.
846 *
847 * For example, if array is:
848 * [[A0, A1],
849 * [B0, B1],
850 * [C0, C1]]
851 * and index is 0, this this will return:
852 * [A0, B0, C0]
853 *
854 * @param array a Javascript Array
855 * @param column (numeric): index within each row array
856 */
857 $scope.columnSliceOf2DArray = function(array, column) {
858 var slice = [];
859 var numRows = array.length;
860 for (var row = 0; row < numRows; row++) {
861 slice.push(array[row][column]);
862 }
863 return slice;
864 }
865
866 /**
epoger@google.comad0e5522013-10-24 15:38:27 +0000867 * Returns a human-readable (in local time zone) time string for a
868 * particular moment in time.
869 *
870 * @param secondsPastEpoch (numeric): seconds past epoch in UTC
871 */
872 $scope.localTimeString = function(secondsPastEpoch) {
873 var d = new Date(secondsPastEpoch * 1000);
874 return d.toString();
875 }
876
commit-bot@chromium.orgceba0792014-02-05 19:49:17 +0000877 /**
878 * Returns a hex color string (such as "#aabbcc") for the given RGB values.
879 *
880 * @param r (numeric): red channel value, 0-255
881 * @param g (numeric): green channel value, 0-255
882 * @param b (numeric): blue channel value, 0-255
883 */
884 $scope.hexColorString = function(r, g, b) {
885 var rString = r.toString(16);
886 if (r < 16) {
887 rString = "0" + rString;
888 }
889 var gString = g.toString(16);
890 if (g < 16) {
891 gString = "0" + gString;
892 }
893 var bString = b.toString(16);
894 if (b < 16) {
895 bString = "0" + bString;
896 }
897 return '#' + rString + gString + bString;
898 }
899
900 /**
901 * Returns a hex color string (such as "#aabbcc") for the given brightness.
902 *
903 * @param brightnessString (string): 0-255, 0 is completely black
904 *
905 * TODO(epoger): It might be nice to tint the color when it's not completely
906 * black or completely white.
907 */
908 $scope.brightnessStringToHexColor = function(brightnessString) {
909 var v = parseInt(brightnessString);
910 return $scope.hexColorString(v, v, v);
911 }
912
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000913 /**
914 * Returns the last path component of image diff URL for a given ImagePair.
915 *
916 * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this
917 * will be relative to different base URLs.
918 *
919 * We must keep this function in sync with _get_difference_locator() in
920 * ../imagediffdb.py
921 *
922 * @param imagePair: ImagePair to generate image diff URL for
923 */
924 $scope.getImageDiffRelativeUrl = function(imagePair) {
925 var before =
commit-bot@chromium.org68a38152014-05-12 20:40:29 +0000926 imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" +
927 imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
commit-bot@chromium.org16f41802014-02-26 19:05:20 +0000928 return before.replace(/[^\w\-]/g, "_") + ".png";
929 }
930
epoger@google.comf9d134d2013-09-27 15:02:44 +0000931 }
932);