blob: fa5a0c635ba8c1b33083c040f69942321df9559b [file] [log] [blame]
epoger@google.comf9d134d2013-09-27 15:02:44 +00001<!DOCTYPE html>
2
epoger@google.com542b65f2013-10-15 20:10:33 +00003<html ng-app="Loader" ng-controller="Loader.Controller">
epoger@google.comf9d134d2013-09-27 15:02:44 +00004
5<head>
epoger@google.com542b65f2013-10-15 20:10:33 +00006 <title ng-bind="windowTitle"></title>
epoger@google.comf9d134d2013-09-27 15:02:44 +00007 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
8 <script src="loader.js"></script>
epoger@google.comeb832592013-10-23 15:07:26 +00009 <link rel="stylesheet" href="view.css">
epoger@google.comf9d134d2013-09-27 15:02:44 +000010</head>
11
12<body>
epoger@google.com4259ef32013-10-29 17:39:09 +000013 <h2>
14 Instructions, roadmap, etc. are at
15 <a href="http://tinyurl.com/SkiaRebaselineServer">
16 http://tinyurl.com/SkiaRebaselineServer
17 </a>
18 </h2>
19
epoger@google.comdcb4e652013-10-11 18:45:33 +000020 <em>
21 {{loadingMessage}}
epoger@google.com5f2bb002013-10-02 18:57:48 +000022 </em>
epoger@google.comafaad3d2013-09-30 15:06:25 +000023
epoger@google.comeb832592013-10-23 15:07:26 +000024 <div ng-hide="!categories"><!-- everything: hide until data is loaded -->
25
epoger@google.comad0e5522013-10-24 15:38:27 +000026 <div class="warning-div"
27 ng-hide="!(header.isEditable && header.isExported)">
epoger@google.com9fb6c8a2013-10-09 18:05:58 +000028 WARNING! These results are editable and exported, so any user
29 who can connect to this server over the network can modify them.
30 </div>
epoger@google.comeb832592013-10-23 15:07:26 +000031
epoger@google.com542b65f2013-10-15 20:10:33 +000032 <div ng-hide="!(header.timeUpdated)">
33 Results current as of {{localTimeString(header.timeUpdated)}}
34 </div>
epoger@google.comeb832592013-10-23 15:07:26 +000035
epoger@google.comad0e5522013-10-24 15:38:27 +000036 <div><!-- tabs -->
37 <div class="tab-spacer" ng-repeat="tab in tabs">
epoger@google.comeb832592013-10-23 15:07:26 +000038 <div class="tab-{{tab == viewingTab}}"
epoger@google.comeb832592013-10-23 15:07:26 +000039 ng-click="setViewingTab(tab)">
40 &nbsp;{{tab}} ({{numResultsPerTab[tab]}})&nbsp;
41 </div>
epoger@google.comad0e5522013-10-24 15:38:27 +000042 <div class="tab-spacer">
epoger@google.comeb832592013-10-23 15:07:26 +000043 &nbsp;
44 </div>
45 </div>
46 </div><!-- tabs -->
47
epoger@google.comad0e5522013-10-24 15:38:27 +000048 <div class="tab-main"><!-- main display area of selected tab -->
epoger@google.comeb832592013-10-23 15:07:26 +000049
50 <br>
epoger@google.comad0e5522013-10-24 15:38:27 +000051 <!-- We only show the filters/settings table on the Unfiled tab. -->
epoger@google.comeb832592013-10-23 15:07:26 +000052 <table ng-hide="viewingTab != defaultTab" border="1">
epoger@google.com5f2bb002013-10-02 18:57:48 +000053 <tr>
epoger@google.comf4394d52013-10-29 15:49:40 +000054 <th colspan="4">
epoger@google.com5f2bb002013-10-02 18:57:48 +000055 Filters
56 </th>
57 <th>
58 Settings
59 </th>
60 </tr>
61 <tr valign="top">
epoger@google.com055e3b52013-10-26 14:31:11 +000062 <!-- TODO(epoger): make this an ng-repeat over resultType, config, etc? -->
epoger@google.com5f2bb002013-10-02 18:57:48 +000063 <td>
64 resultType<br>
epoger@google.com92165bc2013-10-26 15:01:08 +000065 <label ng-repeat="(resultType, count) in categories['resultType'] track by $index">
epoger@google.com5f2bb002013-10-02 18:57:48 +000066 <input type="checkbox"
67 name="resultTypes"
68 value="{{resultType}}"
epoger@google.comad0e5522013-10-24 15:38:27 +000069 ng-checked="!isValueInSet(resultType, hiddenResultTypes)"
70 ng-click="toggleValueInSet(resultType, hiddenResultTypes); setUpdatesPending(true)">
epoger@google.com5f2bb002013-10-02 18:57:48 +000071 {{resultType}} ({{count}})<br>
72 </label>
epoger@google.comf4394d52013-10-29 15:49:40 +000073 <button ng-click="hiddenResultTypes = {}; updateResults()">
74 all
75 </button>
76 <button ng-click="hiddenResultTypes = {}; toggleValuesInSet(allResultTypes, hiddenResultTypes); updateResults()">
77 none
78 </button>
79 <button ng-click="toggleValuesInSet(allResultTypes, hiddenResultTypes); updateResults()">
80 toggle
81 </button>
82 </td>
83 <td ng-repeat="category in ['builder', 'test']">
84 {{category}}
85 <br>
86 <input type="text"
87 ng-model="categoryValueMatch[category]"
88 ng-change="setUpdatesPending(true)"/>
89 <br>
90 <button ng-click="setCategoryValueMatch(category, '')"
91 ng-disabled="('' == categoryValueMatch[category])">
92 clear (show all)
93 </button>
epoger@google.com5f2bb002013-10-02 18:57:48 +000094 </td>
95 <td>
96 config<br>
epoger@google.com92165bc2013-10-26 15:01:08 +000097 <label ng-repeat="(config, count) in categories['config'] track by $index">
epoger@google.com5f2bb002013-10-02 18:57:48 +000098 <input type="checkbox"
99 name="configs"
100 value="{{config}}"
epoger@google.comad0e5522013-10-24 15:38:27 +0000101 ng-checked="!isValueInSet(config, hiddenConfigs)"
102 ng-click="toggleValueInSet(config, hiddenConfigs); setUpdatesPending(true)">
epoger@google.com5f2bb002013-10-02 18:57:48 +0000103 {{config}} ({{count}})<br>
104 </label>
epoger@google.comf4394d52013-10-29 15:49:40 +0000105 <button ng-click="hiddenConfigs = {}; updateResults()">
106 all
107 </button>
108 <button ng-click="hiddenConfigs = {}; toggleValuesInSet(allConfigs, hiddenConfigs); updateResults()">
109 none
110 </button>
111 <button ng-click="toggleValuesInSet(allConfigs, hiddenConfigs); updateResults()">
112 toggle
113 </button>
epoger@google.com5f2bb002013-10-02 18:57:48 +0000114 </td>
115 <td><table>
116 <tr><td>
117 Image size
118 <input type="text" ng-model="imageSizePending"
119 ng-init="imageSizePending=100"
120 ng-change="areUpdatesPending = true"
121 maxlength="4"/>
122 </td></tr>
123 <tr><td>
124 Max records to display
125 <input type="text" ng-model="displayLimitPending"
126 ng-init="displayLimitPending=50"
127 ng-change="areUpdatesPending = true"
128 maxlength="4"/>
129 </td></tr>
130 <tr><td>
epoger@google.comad0e5522013-10-24 15:38:27 +0000131 <button class="update-results-button"
epoger@google.com5f2bb002013-10-02 18:57:48 +0000132 ng-click="updateResults()"
133 ng-disabled="!areUpdatesPending">
134 Update Results
135 </button>
136 </td></tr>
137 </tr></table></td>
138 </tr>
139 </table>
epoger@google.comf9d134d2013-09-27 15:02:44 +0000140
epoger@google.com5f2bb002013-10-02 18:57:48 +0000141 <p>
epoger@google.comeb832592013-10-23 15:07:26 +0000142
epoger@google.comad0e5522013-10-24 15:38:27 +0000143 <!-- Submission UI that we only show in the Pending Approval tab. -->
epoger@google.comeb832592013-10-23 15:07:26 +0000144 <div ng-hide="'Pending Approval' != viewingTab">
145 <div style="display:inline-block">
146 <button style="font-size:20px"
147 ng-click="submitApprovals(filteredTestData)"
148 ng-disabled="submitPending || (filteredTestData.length == 0)">
149 Update these {{filteredTestData.length}} expectations on the server
150 </button>
151 </div>
152 <div style="display:inline-block">
153 <div style="font-size:20px"
154 ng-hide="!submitPending">
155 Submitting, please wait...
156 </div>
157 </div>
epoger@google.com055e3b52013-10-26 14:31:11 +0000158 <div>
159 Advanced settings...
160 <input type="checkbox" ng-model="showSubmitAdvancedSettings">
161 show
162 <ul ng-hide="!showSubmitAdvancedSettings">
epoger@google.com1e698af2013-11-05 21:00:24 +0000163 <li ng-repeat="setting in ['reviewed-by-human', 'ignore-failure']">
epoger@google.com055e3b52013-10-26 14:31:11 +0000164 {{setting}}
165 <input type="checkbox" ng-model="submitAdvancedSettings[setting]">
166 </li>
167 <li ng-repeat="setting in ['bug']">
168 {{setting}}
169 <input type="text" ng-model="submitAdvancedSettings[setting]">
170 </li>
171 </ul>
172 </div>
epoger@google.comeb832592013-10-23 15:07:26 +0000173 </div>
174
175 <p>
176
epoger@google.com7b1c2c12013-11-01 14:29:03 +0000177 <table border="0"><tr><td> <!-- table holding results header + results table -->
178 <table border="0" width="100%"> <!-- results header -->
179 <tr>
180 <td>
181 Found {{filteredTestData.length}} matches;
182 <span ng-hide="filteredTestData.length <= limitedTestData.length">
183 displaying the first {{limitedTestData.length}}
184 </span>
185 <span ng-hide="filteredTestData.length > limitedTestData.length">
186 displaying them all
187 </span>
188 <br>
189 (click on the column header radio buttons to re-sort by that column)
190 </td>
191 <td align="right">
192 <div>
193 all tests shown:
194 <button ng-click="selectAllItems()">
195 select
196 </button>
197 <button ng-click="clearAllItems()">
198 clear
199 </button>
200 <button ng-click="toggleAllItems()">
201 toggle
202 </button>
203 </div>
204 <div ng-repeat="otherTab in tabs">
205 <button ng-click="moveSelectedItemsToTab(otherTab)"
206 ng-disabled="selectedItems.length == 0"
207 ng-hide="otherTab == viewingTab">
208 move {{selectedItems.length}} selected tests to {{otherTab}} tab
209 </button>
210 </div>
211 </td>
212 </tr>
213 </table> <!-- results header -->
214 </td></tr><tr><td>
215 <table border="1"> <!-- results -->
epoger@google.comf9d134d2013-09-27 15:02:44 +0000216 <tr>
epoger@google.comad0e5522013-10-24 15:38:27 +0000217 <!-- Most column headers are displayed in a common fashion... -->
epoger@google.com5f2bb002013-10-02 18:57:48 +0000218 <th ng-repeat="categoryName in ['resultType', 'builder', 'test', 'config']">
219 <input type="radio"
220 name="sortColumnRadio"
221 value="{{categoryName}}"
222 ng-checked="(sortColumn == categoryName)"
223 ng-click="sortResultsBy(categoryName)">
224 {{categoryName}}
225 </th>
epoger@google.comad0e5522013-10-24 15:38:27 +0000226 <!-- ... but there are a few columns where we display things differently. -->
epoger@google.com5f2bb002013-10-02 18:57:48 +0000227 <th>
228 <input type="radio"
229 name="sortColumnRadio"
epoger@google.com055e3b52013-10-26 14:31:11 +0000230 value="bugs"
231 ng-checked="(sortColumn == 'bugs')"
232 ng-click="sortResultsBy('bugs')">
233 bugs
234 </th>
epoger@google.com214a0242013-11-22 19:26:18 +0000235 <th width="{{imageSize}}">
epoger@google.com055e3b52013-10-26 14:31:11 +0000236 <input type="radio"
237 name="sortColumnRadio"
epoger@google.com5f2bb002013-10-02 18:57:48 +0000238 value="expectedHashDigest"
239 ng-checked="(sortColumn == 'expectedHashDigest')"
240 ng-click="sortResultsBy('expectedHashDigest')">
241 expected image
242 </th>
epoger@google.com214a0242013-11-22 19:26:18 +0000243 <th width="{{imageSize}}">
epoger@google.com5f2bb002013-10-02 18:57:48 +0000244 <input type="radio"
245 name="sortColumnRadio"
246 value="actualHashDigest"
247 ng-checked="(sortColumn == 'actualHashDigest')"
248 ng-click="sortResultsBy('actualHashDigest')">
249 actual image
250 </th>
epoger@google.com214a0242013-11-22 19:26:18 +0000251 <th width="{{imageSize}}">
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000252 <input type="radio"
253 name="sortColumnRadio"
254 value="percentDifferingPixels"
255 ng-checked="(sortColumn == 'percentDifferingPixels')"
256 ng-click="sortResultsBy('percentDifferingPixels')">
epoger@google.com214a0242013-11-22 19:26:18 +0000257 differing pixels in white
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000258 </th>
epoger@google.com214a0242013-11-22 19:26:18 +0000259 <th width="{{imageSize}}">
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000260 <input type="radio"
261 name="sortColumnRadio"
262 value="weightedDiffMeasure"
263 ng-checked="(sortColumn == 'weightedDiffMeasure')"
264 ng-click="sortResultsBy('weightedDiffMeasure')">
epoger@google.com214a0242013-11-22 19:26:18 +0000265 difference per pixel
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000266 </th>
267 <th>
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000268 <!-- item-selection checkbox column -->
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000269 </th>
epoger@google.comf9d134d2013-09-27 15:02:44 +0000270 </tr>
epoger@google.comf4394d52013-10-29 15:49:40 +0000271
272 <!-- For most columns... if the user clicks on the cell, and we are on
273 the default tab, update the filter to only show results with the
274 same value for this category.
275 This is made a bit tricky by the fact that AngularJS expressions
276 do not allow control flow statements. See
277 http://docs.angularjs.org/guide/expression -->
epoger@google.com5f2bb002013-10-02 18:57:48 +0000278 <tr ng-repeat="result in limitedTestData">
epoger@google.comf4394d52013-10-29 15:49:40 +0000279 <td ng-click="(viewingTab != defaultTab) || showOnlyResultType(result.resultType)">
280 {{result.resultType}}
281 </td>
282 <td ng-repeat="categoryName in ['builder', 'test']"
283 ng-click="(viewingTab != defaultTab) || setCategoryValueMatch(categoryName, result[categoryName])">
epoger@google.com055e3b52013-10-26 14:31:11 +0000284 {{result[categoryName]}}
285 </td>
epoger@google.comf4394d52013-10-29 15:49:40 +0000286 <td ng-click="(viewingTab != defaultTab) || showOnlyConfig(result.config)">
287 {{result.config}}
288 </td>
epoger@google.com055e3b52013-10-26 14:31:11 +0000289 <td>
290 <a ng-repeat="bug in result['bugs']"
291 href="https://code.google.com/p/skia/issues/detail?id={{bug}}"
292 target="_blank">
293 {{bug}}
294 </a>
295 </td>
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000296
297 <!-- expected image -->
epoger@google.com214a0242013-11-22 19:26:18 +0000298 <td valign="top" width="{{imageSize}}">
299 <a class="image-link" target="_blank" href="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.expectedHashType}}/{{result.test}}/{{result.expectedHashDigest}}.png">
epoger@google.comf9d134d2013-09-27 15:02:44 +0000300 <img width="{{imageSize}}" src="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.expectedHashType}}/{{result.test}}/{{result.expectedHashDigest}}.png"/>
301 </a>
302 </td>
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000303
304 <!-- actual image -->
epoger@google.com214a0242013-11-22 19:26:18 +0000305 <td valign="top" width="{{imageSize}}">
306 <a class="image-link" target="_blank" href="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.actualHashType}}/{{result.test}}/{{result.actualHashDigest}}.png">
epoger@google.comdcb4e652013-10-11 18:45:33 +0000307 <img width="{{imageSize}}" src="http://chromium-skia-gm.commondatastorage.googleapis.com/gm/{{result.actualHashType}}/{{result.test}}/{{result.actualHashDigest}}.png"/>
epoger@google.comf9d134d2013-09-27 15:02:44 +0000308 </a>
309 </td>
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000310
311 <!-- whitediffs: every differing pixel shown in white -->
epoger@google.com214a0242013-11-22 19:26:18 +0000312 <td valign="top" width="{{imageSize}}">
313 <div ng-hide="result.expectedHashDigest == result.actualHashDigest"
314 title="{{result.numDifferingPixels | number:0}} of {{(100 * result.numDifferingPixels / result.percentDifferingPixels) | number:0}} pixels ({{result.percentDifferingPixels.toFixed(4)}}%) differ from expectation.">
315 <a class="image-link" target="_blank" href="/static/generated-images/whitediffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png">
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000316 <img width="{{imageSize}}" src="/static/generated-images/whitediffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png"/>
317 </a><br>
318 {{result.percentDifferingPixels.toFixed(4)}}%
epoger@google.com214a0242013-11-22 19:26:18 +0000319 ({{result.numDifferingPixels}})
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000320 </div>
321 <div ng-hide="result.expectedHashDigest != result.actualHashDigest"
322 style="text-align:center">
323 &ndash;none&ndash;
324 </div>
325 </td>
326
327 <!-- diffs: per-channel RGB deltas -->
epoger@google.com214a0242013-11-22 19:26:18 +0000328 <td valign="top" width="{{imageSize}}">
329 <div ng-hide="result.expectedHashDigest == result.actualHashDigest"
330 title="Weighted difference measure is {{result.weightedDiffMeasure.toFixed(4)}}%. Maximum difference per channel: R={{result.maxDiffPerChannel[0]}}, G={{result.maxDiffPerChannel[1]}}, B={{result.maxDiffPerChannel[2]}}">
331 <a class="image-link" target="_blank" href="/static/generated-images/diffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png">
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000332 <img width="{{imageSize}}" src="/static/generated-images/diffs/{{result.expectedHashDigest}}-vs-{{result.actualHashDigest}}.png"/>
333 </a><br>
334 {{result.weightedDiffMeasure.toFixed(4)}}%
epoger@google.com214a0242013-11-22 19:26:18 +0000335 {{result.maxDiffPerChannel}}
epoger@google.com9dddf6f2013-11-08 16:25:25 +0000336 </div>
337 <div ng-hide="result.expectedHashDigest != result.actualHashDigest"
338 style="text-align:center">
339 &ndash;none&ndash;
340 </div>
341 </td>
342
epoger@google.comeb832592013-10-23 15:07:26 +0000343 <td>
epoger@google.com9fb6c8a2013-10-09 18:05:58 +0000344 <input type="checkbox"
345 name="rowSelect"
346 value="{{result.index}}"
epoger@google.comad0e5522013-10-24 15:38:27 +0000347 ng-checked="isValueInArray(result.index, selectedItems)"
348 ng-click="toggleValueInArray(result.index, selectedItems)">
epoger@google.comf9d134d2013-09-27 15:02:44 +0000349 </tr>
epoger@google.com7b1c2c12013-11-01 14:29:03 +0000350 </table> <!-- results -->
351 </td></tr></table> <!-- table holding results header + results table -->
352
epoger@google.comad0e5522013-10-24 15:38:27 +0000353 </div><!-- main display area of selected tab -->
epoger@google.comeb832592013-10-23 15:07:26 +0000354 </div><!-- everything: hide until data is loaded -->
epoger@google.comf9d134d2013-09-27 15:02:44 +0000355
356 <!-- TODO(epoger): Can we get the base URLs (commondatastorage and
357 issues list) from
358 http://skia.googlecode.com/svn/buildbot/site_config/global_variables.json
359 ? I tried importing the
360 http://skia.googlecode.com/svn/buildbot/skia_tools.js script and using
361 that to do so, but I got Access-Control-Allow-Origin errors.
362 -->
363
364</body>
365</html>