blob: 9586b4d8ae6c4f951fb65be2aa77c4720c6244ce [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') {
126 return zuul.format.progress_bar(job.elapsed_time,
127 job.remaining_time);
128 }
129 else {
130 return zuul.format.status_label(result);
131 }
132 },
133
134 status_label: function(result) {
135 $status = $('<span />');
136 $status.addClass('zuul-result label');
137
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
158 progress_bar: function(elapsed_time, remaining_time) {
159 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 />')
171 .addClass('progress zuul-result')
172 .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 Hesketh0ca1e2e2014-03-21 16:49:05 +1100217 change_header: function(change) {
218 change_id = change.id || 'NA';
219 if (change_id.length === 40) {
220 change_id = change_id.substr(0, 7);
Joshua Hesketh298c4912014-03-20 16:06:25 +1100221 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200222
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100223 $change_link = $('<small />');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100224 if (change.url !== null) {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100225 $change_link.append(
Joshua Hesketh298c4912014-03-20 16:06:25 +1100226 $("<a />").attr("href", change.url).text(change.id)
227 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200228 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100229 else {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100230 $change_link.text(change_id);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200231 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200232
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100233 $left = $('<div />')
234 .addClass('col-xs-8')
235 .html(change.project + '<br />')
236 .append($change_link);
237
238 remaining_time = zuul.format.time(change.remaining_time, true);
239 enqueue_time = zuul.format.enqueue_time(change.enqueue_time);
240 $remaining_time = $('<small />').addClass('time')
241 .attr('title', 'Remaining Time').html(remaining_time);
242 $enqueue_time = $('<small />').addClass('time')
243 .attr('title', 'Elapsed Time').html(enqueue_time);
244
245 $right = $('<div />')
246 .addClass('col-xs-4 text-right')
247 .append($remaining_time, $('<br />'), $enqueue_time);
248
249 $header = $('<div />')
250 .addClass('row')
251 .append($left, $right);
252 return $header;
253 },
254
255 change_list: function(jobs) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100256 var $list = $('<ul />')
257 .addClass('list-group');
258
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100259 $.each(jobs, function (i, job) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100260 var $item = $('<li />')
261 .addClass('list-group-item')
262 .addClass('zuul-change-job')
263 .append(zuul.format.job(job));
Joshua Hesketh298c4912014-03-20 16:06:25 +1100264 $list.append($item);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200265 });
266
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100267 return $list;
268 },
269
270 change_panel: function (change) {
271 var $header = $('<div />')
272 .addClass('panel-heading')
273 .append(zuul.format.change_header(change));
274
275 var $html = $('<div />')
276 .addClass('panel panel-default zuul-change')
277 .append($header)
278 .append(zuul.format.change_list(change.jobs));
Joshua Hesketh298c4912014-03-20 16:06:25 +1100279 return $html;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200280 },
281
282 pipeline: function (pipeline) {
Joshua Hesketh298c4912014-03-20 16:06:25 +1100283 var $html = $('<div />')
Joshua Hesketh4863b602014-03-21 14:19:06 +1100284 .addClass('zuul-pipeline col-md-4')
Joshua Hesketh298c4912014-03-20 16:06:25 +1100285 .append($('<h3 />').text(pipeline.name));
286
Timo Tijhof51516cd2013-04-09 01:32:29 +0200287 if (typeof pipeline.description === 'string') {
Joshua Hesketh298c4912014-03-20 16:06:25 +1100288 $html.append(
289 $('<p />').append(
290 $('<small />').text(pipeline.description)
291 )
292 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200293 }
294
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100295 $.each(pipeline.change_queues,
296 function (queueNum, changeQueue) {
Timo Tijhof51516cd2013-04-09 01:32:29 +0200297 $.each(changeQueue.heads, function (headNum, changes) {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100298 if (pipeline.change_queues.length > 1 &&
299 headNum === 0) {
Timo Tijhof51516cd2013-04-09 01:32:29 +0200300 var name = changeQueue.name;
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100301 var short_name = name;
302 if (short_name.length > 32) {
303 short_name = short_name.substr(0, 32) + '...';
Timo Tijhof51516cd2013-04-09 01:32:29 +0200304 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100305 $html.append(
306 $('<p />')
307 .text('Queue: ')
308 .append(
309 $('<abbr />')
310 .attr('title', name)
311 .text(short_name)
312 )
313 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200314 }
315 $.each(changes, function (changeNum, change) {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100316 $html.append(zuul.format.change_panel(change))
Timo Tijhof51516cd2013-04-09 01:32:29 +0200317 });
318 });
319 });
Joshua Hesketh298c4912014-03-20 16:06:25 +1100320 return $html;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200321 }
322 },
323
324 emit: function () {
325 $jq.trigger.apply($jq, arguments);
326 return this;
327 },
328 on: function () {
329 $jq.on.apply($jq, arguments);
330 return this;
331 },
332 one: function () {
333 $jq.one.apply($jq, arguments);
334 return this;
335 }
336 };
337
338 $jq = $(zuul);
339
340 $jq.on('update-start', function () {
341 $container.addClass('zuul-container-loading');
342 $indicator.addClass('zuul-spinner-on');
343 });
344
345 $jq.on('update-end', function () {
346 $container.removeClass('zuul-container-loading');
347 setTimeout(function () {
348 $indicator.removeClass('zuul-spinner-on');
349 }, 550);
350 });
351
352 $jq.one('update-end', function () {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100353 // Do this asynchronous so that if the first update adds a message, it
354 // will not animate while we fade in the content. Instead it simply
355 // appears with the rest of the content.
Timo Tijhof51516cd2013-04-09 01:32:29 +0200356 setTimeout(function () {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100357 // Fade in the content
358 $container.addClass('zuul-container-ready');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200359 });
360 });
361
362 $(function ($) {
Joshua Hesketh4863b602014-03-21 14:19:06 +1100363 $msg = $('<div />').addClass('alert').hide();
364 $indicator = $('<button class="btn pull-right zuul-spinner">updating '
365 + '<span class="glyphicon glyphicon-refresh"></span>'
366 + '</button>');
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100367 $queueInfo = $('<p>Queue lengths: <span>0</span> events, ' +
368 '<span>0</span> results.</p>');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100369 $queueEventsNum = $queueInfo.find('span').eq(0);
370 $queueResultsNum = $queueEventsNum.next();
Timo Tijhof51516cd2013-04-09 01:32:29 +0200371 $pipelines = $('<div class="row"></div>');
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100372 $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span">' +
373 '</span></p>');
374 $lastReconf = $('<p>Last reconfigured: ' +
375 '<span id="last-reconfigured-span"></span></p>');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200376
Joshua Hesketh4863b602014-03-21 14:19:06 +1100377 $container = $('#zuul-container').append($msg, $indicator,
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100378 $queueInfo, $pipelines,
379 $zuulVersion, $lastReconf);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200380
381 zuul.schedule();
382
383 $(document).on({
384 'show.visibility': function () {
385 zuul.enabled = true;
386 zuul.update();
387 },
388 'hide.visibility': function () {
389 zuul.enabled = false;
390 }
391 });
392 });
393}(jQuery));