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  });