Standardize the usage of "branch" vs. "branchName". https://bugs.webkit.org/show_bug...
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotQueueView.js
1 /*
2  * Copyright (C) 2013 Apple 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 BuildbotQueueView = function(queues)
27 {
28     QueueView.call(this);
29
30     this.queues = queues || [];
31
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."
35         else
36             this.platform = queue.platform;
37         queue.addEventListener(BuildbotQueue.Event.IterationsAdded, this._queueIterationsAdded, this);
38         queue.addEventListener(BuildbotQueue.Event.UnauthorizedAccess, this._unauthorizedAccess, this);
39     }.bind(this));
40
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);
44 };
45
46 BaseObject.addConstructorFunctions(BuildbotQueueView);
47
48 BuildbotQueueView.UpdateInterval = 45000; // 45 seconds
49 BuildbotQueueView.UpdateSoonTimeout = 1000; // 1 second
50
51 BuildbotQueueView.prototype = {
52     constructor: BuildbotQueueView,
53     __proto__: QueueView.prototype,
54
55     _latestProductiveIteration: function(queue)
56     {
57         if (!queue.iterations.length)
58             return null;
59         if (queue.iterations[0].productive)
60             return queue.iterations[0];
61         return queue.iterations[0].previousProductiveIteration;
62     },
63
64     _appendUnauthorizedLineView: function(queue)
65     {
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);
71     },
72
73     _appendPendingRevisionCount: function(queue)
74     {
75         var latestProductiveIteration = this._latestProductiveIteration(queue);
76         if (!latestProductiveIteration)
77             return;
78
79         var totalRevisionsBehind = 0;
80
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)
91                 continue;
92             if (!trac)
93                 continue;
94             if (!trac.latestRecordedRevisionNumber || trac.oldestRecordedRevisionNumber > latestProductiveRevisionNumber) {
95                 trac.loadMoreHistoricalData();
96                 return;
97             }
98
99             totalRevisionsBehind += trac.commitsOnBranch(branch.name, function(commit) { return commit.revisionNumber > latestProductiveRevisionNumber; }).length;
100         }
101
102         if (!totalRevisionsBehind)
103             return;
104
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);
109
110         new PopoverTracker(messageElement, this._presentPopoverForPendingCommits.bind(this), queue);
111     },
112
113     _popoverLinesForCommitRange: function(trac, branchName, firstRevisionNumber, lastRevisionNumber)
114     {
115         function lineForCommit(trac, commit)
116         {
117             var result = document.createElement("div");
118             result.className = "pending-commit";
119
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);
126
127             var authorElement = document.createElement("span");
128             authorElement.className = "author";
129             authorElement.textContent = commit.author;
130             result.appendChild(authorElement);
131
132             var titleElement = document.createElement("span");
133             titleElement.className = "title";
134             titleElement.innerHTML = commit.title.innerHTML;
135             result.appendChild(titleElement);
136
137             return result;
138         }
139
140         console.assert(trac.oldestRecordedRevisionNumber <= firstRevisionNumber);
141
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(branchName, function(commit) { return commit.revisionNumber >= firstRevisionNumber && commit.revisionNumber <= lastRevisionNumber; });
145         return commits.map(function(commit) {
146             return lineForCommit(trac, commit);
147         }, this).reverse();
148     },
149
150     _presentPopoverForPendingCommits: function(element, popover, queue)
151     {
152         var latestProductiveIteration = this._latestProductiveIteration(queue);
153         if (!latestProductiveIteration)
154             return false;
155
156         var content = document.createElement("div");
157         content.className = "commit-history-popover";
158
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)
168                 continue;
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;
176         }
177
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]);
181
182         return true;
183     },
184
185     _presentPopoverForRevisionRange: function(element, popover, context)
186     {
187         var content = document.createElement("div");
188         content.className = "commit-history-popover";
189
190         // FIXME: Nothing guarantees that Trac has historical data for these revisions.
191         var linesForCommits = this._popoverLinesForCommitRange(context.trac, context.branchName, context.firstRevision, context.lastRevision);
192
193         var line = document.createElement("div");
194         line.className = "title";
195
196         if (linesForCommits.length) {
197             line.textContent = "commits since previous result";
198             content.appendChild(line);
199             this._addDividerToPopover(content);
200         } else {
201             line.textContent = "no commits to " + context.branchName + " since previous result";
202             content.appendChild(line);
203         }
204
205         for (var i = 0; i != linesForCommits.length; ++i)
206             content.appendChild(linesForCommits[i]);
207
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]);
211
212         return true;
213     },
214
215     _revisionContentWithPopoverForIteration: function(iteration, previousIteration, branch)
216     {
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);
227         } else
228             console.assert(false, "Should not get here; " + repository.name + " did not specify a known VCS type.");
229         content.classList.add("revision-number");
230
231         if (previousIteration) {
232             console.assert(previousIteration.revision[repositoryName]);
233             var context = {
234                 trac: repository.trac,
235                 branchName: branch.name,
236                 firstRevision: previousIteration.revision[repositoryName] + 1,
237                 lastRevision: iteration.revision[repositoryName]
238             };
239             if (context.firstRevision <= context.lastRevision)
240                 new PopoverTracker(content, this._presentPopoverForRevisionRange.bind(this), context);
241         }
242
243         return content;
244     },
245
246     _addIterationHeadingToPopover: function(iteration, content, additionalText)
247     {
248         var title = document.createElement("div");
249         title.className = "popover-iteration-heading";
250
251         var queueName = document.createElement("span");
252         queueName.className = "queue-name";
253         queueName.textContent = iteration.queue.id;
254         title.appendChild(queueName);
255
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);
262
263         if (additionalText) {
264             var additionalTextElement = document.createElement("span");
265             additionalTextElement.className = "additional-text";
266             additionalTextElement.textContent = additionalText;
267             title.appendChild(additionalTextElement);
268         }
269
270         content.appendChild(title);
271     },
272
273     _addDividerToPopover: function(content)
274     {
275         var divider = document.createElement("div");
276         divider.className = "divider";
277         content.appendChild(divider);
278     },
279
280     revisionContentForIteration: function(iteration, previousDisplayedIteration)
281     {
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])
288                 continue;
289             var content = this._revisionContentWithPopoverForIteration(iteration, previousDisplayedIteration, branch);
290             if (shouldAddPlusSign)
291                 fragment.appendChild(document.createTextNode(" \uff0b "));
292             fragment.appendChild(content);
293             shouldAddPlusSign = true;
294         }
295         console.assert(fragment.childNodes.length);
296         return fragment;
297     },
298
299     appendBuildStyle: function(queues, defaultLabel, appendFunction)
300     {
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);
308
309             appendFunction.call(this, queue);
310         }.bind(this));
311     },
312
313     _updateQueues: function()
314     {
315         this.queues.forEach(function(queue) { queue.update(); });
316     },
317
318     _queueIterationsAdded: function(event)
319     {
320         this.updateSoon();
321
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);
325         }.bind(this));
326     },
327
328     _iterationUpdated: function(event)
329     {
330         this.updateSoon();
331     },
332     
333     _newCommitsRecorded: function(event)
334     {
335         this.updateSoon();
336     },
337
338     _unauthorizedAccess: function(event)
339     {
340         this.updateSoon();
341     }
342
343 };