Index: gm/rebaseline_server/static/loader.js |
diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js |
deleted file mode 100644 |
index bfc639e33b875bdf0476f24f7cd8a0a96c8e736d..0000000000000000000000000000000000000000 |
--- a/gm/rebaseline_server/static/loader.js |
+++ /dev/null |
@@ -1,1035 +0,0 @@ |
-/* |
- * Loader: |
- * Reads GM result reports written out by results.py, and imports |
- * them into $scope.extraColumnHeaders and $scope.imagePairs . |
- */ |
-var Loader = angular.module( |
- 'Loader', |
- ['ConstantsModule'] |
-); |
- |
-Loader.directive( |
- 'resultsUpdatedCallbackDirective', |
- ['$timeout', |
- function($timeout) { |
- return function(scope, element, attrs) { |
- if (scope.$last) { |
- $timeout(function() { |
- scope.resultsUpdatedCallback(); |
- }); |
- } |
- }; |
- } |
- ] |
-); |
- |
-// 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( |
- 'removeHiddenImagePairs', |
- function(constants) { |
- return function(unfilteredImagePairs, filterableColumnNames, showingColumnValues, |
- viewingTab) { |
- var filteredImagePairs = []; |
- for (var i = 0; i < unfilteredImagePairs.length; i++) { |
- var imagePair = unfilteredImagePairs[i]; |
- var extraColumnValues = imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]; |
- var allColumnValuesAreVisible = true; |
- // Loop over all columns, and if any of them contain values not found in |
- // showingColumnValues[columnName], don't include this imagePair. |
- // |
- // We use this same filtering mechanism regardless of whether each column |
- // has USE_FREEFORM_FILTER set or not; if that flag is set, then we will |
- // have already used the freeform text entry block to populate |
- // showingColumnValues[columnName]. |
- for (var j = 0; j < filterableColumnNames.length; j++) { |
- var columnName = filterableColumnNames[j]; |
- var columnValue = extraColumnValues[columnName]; |
- if (!showingColumnValues[columnName][columnValue]) { |
- allColumnValuesAreVisible = false; |
- break; |
- } |
- } |
- if (allColumnValuesAreVisible && (viewingTab == imagePair.tab)) { |
- filteredImagePairs.push(imagePair); |
- } |
- } |
- return filteredImagePairs; |
- }; |
- } |
-); |
- |
-/** |
- * Limit the input imagePairs to some max number, and merge identical rows |
- * (adjacent rows which have the same (imageA, imageB) pair). |
- * |
- * @param unfilteredImagePairs imagePairs to filter |
- * @param maxPairs maximum number of pairs to output, or <0 for no limit |
- * @param mergeIdenticalRows if true, merge identical rows by setting |
- * ROWSPAN>1 on the first merged row, and ROWSPAN=0 for the rest |
- */ |
-Loader.filter( |
- 'mergeAndLimit', |
- function(constants) { |
- return function(unfilteredImagePairs, maxPairs, mergeIdenticalRows) { |
- var numPairs = unfilteredImagePairs.length; |
- if ((maxPairs > 0) && (maxPairs < numPairs)) { |
- numPairs = maxPairs; |
- } |
- var filteredImagePairs = []; |
- if (!mergeIdenticalRows || (numPairs == 1)) { |
- // Take a shortcut if we're not merging identical rows. |
- // We still need to set ROWSPAN to 1 for each row, for the HTML viewer. |
- for (var i = numPairs-1; i >= 0; i--) { |
- var imagePair = unfilteredImagePairs[i]; |
- imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; |
- filteredImagePairs[i] = imagePair; |
- } |
- } else if (numPairs > 1) { |
- // General case--there are at least 2 rows, so we may need to merge some. |
- // Work from the bottom up, so we can keep a running total of how many |
- // rows should be merged, and set ROWSPAN of the top row accordingly. |
- var imagePair = unfilteredImagePairs[numPairs-1]; |
- var nextRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]; |
- var nextRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; |
- imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; |
- filteredImagePairs[numPairs-1] = imagePair; |
- for (var i = numPairs-2; i >= 0; i--) { |
- imagePair = unfilteredImagePairs[i]; |
- var thisRowImageAUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]; |
- var thisRowImageBUrl = imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; |
- if ((thisRowImageAUrl == nextRowImageAUrl) && |
- (thisRowImageBUrl == nextRowImageBUrl)) { |
- imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = |
- filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] + 1; |
- filteredImagePairs[i+1][constants.KEY__IMAGEPAIRS__ROWSPAN] = 0; |
- } else { |
- imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] = 1; |
- nextRowImageAUrl = thisRowImageAUrl; |
- nextRowImageBUrl = thisRowImageBUrl; |
- } |
- filteredImagePairs[i] = imagePair; |
- } |
- } else { |
- // No results. |
- } |
- return filteredImagePairs; |
- }; |
- } |
-); |
- |
- |
-Loader.controller( |
- 'Loader.Controller', |
- function($scope, $http, $filter, $location, $log, $timeout, constants) { |
- $scope.readyToDisplay = false; |
- $scope.constants = constants; |
- $scope.windowTitle = "Loading GM Results..."; |
- $scope.resultsToLoad = $location.search().resultsToLoad; |
- $scope.loadingMessage = "please wait..."; |
- |
- var currSortAsc = true; |
- |
- |
- /** |
- * On initial page load, load a full dictionary of results. |
- * Once the dictionary is loaded, unhide the page elements so they can |
- * render the data. |
- */ |
- $http.get($scope.resultsToLoad).success( |
- function(data, status, header, config) { |
- var dataHeader = data[constants.KEY__ROOT__HEADER]; |
- if (dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] != |
- constants.VALUE__HEADER__SCHEMA_VERSION) { |
- $scope.loadingMessage = "ERROR: Got JSON file with schema version " |
- + dataHeader[constants.KEY__HEADER__SCHEMA_VERSION] |
- + " but expected schema version " |
- + constants.VALUE__HEADER__SCHEMA_VERSION; |
- } else if (dataHeader[constants.KEY__HEADER__IS_STILL_LOADING]) { |
- // Apply the server's requested reload delay to local time, |
- // so we will wait the right number of seconds regardless of clock |
- // skew between client and server. |
- var reloadDelayInSeconds = |
- dataHeader[constants.KEY__HEADER__TIME_NEXT_UPDATE_AVAILABLE] - |
- dataHeader[constants.KEY__HEADER__TIME_UPDATED]; |
- var timeNow = new Date().getTime(); |
- var timeToReload = timeNow + reloadDelayInSeconds * 1000; |
- $scope.loadingMessage = |
- "server is still loading results; will retry at " + |
- $scope.localTimeString(timeToReload / 1000); |
- $timeout( |
- function(){location.reload();}, |
- timeToReload - timeNow); |
- } else { |
- $scope.loadingMessage = "processing data, please wait..."; |
- |
- $scope.header = dataHeader; |
- $scope.extraColumnHeaders = data[constants.KEY__ROOT__EXTRACOLUMNHEADERS]; |
- $scope.orderedColumnNames = data[constants.KEY__ROOT__EXTRACOLUMNORDER]; |
- $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS]; |
- $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS]; |
- |
- // set the default sort column and make it ascending. |
- $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES; |
- $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF; |
- currSortAsc = true; |
- |
- $scope.showSubmitAdvancedSettings = false; |
- $scope.submitAdvancedSettings = {}; |
- $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 |
- // test). This may vary, depending on isEditable. |
- $scope.tabs = [ |
- 'Unfiled', 'Hidden' |
- ]; |
- if (dataHeader[constants.KEY__HEADER__IS_EDITABLE]) { |
- $scope.tabs = $scope.tabs.concat( |
- ['Pending Approval']); |
- } |
- $scope.defaultTab = $scope.tabs[0]; |
- $scope.viewingTab = $scope.defaultTab; |
- |
- // Track the number of results on each tab. |
- $scope.numResultsPerTab = {}; |
- for (var i = 0; i < $scope.tabs.length; i++) { |
- $scope.numResultsPerTab[$scope.tabs[i]] = 0; |
- } |
- $scope.numResultsPerTab[$scope.defaultTab] = $scope.imagePairs.length; |
- |
- // Add index and tab fields to all records. |
- 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.selectedImagePairs = []; |
- |
- // Set up filters. |
- // |
- // filterableColumnNames is a list of all column names we can filter on. |
- // allColumnValues[columnName] is a list of all known values |
- // for a given column. |
- // showingColumnValues[columnName] is a set indicating which values |
- // in a given column would cause us to show a row, rather than hiding it. |
- // |
- // columnStringMatch[columnName] is a string used as a pattern to generate |
- // showingColumnValues[columnName] for columns we filter using free-form text. |
- // It is ignored for any columns with USE_FREEFORM_FILTER == false. |
- $scope.filterableColumnNames = []; |
- $scope.allColumnValues = {}; |
- $scope.showingColumnValues = {}; |
- $scope.columnStringMatch = {}; |
- |
- angular.forEach( |
- Object.keys($scope.extraColumnHeaders), |
- function(columnName) { |
- var columnHeader = $scope.extraColumnHeaders[columnName]; |
- if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__IS_FILTERABLE]) { |
- $scope.filterableColumnNames.push(columnName); |
- $scope.allColumnValues[columnName] = $scope.columnSliceOf2DArray( |
- columnHeader[constants.KEY__EXTRACOLUMNHEADERS__VALUES_AND_COUNTS], 0); |
- $scope.showingColumnValues[columnName] = {}; |
- $scope.toggleValuesInSet($scope.allColumnValues[columnName], |
- $scope.showingColumnValues[columnName]); |
- $scope.columnStringMatch[columnName] = ""; |
- } |
- } |
- ); |
- |
- // TODO(epoger): Special handling for RESULT_TYPE column: |
- // by default, show only KEY__RESULT_TYPE__FAILED results |
- $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE] = {}; |
- $scope.showingColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE][ |
- constants.KEY__RESULT_TYPE__FAILED] = true; |
- |
- // Set up mapping for URL parameters. |
- // parameter name -> copier object to load/save parameter value |
- $scope.queryParameters.map = { |
- 'resultsToLoad': $scope.queryParameters.copiers.simple, |
- 'displayLimitPending': $scope.queryParameters.copiers.simple, |
- 'showThumbnailsPending': $scope.queryParameters.copiers.simple, |
- 'mergeIdenticalRowsPending': $scope.queryParameters.copiers.simple, |
- 'imageSizePending': $scope.queryParameters.copiers.simple, |
- 'sortColumnSubdict': $scope.queryParameters.copiers.simple, |
- 'sortColumnKey': $scope.queryParameters.copiers.simple, |
- }; |
- // Some parameters are handled differently based on whether they USE_FREEFORM_FILTER. |
- angular.forEach( |
- $scope.filterableColumnNames, |
- function(columnName) { |
- if ($scope.extraColumnHeaders[columnName] |
- [constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) { |
- $scope.queryParameters.map[columnName] = |
- $scope.queryParameters.copiers.columnStringMatch; |
- } else { |
- $scope.queryParameters.map[columnName] = |
- $scope.queryParameters.copiers.showingColumnValuesSet; |
- } |
- } |
- ); |
- |
- // If any defaults were overridden in the URL, get them now. |
- $scope.queryParameters.load(); |
- |
- // Any image URLs which are relative should be relative to the JSON |
- // file's source directory; absolute URLs should be left alone. |
- var baseUrlKey = constants.KEY__IMAGESETS__FIELD__BASE_URL; |
- angular.forEach( |
- $scope.imageSets, |
- function(imageSet) { |
- var baseUrl = imageSet[baseUrlKey]; |
- if ((baseUrl.substring(0, 1) != '/') && |
- (baseUrl.indexOf('://') == -1)) { |
- imageSet[baseUrlKey] = $scope.resultsToLoad + '/../' + baseUrl; |
- } |
- } |
- ); |
- |
- $scope.readyToDisplay = true; |
- $scope.updateResults(); |
- $scope.loadingMessage = ""; |
- $scope.windowTitle = "Current GM Results"; |
- |
- $timeout( function() { |
- make_results_header_sticky(); |
- }); |
- } |
- } |
- ).error( |
- function(data, status, header, config) { |
- $scope.loadingMessage = "FAILED to load."; |
- $scope.windowTitle = "Failed to Load GM Results"; |
- } |
- ); |
- |
- |
- // |
- // Select/Clear/Toggle all tests. |
- // |
- |
- /** |
- * Select all currently showing tests. |
- */ |
- $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); |
- } |
- } |
- }; |
- |
- /** |
- * Deselect all currently showing tests. |
- */ |
- $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); |
- } |
- } |
- }; |
- |
- /** |
- * Toggle selection of all currently showing tests. |
- */ |
- $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); |
- } |
- }; |
- |
- /** |
- * Toggle selection state of a subset of the currently showing tests. |
- * |
- * @param startIndex index within $scope.limitedImagePairs of the first |
- * test to toggle selection state of |
- * @param num number of tests (in a contiguous block) to toggle |
- */ |
- $scope.toggleSomeImagePairs = function(startIndex, num) { |
- var numImagePairsShowing = $scope.limitedImagePairs.length; |
- for (var i = startIndex; i < startIndex + num; i++) { |
- var index = $scope.limitedImagePairs[i].index; |
- $scope.toggleValueInArray(index, $scope.selectedImagePairs); |
- } |
- }; |
- |
- |
- // |
- // Tab operations. |
- // |
- |
- /** |
- * Change the selected tab. |
- * |
- * @param tab (string): name of the tab to select |
- */ |
- $scope.setViewingTab = function(tab) { |
- $scope.viewingTab = tab; |
- $scope.updateResults(); |
- }; |
- |
- /** |
- * 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.moveSelectedImagePairsToTab = function(newTab) { |
- $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab); |
- $scope.selectedImagePairs = []; |
- $scope.updateResults(); |
- }; |
- |
- /** |
- * Move a subset of $scope.imagePairs to a different tab. |
- * |
- * @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.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] += numImagePairs; |
- }; |
- |
- |
- // |
- // $scope.queryParameters: |
- // Transfer parameter values between $scope and the URL query string. |
- // |
- $scope.queryParameters = {}; |
- |
- // load and save functions for parameters of each type |
- // (load a parameter value into $scope from nameValuePairs, |
- // save a parameter value from $scope into nameValuePairs) |
- $scope.queryParameters.copiers = { |
- 'simple': { |
- 'load': function(nameValuePairs, name) { |
- var value = nameValuePairs[name]; |
- if (value) { |
- $scope[name] = value; |
- } |
- }, |
- 'save': function(nameValuePairs, name) { |
- nameValuePairs[name] = $scope[name]; |
- } |
- }, |
- |
- 'columnStringMatch': { |
- 'load': function(nameValuePairs, name) { |
- var value = nameValuePairs[name]; |
- if (value) { |
- $scope.columnStringMatch[name] = value; |
- } |
- }, |
- 'save': function(nameValuePairs, name) { |
- nameValuePairs[name] = $scope.columnStringMatch[name]; |
- } |
- }, |
- |
- 'showingColumnValuesSet': { |
- 'load': function(nameValuePairs, name) { |
- var value = nameValuePairs[name]; |
- if (value) { |
- var valueArray = value.split(','); |
- $scope.showingColumnValues[name] = {}; |
- $scope.toggleValuesInSet(valueArray, $scope.showingColumnValues[name]); |
- } |
- }, |
- 'save': function(nameValuePairs, name) { |
- nameValuePairs[name] = Object.keys($scope.showingColumnValues[name]).join(','); |
- } |
- }, |
- |
- }; |
- |
- // Loads all parameters into $scope from the URL query string; |
- // any which are not found within the URL will keep their current value. |
- $scope.queryParameters.load = function() { |
- var nameValuePairs = $location.search(); |
- |
- // If urlSchemaVersion is not specified, we assume the current version. |
- var urlSchemaVersion = constants.URL_VALUE__SCHEMA_VERSION__CURRENT; |
- if (constants.URL_KEY__SCHEMA_VERSION in nameValuePairs) { |
- urlSchemaVersion = nameValuePairs[constants.URL_KEY__SCHEMA_VERSION]; |
- } else if ('hiddenResultTypes' in nameValuePairs) { |
- // The combination of: |
- // - absence of an explicit urlSchemaVersion, and |
- // - presence of the old 'hiddenResultTypes' field |
- // tells us that the URL is from the original urlSchemaVersion. |
- // See https://codereview.chromium.org/367173002/ |
- urlSchemaVersion = 0; |
- } |
- $scope.urlSchemaVersionLoaded = urlSchemaVersion; |
- |
- if (urlSchemaVersion != constants.URL_VALUE__SCHEMA_VERSION__CURRENT) { |
- nameValuePairs = $scope.upconvertUrlNameValuePairs(nameValuePairs, urlSchemaVersion); |
- } |
- angular.forEach($scope.queryParameters.map, |
- function(copier, paramName) { |
- copier.load(nameValuePairs, paramName); |
- } |
- ); |
- }; |
- |
- // Saves all parameters from $scope into the URL query string. |
- $scope.queryParameters.save = function() { |
- var nameValuePairs = {}; |
- nameValuePairs[constants.URL_KEY__SCHEMA_VERSION] = constants.URL_VALUE__SCHEMA_VERSION__CURRENT; |
- angular.forEach($scope.queryParameters.map, |
- function(copier, paramName) { |
- copier.save(nameValuePairs, paramName); |
- } |
- ); |
- $location.search(nameValuePairs); |
- }; |
- |
- /** |
- * Converts URL name/value pairs that were stored by a previous urlSchemaVersion |
- * to the currently needed format. |
- * |
- * @param oldNValuePairs name/value pairs found in the loaded URL |
- * @param oldUrlSchemaVersion which version of the schema was used to generate that URL |
- * |
- * @returns nameValuePairs as needed by the current URL parser |
- */ |
- $scope.upconvertUrlNameValuePairs = function(oldNameValuePairs, oldUrlSchemaVersion) { |
- var newNameValuePairs = {}; |
- angular.forEach(oldNameValuePairs, |
- function(value, name) { |
- if (oldUrlSchemaVersion < 1) { |
- if ('hiddenConfigs' == name) { |
- name = 'config'; |
- var valueSet = {}; |
- $scope.toggleValuesInSet(value.split(','), valueSet); |
- $scope.toggleValuesInSet( |
- $scope.allColumnValues[constants.KEY__EXTRACOLUMNS__CONFIG], |
- valueSet); |
- value = Object.keys(valueSet).join(','); |
- } else if ('hiddenResultTypes' == name) { |
- name = 'resultType'; |
- var valueSet = {}; |
- $scope.toggleValuesInSet(value.split(','), valueSet); |
- $scope.toggleValuesInSet( |
- $scope.allColumnValues[constants.KEY__EXTRACOLUMNS__RESULT_TYPE], |
- valueSet); |
- value = Object.keys(valueSet).join(','); |
- } |
- } |
- |
- newNameValuePairs[name] = value; |
- } |
- ); |
- return newNameValuePairs; |
- } |
- |
- |
- // |
- // updateResults() and friends. |
- // |
- |
- /** |
- * Set $scope.areUpdatesPending (to enable/disable the Update Results |
- * button). |
- * |
- * TODO(epoger): We could reduce the amount of code by just setting the |
- * variable directly (from, e.g., a button's ng-click handler). But when |
- * I tried that, the HTML elements depending on the variable did not get |
- * updated. |
- * It turns out that this is due to variable scoping within an ng-repeat |
- * element; see http://stackoverflow.com/questions/15388344/behavior-of-assignment-expression-invoked-by-ng-click-within-ng-repeat |
- * |
- * @param val boolean value to set $scope.areUpdatesPending to |
- */ |
- $scope.setUpdatesPending = function(val) { |
- $scope.areUpdatesPending = val; |
- } |
- |
- /** |
- * Update the displayed results, based on filters/settings, |
- * and call $scope.queryParameters.save() so that the new filter results |
- * can be bookmarked. |
- */ |
- $scope.updateResults = function() { |
- $scope.renderStartTime = window.performance.now(); |
- $log.debug("renderStartTime: " + $scope.renderStartTime); |
- $scope.displayLimit = $scope.displayLimitPending; |
- $scope.mergeIdenticalRows = $scope.mergeIdenticalRowsPending; |
- |
- // For each USE_FREEFORM_FILTER column, populate showingColumnValues. |
- // This is more efficient than applying the freeform filter within the |
- // tight loop in removeHiddenImagePairs. |
- angular.forEach( |
- $scope.filterableColumnNames, |
- function(columnName) { |
- var columnHeader = $scope.extraColumnHeaders[columnName]; |
- if (columnHeader[constants.KEY__EXTRACOLUMNHEADERS__USE_FREEFORM_FILTER]) { |
- var columnStringMatch = $scope.columnStringMatch[columnName]; |
- var showingColumnValues = {}; |
- angular.forEach( |
- $scope.allColumnValues[columnName], |
- function(columnValue) { |
- if (-1 != columnValue.indexOf(columnStringMatch)) { |
- showingColumnValues[columnValue] = true; |
- } |
- } |
- ); |
- $scope.showingColumnValues[columnName] = showingColumnValues; |
- } |
- } |
- ); |
- |
- // TODO(epoger): Every time we apply a filter, AngularJS creates |
- // another copy of the array. Is there a way we can filter out |
- // the imagePairs as they are displayed, rather than storing multiple |
- // array copies? (For better performance.) |
- if ($scope.viewingTab == $scope.defaultTab) { |
- var doReverse = !currSortAsc; |
- |
- $scope.filteredImagePairs = |
- $filter("orderBy")( |
- $filter("removeHiddenImagePairs")( |
- $scope.imagePairs, |
- $scope.filterableColumnNames, |
- $scope.showingColumnValues, |
- $scope.viewingTab |
- ), |
- // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue], |
- $scope.getSortColumnValue, |
- doReverse); |
- $scope.limitedImagePairs = $filter("mergeAndLimit")( |
- $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdenticalRows); |
- } else { |
- $scope.filteredImagePairs = |
- $filter("orderBy")( |
- $filter("filter")( |
- $scope.imagePairs, |
- {tab: $scope.viewingTab}, |
- true |
- ), |
- // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]); |
- $scope.getSortColumnValue); |
- $scope.limitedImagePairs = $filter("mergeAndLimit")( |
- $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows); |
- } |
- $scope.showThumbnails = $scope.showThumbnailsPending; |
- $scope.imageSize = $scope.imageSizePending; |
- $scope.setUpdatesPending(false); |
- $scope.queryParameters.save(); |
- } |
- |
- /** |
- * This function is called when the results have been completely rendered |
- * after updateResults(). |
- */ |
- $scope.resultsUpdatedCallback = function() { |
- $scope.renderEndTime = window.performance.now(); |
- $log.debug("renderEndTime: " + $scope.renderEndTime); |
- }; |
- |
- /** |
- * Re-sort the displayed results. |
- * |
- * @param subdict (string): which KEY__IMAGEPAIRS__* subdictionary |
- * the sort column key is within, or 'none' if the sort column |
- * key is one of KEY__IMAGEPAIRS__* |
- * @param key (string): sort by value associated with this key in subdict |
- */ |
- $scope.sortResultsBy = function(subdict, key) { |
- // if we are already sorting by this column then toggle between asc/desc |
- if ((subdict === $scope.sortColumnSubdict) && ($scope.sortColumnKey === key)) { |
- currSortAsc = !currSortAsc; |
- } else { |
- $scope.sortColumnSubdict = subdict; |
- $scope.sortColumnKey = key; |
- currSortAsc = true; |
- } |
- $scope.updateResults(); |
- }; |
- |
- /** |
- * Returns ASC or DESC (from constants) if currently the data |
- * is sorted by the provided column. |
- * |
- * @param colName: name of the column for which we need to get the class. |
- */ |
- |
- $scope.sortedByColumnsCls = function (colName) { |
- if ($scope.sortColumnKey !== colName) { |
- return ''; |
- } |
- |
- var result = (currSortAsc) ? constants.ASC : constants.DESC; |
- console.log("sort class:", result); |
- return result; |
- }; |
- |
- /** |
- * 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 if ($scope.sortColumnKey in imagePair) { |
- return imagePair[$scope.sortColumnKey]; |
- } else { |
- return undefined; |
- } |
- }; |
- |
- /** |
- * For a particular ImagePair, return the value we use for the |
- * second-order sort (tiebreaker when multiple rows have |
- * the same getSortColumnValue()). |
- * |
- * We join the imageA and imageB urls for this value, so that we merge |
- * adjacent rows as much as possible. |
- * |
- * @param imagePair: imagePair to get a column value out of. |
- */ |
- $scope.getSecondOrderSortValue = function(imagePair) { |
- return imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" + |
- imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; |
- }; |
- |
- /** |
- * Set $scope.columnStringMatch[name] = value, and update results. |
- * |
- * @param name |
- * @param value |
- */ |
- $scope.setColumnStringMatch = function(name, value) { |
- $scope.columnStringMatch[name] = value; |
- $scope.updateResults(); |
- }; |
- |
- /** |
- * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName] |
- * so that ONLY entries with this columnValue are showing, and update the visible results. |
- * (We update both of those, so we cover both freeform and checkbox filtered columns.) |
- * |
- * @param columnName |
- * @param columnValue |
- */ |
- $scope.showOnlyColumnValue = function(columnName, columnValue) { |
- $scope.columnStringMatch[columnName] = columnValue; |
- $scope.showingColumnValues[columnName] = {}; |
- $scope.toggleValueInSet(columnValue, $scope.showingColumnValues[columnName]); |
- $scope.updateResults(); |
- }; |
- |
- /** |
- * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName] |
- * so that ALL entries are showing, and update the visible results. |
- * (We update both of those, so we cover both freeform and checkbox filtered columns.) |
- * |
- * @param columnName |
- */ |
- $scope.showAllColumnValues = function(columnName) { |
- $scope.columnStringMatch[columnName] = ""; |
- $scope.showingColumnValues[columnName] = {}; |
- $scope.toggleValuesInSet($scope.allColumnValues[columnName], |
- $scope.showingColumnValues[columnName]); |
- $scope.updateResults(); |
- }; |
- |
- |
- // |
- // Operations for sending info back to the server. |
- // |
- |
- /** |
- * Tell the server that the actual results of these particular tests |
- * are acceptable. |
- * |
- * 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(imagePairsSubset) { |
- $scope.submitPending = true; |
- |
- // Convert bug text field to null or 1-item array. |
- var bugs = null; |
- var bugNumber = parseInt($scope.submitAdvancedSettings['bug']); |
- if (!isNaN(bugNumber)) { |
- bugs = [bugNumber]; |
- } |
- |
- // TODO(epoger): This is a suboptimal way to prevent users from |
- // rebaselining failures in alternative renderModes, but it does work. |
- // For a better solution, see |
- // https://code.google.com/p/skia/issues/detail?id=1748 ('gm: add new |
- // result type, RenderModeMismatch') |
- var encounteredComparisonConfig = false; |
- |
- var updatedExpectations = []; |
- for (var i = 0; i < imagePairsSubset.length; i++) { |
- var imagePair = imagePairsSubset[i]; |
- var updatedExpectation = {}; |
- updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = |
- imagePair[constants.KEY__IMAGEPAIRS__EXPECTATIONS]; |
- updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] = |
- imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS]; |
- // IMAGE_B_URL contains the actual image (which is now the expectation) |
- updatedExpectation[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] = |
- imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]; |
- if (0 == updatedExpectation[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS] |
- [constants.KEY__EXTRACOLUMNS__CONFIG] |
- .indexOf('comparison-')) { |
- encounteredComparisonConfig = true; |
- } |
- |
- // Advanced settings... |
- if (null == updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS]) { |
- updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] = {}; |
- } |
- updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] |
- [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) |
- updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] |
- [constants.KEY__EXPECTATIONS__IGNOREFAILURE] = true; |
- } |
- updatedExpectation[constants.KEY__IMAGEPAIRS__EXPECTATIONS] |
- [constants.KEY__EXPECTATIONS__BUGS] = bugs; |
- |
- updatedExpectations.push(updatedExpectation); |
- } |
- if (encounteredComparisonConfig) { |
- alert("Approval failed -- you cannot approve results with config " + |
- "type comparison-*"); |
- $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: modificationData |
- }).success(function(data, status, headers, config) { |
- var imagePairIndicesToMove = []; |
- for (var i = 0; i < imagePairsSubset.length; i++) { |
- imagePairIndicesToMove.push(imagePairsSubset[i].index); |
- } |
- $scope.moveImagePairsToTab(imagePairIndicesToMove, |
- "HackToMakeSureThisImagePairDisappears"); |
- $scope.updateResults(); |
- alert("New baselines submitted successfully!\n\n" + |
- "You still need to commit the updated expectations files on " + |
- "the server side to the Skia repo.\n\n" + |
- "When you click OK, your web UI will reload; after that " + |
- "completes, you will see the updated data (once the server has " + |
- "finished loading the update results into memory!) and you can " + |
- "submit more baselines if you want."); |
- // I don't know why, but if I just call reload() here it doesn't work. |
- // Making a timer call it fixes the problem. |
- $timeout(function(){location.reload();}, 1); |
- }).error(function(data, status, headers, config) { |
- alert("There was an error submitting your baselines.\n\n" + |
- "Please see server-side log for details."); |
- $scope.submitPending = false; |
- }); |
- }; |
- |
- |
- // |
- // Operations we use to mimic Set semantics, in such a way that |
- // checking for presence within the Set is as fast as possible. |
- // But getting a list of all values within the Set is not necessarily |
- // possible. |
- // TODO(epoger): move into a separate .js file? |
- // |
- |
- /** |
- * Returns the number of values present within set "set". |
- * |
- * @param set an Object which we use to mimic set semantics |
- */ |
- $scope.setSize = function(set) { |
- return Object.keys(set).length; |
- }; |
- |
- /** |
- * Returns true if value "value" is present within set "set". |
- * |
- * @param value a value of any type |
- * @param set an Object which we use to mimic set semantics |
- * (this should make isValueInSet faster than if we used an Array) |
- */ |
- $scope.isValueInSet = function(value, set) { |
- return (true == set[value]); |
- }; |
- |
- /** |
- * If value "value" is already in set "set", remove it; otherwise, add it. |
- * |
- * @param value a value of any type |
- * @param set an Object which we use to mimic set semantics |
- */ |
- $scope.toggleValueInSet = function(value, set) { |
- if (true == set[value]) { |
- delete set[value]; |
- } else { |
- set[value] = true; |
- } |
- }; |
- |
- /** |
- * For each value in valueArray, call toggleValueInSet(value, set). |
- * |
- * @param valueArray |
- * @param set |
- */ |
- $scope.toggleValuesInSet = function(valueArray, set) { |
- var arrayLength = valueArray.length; |
- for (var i = 0; i < arrayLength; i++) { |
- $scope.toggleValueInSet(valueArray[i], set); |
- } |
- }; |
- |
- |
- // |
- // Array operations; similar to our Set operations, but operate on a |
- // Javascript Array so we *can* easily get a list of all values in the Set. |
- // TODO(epoger): move into a separate .js file? |
- // |
- |
- /** |
- * Returns true if value "value" is present within array "array". |
- * |
- * @param value a value of any type |
- * @param array a Javascript Array |
- */ |
- $scope.isValueInArray = function(value, array) { |
- return (-1 != array.indexOf(value)); |
- }; |
- |
- /** |
- * If value "value" is already in array "array", remove it; otherwise, |
- * add it. |
- * |
- * @param value a value of any type |
- * @param array a Javascript Array |
- */ |
- $scope.toggleValueInArray = function(value, array) { |
- var i = array.indexOf(value); |
- if (-1 == i) { |
- array.push(value); |
- } else { |
- array.splice(i, 1); |
- } |
- }; |
- |
- |
- // |
- // Miscellaneous utility functions. |
- // TODO(epoger): move into a separate .js file? |
- // |
- |
- /** |
- * Returns a single "column slice" of a 2D array. |
- * |
- * For example, if array is: |
- * [[A0, A1], |
- * [B0, B1], |
- * [C0, C1]] |
- * and index is 0, this this will return: |
- * [A0, B0, C0] |
- * |
- * @param array a Javascript Array |
- * @param column (numeric): index within each row array |
- */ |
- $scope.columnSliceOf2DArray = function(array, column) { |
- var slice = []; |
- var numRows = array.length; |
- for (var row = 0; row < numRows; row++) { |
- slice.push(array[row][column]); |
- } |
- return slice; |
- }; |
- |
- /** |
- * Returns a human-readable (in local time zone) time string for a |
- * particular moment in time. |
- * |
- * @param secondsPastEpoch (numeric): seconds past epoch in UTC |
- */ |
- $scope.localTimeString = function(secondsPastEpoch) { |
- var d = new Date(secondsPastEpoch * 1000); |
- return d.toString(); |
- }; |
- |
- /** |
- * Returns a hex color string (such as "#aabbcc") for the given RGB values. |
- * |
- * @param r (numeric): red channel value, 0-255 |
- * @param g (numeric): green channel value, 0-255 |
- * @param b (numeric): blue channel value, 0-255 |
- */ |
- $scope.hexColorString = function(r, g, b) { |
- var rString = r.toString(16); |
- if (r < 16) { |
- rString = "0" + rString; |
- } |
- var gString = g.toString(16); |
- if (g < 16) { |
- gString = "0" + gString; |
- } |
- var bString = b.toString(16); |
- if (b < 16) { |
- bString = "0" + bString; |
- } |
- return '#' + rString + gString + bString; |
- }; |
- |
- /** |
- * Returns a hex color string (such as "#aabbcc") for the given brightness. |
- * |
- * @param brightnessString (string): 0-255, 0 is completely black |
- * |
- * TODO(epoger): It might be nice to tint the color when it's not completely |
- * black or completely white. |
- */ |
- $scope.brightnessStringToHexColor = function(brightnessString) { |
- var v = parseInt(brightnessString); |
- return $scope.hexColorString(v, v, v); |
- }; |
- |
- } |
-); |