Dashboard code restructuring
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / QueueView.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 QueueView = function()
27 {
28     BaseObject.call(this);
29
30     this.element = document.createElement("div");
31     this.element.classList.add("queue-view");
32     this.element.__queueView = this;
33
34     this.updateTimer = null;
35     setTimeout(this._updateHiddenState.bind(this), 0); // Lets subclass constructor finish before calling _updateHiddenState.
36     settings.addSettingListener("hiddenPlatformFamilies", this._updateHiddenState.bind(this));
37 };
38
39 BaseObject.addConstructorFunctions(QueueView);
40
41 QueueView.UpdateInterval = 45000; // 45 seconds
42 QueueView.UpdateSoonTimeout = 1000; // 1 second
43
44 QueueView.prototype = {
45     constructor: QueueView,
46     __proto__: BaseObject.prototype,
47
48     updateSoon: function()
49     {
50         if (this._updateTimeout)
51             return;
52         this._updateTimeout = setTimeout(this.update.bind(this), QueueView.UpdateSoonTimeout);
53     },
54
55     update: function()
56     {
57         if (this._updateTimeout) {
58             clearTimeout(this._updateTimeout);
59             delete this._updateTimeout;
60         }
61
62         // Implemented by subclasses.
63     },
64
65     _updateHiddenState: function()
66     {
67         if (!settings.available())
68             return;
69
70         var hiddenPlatformFamilies = settings.getObject("hiddenPlatformFamilies");
71         var wasHidden = !this.updateTimer;
72         var isHidden = hiddenPlatformFamilies && hiddenPlatformFamilies.contains(settings.parsePlatformFamily(this.platform));
73
74         if (wasHidden && !isHidden) {
75             this._updateQueues();
76             this.updateTimer = setInterval(this._updateQueues.bind(this), QueueView.UpdateInterval);
77         } else if (!wasHidden && isHidden) {
78             clearInterval(this.updateTimer);
79             this.updateTimer = null;
80         }
81     },
82
83     addLinkToRow: function(rowElement, className, text, url)
84     {   
85         var linkElement = document.createElement("a");
86         linkElement.className = className;
87         linkElement.textContent = text;
88         linkElement.href = url;
89         linkElement.target = "_blank";
90         rowElement.appendChild(linkElement);
91     },  
92
93     addTextToRow: function(rowElement, className, text)
94     {   
95         var spanElement = document.createElement("span");
96         spanElement.className = className;
97         spanElement.textContent = text;
98         rowElement.appendChild(spanElement);
99     },
100
101     _addDividerToPopover: function(content)
102     {
103         var divider = document.createElement("div");
104         divider.className = "divider";
105         content.appendChild(divider);
106     },
107
108     _appendPendingRevisionCount: function(queue, latestIterationGetter)
109     {
110         var latestProductiveIteration = latestIterationGetter();
111         if (!latestProductiveIteration)
112             return;
113
114         var totalRevisionsBehind = 0;
115
116         // FIXME: To be 100% correct, we should also filter out changes that are ignored by
117         // the queue, see _should_file_trigger_build in wkbuild.py.
118         var branches = queue.branches;
119         for (var i = 0; i < branches.length; ++i) {
120             var branch = branches[i];
121             var repository = branch.repository;
122             var repositoryName = repository.name;
123             var trac = repository.trac;
124             var latestProductiveRevisionNumber = latestProductiveIteration.revision[repositoryName];
125             if (!latestProductiveRevisionNumber)
126                 continue;
127             if (!trac)
128                 continue;
129             if (!trac.latestRecordedRevisionNumber || trac.indexOfRevision(trac.oldestRecordedRevisionNumber) > trac.indexOfRevision(latestProductiveRevisionNumber)) {
130                 trac.loadMoreHistoricalData();
131                 return;
132             }
133
134             totalRevisionsBehind += trac.commitsOnBranchLaterThanRevision(branch.name, latestProductiveRevisionNumber).length;
135         }
136
137         if (!totalRevisionsBehind)
138             return;
139
140         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.
141         messageElement.textContent = totalRevisionsBehind + " " + (totalRevisionsBehind === 1 ? "revision behind" : "revisions behind");
142         var status = new StatusLineView(messageElement, StatusLineView.Status.NoBubble);
143         this.element.appendChild(status.element);
144
145         new PopoverTracker(messageElement, this._presentPopoverForPendingCommits.bind(this, latestIterationGetter), queue);
146     },
147
148     _popoverLinesForCommitRange: function(trac, branch, firstRevisionNumber, lastRevisionNumber)
149     {
150         function lineForCommit(trac, commit)
151         {
152             var result = document.createElement("div");
153             result.className = "pending-commit";
154
155             var linkElement = document.createElement("a");
156             linkElement.className = "revision";
157             linkElement.href = trac.revisionURL(commit.revisionNumber);
158             linkElement.target = "_blank";
159             linkElement.textContent = this._formatRevisionForDisplay(commit.revisionNumber, branch.repository);
160             result.appendChild(linkElement);
161
162             var authorElement = document.createElement("span");
163             authorElement.className = "author";
164             authorElement.textContent = commit.author;
165             result.appendChild(authorElement);
166
167             var titleElement = document.createElement("span");
168             titleElement.className = "title";
169             titleElement.innerHTML = commit.title.innerHTML;
170             result.appendChild(titleElement);
171
172             return result;
173         }
174
175         console.assert(trac.indexOfRevision(trac.oldestRecordedRevisionNumber) <= trac.indexOfRevision(firstRevisionNumber));
176
177         // FIXME: To be 100% correct, we should also filter out changes that are ignored by
178         // the queue, see _should_file_trigger_build in wkbuild.py.
179         var commits = trac.commitsOnBranchInRevisionRange(branch.name, firstRevisionNumber, lastRevisionNumber);
180         return commits.map(function(commit) {
181             return lineForCommit.call(this, trac, commit);
182         }, this).reverse();
183     },
184
185     _presentPopoverForPendingCommits: function(latestIterationGetter, element, popover, queue)
186     {
187         var latestProductiveIteration = latestIterationGetter();
188         if (!latestProductiveIteration)
189             return false;
190
191         var content = document.createElement("div");
192         content.className = "commit-history-popover";
193
194         var shouldAddDivider = false;
195         var branches = queue.branches;
196         for (var i = 0; i < branches.length; ++i) {
197             var branch = branches[i];
198             var repository = branch.repository;
199             var repositoryName = repository.name;
200             var trac = repository.trac;
201             var latestProductiveRevisionNumber = latestProductiveIteration.revision[repositoryName];
202             if (!latestProductiveRevisionNumber || !trac.latestRecordedRevisionNumber)
203                 continue;
204             var nextRevision = trac.nextRevision(branch.name, latestProductiveRevisionNumber);
205             if (nextRevision === Trac.NO_MORE_REVISIONS)
206                 continue;
207             var lines = this._popoverLinesForCommitRange(trac, branch, nextRevision, trac.latestRecordedRevisionNumber);
208             var length = lines.length;
209             if (length && shouldAddDivider)
210                 this._addDividerToPopover(content);
211             for (var j = 0; j < length; ++j)
212                 content.appendChild(lines[j]);
213             shouldAddDivider = shouldAddDivider || length > 0;
214         }
215
216         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
217         popover.content = content;
218         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
219
220         return true;
221     },
222
223     _formatRevisionForDisplay: function(revision, repository)
224     {
225         console.assert(repository.isSVN || repository.isGit, "Should not get here; " + repository.name + " did not specify a known VCS type.");
226         if (repository.isSVN)
227             return "r" + revision;
228         // Truncating for display. Git traditionally uses seven characters for a short hash.
229         return revision.substr(0, 7);
230     }
231 };