github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/public/static/js/version.js (about) 1 mciModule.controller('VersionController', function($scope, $rootScope, $location, $http, $filter, $now, $window, notificationService) { 2 var nsPerMs = 1000000 3 $scope.canEdit = $window.canEdit 4 $scope.jiraHost = $window.jiraHost; 5 6 var dateSorter = function(a, b){ return (+a) - (+b) } 7 $scope.tab = 0 8 $scope.version = {}; 9 $scope.taskStatuses = {}; 10 hash = $location.hash(); 11 path = $location.path(); 12 $scope.collapsed = localStorage.getItem("collapsed") == "true"; 13 14 // If a tab number is specified in the URL, parse it out and set the tab 15 // number in the scope so that the correct tab is open when the page loads. 16 if (path && !isNaN(parseInt(path.substring(1)))) { 17 $scope.tab = parseInt(path.substring(1)); 18 } else if (!isNaN(parseInt(hash))) { 19 $scope.tab = parseInt(hash); 20 } 21 22 $scope.$watch("collapsed", function() { 23 localStorage.setItem("collapsed", $scope.collapsed); 24 }); 25 26 $scope.getTab = function() { 27 return $scope.tab; 28 } 29 30 $scope.setTab = function(tabnum) { 31 $scope.tab = tabnum; 32 setTimeout(function() { 33 $location.hash('' + $scope.tab); 34 $scope.$apply(); 35 }, 0) 36 } 37 38 $rootScope.$on("version_updated", function(e, newVersion){ 39 // cheat and copy over the patch info, since it never changes. 40 newVersion.PatchInfo = $scope.version['PatchInfo'] 41 $scope.setVersion(newVersion); 42 }) 43 44 $scope.setVersion = function(version) { 45 $scope.version = version; 46 47 $scope.commit = { 48 message: $scope.version.Version.message, 49 author: $scope.version.Version.author, 50 author_email: $scope.version.Version.author_email, 51 push_time: $scope.version.Version.create_time, 52 gitspec: $scope.version.Version.revision, 53 repo_owner: $scope.version.repo_owner, 54 repo_name: $scope.version.repo_name 55 }; 56 57 $scope.taskStatuses = {}; 58 var taskNames = {}; 59 $scope.taskGrid = {}; 60 61 if (version.PatchInfo) { 62 // setup diff data to use statusFilter 63 for (var i = 0; i < version.PatchInfo.StatusDiffs.length; ++i) { 64 var original = version.PatchInfo.StatusDiffs[i].diff.original; 65 66 // in case the base task has not yet run 67 if (_.size(original) !== 0) { 68 version.PatchInfo.StatusDiffs[i].diff.original = { 69 'task_end_details': original, 70 'status': original.status, 71 }; 72 } 73 74 var patch = version.PatchInfo.StatusDiffs[i].diff.patch; 75 76 // in case the patch task has not yet run 77 if (_.size(patch) !== 0) { 78 version.PatchInfo.StatusDiffs[i].diff.patch = { 79 'task_end_details': patch, 80 'status': patch.status, 81 }; 82 } 83 } 84 } 85 86 for (var i = 0; i < version.Builds.length; ++i) { 87 row = {} 88 $scope.taskStatuses[version.Builds[i].Build._id] = []; 89 for (var j = 0; j < version.Builds[i].Tasks.length; ++j) { 90 row[version.Builds[i].Tasks[j].Task.display_name] = version.Builds[i].Tasks[j].Task; 91 $scope.taskStatuses[version.Builds[i].Build._id].push({ 92 "class": $filter('statusFilter')(version.Builds[i].Tasks[j].Task), 93 "tooltip": version.Builds[i].Tasks[j].Task.display_name + " - " + $filter('statusLabel')(version.Builds[i].Tasks[j].Task), 94 "link": "/task/" + version.Builds[i].Tasks[j].Task.id 95 }); 96 taskNames[version.Builds[i].Tasks[j].Task.display_name] = 1; 97 } 98 $scope.taskGrid[version.Builds[i].Build.display_name] = row; 99 } 100 $scope.taskNames = Object.keys(taskNames).sort() 101 $scope.lastUpdate = $now.now(); 102 103 //calculate makespan and total processing time for the version 104 var nonZeroTimeFilter = function(y){return (+y) != (+new Date(0))}; 105 106 var tasks = _.filter(version.Builds.map(function(x){ return _.pluck(x['Tasks'] || [], "Task") }).reduce(function(x,y){return x.concat(y)}, []), function(task){ 107 return task.status == "success" || task.status == "failed" 108 }); 109 var taskStartTimes = _.filter(_.pluck(tasks, "start_time").map(function(x){return new Date(x)}), nonZeroTimeFilter).sort(dateSorter); 110 var taskEndTimes = _.filter(tasks.map(function(x){ 111 if(x.time_taken == 0 || +new Date(x.start_time) == +new Date(0)){ 112 return new Date(0); 113 }else{ 114 return new Date((+new Date(x.start_time)) + (x.time_taken/nsPerMs)); 115 } 116 }), nonZeroTimeFilter).sort(dateSorter); 117 118 if(taskStartTimes.length == 0 || taskEndTimes.length == 0) { 119 $scope.makeSpanMS = 0; 120 }else { 121 $scope.makeSpanMS = taskEndTimes[taskEndTimes.length-1] - taskStartTimes[0]; 122 } 123 124 $scope.makeSpanMS = taskEndTimes[taskEndTimes.length-1] - taskStartTimes[0]; 125 126 var availableTasks = _.filter(tasks, function(t){ 127 return +new Date(t.start_time) != +new Date(0); 128 }) 129 130 $scope.totalTimeMS = _.reduce(_.pluck(availableTasks, "time_taken"), function(x, y){return x+y}, 0) / nsPerMs; 131 }; 132 133 $scope.getGridLink = function(bv, test) { 134 if (!(bv in $scope.taskGrid)) { 135 return '#'; 136 } 137 var cell = $scope.taskGrid[bv][test] 138 if (!cell) { 139 return '#'; 140 } 141 return '/task/' + cell.id; 142 } 143 144 $scope.getGridClass = function(bv, test) { 145 var returnval = ''; 146 var bvRow = $scope.taskGrid[bv]; 147 if (!bvRow) return 'skipped'; 148 var cell = bvRow[test]; 149 if (!cell) return 'skipped'; 150 if (cell.status == 'started' || cell.status == 'dispatched') { 151 return 'started'; 152 } else if (cell.status == 'undispatched') { 153 return 'undispatched ' + (cell.activated ? 'active' : ' inactive'); 154 } else if (cell.status == 'failed') { 155 if ('task_end_details' in cell) { 156 if ('type' in cell.task_end_details) { 157 if (cell.task_end_details.type == 'system') { 158 return 'system-failed'; 159 } 160 } 161 } 162 return 'failure'; 163 } else if (cell.status == 'success') { 164 return 'success'; 165 } 166 } 167 168 $scope.load = function() { 169 $http.get('/version_json/' + $scope.version.Version.id). 170 success(function(data) { 171 if (data.error) { 172 notificationService.pushNotification(data.error); 173 } else { 174 $scope.setVersion(data); 175 } 176 }). 177 error(function(data) { 178 notificationService.pushNotification("Error occurred - " + data.error); 179 }); 180 }; 181 182 $scope.setVersion($window.version); 183 $scope.plugins = $window.plugins; 184 }); 185 186 187 mciModule.controller('VersionHistoryDrawerCtrl', function($scope, $window, $filter, $timeout, historyDrawerService) { 188 189 // cache the task being displayed on the page 190 $scope.version = $window.version; 191 192 // cache the element for the content of the drawer 193 var drawerContentsEl = $('#drawer-contents'); 194 195 // is the specified revision the one with the current task in it? 196 $scope.isCurrent = function(revision) { 197 return revision.revision === $scope.version.Version.revision; 198 } 199 200 // helper to convert the history fetched from the backend into revisions, 201 // grouped by date, for front-end display 202 function groupHistory(history) { 203 // group the revisions by date, ordered backwards by date 204 var groupedRevisions = []; 205 var datesSeen = {}; // to avoid double-entering dates 206 history.forEach(function(revision) { 207 var date = revision.push_time.substring(0, 10); 208 209 // if we haven't seen the date, add a new entry for it 210 if (!datesSeen[date]) { 211 groupedRevisions.push({ 212 date: date, 213 revisions: [], 214 }); 215 datesSeen[date] = true; 216 } 217 218 // push the revision onto its appropriate date group (always the 219 // last in the list) 220 groupedRevisions[groupedRevisions.length - 1].revisions.push(revision); 221 }); 222 return groupedRevisions; 223 } 224 225 // make a backend call to get the drawer contents 226 function fetchHistory() { 227 historyDrawerService.fetchVersionHistory($scope.version.Version.id, 'surround', 20, { 228 success: function(data) { 229 230 // save the revisions as a list 231 $scope.revisions = data.revisions; 232 233 // group the history by revision, and save it 234 $scope.groupedRevisions = groupHistory(data.revisions); 235 236 // scroll to the relevant element 237 $timeout( 238 function() { 239 var currentRevisionDomEl = $('.drawer-item-highlighted')[0]; 240 if (!currentRevisionDomEl) { 241 return; 242 } 243 var offsetTop = $(currentRevisionDomEl).position().top; 244 var drawerContentsHeight = drawerContentsEl.height(); 245 if (offsetTop >= drawerContentsHeight) { 246 drawerContentsEl.scrollTop(offsetTop); 247 } 248 }, 500) 249 }, 250 error: function(data) { 251 console.log('error fetching history: ' + data); 252 } 253 }); 254 255 } 256 257 // function fired when scrolling up hits the top of the frame, 258 // loads more revisions asynchronously 259 var fetchLaterRevisions = _.debounce( 260 function() { 261 // get the most recent revision in the history 262 var mostRecentRevision = ($scope.revisions && $scope.revisions[0]); 263 264 // no history 265 if (!mostRecentRevision) { 266 return; 267 } 268 269 // get a version id from it 270 var anchorId = mostRecentRevision.version_id; 271 272 historyDrawerService.fetchVersionHistory(anchorId, 'after', 20, { 273 success: function(data) { 274 // no computation necessary 275 if (!data) { 276 return; 277 } 278 279 // place on the beginning of the stored revisions 280 $scope.revisions = data.revisions.concat($scope.revisions); 281 282 // regroup 283 $scope.groupedRevisions = groupHistory($scope.revisions); 284 285 }, 286 error: function(data) { 287 console.log('error fetching later revisions: ' + data); 288 } 289 }) 290 }, 500, true); 291 292 // function fired when scrolling down hits the bottom of the frame, 293 // loads more revisions asynchronously 294 var fetchEarlierRevisions = _.debounce( 295 296 function() { 297 // get the least recent revision in the history 298 var leastRecentRevision = ($scope.revisions && 299 $scope.revisions[$scope.revisions.length - 1]); 300 301 // no history 302 if (!leastRecentRevision) { 303 return; 304 } 305 306 // get a version id from it 307 var anchorId = leastRecentRevision.version_id; 308 309 historyDrawerService.fetchVersionHistory( 310 anchorId, 311 'before', 312 20, { 313 success: function(data) { 314 // no computation necessary 315 if (!data) { 316 return; 317 } 318 319 // place on the end of the stored revisions 320 $scope.revisions = $scope.revisions.concat(data.revisions); 321 322 // regroup 323 $scope.groupedRevisions = groupHistory($scope.revisions); 324 325 }, 326 error: function(data) { 327 console.log('error fetching earlier revisions: ' + data); 328 } 329 } 330 ) 331 332 }, 333 500, 334 true 335 ); 336 337 // make the initial call to fetch the history 338 fetchHistory(); 339 340 /* infinite scroll stuff */ 341 342 // the drawer header element 343 var drawerHeaderEl = $('#drawer-header'); 344 345 // the filled part of the drawer 346 var drawerFilledEl = $('#drawer-filled'); 347 348 // scrolling function to fire if the element is not actually scrollable 349 // (does not overflow its div) 350 var loadMoreSmall = _.debounce( 351 function(e) { 352 var evt = window.event || e; 353 if (evt.wheelDelta) { 354 if (evt.wheelDelta < 0) { 355 fetchEarlierRevisions(); 356 } else { 357 fetchLaterRevisions(); 358 } 359 } 360 361 // firefox: mouse wheel info is in a subobject called detail 362 else if (evt.detail && evt.detail.wheelDelta) { 363 if (evt.detail.wheelDelta < 0) { 364 fetchLaterRevisions(); 365 } else { 366 fetchEarlierRevisions(); 367 } 368 } 369 }, 370 500, 371 true 372 ); 373 374 // activates infinite scrolling if the drawer contents are not large enough 375 // to be normally scrollable 376 var smallScrollFunc = function(e) { 377 if (drawerFilledEl.height() < drawerContentsEl.height()) { 378 loadMoreSmall(e); 379 } 380 } 381 382 drawerContentsEl.on('mousewheel DOMMouseScroll onmousewheel', smallScrollFunc); 383 384 // scrolling function to fire if the element is scrollable (it overflows 385 // its div) 386 var bigScrollFunc = function() { 387 if (drawerContentsEl.scrollTop() === 0) { 388 // we hit the top of the drawer 389 fetchLaterRevisions(); 390 391 } else if (drawerContentsEl.scrollTop() + 10 >= 392 drawerContentsEl[0].scrollHeight - drawerContentsEl.height()) { 393 394 // we hit the bottom of the drawer 395 fetchEarlierRevisions(); 396 397 } 398 } 399 400 // set up infinite scrolling on the drawer element 401 drawerContentsEl.scroll(bigScrollFunc); 402 403 var eopFilter = $filter('endOfPath'); 404 $scope.failuresTooltip = function(failures) { 405 return _.map(failures, function(failure) { 406 return eopFilter(failure); 407 }).join('\n'); 408 } 409 410 });