blob: 61b25c7b0a9bfe8beca1f78f475fd5d5d1313109 [file] [log] [blame]
Joshua Hesketh4db99cb2014-05-23 11:28:08 +10001// jquery plugin for Zuul status page
Timo Tijhof51516cd2013-04-09 01:32:29 +02002//
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 ($) {
Timo Tijhof4be9f742015-04-02 01:13:19 +010021 'use strict';
22
Joshua Heskethace48892014-03-22 17:18:31 +110023 function set_cookie(name, value) {
Monty Taylor860bb0a2014-03-22 09:41:25 -070024 document.cookie = name + '=' + value + '; path=/';
Joshua Heskethace48892014-03-22 17:18:31 +110025 }
26
27 function read_cookie(name, default_value) {
Monty Taylor860bb0a2014-03-22 09:41:25 -070028 var nameEQ = name + '=';
Joshua Heskethace48892014-03-22 17:18:31 +110029 var ca = document.cookie.split(';');
30 for(var i=0;i < ca.length;i++) {
31 var c = ca[i];
Monty Taylor860bb0a2014-03-22 09:41:25 -070032 while (c.charAt(0) === ' ') {
33 c = c.substring(1, c.length);
34 }
35 if (c.indexOf(nameEQ) === 0) {
Joshua Heskethace48892014-03-22 17:18:31 +110036 return c.substring(nameEQ.length, c.length);
37 }
38 }
39 return default_value;
40 }
41
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100042 $.zuul = function(options) {
Timo Tijhof4be9f742015-04-02 01:13:19 +010043 options = $.extend({
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100044 'enabled': true,
45 'graphite_url': '',
46 'source': 'status.json',
47 'msg_id': '#zuul_msg',
48 'pipelines_id': '#zuul_pipelines',
49 'queue_events_num': '#zuul_queue_events_num',
50 'queue_results_num': '#zuul_queue_results_num',
51 }, options);
Joshua Heskethace48892014-03-22 17:18:31 +110052
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100053 var collapsed_exceptions = [];
Joshua Hesketh6f94dd12014-08-20 16:14:53 +100054 var current_filter = read_cookie('zuul_filter_string', '');
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100055 var $jq;
Timo Tijhof51516cd2013-04-09 01:32:29 +020056
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100057 var xhr,
58 zuul_graph_update_count = 0,
59 zuul_sparkline_urls = {};
Joshua Hesketh9d013542014-04-03 13:08:04 +110060
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100061 function get_sparkline_url(pipeline_name) {
62 if (options.graphite_url !== '') {
63 if (!(pipeline_name in zuul_sparkline_urls)) {
64 zuul_sparkline_urls[pipeline_name] = $.fn.graphite
65 .geturl({
66 url: options.graphite_url,
67 from: "-8hours",
68 width: 100,
69 height: 26,
70 margin: 0,
71 hideLegend: true,
72 hideAxes: true,
73 hideGrid: true,
74 target: [
75 "color(stats.gauges.zuul.pipeline." + pipeline_name
Timo Tijhof4be9f742015-04-02 01:13:19 +010076 + ".current_changes, '6b8182')"
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100077 ]
Joshua Hesketh298c4912014-03-20 16:06:25 +110078 });
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100079 }
80 return zuul_sparkline_urls[pipeline_name];
81 }
82 return false;
83 }
Joshua Hesketh298c4912014-03-20 16:06:25 +110084
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +100085 var format = {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +110086 job: function(job) {
Joshua Hesketh9e6ce532014-04-01 13:11:53 +110087 var $job_line = $('<span />');
88
Joshua Hesketh6b1a2182014-03-21 14:40:04 +110089 if (job.url !== null) {
Joshua Hesketh9e6ce532014-04-01 13:11:53 +110090 $job_line.append(
91 $('<a />')
92 .addClass('zuul-job-name')
93 .attr('href', job.url)
94 .text(job.name)
95 );
Joshua Hesketh6b1a2182014-03-21 14:40:04 +110096 }
Joshua Hesketh9e6ce532014-04-01 13:11:53 +110097 else {
98 $job_line.append(
99 $('<span />')
100 .addClass('zuul-job-name')
101 .text(job.name)
102 );
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100103 }
Joshua Hesketh9e6ce532014-04-01 13:11:53 +1100104
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000105 $job_line.append(this.job_status(job));
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100106
107 if (job.voting === false) {
108 $job_line.append(
Joshua Hesketh9e6ce532014-04-01 13:11:53 +1100109 $(' <small />')
110 .addClass('zuul-non-voting-desc')
111 .text(' (non-voting)')
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100112 );
113 }
Joshua Hesketh9e6ce532014-04-01 13:11:53 +1100114
Joshua Hesketha95fd552014-08-21 11:21:05 +1000115 $job_line.append($('<div style="clear: both"></div>'));
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100116 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
Monty Taylor860bb0a2014-03-22 09:41:25 -0700125 if (result === 'in progress') {
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000126 return this.job_progress_bar(job.elapsed_time,
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100127 job.remaining_time);
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100128 }
129 else {
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000130 return this.status_label(result);
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100131 }
132 },
133
134 status_label: function(result) {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700135 var $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;
Monty Taylor860bb0a2014-03-22 09:41:25 -0700182 var status = 'text-success';
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000183 var text = this.time(delta, true);
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100184 if (delta > (4 * hours)) {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700185 status = 'text-danger';
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100186 } else if (delta > (2 * hours)) {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700187 status = 'text-warning';
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100188 }
189 return '<span class="' + status + '">' + text + '</span>';
190 },
191
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100192 time: function(ms, words) {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700193 if (typeof(words) === 'undefined') {
194 words = false;
195 }
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100196 var seconds = (+ms)/1000;
197 var minutes = Math.floor(seconds/60);
198 var hours = Math.floor(minutes/60);
199 seconds = Math.floor(seconds % 60);
200 minutes = Math.floor(minutes % 60);
Monty Taylor860bb0a2014-03-22 09:41:25 -0700201 var r = '';
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100202 if (words) {
203 if (hours) {
204 r += hours;
205 r += ' hr ';
206 }
207 r += minutes + ' min';
208 } else {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700209 if (hours < 10) {
210 r += '0';
211 }
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100212 r += hours + ':';
Monty Taylor860bb0a2014-03-22 09:41:25 -0700213 if (minutes < 10) {
214 r += '0';
215 }
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100216 r += minutes + ':';
Monty Taylor860bb0a2014-03-22 09:41:25 -0700217 if (seconds < 10) {
218 r += '0';
219 }
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100220 r += seconds;
221 }
222 return r;
223 },
224
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100225 change_total_progress_bar: function(change) {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700226 var job_percent = Math.floor(100 / change.jobs.length);
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100227 var $bar_outter = $('<div />')
228 .addClass('progress zuul-change-total-result');
229
230 $.each(change.jobs, function (i, job) {
231 var result = job.result ? job.result.toLowerCase() : null;
232 if (result === null) {
233 result = job.url ? 'in progress' : 'queued';
234 }
235
Monty Taylor860bb0a2014-03-22 09:41:25 -0700236 if (result !== 'queued') {
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100237 var $bar_inner = $('<div />')
238 .addClass('progress-bar');
239
240 switch (result) {
241 case 'success':
242 $bar_inner.addClass('progress-bar-success');
243 break;
244 case 'lost':
245 case 'failure':
246 $bar_inner.addClass('progress-bar-danger');
247 break;
248 case 'unstable':
249 $bar_inner.addClass('progress-bar-warning');
250 break;
251 case 'in progress':
252 case 'queued':
253 break;
254 }
255 $bar_inner.attr('title', job.name)
256 .css('width', job_percent + '%');
257 $bar_outter.append($bar_inner);
258 }
259 });
260 return $bar_outter;
261 },
262
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100263 change_header: function(change) {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700264 var change_id = change.id || 'NA';
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100265 if (change_id.length === 40) {
266 change_id = change_id.substr(0, 7);
Joshua Hesketh298c4912014-03-20 16:06:25 +1100267 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200268
Monty Taylor860bb0a2014-03-22 09:41:25 -0700269 var $change_link = $('<small />');
Joshua Hesketh298c4912014-03-20 16:06:25 +1100270 if (change.url !== null) {
Joshua Heskethcd7a0772014-08-21 11:32:44 +1000271 if (/^[0-9a-f]{40}$/.test(change.id)) {
272 var change_id_short = change.id.slice(0, 7);
273 $change_link.append(
274 $('<a />').attr('href', change.url).append(
275 $('<abbr />')
276 .attr('title', change.id)
277 .text(change_id_short)
278 )
279 );
280 }
281 else {
282 $change_link.append(
283 $('<a />').attr('href', change.url).text(change.id)
284 );
285 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200286 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100287 else {
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100288 $change_link.text(change_id);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200289 }
Timo Tijhof51516cd2013-04-09 01:32:29 +0200290
Monty Taylor860bb0a2014-03-22 09:41:25 -0700291 var $change_progress_row_left = $('<div />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100292 .addClass('col-xs-3')
293 .append($change_link);
Monty Taylor860bb0a2014-03-22 09:41:25 -0700294 var $change_progress_row_right = $('<div />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100295 .addClass('col-xs-9')
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000296 .append(this.change_total_progress_bar(change));
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100297
Monty Taylor860bb0a2014-03-22 09:41:25 -0700298 var $change_progress_row = $('<div />')
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100299 .addClass('row')
300 .append($change_progress_row_left)
Monty Taylor860bb0a2014-03-22 09:41:25 -0700301 .append($change_progress_row_right);
Joshua Heskethf1b06ca2014-03-21 17:01:12 +1100302
Monty Taylor860bb0a2014-03-22 09:41:25 -0700303 var $project_span = $('<span />')
Joshua Heskethace48892014-03-22 17:18:31 +1100304 .addClass('change_project')
305 .text(change.project);
306
Monty Taylor860bb0a2014-03-22 09:41:25 -0700307 var $left = $('<div />')
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100308 .addClass('col-xs-8')
Joshua Hesketh1751e4f2014-04-01 13:24:51 +1100309 .append($project_span, $change_progress_row);
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100310
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000311 var remaining_time = this.time(
Monty Taylor860bb0a2014-03-22 09:41:25 -0700312 change.remaining_time, true);
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000313 var enqueue_time = this.enqueue_time(
Monty Taylor860bb0a2014-03-22 09:41:25 -0700314 change.enqueue_time);
315 var $remaining_time = $('<small />').addClass('time')
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100316 .attr('title', 'Remaining Time').html(remaining_time);
Monty Taylor860bb0a2014-03-22 09:41:25 -0700317 var $enqueue_time = $('<small />').addClass('time')
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100318 .attr('title', 'Elapsed Time').html(enqueue_time);
319
James E. Blair6dc954b2015-02-11 18:32:19 -0800320 var $right = $('<div />');
321 if (change.live === true) {
322 $right.addClass('col-xs-4 text-right')
323 .append($remaining_time, $('<br />'), $enqueue_time);
324 }
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100325
Monty Taylor860bb0a2014-03-22 09:41:25 -0700326 var $header = $('<div />')
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100327 .addClass('row')
328 .append($left, $right);
329 return $header;
330 },
331
332 change_list: function(jobs) {
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000333 var format = this;
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100334 var $list = $('<ul />')
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100335 .addClass('list-group zuul-patchset-body');
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100336
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100337 $.each(jobs, function (i, job) {
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100338 var $item = $('<li />')
339 .addClass('list-group-item')
340 .addClass('zuul-change-job')
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000341 .append(format.job(job));
Joshua Hesketh298c4912014-03-20 16:06:25 +1100342 $list.append($item);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200343 });
344
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100345 return $list;
346 },
347
348 change_panel: function (change) {
349 var $header = $('<div />')
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100350 .addClass('panel-heading zuul-patchset-header')
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000351 .append(this.change_header(change));
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100352
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100353 var panel_id = change.id ? change.id.replace(',', '_')
354 : change.project.replace('/', '_') +
Monty Taylor860bb0a2014-03-22 09:41:25 -0700355 '-' + change.enqueue_time;
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100356 var $panel = $('<div />')
Monty Taylor860bb0a2014-03-22 09:41:25 -0700357 .attr('id', panel_id)
Joshua Hesketh0ca1e2e2014-03-21 16:49:05 +1100358 .addClass('panel panel-default zuul-change')
359 .append($header)
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000360 .append(this.change_list(change.jobs));
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100361
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000362 $header.click(this.toggle_patchset);
Joshua Heskethdb8046e2014-03-21 18:42:25 +1100363 return $panel;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200364 },
365
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100366 change_status_icon: function(change) {
367 var icon_name = 'green.png';
368 var icon_title = 'Succeeding';
369
370 if (change.active !== true) {
371 // Grey icon
372 icon_name = 'grey.png';
373 icon_title = 'Waiting until closer to head of queue to' +
374 ' start jobs';
375 }
James E. Blair107c3852015-02-07 08:23:10 -0800376 else if (change.live !== true) {
377 // Grey icon
378 icon_name = 'grey.png';
Joshua Hesketh6bc619d2015-02-11 09:30:21 +1100379 icon_title = 'Dependent change required for testing';
James E. Blair107c3852015-02-07 08:23:10 -0800380 }
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100381 else if (change.failing_reasons &&
382 change.failing_reasons.length > 0) {
383 var reason = change.failing_reasons.join(', ');
384 icon_title = 'Failing because ' + reason;
385 if (reason.match(/merge conflict/)) {
386 // Black icon
387 icon_name = 'black.png';
388 }
389 else {
390 // Red icon
391 icon_name = 'red.png';
392 }
393 }
394
395 var $icon = $('<img />')
396 .attr('src', 'images/' + icon_name)
397 .attr('title', icon_title)
398 .css('margin-top', '-6px');
399
400 return $icon;
401 },
402
403 change_with_status_tree: function(change, change_queue) {
404 var $change_row = $('<tr />');
405
406 for (var i = 0; i < change_queue._tree_columns; i++) {
407 var $tree_cell = $('<td />')
408 .css('height', '100%')
409 .css('padding', '0 0 10px 0')
410 .css('margin', '0')
411 .css('width', '16px')
412 .css('min-width', '16px')
413 .css('overflow', 'hidden')
414 .css('vertical-align', 'top');
415
416 if (i < change._tree.length && change._tree[i] !== null) {
417 $tree_cell.css('background-image',
418 'url(\'images/line.png\')')
419 .css('background-repeat', 'repeat-y');
420 }
421
422 if (i === change._tree_index) {
423 $tree_cell.append(
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000424 this.change_status_icon(change));
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100425 }
426 if (change._tree_branches.indexOf(i) !== -1) {
427 var $image = $('<img />')
428 .css('vertical-align', 'baseline');
429 if (change._tree_branches.indexOf(i) ===
430 change._tree_branches.length - 1) {
431 // Angle line
432 $image.attr('src', 'images/line-angle.png');
433 }
434 else {
435 // T line
436 $image.attr('src', 'images/line-t.png');
437 }
438 $tree_cell.append($image);
439 }
440 $change_row.append($tree_cell);
441 }
442
443 var change_width = 360 - 16*change_queue._tree_columns;
444 var $change_column = $('<td />')
445 .css('width', change_width + 'px')
446 .addClass('zuul-change-cell')
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000447 .append(this.change_panel(change));
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100448
449 $change_row.append($change_column);
450
451 var $change_table = $('<table />')
452 .addClass('zuul-change-box')
453 .css('-moz-box-sizing', 'content-box')
454 .css('box-sizing', 'content-box')
455 .append($change_row);
456
457 return $change_table;
458 },
459
Joshua Hesketh9d013542014-04-03 13:08:04 +1100460 pipeline_sparkline: function(pipeline_name) {
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000461 if (options.graphite_url !== '') {
Joshua Hesketh9d013542014-04-03 13:08:04 +1100462 var $sparkline = $('<img />')
463 .addClass('pull-right')
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000464 .attr('src', get_sparkline_url(pipeline_name));
Joshua Hesketh9d013542014-04-03 13:08:04 +1100465 return $sparkline;
466 }
467 return false;
468 },
469
470 pipeline_header: function(pipeline, count) {
471 // Format the pipeline name, sparkline and description
472 var $header_div = $('<div />')
473 .addClass('zuul-pipeline-header');
474
475 var $heading = $('<h3 />')
476 .css('vertical-align', 'middle')
477 .text(pipeline.name)
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100478 .append(
Joshua Hesketh9d013542014-04-03 13:08:04 +1100479 $('<span />')
480 .addClass('badge pull-right')
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100481 .css('vertical-align', 'middle')
Joshua Hesketh9d013542014-04-03 13:08:04 +1100482 .css('margin-top', '0.5em')
483 .text(count)
484 )
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000485 .append(this.pipeline_sparkline(pipeline.name));
Joshua Hesketh9d013542014-04-03 13:08:04 +1100486
487 $header_div.append($heading);
Joshua Hesketh298c4912014-03-20 16:06:25 +1100488
Timo Tijhof51516cd2013-04-09 01:32:29 +0200489 if (typeof pipeline.description === 'string') {
Joshua Hesketh9d013542014-04-03 13:08:04 +1100490 $header_div.append(
Joshua Hesketh298c4912014-03-20 16:06:25 +1100491 $('<p />').append(
492 $('<small />').text(pipeline.description)
493 )
494 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200495 }
Joshua Hesketh9d013542014-04-03 13:08:04 +1100496 return $header_div;
497 },
498
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000499 pipeline: function (pipeline, count) {
500 var format = this;
Joshua Hesketh9d013542014-04-03 13:08:04 +1100501 var $html = $('<div />')
502 .addClass('zuul-pipeline col-md-4')
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000503 .append(this.pipeline_header(pipeline, count));
Timo Tijhof51516cd2013-04-09 01:32:29 +0200504
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100505 $.each(pipeline.change_queues,
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100506 function (queue_i, change_queue) {
507 $.each(change_queue.heads, function (head_i, changes) {
Joshua Heskethcbdcca12014-03-20 16:06:25 +1100508 if (pipeline.change_queues.length > 1 &&
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100509 head_i === 0) {
510 var name = change_queue.name;
Joshua Hesketh6b1a2182014-03-21 14:40:04 +1100511 var short_name = name;
512 if (short_name.length > 32) {
513 short_name = short_name.substr(0, 32) + '...';
Timo Tijhof51516cd2013-04-09 01:32:29 +0200514 }
Joshua Hesketh298c4912014-03-20 16:06:25 +1100515 $html.append(
516 $('<p />')
517 .text('Queue: ')
518 .append(
519 $('<abbr />')
520 .attr('title', name)
521 .text(short_name)
522 )
523 );
Timo Tijhof51516cd2013-04-09 01:32:29 +0200524 }
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100525
526 $.each(changes, function (change_i, change) {
527 var $change_box =
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000528 format.change_with_status_tree(
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100529 change, change_queue);
530 $html.append($change_box);
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000531 format.display_patchset($change_box);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200532 });
533 });
534 });
Joshua Hesketh298c4912014-03-20 16:06:25 +1100535 return $html;
Joshua Heskethace48892014-03-22 17:18:31 +1100536 },
537
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000538 toggle_patchset: function(e) {
539 // Toggle showing/hiding the patchset when the header is
540 // clicked.
541
542 // Grab the patchset panel
543 var $panel = $(e.target).parents('.zuul-change');
544 var $body = $panel.children('.zuul-patchset-body');
545 $body.toggle(200);
546 var collapsed_index = collapsed_exceptions.indexOf(
547 $panel.attr('id'));
548 if (collapsed_index === -1 ) {
549 // Currently not an exception, add it to list
550 collapsed_exceptions.push($panel.attr('id'));
551 }
552 else {
553 // Currently an except, remove from exceptions
554 collapsed_exceptions.splice(collapsed_index, 1);
555 }
556 },
557
558 display_patchset: function($change_box, animate) {
559 // Determine if to show or hide the patchset and/or the results
560 // when loaded
561
562 // See if we should hide the body/results
563 var $panel = $change_box.find('.zuul-change');
564 var panel_change = $panel.attr('id');
565 var $body = $panel.children('.zuul-patchset-body');
566 var expand_by_default = $('#expand_by_default')
567 .prop('checked');
568
569 var collapsed_index = collapsed_exceptions
570 .indexOf(panel_change);
571
572 if (expand_by_default && collapsed_index === -1 ||
573 !expand_by_default && collapsed_index !== -1) {
574 // Expand by default, or is an exception
575 $body.show(animate);
576 }
577 else {
578 $body.hide(animate);
579 }
580
581 // Check if we should hide the whole panel
582 var panel_project = $panel.find('.change_project').text()
583 .toLowerCase();
584
585
586 var panel_pipeline = $change_box
587 .parents('.zuul-pipeline')
588 .find('.zuul-pipeline-header > h3')
589 .html()
590 .toLowerCase();
591
592 if (current_filter !== '') {
593 var show_panel = false;
594 var filter = current_filter.trim().split(/[\s,]+/);
595 $.each(filter, function(index, f_val) {
596 if (f_val !== '') {
597 f_val = f_val.toLowerCase();
598 if (panel_project.indexOf(f_val) !== -1 ||
599 panel_pipeline.indexOf(f_val) !== -1 ||
600 panel_change.indexOf(f_val) !== -1) {
601 show_panel = true;
602 }
603 }
604 });
605 if (show_panel === true) {
606 $change_box.show(animate);
607 }
608 else {
609 $change_box.hide(animate);
610 }
611 }
612 else {
613 $change_box.show(animate);
614 }
615 },
616 };
617
618 var app = {
619 schedule: function (app) {
Timo Tijhof4be9f742015-04-02 01:13:19 +0100620 app = app || this;
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000621 if (!options.enabled) {
622 setTimeout(function() {app.schedule(app);}, 5000);
623 return;
624 }
625 app.update().complete(function () {
626 setTimeout(function() {app.schedule(app);}, 5000);
627 });
628
629 /* Only update graphs every minute */
630 if (zuul_graph_update_count > 11) {
631 zuul_graph_update_count = 0;
632 zuul.update_sparklines();
633 }
634 },
635
636 /** @return {jQuery.Promise} */
637 update: function () {
638 // Cancel the previous update if it hasn't completed yet.
639 if (xhr) {
640 xhr.abort();
641 }
642
643 this.emit('update-start');
644 var app = this;
645
Timo Tijhof4be9f742015-04-02 01:13:19 +0100646 var $msg = $(options.msg_id);
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000647 xhr = $.getJSON(options.source)
648 .done(function (data) {
649 if ('message' in data) {
650 $msg.removeClass('alert-danger')
651 .addClass('alert-info')
652 .text(data.message)
653 .show();
654 } else {
655 $msg.empty()
656 .hide();
657 }
658
659 if ('zuul_version' in data) {
660 $('#zuul-version-span').text(data.zuul_version);
661 }
662 if ('last_reconfigured' in data) {
663 var last_reconfigured =
664 new Date(data.last_reconfigured);
665 $('#last-reconfigured-span').text(
666 last_reconfigured.toString());
667 }
668
669 var $pipelines = $(options.pipelines_id);
670 $pipelines.html('');
671 $.each(data.pipelines, function (i, pipeline) {
672 var count = app.create_tree(pipeline);
673 $pipelines.append(
674 format.pipeline(pipeline, count));
675 });
676
677 $(options.queue_events_num).text(
678 data.trigger_event_queue ?
679 data.trigger_event_queue.length : '0'
680 );
681 $(options.queue_results_num).text(
682 data.result_event_queue ?
683 data.result_event_queue.length : '0'
684 );
685 })
686 .fail(function (err, jqXHR, errMsg) {
Joshua Heskethd50eb582014-04-30 19:51:22 +1000687 $msg.text(options.source + ': ' + errMsg)
688 .addClass('alert-danger')
689 .removeClass('zuul-msg-wrap-off')
690 .show();
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000691 })
692 .complete(function () {
693 xhr = undefined;
694 app.emit('update-end');
695 });
696
697 return xhr;
698 },
699
700 update_sparklines: function() {
701 $.each(zuul_sparkline_urls, function(name, url) {
702 var newimg = new Image();
703 var parts = url.split('#');
704 newimg.src = parts[0] + '#' + new Date().getTime();
Timo Tijhof4be9f742015-04-02 01:13:19 +0100705 $(newimg).load(function () {
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000706 zuul_sparkline_urls[name] = newimg.src;
707 });
708 });
709 },
710
711 emit: function () {
712 $jq.trigger.apply($jq, arguments);
713 return this;
714 },
715 on: function () {
716 $jq.on.apply($jq, arguments);
717 return this;
718 },
719 one: function () {
720 $jq.one.apply($jq, arguments);
721 return this;
722 },
723
724 control_form: function() {
725 // Build the filter form filling anything from cookies
726
727 var $control_form = $('<form />')
728 .attr('role', 'form')
729 .addClass('form-inline')
730 .submit(this.handle_filter_change);
731
732 $control_form
733 .append(this.filter_form_group())
734 .append(this.expand_form_group());
735
736 return $control_form;
737 },
738
Joshua Hesketh1ed6f9d2014-03-31 22:53:06 +1100739 filter_form_group: function() {
Joshua Heskethace48892014-03-22 17:18:31 +1100740 // Update the filter form with a clear button if required
741
742 var $label = $('<label />')
743 .addClass('control-label')
744 .attr('for', 'filter_string')
745 .text('Filters')
746 .css('padding-right', '0.5em');
747
748 var $input = $('<input />')
749 .attr('type', 'text')
750 .attr('id', 'filter_string')
751 .addClass('form-control')
752 .attr('title',
753 'project(s), pipeline(s) or review(s) comma ' +
754 'separated')
Joshua Hesketh1ed6f9d2014-03-31 22:53:06 +1100755 .attr('value', current_filter);
Joshua Heskethace48892014-03-22 17:18:31 +1100756
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000757 $input.change(this.handle_filter_change);
Joshua Heskethace48892014-03-22 17:18:31 +1100758
759 var $clear_icon = $('<span />')
760 .addClass('form-control-feedback')
761 .addClass('glyphicon glyphicon-remove-circle')
762 .attr('id', 'filter_form_clear_box')
763 .attr('title', 'clear filter')
764 .css('cursor', 'pointer');
765
766 $clear_icon.click(function() {
767 $('#filter_string').val('').change();
768 });
769
Joshua Hesketh1ed6f9d2014-03-31 22:53:06 +1100770 if (current_filter === '') {
Joshua Heskethace48892014-03-22 17:18:31 +1100771 $clear_icon.hide();
772 }
773
774 var $form_group = $('<div />')
775 .addClass('form-group has-feedback')
776 .append($label, $input, $clear_icon);
777 return $form_group;
778 },
779
Joshua Heskethae230f62014-03-22 22:14:44 +1100780 expand_form_group: function() {
Monty Taylor860bb0a2014-03-22 09:41:25 -0700781 var expand_by_default = (
Joshua Heskethae230f62014-03-22 22:14:44 +1100782 read_cookie('zuul_expand_by_default', false) === 'true');
783
Monty Taylor860bb0a2014-03-22 09:41:25 -0700784 var $checkbox = $('<input />')
Joshua Heskethae230f62014-03-22 22:14:44 +1100785 .attr('type', 'checkbox')
786 .attr('id', 'expand_by_default')
787 .prop('checked', expand_by_default)
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000788 .change(this.handle_expand_by_default);
Joshua Heskethae230f62014-03-22 22:14:44 +1100789
Monty Taylor860bb0a2014-03-22 09:41:25 -0700790 var $label = $('<label />')
Joshua Heskethae230f62014-03-22 22:14:44 +1100791 .css('padding-left', '1em')
792 .html('Expand by default: ')
793 .append($checkbox);
794
795 var $form_group = $('<div />')
796 .addClass('checkbox')
797 .append($label);
798 return $form_group;
799 },
800
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000801 handle_filter_change: function() {
802 // Update the filter and save it to a cookie
803 current_filter = $('#filter_string').val();
804 set_cookie('zuul_filter_string', current_filter);
805 if (current_filter === '') {
806 $('#filter_form_clear_box').hide();
Joshua Heskethace48892014-03-22 17:18:31 +1100807 }
808 else {
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000809 $('#filter_form_clear_box').show();
Joshua Heskethace48892014-03-22 17:18:31 +1100810 }
Joshua Heskethace48892014-03-22 17:18:31 +1100811
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000812 $('.zuul-change-box').each(function(index, obj) {
813 var $change_box = $(obj);
814 format.display_patchset($change_box, 200);
Joshua Hesketh5caf4f62014-04-01 12:52:43 +1100815 });
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000816 return false;
Timo Tijhof51516cd2013-04-09 01:32:29 +0200817 },
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000818
819 handle_expand_by_default: function(e) {
820 // Handle toggling expand by default
821 set_cookie('zuul_expand_by_default', e.target.checked);
822 collapsed_exceptions = [];
823 $('.zuul-change-box').each(function(index, obj) {
824 var $change_box = $(obj);
825 format.display_patchset($change_box, 200);
826 });
827 },
828
829 create_tree: function(pipeline) {
830 var count = 0;
831 var pipeline_max_tree_columns = 1;
832 $.each(pipeline.change_queues, function(change_queue_i,
833 change_queue) {
834 var tree = [];
835 var max_tree_columns = 1;
836 var changes = [];
837 var last_tree_length = 0;
838 $.each(change_queue.heads, function(head_i, head) {
839 $.each(head, function(change_i, change) {
840 changes[change.id] = change;
841 change._tree_position = change_i;
842 });
843 });
844 $.each(change_queue.heads, function(head_i, head) {
845 $.each(head, function(change_i, change) {
James E. Blaird1d3ce32015-02-11 17:56:45 -0800846 if (change.live === true) {
847 count += 1;
848 }
Joshua Hesketh0f5c66a2014-04-30 19:23:36 +1000849 var idx = tree.indexOf(change.id);
850 if (idx > -1) {
851 change._tree_index = idx;
852 // remove...
853 tree[idx] = null;
854 while (tree[tree.length - 1] === null) {
855 tree.pop();
856 }
857 } else {
858 change._tree_index = 0;
859 }
860 change._tree_branches = [];
861 change._tree = [];
862 if (typeof(change.items_behind) === 'undefined') {
863 change.items_behind = [];
864 }
865 change.items_behind.sort(function(a, b) {
866 return (changes[b]._tree_position -
867 changes[a]._tree_position);
868 });
869 $.each(change.items_behind, function(i, id) {
870 tree.push(id);
871 if (tree.length>last_tree_length &&
872 last_tree_length > 0) {
873 change._tree_branches.push(
874 tree.length - 1);
875 }
876 });
877 if (tree.length > max_tree_columns) {
878 max_tree_columns = tree.length;
879 }
880 if (tree.length > pipeline_max_tree_columns) {
881 pipeline_max_tree_columns = tree.length;
882 }
883 change._tree = tree.slice(0); // make a copy
884 last_tree_length = tree.length;
885 });
886 });
887 change_queue._tree_columns = max_tree_columns;
888 });
889 pipeline._tree_columns = pipeline_max_tree_columns;
890 return count;
891 },
892 };
893
894 $jq = $(app);
895 return {
896 options: options,
897 format: format,
898 app: app,
899 jq: $jq
900 };
Timo Tijhof4be9f742015-04-02 01:13:19 +0100901 };
Timo Tijhof51516cd2013-04-09 01:32:29 +0200902}(jQuery));