var PHPCensor = { intervals: {}, widgets: {}, webNotifiedBuilds: [], /* @var STATUS Refer to \PHPCensor\Model\Build.php constants. TODO: Transfer this variable to Build JS class so Build JS itself can use it as well. */ STATUS: [ 'Pending', 'Running', 'Success', 'Failed' ], init: function () { $(document).ready(function () { // Update latest builds every 5 seconds: PHPCensor.getBuilds(); PHPCensor.intervals.getBuilds = setInterval(PHPCensor.getBuilds, 5000); // Update latest project builds every 10 seconds: if (typeof PROJECT_ID != 'undefined') { PHPCensor.intervals.getProjectBuilds = setInterval(PHPCensor.getProjectBuilds, 10000); } }); $(window).on('builds-updated', function (e, data) { PHPCensor.updateHeaderBuilds(data); }); }, /** * Shallow comparison that determines that the build * has been shown as at least once as a web notification. * Also adds the build to a list of shown web notifications * if it's not found in the list. * @param object build * @return boolean */ isWebNotifiedBuild: function (build) { var o = PHPCensor.webNotifiedBuilds; for (var i = 0; i < o.length; i++) { var webNotifiedBuild = o[i]; var b = webNotifiedBuild.projectTitle === build.projectTitle && webNotifiedBuild.branch === build.branch && webNotifiedBuild.status === build.status && webNotifiedBuild.datePerformed === build.datePerformed && webNotifiedBuild.dateFinished === build.dateFinished; if (b) { return true; } } /* It's impossible to remember or use all previously shown builds. So let's clear them out once they reach 1000. @var 1000 Estimated. */ if (PHPCensor.webNotifiedBuilds.length > 1000) { PHPCensor.webNotifiedBuilds = []; } PHPCensor.webNotifiedBuilds.push(build); return false; }, /** * Web notification. * Chrome doesn't allow insecure protocols. * Enable HTTPS even on localhost in order for * web notifications to work properly. * @param object data Contains an array of builds. * @return void */ showWebNotification: function (data) { var pending = data.pending; var running = data.running; var success = data.success; var failed = data.failed; var notification = null; //Determine which notification to show. //TODO: Refactor. Use foreach. if (pending && pending.count > 0) { notification = pending; } else if (running && running.count > 0) { notification = running; } else if (success && success.count > 0) { notification = success; } else if (failed && failed.count > 0) { notification = failed; } if (notification) { var msg = ''; if (!Notify.needsPermission) { var items = notification.items; for (var item in items) { var build = items[item].build; var projTitle = build.project_title; var branch = build.branch; var status = PHPCensor.STATUS[build.status]; var datePerformed = build.date_performed; var dateFinished = build.date_finished; var rn = "\r\n"; var build = { projectTitle: projTitle, branch: branch, status: status, datePerformed: datePerformed, dateFinished: dateFinished }; //Ignore if the last displayed notification is //similar to what we're again about to display. if (!PHPCensor.isWebNotifiedBuild(build)) { msg += 'Project title: ' + projTitle + rn + 'Git branch: ' + branch + rn + 'Status: ' + status + rn; //Build details is empty during //widget-all-projects-update. if (datePerformed.length > 0) { msg += datePerformed + rn; } if (dateFinished.length > 0) { msg += dateFinished; } new Notify( 'PHP Censor Web Notification', {body: msg} ).show(); } } } else if (Notify.isSupported()) { Notify.requestPermission(null, function(){ msg = 'Web notifications permission ' + 'has been denied by the user.' console.warn(msg); }); } } }, getBuilds: function () { $.ajax({ url: APP_URL + 'build/ajax-queue', success: function (data) { $(window).trigger('builds-updated', [data]); }, error: PHPCensor.handleFailedAjax }); if (NOTIFICATIONS) { $.ajax({ url: APP_URL + 'web-notifications/builds-updated', success: function (data) { PHPCensor.showWebNotification(data); }, error: PHPCensor.handleFailedAjax }); } }, getProjectBuilds: function () { $.ajax({ url: APP_URL + 'project/ajax-builds/' + PROJECT_ID + '?branch=' + PROJECT_BRANCH + '&environment=' + PROJECT_ENVIRONMENT + '&per_page=' + PER_PAGE + '&page=' + PAGE, success: function (data) { $('#latest-builds').html(data); }, error: PHPCensor.handleFailedAjax }); }, updateHeaderBuilds: function (data) { $('.app-pending-list').empty(); $('.app-running-list').empty(); if (!data.pending.count) { $('.app-pending').hide(); } else { $('.app-pending').show(); $('.app-pending .header').text(Lang.get('n_builds_pending', data.pending.count)); $.each(data.pending.items, function (idx, build) { $('.app-pending-list').append(build.header_row); }); } if (!data.running.count) { $('.app-running').hide(); } else { $('.app-running').show(); $('.app-running .header').text(Lang.get('n_builds_running', data.running.count)); $.each(data.running.items, function (idx, build) { $('.app-running-list').append(build.header_row); }); } }, get: function (uri, success) { $.ajax({ url: window.APP_URL + uri, success: function (data) { success(); }, error: PHPCensor.handleFailedAjax }); }, handleFailedAjax: function (xhr) { if (xhr.status == 401) { window.location.href = window.APP_URL + 'session/login'; } } }; PHPCensor.init(); function handleFailedAjax(xhr) { PHPCensor.handleFailedAjax(xhr); } /** * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind * for the details of code below */ if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () { }, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; } /** * Used for delete buttons in the system, just to prevent accidental clicks. */ function confirmDelete(url, reloadAfter) { var dialog = new PHPCensorConfirmDialog({ title: Lang.get('confirm_title'), message: Lang.get('confirm_message'), confirmBtnCaption: Lang.get('confirm_ok'), cancelBtnCaption: Lang.get('confirm_cancel'), /* confirm-btn click handler */ confirmed: function (e) { var dialog = this; e.preventDefault(); /* Call delete URL */ $.ajax({ url: url, success: function (data) { if (reloadAfter) { dialog.onClose = function () { window.location.reload(); }; } dialog.showStatusMessage(Lang.get('confirm_success'), 500); }, error: function (data) { dialog.showStatusMessage(Lang.get('confirm_failed') + data.statusText); if (data.status == 401) { handleFailedAjax(data); } } }); } }); dialog.show(); return dialog; } /** * PHPCensorConfirmDialog constructor options object * @type {{message: string, title: string, confirmBtnCaption: string, cancelBtnCaption: string, confirmed: Function}} */ var PHPCensorConfirmDialogOptions = { message: 'Are you sure?', title: 'Confirmation', confirmBtnCaption: 'Ok', cancelBtnCaption: 'Cancel', confirmed: function (e) { this.close(); } }; var PHPCensorConfirmDialog = Class.extend({ /** * @private * @var {bool} Determines whether the dialog has been confirmed */ confirmed: false, /** * @param {PHPCensorConfirmDialogOptions} options */ init: function (options) { options = options ? $.extend(PHPCensorConfirmDialogOptions, options) : PHPCensorConfirmDialogOptions; if (!$('#confirm-dialog').length) { /* Add the dialog html to a page on first use. No need to have it there before first use. */ $('body').append( '