20d36c000fd1333739af5c39f5c50be4befd3e22
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotQueue.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 BuildbotQueue = function(buildbot, id, info)
27 {
28     BaseObject.call(this);
29
30     console.assert(buildbot);
31     console.assert(id);
32
33     this.buildbot = buildbot;
34     this.id = id;
35
36     this.branch = info.branch || null;
37     this.platform = info.platform.name || "unknown";
38     this.debug = info.debug || false;
39     this.builder = info.builder || false;
40     this.tester = info.tester || false;
41     this.architecture = info.architecture || null;
42     this.testCategory = info.testCategory || null;
43
44     this.iterations = [];
45     this._knownIterations = {};
46 };
47
48 BaseObject.addConstructorFunctions(BuildbotQueue);
49
50 BuildbotQueue.RecentIterationsToLoad = 10;
51
52 BuildbotQueue.Event = {
53     IterationsAdded: "iterations-added",
54     UnauthorizedAccess: "unauthorized-access"
55 };
56
57 BuildbotQueue.prototype = {
58     constructor: BuildbotQueue,
59     __proto__: BaseObject.prototype,
60
61     get baseURL()
62     {
63         return this.buildbot.baseURL + "json/builders/" + encodeURIComponent(this.id);
64     },
65
66     get allIterationsURL()
67     {
68         // Getting too many builds results in a timeout error, 10000 is OK.
69         return this.buildbot.baseURL + "json/builders/" + encodeURIComponent(this.id) + "/builds/_all/?max=10000";
70     },
71
72     get overviewURL()
73     {
74         return this.buildbot.baseURL + "builders/" + encodeURIComponent(this.id) + "?numbuilds=50";
75     },
76
77     get recentFailedIterationCount()
78     {
79         var firstFinishedIteration = this.mostRecentFinishedIteration;
80         var mostRecentSuccessfulIteration = this.mostRecentSuccessfulIteration;
81         return this.iterations.indexOf(mostRecentSuccessfulIteration) - this.iterations.indexOf(firstFinishedIteration);
82     },
83
84     get firstRecentUnsuccessfulIteration()
85     {
86         if (!this.iterations.length)
87             return null;
88
89         for (var i = 0; i < this.iterations.length; ++i) {
90             if (!this.iterations[i].finished || !this.iterations[i].successful)
91                 continue;
92             if (this.iterations[i - 1] && this.iterations[i - 1].finished && !this.iterations[i - 1].successful)
93                 return this.iterations[i - 1];
94             return null;
95         }
96
97         if (!this.iterations[this.iterations.length - 1].successful)
98             return this.iterations[this.iterations.length - 1];
99
100         return null;
101     },
102
103     get mostRecentFinishedIteration()
104     {
105         for (var i = 0; i < this.iterations.length; ++i) {
106             if (!this.iterations[i].finished)
107                 continue;
108             return this.iterations[i];
109         }
110
111         return null;
112     },
113
114     get mostRecentSuccessfulIteration()
115     {
116         for (var i = 0; i < this.iterations.length; ++i) {
117             if (!this.iterations[i].finished || !this.iterations[i].successful)
118                 continue;
119             return this.iterations[i];
120         }
121
122         return null;
123     },
124
125     _load: function(url, callback)
126     {
127         if (this.buildbot.needsAuthentication && this.buildbot.authenticationStatus === Buildbot.AuthenticationStatus.InvalidCredentials)
128             return;
129
130         JSON.load(
131             url,
132             function(data) {
133                 this.buildbot.isAuthenticated = true;
134                 callback(data);
135             }.bind(this),
136             function(data) {
137                 if (this.buildbot.isAuthenticated) {
138                     // FIXME (128006): Safari/WebKit should coalesce authentication requests with the same origin and authentication realm.
139                     // In absence of the fix, Safari presents additional authentication dialogs regardless of whether an earlier authentication
140                     // dialog was dismissed. As a way to ameliorate the user experience where a person authenticated successfully using an
141                     // earlier authentication dialog and cancelled the authentication dialog associated with the load for this queue, we call
142                     // ourself so that we can schedule another load, which should complete successfully now that we have credentials.
143                     this._load(url, callback);
144                     return;
145                 }
146                 if (data.errorType === JSON.LoadError && data.errorHTTPCode === 401) {
147                     this.buildbot.isAuthenticated = false;
148                     this.dispatchEventToListeners(BuildbotQueue.Event.UnauthorizedAccess, { });
149                 }
150             }.bind(this),
151             {withCredentials: this.buildbot.needsAuthentication}
152         );
153     },
154
155     loadMoreHistoricalIterations: function()
156     {
157         var indexOfFirstNewlyLoadingIteration;
158         for (var i = 0; i < this.iterations.length; ++i) {
159             if (indexOfFirstNewlyLoadingIteration !== undefined && i >= indexOfFirstNewlyLoadingIteration + BuildbotQueue.RecentIterationsToLoad)
160                 return;
161             var iteration = this.iterations[i];
162             if (!iteration.finished)
163                 continue;
164             if (iteration.isLoading) {
165                 // Caller lacks visibility into loading, so it is likely to call this function too often.
166                 // Give it a chance to analyze everything that's been already requested first, and then it can decide whether it needs more.
167                 return;
168             }
169             if (iteration.loaded && indexOfFirstNewlyLoadingIteration !== undefined) {
170                 // There was a gap between loaded iterations, which we've closed now.
171                 return;
172             }
173             if (!iteration.loaded) {
174                 if (indexOfFirstNewlyLoadingIteration === undefined)
175                     indexOfFirstNewlyLoadingIteration = i;
176                 iteration.update();
177             }
178         }
179     },
180
181     update: function()
182     {
183         this._load(this.baseURL, function(data) {
184             if (!(data.cachedBuilds instanceof Array))
185                 return;
186
187             var currentBuilds = {};
188             if (data.currentBuilds instanceof Array)
189                 data.currentBuilds.forEach(function(id) { currentBuilds[id] = true; });
190
191             var loadingStop = Math.max(0, data.cachedBuilds.length - BuildbotQueue.RecentIterationsToLoad);
192
193             var newIterations = [];
194
195             for (var i = data.cachedBuilds.length - 1; i >= 0; --i) {
196                 var iteration = this._knownIterations[data.cachedBuilds[i]];
197                 if (!iteration) {
198                     iteration = new BuildbotIteration(this, parseInt(data.cachedBuilds[i], 10), !(data.cachedBuilds[i] in currentBuilds));
199                     newIterations.push(iteration);
200                     this.iterations.push(iteration);
201                     this._knownIterations[iteration.id] = iteration;
202                 }
203
204                 if (i >= loadingStop && (!iteration.finished || !iteration.loaded))
205                     iteration.update();
206             }
207
208             if (!newIterations.length)
209                 return;
210
211             this.sortIterations();
212
213             this.dispatchEventToListeners(BuildbotQueue.Event.IterationsAdded, {addedIterations: newIterations});
214         }.bind(this));
215     },
216
217     loadAll: function(callback)
218     {
219         // FIXME: Don't load everything at once, do it incrementally as requested.
220         this._load(this.allIterationsURL, function(data) {
221             for (var idString in data) {
222                 console.assert(typeof idString === "string");
223                 iteration = new BuildbotIteration(this, data[idString]);
224                 this.iterations.push(iteration);
225                 this._knownIterations[iteration.id] = iteration;
226             }
227
228             callback(this);
229         }.bind(this));
230     },
231
232     sortIterations: function()
233     {
234         function compareIterations(a, b)
235         {
236             var result = b.openSourceRevision - a.openSourceRevision;
237             if (result)
238                 return result;
239
240             result = b.internalRevision - a.internalRevision;
241             if (result)
242                 return result;
243
244             return b.id - a.id;
245         }
246
247         this.iterations.sort(compareIterations);
248     }
249 };