diff options
author | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-02-26 19:05:20 +0000 |
---|---|---|
committer | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-02-26 19:05:20 +0000 |
commit | 16f418080ff6751e15e0193263149412de9c848a (patch) | |
tree | 4704fbad68889e675a1a6c6a42723be715d8eb5e /gm/rebaseline_server/static | |
parent | 1caedbb216703df58ce5ebe29955e53fa098d8dc (diff) |
rebaseline_server: use new intermediate JSON format as described in https://goto.google.com/ChangingRbsJson
There are still some places in the frontend (HTML+Javascript) code where
we assume we are handling expectations vs actuals, but there are just a few
and we should be able to remove them easily in a coming CL.
At that point, the frontend will work just as well for displaying any set
of image pairs.
BUG=skia:1919
NOTRY=True
R=rmistry@google.com
Author: epoger@google.com
Review URL: https://codereview.chromium.org/178253010
git-svn-id: http://skia.googlecode.com/svn/trunk@13598 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'gm/rebaseline_server/static')
-rw-r--r-- | gm/rebaseline_server/static/constants.js | 69 | ||||
-rw-r--r-- | gm/rebaseline_server/static/loader.js | 317 | ||||
-rw-r--r-- | gm/rebaseline_server/static/view.html | 166 |
3 files changed, 338 insertions, 214 deletions
diff --git a/gm/rebaseline_server/static/constants.js b/gm/rebaseline_server/static/constants.js new file mode 100644 index 0000000000..00c1852458 --- /dev/null +++ b/gm/rebaseline_server/static/constants.js @@ -0,0 +1,69 @@ +/* + * Constants used by our imagediff-viewing Javascript code. + */ +var module = angular.module( + 'ConstantsModule', + [] +); + +module.constant('constants', (function() { + return { + // NOTE: Keep these in sync with ../column.py + KEY__HEADER_TEXT: 'headerText', + KEY__HEADER_URL: 'headerUrl', + KEY__IS_FILTERABLE: 'isFilterable', + KEY__IS_SORTABLE: 'isSortable', + KEY__VALUES_AND_COUNTS: 'valuesAndCounts', + + // NOTE: Keep these in sync with ../imagediffdb.py + KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL: 'maxDiffPerChannel', + KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS: 'numDifferingPixels', + KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS: 'percentDifferingPixels', + KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF: 'perceptualDifference', + KEY__DIFFERENCE_DATA__WEIGHTED_DIFF: 'weightedDiffMeasure', + + // NOTE: Keep these in sync with ../imagepair.py + KEY__DIFFERENCE_DATA: 'differenceData', + KEY__EXPECTATIONS_DATA: 'expectations', + KEY__EXTRA_COLUMN_VALUES: 'extraColumns', + KEY__IMAGE_A_URL: 'imageAUrl', + KEY__IMAGE_B_URL: 'imageBUrl', + KEY__IS_DIFFERENT: 'isDifferent', + + // NOTE: Keep these in sync with ../imagepairset.py + KEY__EXTRACOLUMNHEADERS: 'extraColumnHeaders', + KEY__IMAGEPAIRS: 'imagePairs', + KEY__IMAGESETS: 'imageSets', + KEY__IMAGESETS__BASE_URL: 'baseUrl', + KEY__IMAGESETS__DESCRIPTION: 'description', + + // NOTE: Keep these in sync with ../results.py + KEY__EXPECTATIONS__BUGS: 'bugs', + KEY__EXPECTATIONS__IGNOREFAILURE: 'ignore-failure', + KEY__EXPECTATIONS__REVIEWED: 'reviewed-by-human', + KEY__EXTRACOLUMN__BUILDER: 'builder', + KEY__EXTRACOLUMN__CONFIG: 'config', + KEY__EXTRACOLUMN__RESULT_TYPE: 'resultType', + KEY__EXTRACOLUMN__TEST: 'test', + KEY__HEADER__RESULTS_ALL: 'all', + KEY__HEADER__RESULTS_FAILURES: 'failures', + KEY__NEW_IMAGE_URL: 'newImageUrl', + KEY__RESULT_TYPE__FAILED: 'failed', + KEY__RESULT_TYPE__FAILUREIGNORED: 'failure-ignored', + KEY__RESULT_TYPE__NOCOMPARISON: 'no-comparison', + KEY__RESULT_TYPE__SUCCEEDED: 'succeeded', + + // NOTE: Keep these in sync with ../server.py + KEY__EDITS__MODIFICATIONS: 'modifications', + KEY__EDITS__OLD_RESULTS_HASH: 'oldResultsHash', + KEY__EDITS__OLD_RESULTS_TYPE: 'oldResultsType', + KEY__HEADER: 'header', + KEY__HEADER__DATAHASH: 'dataHash', + KEY__HEADER__IS_EDITABLE: 'isEditable', + KEY__HEADER__IS_EXPORTED: 'isExported', + KEY__HEADER__IS_STILL_LOADING: 'resultsStillLoading', + KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE: 'timeNextUpdateAvailable', + KEY__HEADER__TIME_UPDATED: 'timeUpdated', + KEY__HEADER__TYPE: 'type', + } +})()) diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js index 9ae84d613c..ab79df8189 100644 --- a/gm/rebaseline_server/static/loader.js +++ b/gm/rebaseline_server/static/loader.js @@ -1,38 +1,42 @@ /* * Loader: * Reads GM result reports written out by results.py, and imports - * them into $scope.categories and $scope.testData . + * them into $scope.extraColumnHeaders and $scope.imagePairs . */ var Loader = angular.module( 'Loader', - ['diff_viewer'] + ['ConstantsModule', 'diff_viewer'] ); - // TODO(epoger): Combine ALL of our filtering operations (including // truncation) into this one filter, so that runs most efficiently? // (We would have to make sure truncation still took place after // sorting, though.) Loader.filter( - 'removeHiddenItems', - function() { - return function(unfilteredItems, hiddenResultTypes, hiddenConfigs, + 'removeHiddenImagePairs', + function(constants) { + return function(unfilteredImagePairs, hiddenResultTypes, hiddenConfigs, builderSubstring, testSubstring, viewingTab) { - var filteredItems = []; - for (var i = 0; i < unfilteredItems.length; i++) { - var item = unfilteredItems[i]; + var filteredImagePairs = []; + for (var i = 0; i < unfilteredImagePairs.length; i++) { + var imagePair = unfilteredImagePairs[i]; + var extraColumnValues = imagePair[constants.KEY__EXTRA_COLUMN_VALUES]; // For performance, we examine the "set" objects directly rather // than calling $scope.isValueInSet(). // Besides, I don't think we have access to $scope in here... - if (!(true == hiddenResultTypes[item.resultType]) && - !(true == hiddenConfigs[item.config]) && - !(-1 == item.builder.indexOf(builderSubstring)) && - !(-1 == item.test.indexOf(testSubstring)) && - (viewingTab == item.tab)) { - filteredItems.push(item); + if (!(true == hiddenResultTypes[extraColumnValues[ + constants.KEY__EXTRACOLUMN__RESULT_TYPE]]) && + !(true == hiddenConfigs[extraColumnValues[ + constants.KEY__EXTRACOLUMN__CONFIG]]) && + !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__BUILDER] + .indexOf(builderSubstring)) && + !(-1 == extraColumnValues[constants.KEY__EXTRACOLUMN__TEST] + .indexOf(testSubstring)) && + (viewingTab == imagePair.tab)) { + filteredImagePairs.push(imagePair); } } - return filteredItems; + return filteredImagePairs; }; } ); @@ -40,7 +44,8 @@ Loader.filter( Loader.controller( 'Loader.Controller', - function($scope, $http, $filter, $location, $timeout) { + function($scope, $http, $filter, $location, $timeout, constants) { + $scope.constants = constants; $scope.windowTitle = "Loading GM Results..."; $scope.resultsToLoad = $location.search().resultsToLoad; $scope.loadingMessage = "Loading results of type '" + $scope.resultsToLoad + @@ -53,26 +58,33 @@ Loader.controller( */ $http.get("/results/" + $scope.resultsToLoad).success( function(data, status, header, config) { - if (data.header.resultsStillLoading) { + var dataHeader = data[constants.KEY__HEADER]; + if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) { $scope.loadingMessage = "Server is still loading results; will retry at " + - $scope.localTimeString(data.header.timeNextUpdateAvailable); + $scope.localTimeString(dataHeader[ + constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE]); $timeout( function(){location.reload();}, - (data.header.timeNextUpdateAvailable * 1000) - new Date().getTime()); + (dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] + * 1000) - new Date().getTime()); } else { $scope.loadingMessage = "Processing data, please wait..."; - $scope.header = data.header; - $scope.categories = data.categories; - $scope.testData = data.testData; - $scope.sortColumn = 'weightedDiffMeasure'; + $scope.header = dataHeader; + $scope.extraColumnHeaders = data[constants.KEY__EXTRACOLUMNHEADERS]; + $scope.imagePairs = data[constants.KEY__IMAGEPAIRS]; + $scope.imageSets = data[constants.KEY__IMAGESETS]; + $scope.sortColumnSubdict = constants.KEY__DIFFERENCE_DATA; + $scope.sortColumnKey = constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF; $scope.showTodos = false; $scope.showSubmitAdvancedSettings = false; $scope.submitAdvancedSettings = {}; - $scope.submitAdvancedSettings['reviewed-by-human'] = true; - $scope.submitAdvancedSettings['ignore-failure'] = false; + $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__REVIEWED] = true; + $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__IGNOREFAILURE] = false; $scope.submitAdvancedSettings['bug'] = ''; // Create the list of tabs (lists into which the user can file each @@ -80,7 +92,7 @@ Loader.controller( $scope.tabs = [ 'Unfiled', 'Hidden' ]; - if (data.header.isEditable) { + if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) { $scope.tabs = $scope.tabs.concat( ['Pending Approval']); } @@ -92,26 +104,32 @@ Loader.controller( for (var i = 0; i < $scope.tabs.length; i++) { $scope.numResultsPerTab[$scope.tabs[i]] = 0; } - $scope.numResultsPerTab[$scope.defaultTab] = $scope.testData.length; + $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length; // Add index and tab fields to all records. - for (var i = 0; i < $scope.testData.length; i++) { - $scope.testData[i].index = i; - $scope.testData[i].tab = $scope.defaultTab; + for (var i = 0; i < $scope.imagePairs.length; i++) { + $scope.imagePairs[i].index = i; + $scope.imagePairs[i].tab = $scope.defaultTab; } // Arrays within which the user can toggle individual elements. - $scope.selectedItems = []; + $scope.selectedImagePairs = []; // Sets within which the user can toggle individual elements. - $scope.hiddenResultTypes = { - 'failure-ignored': true, - 'no-comparison': true, - 'succeeded': true, - }; - $scope.allResultTypes = Object.keys(data.categories['resultType']); + $scope.hiddenResultTypes = {}; + $scope.hiddenResultTypes[ + constants.KEY__RESULT_TYPE__FAILUREIGNORED] = true; + $scope.hiddenResultTypes[ + constants.KEY__RESULT_TYPE__NOCOMPARISON] = true; + $scope.hiddenResultTypes[ + constants.KEY__RESULT_TYPE__SUCCEEDED] = true; + $scope.allResultTypes = Object.keys( + $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE] + [constants.KEY__VALUES_AND_COUNTS]); $scope.hiddenConfigs = {}; - $scope.allConfigs = Object.keys(data.categories['config']); + $scope.allConfigs = Object.keys( + $scope.extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG] + [constants.KEY__VALUES_AND_COUNTS]); // Associative array of partial string matches per category. $scope.categoryValueMatch = {}; @@ -142,12 +160,12 @@ Loader.controller( /** * Select all currently showing tests. */ - $scope.selectAllItems = function() { - var numItemsShowing = $scope.limitedTestData.length; - for (var i = 0; i < numItemsShowing; i++) { - var index = $scope.limitedTestData[i].index; - if (!$scope.isValueInArray(index, $scope.selectedItems)) { - $scope.toggleValueInArray(index, $scope.selectedItems); + $scope.selectAllImagePairs = function() { + var numImagePairsShowing = $scope.limitedImagePairs.length; + for (var i = 0; i < numImagePairsShowing; i++) { + var index = $scope.limitedImagePairs[i].index; + if (!$scope.isValueInArray(index, $scope.selectedImagePairs)) { + $scope.toggleValueInArray(index, $scope.selectedImagePairs); } } } @@ -155,12 +173,12 @@ Loader.controller( /** * Deselect all currently showing tests. */ - $scope.clearAllItems = function() { - var numItemsShowing = $scope.limitedTestData.length; - for (var i = 0; i < numItemsShowing; i++) { - var index = $scope.limitedTestData[i].index; - if ($scope.isValueInArray(index, $scope.selectedItems)) { - $scope.toggleValueInArray(index, $scope.selectedItems); + $scope.clearAllImagePairs = function() { + var numImagePairsShowing = $scope.limitedImagePairs.length; + for (var i = 0; i < numImagePairsShowing; i++) { + var index = $scope.limitedImagePairs[i].index; + if ($scope.isValueInArray(index, $scope.selectedImagePairs)) { + $scope.toggleValueInArray(index, $scope.selectedImagePairs); } } } @@ -168,11 +186,11 @@ Loader.controller( /** * Toggle selection of all currently showing tests. */ - $scope.toggleAllItems = function() { - var numItemsShowing = $scope.limitedTestData.length; - for (var i = 0; i < numItemsShowing; i++) { - var index = $scope.limitedTestData[i].index; - $scope.toggleValueInArray(index, $scope.selectedItems); + $scope.toggleAllImagePairs = function() { + var numImagePairsShowing = $scope.limitedImagePairs.length; + for (var i = 0; i < numImagePairsShowing; i++) { + var index = $scope.limitedImagePairs[i].index; + $scope.toggleValueInArray(index, $scope.selectedImagePairs); } } @@ -192,33 +210,33 @@ Loader.controller( } /** - * Move the items in $scope.selectedItems to a different tab, - * and then clear $scope.selectedItems. + * Move the imagePairs in $scope.selectedImagePairs to a different tab, + * and then clear $scope.selectedImagePairs. * * @param newTab (string): name of the tab to move the tests to */ - $scope.moveSelectedItemsToTab = function(newTab) { - $scope.moveItemsToTab($scope.selectedItems, newTab); - $scope.selectedItems = []; + $scope.moveSelectedImagePairsToTab = function(newTab) { + $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab); + $scope.selectedImagePairs = []; $scope.updateResults(); } /** - * Move a subset of $scope.testData to a different tab. + * Move a subset of $scope.imagePairs to a different tab. * - * @param itemIndices (array of ints): indices into $scope.testData + * @param imagePairIndices (array of ints): indices into $scope.imagePairs * indicating which test results to move * @param newTab (string): name of the tab to move the tests to */ - $scope.moveItemsToTab = function(itemIndices, newTab) { - var itemIndex; - var numItems = itemIndices.length; - for (var i = 0; i < numItems; i++) { - itemIndex = itemIndices[i]; - $scope.numResultsPerTab[$scope.testData[itemIndex].tab]--; - $scope.testData[itemIndex].tab = newTab; + $scope.moveImagePairsToTab = function(imagePairIndices, newTab) { + var imagePairIndex; + var numImagePairs = imagePairIndices.length; + for (var i = 0; i < numImagePairs; i++) { + imagePairIndex = imagePairIndices[i]; + $scope.numResultsPerTab[$scope.imagePairs[imagePairIndex].tab]--; + $scope.imagePairs[imagePairIndex].tab = newTab; } - $scope.numResultsPerTab[newTab] += numItems; + $scope.numResultsPerTab[newTab] += numImagePairs; } @@ -277,14 +295,16 @@ Loader.controller( 'resultsToLoad': $scope.queryParameters.copiers.simple, 'displayLimitPending': $scope.queryParameters.copiers.simple, 'imageSizePending': $scope.queryParameters.copiers.simple, - 'sortColumn': $scope.queryParameters.copiers.simple, - - 'builder': $scope.queryParameters.copiers.categoryValueMatch, - 'test': $scope.queryParameters.copiers.categoryValueMatch, + 'sortColumnSubdict': $scope.queryParameters.copiers.simple, + 'sortColumnKey': $scope.queryParameters.copiers.simple, 'hiddenResultTypes': $scope.queryParameters.copiers.set, 'hiddenConfigs': $scope.queryParameters.copiers.set, }; + $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__BUILDER] = + $scope.queryParameters.copiers.categoryValueMatch; + $scope.queryParameters.map[constants.KEY__EXTRACOLUMN__TEST] = + $scope.queryParameters.copiers.categoryValueMatch; // Loads all parameters into $scope from the URL query string; // any which are not found within the URL will keep their current value. @@ -339,7 +359,7 @@ Loader.controller( $scope.displayLimit = $scope.displayLimitPending; // TODO(epoger): Every time we apply a filter, AngularJS creates // another copy of the array. Is there a way we can filter out - // the items as they are displayed, rather than storing multiple + // the imagePairs as they are displayed, rather than storing multiple // array copies? (For better performance.) if ($scope.viewingTab == $scope.defaultTab) { @@ -347,32 +367,34 @@ Loader.controller( // TODO(epoger): Until we allow the user to reverse sort order, // there are certain columns we want to sort in a different order. var doReverse = ( - ($scope.sortColumn == 'percentDifferingPixels') || - ($scope.sortColumn == 'weightedDiffMeasure')); + ($scope.sortColumnKey == + constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS) || + ($scope.sortColumnKey == + constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF)); - $scope.filteredTestData = + $scope.filteredImagePairs = $filter("orderBy")( - $filter("removeHiddenItems")( - $scope.testData, + $filter("removeHiddenImagePairs")( + $scope.imagePairs, $scope.hiddenResultTypes, $scope.hiddenConfigs, $scope.categoryValueMatch.builder, $scope.categoryValueMatch.test, $scope.viewingTab ), - $scope.sortColumn, doReverse); - $scope.limitedTestData = $filter("limitTo")( - $scope.filteredTestData, $scope.displayLimit); + $scope.getSortColumnValue, doReverse); + $scope.limitedImagePairs = $filter("limitTo")( + $scope.filteredImagePairs, $scope.displayLimit); } else { - $scope.filteredTestData = + $scope.filteredImagePairs = $filter("orderBy")( $filter("filter")( - $scope.testData, + $scope.imagePairs, {tab: $scope.viewingTab}, true ), - $scope.sortColumn); - $scope.limitedTestData = $scope.filteredTestData; + $scope.getSortColumnValue); + $scope.limitedImagePairs = $scope.filteredImagePairs; } $scope.imageSize = $scope.imageSizePending; $scope.setUpdatesPending(false); @@ -382,14 +404,33 @@ Loader.controller( /** * Re-sort the displayed results. * - * @param sortColumn (string): name of the column to sort on + * @param subdict (string): which subdictionary + * (constants.KEY__DIFFERENCE_DATA, constants.KEY__EXPECTATIONS_DATA, + * constants.KEY__EXTRA_COLUMN_VALUES) the sort column key is within + * @param key (string): sort by value associated with this key in subdict */ - $scope.sortResultsBy = function(sortColumn) { - $scope.sortColumn = sortColumn; + $scope.sortResultsBy = function(subdict, key) { + $scope.sortColumnSubdict = subdict; + $scope.sortColumnKey = key; $scope.updateResults(); } /** + * For a particular ImagePair, return the value of the column we are + * sorting on (according to $scope.sortColumnSubdict and + * $scope.sortColumnKey). + * + * @param imagePair: imagePair to get a column value out of. + */ + $scope.getSortColumnValue = function(imagePair) { + if ($scope.sortColumnSubdict in imagePair) { + return imagePair[$scope.sortColumnSubdict][$scope.sortColumnKey]; + } else { + return undefined; + } + } + + /** * Set $scope.categoryValueMatch[name] = value, and update results. * * @param name @@ -456,10 +497,13 @@ Loader.controller( * Tell the server that the actual results of these particular tests * are acceptable. * - * @param testDataSubset an array of test results, most likely a subset of - * $scope.testData (perhaps with some modifications) + * TODO(epoger): This assumes that the original expectations are in + * imageSetA, and the actuals are in imageSetB. + * + * @param imagePairsSubset an array of test results, most likely a subset of + * $scope.imagePairs (perhaps with some modifications) */ - $scope.submitApprovals = function(testDataSubset) { + $scope.submitApprovals = function(imagePairsSubset) { $scope.submitPending = true; // Convert bug text field to null or 1-item array. @@ -476,30 +520,40 @@ Loader.controller( // result type, RenderModeMismatch') var encounteredComparisonConfig = false; - var newResults = []; - for (var i = 0; i < testDataSubset.length; i++) { - var actualResult = testDataSubset[i]; - var expectedResult = { - builder: actualResult['builder'], - test: actualResult['test'], - config: actualResult['config'], - expectedHashType: actualResult['actualHashType'], - expectedHashDigest: actualResult['actualHashDigest'], - }; - if (0 == expectedResult.config.indexOf('comparison-')) { + var updatedExpectations = []; + for (var i = 0; i < imagePairsSubset.length; i++) { + var imagePair = imagePairsSubset[i]; + var updatedExpectation = {}; + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = + imagePair[constants.KEY__EXPECTATIONS_DATA]; + updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] = + imagePair[constants.KEY__EXTRA_COLUMN_VALUES]; + updatedExpectation[constants.KEY__NEW_IMAGE_URL] = + imagePair[constants.KEY__IMAGE_B_URL]; + if (0 == updatedExpectation[constants.KEY__EXTRA_COLUMN_VALUES] + [constants.KEY__EXTRACOLUMN__CONFIG] + .indexOf('comparison-')) { encounteredComparisonConfig = true; } // Advanced settings... - expectedResult['reviewed-by-human'] = - $scope.submitAdvancedSettings['reviewed-by-human']; - if (true == $scope.submitAdvancedSettings['ignore-failure']) { + if (null == updatedExpectation[constants.KEY__EXPECTATIONS_DATA]) { + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] = {}; + } + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] + [constants.KEY__EXPECTATIONS__REVIEWED] = + $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__REVIEWED]; + if (true == $scope.submitAdvancedSettings[ + constants.KEY__EXPECTATIONS__IGNOREFAILURE]) { // if it's false, don't send it at all (just keep the default) - expectedResult['ignore-failure'] = true; + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] + [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true; } - expectedResult['bugs'] = bugs; + updatedExpectation[constants.KEY__EXPECTATIONS_DATA] + [constants.KEY__EXPECTATIONS__BUGS] = bugs; - newResults.push(expectedResult); + updatedExpectations.push(updatedExpectation); } if (encounteredComparisonConfig) { alert("Approval failed -- you cannot approve results with config " + @@ -507,21 +561,24 @@ Loader.controller( $scope.submitPending = false; return; } + var modificationData = {}; + modificationData[constants.KEY__EDITS__MODIFICATIONS] = + updatedExpectations; + modificationData[constants.KEY__EDITS__OLD_RESULTS_HASH] = + $scope.header[constants.KEY__HEADER__DATAHASH]; + modificationData[constants.KEY__EDITS__OLD_RESULTS_TYPE] = + $scope.header[constants.KEY__HEADER__TYPE]; $http({ method: "POST", url: "/edits", - data: { - oldResultsType: $scope.header.type, - oldResultsHash: $scope.header.dataHash, - modifications: newResults - } + data: modificationData }).success(function(data, status, headers, config) { - var itemIndicesToMove = []; - for (var i = 0; i < testDataSubset.length; i++) { - itemIndicesToMove.push(testDataSubset[i].index); + var imagePairIndicesToMove = []; + for (var i = 0; i < imagePairsSubset.length; i++) { + imagePairIndicesToMove.push(imagePairsSubset[i].index); } - $scope.moveItemsToTab(itemIndicesToMove, - "HackToMakeSureThisItemDisappears"); + $scope.moveImagePairsToTab(imagePairIndicesToMove, + "HackToMakeSureThisImagePairDisappears"); $scope.updateResults(); alert("New baselines submitted successfully!\n\n" + "You still need to commit the updated expectations files on " + @@ -682,5 +739,23 @@ Loader.controller( return $scope.hexColorString(v, v, v); } + /** + * Returns the last path component of image diff URL for a given ImagePair. + * + * Depending on which diff this is (whitediffs, pixeldiffs, etc.) this + * will be relative to different base URLs. + * + * We must keep this function in sync with _get_difference_locator() in + * ../imagediffdb.py + * + * @param imagePair: ImagePair to generate image diff URL for + */ + $scope.getImageDiffRelativeUrl = function(imagePair) { + var before = + imagePair[constants.KEY__IMAGE_A_URL] + "-vs-" + + imagePair[constants.KEY__IMAGE_B_URL]; + return before.replace(/[^\w\-]/g, "_") + ".png"; + } + } ); diff --git a/gm/rebaseline_server/static/view.html b/gm/rebaseline_server/static/view.html index 9ce3fae140..36fca750b1 100644 --- a/gm/rebaseline_server/static/view.html +++ b/gm/rebaseline_server/static/view.html @@ -5,8 +5,9 @@ <head> <title ng-bind="windowTitle"></title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script> - <script src="loader.js"></script> + <script src="constants.js"></script> <script src="diff_viewer.js"></script> + <script src="loader.js"></script> <link rel="stylesheet" href="view.css"> </head> @@ -22,16 +23,16 @@ {{loadingMessage}} </em> - <div ng-hide="!categories"><!-- everything: hide until data is loaded --> + <div ng-hide="!extraColumnHeaders"><!-- everything: hide until data is loaded --> <div class="warning-div" - ng-hide="!(header.isEditable && header.isExported)"> + ng-hide="!(header[constants.KEY__HEADER__IS_EDITABLE] && header[constants.KEY__HEADER__IS_EXPORTED])"> WARNING! These results are editable and exported, so any user who can connect to this server over the network can modify them. </div> - <div ng-hide="!(header.timeUpdated)"> - Results current as of {{localTimeString(header.timeUpdated)}} + <div ng-hide="!(header[constants.KEY__HEADER___TIME_UPDATED])"> + Results current as of {{localTimeString(header[constants.KEY__HEADER__TIME_UPDATED])}} </div> <div><!-- tabs --> @@ -60,10 +61,9 @@ </th> </tr> <tr valign="top"> - <!-- TODO(epoger): make this an ng-repeat over resultType, config, etc? --> <td> resultType<br> - <label ng-repeat="(resultType, count) in categories['resultType'] track by $index"> + <label ng-repeat="(resultType, count) in extraColumnHeaders[constants.KEY__EXTRACOLUMN__RESULT_TYPE][constants.KEY__VALUES_AND_COUNTS] track by $index"> <input type="checkbox" name="resultTypes" value="{{resultType}}" @@ -81,7 +81,7 @@ toggle </button> </td> - <td ng-repeat="category in ['builder', 'test']"> + <td ng-repeat="category in [constants.KEY__EXTRACOLUMN__BUILDER, constants.KEY__EXTRACOLUMN__TEST]"> {{category}} <br> <input type="text" @@ -95,7 +95,7 @@ </td> <td> config<br> - <label ng-repeat="(config, count) in categories['config'] track by $index"> + <label ng-repeat="(config, count) in extraColumnHeaders[constants.KEY__EXTRACOLUMN__CONFIG][constants.KEY__VALUES_AND_COUNTS] track by $index"> <input type="checkbox" name="configs" value="{{config}}" @@ -145,9 +145,9 @@ <div ng-hide="'Pending Approval' != viewingTab"> <div style="display:inline-block"> <button style="font-size:20px" - ng-click="submitApprovals(filteredTestData)" - ng-disabled="submitPending || (filteredTestData.length == 0)"> - Update these {{filteredTestData.length}} expectations on the server + ng-click="submitApprovals(filteredImagePairs)" + ng-disabled="submitPending || (filteredImagePairs.length == 0)"> + Update these {{filteredImagePairs.length}} expectations on the server </button> </div> <div style="display:inline-block"> @@ -161,7 +161,7 @@ <input type="checkbox" ng-model="showSubmitAdvancedSettings"> show <ul ng-hide="!showSubmitAdvancedSettings"> - <li ng-repeat="setting in ['reviewed-by-human', 'ignore-failure']"> + <li ng-repeat="setting in [constants.KEY__EXPECTATIONS__REVIEWED, constants.KEY__EXPECTATIONS__IGNOREFAILURE]"> {{setting}} <input type="checkbox" ng-model="submitAdvancedSettings[setting]"> </li> @@ -179,11 +179,11 @@ <table border="0" width="100%"> <!-- results header --> <tr> <td> - Found {{filteredTestData.length}} matches; - <span ng-hide="filteredTestData.length <= limitedTestData.length"> - displaying the first {{limitedTestData.length}} + Found {{filteredImagePairs.length}} matches; + <span ng-hide="filteredImagePairs.length <= limitedImagePairs.length"> + displaying the first {{limitedImagePairs.length}} </span> - <span ng-hide="filteredTestData.length > limitedTestData.length"> + <span ng-hide="filteredImagePairs.length > limitedImagePairs.length"> displaying them all </span> <br> @@ -192,21 +192,21 @@ <td align="right"> <div> all tests shown: - <button ng-click="selectAllItems()"> + <button ng-click="selectAllImagePairs()"> select </button> - <button ng-click="clearAllItems()"> + <button ng-click="clearAllImagePairs()"> clear </button> - <button ng-click="toggleAllItems()"> + <button ng-click="toggleAllImagePairs()"> toggle </button> </div> <div ng-repeat="otherTab in tabs"> - <button ng-click="moveSelectedItemsToTab(otherTab)" - ng-disabled="selectedItems.length == 0" + <button ng-click="moveSelectedImagePairsToTab(otherTab)" + ng-disabled="selectedImagePairs.length == 0" ng-hide="otherTab == viewingTab"> - move {{selectedItems.length}} selected tests to {{otherTab}} tab + move {{selectedImagePairs.length}} selected tests to {{otherTab}} tab </button> </div> </td> @@ -216,12 +216,12 @@ <table border="1" ng-app="diff_viewer"> <!-- results --> <tr> <!-- Most column headers are displayed in a common fashion... --> - <th ng-repeat="categoryName in ['resultType', 'builder', 'test', 'config']"> + <th ng-repeat="categoryName in [constants.KEY__EXTRACOLUMN__RESULT_TYPE, constants.KEY__EXTRACOLUMN__BUILDER, constants.KEY__EXTRACOLUMN__TEST, constants.KEY__EXTRACOLUMN__CONFIG]"> <input type="radio" name="sortColumnRadio" value="{{categoryName}}" - ng-checked="(sortColumn == categoryName)" - ng-click="sortResultsBy(categoryName)"> + ng-checked="(sortColumnKey == categoryName)" + ng-click="sortResultsBy(constants.KEY__EXTRA_COLUMN_VALUES, categoryName)"> {{categoryName}} </th> <!-- ... but there are a few columns where we display things differently. --> @@ -229,40 +229,30 @@ <input type="radio" name="sortColumnRadio" value="bugs" - ng-checked="(sortColumn == 'bugs')" - ng-click="sortResultsBy('bugs')"> + ng-checked="(sortColumnKey == constants.KEY__EXPECTATIONS__BUGS)" + ng-click="sortResultsBy(constants.KEY__EXPECTATIONS_DATA, constants.KEY__EXPECTATIONS__BUGS)"> bugs </th> <th width="{{imageSize}}"> - <input type="radio" - name="sortColumnRadio" - value="expectedHashDigest" - ng-checked="(sortColumn == 'expectedHashDigest')" - ng-click="sortResultsBy('expectedHashDigest')"> - expected image + {{imageSets[0][constants.KEY__IMAGESETS__DESCRIPTION]}} </th> <th width="{{imageSize}}"> - <input type="radio" - name="sortColumnRadio" - value="actualHashDigest" - ng-checked="(sortColumn == 'actualHashDigest')" - ng-click="sortResultsBy('actualHashDigest')"> - actual image + {{imageSets[1][constants.KEY__IMAGESETS__DESCRIPTION]}} </th> <th width="{{imageSize}}"> <input type="radio" name="sortColumnRadio" value="percentDifferingPixels" - ng-checked="(sortColumn == 'percentDifferingPixels')" - ng-click="sortResultsBy('percentDifferingPixels')"> + ng-checked="(sortColumnKey == constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS)" + ng-click="sortResultsBy(constants.KEY__DIFFERENCE_DATA, constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS)"> differing pixels in white </th> <th width="{{imageSize}}"> <input type="radio" name="sortColumnRadio" value="weightedDiffMeasure" - ng-checked="(sortColumn == 'weightedDiffMeasure')" - ng-click="sortResultsBy('weightedDiffMeasure')"> + ng-checked="(sortColumnKey == constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF)" + ng-click="sortResultsBy(constants.KEY__DIFFERENCE_DATA, constants.KEY__DIFFERENCE_DATA__WEIGHTED_DIFF)"> perceptual difference <br> <input type="range" ng-model="pixelDiffBgColorBrightness" @@ -272,18 +262,18 @@ min="0" max="255"/> </th> <th> - <!-- item-selection checkbox column --> + <!-- imagepair-selection checkbox column --> </th> </tr> - <tr ng-repeat="result in limitedTestData" ng-controller="ImageController"> + <tr ng-repeat="imagePair in limitedImagePairs" ng-controller="ImageController"> <td> - {{result.resultType}} + {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__RESULT_TYPE]}} <br> <button class="show-only-button" ng-hide="viewingTab != defaultTab" - ng-click="showOnlyResultType(result.resultType)" - title="show only results of type '{{result.resultType}}'"> + ng-click="showOnlyResultType(imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__RESULT_TYPE])" + title="show only results of type {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__RESULT_TYPE]}}"> show only </button> <br> @@ -295,14 +285,14 @@ show all </button> </td> - <td ng-repeat="categoryName in ['builder', 'test']"> - {{result[categoryName]}} + <td ng-repeat="categoryName in [constants.KEY__EXTRACOLUMN__BUILDER, constants.KEY__EXTRACOLUMN__TEST]"> + {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName]}} <br> <button class="show-only-button" ng-hide="viewingTab != defaultTab" - ng-disabled="result[categoryName] == categoryValueMatch[categoryName]" - ng-click="setCategoryValueMatch(categoryName, result[categoryName])" - title="show only results of {{categoryName}} '{{result[categoryName]}}'"> + ng-disabled="imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName] == categoryValueMatch[categoryName]" + ng-click="setCategoryValueMatch(categoryName, imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName])" + title="show only results of {{categoryName}} {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][categoryName]}}"> show only </button> <br> @@ -315,12 +305,12 @@ </button> </td> <td> - {{result.config}} + {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__CONFIG]}} <br> <button class="show-only-button" ng-hide="viewingTab != defaultTab" - ng-click="showOnlyConfig(result.config)" - title="show only results of config '{{result.config}}'"> + ng-click="showOnlyConfig(imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__CONFIG])" + title="show only results of config {{imagePair[constants.KEY__EXTRA_COLUMN_VALUES][constants.KEY__EXTRACOLUMN__CONFIG]}}"> show only </button> <br> @@ -333,43 +323,41 @@ </button> </td> <td> - <a ng-repeat="bug in result['bugs']" + <a ng-repeat="bug in imagePair[constants.KEY__EXPECTATIONS_DATA][constants.KEY__EXPECTATIONS__BUGS]" href="https://code.google.com/p/skia/issues/detail?id={{bug}}" target="_blank"> {{bug}} </a> </td> - <!-- expected image --> + <!-- image A --> <td valign="bottom" width="{{imageSize}}"> - <a href="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.expectedHashType}}/{{result.test}}/{{result.expectedHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="{{imageSets[0][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_A_URL]}}" target="_blank">View Image</a><br/> <img-compare type="baseline" width="{{imageSize}}" - src="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.expectedHashType}}/{{result.test}}/{{result.expectedHashDigest}}.png" /> - + src="{{imageSets[0][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_A_URL]}}" /> </td> - <!-- actual image --> + <!-- image B --> <td valign="bottom" width="{{imageSize}}"> - <a href="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.actualHashType}}/{{result.test}}/{{result.actualHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="{{imageSets[1][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_B_URL]}}" target="_blank">View Image</a><br/> <img-compare type="test" width="{{imageSize}}" - src="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.actualHashType}}/{{result.test}}/{{result.actualHashDigest}}.png" /> - + src="{{imageSets[1][constants.KEY__IMAGESETS__BASE_URL]}}/{{imagePair[constants.KEY__IMAGE_B_URL]}}" /> </td> <!-- whitediffs: every differing pixel shown in white --> <td valign="bottom" width="{{imageSize}}"> - <div ng-hide="result.expectedHashDigest == result.actualHashDigest" - title="{{result.numDifferingPixels | number:0}} of {{(100 * result.numDifferingPixels / result.percentDifferingPixels) | number:0}} pixels ({{result.percentDifferingPixels.toFixed(4)}}%) differ from expectation."> + <div ng-hide="!imagePair[constants.KEY__IS_DIFFERENT]" + title="{{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS] | number:0}} of {{(100 * imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS] / imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS]) | number:0}} pixels ({{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS].toFixed(4)}}%) differ from expectation."> - {{result.percentDifferingPixels.toFixed(4)}}% - ({{result.numDifferingPixels}}) + {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCENT_DIFF_PIXELS].toFixed(4)}}% + ({{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__NUM_DIFF_PIXELS]}}) <br/> - <a href="/static/generated-images/whitediffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="/static/generated-images/whitediffs/{{getImageDiffRelativeUrl(imagePair)}}" target="_blank">View Image</a><br/> <img-compare type="differingPixelsInWhite" width="{{imageSize}}" - src="/static/generated-images/whitediffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" /> + src="/static/generated-images/whitediffs/{{getImageDiffRelativeUrl(imagePair)}}" /> </div> - <div ng-hide="result.expectedHashDigest != result.actualHashDigest" + <div ng-hide="imagePair[constants.KEY__IS_DIFFERENT]" style="text-align:center"> –none– </div> @@ -377,23 +365,23 @@ <!-- diffs: per-channel RGB deltas --> <td valign="bottom" width="{{imageSize}}"> - <div ng-hide="result.expectedHashDigest == result.actualHashDigest" - title="Perceptual difference measure is {{result.perceptualDifference.toFixed(4)}}%. Maximum difference per channel: R={{result.maxDiffPerChannel[0]}}, G={{result.maxDiffPerChannel[1]}}, B={{result.maxDiffPerChannel[2]}}"> + <div ng-hide="!imagePair[constants.KEY__IS_DIFFERENT]" + title="Perceptual difference measure is {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF].toFixed(4)}}%. Maximum difference per channel: R={{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL][0]}}, G={{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL][1]}}, B={{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL][2]}}"> - {{result.perceptualDifference.toFixed(4)}}% - {{result.maxDiffPerChannel}} + {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__PERCEPTUAL_DIFF].toFixed(4)}}% + {{imagePair[constants.KEY__DIFFERENCE_DATA][constants.KEY__DIFFERENCE_DATA__MAX_DIFF_PER_CHANNEL]}} <br/> - <a href="/static/generated-images/diffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" target="_blank">View Image</a><br/> + <a href="/static/generated-images/diffs/{{getImageDiffRelativeUrl(imagePair)}}" target="_blank">View Image</a><br/> <img-compare ng-style="{backgroundColor: pixelDiffBgColor}" type="differencePerPixel" width="{{imageSize}}" - src="/static/generated-images/diffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png" + src="/static/generated-images/diffs/{{getImageDiffRelativeUrl(imagePair)}}" ng-mousedown="MagnifyDraw($event, true)" ng-mousemove="MagnifyDraw($event, false)" ng-mouseup="MagnifyEnd($event)" ng-mouseleave="MagnifyEnd($event)" /> </div> - <div ng-hide="result.expectedHashDigest != result.actualHashDigest" + <div ng-hide="imagePair[constants.KEY__IS_DIFFERENT]" style="text-align:center"> –none– </div> @@ -402,23 +390,15 @@ <td> <input type="checkbox" name="rowSelect" - value="{{result.index}}" - ng-checked="isValueInArray(result.index, selectedItems)" - ng-click="toggleValueInArray(result.index, selectedItems)"> + value="{{imagePair.index}}" + ng-checked="isValueInArray(imagePair.index, selectedImagePairs)" + ng-click="toggleValueInArray(imagePair.index, selectedImagePairs)"> </tr> - </table> <!-- results --> - </td></tr></table> <!-- table holding results header + results table --> + </table> <!-- imagePairs --> + </td></tr></table> <!-- table holding results header + imagePairs table --> </div><!-- main display area of selected tab --> </div><!-- everything: hide until data is loaded --> - <!-- TODO(epoger): Can we get the base URLs (commondatastorage and - issues list) from - https://skia.googlesource.com/buildbot/+/master/site_config/global_variables.json ? - I tried importing the - http://skia.googlecode.com/svn/buildbot/skia_tools.js script and using - that to do so, but I got Access-Control-Allow-Origin errors. - --> - </body> </html> |