blob: e3d5e4a7b38c9d85c62fb508dbcacf9fb8a92d3b [file] [log] [blame]
/*
* Copyright (C) 2013 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.
*/
QueueView = function()
{
BaseObject.call(this);
this.element = document.createElement("div");
this.element.classList.add("queue-view");
this.element.__queueView = this;
this.updateTimer = null;
setTimeout(this._updateHiddenState.bind(this), 0); // Lets subclass constructor finish before calling _updateHiddenState.
settings.addSettingListener("hiddenPlatformFamilies", this._updateHiddenState.bind(this));
};
BaseObject.addConstructorFunctions(QueueView);
QueueView.UpdateInterval = 45000; // 45 seconds
QueueView.UpdateSoonTimeout = 1000; // 1 second
QueueView.prototype = {
constructor: QueueView,
__proto__: BaseObject.prototype,
updateSoon: function()
{
if (this._updateTimeout)
return;
this._updateTimeout = setTimeout(this.update.bind(this), QueueView.UpdateSoonTimeout);
},
update: function()
{
if (this._updateTimeout) {
clearTimeout(this._updateTimeout);
delete this._updateTimeout;
}
// Implemented by subclasses.
},
_updateHiddenState: function()
{
if (!settings.available())
return;
var hiddenPlatformFamilies = settings.getObject("hiddenPlatformFamilies");
var wasHidden = !this.updateTimer;
var isHidden = hiddenPlatformFamilies && hiddenPlatformFamilies.contains(settings.parsePlatformFamily(this.platform));
if (wasHidden && !isHidden) {
this._updateQueues();
this.updateTimer = setInterval(this._updateQueues.bind(this), QueueView.UpdateInterval);
} else if (!wasHidden && isHidden) {
clearInterval(this.updateTimer);
this.updateTimer = null;
}
},
addLinkToRow: function(rowElement, className, text, url)
{
var linkElement = document.createElement("a");
linkElement.className = className;
linkElement.textContent = text;
linkElement.href = url;
linkElement.target = "_blank";
rowElement.appendChild(linkElement);
},
addTextToRow: function(rowElement, className, text)
{
var spanElement = document.createElement("span");
spanElement.className = className;
spanElement.textContent = text;
rowElement.appendChild(spanElement);
},
_addDividerToPopover: function(content)
{
var divider = document.createElement("div");
divider.className = "divider";
content.appendChild(divider);
},
_appendPendingRevisionCount: function(queue, latestIterationGetter)
{
var latestProductiveIteration = latestIterationGetter();
if (!latestProductiveIteration)
return;
var totalRevisionsBehind = 0;
// FIXME: To be 100% correct, we should also filter out changes that are ignored by
// the queue, see _should_file_trigger_build in wkbuild.py.
var branches = queue.branches;
for (var i = 0; i < branches.length; ++i) {
var branch = branches[i];
var repository = branch.repository;
var repositoryName = repository.name;
var trac = repository.trac;
var latestProductiveRevisionNumber = latestProductiveIteration.revision[repositoryName];
if (!latestProductiveRevisionNumber)
continue;
if (!trac)
continue;
if (!trac.latestRecordedRevisionNumber || trac.indexOfRevision(trac.oldestRecordedRevisionNumber) > trac.indexOfRevision(latestProductiveRevisionNumber)) {
trac.loadMoreHistoricalData();
return;
}
totalRevisionsBehind += trac.commitsOnBranchLaterThanRevision(branch.name, latestProductiveRevisionNumber).length;
}
if (!totalRevisionsBehind)
return;
var messageElement = document.createElement("span"); // We can't just pass text to StatusLineView here, because we need an element that perfectly fits the text for popover positioning.
messageElement.textContent = totalRevisionsBehind + " " + (totalRevisionsBehind === 1 ? "revision behind" : "revisions behind");
var status = new StatusLineView(messageElement, StatusLineView.Status.NoBubble);
this.element.appendChild(status.element);
new PopoverTracker(messageElement, this._presentPopoverForPendingCommits.bind(this, latestIterationGetter), queue);
},
_popoverLinesForCommitRange: function(trac, branch, firstRevisionNumber, lastRevisionNumber)
{
function lineForCommit(trac, commit)
{
var result = document.createElement("div");
result.className = "pending-commit";
var linkElement = document.createElement("a");
linkElement.className = "revision";
linkElement.href = trac.revisionURL(commit.revisionNumber);
linkElement.target = "_blank";
linkElement.textContent = this._formatRevisionForDisplay(commit.revisionNumber, branch.repository);
result.appendChild(linkElement);
var authorElement = document.createElement("span");
authorElement.className = "author";
authorElement.textContent = commit.author;
result.appendChild(authorElement);
var titleElement = document.createElement("span");
titleElement.className = "title";
titleElement.innerHTML = commit.title.innerHTML;
result.appendChild(titleElement);
return result;
}
console.assert(trac.indexOfRevision(trac.oldestRecordedRevisionNumber) <= trac.indexOfRevision(firstRevisionNumber));
// FIXME: To be 100% correct, we should also filter out changes that are ignored by
// the queue, see _should_file_trigger_build in wkbuild.py.
var commits = trac.commitsOnBranchInRevisionRange(branch.name, firstRevisionNumber, lastRevisionNumber);
return commits.map(function(commit) {
return lineForCommit.call(this, trac, commit);
}, this).reverse();
},
_presentPopoverForPendingCommits: function(latestIterationGetter, element, popover, queue)
{
var latestProductiveIteration = latestIterationGetter();
if (!latestProductiveIteration)
return false;
var content = document.createElement("div");
content.className = "commit-history-popover";
var shouldAddDivider = false;
var branches = queue.branches;
for (var i = 0; i < branches.length; ++i) {
var branch = branches[i];
var repository = branch.repository;
var repositoryName = repository.name;
var trac = repository.trac;
var latestProductiveRevisionNumber = latestProductiveIteration.revision[repositoryName];
if (!latestProductiveRevisionNumber || !trac.latestRecordedRevisionNumber)
continue;
var nextRevision = trac.nextRevision(branch.name, latestProductiveRevisionNumber);
if (nextRevision === Trac.NO_MORE_REVISIONS)
continue;
var lines = this._popoverLinesForCommitRange(trac, branch, nextRevision, trac.latestRecordedRevisionNumber);
var length = lines.length;
if (length && shouldAddDivider)
this._addDividerToPopover(content);
for (var j = 0; j < length; ++j)
content.appendChild(lines[j]);
shouldAddDivider = shouldAddDivider || length > 0;
}
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;
},
_formatRevisionForDisplay: function(revision, repository)
{
console.assert(repository.isSVN || repository.isGit, "Should not get here; " + repository.name + " did not specify a known VCS type.");
if (repository.isSVN)
return "r" + revision;
// Truncating for display. Git traditionally uses seven characters for a short hash.
return revision.substr(0, 7);
},
_readableTimeString: function(seconds)
{
var secondsInHour = 60 * 60;
var hours = Math.floor(seconds / secondsInHour);
var minutes = Math.floor((seconds - hours * secondsInHour) / 60);
var hoursPart = "";
if (hours === 1)
hoursPart = "1\xa0hour and ";
else if (hours > 0)
hoursPart = hours + "\xa0hours and ";
if (!minutes)
return "less than a minute";
if (minutes === 1)
return hoursPart + "1\xa0minute";
return hoursPart + minutes + "\xa0minutes";
}
};