1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5(function(){
6
7var lkgrURL = 'http://chromium-status.appspot.com/lkgr';
8
9// Interval at which to reload the non-CL bot status.
10var botStatusRefreshIntervalInMs = 60 * 1000;
11// Interval at which to check for LKGR updates.
12var lkgrRefreshIntervalInMs = 60 * 1000;
13
14function getClassForTryJobResult(result) {
15  // Some win bots seem to report a null result while building.
16  if (result === null)
17    result = buildbot.RUNNING;
18
19  switch (parseInt(result)) {
20  case buildbot.RUNNING:
21    return "running";
22
23  case buildbot.SUCCESS:
24    return "success";
25
26  case buildbot.WARNINGS:
27    return "warnings";
28
29  case buildbot.FAILURE:
30    return "failure";
31
32  case buildbot.SKIPPED:
33    return "skipped";
34
35  case buildbot.EXCEPTION:
36    return "exception";
37
38  case buildbot.RETRY:
39    return "retry";
40
41  case buildbot.NOT_STARTED:
42  default:
43    return "never";
44  }
45}
46
47// Remove try jobs that have been supplanted by newer runs.
48function filterOldTryJobs(tryJobs) {
49  var latest = {};
50  tryJobs.forEach(function(tryJob) {
51    if (!latest[tryJob.builder] ||
52        latest[tryJob.builder].buildnumber < tryJob.buildnumber)
53      latest[tryJob.builder] = tryJob;
54  });
55
56  var result = [];
57  tryJobs.forEach(function(tryJob) {
58    if (tryJob.buildnumber == latest[tryJob.builder].buildnumber)
59      result.push(tryJob);
60  });
61
62  return result;
63}
64
65function createTryJobAnchorTitle(tryJob, fullTryJob) {
66  var title = tryJob.builder;
67
68  if (!fullTryJob)
69    return title;
70
71  var stepText = [];
72  if (fullTryJob.currentStep)
73    stepText.push("running " + fullTryJob.currentStep.name);
74
75  if (fullTryJob.results == buildbot.FAILURE && fullTryJob.text) {
76    stepText.push(fullTryJob.text.join(" "));
77  } else {
78    // Sometimes a step can fail without setting the try job text.  Look
79    // through all the steps to identify if this is the case.
80    var text = [];
81    fullTryJob.steps.forEach(function(step) {
82      if (step.results[0] == buildbot.FAILURE)
83        text.push(step.results[1][0]);
84    });
85
86    if (text.length > 0) {
87      text.unshift("failure");
88      stepText.push(text.join(" "));
89    }
90  }
91
92  if (stepText.length > 0)
93    title += ": " + stepText.join("; ");
94
95  return title;
96}
97
98function createPatchsetStatusElement(patchset) {
99  var table = document.createElement("div");
100  table.className = "issue-status";
101
102  var tryJobs = filterOldTryJobs(patchset.try_job_results);
103  tryJobs.forEach(function(tryJob) {
104    var key = tryJob.builder + "-" + tryJob.buildnumber;
105    var fullTryJob = patchset.full_try_job_results &&
106        patchset.full_try_job_results[key];
107
108    var tryJobAnchor = document.createElement("a");
109    tryJobAnchor.textContent = "  ";
110    tryJobAnchor.title = createTryJobAnchorTitle(tryJob, fullTryJob);
111    tryJobAnchor.className = "issue-status-build " +
112      getClassForTryJobResult(tryJob.result);
113    tryJobAnchor.target = "_blank";
114    tryJobAnchor.href = tryJob.url;
115    table.appendChild(tryJobAnchor);
116  });
117
118  return table;
119}
120
121function getLastFullPatchsetWithTryJobs(issue) {
122  var index = issue.patchsets.length - 1;
123  var fullPatchsets = issue.full_patchsets;
124  while (index >= 0 &&
125         (!fullPatchsets ||
126          !fullPatchsets[issue.patchsets[index]] ||
127          !fullPatchsets[issue.patchsets[index]].try_job_results ||
128          fullPatchsets[issue.patchsets[index]].try_job_results.length == 0)) {
129    index--;
130  }
131
132  return index >= 0 ? fullPatchsets[issue.patchsets[index]] : null;
133}
134
135function createTryStatusRow(issue) {
136  var table = document.getElementById("status-table");
137
138  // Order by decreasing issue number.
139  var position =
140      document.getElementsByClassName("trunk-status-row")[0].rowIndex;
141  while (position > 0 &&
142         parseInt(issue.issue) >
143             parseInt(table.rows[position - 1].getAttribute("data-issue"))) {
144    position--;
145  }
146
147  var row = table.insertRow(position);
148  row.setAttribute("data-issue", issue.issue);
149
150  return row;
151}
152
153function updateIssueDisplay(issue) {
154  var codereviewBaseURL = "https://codereview.chromium.org";
155
156  var lastFullPatchset = getLastFullPatchsetWithTryJobs(issue);
157
158  var row = document.querySelector("*[data-issue='" + issue.issue + "']");
159  if (!lastFullPatchset) {
160    if (row)
161      row.parentNode.removeChild(row);
162    return;
163  }
164
165  if (!row)
166    row = createTryStatusRow(issue);
167
168  var label = row.childNodes[0] || row.insertCell(-1);
169  var status = row.childNodes[1] || row.insertCell(-1);
170
171  label.className = "status-label";
172  var clAnchor = label.childNodes[0] ||
173    label.appendChild(document.createElement("a"));
174  clAnchor.textContent = "CL " + issue.issue;
175  clAnchor.href = codereviewBaseURL + "/" + issue.issue;
176  clAnchor.title = issue.subject;
177  if (lastFullPatchset && lastFullPatchset.message)
178    clAnchor.title += " | " + lastFullPatchset.message;
179  clAnchor.target = "_blank";
180
181  var statusElement = createPatchsetStatusElement(lastFullPatchset);
182  if (status.childElementCount < 1)
183    status.appendChild(statusElement);
184  else
185    status.replaceChild(statusElement, status.firstChild);
186}
187
188function removeIssueDisplay(issueNumber) {
189  var row = document.querySelector("*[data-issue='" + issueNumber + "']");
190  row.parentNode.removeChild(row);
191}
192
193function addTryStatusRows() {
194  buildbot.getActiveIssues().forEach(updateIssueDisplay);
195}
196
197function updateLKGR(lkgr) {
198  var link = document.getElementById('link_lkgr');
199  link.textContent = 'LKGR (' + lkgr + ')';
200}
201
202function addBotStatusRow(bot, className) {
203  var table = document.getElementById("status-table");
204
205  var baseURL = "http://build.chromium.org/p/chromium" +
206    (bot.id != "" ? "." + bot.id : "");
207  var consoleURL = baseURL + "/console";
208  var statusURL = baseURL + "/horizontal_one_box_per_builder";
209
210  var row = table.insertRow(-1);
211  row.className = "trunk-status-row " + className;
212  var label = row.insertCell(-1);
213  label.className = "status-label";
214  var labelAnchor = document.createElement("a");
215  labelAnchor.href = consoleURL;
216  labelAnchor.target = "_blank";
217  labelAnchor.id = "link_" + bot.id;
218  labelAnchor.textContent = bot.label;
219  label.appendChild(labelAnchor);
220
221  var status = row.insertCell(-1);
222  status.className = "trunk-status-cell";
223  var statusIframe = document.createElement("iframe");
224  statusIframe.scrolling = "no";
225  statusIframe.src = statusURL;
226  status.appendChild(statusIframe);
227}
228
229function addBotStatusRows() {
230  var closerBots = [
231    {id: "", label: "Chromium"},
232    {id: "win", label: "Win"},
233    {id: "mac", label: "Mac"},
234    {id: "linux", label: "Linux"},
235    {id: "chromiumos", label: "ChromiumOS"},
236    {id: "chrome", label: "Official"},
237    {id: "memory", label: "Memory"}
238  ];
239
240  var otherBots = [
241    {id: "lkgr", label: "LKGR"},
242    {id: "perf", label: "Perf"},
243    {id: "memory.fyi", label: "Memory FYI"},
244    {id: "gpu", label: "GPU"},
245    {id: "gpu.fyi", label: "GPU FYI"}
246  ];
247
248  closerBots.forEach(function(bot) {
249    addBotStatusRow(bot, "closer-status-row");
250  });
251
252  otherBots.forEach(function(bot) {
253    addBotStatusRow(bot, "other-status-row");
254  });
255}
256
257function fillStatusTable() {
258  addBotStatusRows();
259  addTryStatusRows();
260}
261
262function main() {
263  buildbot.requestURL(lkgrURL, "text", updateLKGR);
264  fillStatusTable();
265
266  buildbot.getActiveIssues().setEventCallback(function(request) {
267    // NOTE(wittman): It doesn't appear that we can reliably detect closing of
268    // the popup and remove the event callback, so ensure the popup window is
269    // displayed before processing the event.
270    if (!chrome.extension.getViews({type: "popup"}))
271      return;
272
273    switch (request.event) {
274    case "issueUpdated":
275    case "issueAdded":
276      updateIssueDisplay(buildbot.getActiveIssues().getIssue(request.issue));
277      break;
278
279    case "issueRemoved":
280      removeIssueDisplay(request.issue);
281      break;
282    }
283  });
284
285  setInterval(function() {
286    buildbot.requestURL(lkgrURL, "text", updateLKGR);
287  }, lkgrRefreshIntervalInMs);
288
289  setInterval(function() {
290    var botStatusElements =
291        document.getElementsByClassName("trunk-status-iframe");
292    for (var i = 0; i < botStatusElements.length; i++)
293      // Force a reload of the iframe in a way that doesn't cause cross-domain
294      // policy violations.
295      botStatusElements.item(i).src = botStatusElements.item(i).src;
296  }, botStatusRefreshIntervalInMs);
297}
298
299main();
300
301})();
302