2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 BuildbotQueue = function(buildbot, id, info)
28 BaseObject.call(this);
30 console.assert(buildbot);
33 this.buildbot = buildbot;
36 // FIXME: Some of these are presentation only, and should be handled above BuildbotQueue level.
37 this.branches = info.branches;
38 this.platform = info.platform.name;
39 this.debug = info.debug;
40 this.builder = info.builder;
41 this.tester = info.tester;
42 this.performance = info.performance;
43 this.staticAnalyzer = info.staticAnalyzer;
44 this.leaks = info.leaks;
45 this.architecture = info.architecture;
46 this.testCategory = info.testCategory;
47 this.heading = info.heading;
48 this.crashesOnly = info.crashesOnly;
51 this._knownIterations = {};
53 // Some queues process changes out of order, but we need to display results for the latest commit,
54 // not the latest build. BuildbotQueue ensures that at least one productive iteration
55 // that was run in order gets loaded (if the queue had any productive iterations, of course).
56 this._hasLoadedIterationForInOrderResult = false;
59 BaseObject.addConstructorFunctions(BuildbotQueue);
61 BuildbotQueue.RecentIterationsToLoad = 10;
63 BuildbotQueue.Event = {
64 IterationsAdded: "iterations-added",
65 UnauthorizedAccess: "unauthorized-access"
68 BuildbotQueue.prototype = {
69 constructor: BuildbotQueue,
70 __proto__: BaseObject.prototype,
74 return this.buildbot.baseURL + "json/builders/" + encodeURIComponent(this.id);
77 get allIterationsURL()
79 // Getting too many builds results in a timeout error, 10000 is OK.
80 return this.buildbot.baseURL + "json/builders/" + encodeURIComponent(this.id) + "/builds/_all/?max=10000";
85 return this.buildbot.baseURL + "builders/" + encodeURIComponent(this.id) + "?numbuilds=50";
88 get recentFailedIterationCount()
90 var firstFinishedIteration = this.mostRecentFinishedIteration;
91 var mostRecentSuccessfulIteration = this.mostRecentSuccessfulIteration;
92 return this.iterations.indexOf(mostRecentSuccessfulIteration) - this.iterations.indexOf(firstFinishedIteration);
95 get firstRecentUnsuccessfulIteration()
97 if (!this.iterations.length)
100 for (var i = 0; i < this.iterations.length; ++i) {
101 if (!this.iterations[i].finished || !this.iterations[i].successful)
103 if (this.iterations[i - 1] && this.iterations[i - 1].finished && !this.iterations[i - 1].successful)
104 return this.iterations[i - 1];
108 if (!this.iterations[this.iterations.length - 1].successful)
109 return this.iterations[this.iterations.length - 1];
114 get mostRecentFinishedIteration()
116 for (var i = 0; i < this.iterations.length; ++i) {
117 if (!this.iterations[i].finished)
119 return this.iterations[i];
125 get mostRecentSuccessfulIteration()
127 for (var i = 0; i < this.iterations.length; ++i) {
128 if (!this.iterations[i].finished || !this.iterations[i].successful)
130 return this.iterations[i];
136 _load: function(url, callback)
138 if (this.buildbot.needsAuthentication && this.buildbot.authenticationStatus === Buildbot.AuthenticationStatus.InvalidCredentials)
144 this.buildbot.isAuthenticated = true;
148 if (data.errorType !== JSON.LoadError || data.errorHTTPCode !== 401)
150 if (this.buildbot.isAuthenticated) {
151 // FIXME (128006): Safari/WebKit should coalesce authentication requests with the same origin and authentication realm.
152 // In absence of the fix, Safari presents additional authentication dialogs regardless of whether an earlier authentication
153 // dialog was dismissed. As a way to ameliorate the user experience where a person authenticated successfully using an
154 // earlier authentication dialog and cancelled the authentication dialog associated with the load for this queue, we call
155 // ourself so that we can schedule another load, which should complete successfully now that we have credentials.
156 this._load(url, callback);
160 this.buildbot.isAuthenticated = false;
161 this.dispatchEventToListeners(BuildbotQueue.Event.UnauthorizedAccess, { });
163 {withCredentials: this.buildbot.needsAuthentication}
167 loadMoreHistoricalIterations: function()
169 var indexOfFirstNewlyLoadingIteration;
170 for (var i = 0; i < this.iterations.length; ++i) {
171 if (indexOfFirstNewlyLoadingIteration !== undefined && i >= indexOfFirstNewlyLoadingIteration + BuildbotQueue.RecentIterationsToLoad)
173 var iteration = this.iterations[i];
174 if (!iteration.finished)
176 if (iteration.isLoading) {
177 // Caller lacks visibility into loading, so it is likely to call this function too often.
178 // Give it a chance to analyze everything that's been already requested first, and then it can decide whether it needs more.
181 if (iteration.loaded && indexOfFirstNewlyLoadingIteration !== undefined) {
182 // There was a gap between loaded iterations, which we've closed now.
185 if (!iteration.loaded) {
186 if (indexOfFirstNewlyLoadingIteration === undefined)
187 indexOfFirstNewlyLoadingIteration = i;
188 if (!this._hasLoadedIterationForInOrderResult)
189 iteration.addEventListener(BuildbotIteration.Event.Updated, this._checkForInOrderResult.bind(this));
197 this._load(this.baseURL, function(data) {
198 if (!(data.cachedBuilds instanceof Array))
201 var currentBuilds = {};
202 if (data.currentBuilds instanceof Array)
203 data.currentBuilds.forEach(function(id) { currentBuilds[id] = true; });
205 var loadingStop = Math.max(0, data.cachedBuilds.length - BuildbotQueue.RecentIterationsToLoad);
207 var newIterations = [];
209 for (var i = data.cachedBuilds.length - 1; i >= 0; --i) {
210 var iteration = this._knownIterations[data.cachedBuilds[i]];
212 iteration = new BuildbotIteration(this, parseInt(data.cachedBuilds[i], 10), !(data.cachedBuilds[i] in currentBuilds));
213 newIterations.push(iteration);
214 this.iterations.push(iteration);
215 this._knownIterations[iteration.id] = iteration;
218 if (i >= loadingStop && (!iteration.finished || !iteration.loaded)) {
219 if (!this._hasLoadedIterationForInOrderResult)
220 iteration.addEventListener(BuildbotIteration.Event.Updated, this._checkForInOrderResult.bind(this));
225 if (!newIterations.length)
228 this.sortIterations();
230 this.dispatchEventToListeners(BuildbotQueue.Event.IterationsAdded, {addedIterations: newIterations});
234 _checkForInOrderResult: function(event)
236 if (this._hasLoadedIterationForInOrderResult)
238 var iterationsInOriginalOrder = this.iterations.concat().sort(function(a, b) { return b.id - a.id; });
239 for (var i = 0; i < iterationsInOriginalOrder.length - 1; ++i) {
240 var i1 = iterationsInOriginalOrder[i];
241 var i2 = iterationsInOriginalOrder[i + 1];
242 if (i1.productive && i2.loaded && this.compareIterationsByRevisions(i1, i2) < 0) {
243 this._hasLoadedIterationForInOrderResult = true;
247 this.loadMoreHistoricalIterations();
250 loadAll: function(callback)
252 // FIXME: Don't load everything at once, do it incrementally as requested.
253 this._load(this.allIterationsURL, function(data) {
254 for (var idString in data) {
255 console.assert(typeof idString === "string");
256 var iteration = new BuildbotIteration(this, data[idString]);
257 this.iterations.push(iteration);
258 this._knownIterations[iteration.id] = iteration;
261 this.sortIterations();
263 this._hasLoadedIterationForInOrderResult = true;
269 compareIterations: function(a, b)
271 result = this.compareIterationsByRevisions(a, b);
275 // A loaded iteration may not have revision numbers if it failed early, before svn steps finished.
276 result = b.loaded - a.loaded;
283 compareIterationsByRevisions: function(a, b)
285 var sortedRepositories = Dashboard.sortedRepositories;
286 for (var i = 0; i < sortedRepositories.length; ++i) {
287 var repositoryName = sortedRepositories[i].name;
288 var result = b.revision[repositoryName] - a.revision[repositoryName];
296 sortIterations: function()
298 this.iterations.sort(this.compareIterations.bind(this));