Dashboard code restructuring
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotCombinedQueueView.js
1 /*
2  * Copyright (C) 2013, 2015 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 BuildbotCombinedQueueView = function(queue)
27 {
28     console.assert(queue.branches === undefined);
29     var indicesOfFirstQueueWithRepository = {};
30     var combinedQueues = queue.combinedQueues;
31     var buildbot = combinedQueues[0].buildbot;
32     for (var i = 0; i < combinedQueues.length; ++i) {
33         var subQueue = combinedQueues[i];
34         console.assert(buildbot === subQueue.buildbot);
35         var branches = subQueue.branches;
36         for (var j = 0; j < branches.length; ++j) {
37             var branch = branches[j];
38             var repositoryName = branch.repository.name;
39             var expected = indicesOfFirstQueueWithRepository[repositoryName];
40             if (expected === undefined) {
41                 indicesOfFirstQueueWithRepository[repositoryName] = { queueIndex: i, branchIndex: j };
42                 continue;
43             }
44             var expectedBranch = combinedQueues[expected.queueIndex].branches[expected.branchIndex];
45             var message = queue.id + ": " + branch.name + " === combinedQueues[" + i + "].branch[" + j + "] ";
46             message += "=== combinedQueues[" + expected.queueIndex + "].branch[" + expected.branchIndex + "] === " + expectedBranch.name;
47             console.assert(branch.name === expectedBranch.name, message);
48         }
49     }
50
51     BuildbotQueueView.call(this, queue.combinedQueues);
52
53     this._alwaysExpand = false;
54     this.combinedQueue = queue;
55     this.update();
56 };
57
58 BaseObject.addConstructorFunctions(BuildbotCombinedQueueView);
59
60 BuildbotCombinedQueueView.prototype = {
61     constructor: BuildbotCombinedQueueView,
62     __proto__: BuildbotQueueView.prototype,
63
64     update: function()
65     {
66         BuildbotQueueView.prototype.update.call(this);
67
68         this.element.removeChildren();
69
70         if (!this._alwaysExpand && this._queuesShouldDisplayCombined()) {
71             var releaseLabel = document.createElement("a");
72             releaseLabel.classList.add("queueLabel");
73             releaseLabel.href = "#";
74             releaseLabel.textContent = this.combinedQueue.heading;
75             releaseLabel.onclick = function() { this._alwaysExpand = true; this.update(); return false; }.bind(this);
76             this.element.appendChild(releaseLabel);
77
78             var queue = this.queues[0]; // All queues in the combined queue are from the same buildbot.
79             if (queue.buildbot.needsAuthentication && !queue.buildbot.isAuthenticated) {
80                 this._appendUnauthorizedLineView(queue);
81                 return;
82             }
83
84             // Show the revision for the slowest queue, because we don't know if any newer revisions are green on all queues.
85             // This can be slightly misleading after fixing a problem, because we can show a known broken revision as green.
86             var slowestQueue = this.queues.slice().sort(function(a, b) { return BuildbotQueue.prototype.compareIterationsByRevisions(a.mostRecentSuccessfulIteration, b.mostRecentSuccessfulIteration); }).pop();
87             this._appendPendingRevisionCount(slowestQueue, this._latestProductiveIteration.bind(this, slowestQueue));
88
89             var message = this.revisionContentForIteration(slowestQueue.mostRecentSuccessfulIteration);
90             var statusMessagePassed = "all " + (queue.builder ? "builds succeeded" : "tests passed");
91             var status = new StatusLineView(message, StatusLineView.Status.Good, statusMessagePassed, null, null);
92             new PopoverTracker(status.statusBubbleElement, this._presentPopoverForCombinedGreenBubble.bind(this));
93             this.element.appendChild(status.element);
94         } else {
95             this.appendBuildStyle.call(this, this.queues, null, function(queue) {
96                 if (queue.buildbot.needsAuthentication && !queue.buildbot.isAuthenticated) {
97                     this._appendUnauthorizedLineView(queue);
98                     return;
99                 }
100
101                 this._appendPendingRevisionCount(queue, this._latestProductiveIteration.bind(this, queue));
102
103                 var firstRecentUnsuccessfulIteration = queue.firstRecentUnsuccessfulIteration;
104                 var mostRecentFinishedIteration = queue.mostRecentFinishedIteration;
105                 var mostRecentSuccessfulIteration = queue.mostRecentSuccessfulIteration;
106
107                 if (firstRecentUnsuccessfulIteration && firstRecentUnsuccessfulIteration.loaded && mostRecentFinishedIteration && mostRecentFinishedIteration.loaded) {
108                     console.assert(!mostRecentFinishedIteration.successful);
109                     var message = this.revisionContentForIteration(mostRecentFinishedIteration, mostRecentFinishedIteration.productive ? mostRecentSuccessfulIteration : null);
110                     if (!mostRecentFinishedIteration.productive)
111                         var status = StatusLineView.Status.Danger;
112                     else {
113                         // Direct links to some common logs.
114                         var url = mostRecentFinishedIteration.failureLogURL("build log");
115                         if (!url)
116                             url = mostRecentFinishedIteration.failureLogURL("stdio");
117                         var status = StatusLineView.Status.Bad;
118                     }
119
120                     // Show a popover when the URL is not a main build page one, because there are usually multiple logs, and it's good to provide a choice.
121                     var needsPopover = !url;
122
123                     // Some other step failed, link to main buildbot page for the iteration.
124                     if (!url)
125                         url = queue.buildbot.buildPageURLForIteration(mostRecentFinishedIteration);
126                     var status = new StatusLineView(message, status, mostRecentFinishedIteration.text, null, url);
127                     this.element.appendChild(status.element);
128
129                     if (needsPopover)
130                         new PopoverTracker(status.statusBubbleElement, this._presentIndividualQueuePopover.bind(this), mostRecentFinishedIteration);
131                 }
132
133                 var statusMessagePassed = "all " + (queue.builder ? "builds succeeded" : "tests passed");
134                 if (mostRecentSuccessfulIteration && mostRecentSuccessfulIteration.loaded) {
135                     var message = this.revisionContentForIteration(mostRecentSuccessfulIteration);
136                     var url = queue.buildbot.buildPageURLForIteration(mostRecentSuccessfulIteration);
137                     var status = new StatusLineView(message, StatusLineView.Status.Good, firstRecentUnsuccessfulIteration ? "last succeeded" : statusMessagePassed, null, url);
138                     this.element.appendChild(status.element);
139                 } else {
140                     var status = new StatusLineView("unknown", StatusLineView.Status.Neutral, firstRecentUnsuccessfulIteration ? "last succeeded" : statusMessagePassed);
141                     this.element.appendChild(status.element);
142
143                     if (firstRecentUnsuccessfulIteration) {
144                         // We have a failed iteration but no successful. It might be further back in time.
145                         queue.loadMoreHistoricalIterations();
146                     }
147                 }
148             });
149         }
150     },
151
152     // All queues are green, or all are unauthorized (the latter case always applies to all queues, because they are all from the same buildbot).
153     _queuesShouldDisplayCombined: function()
154     {
155         for (var i = 0, end = this.queues.length; i < end; ++i) {
156             var queue = this.queues[i];
157             if (queue.buildbot.needsAuthentication && !queue.buildbot.isAuthenticated)
158                 return true;
159             if (!queue.mostRecentFinishedIteration || !queue.mostRecentFinishedIteration.successful)
160                 return false;
161         }
162         return true;
163     },
164
165     _presentPopoverForCombinedGreenBubble: function(element, popover)
166     {
167         var content = document.createElement("div");
168         content.className = "combined-queue-popover";
169
170         var title = document.createElement("div");
171         title.className = "popover-iteration-heading";
172         title.textContent = "latest tested revisions";
173         content.appendChild(title);
174
175         this._addDividerToPopover(content);
176
177         function addQueue(queue, view) {
178             var line = document.createElement("div");
179             var link = document.createElement("a");
180             link.className = "queue-link";
181             link.href = queue.overviewURL;
182             link.textContent = queue.heading;
183             link.target = "_blank";
184             line.appendChild(link);
185             var revision = document.createElement("span");
186             revision.className = "revision";
187             revision.appendChild(view.revisionContentForIteration(queue.mostRecentSuccessfulIteration));
188             line.appendChild(revision);
189             content.appendChild(line);
190         }
191
192         for (var i = 0, end = this.queues.length; i < end; ++i)
193             addQueue(this.queues[i], this);
194
195         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
196         popover.content = content;
197         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
198         return true;
199     },
200
201     _presentIndividualQueuePopover: function(element, popover, iteration)
202     {
203         var content = document.createElement("div");
204         content.className = "build-logs-popover";
205
206         function addLog(name, url) {
207             var line = document.createElement("a");
208             line.className = "build-log-link";
209             line.href = url;
210             line.textContent = name;
211             line.target = "_blank";
212             content.appendChild(line);
213         }
214
215         this._addIterationHeadingToPopover(iteration, content);
216         this._addDividerToPopover(content);
217         
218         var logsHeadingLine = document.createElement("div");
219         logsHeadingLine.className = "build-logs-heading";
220         logsHeadingLine.textContent = iteration.firstFailedStepName + " failed";
221         content.appendChild(logsHeadingLine);
222
223         for (var i = 0, end = iteration.failureLogs.length; i < end; ++i)
224             addLog(iteration.failureLogs[i][0], iteration.failureLogs[i][1]);
225
226         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
227         popover.content = content;
228         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
229         return true;
230     },
231 };