More improvements to HTTP baseline viewer (for GM results)

(SkipBuildbotRuns)

R=borenet@google.com, bsalomon@google.com

Review URL: https://codereview.chromium.org/25555003

git-svn-id: http://skia.googlecode.com/svn/trunk@11581 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
epoger@google.com 2013-10-02 18:57:48 +00:00
parent 55f5682523
commit 5f2bb002bd
3 changed files with 225 additions and 64 deletions

View File

@ -124,6 +124,13 @@ class Results(object):
"""
test_data = []
category_dict = {}
Results._EnsureIncludedInCategoryDict(category_dict, 'resultType', [
gm_json.JSONKEY_ACTUALRESULTS_FAILED,
gm_json.JSONKEY_ACTUALRESULTS_FAILUREIGNORED,
gm_json.JSONKEY_ACTUALRESULTS_NOCOMPARISON,
gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED,
])
for builder in sorted(actual_builder_dicts.keys()):
actual_results_for_this_builder = (
actual_builder_dicts[builder][gm_json.JSONKEY_ACTUALRESULTS])
@ -205,17 +212,13 @@ class Results(object):
"expectedHashDigest": str(expected_image[1]),
}
Results._AddToCategoryDict(category_dict, results_for_this_test)
# TODO(epoger): For now, don't include succeeded results in the raw
# data. There are so many of them that they make the client too slow.
if updated_result_type != gm_json.JSONKEY_ACTUALRESULTS_SUCCEEDED:
test_data.append(results_for_this_test)
test_data.append(results_for_this_test)
return {"categories": category_dict, "testData": test_data}
@staticmethod
def _AddToCategoryDict(category_dict, test_results):
"""Add test_results to the category dictionary we are building
(see documentation of self.GetAll() for the format of this dictionary).
"""Add test_results to the category dictionary we are building.
(See documentation of self.GetAll() for the format of this dictionary.)
params:
category_dict: category dict-of-dicts to add to; modify this in-place
@ -235,3 +238,22 @@ class Results(object):
if not category_dict[category].get(category_value):
category_dict[category][category_value] = 0
category_dict[category][category_value] += 1
@staticmethod
def _EnsureIncludedInCategoryDict(category_dict,
category_name, category_values):
"""Ensure that the category name/value pairs are included in category_dict,
even if there aren't any results with that name/value pair.
(See documentation of self.GetAll() for the format of this dictionary.)
params:
category_dict: category dict-of-dicts to modify
category_name: category name, as a string
category_values: list of values we want to make sure are represented
for this category
"""
if not category_dict.get(category_name):
category_dict[category_name] = {}
for category_value in category_values:
if not category_dict[category_name].get(category_value):
category_dict[category_name][category_value] = 0

View File

@ -7,16 +7,99 @@ var Loader = angular.module(
'Loader',
[]
);
// 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) {
var filteredItems = [];
for (var i = 0; i < unfilteredItems.length; i++) {
var item = unfilteredItems[i];
if ((hiddenResultTypes.indexOf(item.resultType) < 0) &&
(hiddenConfigs.indexOf(item.config) < 0)) {
filteredItems.push(item);
}
}
return filteredItems;
};
}
);
Loader.controller(
'Loader.Controller',
function($scope, $http) {
function($scope, $http, $filter) {
$http.get("/results/all").then(
function(response) {
$scope.categories = response.data.categories;
$scope.testData = response.data.testData;
$scope.sortColumn = 'test';
$scope.showResultsOfType = 'failed';
$scope.hiddenResultTypes = [
'failure-ignored', 'no-comparison', 'succeeded'];
$scope.hiddenConfigs = [];
$scope.updateResults();
}
);
$scope.isHiddenResultType = function(thisResultType) {
return ($scope.hiddenResultTypes.indexOf(thisResultType) >= 0);
}
$scope.toggleHiddenResultType = function(thisResultType) {
var i = $scope.hiddenResultTypes.indexOf(thisResultType);
if (i >= 0) {
$scope.hiddenResultTypes.splice(i, 1);
} else {
$scope.hiddenResultTypes.push(thisResultType);
}
$scope.areUpdatesPending = true;
}
// TODO(epoger): Rather than maintaining these as hard-coded
// variants of isHiddenResultType and toggleHiddenResultType, we
// should create general-purpose functions that can work with ANY
// category.
// But for now, I wanted to see this working. :-)
$scope.isHiddenConfig = function(thisConfig) {
return ($scope.hiddenConfigs.indexOf(thisConfig) >= 0);
}
$scope.toggleHiddenConfig = function(thisConfig) {
var i = $scope.hiddenConfigs.indexOf(thisConfig);
if (i >= 0) {
$scope.hiddenConfigs.splice(i, 1);
} else {
$scope.hiddenConfigs.push(thisConfig);
}
$scope.areUpdatesPending = true;
}
$scope.updateResults = function() {
$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
// array copies? (For better performance.)
$scope.filteredTestData =
$filter("orderBy")(
$filter("removeHiddenItems")(
$scope.testData,
$scope.hiddenResultTypes,
$scope.hiddenConfigs
),
$scope.sortColumn);
$scope.limitedTestData = $filter("limitTo")(
$scope.filteredTestData, $scope.displayLimit);
$scope.imageSize = $scope.imageSizePending;
$scope.areUpdatesPending = false;
}
$scope.sortResultsBy = function(sortColumn) {
$scope.sortColumn = sortColumn;
$scope.updateResults();
}
}
);

View File

@ -18,67 +18,122 @@
<!-- TODO(epoger): Add some indication of how old the
expected/actual data is -->
Settings:
<ul>
<!-- TODO(epoger): Now that we get multiple result types in a single
fetch, modify the UI: add a column showing resultType, and allow
the user to sort/filter on that column just like all the
others. -->
<li>show results of type
<select ng-model="showResultsOfType"
ng-init="showResultsOfType='failed'">
<option ng-repeat="(resultType, count) in categories['resultType']"
value="{{resultType}}">
{{resultType}} ({{count}})
</option>
</select>
<!--
TODO(epoger): See results.py: for now, I have disabled
returning succeeded tests as part of the JSON, because it
makes the returned JSON too big (and slows down the client).
<em ng-hide="categories">
Loading data, please wait...
</em>
Also, we should use some sort of lazy-loading technique
(e.g. http://www.appelsiini.net/projects/lazyload ), so that
the images are only loaded as they become viewable...
that will help with long lists like resultType='no-comparison'.
-->
<br>
TODO(epoger): 'no-comparison' will probably take forever;
see HTML source for details
<br>
TODO(epoger): 'succeeded' will not show any results;
see HTML source for details
</li>
<li>image size
<input type="text" ng-model="imageSizePending"
ng-init="imageSizePending=100; imageSize=100"
maxlength="4"/>
<button ng:click="imageSize=imageSizePending">apply</button>
</li>
</ul>
<div ng-hide="!categories">
<table border="1">
<tr>
<th colspan="2">
Filters
</th>
<th>
Settings
</th>
</tr>
<tr valign="top">
<td>
resultType<br>
<label ng-repeat="(resultType, count) in categories['resultType']">
<input type="checkbox"
name="resultTypes"
value="{{resultType}}"
ng-checked="!isHiddenResultType(resultType)"
ng-click="toggleHiddenResultType(resultType)">
{{resultType}} ({{count}})<br>
</label>
</td>
<td>
config<br>
<label ng-repeat="(config, count) in categories['config']">
<input type="checkbox"
name="configs"
value="{{config}}"
ng-checked="!isHiddenConfig(config)"
ng-click="toggleHiddenConfig(config)">
{{config}} ({{count}})<br>
</label>
</td>
<td><table>
<tr><td>
Image size
<input type="text" ng-model="imageSizePending"
ng-init="imageSizePending=100"
ng-change="areUpdatesPending = true"
maxlength="4"/>
</td></tr>
<tr><td>
Max records to display
<input type="text" ng-model="displayLimitPending"
ng-init="displayLimitPending=50"
ng-change="areUpdatesPending = true"
maxlength="4"/>
</td></tr>
<tr><td>
<button style="font-size:30px"
ng-click="updateResults()"
ng-disabled="!areUpdatesPending">
Update Results
</button>
</td></tr>
</tr></table></td>
</tr>
</table>
<p>
Click on the column header radio buttons to re-sort by that column...<br>
<!-- TODO(epoger): Show some sort of "loading" message, instead of
an empty table, while the data is loading. Otherwise, if there are
a lot of failures and it takes a long time to load them, the user
might think there are NO failures and leave the page! -->
TODO(epoger): Add ability to filter builder and test names
(using a free-form text field, with partial string match)
<br>
TODO(epoger): Add more columns, such as pixel diffs, notes/bugs,
ignoreFailure boolean
<br>
TODO(epoger): Improve the column sorting, as per
<a href="http://jsfiddle.net/vojtajina/js64b/14/">
http://jsfiddle.net/vojtajina/js64b/14/
</a>
<br>
TODO(epoger): Right now, if you change which column is used to
sort the data, the column widths may fluctuate based on the
longest string <i>currently visible</i> within the top {{displayLimit}}
results. Can we fix the column widths to be wide enough to hold
any result, even the currently hidden results?
<p>
Found {{filteredTestData.length}} matches, and displaying the first
{{displayLimit}}: <br>
<!-- TODO(epoger): If (displayLimit <= filteredTestData.length),
modify this message to indicate that all results are shown. -->
(click on the column header radio buttons to re-sort by that column)
<br>
<table border="1">
<tr>
<th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="builder">Builder</input></th>
<th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="test">Test</input></th>
<th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="config">Config</input></th>
<th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="expectedHashDigest">Expected Image</input></th>
<th><input ng-model="sortColumn" name="sortColumnRadio" type="radio" value="actualHashDigest">Actual Image</input></th>
<!-- TODO(epoger): Add more columns, such as...
pixel diff
notes/bugs
ignoreFailure boolean
-->
<th ng-repeat="categoryName in ['resultType', 'builder', 'test', 'config']">
<input type="radio"
name="sortColumnRadio"
value="{{categoryName}}"
ng-checked="(sortColumn == categoryName)"
ng-click="sortResultsBy(categoryName)">
{{categoryName}}
</th>
<th>
<input type="radio"
name="sortColumnRadio"
value="expectedHashDigest"
ng-checked="(sortColumn == 'expectedHashDigest')"
ng-click="sortResultsBy('expectedHashDigest')">
expected image
</th>
<th>
<input type="radio"
name="sortColumnRadio"
value="actualHashDigest"
ng-checked="(sortColumn == 'actualHashDigest')"
ng-click="sortResultsBy('actualHashDigest')">
actual image
</th>
</tr>
<!-- TODO(epoger): improve the column sorting, as per
http://jsfiddle.net/vojtajina/js64b/14/ -->
<tr ng-repeat="result in testData | filter: { resultType: showResultsOfType } | orderBy: sortColumn">
<tr ng-repeat="result in limitedTestData">
<td>{{result.resultType}}</td>
<td>{{result.builder}}</td>
<td>{{result.test}}</td>
<td>{{result.config}}</td>
@ -95,6 +150,7 @@
</tr>
</table>
</div>
</div>
<!-- TODO(epoger): Can we get the base URLs (commondatastorage and
issues list) from