blob: 09aa6ca1bfc1b9cda30b95e85f84bc7c00b8b022 [file] [log] [blame]
Timo Tijhof51516cd2013-04-09 01:32:29 +02001// Client script for Zuul status page
2//
3// Copyright 2012 OpenStack Foundation
4// Copyright 2013 Timo Tijhof
5// Copyright 2013 Wikimedia Foundation
Joshua Hesketh6b1a2182014-03-21 14:40:04 +11006// Copyright 2014 Rackspace Australia
Timo Tijhof51516cd2013-04-09 01:32:29 +02007//
8// Licensed under the Apache License, Version 2.0 (the "License"); you may
9// not use this file except in compliance with the License. You may obtain
10// a copy of the License at
11//
12// http://www.apache.org/licenses/LICENSE-2.0
13//
14// Unless required by applicable law or agreed to in writing, software
15// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17// License for the specific language governing permissions and limitations
18// under the License.
19
20(function ($) {
Joshua Hesketh4863b602014-03-21 14:19:06 +110021 var $container, $msg, $indicator, $queueInfo, $queueEventsNum,
Joshua Heskethcbdcca12014-03-20 16:06:25 +110022 $queueResultsNum, $pipelines, $jq;
Joshua Hesketh298c4912014-03-20 16:06:25 +110023 var xhr, zuul,
Timo Tijhof51516cd2013-04-09 01:32:29 +020024 demo = location.search.match(/[?&]demo=([^?&]*)/),
Joshua Hesketh668700b2014-03-21 14:25:53 +110025 source_url = location.search.match(/[?&]source_url=([^?&]*)/),
Timo Tijhof51516cd2013-04-09 01:32:29 +020026 source = demo ?
27 './status-' + (demo[1] || 'basic') + '.json-sample' :
James E. Blair7c7ed7a2013-05-15 13:13:26 -070028 'status.json';
Joshua Hesketh668700b2014-03-21 14:25:53 +110029 source = source_url ? source_url[1] : source;
Timo Tijhof51516cd2013-04-09 01:32:29 +020030
31 zuul = {
32 enabled: true,
Joshua Heskethdb8046e2014-03-21 18:42:25 +110033 collapsed_exceptions: [],
Timo Tijhof51516cd2013-04-09 01:32:29 +020034
35 schedule: function () {
36 if (!zuul.enabled) {
37 setTimeout(zuul.schedule, 5000);
38 return;
39 }
40 zuul.update().complete(function () {
41 setTimeout(zuul.schedule, 5000);
42 });
43 },
44
45 /** @return {jQuery.Promise} */
46 update: function () {
47 // Cancel the previous update if it hasn't completed yet.
48 if (xhr) {
49 xhr.abort();
50 }
51
52 zuul.emit('update-start');
53
Joshua Hesketh298c4912014-03-20 16:06:25 +110054 xhr = $.getJSON(source)
55 .done(function (data) {
56 if ('message' in data) {
Joshua Hesketh4863b602014-03-21 14:19:06 +110057 $msg.removeClass('alert-danger').addClass('alert-info');
Joshua Hesketh298c4912014-03-20 16:06:25 +110058 $msg.text(data.message);
Joshua Hesketh4863b602014-03-21 14:19:06 +110059 $msg.show();
Joshua Hesketh298c4912014-03-20 16:06:25 +110060 } else {
61 $msg.empty();
Joshua Hesketh4863b602014-03-21 14:19:06 +110062 $msg.hide();
Joshua Hesketh298c4912014-03-20 16:06:25 +110063 }
Timo Tijhof51516cd2013-04-09 01:32:29 +020064
Joshua Hesketh298c4912014-03-20 16:06:25 +110065 if ('zuul_version' in data) {
66 $('#zuul-version-span').text(data['zuul_version']);
67 }
68 if ('last_reconfigured' in data) {
69 var last_reconfigured =
70 new Date(data['last_reconfigured']);
71 $('#last-reconfigured-span').text(
72 last_reconfigured.toString());
73 }
74
75 $pipelines.html('');
76 $.each(data.pipelines, function (i, pipeline) {
77 $pipelines.append(zuul.format.pipeline(pipeline));
78 });
79
80 $queueEventsNum.text(
81 data.trigger_event_queue ?
82 data.trigger_event_queue.length : '0'
83 );
84 $queueResultsNum.text(
85 data.result_event_queue ?
86 data.result_event_queue.length : '0'
87 );
88 })
89 .fail(function (err, jqXHR, errMsg) {
90 $msg.text(source + ': ' + errMsg).show();
Timo Tijhof51516cd2013-04-09 01:32:29 +020091 $msgWrap.removeClass('zuul-msg-wrap-off');
Joshua Hesketh298c4912014-03-20 16:06:25 +110092 })
93 .complete(function () {
94 xhr = undefined;
95 zuul.emit('update-end');
Timo Tijhof51516cd2013-04-09 01:32:29 +020096 });
97
Timo Tijhof51516cd2013-04-09 01:32:29 +020098 return xhr;
99 },
100
101 format: {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100102 job: function(job) {
103 if (job.url !== null) {
104 $job_line = $('<a href="' + job.url + '" />');
105 }
106 else{
107 $job_line = $('<span />');
108 }
109 $job_line.text(job.name)
110 .append(zuul.format.job_status(job));
111
112 if (job.voting === false) {
113 $job_line.append(
114 $(' <small />').text(' (non-voting)')
115 );
116 }
117 return $job_line;
118 },
119
120 job_status: function(job) {
121 var result = job.result ? job.result.toLowerCase() : null;
122 if (result === null) {
123 result = job.url ? 'in progress' : 'queued';
124 }
125
126 if (result == 'in progress') {
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100127 return zuul.format.job_progress_bar(job.elapsed_time,
128 job.remaining_time);
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100129 }
130 else {
131 return zuul.format.status_label(result);
132 }
133 },
134
135 status_label: function(result) {
136 $status = $('<span />');
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100137 $status.addClass('zuul-job-result label');
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100138
139 switch (result) {
140 case 'success':
141 $status.addClass('label-success');
142 break;
143 case 'failure':
144 $status.addClass('label-danger');
145 break;
146 case 'unstable':
147 $status.addClass('label-warning');
148 break;
149 case 'in progress':
150 case 'queued':
151 case 'lost':
152 $status.addClass('label-default');
153 break;
154 }
155 $status.text(result);
156 return $status;
157 },
158
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100159 job_progress_bar: function(elapsed_time, remaining_time) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100160 var progress_percent = 100 * (elapsed_time / (elapsed_time +
161 remaining_time));
162 var $bar_inner = $('<div />')
163 .addClass('progress-bar')
164 .attr('role', 'progressbar')
165 .attr('aria-valuenow', 'progressbar')
166 .attr('aria-valuemin', progress_percent)
167 .attr('aria-valuemin', '0')
168 .attr('aria-valuemax', '100')
169 .css('width', progress_percent + '%');
170
171 var $bar_outter = $('<div />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100172 .addClass('progress zuul-job-result')
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100173 .append($bar_inner);
174
175 return $bar_outter;
176 },
177
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100178 enqueue_time: function(ms) {
179 // Special format case for enqueue time to add style
180 var hours = 60 * 60 * 1000;
181 var now = Date.now();
182 var delta = now - ms;
183 var status = "text-success";
184 var text = zuul.format.time(delta, true);
185 if (delta > (4 * hours)) {
186 status = "text-danger";
187 } else if (delta > (2 * hours)) {
188 status = "text-warning";
189 }
190 return '<span class="' + status + '">' + text + '</span>';
191 },
192
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100193 time: function(ms, words) {
194 if (typeof(words) === 'undefined') words = false;
195 var seconds = (+ms)/1000;
196 var minutes = Math.floor(seconds/60);
197 var hours = Math.floor(minutes/60);
198 seconds = Math.floor(seconds % 60);
199 minutes = Math.floor(minutes % 60);
200 r = '';
201 if (words) {
202 if (hours) {
203 r += hours;
204 r += ' hr ';
205 }
206 r += minutes + ' min';
207 } else {
208 if (hours < 10) r += '0';
209 r += hours + ':';
210 if (minutes < 10) r += '0';
211 r += minutes + ':';
212 if (seconds < 10) r += '0';
213 r += seconds;
214 }
215 return r;
216 },
217
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100218 change_total_progress_bar: function(change) {
219 job_percent = Math.floor(100 / change.jobs.length);
220 var $bar_outter = $('<div />')
221 .addClass('progress zuul-change-total-result');
222
223 $.each(change.jobs, function (i, job) {
224 var result = job.result ? job.result.toLowerCase() : null;
225 if (result === null) {
226 result = job.url ? 'in progress' : 'queued';
227 }
228
229 if (result != 'queued') {
230 var $bar_inner = $('<div />')
231 .addClass('progress-bar');
232
233 switch (result) {
234 case 'success':
235 $bar_inner.addClass('progress-bar-success');
236 break;
237 case 'lost':
238 case 'failure':
239 $bar_inner.addClass('progress-bar-danger');
240 break;
241 case 'unstable':
242 $bar_inner.addClass('progress-bar-warning');
243 break;
244 case 'in progress':
245 case 'queued':
246 break;
247 }
248 $bar_inner.attr('title', job.name)
249 .css('width', job_percent + '%');
250 $bar_outter.append($bar_inner);
251 }
252 });
253 return $bar_outter;
254 },
255
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100256 change_header: function(change) {
257 change_id = change.id || 'NA';
258 if (change_id.length === 40) {
259 change_id = change_id.substr(0, 7);
Joshua Hesketh298c4912014-03-20 16:06:25 +1100260 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200261
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100262 $change_link = $('<small />');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100263 if (change.url !== null) {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100264 $change_link.append(
Joshua Hesketh298c4912014-03-20 16:06:25 +1100265 $("<a />").attr("href", change.url).text(change.id)
266 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200267 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100268 else {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100269 $change_link.text(change_id);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200270 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200271
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100272 $change_progress_row_left = $('<div />')
273 .addClass('col-xs-3')
274 .append($change_link);
275 $change_progress_row_right = $('<div />')
276 .addClass('col-xs-9')
277 .append(zuul.format.change_total_progress_bar(change))
278
279 $change_progress_row = $('<div />')
280 .addClass('row')
281 .append($change_progress_row_left)
282 .append($change_progress_row_right)
283
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100284 $left = $('<div />')
285 .addClass('col-xs-8')
286 .html(change.project + '<br />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100287 .append($change_progress_row);
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100288
289 remaining_time = zuul.format.time(change.remaining_time, true);
290 enqueue_time = zuul.format.enqueue_time(change.enqueue_time);
291 $remaining_time = $('<small />').addClass('time')
292 .attr('title', 'Remaining Time').html(remaining_time);
293 $enqueue_time = $('<small />').addClass('time')
294 .attr('title', 'Elapsed Time').html(enqueue_time);
295
296 $right = $('<div />')
297 .addClass('col-xs-4 text-right')
298 .append($remaining_time, $('<br />'), $enqueue_time);
299
300 $header = $('<div />')
301 .addClass('row')
302 .append($left, $right);
303 return $header;
304 },
305
306 change_list: function(jobs) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100307 var $list = $('<ul />')
308 .addClass('list-group');
309
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100310 $.each(jobs, function (i, job) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100311 var $item = $('<li />')
312 .addClass('list-group-item')
313 .addClass('zuul-change-job')
314 .append(zuul.format.job(job));
Joshua Hesketh298c4912014-03-20 16:06:25 +1100315 $list.append($item);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200316 });
317
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100318 return $list;
319 },
320
321 change_panel: function (change) {
322 var $header = $('<div />')
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100323 .addClass('panel-heading patchset-header')
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100324 .append(zuul.format.change_header(change));
325
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100326 var panel_id = change.id ? change.id.replace(',', '_')
327 : change.project.replace('/', '_') +
328 '-' + change.enqueue_time
329 var $panel = $('<div />')
330 .attr("id", panel_id)
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100331 .addClass('panel panel-default zuul-change')
332 .append($header)
333 .append(zuul.format.change_list(change.jobs));
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100334
335 $header.click(zuul.toggle_patchset);
336 zuul.display_patchset($panel);
337 return $panel;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200338 },
339
340 pipeline: function (pipeline) {
Joshua Hesketh298c4912014-03-20 16:06:25 +1100341 var $html = $('<div />')
Joshua Hesketh4863b602014-03-21 14:19:06 +1100342 .addClass('zuul-pipeline col-md-4')
Joshua Hesketh298c4912014-03-20 16:06:25 +1100343 .append($('<h3 />').text(pipeline.name));
344
Timo Tijhof51516cd2013-04-09 01:32:29 +0200345 if (typeof pipeline.description === 'string') {
Joshua Hesketh298c4912014-03-20 16:06:25 +1100346 $html.append(
347 $('<p />').append(
348 $('<small />').text(pipeline.description)
349 )
350 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200351 }
352
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100353 $.each(pipeline.change_queues,
354 function (queueNum, changeQueue) {
Timo Tijhof51516cd2013-04-09 01:32:29 +0200355 $.each(changeQueue.heads, function (headNum, changes) {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100356 if (pipeline.change_queues.length > 1 &&
357 headNum === 0) {
Timo Tijhof51516cd2013-04-09 01:32:29 +0200358 var name = changeQueue.name;
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100359 var short_name = name;
360 if (short_name.length > 32) {
361 short_name = short_name.substr(0, 32) + '...';
Timo Tijhof51516cd2013-04-09 01:32:29 +0200362 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100363 $html.append(
364 $('<p />')
365 .text('Queue: ')
366 .append(
367 $('<abbr />')
368 .attr('title', name)
369 .text(short_name)
370 )
371 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200372 }
373 $.each(changes, function (changeNum, change) {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100374 $html.append(zuul.format.change_panel(change))
Timo Tijhof51516cd2013-04-09 01:32:29 +0200375 });
376 });
377 });
Joshua Hesketh298c4912014-03-20 16:06:25 +1100378 return $html;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200379 }
380 },
381
382 emit: function () {
383 $jq.trigger.apply($jq, arguments);
384 return this;
385 },
386 on: function () {
387 $jq.on.apply($jq, arguments);
388 return this;
389 },
390 one: function () {
391 $jq.one.apply($jq, arguments);
392 return this;
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100393 },
394
395 toggle_patchset: function(e) {
396 // Toggle showing/hiding the patchset when the header is clicked
397 // Grab the patchset panel
398 var $panel = $(e.target).parents('.zuul-change');
399 var $body = $panel.children(':not(.patchset-header)');
400 $body.toggle(200);
401 var collapsed_index = zuul.collapsed_exceptions.indexOf(
402 $panel.attr('id'));
403 if (collapsed_index == -1 ) {
404 // Currently not an exception, add it to list
405 zuul.collapsed_exceptions.push($panel.attr('id'));
406 }
407 else {
408 // Currently an except, remove from exceptions
409 zuul.collapsed_exceptions.splice(collapsed_index, 1);
410 }
411 },
412
413 display_patchset: function($panel) {
414 // Determine if to show or hide the patchset when loaded
415 var $body = $panel.children(':not(.patchset-header)');
416 var collapsed_index = zuul.collapsed_exceptions.indexOf(
417 $panel.attr('id'));
418 if (collapsed_index == -1 ) {
419 // Currently not an exception
420 // we are hiding by default
421 $body.hide();
422 }
423 else {
424 // Currently an exception
425 // Do nothing more (will display)
426 }
427 },
Timo Tijhof51516cd2013-04-09 01:32:29 +0200428 };
429
430 $jq = $(zuul);
431
432 $jq.on('update-start', function () {
433 $container.addClass('zuul-container-loading');
434 $indicator.addClass('zuul-spinner-on');
435 });
436
437 $jq.on('update-end', function () {
438 $container.removeClass('zuul-container-loading');
439 setTimeout(function () {
440 $indicator.removeClass('zuul-spinner-on');
441 }, 550);
442 });
443
444 $jq.one('update-end', function () {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100445 // Do this asynchronous so that if the first update adds a message, it
446 // will not animate while we fade in the content. Instead it simply
447 // appears with the rest of the content.
Timo Tijhof51516cd2013-04-09 01:32:29 +0200448 setTimeout(function () {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100449 // Fade in the content
450 $container.addClass('zuul-container-ready');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200451 });
452 });
453
454 $(function ($) {
Joshua Hesketh4863b602014-03-21 14:19:06 +1100455 $msg = $('<div />').addClass('alert').hide();
456 $indicator = $('<button class="btn pull-right zuul-spinner">updating '
457 + '<span class="glyphicon glyphicon-refresh"></span>'
458 + '</button>');
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100459 $queueInfo = $('<p>Queue lengths: <span>0</span> events, ' +
460 '<span>0</span> results.</p>');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100461 $queueEventsNum = $queueInfo.find('span').eq(0);
462 $queueResultsNum = $queueEventsNum.next();
Timo Tijhof51516cd2013-04-09 01:32:29 +0200463 $pipelines = $('<div class="row"></div>');
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100464 $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span">' +
465 '</span></p>');
466 $lastReconf = $('<p>Last reconfigured: ' +
467 '<span id="last-reconfigured-span"></span></p>');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200468
Joshua Hesketh4863b602014-03-21 14:19:06 +1100469 $container = $('#zuul-container').append($msg, $indicator,
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100470 $queueInfo, $pipelines,
471 $zuulVersion, $lastReconf);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200472
473 zuul.schedule();
474
475 $(document).on({
476 'show.visibility': function () {
477 zuul.enabled = true;
478 zuul.update();
479 },
480 'hide.visibility': function () {
481 zuul.enabled = false;
482 }
483 });
484 });
485}(jQuery));