diff options
author | epoger <epoger@google.com> | 2014-06-11 14:01:35 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-06-11 14:01:35 -0700 |
commit | 6b28120e4c5e05a68e4947a3558f8d6615720a6e (patch) | |
tree | ce2df498f05d8e4ab74f5e291239c542c2b925dc /gm | |
parent | 66c9582d1bed717d72c9d3f789f761cb08e28264 (diff) |
rebaseline_server: merge identical results (across multiple builders/configs) into a single row
BUG=skia:2534
R=jcgregorio@google.com
Author: epoger@google.com
Review URL: https://codereview.chromium.org/326013002
Diffstat (limited to 'gm')
-rw-r--r-- | gm/rebaseline_server/static/constants.js | 3 | ||||
-rw-r--r-- | gm/rebaseline_server/static/loader.js | 108 | ||||
-rw-r--r-- | gm/rebaseline_server/static/view.css | 8 | ||||
-rw-r--r-- | gm/rebaseline_server/static/view.html | 45 |
4 files changed, 145 insertions, 19 deletions
diff --git a/gm/rebaseline_server/static/constants.js b/gm/rebaseline_server/static/constants.js index ecca98eae8..8e71311266 100644 --- a/gm/rebaseline_server/static/constants.js +++ b/gm/rebaseline_server/static/constants.js @@ -74,5 +74,8 @@ module.constant('constants', (function() { KEY__EDITS__MODIFICATIONS: 'modifications', KEY__EDITS__OLD_RESULTS_HASH: 'oldResultsHash', KEY__EDITS__OLD_RESULTS_TYPE: 'oldResultsType', + + // These are just used on the client side, no need to sync with server code. + KEY__IMAGEPAIRS__ROWSPAN: 'rowspan', } })()) diff --git a/gm/rebaseline_server/static/loader.js b/gm/rebaseline_server/static/loader.js index e05f1c8f86..296689bde2 100644 --- a/gm/rebaseline_server/static/loader.js +++ b/gm/rebaseline_server/static/loader.js @@ -56,6 +56,65 @@ Loader.filter( } ); +/** + * 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', @@ -234,6 +293,21 @@ Loader.controller( } } + /** + * 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. @@ -335,6 +409,7 @@ Loader.controller( '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, @@ -400,6 +475,7 @@ Loader.controller( $scope.renderStartTime = window.performance.now(); $log.debug("renderStartTime: " + $scope.renderStartTime); $scope.displayLimit = $scope.displayLimitPending; + $scope.mergeIdenticalRows = $scope.mergeIdenticalRowsPending; // 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 @@ -425,9 +501,10 @@ Loader.controller( $scope.categoryValueMatch.test, $scope.viewingTab ), - $scope.getSortColumnValue, doReverse); - $scope.limitedImagePairs = $filter("limitTo")( - $scope.filteredImagePairs, $scope.displayLimit); + [$scope.getSortColumnValue, $scope.getSecondOrderSortValue], + doReverse); + $scope.limitedImagePairs = $filter("mergeAndLimit")( + $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdenticalRows); } else { $scope.filteredImagePairs = $filter("orderBy")( @@ -436,8 +513,9 @@ Loader.controller( {tab: $scope.viewingTab}, true ), - $scope.getSortColumnValue); - $scope.limitedImagePairs = $scope.filteredImagePairs; + [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]); + $scope.limitedImagePairs = $filter("mergeAndLimit")( + $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows); } $scope.showThumbnails = $scope.showThumbnailsPending; $scope.imageSize = $scope.imageSizePending; @@ -458,7 +536,8 @@ Loader.controller( * Re-sort the displayed results. * * @param subdict (string): which KEY__IMAGEPAIRS__* subdictionary - * the sort column key is within + * 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) { @@ -477,12 +556,29 @@ Loader.controller( $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.categoryValueMatch[name] = value, and update results. * * @param name diff --git a/gm/rebaseline_server/static/view.css b/gm/rebaseline_server/static/view.css index 82eda618e0..0e8641246d 100644 --- a/gm/rebaseline_server/static/view.css +++ b/gm/rebaseline_server/static/view.css @@ -50,6 +50,14 @@ font-size: 30px; } +/* Odd and even lines within results display. */ +.results-odd { + background-color: #ddffdd; +} +.results-even { + background-color: #ddddff; +} + .show-only-button { font-size: 8px; } diff --git a/gm/rebaseline_server/static/view.html b/gm/rebaseline_server/static/view.html index 5ded6973f7..68340ebe14 100644 --- a/gm/rebaseline_server/static/view.html +++ b/gm/rebaseline_server/static/view.html @@ -126,6 +126,12 @@ Show thumbnails </td></tr> <tr><td> + <input type="checkbox" ng-model="mergeIdenticalRowsPending" + ng-init="mergeIdenticalRowsPending = true" + ng-change="areUpdatesPending = true"/> + Merge identical rows + </td></tr> + <tr><td> Image width <input type="text" ng-model="imageSizePending" ng-init="imageSizePending=100" @@ -248,9 +254,19 @@ bugs </th> <th width="{{imageSize}}"> + <input type="radio" + name="sortColumnRadio" + value="imageA" + ng-checked="(sortColumnKey == constants.KEY__IMAGEPAIRS__IMAGE_A_URL)" + ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"> {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}} </th> <th width="{{imageSize}}"> + <input type="radio" + name="sortColumnRadio" + value="imageB" + ng-checked="(sortColumnKey == constants.KEY__IMAGEPAIRS__IMAGE_B_URL)" + ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"> {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}} </th> <th width="{{imageSize}}"> @@ -280,7 +296,9 @@ </th> </tr> - <tr ng-repeat="imagePair in limitedImagePairs" results-updated-callback-directive> + <tr ng-repeat="imagePair in limitedImagePairs" valign="top" + ng-class-odd="'results-odd'" ng-class-even="'results-even'" + results-updated-callback-directive> <td> {{imagePair[constants.KEY__IMAGEPAIRS__EXTRACOLUMNS][constants.KEY__EXTRACOLUMNS__RESULT_TYPE]}} <br> @@ -345,7 +363,7 @@ </td> <!-- image A --> - <td valign="bottom" width="{{imageSize}}"> + <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] != null"> <a href="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL]}}" target="_blank">View Image</a><br/> <img ng-if="showThumbnails" @@ -359,7 +377,7 @@ </td> <!-- image B --> - <td valign="bottom" width="{{imageSize}}"> + <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL] != null"> <a href="{{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL]}}" target="_blank">View Image</a><br/> <img ng-if="showThumbnails" @@ -373,17 +391,17 @@ </td> <!-- whitediffs: every differing pixel shown in white --> - <td valign="bottom" width="{{imageSize}}"> + <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" title="{{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS] | number:0}} of {{(100 * imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS] / imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS]) | number:0}} pixels ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}%) differ from expectation."> - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}% - ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS]}}) - <br/> <a href="{{imageSets[constants.KEY__IMAGESETS__SET__WHITEDIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{getImageDiffRelativeUrl(imagePair)}}" target="_blank">View Image</a><br/> <img ng-if="showThumbnails" width="{{imageSize}}" ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__WHITEDIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{getImageDiffRelativeUrl(imagePair)}}" /> + <br/> + {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS].toFixed(4)}}% + ({{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__NUM_DIFF_PIXELS]}}) </div> <div ng-show="!imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" style="text-align:center"> @@ -392,18 +410,18 @@ </td> <!-- diffs: per-channel RGB deltas --> - <td valign="bottom" width="{{imageSize}}"> + <td width="{{imageSize}}" ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> <div ng-if="imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" title="Perceptual difference measure is {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}%. Maximum difference per channel: R={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][0]}}, G={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][1]}}, B={{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL][2]}}"> - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}% - {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL]}} - <br/> <a href="{{imageSets[constants.KEY__IMAGESETS__SET__DIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{getImageDiffRelativeUrl(imagePair)}}" target="_blank">View Image</a><br/> <img ng-if="showThumbnails" ng-style="{backgroundColor: pixelDiffBgColor}" width="{{imageSize}}" ng-src="{{imageSets[constants.KEY__IMAGESETS__SET__DIFFS][constants.KEY__IMAGESETS__FIELD__BASE_URL]}}/{{getImageDiffRelativeUrl(imagePair)}}" /> + <br/> + {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF].toFixed(4)}}% + {{imagePair[constants.KEY__IMAGEPAIRS__DIFFERENCES][constants.KEY__DIFFERENCES__MAX_DIFF_PER_CHANNEL]}} </div> <div ng-show="!imagePair[constants.KEY__IMAGEPAIRS__IS_DIFFERENT]" style="text-align:center"> @@ -411,12 +429,13 @@ </div> </td> - <td> + <td ng-if="imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN] > 0" rowspan="{{imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN]}}"> + <br/> <input type="checkbox" name="rowSelect" value="{{imagePair.index}}" ng-checked="isValueInArray(imagePair.index, selectedImagePairs)" - ng-click="toggleValueInArray(imagePair.index, selectedImagePairs)"> + ng-click="toggleSomeImagePairs($index, imagePair[constants.KEY__IMAGEPAIRS__ROWSPAN])"> </tr> </table> <!-- imagePairs --> </td></tr></table> <!-- table holding results header + imagePairs table --> |