blob: 2f9d3b74edd32a4aa716838cb309d5e39988797a [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
6//
7// Licensed under the Apache License, Version 2.0 (the "License"); you may
8// not use this file except in compliance with the License. You may obtain
9// a copy of the License at
10//
11// http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16// License for the specific language governing permissions and limitations
17// under the License.
18
19(function ($) {
20 var $container, $msg, $msgWrap, $indicator, $queueInfo, $queueEventsNum, $queueResultsNum, $pipelines,
21 prevHtml, xhr, zuul, $jq,
22 demo = location.search.match(/[?&]demo=([^?&]*)/),
23 source = demo ?
24 './status-' + (demo[1] || 'basic') + '.json-sample' :
James E. Blair7c7ed7a2013-05-15 13:13:26 -070025 'status.json';
Timo Tijhof51516cd2013-04-09 01:32:29 +020026
27 zuul = {
28 enabled: true,
29
30 schedule: function () {
31 if (!zuul.enabled) {
32 setTimeout(zuul.schedule, 5000);
33 return;
34 }
35 zuul.update().complete(function () {
36 setTimeout(zuul.schedule, 5000);
37 });
38 },
39
40 /** @return {jQuery.Promise} */
41 update: function () {
42 // Cancel the previous update if it hasn't completed yet.
43 if (xhr) {
44 xhr.abort();
45 }
46
47 zuul.emit('update-start');
48
49 xhr = $.ajax({
50 url: source,
51 dataType: 'json',
52 cache: false
53 })
54 .done(function (data) {
55 var html = '';
56 data = data || {};
57
58 if ('message' in data) {
59 $msg.html(data.message);
60 $msgWrap.removeClass('zuul-msg-wrap-off');
61 } else {
62 $msg.empty();
63 $msgWrap.addClass('zuul-msg-wrap-off');
64 }
65
Sergey Lukjanovdf550c52014-01-11 00:05:16 +040066 if ('zuul_version' in data) {
67 $('#zuul-version-span').text(data['zuul_version']);
68 }
Sergey Lukjanov4df0a222014-01-11 00:12:47 +040069 if ('last_reconfigured' in data) {
70 var last_reconfigured = new Date(data['last_reconfigured']);
71 $('#last-reconfigured-span').text(last_reconfigured.toString());
72 }
Sergey Lukjanovdf550c52014-01-11 00:05:16 +040073
Timo Tijhof51516cd2013-04-09 01:32:29 +020074 $.each(data.pipelines, function (i, pipeline) {
75 html += zuul.format.pipeline(pipeline);
76 });
77
78 // Only re-parse the DOM if we have to
79 if (html !== prevHtml) {
80 prevHtml = html;
81 $pipelines.html(html);
82 }
83
84 $queueEventsNum.text(
85 data.trigger_event_queue ? data.trigger_event_queue.length : '0'
86 );
87 $queueResultsNum.text(
88 data.result_event_queue ? data.result_event_queue.length : '0'
89 );
90 })
91 .fail(function (err, jqXHR, errMsg) {
92 $msg.text(source + ': ' + errMsg).show();
93 $msgWrap.removeClass('zuul-msg-wrap-off');
94 })
95 .complete(function () {
96 xhr = undefined;
97 zuul.emit('update-end');
98 });
99
100 return xhr;
101 },
102
103 format: {
104 change: function (change) {
105 var html = '<div class="well well-small zuul-change"><ul class="nav nav-list">',
106 id = change.id,
107 url = change.url;
108
109 html += '<li class="nav-header">' + change.project;
110 if (id.length === 40) {
111 id = id.substr(0, 7);
112 }
113 html += ' <span class="zuul-change-id">';
114 if (url !== null) {
115 html += '<a href="' + url + '">';
116 }
117 html += id;
118 if (url !== null) {
119 html += '</a>';
120 }
121 html += '</span></li>';
122
123 $.each(change.jobs, function (i, job) {
124 var result = job.result ? job.result.toLowerCase() : null,
125 resultClass = 'zuul-result label';
126 if (result === null) {
127 result = job.url ? 'in progress' : 'queued';
128 }
129 switch (result) {
130 case 'success':
131 resultClass += ' label-success';
132 break;
133 case 'failure':
134 resultClass += ' label-important';
135 break;
136 case 'lost':
137 case 'unstable':
138 resultClass += ' label-warning';
139 break;
140 }
141 html += '<li class="zuul-change-job">';
142 html += job.url !== null ?
143 '<a href="' + job.url + '" class="zuul-change-job-link">' :
144 '<span class="zuul-change-job-link">';
145 html += job.name;
146 html += ' <span class="' + resultClass + '">' + result + '</span>';
147 if (job.voting === false) {
148 html += ' <span class="muted">(non-voting)</span>';
149 }
150 html += job.url !== null ? '</a>' : '</span>';
151 html += '</li>';
152 });
153
154 html += '</ul></div>';
155 return html;
156 },
157
158 pipeline: function (pipeline) {
159 var html = '<div class="zuul-pipeline span4"><h3>' +
160 pipeline.name + '</h3>';
161 if (typeof pipeline.description === 'string') {
162 html += '<p><small>' + pipeline.description + '</small></p>';
163 }
164
165 $.each(pipeline.change_queues, function (queueNum, changeQueue) {
166 $.each(changeQueue.heads, function (headNum, changes) {
167 if (pipeline.change_queues.length > 1 && headNum === 0) {
168 var name = changeQueue.name;
169 html += '<p>Queue: <abbr title="' + name + '">';
170 if (name.length > 32) {
171 name = name.substr(0, 32) + '...';
172 }
173 html += name + '</abbr></p>';
174 }
175 $.each(changes, function (changeNum, change) {
176 // If there are multiple changes in the same head it means they're connected
177 if (changeNum > 0) {
178 html += '<div class="zuul-change-arrow">&uarr;</div>';
179 }
180 html += zuul.format.change(change);
181 });
182 });
183 });
184
185 html += '</div>';
186 return html;
187 }
188 },
189
190 emit: function () {
191 $jq.trigger.apply($jq, arguments);
192 return this;
193 },
194 on: function () {
195 $jq.on.apply($jq, arguments);
196 return this;
197 },
198 one: function () {
199 $jq.one.apply($jq, arguments);
200 return this;
201 }
202 };
203
204 $jq = $(zuul);
205
206 $jq.on('update-start', function () {
207 $container.addClass('zuul-container-loading');
208 $indicator.addClass('zuul-spinner-on');
209 });
210
211 $jq.on('update-end', function () {
212 $container.removeClass('zuul-container-loading');
213 setTimeout(function () {
214 $indicator.removeClass('zuul-spinner-on');
215 }, 550);
216 });
217
218 $jq.one('update-end', function () {
219 // Do this asynchronous so that if the first update adds a message, it will not animate
220 // while we fade in the content. Instead it simply appears with the rest of the content.
221 setTimeout(function () {
222 $container.addClass('zuul-container-ready'); // Fades in the content
223 });
224 });
225
226 $(function ($) {
227 $msg = $('<div class="zuul-msg alert alert-error"></div>');
228 $msgWrap = $msg.wrap('<div class="zuul-msg-wrap zuul-msg-wrap-off"></div>').parent();
229 $indicator = $('<span class="btn pull-right zuul-spinner">updating <i class="icon-refresh"></i></span>');
230 $queueInfo = $('<p>Queue lengths: <span>0</span> events, <span>0</span> results.</p>');
231 $queueEventsNum = $queueInfo.find('span').eq(0);
232 $queueResultsNum = $queueEventsNum.next();
233 $pipelines = $('<div class="row"></div>');
Sergey Lukjanovdf550c52014-01-11 00:05:16 +0400234 $zuulVersion = $('<p>Zuul version: <span id="zuul-version-span"></span></p>');
Sergey Lukjanov4df0a222014-01-11 00:12:47 +0400235 $lastReconf = $('<p>Last reconfigured: <span id="last-reconfigured-span"></span></p>');
Timo Tijhof51516cd2013-04-09 01:32:29 +0200236
Sergey Lukjanov4df0a222014-01-11 00:12:47 +0400237 $container = $('#zuul-container').append($msgWrap, $indicator, $queueInfo, $pipelines, $zuulVersion, $lastReconf);
Timo Tijhof51516cd2013-04-09 01:32:29 +0200238
239 zuul.schedule();
240
241 $(document).on({
242 'show.visibility': function () {
243 zuul.enabled = true;
244 zuul.update();
245 },
246 'hide.visibility': function () {
247 zuul.enabled = false;
248 }
249 });
250 });
251}(jQuery));