github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/public/static/js/mci_module.js (about) 1 var mciModule = angular.module('MCI', [ 2 'filters.common', 3 'directives.eventLogs', 4 'directives.visualization', 5 'directives.badges', 6 'directives.admin', 7 'directives.drawer', 8 'directives.github', 9 'directives.patch', 10 'directives.spawn', 11 'directives.events', 12 'directives.tristateCheckbox', 13 'directives.svg', 14 'directives.confirm', 15 'mciServices.rest', 16 'mciServices.locationHash', 17 'md5', 18 'ngSanitize' 19 ], function($interpolateProvider) { 20 // Use [[ ]] to delimit AngularJS bindings, because using {{ }} confuses go 21 $interpolateProvider.startSymbol('[['); 22 $interpolateProvider.endSymbol(']]'); 23 }).factory('$now', [function() { 24 return { 25 now: function() { 26 return new Date(); 27 } 28 }; 29 }]).filter('taskStatusLabel', function() { 30 return function(status, type) { 31 switch (status) { 32 case 'started': 33 return type + '-info'; 34 case 'undispatched': 35 return type + '-default'; 36 case 'dispatched': 37 return type + '-info'; 38 case 'failed': 39 return type + '-danger'; 40 case 'cancelled': 41 return type + '-warning'; 42 case 'success': 43 return type + '-success'; 44 default: 45 return type + '-default'; 46 } 47 } 48 }).directive('bsPopover', function($parse, $compile, $templateCache, $q, $http) { 49 // This is adapted from https://github.com/mgcrea/angular-strap/blob/master/src/directives/popover.js 50 // but hacked to allow passing in the 'container' option 51 // and fix weirdness by wrapping element with $() 52 53 // Hide popovers when pressing esc 54 $('body').on('keyup', function(ev) { 55 if (ev.keyCode === 27) { 56 $('.popover.in').popover('hide'); 57 } 58 }); 59 var type = 'popover', 60 dataPrefix = !!$.fn.emulateTransitionEnd ? 'bs.' : '', 61 evSuffix = dataPrefix ? '.' + dataPrefix + type : ''; 62 63 return { 64 restrict: 'A', 65 scope: true, 66 link: function postLink(scope, element, attr, ctrl) { 67 var getter = $parse(attr.bsPopover), 68 setter = getter.assign, 69 value = getter(scope), 70 options = {}; 71 72 if (angular.isObject(value)) { 73 options = value; 74 } 75 76 $q.when(options.content || $templateCache.get(value) || $http.get(value, { 77 cache: true 78 })).then(function onSuccess(template) { 79 80 // Handle response from $http promise 81 if (angular.isObject(template)) { 82 template = template.data; 83 } 84 85 // Handle data-placement and data-trigger attributes 86 _.forEach(['placement', 'trigger', 'container'], function(name) { 87 if (!!attr[name]) { 88 options[name] = attr[name]; 89 } 90 }); 91 92 // Handle data-unique attribute 93 if (!!attr.unique) { 94 $(element).on('show' + evSuffix, function(ev) { // requires bootstrap 2.3.0+ 95 // Hide any active popover except self 96 $('.popover.in').not(element).popover('hide'); 97 }); 98 } 99 100 // Handle data-hide attribute to toggle visibility 101 if (!!attr.hide) { 102 scope.$watch(attr.hide, function(newValue, oldValue) { 103 if (!!newValue) { 104 popover.hide(); 105 } else if (newValue !== oldValue) { 106 $timeout(function() { 107 popover.show(); 108 }); 109 } 110 }); 111 } 112 113 if (!!attr.show) { 114 scope.$watch(attr.show, function(newValue, oldValue) { 115 if (!!newValue) { 116 $timeout(function() { 117 popover.show(); 118 }); 119 } else if (newValue !== oldValue) { 120 popover.hide(); 121 } 122 }); 123 } 124 125 // Initialize popover 126 $(element).popover(angular.extend({}, options, { 127 content: template, 128 html: true 129 })); 130 131 // Bootstrap override to provide tip() reference & compilation 132 var popover = $(element).data(dataPrefix + type); 133 popover.hasContent = function() { 134 return this.getTitle() || template; // fix multiple $compile() 135 }; 136 popover.getPosition = function() { 137 var r = $.fn.popover.Constructor.prototype.getPosition.apply(this, arguments); 138 139 // Compile content 140 $compile(this.$tip)(scope); 141 scope.$digest(); 142 143 // Bind popover to the tip() 144 this.$tip.data(dataPrefix + type, this); 145 146 return r; 147 }; 148 149 // Provide scope display functions 150 scope.$popover = function(name) { 151 popover(name); 152 }; 153 _.forEach(['show', 'hide'], function(name) { 154 scope[name] = function() { 155 popover[name](); 156 }; 157 }); 158 scope.dismiss = scope.hide; 159 160 // Emit popover events 161 _.forEach(['show', 'shown', 'hide', 'hidden'], function(name) { 162 $(element).on(name + evSuffix, function(ev) { 163 scope.$emit('popover-' + name, ev); 164 }); 165 }); 166 167 }); 168 } 169 } 170 }).directive('elementTooltip', function() { 171 return { 172 scope: true, 173 link: function(scope, element, attrs) { 174 scope.$watch(attrs.elementTooltip, function(tip) { 175 var obj = { 176 title: tip 177 }; 178 if (attrs.elementTooltipContainer) { 179 obj.container = attrs.elementTooltipContainer; 180 } 181 182 $(element).elementTooltip = $(element).tooltip(obj); 183 $(element).attr('title', tip).tooltip('fixTitle'); 184 }); 185 } 186 } 187 }).directive('buildTasksResultsBar', function() { 188 return function(scope, element, attrs) { 189 // Progress bar to display the state of tasks for a given uiBuild 190 scope.$watch(attrs.buildTasksResultsBar, function(build) { 191 if (build) { 192 var numSuccess = 0; 193 var numFailed = 0; 194 var numStarted = 0; 195 var numNeither = 0; 196 197 for (var i = 0; i < build.Tasks.length; ++i) { 198 switch (build.Tasks[i].Task.Status) { 199 case 'success': 200 ++numSuccess; 201 break; 202 case 'failed': 203 ++numFailed; 204 break; 205 case 'started': 206 ++numStarted; 207 break; 208 default: 209 ++numNeither; 210 break; 211 } 212 } 213 214 var successTitle = numSuccess + " task" + (numSuccess == 1 ? "" : "s") + " succeeded"; 215 var failedTitle = numFailed + " task" + (numFailed == 1 ? "" : "s") + " failed"; 216 var startedTitle = numStarted + " task" + (numStarted == 1 ? "" : "s") + " in progress"; 217 var neitherTitle = numNeither + " task" + (numNeither == 1 ? "" : "s") + " not started or cancelled"; 218 element.html('<div class="progress-bar progress-bar-success" role="progressbar" style="width: ' + (numSuccess / build.Tasks.length * 100) + '%" data-toggle="tooltip" data-animation="" title="' + successTitle + '"></div>' + 219 '<div class="progress-bar progress-bar-danger" role="progressbar" style="width: ' + (numFailed / build.Tasks.length * 100) + '%" data-toggle="tooltip" data-animation="" title="' + failedTitle + '"></div>' + 220 '<div class="progress-bar progress-bar-warning" role="progressbar" style="width: ' + (numStarted / build.Tasks.length * 100) + '%" data-toggle="tooltip" data-animation="" title="' + startedTitle + '"></div>' + 221 '<div class="progress-bar progress-bar-default" role="progressbar" style="width: ' + (numNeither / build.Tasks.length * 100) + '%" data-toggle="tooltip" data-animation="" title="' + neitherTitle + '"></div>'); 222 223 $(element.children('*[data-toggle="tooltip"]')).each(function(i, el) { 224 $(el).tooltip(); 225 }); 226 } 227 }); 228 }; 229 }).filter('statusFilter', function() { 230 return function(task) { 231 // for task test results, return the status passed in 232 if (task !== Object(task)) { 233 return task; 234 } 235 var cls = task.status; 236 if (task.status == 'undispatched') { 237 if (!task.activated) { 238 cls = 'inactive'; 239 } else { 240 cls = 'unstarted'; 241 } 242 } else if (task.status == 'started') { 243 cls = 'started'; 244 } else if (task.status == 'success') { 245 cls = 'success'; 246 } else if (task.status == 'failed') { 247 cls = 'failed'; 248 if ('task_end_details' in task) { 249 if ('type' in task.task_end_details) { 250 if (task.task_end_details.type == 'system') { 251 cls = 'system-failed'; 252 } 253 } 254 if ('timed_out' in task.task_end_details) { 255 if (task.task_end_details.timed_out && 'desc' in task.task_end_details && task.task_end_details.desc == 'heartbeat') { 256 cls = 'system-failed'; 257 } 258 } 259 } 260 } 261 return cls; 262 } 263 }).filter('statusLabel', function() { 264 return function(task) { 265 if (task.status == 'started') { 266 return 'started'; 267 } else if (task.status == 'undispatched' && task.activated) { 268 if (task.task_waiting) { 269 return task.task_waiting; 270 } 271 return 'scheduled'; 272 } else if (task.status == 'undispatched' && !task.activated){ 273 // dispatch_time could be a string or a number. to check when the dispatch_time 274 // is a real value, this if-statement accounts for cases where 275 // dispatch_time is 0, "0" or even new Date(0) or older. 276 if(+task.dispatch_time == 0 || (typeof task.dispatch_time == "string" && +new Date(task.dispatch_time) <= 0)){ 277 return "not scheduled" 278 } 279 return 'aborted'; 280 } else if (task.status == 'success') { 281 return 'success'; 282 } else if (task.status == 'failed') { 283 if ('task_end_details' in task) { 284 if ('timed_out' in task.task_end_details) { 285 if (task.task_end_details.timed_out && 'desc' in task.task_end_details && task.task_end_details.desc == 'heartbeat') { 286 return 'system unresponsive'; 287 } 288 289 if (task.task_end_details.type == 'system') { 290 return 'system timed out'; 291 } 292 return 'test timed out'; 293 } 294 if (task.task_end_details.type == 'system') { 295 return 'system failure'; 296 } 297 return 'failed'; 298 } 299 } 300 return task.status; 301 } 302 }).filter('endOfPath', function() { 303 return function(input) { 304 var lastSlash = input.lastIndexOf('/'); 305 if (lastSlash === -1 || lastSlash === input.length - 1) { 306 // try to find the index using windows-style filesystem separators 307 lastSlash = input.lastIndexOf('\\'); 308 if (lastSlash === -1 || lastSlash === input.length - 1) { 309 return input; 310 } 311 } 312 return input.substring(lastSlash + 1); 313 } 314 }).filter('buildStatus', function() { 315 // given a list of tasks, returns the status of the overall build. 316 return function(tasks) { 317 var countSuccess = 0; 318 var countInProgress = 0; 319 var countActive = 0; 320 for(i=0;i<tasks.length;i++){ 321 // if any task is failed, the build status is "failed" 322 if(tasks[i].status == "failed"){ 323 return "block-status-failed"; 324 } 325 if(tasks[i].status == "success"){ 326 countSuccess++; 327 } else if(tasks[i].status == "dispatched" || tasks[i].status=="started"){ 328 countInProgress++; 329 } else if(tasks[i].status == "undispatched") { 330 countActive += tasks[i].activated ? 1 : 0; 331 } 332 } 333 if(countSuccess == tasks.length){ 334 // all tasks are passing 335 return "block-status-success"; 336 }else if(countInProgress>0){ 337 // no failures yet, but at least 1 task in still progress 338 return "block-status-started"; 339 }else if(countActive>0){ 340 // no failures yet, but at least 1 task still active 341 return "block-status-created"; 342 } 343 // no active tasks pending 344 return "block-status-inactive"; 345 } 346 }).factory('mciTime', [function() { 347 var $time = { 348 now: function() { 349 return new Date(); 350 }, 351 // Some browsers, e.g. Safari, don't handle things like new Date(undefined) 352 // particularly well, so this is to avoid that headache 353 fromNanoseconds: function(nano) { 354 if (nano) { 355 return new Date(Math.ceil(nano / (1000 * 1000))); 356 } 357 return null; 358 }, 359 fromMilliseconds: function(ms) { 360 if (ms) { 361 return new Date(ms); 362 } 363 return null; 364 }, 365 finishConditional: function(start, finish, now) { 366 // Pretty common calculation - if start is undefined, return 0. 367 // If start is defined and finish isn't, return now - start in millis, 368 // and if start and finish are both defined, return finish - start in millis 369 370 if (!start || isNaN(start.getTime()) || start.getTime() <= 0) { 371 return 0; 372 } else if (!finish || isNaN(finish.getTime()) || finish.getTime() <= 0) { 373 return (now || $time.now()).getTime() - start.getTime(); 374 } else { 375 return finish.getTime() - start.getTime(); 376 } 377 } 378 }; 379 380 return $time; 381 }]).config(['$compileProvider', function ($compileProvider) { 382 //$compileProvider.debugInfoEnabled(false); 383 }]); 384