blob: 43930a2c30f68b8e8bfb680cebb80b0f0cdd5615 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"use strict";
2/* All shared functionality to go in libtoaster object.
3 * This object really just helps readability since we can then have
4 * a traceable namespace.
5 */
6var libtoaster = (function (){
7
8 /* makeTypeahead parameters
9 * elementSelector: JQuery elementSelector string
10 * xhrUrl: the url to get the JSON from expects JSON in the form:
11 * { "list": [ { "name": "test", "detail" : "a test thing" }, .... ] }
12 * xhrParams: the data/parameters to pass to the getJSON url e.g.
13 * { 'type' : 'projects' } the text typed will be passed as 'search'.
14 * selectedCB: function to call once an item has been selected one
15 * arg of the item.
16 */
17 function _makeTypeahead (jQElement, xhrUrl, xhrParams, selectedCB) {
18 if (!xhrUrl || xhrUrl.length === 0)
19 throw("No url to typeahead supplied");
20
21 var xhrReq;
22
23 jQElement.typeahead({
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050024 // each time the typeahead's choices change, a
25 // "typeahead-choices-change" event is fired with an object
26 // containing the available choices in a "choices" property
Patrick Williamsc124f4f2015-09-15 14:41:29 -050027 source: function(query, process){
28 xhrParams.search = query;
29
30 /* If we have a request in progress don't fire off another one*/
31 if (xhrReq)
32 xhrReq.abort();
33
34 xhrReq = $.getJSON(xhrUrl, this.options.xhrParams, function(data){
35 if (data.error !== "ok") {
36 console.log("Error getting data from server "+data.error);
37 return;
38 }
39
40 xhrReq = null;
41
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050042 jQElement.trigger("typeahead-choices-change", {choices: data.results});
43
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044 return process(data.results);
45 });
46 },
47 updater: function(item) {
48 var itemObj = this.$menu.find('.active').data('itemObject');
49 selectedCB(itemObj);
50 return item;
51 },
52 matcher: function(item) {
53 if (!item.hasOwnProperty('name')) {
54 console.log("Name property missing in data");
55 return 0;
56 }
57
58 if (this.$element.val().length === 0)
59 return 0;
60
61 return 1;
62 },
63 highlighter: function (item) {
64 /* Use jquery to escape the item name and detail */
65 var current = $("<span></span>").text(item.name + ' '+item.detail);
66 current = current.html();
67
68 var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
69 return current.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
70 return '<strong>' + match + '</strong>'
71 })
72 },
73 sorter: function (items) { return items; },
74 xhrUrl: xhrUrl,
75 xhrParams: xhrParams,
76 xhrReq: xhrReq,
77 });
78
79
80 /* Copy of bootstrap's render func but sets selectedObject value */
81 function customRenderFunc (items) {
82 var that = this;
83
84 items = $(items).map(function (i, item) {
85 i = $(that.options.item).attr('data-value', item.name).data('itemObject', item);
86 i.find('a').html(that.highlighter(item));
87 return i[0];
88 });
89
90 items.first().addClass('active');
91 this.$menu.html(items);
92 return this;
93 }
94
95 jQElement.data('typeahead').render = customRenderFunc;
96 }
97
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050098 /* startABuild:
99 * url: xhr_buildrequest or null for current project
100 * targets: an array or space separated list of targets to build
101 * onsuccess: callback for successful execution
102 * onfail: callback for failed execution
103 */
104 function _startABuild (url, targets, onsuccess, onfail) {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500105
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500106 if (!url)
107 url = libtoaster.ctx.xhrBuildRequestUrl;
108
109 /* Flatten the array of targets into a space spearated list */
110 if (targets instanceof Array){
111 targets = targets.reduce(function(prevV, nextV){
112 return prev + ' ' + next;
113 });
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114 }
115
116 $.ajax( {
117 type: "POST",
118 url: url,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500119 data: { 'targets' : targets },
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
121 success: function (_data) {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500122 if (_data.error !== "ok") {
123 console.warn(_data.error);
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500124 } else {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500125 if (onsuccess !== undefined) onsuccess(_data);
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500126 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500127 },
128 error: function (_data) {
129 console.warn("Call failed");
130 console.warn(_data);
131 if (onfail) onfail(data);
132 } });
133 }
134
135 /* cancelABuild:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500136 * url: xhr_buildrequest url or null for current project
137 * buildRequestIds: space separated list of build request ids
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138 * onsuccess: callback for successful execution
139 * onfail: callback for failed execution
140 */
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500141 function _cancelABuild(url, buildRequestIds, onsuccess, onfail){
142 if (!url)
143 url = libtoaster.ctx.xhrBuildRequestUrl;
144
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500145 $.ajax( {
146 type: "POST",
147 url: url,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500148 data: { 'buildCancel': buildRequestIds },
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
150 success: function (_data) {
151 if (_data.error !== "ok") {
152 console.warn(_data.error);
153 } else {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500154 if (onsuccess) onsuccess(_data);
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 }
156 },
157 error: function (_data) {
158 console.warn("Call failed");
159 console.warn(_data);
160 if (onfail) onfail(_data);
161 }
162 });
163 }
164
165 /* Get a project's configuration info */
166 function _getProjectInfo(url, onsuccess, onfail){
167 $.ajax({
168 type: "GET",
169 data : { format: "json" },
170 url: url,
171 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
172 success: function (_data) {
173 if (_data.error !== "ok") {
174 console.warn(_data.error);
175 } else {
176 if (onsuccess !== undefined) onsuccess(_data);
177 }
178 },
179 error: function (_data) {
180 console.warn(_data);
181 if (onfail) onfail(_data);
182 }
183 });
184 }
185
186 /* Properties for data can be:
187 * layerDel (csv)
188 * layerAdd (csv)
189 * projectName
190 * projectVersion
191 * machineName
192 */
193 function _editCurrentProject(data, onSuccess, onFail){
194 $.ajax({
195 type: "POST",
196 url: libtoaster.ctx.projectPageUrl + "?format=json",
197 data: data,
198 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
199 success: function (data) {
200 if (data.error != "ok") {
201 console.log(data.error);
202 if (onFail !== undefined)
203 onFail(data);
204 } else {
205 if (onSuccess !== undefined)
206 onSuccess(data);
207 }
208 },
209 error: function (data) {
210 console.log("Call failed");
211 console.log(data);
212 }
213 });
214 }
215
216 function _getLayerDepsForProject(url, onSuccess, onFail){
217 /* Check for dependencies not in the current project */
218 $.getJSON(url,
219 { format: 'json' },
220 function(data) {
221 if (data.error != "ok") {
222 console.log(data.error);
223 if (onFail !== undefined)
224 onFail(data);
225 } else {
226 var deps = {};
227 /* Filter out layer dep ids which are in the
228 * project already.
229 */
230 deps.list = data.layerdeps.list.filter(function(layerObj){
231 return (data.projectlayers.lastIndexOf(layerObj.id) < 0);
232 });
233
234 onSuccess(deps);
235 }
236 }, function() {
237 console.log("E: Failed to make request");
238 });
239 }
240
241 /* parses the query string of the current window.location to an object */
242 function _parseUrlParams() {
243 var string = window.location.search;
244 string = string.substr(1);
245 var stringArray = string.split ("&");
246 var obj = {};
247
248 for (var i in stringArray) {
249 var keyVal = stringArray[i].split ("=");
250 obj[keyVal[0]] = keyVal[1];
251 }
252
253 return obj;
254 }
255
256 /* takes a flat object and outputs it as a query string
257 * e.g. the output of dumpsUrlParams
258 */
259 function _dumpsUrlParams(obj) {
260 var str = "?";
261
262 for (var key in obj){
263 if (!obj[key])
264 continue;
265
266 str += key+ "="+obj[key].toString();
267 str += "&";
268 }
269
270 /* Maintain the current hash */
271 str += window.location.hash;
272
273 return str;
274 }
275
276 function _addRmLayer(layerObj, add, doneCb){
277 if (add === true) {
278 /* If adding get the deps for this layer */
279 libtoaster.getLayerDepsForProject(layerObj.layerdetailurl,
280 function (layers) {
281
282 /* got result for dependencies */
283 if (layers.list.length === 0){
284 var editData = { layerAdd : layerObj.id };
285 libtoaster.editCurrentProject(editData, function() {
286 doneCb([]);
287 });
288 return;
289 } else {
290 try {
291 showLayerDepsModal(layerObj, layers.list, null, null, true, doneCb);
292 } catch (e) {
293 $.getScript(libtoaster.ctx.jsUrl + "layerDepsModal.js", function(){
294 showLayerDepsModal(layerObj, layers.list, null, null, true, doneCb);
295 }, function(){
296 console.warn("Failed to load layerDepsModal");
297 });
298 }
299 }
300 }, null);
301 } else if (add === false) {
302 var editData = { layerDel : layerObj.id };
303
304 libtoaster.editCurrentProject(editData, function () {
305 doneCb([]);
306 }, function () {
307 console.warn ("Removing layer from project failed");
308 doneCb(null);
309 });
310 }
311 }
312
313 function _makeLayerAddRmAlertMsg(layer, layerDepsList, add) {
314 var alertMsg;
315
316 if (layerDepsList.length > 0 && add === true) {
317 alertMsg = $("<span>You have added <strong>"+(layerDepsList.length+1)+"</strong> layers to your project: <a id=\"layer-affected-name\"></a> and its dependencies </span>");
318
319 /* Build the layer deps list */
320 layerDepsList.map(function(layer, i){
321 var link = $("<a></a>");
322
323 link.attr("href", layer.layerdetailurl);
324 link.text(layer.name);
325 link.tooltip({title: layer.tooltip});
326
327 if (i !== 0)
328 alertMsg.append(", ");
329
330 alertMsg.append(link);
331 });
332 } else if (layerDepsList.length === 0 && add === true) {
333 alertMsg = $("<span>You have added <strong>1</strong> layer to your project: <a id=\"layer-affected-name\"></a></span></span>");
334 } else if (add === false) {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500335 alertMsg = $("<span>You have removed <strong>1</strong> layer from your project: <a id=\"layer-affected-name\"></a></span>");
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500336 }
337
338 alertMsg.children("#layer-affected-name").text(layer.name);
339 alertMsg.children("#layer-affected-name").attr("href", layer.layerdetailurl);
340
341 return alertMsg.html();
342 }
343
344 function _showChangeNotification(message){
345 var alertMsg = $("#change-notification-msg");
346
347 alertMsg.html(message);
348 $("#change-notification, #change-notification *").fadeIn();
349 }
350
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500351 function _createCustomRecipe(name, baseRecipeId, doneCb){
352 var data = {
353 'name' : name,
354 'project' : libtoaster.ctx.projectId,
355 'base' : baseRecipeId,
356 };
357
358 $.ajax({
359 type: "POST",
360 url: libtoaster.ctx.xhrCustomRecipeUrl,
361 data: data,
362 headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
363 success: function (ret) {
364 if (doneCb){
365 doneCb(ret);
366 } else if (ret.error !== "ok") {
367 console.warn(ret.error);
368 }
369 },
370 error: function (ret) {
371 console.warn("Call failed");
372 console.warn(ret);
373 }
374 });
375 }
376
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500377
378 return {
379 reload_params : reload_params,
380 startABuild : _startABuild,
381 cancelABuild : _cancelABuild,
382 makeTypeahead : _makeTypeahead,
383 getProjectInfo: _getProjectInfo,
384 getLayerDepsForProject : _getLayerDepsForProject,
385 editCurrentProject : _editCurrentProject,
386 debug: false,
387 parseUrlParams : _parseUrlParams,
388 dumpsUrlParams : _dumpsUrlParams,
389 addRmLayer : _addRmLayer,
390 makeLayerAddRmAlertMsg : _makeLayerAddRmAlertMsg,
391 showChangeNotification : _showChangeNotification,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500392 createCustomRecipe: _createCustomRecipe,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500393 };
394})();
395
396/* keep this in the global scope for compatability */
397function reload_params(params) {
398 var uri = window.location.href;
399 var splitlist = uri.split("?");
400 var url = splitlist[0];
401 var parameters = splitlist[1];
402 // deserialize the call parameters
403 var cparams = [];
404 if(parameters)
405 cparams = parameters.split("&");
406
407 var nparams = {};
408 for (var i = 0; i < cparams.length; i++) {
409 var temp = cparams[i].split("=");
410 nparams[temp[0]] = temp[1];
411 }
412 // update parameter values
413 for (i in params) {
414 nparams[encodeURIComponent(i)] = encodeURIComponent(params[i]);
415 }
416 // serialize the structure
417 var callparams = [];
418 for (i in nparams) {
419 callparams.push(i+"="+nparams[i]);
420 }
421 window.location.href = url+"?"+callparams.join('&');
422}
423
424
425/* Things that happen for all pages */
426$(document).ready(function() {
427
428 var ajaxLoadingTimer;
429
430 /* If we don't have a console object which might be the case in some
431 * browsers, no-op it to avoid undefined errors.
432 */
433 if (!window.console) {
434 window.console = {};
435 window.console.warn = function() {};
436 window.console.error = function() {};
437 }
438
439 /*
440 * PrettyPrint plugin.
441 *
442 */
443 // Init
444 prettyPrint();
445
446 // Prevent invalid links from jumping page scroll
447 $('a[href=#]').click(function() {
448 return false;
449 });
450
451
452 /* START TODO Delete this section now redundant */
453 /* Belen's additions */
454
455 // turn Edit columns dropdown into a multiselect menu
456 $('.dropdown-menu input, .dropdown-menu label').click(function(e) {
457 e.stopPropagation();
458 });
459
460 // enable popovers in any table cells that contain an anchor with the
461 // .btn class applied, and make sure popovers work on click, are mutually
462 // exclusive and they close when your click outside their area
463
464 $('html').click(function(){
465 $('td > a.btn').popover('hide');
466 });
467
468 $('td > a.btn').popover({
469 html:true,
470 placement:'left',
471 container:'body',
472 trigger:'manual'
473 }).click(function(e){
474 $('td > a.btn').not(this).popover('hide');
475 // ideally we would use 'toggle' here
476 // but it seems buggy in our Bootstrap version
477 $(this).popover('show');
478 e.stopPropagation();
479 });
480
481 // enable tooltips for applied filters
482 $('th a.btn-primary').tooltip({container:'body', html:true, placement:'bottom', delay:{hide:1500}});
483
484 // hide applied filter tooltip when you click on the filter button
485 $('th a.btn-primary').click(function () {
486 $('.tooltip').hide();
487 });
488
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500489 /* Initialise bootstrap tooltips */
490 $(".get-help, [data-toggle=tooltip]").tooltip({
491 container : 'body',
492 html : true,
493 delay: { show : 300 }
494 });
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495
496 // show help bubble only on hover inside tables
497 $(".hover-help").css("visibility","hidden");
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500498
499 $("table").on("mouseover", "th, td", function () {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500 $(this).find(".hover-help").css("visibility","visible");
501 });
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500502
503 $("table").on("mouseleave", "th, td", function () {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500504 $(this).find(".hover-help").css("visibility","hidden");
505 });
506
507 /* END TODO Delete this section now redundant */
508
509 // show task type and outcome in task details pages
510 $(".task-info").tooltip({ container: 'body', html: true, delay: {show: 200}, placement: 'right' });
511
512 // initialise the tooltips for the icon-pencil icons
513 $(".icon-pencil").tooltip({ container: 'body', html: true, delay: {show: 400}, title: "Change" });
514
515 // initialise the tooltips for the download icons
516 $(".icon-download-alt").tooltip({ container: 'body', html: true, delay: { show: 200 } });
517
518 // initialise popover for debug information
519 $(".icon-info-sign").popover( { placement: 'bottom', html: true, container: 'body' });
520
521 // linking directly to tabs
522 $(function(){
523 var hash = window.location.hash;
524 $('ul.nav a[href="' + hash + '"]').tab('show');
525
526 $('.nav-tabs a').click(function () {
527 $(this).tab('show');
528 $('body').scrollTop();
529 });
530 });
531
532 // toggle for long content (variables, python stack trace, etc)
533 $('.full, .full-hide').hide();
534 $('.full-show').click(function(){
535 $('.full').slideDown(function(){
536 $('.full-hide').show();
537 });
538 $(this).hide();
539 });
540 $('.full-hide').click(function(){
541 $(this).hide();
542 $('.full').slideUp(function(){
543 $('.full-show').show();
544 });
545 });
546
547 //toggle the errors and warnings sections
548 $('.show-errors').click(function() {
549 $('#collapse-errors').addClass('in');
550 });
551 $('.toggle-errors').click(function() {
552 $('#collapse-errors').toggleClass('in');
553 });
554 $('.show-warnings').click(function() {
555 $('#collapse-warnings').addClass('in');
556 });
557 $('.toggle-warnings').click(function() {
558 $('#collapse-warnings').toggleClass('in');
559 });
560 $('.show-exceptions').click(function() {
561 $('#collapse-exceptions').addClass('in');
562 });
563 $('.toggle-exceptions').click(function() {
564 $('#collapse-exceptions').toggleClass('in');
565 });
566
567
568 $("#hide-alert").click(function(){
569 $(this).parent().fadeOut();
570 });
571
572 //show warnings section when requested from the previous page
573 if (location.href.search('#warnings') > -1) {
574 $('#collapse-warnings').addClass('in');
575 }
576
577 /* Show the loading notification if nothing has happend after 1.5
578 * seconds
579 */
580 $(document).bind("ajaxStart", function(){
581 if (ajaxLoadingTimer)
582 window.clearTimeout(ajaxLoadingTimer);
583
584 ajaxLoadingTimer = window.setTimeout(function() {
585 $("#loading-notification").fadeIn();
586 }, 1200);
587 });
588
589 $(document).bind("ajaxStop", function(){
590 if (ajaxLoadingTimer)
591 window.clearTimeout(ajaxLoadingTimer);
592
593 $("#loading-notification").fadeOut();
594 });
595
596 $(document).ajaxError(function(event, jqxhr, settings, errMsg){
597 if (errMsg === 'abort')
598 return;
599
600 console.warn("Problem with xhr call");
601 console.warn(errMsg);
602 console.warn(jqxhr.responseText);
603 });
604
605 function check_for_duplicate_ids () {
606 /* warn about duplicate element ids */
607 var ids = {};
608 $("[id]").each(function() {
609 if (this.id && ids[this.id]) {
610 console.warn('Duplicate element id #'+this.id);
611 }
612 ids[this.id] = true;
613 });
614 }
615
616 if (libtoaster.debug) {
617 check_for_duplicate_ids();
618 } else {
619 /* Debug is false so supress warnings by overriding the functions */
620 window.console.warn = function () {};
621 window.console.error = function () {};
622 }
623});