blob: f1d5eabdbcc176d8d63224dd0a784b0c37da0b8b [file] [log] [blame]
/*
* Copyright (C) 2013, 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
BuildbotQueueView = function(queues)
{
QueueView.call(this);
this.queues = queues || [];
this.queues.forEach(function(queue) {
if (this.platform && this.platform != queue.platform)
throw "A buildbot view may not contain queues for multiple platforms."
else
this.platform = queue.platform;
queue.addEventListener(BuildbotQueue.Event.IterationsAdded, this._queueIterationsAdded, this);
queue.addEventListener(BuildbotQueue.Event.UnauthorizedAccess, this._unauthorizedAccess, this);
}.bind(this));
var sortedRepositories = Dashboard.sortedRepositories;
for (var i = 0; i < sortedRepositories.length; i++) {
var trac = sortedRepositories[i].trac;
if (trac)
trac.addEventListener(Trac.Event.CommitsUpdated, this._newCommitsRecorded, this);
}
};
BaseObject.addConstructorFunctions(BuildbotQueueView);
BuildbotQueueView.UpdateInterval = 45000; // 45 seconds
BuildbotQueueView.UpdateSoonTimeout = 1000; // 1 second
BuildbotQueueView.prototype = {
constructor: BuildbotQueueView,
__proto__: QueueView.prototype,
_latestProductiveIteration: function(queue)
{
if (!queue.iterations.length)
return null;
if (queue.iterations[0].productive)
return queue.iterations[0];
return queue.iterations[0].previousProductiveIteration;
},
_appendUnauthorizedLineView: function(queue)
{
console.assert(queue.buildbot.needsAuthentication);
console.assert(!queue.buildbot.isAuthenticated);
var javascriptURL = "javascript:buildbots[" + buildbots.indexOf(queue.buildbot) + "].updateQueues(Buildbot.UpdateReason.Reauthenticate)";
var status = new StatusLineView("unauthorized", StatusLineView.Status.Unauthorized, "", null, javascriptURL);
this.element.appendChild(status.element);
},
_createLoadingIndicator: function(iteration, heading) {
var content = document.createElement("div");
content.className = "test-results-popover";
this._addIterationHeadingToPopover(iteration, content, heading);
this._addDividerToPopover(content);
var loadingIndicator = document.createElement("div");
loadingIndicator.className = "loading-indicator";
loadingIndicator.textContent = "Loading\u2026";
content.appendChild(loadingIndicator);
return content;
},
// Work around bug 80159: -webkit-user-select:none not respected when copying content.
// We set clipboard data manually, temporarily making non-selectable content hidden
// to easily get accurate selection text.
_onPopoverCopy: function(event) {
var iterator = document.createNodeIterator(
event.currentTarget,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: function(element) {
if (window.getComputedStyle(element).webkitUserSelect !== "none")
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_SKIP;
}
}
);
while ((node = iterator.nextNode()))
node.style.visibility = "visible";
event.currentTarget.style.visibility = "hidden";
event.clipboardData.setData('text', window.getSelection());
event.currentTarget.style.visibility = "";
return false;
},
_popoverContentForJavaScriptCoreTestRegressions: function(iteration, testName)
{
var content = document.createElement("div");
content.className = "test-results-popover";
this._addIterationHeadingToPopover(iteration, content, "javascriptcore test failures", iteration.queue.buildbot.javaScriptCoreTestStdioUrlForIteration(iteration, testName));
this._addDividerToPopover(content);
if (!iteration.javaScriptCoreTestResults.regressions) {
var message = document.createElement("div");
message.className = "loading-failure";
message.textContent = "Test results couldn\u2019t be loaded";
content.appendChild(message);
return content;
}
var regressions = iteration.javaScriptCoreTestResults.regressions;
for (var i = 0, end = regressions.length; i != end; ++i) {
var test = regressions[i];
var rowElement = document.createElement("div");
var testPathElement = document.createElement("span");
testPathElement.className = "test-path";
testPathElement.textContent = test;
rowElement.appendChild(testPathElement);
content.appendChild(rowElement);
}
content.oncopy = this._onPopoverCopy;
return content;
},
_presentPopoverForJavaScriptCoreTestRegressions: function(testName, element, popover, iteration)
{
if (iteration.javaScriptCoreTestResults.regressions)
var content = this._popoverContentForJavaScriptCoreTestRegressions(iteration, testName);
else {
var content = this._createLoadingIndicator(iteration, "javascriptcore test failures");
iteration.loadJavaScriptCoreTestResults(testName, function() {
popover.content = this._popoverContentForJavaScriptCoreTestRegressions(iteration, testName);
}.bind(this));
}
var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
popover.content = content;
popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
return true;
},
_presentPopoverForRevisionRange: function(element, popover, context)
{
var content = document.createElement("div");
content.className = "commit-history-popover";
// FIXME: Nothing guarantees that Trac has historical data for these revisions.
var linesForCommits = this._popoverLinesForCommitRange(context.trac, context.branch, context.firstRevision, context.lastRevision);
var line = document.createElement("div");
line.className = "title";
if (linesForCommits.length) {
line.textContent = "commits since previous result";
content.appendChild(line);
this._addDividerToPopover(content);
} else {
line.textContent = "no commits to " + context.branch.name + " since previous result";
content.appendChild(line);
}
for (var i = 0; i != linesForCommits.length; ++i)
content.appendChild(linesForCommits[i]);
var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
popover.content = content;
popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
return true;
},
_revisionContentWithPopoverForIteration: function(iteration, previousIteration, branch)
{
var repository = branch.repository;
var repositoryName = repository.name;
console.assert(iteration.revision[repositoryName]);
var trac = repository.trac;
var content = document.createElement("span");
content.textContent = this._formatRevisionForDisplay(iteration.revision[repositoryName], repository);
content.classList.add("revision-number");
if (!previousIteration)
return content;
var previousIterationRevision = previousIteration.revision[repositoryName];
console.assert(previousIterationRevision);
var previousIterationRevisionIndex = trac.indexOfRevision(previousIterationRevision);
if (previousIterationRevisionIndex === -1)
return content;
var firstRevision = trac.nextRevision(branch.name, previousIterationRevision);
if (firstRevision === Trac.NO_MORE_REVISIONS)
return content;
var firstRevisionIndex = trac.indexOfRevision(firstRevision);
console.assert(firstRevisionIndex !== -1);
var lastRevision = iteration.revision[repositoryName];
var lastRevisionIndex = trac.indexOfRevision(lastRevision);
if (lastRevisionIndex === -1)
return content;
if (firstRevisionIndex > lastRevisionIndex)
return content;
var context = {
trac: trac,
branch: branch,
firstRevision: firstRevision,
lastRevision: lastRevision,
};
new PopoverTracker(content, this._presentPopoverForRevisionRange.bind(this), context);
return content;
},
_addIterationHeadingToPopover: function(iteration, content, additionalText, additionalTextTarget)
{
var title = document.createElement("div");
title.className = "popover-iteration-heading";
var queueName = document.createElement("span");
queueName.className = "queue-name";
queueName.textContent = iteration.queue.id;
title.appendChild(queueName);
var buildbotLink = document.createElement("a");
buildbotLink.className = "buildbot-link";
buildbotLink.textContent = "build #" + iteration.id;
buildbotLink.href = iteration.queue.buildbot.buildPageURLForIteration(iteration);
buildbotLink.target = "_blank";
title.appendChild(buildbotLink);
if (additionalText) {
var elementType = additionalTextTarget ? "a" : "span";
var additionalTextElement = document.createElement(elementType);
additionalTextElement.className = "additional-text";
additionalTextElement.textContent = additionalText;
if (additionalTextTarget) {
additionalTextElement.href = additionalTextTarget;
additionalTextElement.target = "_blank";
}
title.appendChild(additionalTextElement);
}
content.appendChild(title);
},
revisionContentForIteration: function(iteration, previousDisplayedIteration)
{
var fragment = document.createDocumentFragment();
var shouldAddPlusSign = false;
var branches = iteration.queue.branches;
for (var i = 0; i < branches.length; ++i) {
var branch = branches[i];
if (!iteration.revision[branch.repository.name])
continue;
var content = this._revisionContentWithPopoverForIteration(iteration, previousDisplayedIteration, branch);
if (shouldAddPlusSign)
fragment.appendChild(document.createTextNode(" \uff0b "));
fragment.appendChild(content);
shouldAddPlusSign = true;
}
return fragment;
},
_testStepFailureDescription: function(failedStep)
{
if (!failedStep.failureCount)
return BuildbotIteration.TestSteps[failedStep.name] + " failed";
if (failedStep.failureCount === 1)
return BuildbotIteration.TestSteps[failedStep.name] + " failure";
return BuildbotIteration.TestSteps[failedStep.name] + " failures";
},
_testStepFailureDescriptionWithCount: function(failedStep)
{
if (!failedStep.failureCount)
return this._testStepFailureDescription(failedStep);
if (failedStep.tooManyFailures) {
// E.g. "50+ layout test failures", preventing line breaks around the "+".
return failedStep.failureCount + "\ufeff\uff0b\u00a0" + this._testStepFailureDescription(failedStep);
}
// E.g. "1 layout test failure", preventing line break after the number.
return failedStep.failureCount + "\u00a0" + this._testStepFailureDescription(failedStep);
},
appendBuildStyle: function(queues, defaultLabel, appendFunction)
{
queues.forEach(function(queue) {
var releaseLabel = document.createElement("a");
releaseLabel.classList.add("queueLabel");
releaseLabel.textContent = queue.heading ? queue.heading : defaultLabel;
releaseLabel.href = queue.overviewURL;
releaseLabel.target = "_blank";
this.element.appendChild(releaseLabel);
appendFunction.call(this, queue);
}.bind(this));
},
_updateQueues: function()
{
this.queues.forEach(function(queue) { queue.update(); });
},
_queueIterationsAdded: function(event)
{
this.updateSoon();
event.data.addedIterations.forEach(function(iteration) {
iteration.addEventListener(BuildbotIteration.Event.Updated, this._iterationUpdated, this);
iteration.addEventListener(BuildbotIteration.Event.UnauthorizedAccess, this._unauthorizedAccess, this);
}.bind(this));
},
_iterationUpdated: function(event)
{
this.updateSoon();
},
_newCommitsRecorded: function(event)
{
this.updateSoon();
},
_unauthorizedAccess: function(event)
{
this.updateSoon();
}
};