2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 BuildbotQueueView = function(queues)
30 this.queues = queues || [];
32 this.queues.forEach(function(queue) {
33 if (this.platform && this.platform != queue.platform)
34 throw "A buildbot view may not contain queues for multiple platforms."
36 this.platform = queue.platform;
37 queue.addEventListener(BuildbotQueue.Event.IterationsAdded, this._queueIterationsAdded, this);
38 queue.addEventListener(BuildbotQueue.Event.UnauthorizedAccess, this._unauthorizedAccess, this);
41 Dashboard.Repository.OpenSource.trac.addEventListener(Trac.Event.CommitsUpdated, this._newCommitsRecorded, this);
42 if (typeof Dashboard.Repository.Internal.trac != "undefined")
43 Dashboard.Repository.Internal.trac.addEventListener(Trac.Event.CommitsUpdated, this._newCommitsRecorded, this);
46 BaseObject.addConstructorFunctions(BuildbotQueueView);
48 BuildbotQueueView.UpdateInterval = 45000; // 45 seconds
49 BuildbotQueueView.UpdateSoonTimeout = 1000; // 1 second
51 BuildbotQueueView.prototype = {
52 constructor: BuildbotQueueView,
53 __proto__: QueueView.prototype,
55 _latestProductiveIteration: function(queue)
57 if (!queue.iterations.length)
59 if (queue.iterations[0].productive)
60 return queue.iterations[0];
61 return queue.iterations[0].previousProductiveIteration;
64 _appendUnauthorizedLineView: function(queue)
66 console.assert(queue.buildbot.needsAuthentication);
67 console.assert(!queue.buildbot.isAuthenticated);
68 var javascriptURL = "javascript:buildbots[" + buildbots.indexOf(queue.buildbot) + "].updateQueues(Buildbot.UpdateReason.Reauthenticate)";
69 var status = new StatusLineView("unauthorized", StatusLineView.Status.Unauthorized, "", null, javascriptURL);
70 this.element.appendChild(status.element);
73 _appendPendingRevisionCount: function(queue)
75 var latestProductiveIteration = this._latestProductiveIteration(queue);
76 if (!latestProductiveIteration)
79 var totalRevisionsBehind = 0;
81 // FIXME: To be 100% correct, we should also filter out changes that are ignored by
82 // the queue, see _should_file_trigger_build in wkbuild.py.
83 var branches = queue.branches;
84 for (var i = 0; i < branches.length; ++i) {
85 var branch = branches[i];
86 var repository = branch.repository;
87 var repositoryName = repository.name;
88 var trac = repository.trac;
89 var latestProductiveRevisionNumber = latestProductiveIteration.revision[repositoryName];
90 if (!latestProductiveRevisionNumber)
94 if (!trac.latestRecordedRevisionNumber || trac.oldestRecordedRevisionNumber > latestProductiveRevisionNumber) {
95 trac.loadMoreHistoricalData();
99 totalRevisionsBehind += trac.commitsOnBranch(branch.name, function(commit) { return commit.revisionNumber > latestProductiveRevisionNumber; }).length;
102 if (!totalRevisionsBehind)
105 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.
106 messageElement.textContent = totalRevisionsBehind + " " + (totalRevisionsBehind === 1 ? "revision behind" : "revisions behind");
107 var status = new StatusLineView(messageElement, StatusLineView.Status.NoBubble);
108 this.element.appendChild(status.element);
110 new PopoverTracker(messageElement, this._presentPopoverForPendingCommits.bind(this), queue);
113 _popoverLinesForCommitRange: function(trac, branch, firstRevisionNumber, lastRevisionNumber)
115 function lineForCommit(trac, commit)
117 var result = document.createElement("div");
118 result.className = "pending-commit";
120 var linkElement = document.createElement("a");
121 linkElement.className = "revision";
122 linkElement.href = trac.revisionURL(commit.revisionNumber);
123 linkElement.target = "_blank";
124 linkElement.textContent = "r" + commit.revisionNumber;
125 result.appendChild(linkElement);
127 var authorElement = document.createElement("span");
128 authorElement.className = "author";
129 authorElement.textContent = commit.author;
130 result.appendChild(authorElement);
132 var titleElement = document.createElement("span");
133 titleElement.className = "title";
134 titleElement.innerHTML = commit.title.innerHTML;
135 result.appendChild(titleElement);
140 console.assert(trac.oldestRecordedRevisionNumber <= firstRevisionNumber);
142 // FIXME: To be 100% correct, we should also filter out changes that are ignored by
143 // the queue, see _should_file_trigger_build in wkbuild.py.
144 var commits = trac.commitsOnBranch(branch, function(commit) { return commit.revisionNumber >= firstRevisionNumber && commit.revisionNumber <= lastRevisionNumber; });
145 return commits.map(function(commit) {
146 return lineForCommit(trac, commit);
150 _presentPopoverForPendingCommits: function(element, popover, queue)
152 var latestProductiveIteration = this._latestProductiveIteration(queue);
153 if (!latestProductiveIteration)
156 var content = document.createElement("div");
157 content.className = "commit-history-popover";
159 var shouldAddDivider = false;
160 var branches = queue.branches;
161 for (var i = 0; i < branches.length; ++i) {
162 var branch = branches[i];
163 var repository = branch.repository;
164 var repositoryName = repository.name;
165 var trac = repository.trac;
166 var latestProductiveRevisionNumber = latestProductiveIteration.revision[repositoryName];
167 if (!latestProductiveRevisionNumber || !trac.latestRecordedRevisionNumber)
169 var lines = this._popoverLinesForCommitRange(trac, branch.name, latestProductiveRevisionNumber + 1, trac.latestRecordedRevisionNumber);
170 var length = lines.length;
171 if (length && shouldAddDivider)
172 this._addDividerToPopover(content);
173 for (var j = 0; j < length; ++j)
174 content.appendChild(lines[j]);
175 shouldAddDivider = shouldAddDivider || length > 0;
178 var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
179 popover.content = content;
180 popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
185 _presentPopoverForRevisionRange: function(element, popover, context)
187 var content = document.createElement("div");
188 content.className = "commit-history-popover";
190 // FIXME: Nothing guarantees that Trac has historical data for these revisions.
191 var linesForCommits = this._popoverLinesForCommitRange(context.trac, context.branch, context.firstRevision, context.lastRevision);
193 var line = document.createElement("div");
194 line.className = "title";
196 if (linesForCommits.length) {
197 line.textContent = "commits since previous result";
198 content.appendChild(line);
199 this._addDividerToPopover(content);
201 line.textContent = "no commits to " + context.branch + " since previous result";
202 content.appendChild(line);
205 for (var i = 0; i != linesForCommits.length; ++i)
206 content.appendChild(linesForCommits[i]);
208 var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
209 popover.content = content;
210 popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
215 _revisionContentWithPopoverForIteration: function(iteration, previousIteration, branch)
217 var repository = branch.repository;
218 var repositoryName = repository.name;
219 console.assert(iteration.revision[repositoryName]);
220 var content = document.createElement("span");
221 var revision = iteration.revision[repositoryName];
222 if (repository.isSVN)
223 content.textContent = "r" + revision;
224 else if (repository.isGit) {
225 // Truncating for display. Git traditionally uses seven characters for a short hash.
226 content.textContent = revision.substr(0, 7);
228 console.assert(false, "Should not get here; " + repository.name + " did not specify a known VCS type.");
229 content.classList.add("revision-number");
231 if (previousIteration) {
232 console.assert(previousIteration.revision[repositoryName]);
234 trac: repository.trac,
236 firstRevision: previousIteration.revision[repositoryName] + 1,
237 lastRevision: iteration.revision[repositoryName]
239 if (context.firstRevision <= context.lastRevision)
240 new PopoverTracker(content, this._presentPopoverForRevisionRange.bind(this), context);
246 _addIterationHeadingToPopover: function(iteration, content, additionalText)
248 var title = document.createElement("div");
249 title.className = "popover-iteration-heading";
251 var queueName = document.createElement("span");
252 queueName.className = "queue-name";
253 queueName.textContent = iteration.queue.id;
254 title.appendChild(queueName);
256 var buildbotLink = document.createElement("a");
257 buildbotLink.className = "buildbot-link";
258 buildbotLink.textContent = "build #" + iteration.id;
259 buildbotLink.href = iteration.queue.buildbot.buildPageURLForIteration(iteration);
260 buildbotLink.target = "_blank";
261 title.appendChild(buildbotLink);
263 if (additionalText) {
264 var additionalTextElement = document.createElement("span");
265 additionalTextElement.className = "additional-text";
266 additionalTextElement.textContent = additionalText;
267 title.appendChild(additionalTextElement);
270 content.appendChild(title);
273 _addDividerToPopover: function(content)
275 var divider = document.createElement("div");
276 divider.className = "divider";
277 content.appendChild(divider);
280 revisionContentForIteration: function(iteration, previousDisplayedIteration)
282 var fragment = document.createDocumentFragment();
283 var shouldAddPlusSign = false;
284 var branches = iteration.queue.branches;
285 for (var i = 0; i < branches.length; ++i) {
286 var branch = branches[i];
287 if (!iteration.revision[branch.repository.name])
289 var content = this._revisionContentWithPopoverForIteration(iteration, previousDisplayedIteration, branch);
290 if (shouldAddPlusSign)
291 fragment.appendChild(document.createTextNode(" \uff0b "));
292 fragment.appendChild(content);
293 shouldAddPlusSign = true;
295 console.assert(fragment.childNodes.length);
299 appendBuildStyle: function(queues, defaultLabel, appendFunction)
301 queues.forEach(function(queue) {
302 var releaseLabel = document.createElement("a");
303 releaseLabel.classList.add("queueLabel");
304 releaseLabel.textContent = queue.heading ? queue.heading : defaultLabel;
305 releaseLabel.href = queue.overviewURL;
306 releaseLabel.target = "_blank";
307 this.element.appendChild(releaseLabel);
309 appendFunction.call(this, queue);
313 _updateQueues: function()
315 this.queues.forEach(function(queue) { queue.update(); });
318 _queueIterationsAdded: function(event)
322 event.data.addedIterations.forEach(function(iteration) {
323 iteration.addEventListener(BuildbotIteration.Event.Updated, this._iterationUpdated, this);
324 iteration.addEventListener(BuildbotIteration.Event.UnauthorizedAccess, this._unauthorizedAccess, this);
328 _iterationUpdated: function(event)
333 _newCommitsRecorded: function(event)
338 _unauthorizedAccess: function(event)