blob: 6836cf07c0a58c2bf591fb2696965490787afed5 [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,
33
34 schedule: function () {
35 if (!zuul.enabled) {
36 setTimeout(zuul.schedule, 5000);
37 return;
38 }
39 zuul.update().complete(function () {
40 setTimeout(zuul.schedule, 5000);
41 });
42 },
43
44 /** @return {jQuery.Promise} */
45 update: function () {
46 // Cancel the previous update if it hasn't completed yet.
47 if (xhr) {
48 xhr.abort();
49 }
50
51 zuul.emit('update-start');
52
Joshua Hesketh298c4912014-03-20 16:06:25 +110053 xhr = $.getJSON(source)
54 .done(function (data) {
55 if ('message' in data) {
Joshua Hesketh4863b602014-03-21 14:19:06 +110056 $msg.removeClass('alert-danger').addClass('alert-info');
Joshua Hesketh298c4912014-03-20 16:06:25 +110057 $msg.text(data.message);
Joshua Hesketh4863b602014-03-21 14:19:06 +110058 $msg.show();
Joshua Hesketh298c4912014-03-20 16:06:25 +110059 } else {
60 $msg.empty();
Joshua Hesketh4863b602014-03-21 14:19:06 +110061 $msg.hide();
Joshua Hesketh298c4912014-03-20 16:06:25 +110062 }
Timo Tijhof51516cd2013-04-09 01:32:29 +020063
Joshua Hesketh298c4912014-03-20 16:06:25 +110064 if ('zuul_version' in data) {
65 $('#zuul-version-span').text(data['zuul_version']);
66 }
67 if ('last_reconfigured' in data) {
68 var last_reconfigured =
69 new Date(data['last_reconfigured']);
70 $('#last-reconfigured-span').text(
71 last_reconfigured.toString());
72 }
73
74 $pipelines.html('');
75 $.each(data.pipelines, function (i, pipeline) {
76 $pipelines.append(zuul.format.pipeline(pipeline));
77 });
78
79 $queueEventsNum.text(
80 data.trigger_event_queue ?
81 data.trigger_event_queue.length : '0'
82 );
83 $queueResultsNum.text(
84 data.result_event_queue ?
85 data.result_event_queue.length : '0'
86 );
87 })
88 .fail(function (err, jqXHR, errMsg) {
89 $msg.text(source + ': ' + errMsg).show();
Timo Tijhof51516cd2013-04-09 01:32:29 +020090 $msgWrap.removeClass('zuul-msg-wrap-off');
Joshua Hesketh298c4912014-03-20 16:06:25 +110091 })
92 .complete(function () {
93 xhr = undefined;
94 zuul.emit('update-end');
Timo Tijhof51516cd2013-04-09 01:32:29 +020095 });
96
Timo Tijhof51516cd2013-04-09 01:32:29 +020097 return xhr;
98 },
99
100 format: {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100101 job: function(job) {
102 if (job.url !== null) {
103 $job_line = $('<a href="' + job.url + '" />');
104 }
105 else{
106 $job_line = $('<span />');
107 }
108 $job_line.text(job.name)
109 .append(zuul.format.job_status(job));
110
111 if (job.voting === false) {
112 $job_line.append(
113 $(' <small />').text(' (non-voting)')
114 );
115 }
116 return $job_line;
117 },
118
119 job_status: function(job) {
120 var result = job.result ? job.result.toLowerCase() : null;
121 if (result === null) {
122 result = job.url ? 'in progress' : 'queued';
123 }
124
125 if (result == 'in progress') {
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100126 return zuul.format.job_progress_bar(job.elapsed_time,
127 job.remaining_time);
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100128 }
129 else {
130 return zuul.format.status_label(result);
131 }
132 },
133
134 status_label: function(result) {
135 $status = $('<span />');
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100136 $status.addClass('zuul-job-result label');
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100137
138 switch (result) {
139 case 'success':
140 $status.addClass('label-success');
141 break;
142 case 'failure':
143 $status.addClass('label-danger');
144 break;
145 case 'unstable':
146 $status.addClass('label-warning');
147 break;
148 case 'in progress':
149 case 'queued':
150 case 'lost':
151 $status.addClass('label-default');
152 break;
153 }
154 $status.text(result);
155 return $status;
156 },
157
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100158 job_progress_bar: function(elapsed_time, remaining_time) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100159 var progress_percent = 100 * (elapsed_time / (elapsed_time +
160 remaining_time));
161 var $bar_inner = $('<div />')
162 .addClass('progress-bar')
163 .attr('role', 'progressbar')
164 .attr('aria-valuenow', 'progressbar')
165 .attr('aria-valuemin', progress_percent)
166 .attr('aria-valuemin', '0')
167 .attr('aria-valuemax', '100')
168 .css('width', progress_percent + '%');
169
170 var $bar_outter = $('<div />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100171 .addClass('progress zuul-job-result')
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100172 .append($bar_inner);
173
174 return $bar_outter;
175 },
176
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100177 enqueue_time: function(ms) {
178 // Special format case for enqueue time to add style
179 var hours = 60 * 60 * 1000;
180 var now = Date.now();
181 var delta = now - ms;
182 var status = "text-success";
183 var text = zuul.format.time(delta, true);
184 if (delta > (4 * hours)) {
185 status = "text-danger";
186 } else if (delta > (2 * hours)) {
187 status = "text-warning";
188 }
189 return '<span class="' + status + '">' + text + '</span>';
190 },
191
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100192 time: function(ms, words) {
193 if (typeof(words) === 'undefined') words = false;
194 var seconds = (+ms)/1000;
195 var minutes = Math.floor(seconds/60);
196 var hours = Math.floor(minutes/60);
197 seconds = Math.floor(seconds % 60);
198 minutes = Math.floor(minutes % 60);
199 r = '';
200 if (words) {
201 if (hours) {
202 r += hours;
203 r += ' hr ';
204 }
205 r += minutes + ' min';
206 } else {
207 if (hours < 10) r += '0';
208 r += hours + ':';
209 if (minutes < 10) r += '0';
210 r += minutes + ':';
211 if (seconds < 10) r += '0';
212 r += seconds;
213 }
214 return r;
215 },
216
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100217 change_total_progress_bar: function(change) {
218 job_percent = Math.floor(100 / change.jobs.length);
219 var $bar_outter = $('<div />')
220 .addClass('progress zuul-change-total-result');
221
222 $.each(change.jobs, function (i, job) {
223 var result = job.result ? job.result.toLowerCase() : null;
224 if (result === null) {
225 result = job.url ? 'in progress' : 'queued';
226 }
227
228 if (result != 'queued') {
229 var $bar_inner = $('<div />')
230 .addClass('progress-bar');
231
232 switch (result) {
233 case 'success':
234 $bar_inner.addClass('progress-bar-success');
235 break;
236 case 'lost':
237 case 'failure':
238 $bar_inner.addClass('progress-bar-danger');
239 break;
240 case 'unstable':
241 $bar_inner.addClass('progress-bar-warning');
242 break;
243 case 'in progress':
244 case 'queued':
245 break;
246 }
247 $bar_inner.attr('title', job.name)
248 .css('width', job_percent + '%');
249 $bar_outter.append($bar_inner);
250 }
251 });
252 return $bar_outter;
253 },
254
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100255 change_header: function(change) {
256 change_id = change.id || 'NA';
257 if (change_id.length === 40) {
258 change_id = change_id.substr(0, 7);
Joshua Hesketh298c4912014-03-20 16:06:25 +1100259 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200260
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100261 $change_link = $('<small />');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100262 if (change.url !== null) {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100263 $change_link.append(
Joshua Hesketh298c4912014-03-20 16:06:25 +1100264 $("<a />").attr("href", change.url).text(change.id)
265 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200266 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100267 else {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100268 $change_link.text(change_id);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200269 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200270
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100271 $change_progress_row_left = $('<div />')
272 .addClass('col-xs-3')
273 .append($change_link);
274 $change_progress_row_right = $('<div />')
275 .addClass('col-xs-9')
276 .append(zuul.format.change_total_progress_bar(change))
277
278 $change_progress_row = $('<div />')
279 .addClass('row')
280 .append($change_progress_row_left)
281 .append($change_progress_row_right)
282
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100283 $left = $('<div />')
284 .addClass('col-xs-8')
285 .html(change.project + '<br />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100286 .append($change_progress_row);
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100287
288 remaining_time = zuul.format.time(change.remaining_time, true);
289 enqueue_time = zuul.format.enqueue_time(change.enqueue_time);
290 $remaining_time = $('<small />').addClass('time')
291 .attr('title', 'Remaining Time').html(remaining_time);
292 $enqueue_time = $('<small />').addClass('time')
293 .attr('title', 'Elapsed Time').html(enqueue_time);
294
295 $right = $('<div />')
296 .addClass('col-xs-4 text-right')
297 .append($remaining_time, $('<br />'), $enqueue_time);
298
299 $header = $('<div />')
300 .addClass('row')
301 .append($left, $right);
302 return $header;
303 },
304
305 change_list: function(jobs) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100306 var $list = $('<ul />')
307 .addClass('list-group');
308
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100309 $.each(jobs, function (i, job) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100310 var $item = $('<li />')
311 .addClass('list-group-item')
312 .addClass('zuul-change-job')
313 .append(zuul.format.job(job));
Joshua Hesketh298c4912014-03-20 16:06:25 +1100314 $list.append($item);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200315 });
316
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100317 return $list;
318 },
319
320 change_panel: function (change) {
321 var $header = $('<div />')
322 .addClass('panel-heading')
323 .append(zuul.format.change_header(change));
324
325 var $html = $('<div />')
326 .addClass('panel panel-default zuul-change')
327 .append($header)
328 .append(zuul.format.change_list(change.jobs));
Joshua Hesketh298c4912014-03-20 16:06:25 +1100329 return $html;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200330 },
331
332 pipeline: function (pipeline) {
Joshua Hesketh298c4912014-03-20 16:06:25 +1100333 var $html = $('<div />')
Joshua Hesketh4863b602014-03-21 14:19:06 +1100334 .addClass('zuul-pipeline col-md-4')
Joshua Hesketh298c4912014-03-20 16:06:25 +1100335 .append($('<h3 />').text(pipeline.name));
336
Timo Tijhof51516cd2013-04-09 01:32:29 +0200337 if (typeof pipeline.description === 'string') {
Joshua Hesketh298c4912014-03-20 16:06:25 +1100338 $html.append(
339 $('<p />').append(
340 $('<small />').text(pipeline.description)
341 )
342 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200343 }
344
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100345 $.each(pipeline.change_queues,
346 function (queueNum, changeQueue) {
Timo Tijhof51516cd2013-04-09 01:32:29 +0200347 $.each(changeQueue.heads, function (headNum, changes) {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100348 if (pipeline.change_queues.length > 1 &&
349 headNum === 0) {
Timo Tijhof51516cd2013-04-09 01:32:29 +0200350 var name = changeQueue.name;
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100351 var short_name = name;
352 if (short_name.length > 32) {
353 short_name = short_name.substr(0, 32) + '...';
Timo Tijhof51516cd2013-04-09 01:32:29 +0200354 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100355 $html.append(
356 $('<p />')
357 .text('Queue: ')
358 .append(
359 $('<abbr />')
360 .attr('title', name)
361 .text(short_name)
362 )
363 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200364 }
365 $.each(changes, function (changeNum, change) {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100366 $html.append(zuul.format.change_panel(change))
Timo Tijhof51516cd2013-04-09 01:32:29 +0200367 });
368 });
369 });
Joshua Hesketh298c4912014-03-20 16:06:25 +1100370 return $html;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200371 }
372 },
373
374 emit: function () {
375 $jq.trigger.apply($jq, arguments);
376 return this;
377 },
378 on: function () {
379 $jq.on.apply($jq, arguments);
380 return this;
381 },
382 one: function () {
383 $jq.one.apply($jq, arguments);
384 return this;
385 }
386 };
387
388 $jq = $(zuul);
389
390 $jq.on('update-start', function () {
391 $container.addClass('zuul-container-loading');
392 $indicator.addClass('zuul-spinner-on');
393 });
394
395 $jq.on('update-end', function () {
396 $container.removeClass('zuul-container-loading');
397 setTimeout(function () {
398 $indicator.removeClass('zuul-spinner-on');
399 }, 550);
400 });
401
402 $jq.one('update-end', function () {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100403 // Do this asynchronous so that if the first update adds a message, it
404 // will not animate while we fade in the content. Instead it simply
405 // appears with the rest of the content.
Timo Tijhof51516cd2013-04-09 01:32:29 +0200406 setTimeout(function () {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100407 // Fade in the content
408 $container.addClass('zuul-container-ready');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200409 });
410 });
411
412 $(function ($) {
Joshua Hesketh4863b602014-03-21 14:19:06 +1100413 $msg = $('<div />').addClass('alert').hide();
414 $indicator = $('<button class="btn pull-right zuul-spinner">updating '
415 + '<span class="glyphicon glyphicon-refresh"></span>'
416 + '</button>');
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100417 $queueInfo = $('<p>Queue lengths: <span>0</span> events, ' +
418 '<span>0</span> results.</p>');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100419 $queueEventsNum = $queueInfo.find('span').eq(0);
420 $queueResultsNum = $queueEventsNum.next();
Timo Tijhof51516cd2013-04-09 01:32:29 +0200421 $pipelines = $('<div class="row"></div>');
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100422 $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span">' +
423 '</span></p>');
424 $lastReconf = $('<p>Last reconfigured: ' +
425 '<span id="last-reconfigured-span"></span></p>');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200426
Joshua Hesketh4863b602014-03-21 14:19:06 +1100427 $container = $('#zuul-container').append($msg, $indicator,
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100428 $queueInfo, $pipelines,
429 $zuulVersion, $lastReconf);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200430
431 zuul.schedule();
432
433 $(document).on({
434 'show.visibility': function () {
435 zuul.enabled = true;
436 zuul.update();
437 },
438 'hide.visibility': function () {
439 zuul.enabled = false;
440 }
441 });
442 });
443}(jQuery));