build.webkit.org/dashboard unnecessarily loads results of hidden queues
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / Trac.js
1 /*
2  * Copyright (C) 2013, 2014 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 Trac = function(baseURL, options)
27 {
28     BaseObject.call(this);
29
30     console.assert(baseURL);
31
32     this.baseURL = baseURL;
33     this._needsAuthentication = (typeof options === "object") && options[Trac.NeedsAuthentication] === true;
34
35     this.recordedCommits = []; // Will be sorted in ascending order.
36 };
37
38 BaseObject.addConstructorFunctions(Trac);
39
40 Trac.NeedsAuthentication = "needsAuthentication";
41 Trac.UpdateInterval = 45000; // 45 seconds
42
43 Trac.Event = {
44     CommitsUpdated: "commits-updated",
45     Loaded: "loaded"
46 };
47
48 Trac.prototype = {
49     constructor: Trac,
50     __proto__: BaseObject.prototype,
51
52     get latestRecordedRevisionNumber()
53     {
54         if (!this.recordedCommits.length)
55             return undefined;
56         return this.recordedCommits[this.recordedCommits.length - 1].revisionNumber;
57     },
58
59     revisionURL: function(revision)
60     {
61         return this.baseURL + "changeset/" + encodeURIComponent(revision);
62     },
63
64     _xmlTimelineURL: function(fromDate, toDate)
65     {
66         if (typeof fromDate === "undefined") {
67             fromDate = new Date();
68             toDate = new Date(fromDate);
69             // By default, get at least one full day of changesets, as the current day may have only begun.
70             fromDate.setDate(fromDate.getDate() - 1);
71         } else if (typeof toDate === "undefined")
72             toDate = fromDate;
73
74         console.assert(fromDate <= toDate);
75
76         var fromDay = new Date(fromDate.getFullYear(), fromDate.getMonth(), fromDate.getDate());
77         var toDay = new Date(toDate.getFullYear(), toDate.getMonth(), toDate.getDate());
78
79         return this.baseURL + "timeline?changeset=on&format=rss&max=0" +
80             "&from=" +  (toDay.getMonth() + 1) + "%2F" + toDay.getDate() + "%2F" + (toDay.getFullYear() % 100) +
81             "&daysback=" + ((toDay - fromDay) / 1000 / 60 / 60 / 24);
82     },
83
84     _convertCommitInfoElementToObject: function(doc, commitElement)
85     {
86         var link = doc.evaluate("./link", commitElement, null, XPathResult.STRING_TYPE).stringValue;
87         var revisionNumber = parseInt(/\d+$/.exec(link))
88
89         function tracNSResolver(prefix)
90         {
91             if (prefix == "dc")
92                 return "http://purl.org/dc/elements/1.1/";
93             return null;
94         }
95
96         var author = doc.evaluate("./author|dc:creator", commitElement, tracNSResolver, XPathResult.STRING_TYPE).stringValue;
97         var date = doc.evaluate("./pubDate", commitElement, null, XPathResult.STRING_TYPE).stringValue;
98         date = new Date(Date.parse(date));
99         var description = doc.evaluate("./description", commitElement, null, XPathResult.STRING_TYPE).stringValue;
100
101         // The feed contains a <title>, but it's not parsed as well as what we are getting from description.
102         var parsedDescription = document.createElement("div");
103         parsedDescription.innerHTML = description;
104         var title = document.createElement("div");
105         var node = parsedDescription.firstChild.firstChild;
106         while (node && node.tagName != "BR") {
107             title.appendChild(node.cloneNode(true));
108             node = node.nextSibling;
109         }
110
111         // For some reason, trac titles start with a newline. Delete it.
112         if (title.firstChild && title.firstChild.nodeType == Node.TEXT_NODE && title.firstChild.textContent.length > 0 && title.firstChild.textContent[0] == "\n")
113             title.firstChild.textContent = title.firstChild.textContent.substring(1);
114
115         return {
116             revisionNumber: revisionNumber,
117             link: link,
118             title: title,
119             author: author,
120             date: date,
121             description: description
122         };
123     },
124
125     _loaded: function(dataDocument)
126     {
127         var recordedRevisionNumbers = this.recordedCommits.reduce(function(previousResult, commit) {
128             previousResult[commit.revisionNumber] = commit;
129             return previousResult;
130         }, {});
131
132         var knownCommitsWereUpdated = false;
133         var newCommits = [];
134
135         var commitInfoElements = dataDocument.evaluate("/rss/channel/item", dataDocument, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
136         var commitInfoElement;
137         while (commitInfoElement = commitInfoElements.iterateNext()) {
138             var commit = this._convertCommitInfoElementToObject(dataDocument, commitInfoElement);
139             if (commit.revisionNumber in recordedRevisionNumbers) {
140                 // Author could have changed, as commit queue replaces it after the fact.
141                 console.assert(recordedRevisionNumbers[commit.revisionNumber].revisionNumber === commit.revisionNumber);
142                 if (recordedRevisionNumbers[commit.revisionNumber].author != commit.author) {
143                     recordedRevisionNumbers[commit.revisionNumber].author = commit.author;
144                     knownCommitWasUpdated = true;
145                 }
146             } else
147                 newCommits.push(commit);
148         }
149
150         if (newCommits.length)
151             this.recordedCommits = newCommits.concat(this.recordedCommits).sort(function(a, b) { return a.revisionNumber - b.revisionNumber; });
152
153         if (newCommits.length || knownCommitsWereUpdated)
154             this.dispatchEventToListeners(Trac.Event.CommitsUpdated, null);
155     },
156
157     load: function(fromDate, toDate)
158     {
159         loadXML(this._xmlTimelineURL(fromDate, toDate), function(dataDocument) {
160             this._loaded(dataDocument);
161             this.dispatchEventToListeners(Trac.Event.Loaded, [fromDate, toDate]);
162         }.bind(this), this._needsAuthentication ? { withCredentials: true } : {});
163     },
164
165     update: function()
166     {
167         loadXML(this._xmlTimelineURL(), this._loaded.bind(this), this._needsAuthentication ? { withCredentials: true } : {});
168     },
169
170     startPeriodicUpdates: function()
171     {
172         this.update();
173         this.updateTimer = setInterval(this.update.bind(this), Trac.UpdateInterval);
174     }
175 };