github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/public/static/js/task.js (about)

     1  mciModule.controller('TaskHistoryDrawerCtrl', function($scope, $window, $location, $filter, $timeout, historyDrawerService) {
     2  
     3    // cache the task being displayed on the page
     4    $scope.task = $window.task_data;
     5  
     6    // cache the element for the content of the drawer
     7    var drawerContentsEl = $('#drawer-contents');
     8  
     9    // is the specified revision the one with the current task in it?
    10    $scope.isCurrent = function(revision) {
    11      return revision.revision === $scope.task.gitspec;
    12    }
    13  
    14    // helper to convert the history fetched from the backend into revisions,
    15    // grouped by date, for front-end display
    16    function groupHistory(history) {
    17  
    18      // group the revisions by date, ordered backwards by date
    19      var groupedRevisions = [];
    20      var datesSeen = {}; // to avoid double-entering dates
    21      history.forEach(function(revision) {
    22        var date = revision.push_time.substring(0, 10);
    23  
    24        // if we haven't seen the date, add a new entry for it
    25        if (!datesSeen[date]) {
    26          groupedRevisions.push({
    27            date: date,
    28            revisions: [],
    29          });
    30          datesSeen[date] = true;
    31        }
    32  
    33        // push the revision onto its appropriate date group (always the
    34        // last in the list)
    35        groupedRevisions[groupedRevisions.length - 1].revisions.push(revision);
    36      });
    37  
    38      return groupedRevisions;
    39  
    40    }
    41  
    42    // make a backend call to get the drawer contents
    43    function fetchHistory() {
    44      historyDrawerService.fetchTaskHistory($scope.task.id, 'surround', 20, {
    45        success: function(data) {
    46  
    47          // save the revisions as a list
    48          $scope.revisions = data.revisions;
    49  
    50          // group the history by revision, and save it
    51          $scope.groupedRevisions = groupHistory(data.revisions);
    52  
    53          // scroll to the relevant element
    54          $timeout(
    55            function() {
    56              var currentRevisionDomEl = $('.drawer-item-highlighted')[0];
    57              if (!currentRevisionDomEl) {
    58                return;
    59              }
    60              var offsetTop = $(currentRevisionDomEl).position().top;
    61              var drawerContentsHeight = drawerContentsEl.height();
    62              if (offsetTop >= drawerContentsHeight) {
    63                drawerContentsEl.scrollTop(offsetTop);
    64              }
    65            }, 500)
    66        },
    67        error: function(data) {
    68          console.log('error fetching history: ' + data);
    69        }
    70      });
    71  
    72    }
    73  
    74    // function fired when scrolling up hits the top of the frame,
    75    // loads more revisions asynchronously
    76    var fetchLaterRevisions = _.debounce(
    77      function() {
    78        // get the most recent revision in the history
    79        var mostRecentRevision = ($scope.revisions && $scope.revisions[0]);
    80  
    81        // no history
    82        if (!mostRecentRevision) {
    83          return
    84        }
    85  
    86        // get a task id from it
    87        var anchorId = mostRecentRevision.task.id;
    88  
    89        historyDrawerService.fetchTaskHistory(anchorId, 'after', 20, {
    90          success: function(data) {
    91            // no computation necessary
    92            if (!data) {
    93              return
    94            }
    95  
    96            // place on the beginning of the stored revisions
    97            $scope.revisions = data.revisions.concat($scope.revisions);
    98  
    99            // regroup
   100            $scope.groupedRevisions = groupHistory($scope.revisions);
   101  
   102          },
   103          error: function(data) {
   104            console.log('error fetching later revisions: ' + data);
   105          }
   106        })
   107      }, 500, true);
   108  
   109    // function fired when scrolling down hits the bottom of the frame,
   110    // loads more revisions asynchronously
   111    var fetchEarlierRevisions = _.debounce(
   112  
   113      function() {
   114        // get the least recent revision in the history
   115        var leastRecentRevision = ($scope.revisions &&
   116          $scope.revisions[$scope.revisions.length - 1]);
   117  
   118        // no history
   119        if (!leastRecentRevision) {
   120          return
   121        }
   122  
   123        // get a task id from it
   124        var anchorId = leastRecentRevision.task.id;
   125  
   126        historyDrawerService.fetchTaskHistory(
   127          anchorId,
   128          'before',
   129          20, {
   130            success: function(data) {
   131              // no computation necessary
   132              if (!data) {
   133                return
   134              }
   135  
   136              // place on the end of the stored revisions
   137              $scope.revisions = $scope.revisions.concat(data.revisions);
   138  
   139              // regroup
   140              $scope.groupedRevisions = groupHistory($scope.revisions);
   141  
   142            },
   143            error: function(data) {
   144              console.log('error fetching earlier revisions: ' + data);
   145            }
   146          }
   147        )
   148  
   149      },
   150      500,
   151      true
   152    );
   153  
   154    // make the initial call to fetch the history
   155    fetchHistory();
   156  
   157    /* infinite scroll stuff */
   158  
   159    // the drawer header element
   160    var drawerHeaderEl = $('#drawer-header');
   161  
   162    // the filled part of the drawer
   163    var drawerFilledEl = $('#drawer-filled');
   164  
   165    // scrolling function to fire if the element is not actually scrollable
   166    // (does not overflow its div)
   167    var loadMoreSmall = _.debounce(
   168      function(e) {
   169        var evt = window.event || e;
   170        if (evt.wheelDelta) {
   171          if (evt.wheelDelta < 0) {
   172            fetchEarlierRevisions();
   173          } else {
   174            fetchLaterRevisions();
   175          }
   176        }
   177  
   178        // firefox
   179        else if (evt.detail && evt.detail.wheelDelta) {
   180          if (evt.detail.wheelDelta < 0) {
   181            fetchLaterRevisions();
   182          } else {
   183            fetchEarlierRevisions();
   184          }
   185        }
   186      },
   187      500,
   188      true
   189    );
   190  
   191    // activates infinite scrolling if the drawer contents are not large enough
   192    // to be normally scrollable
   193    var smallScrollFunc = function(e) {
   194      if (drawerFilledEl.height() < drawerContentsEl.height()) {
   195        loadMoreSmall(e);
   196      }
   197    }
   198  
   199    drawerContentsEl.on('mousewheel DOMMouseScroll onmousewheel', smallScrollFunc);
   200  
   201    // scrolling function to fire if the element is scrollable (it overflows
   202    // its div)
   203    var bigScrollFunc = function() {
   204      if (drawerContentsEl.scrollTop() === 0) {
   205        // we hit the top of the drawer
   206        fetchLaterRevisions();
   207  
   208      } else if (drawerContentsEl.scrollTop() + 10 >=
   209        drawerContentsEl[0].scrollHeight - drawerContentsEl.height()) {
   210  
   211        // we hit the bottom of the drawer
   212        fetchEarlierRevisions();
   213  
   214      }
   215    }
   216  
   217    // set up infinite scrolling on the drawer element
   218    drawerContentsEl.scroll(bigScrollFunc);
   219  
   220    var eopFilter = $filter('endOfPath');
   221    $scope.failuresTooltip = function(failures) {
   222      return _.map(failures, function(failure) {
   223        return eopFilter(failure);
   224      }).join('\n');
   225    }
   226  
   227  });
   228  
   229  mciModule.controller('TaskCtrl', function($scope, $rootScope, $now, $timeout, $interval, md5, $filter, $window, $http, $locationHash) {
   230    $scope.userTz = $window.userTz;
   231    $scope.haveUser = $window.have_user;
   232    $scope.taskHost = $window.taskHost;
   233  
   234    // Returns true if 'testResult' represents a test failure, and returns false otherwise.
   235    $scope.hasTestFailureStatus = function hasTestFailureStatus(testResult) {
   236      var failureStatuses = ['fail', 'silentfail'];
   237      return failureStatuses.indexOf(testResult.status) >= 0;
   238    };
   239  
   240    $scope.getURL = function(testResult, isRaw) {
   241      var url = (isRaw) ? testResult.url_raw : testResult.url;
   242  
   243      if (url != '') {
   244        return url;
   245      }
   246  
   247      var logid = testResult.log_id;
   248      var linenum = testResult.line_num || 0;
   249  
   250      url = '/test_log/' + logid + '#L' + linenum;
   251      if (isRaw) {
   252        url = '/test_log/' + logid + '?raw=1';
   253      }
   254  
   255      return url;
   256    };
   257  
   258    $scope.hideURL = function(testResult, isRaw) {
   259      var url = isRaw ? testResult.url_raw : testResult.url;
   260      return !((url != '') || (testResult.log_id));
   261    };
   262  
   263    $scope.hasBothURL = function(testResult) {
   264      return !($scope.hideURL(testResult) || $scope.hideURL(testResult,'raw'))
   265    };
   266  
   267    var hash = $locationHash.get();
   268    $scope.hash = hash;
   269  
   270    $scope.getSpawnLink = function(){
   271      if(!$scope.haveUser) { // user is not logged in, so we won't provide a link.
   272        return ""
   273      }
   274      if(!$scope.taskHost || $scope.taskHost.distro.provider == "static" || !$scope.taskHost.distro.spawn_allowed){
   275        return ""
   276      }
   277      return "/spawn?distro_id=" + $scope.taskHost.distro._id + "&task_id=" + $scope.task.id
   278    }
   279  
   280    $scope.setSortBy = function(order) {
   281      $scope.sortBy = order;
   282      hash.sort = order.name;
   283      $locationHash.set(hash);
   284    };
   285  
   286    $scope.linkToTest = function(testName) {
   287      if (hash.test === testName) {
   288        delete hash.test;
   289        $locationHash.set(hash);
   290      } else {
   291        hash.test = testName;
   292        $locationHash.set(hash);
   293      }
   294    };
   295  
   296  
   297    $scope.setTask = function(task) {
   298      $scope.task = task;
   299      $scope.md5 = md5;
   300      $scope.maxTests = 1;
   301  
   302      /**
   303       * Defines the sort order for a test's status.
   304       */
   305      function ordinalForTestStatus(testResult) {
   306        var orderedTestStatuses = ['fail', 'silentfail', 'pass', 'skip'];
   307        return orderedTestStatuses.indexOf(testResult.status);
   308      }
   309  
   310      $scope.sortOrders = [{
   311        name: 'Status',
   312        by: [ordinalForTestStatus, 'display_name'],
   313        reverse: false
   314      }, {
   315        name: 'Name',
   316        by: ['display_name'],
   317        reverse: false
   318      }, {
   319        name: 'Time Taken',
   320        by: ['time_taken', 'display_name'],
   321        reverse: true
   322      }, {
   323        name: 'Sequence',
   324        by: [''],
   325        reverse: true
   326      }];
   327  
   328      var totalTestTime = 0;
   329      (task.test_results || []).forEach(function(testResult) {
   330        testResult.time_taken = testResult.end - testResult.start;
   331        totalTestTime += testResult.time_taken;
   332        testResult.display_name = $filter('endOfPath')(testResult.test_file);
   333      });
   334      $scope.totalTestTimeNano = totalTestTime * 1000 * 1000 * 1000;
   335  
   336      if (hash.sort) {
   337        var index = _.indexOf(_.pluck($scope.sortOrders, 'name'), hash.sort);
   338        if (index != -1) {
   339          $scope.sortBy = $scope.sortOrders[index];
   340        }
   341      }
   342  
   343      if (task.execution > 0 || task.archived) {
   344        $scope.otherExecutions = _.range(task.total_executions + 1)
   345      }
   346  
   347      $scope.sortBy = $scope.sortOrders[0];
   348  
   349      $scope.isMet = function(dependency) {
   350        // check if a dependency is met, unmet, or in progress
   351        if (dependency.task_waiting == "blocked") {
   352          return "unmet";
   353        }
   354        if (dependency.status != "failed" && dependency.status != "success") {
   355          // if we didn't succeed or fail, don't report anything
   356          return "";
   357        }
   358        if (dependency.status == dependency.required || dependency.required == "*") {
   359          return "met";
   360        }
   361        return "unmet";
   362      };
   363  
   364      $scope.timeTaken = $scope.task.time_taken
   365  
   366      if ($scope.task.patch_info) {
   367        $scope.baseTimeTaken = $scope.task.patch_info.base_time_taken;
   368      }
   369  
   370      if ($scope.task.status != 'failed' && $scope.task.status != 'success') {
   371        updateFunc = function() {
   372          $scope.task.current_time += 1000000000; // 1 second
   373          $scope.timeTaken = $scope.task.current_time - $scope.task.start_time;
   374          $scope.timeToCompletion = $scope.task.expected_duration - ($scope.task.current_time - $scope.task.start_time);
   375          if ($scope.timeToCompletion < 0) {
   376            $scope.timeToCompletion = 'unknown';
   377          }
   378          if ($scope.task.status === 'undispatched'){
   379            $scope.timeToCompletion = $scope.task.expected_duration;
   380          }
   381        }
   382        updateFunc();
   383        var updateTimers = $interval(updateFunc, 1000);
   384      }
   385    };
   386  
   387   $rootScope.$on("task_updated", function(e, newTask){
   388      newTask.version_id = $scope.task.version_id;
   389      newTask.message = $scope.task.message;
   390      newTask.author = $scope.task.author;
   391      newTask.author_email = $scope.task.author_email;
   392      newTask.min_queue_pos = $scope.task.min_queue_pos;
   393      newTask.patch_info = $scope.task.patch_info;
   394      newTask.build_variant_display = $scope.task.build_variant_display;
   395      newTask.depends_on = $scope.task.depends_on;
   396      $scope.setTask(newTask);
   397   })
   398  
   399    $scope.setTask($window.task_data);
   400    $scope.plugins = $window.plugins
   401  
   402    $scope.maxTestTime = 1;
   403  
   404    $scope.lastUpdate = $now.now();
   405  
   406    $scope.githubLink = function() {
   407      if (!$scope.task) {
   408        return '#';
   409      }
   410      var projectComponents = $scope.task.project.split('-');
   411      if (projectComponents[projectComponents.length - 1] === 'master') {
   412        projectComponents = projectComponents.slice(0, projectComponents.length - 1);
   413      }
   414      return '//github.com/' + projectComponents.join('/') + '/commit/' + $scope.task.gitspec;
   415    };
   416  
   417    // Returns URL to task history page with a filter on the particular test
   418    // and test status enabled.
   419    $scope.getTestHistoryUrl = function(project, task, test) {
   420      var url = '/task_history/' +
   421        encodeURIComponent(project) + '/' +
   422        encodeURIComponent(task.display_name) + '?revision=' +
   423        encodeURIComponent(task.gitspec);
   424      if (test) {
   425        url += '#' + encodeURIComponent(test.display_name) + '=' +
   426          encodeURIComponent(test.status);
   427      }
   428      return url
   429    };
   430  
   431  });
   432  
   433  mciModule.directive('testsResultsBar', function($filter) {
   434    return {
   435      scope: true,
   436      link: function(scope, element, attrs) {
   437        scope.$watch(attrs.testsResultsBar, function(testResults) {
   438          if (testResults) {
   439            var numSuccess = 0;
   440            var numFailed = 0;
   441            var successTimeTaken = 0;
   442            var failureTimeTaken = 0;
   443            _.each(testResults, function(result) {
   444              switch (result.status) {
   445                case 'pass':
   446                  numSuccess++;
   447                  successTimeTaken += (result.end - result.start);
   448                  break;
   449                case 'fail':
   450                case 'silentfail':
   451                  numFailed++;
   452                  failureTimeTaken += (result.end - result.start);
   453                  break;
   454              }
   455            });
   456  
   457            successTimeTaken = $filter('nanoToSeconds')(successTimeTaken)
   458            failureTimeTaken = $filter('nanoToSeconds')(failureTimeTaken)
   459            var successTitle = numSuccess + ' test' + (numSuccess == 1 ? '' : 's') + ' succeeded in ' + $filter('stringifyNanoseconds')(successTimeTaken);
   460            var failedTitle = numFailed + ' test' + (numFailed == 1 ? '' : 's') + ' failed in ' + $filter('stringifyNanoseconds')(failureTimeTaken);
   461            element.html('<div class="progress-bar progress-bar-success" role="progressbar" style="width: ' + (numSuccess / testResults.length * 100) + '%" data-animation="" data-toggle="tooltip" title="' + successTitle + '"></div>' +
   462              '<div class="progress-bar progress-bar-danger" role="progressbar" style="width: ' + (numFailed / testResults.length * 100) + '%"  data-animation="" data-toggle="tooltip" title="' + failedTitle + '"></div>')
   463  
   464            $(element.children('*[data-toggle="tooltip"]')).each(function(i, el) {
   465              $(el).tooltip();
   466            });
   467  
   468            scope.barWidth = testResults.length / scope.maxTests * 90;
   469          }
   470        });
   471      }
   472    }
   473  });
   474  
   475  mciModule.directive('testResults', function() {
   476    return {
   477      scope: true,
   478      link: function(scope, element, attrs) {
   479        scope.maxTestTime = 1;
   480        scope.$watch(attrs.testResults, function(testResults) {
   481          if (testResults) {
   482            for (var i = 0; i < testResults.length; i++) {
   483              var timeTaken = testResults[i].end - testResults[i].start;
   484              if (scope.maxTestTime < timeTaken) {
   485                scope.maxTestTime = timeTaken;
   486              }
   487            }
   488          }
   489        });
   490      }
   491    }
   492  });
   493  
   494  mciModule.directive('testResultBar', function($filter) {
   495    return {
   496      scope: true,
   497      link: function(scope, element, attrs) {
   498        scope.$watch(attrs.testResultBar, function(testResult) {
   499          var timeTaken = testResult.end - testResult.start;
   500          scope.timeTaken = timeTaken;
   501  
   502          switch (testResult.status) {
   503            case 'pass':
   504              scope.progressBarClass = 'progress-bar-success';
   505              break;
   506            case 'fail':
   507              scope.progressBarClass = 'progress-bar-danger';
   508              break;
   509            case 'silentfail':
   510              scope.progressBarClass = 'progress-bar-silently-failed';
   511              break;
   512            default:
   513              scope.progressBarClass = 'progress-bar-default';
   514          }
   515  
   516          var timeInNano = scope.timeTaken * 1000 * 1000 * 1000;
   517          $(element).tooltip({
   518            title: $filter('stringifyNanoseconds')(timeInNano),
   519            animation: false,
   520          });
   521        });
   522  
   523        scope.$watch('maxTestTime', function(maxTestTime) {
   524          scope.barWidth = scope.timeTaken / maxTestTime * 90;
   525          if (scope.barWidth < 5) {
   526            scope.barWidth = 5;
   527          }
   528        });
   529      }
   530    }
   531  });
   532  
   533  mciModule.controller('TaskLogCtrl', ['$scope', '$timeout', '$http', '$location', '$window', '$filter', 'notificationService', function($scope, $timeout, $http, $location, $window, $filter, notifier) {
   534    $scope.logs = 'Loading...';
   535    $scope.task = {};
   536    $scope.eventLogs = 'EV';
   537    $scope.systemLogs = 'S';
   538    $scope.agentLogs = 'E';
   539    $scope.taskLogs = 'T';
   540    $scope.allLogs = 'ALL';
   541    $scope.userTz = $window.userTz;
   542  
   543    var logSpec = $location.path().split('/');
   544    $scope.currentLogs = logSpec[2] || $scope.taskLogs;
   545  
   546    $scope.$watch('currentLogs', function() {
   547      $scope.getLogs();
   548    });
   549  
   550    $scope.setCurrentLogs = function(currentLogs) {
   551      $scope.logs = 'Loading...';
   552      $scope.currentLogs = currentLogs;
   553      $location.path('log/' + currentLogs);
   554    };
   555  
   556    $scope.formatTimestamp = function(logEntry, minVersion) {
   557      if (!logEntry.version || logEntry.version < minVersion) {
   558        return '';
   559      }
   560  
   561      var converter = $filter('convertDateToUserTimezone');
   562      var format = 'YYYY/MM/DD HH:mm:ss.SSS';
   563      var timestamp = converter(logEntry.timestamp, $scope.userTz, format);
   564      return '[' + timestamp + '] '
   565    }
   566  
   567    $scope.getLogs = function() {
   568      $http.get('/json/task_log/' + $scope.taskId + '/' + $scope.task.execution + '?type=' + $scope.currentLogs).
   569      success(function(data, status) {
   570        if ($scope.currentLogs == $scope.eventLogs) {
   571          $scope.eventLogData = data.reverse()
   572        } else {
   573          if (data && data.LogMessages) {
   574            //read the log messages out, and reverse their order (since they are returned backwards)
   575            $scope.logs = _.map(data.LogMessages, function(entry) {
   576              var msg = entry.m.replace(/&#34;/g, '"');
   577              var date = new Date(entry.ts);
   578              return {
   579                message: msg,
   580                severity: entry.s,
   581                timestamp: date,
   582                version: entry.v
   583              };
   584            });
   585          } else {
   586            $scope.logs = [];
   587          }
   588        }
   589      }).
   590      error(function(jqXHR, status, errorThrown) {
   591        	notifier.pushNotification('Error retrieving logs: ' + jqXHR, 'errorHeader');
   592      });
   593  
   594      // If we already have an outstanding timeout, cancel it
   595      if ($scope.getLogsTimeout) {
   596        $timeout.cancel($scope.getLogsTimeout);
   597      }
   598  
   599      $scope.getLogsTimeout = $timeout(function() {
   600        $scope.getLogs();
   601      }, 5000);
   602    };
   603  
   604    $scope.getRawLogLink = function(isRaw) {
   605      if ($scope.currentLogs === $scope.eventLogs) {
   606        return '/event_log/task/' + $scope.taskId;
   607      } else {
   608        var raw = isRaw ? '&text=true' : '';
   609        return '/task_log_raw/' + $scope.taskId + '/' + $scope.task.execution + '?type=' + $scope.currentLogs + raw;
   610      }
   611    };
   612  
   613    $scope.setTask = function(task) {
   614      $scope.task = task;
   615      $scope.taskId = task.id;
   616    };
   617  
   618    $scope.setTask($window.task_data);
   619  
   620  }]);