Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(123)

Side by Side Diff: Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/scripts/ui/results.js

Issue 13712005: Move GardeningServer out of BuildSlaveSupport (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 var ui = ui || {};
27 ui.results = ui.results || {};
28
29 (function(){
30
31 var kResultsPrefetchDelayMS = 500;
32
33 // FIXME: Rather than using table, should we be using something fancier?
34 ui.results.Comparison = base.extends('table', {
35 init: function()
36 {
37 this.className = 'comparison';
38 this.innerHTML = '<thead><tr><th>Expected</th><th>Actual</th><th>Diff</t h></tr></thead>' +
39 '<tbody><tr><td class="expected result-container"></td> <td class="actual result-container"></td><td class="diff result-container"></td> </tr></tbody>';
40 },
41 _selectorForKind: function(kind)
42 {
43 switch (kind) {
44 case results.kExpectedKind:
45 return '.expected';
46 case results.kActualKind:
47 return '.actual';
48 case results.kDiffKind:
49 return '.diff';
50 }
51 return '.unknown';
52 },
53 update: function(kind, result)
54 {
55 var selector = this._selectorForKind(kind);
56 $(selector, this).empty().append(result);
57 return result;
58 },
59 });
60
61 // We'd really like TextResult and ImageResult to extend a common Result base
62 // class, but we can't seem to do that because they inherit from different
63 // HTMLElements. We could have them inherit from <div>, but that seems lame.
64
65 ui.results.TextResult = base.extends('iframe', {
66 init: function(url)
67 {
68 this.className = 'text-result';
69 this.src = url;
70 }
71 });
72
73 ui.results.ImageResult = base.extends('img', {
74 init: function(url)
75 {
76 this.className = 'image-result';
77 this.src = url;
78 }
79 });
80
81 ui.results.AudioResult = base.extends('audio', {
82 init: function(url)
83 {
84 this.className = 'audio-result';
85 this.src = url;
86 this.controls = 'controls';
87 }
88 });
89
90 function constructorForResultType(type)
91 {
92 if (type == results.kImageType)
93 return ui.results.ImageResult;
94 if (type == results.kAudioType)
95 return ui.results.AudioResult;
96 return ui.results.TextResult;
97 }
98
99 ui.results.ResultsGrid = base.extends('div', {
100 init: function()
101 {
102 this.className = 'results-grid';
103 },
104 _addResult: function(comparison, constructor, resultsURLsByKind, kind)
105 {
106 var url = resultsURLsByKind[kind];
107 if (!url)
108 return;
109 comparison.update(kind, new constructor(url));
110 },
111 addComparison: function(resultType, resultsURLsByKind)
112 {
113 var comparison = new ui.results.Comparison();
114 var constructor = constructorForResultType(resultType);
115
116 this._addResult(comparison, constructor, resultsURLsByKind, results.kExp ectedKind);
117 this._addResult(comparison, constructor, resultsURLsByKind, results.kAct ualKind);
118 this._addResult(comparison, constructor, resultsURLsByKind, results.kDif fKind);
119
120 this.appendChild(comparison);
121 return comparison;
122 },
123 addRow: function(resultType, url)
124 {
125 var constructor = constructorForResultType(resultType);
126 var view = new constructor(url);
127 this.appendChild(view);
128 return view;
129 },
130 addResults: function(resultsURLs)
131 {
132 var resultsURLsByTypeAndKind = {};
133
134 resultsURLsByTypeAndKind[results.kImageType] = {};
135 resultsURLsByTypeAndKind[results.kAudioType] = {};
136 resultsURLsByTypeAndKind[results.kTextType] = {};
137
138 resultsURLs.forEach(function(url) {
139 resultsURLsByTypeAndKind[results.resultType(url)][results.resultKind (url)] = url;
140 });
141
142 $.each(resultsURLsByTypeAndKind, function(resultType, resultsURLsByKind) {
143 if ($.isEmptyObject(resultsURLsByKind))
144 return;
145 if (results.kUnknownKind in resultsURLsByKind) {
146 // This is something like "crash" that isn't a comparison.
147 this.addRow(resultType, resultsURLsByKind[results.kUnknownKind]) ;
148 return;
149 }
150 this.addComparison(resultType, resultsURLsByKind);
151 }.bind(this));
152
153 if (!this.children.length)
154 this.textContent = 'No results to display.'
155 }
156 });
157
158 ui.results.ResultsDetails = base.extends('div', {
159 init: function(delegate, failureInfo)
160 {
161 this.className = 'results-detail';
162 this._delegate = delegate;
163 this._failureInfo = failureInfo;
164 this._haveShownOnce = false;
165 },
166 show: function() {
167 if (this._haveShownOnce)
168 return;
169 this._haveShownOnce = true;
170 this._delegate.fetchResultsURLs(this._failureInfo, function(resultsURLs) {
171 var resultsGrid = new ui.results.ResultsGrid();
172 resultsGrid.addResults(resultsURLs);
173
174 $(this).empty().append(
175 new ui.actions.List([
176 new ui.actions.Previous(),
177 new ui.actions.Next()
178 ])).append(resultsGrid);
179
180
181 }.bind(this));
182 },
183 });
184
185 function isAnyReftest(testName, resultsByTest)
186 {
187 return Object.keys(resultsByTest[testName]).map(function(builder) {
188 return resultsByTest[testName][builder];
189 }).some(function(resultNode) {
190 return resultNode.reftest_type && resultNode.reftest_type.length;
191 });
192 }
193
194 ui.results.FlakinessData = base.extends('iframe', {
195 init: function()
196 {
197 this.className = 'flakiness-iframe';
198 this.src = ui.urlForEmbeddedFlakinessDashboard();
199 this.addEventListener('load', function() {
200 window.addEventListener('message', this._handleMessage.bind(this));
201 });
202 },
203 _handleMessage: function(event) {
204 if (!this.contentWindow)
205 return;
206
207 // Check for null event.origin so that the unittests can get past this p oint.
208 // FIXME: Is this safe? In practice, there's no meaningful harm that can come from
209 // a malicious page sending us heightChanged commands, so it doesn't rea lly matter.
210 if (event.origin !== 'null' && event.origin != 'http://test-results.apps pot.com') {
211 console.log('Invalid origin: ' + event.origin);
212 return;
213 }
214
215 if (event.data.command != 'heightChanged') {
216 console.log('Unknown postMessage command: ' + event.data);
217 return;
218 }
219
220 this.style.height = event.data.height + 'px';
221 }
222 });
223
224 ui.results.TestSelector = base.extends('div', {
225 init: function(delegate, resultsByTest)
226 {
227 this.className = 'test-selector';
228 this._delegate = delegate;
229
230 var topPanel = document.createElement('div');
231 topPanel.className = 'top-panel';
232 this.appendChild(topPanel);
233
234 this._appendResizeHandle();
235
236 var bottomPanel = document.createElement('div');
237 bottomPanel.className = 'bottom-panel';
238 this.appendChild(bottomPanel);
239
240 this._flakinessData = new ui.results.FlakinessData();
241 this.appendChild(this._flakinessData);
242
243 var testNames = Object.keys(resultsByTest);
244 testNames.sort().forEach(function(testName) {
245 var nonLinkTitle = document.createElement('a');
246 nonLinkTitle.classList.add('non-link-title');
247 nonLinkTitle.textContent = testName;
248
249 var linkTitle = document.createElement('a');
250 linkTitle.classList.add('link-title');
251 linkTitle.setAttribute('href', ui.urlForFlakinessDashboard([testName ]))
252 linkTitle.textContent = testName;
253
254 var header = document.createElement('h3');
255 header.appendChild(nonLinkTitle);
256 header.appendChild(linkTitle);
257 header.addEventListener('click', this._showResults.bind(this, header , false));
258 topPanel.appendChild(header);
259 }, this);
260
261 // If we have a small amount of content, don't show the resize handler.
262 // Otherwise, set the minHeight so that the percentage height of the
263 // topPanel is not too small.
264 if (testNames.length <= 4)
265 this.removeChild(this.querySelector('.resize-handle'));
266 else
267 topPanel.style.minHeight = '100px';
268 },
269 _appendResizeHandle: function()
270 {
271 var resizeHandle = document.createElement('div');
272 resizeHandle.className = 'resize-handle';
273 this.appendChild(resizeHandle);
274
275 resizeHandle.addEventListener('mousedown', function(event) {
276 this._is_resizing = true;
277 event.preventDefault();
278 }.bind(this));
279
280 var cancelResize = function(event) { this._is_resizing = false; }.bind(t his);
281 this.addEventListener('mouseup', cancelResize);
282 // FIXME: Use addEventListener once WebKit adds support for mouseleave/m ouseenter.
283 $(window).bind('mouseleave', cancelResize);
284
285 this.addEventListener('mousemove', function(event) {
286 if (!this._is_resizing)
287 return;
288 var mouseY = event.clientY + document.body.scrollTop - this.offsetTo p;
289 var percentage = 100 * mouseY / this.offsetHeight;
290 document.querySelector('.top-panel').style.maxHeight = percentage + '%';
291 }.bind(this))
292 },
293 _showResults: function(header, scrollInfoView)
294 {
295 if (!header)
296 return false;
297
298 var activeHeader = this.querySelector('.active')
299 if (activeHeader)
300 activeHeader.classList.remove('active');
301 header.classList.add('active');
302
303 var testName = this.currentTestName();
304 this._flakinessData.src = ui.urlForEmbeddedFlakinessDashboard([testName] );
305
306 var bottomPanel = this.querySelector('.bottom-panel')
307 bottomPanel.innerHTML = '';
308 bottomPanel.appendChild(this._delegate.contentForTest(testName));
309
310 var topPanel = this.querySelector('.top-panel');
311 if (scrollInfoView) {
312 topPanel.scrollTop = header.offsetTop;
313 if (header.offsetTop - topPanel.scrollTop < header.offsetHeight)
314 topPanel.scrollTop = topPanel.scrollTop - header.offsetHeight;
315 }
316
317 var resultsDetails = this.querySelectorAll('.results-detail');
318 if (resultsDetails.length)
319 resultsDetails[0].show();
320 setTimeout(function() {
321 Array.prototype.forEach.call(resultsDetails, function(resultsDetail) {
322 resultsDetail.show();
323 });
324 }, kResultsPrefetchDelayMS);
325
326 return true;
327 },
328 nextResult: function()
329 {
330 if (this.querySelector('.builder-selector').nextResult())
331 return true;
332 return this.nextTest();
333 },
334 previousResult: function()
335 {
336 if (this.querySelector('.builder-selector').previousResult())
337 return true;
338 return this.previousTest();
339 },
340 nextTest: function()
341 {
342 return this._showResults(this.querySelector('.active').nextSibling, true );
343 },
344 previousTest: function()
345 {
346 var succeeded = this._showResults(this.querySelector('.active').previous Sibling, true);
347 if (succeeded)
348 this.querySelector('.builder-selector').lastResult();
349 return succeeded;
350 },
351 firstResult: function()
352 {
353 this._showResults(this.querySelector('h3'), true);
354 },
355 currentTestName: function()
356 {
357 return this.querySelector('.active .non-link-title').textContent;
358 }
359 });
360
361 ui.results.BuilderSelector = base.extends('div', {
362 init: function(delegate, testName, resultsByBuilder)
363 {
364 this.className = 'builder-selector';
365 this._delegate = delegate;
366
367 var tabStrip = this.appendChild(document.createElement('ul'));
368
369 Object.keys(resultsByBuilder).sort().forEach(function(builderName) {
370 var builderHash = base.underscoredBuilderName(builderName);
371
372 var link = document.createElement('a');
373 $(link).attr('href', "#" + builderHash).text(ui.displayNameForBuilde r(builderName));
374 tabStrip.appendChild(document.createElement('li')).appendChild(link) ;
375
376 var content = this._delegate.contentForTestAndBuilder(testName, buil derName);
377 content.id = builderHash;
378 this.appendChild(content);
379 }, this);
380
381 $(this).tabs();
382 },
383 nextResult: function()
384 {
385 var nextIndex = $(this).tabs('option', 'selected') + 1;
386 if (nextIndex >= $(this).tabs('length'))
387 return false
388 $(this).tabs('option', 'selected', nextIndex);
389 return true;
390 },
391 previousResult: function()
392 {
393 var previousIndex = $(this).tabs('option', 'selected') - 1;
394 if (previousIndex < 0)
395 return false;
396 $(this).tabs('option', 'selected', previousIndex);
397 return true;
398 },
399 firstResult: function()
400 {
401 $(this).tabs('option', 'selected', 0);
402 },
403 lastResult: function()
404 {
405 $(this).tabs('option', 'selected', $(this).tabs('length') - 1);
406 }
407 });
408
409 ui.results.View = base.extends('div', {
410 init: function(delegate)
411 {
412 this.className = 'results-view';
413 this._delegate = delegate;
414 },
415 contentForTest: function(testName)
416 {
417 var rebaselineAction;
418 if (isAnyReftest(testName, this._resultsByTest))
419 rebaselineAction = $('<div class="non-action-button">Reftests cannot be rebaselined. Email webkit-gardening@chromium.org if unsure how to fix this.< /div>');
420 else
421 rebaselineAction = new ui.actions.List([new ui.actions.Rebaseline(). makeDefault()]);
422 $(rebaselineAction).addClass('rebaseline-action');
423
424 var builderSelector = new ui.results.BuilderSelector(this, testName, thi s._resultsByTest[testName]);
425 $(builderSelector).append(rebaselineAction).append($('<br style="clear:b oth">'));
426 $(builderSelector).bind('tabsselect', function(event, ui) {
427 // We will probably have pre-fetched the tab already, but we need to make sure.
428 ui.panel.show();
429 });
430 return builderSelector;
431 },
432 contentForTestAndBuilder: function(testName, builderName)
433 {
434 var failureInfo = results.failureInfoForTestAndBuilder(this._resultsByTe st, testName, builderName);
435 return new ui.results.ResultsDetails(this, failureInfo);
436 },
437 setResultsByTest: function(resultsByTest)
438 {
439 $(this).empty();
440 this._resultsByTest = resultsByTest;
441 this._testSelector = new ui.results.TestSelector(this, resultsByTest);
442 this.appendChild(this._testSelector);
443 },
444 fetchResultsURLs: function(failureInfo, callback)
445 {
446 this._delegate.fetchResultsURLs(failureInfo, callback)
447 },
448 nextResult: function()
449 {
450 return this._testSelector.nextResult();
451 },
452 previousResult: function()
453 {
454 return this._testSelector.previousResult();
455 },
456 nextTest: function()
457 {
458 return this._testSelector.nextTest();
459 },
460 previousTest: function()
461 {
462 return this._testSelector.previousTest();
463 },
464 firstResult: function()
465 {
466 this._testSelector.firstResult()
467 },
468 currentTestName: function()
469 {
470 return this._testSelector.currentTestName()
471 }
472 });
473
474 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698