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:
parent
55f5682523
commit
5f2bb002bd
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user