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

     1  mciModule.controller('TaskTimingController', function($scope, $http, $window, $filter, $locationHash, mciTime, notificationService) {
     2      $scope.currentProject = $window.activeProject;
     3      // sort the task names for the current project
     4      $scope.currentProject.task_names.sort()
     5      $scope.currentProject.build_variants.sort(function(a,b){
     6          return (a.name < b.name) ? -1 : 1
     7      });
     8      $scope.currentBV = "";
     9      $scope.currentTask = "";
    10      $scope.currentHover = -1;
    11  
    12      var allTasksField = "All Tasks";
    13      var time_taken = "tt";
    14      var makespan = "makespan";
    15      var processing_time = "tpt";
    16      var repotracker_requester = "gitter_request";
    17      var patch_requester = "patch_request"
    18      var nsPerMs = 1000000;
    19  
    20      var initialHash = $locationHash.get();
    21      // TODO do we keep this? 
    22      if (initialHash.buildVariant) {
    23          for (var i = 0; i < $scope.currentProject.build_variants.length; ++i) {
    24              if ($scope.currentProject.build_variants[i].name === initialHash.buildVariant) {
    25                  $scope.currentBV = $scope.currentProject.build_variants[i];
    26              }
    27          }
    28      } else {
    29          // check if there are no build variants in the project
    30          if ($scope.currentProject.build_variants != []) {
    31              $scope.currentBV = $scope.currentProject.build_variants[0];
    32          }
    33      }
    34  
    35      // try to get the task from the current build variant, 
    36      // if there isn't one then get the first task 
    37      if(initialHash.taskName){
    38          $scope.currentTask = initialHash.taskName
    39      } else {
    40          if ($scope.currentProject.task_names != []) {
    41              if ($scope.currentBV.task_names != []) {
    42                  $scope.currentTask = $scope.currentBV.task_names[0]; 
    43              } else {
    44                  $scope.currentTask = $scope.currentProject.task_names[0];
    45              }
    46          }
    47      }
    48  
    49  
    50  
    51      $scope.taskData = {};
    52      $scope.locked = false;
    53      $scope.hoverInfo = {hidden: true};
    54      $scope.allTasks = false;
    55  
    56      $scope.requestViewOptions = [
    57          {
    58              name: "Commits",
    59              requester: repotracker_requester
    60          },
    61          {
    62              name: "Patches",
    63              requester : patch_requester
    64          }
    65      ];
    66  
    67      // use the location hash for patches vs commit view
    68      if (initialHash.requester) {
    69        r = initialHash.requester;
    70        if (r == repotracker_requester){
    71          $scope.currentRequest = $scope.requestViewOptions[0];
    72        }
    73        if (r == patch_requester){
    74          $scope.currentRequest = $scope.requestViewOptions[1];
    75        }
    76      }  else {
    77        $scope.currentRequest = $scope.requestViewOptions[0]; 
    78      }
    79  
    80      $scope.setCurrentRequest = function(requestView) {
    81          $scope.currentRequest = requestView;
    82          $scope.load()
    83      }
    84  
    85      // normal task options 
    86      $scope.timeDiffOptions = [
    87          {
    88              name: "Start \u21E2 Finish",
    89              diff: ["finish_time", "start_time"]
    90  
    91          },
    92          {
    93              name: "Scheduled \u21E2 Start",
    94              diff: ["start_time", "scheduled_time"]
    95          },
    96          {
    97              name: "Scheduled \u21E2 Finish",
    98              diff: ["finish_time", "scheduled_time"]
    99          }
   100      ];
   101  
   102      $scope.timeDiff = $scope.timeDiffOptions[0];
   103  
   104      $scope.setTimeDiff = function(diff) {
   105          $scope.timeDiff = diff;
   106          $scope.load();
   107      }
   108  
   109      // options for the all tasks functionality 
   110      $scope.allTasksOptions = [
   111          {
   112              name: "Makespan",
   113              type: makespan,
   114  
   115          },
   116          { name: "Total Processing Time",
   117              type: processing_time,
   118          },
   119      ];
   120      $scope.allTasksView = $scope.allTasksOptions[0];
   121  
   122  
   123      $scope.setAllTasksView = function(view) {
   124          $scope.allTasksView = view;
   125          $scope.load();
   126      }
   127  
   128      $scope.isAllTasks = function(){
   129        return $scope.allTasks || $scope.currentTask == "All Tasks";
   130      }
   131  
   132  
   133      if (initialHash.limit) {
   134        $scope.numTasks = initialHash.limit;
   135      } else {
   136        $scope.numTasks = 50; 
   137      }
   138      $scope.numTasksOptions = [25, 50, 100, 200, 500, 1000, 2000];
   139  
   140  
   141      $scope.setNumTasks = function(num){
   142          $scope.numTasks = num;
   143          $scope.load();
   144      }
   145  
   146      // add an all tasks field
   147      $scope.currentProject.task_names.unshift(allTasksField);
   148  
   149  
   150  
   151      $scope.setBuildVariant = function(bv) {
   152          $scope.currentBV = bv;
   153          $scope.load();
   154  
   155      };
   156  
   157      $scope.setTaskName = function(task) {
   158          if (task == allTasksField){
   159              $scope.allTasks = true;
   160              $scope.currentTask = task;
   161              $scope.load();
   162              return
   163          }
   164          $scope.allTasks = false;
   165          $scope.currentTask = task;
   166          $scope.load();
   167      }
   168  
   169  
   170      // check that task is in list of build variants
   171      $scope.checkTaskForGraph = function (task) {
   172          if (task == allTasksField){
   173              return true;
   174          }
   175          return _.some($scope.currentBV.task_names, function(name){ return name == task});
   176      }
   177  
   178  
   179      $scope.getLink = function() {
   180          if ($scope.isAllTasks()) {
   181              return "/build/" + $scope.hoverInfo.id;
   182          } else {
   183              return "/task/" + $scope.hoverInfo.id;
   184          }
   185      }
   186  
   187      // filter function to remove zero times from a list of times
   188      var nonZeroTimeFilter = function(y){return (+y) != (+new Date(0))}
   189  
   190      var isPatch = function(){
   191          return $scope.currentRequest.requester == patch_requester;
   192      }
   193  
   194      var getHoverInfo = function(i){
   195          var hoverInfo;
   196          if (isPatch()){
   197              hoverInfo =  {
   198                  "revision" : $scope.versions[i].Revision,
   199                  "duration" : formatDuration(yMap($scope.taskData[i])),
   200                  "id" : $scope.taskData[i].id,
   201                  "message" : $scope.versions[i].Description,
   202                  "author": $scope.versions[i].Author,
   203                  "create_time": $scope.versions[i].CreateTime,
   204                  "hidden": false
   205              }
   206          } else {
   207              hoverInfo =  {
   208                  "revision" : $scope.versions[i].revision,
   209                  "duration" : formatDuration(yMap($scope.taskData[i])),
   210                  "id" : $scope.taskData[i].id,
   211                  "message" : $scope.versions[i].message,
   212                  "author": $scope.versions[i].author,
   213                  "create_time": $scope.versions[i].create_time,
   214                  "hidden": false
   215              }
   216          }
   217          if (!$scope.isAllTasks()){
   218            hoverInfo.host = $scope.taskData[i].host
   219            hoverInfo.distro = $scope.taskData[i].distro
   220          }
   221          return hoverInfo
   222      }
   223  
   224      function calculateMakespan(build) {
   225          var tasks = build.tasks;
   226          if (!tasks) {
   227              return 0;
   228          }
   229          // extract the start an end times for the tasks in the build, discarding the zero times
   230          var taskStartTimes = _.filter(_.pluck(tasks, "start_time").map(function(x){return new Date(x)}), nonZeroTimeFilter).sort();
   231          var taskEndTimes = _.filter(tasks.map(function(x){
   232              if(x.time_taken == 0 || +new Date(x.start_time) == +new Date(0)){
   233                  return new Date(0);
   234              }else {
   235                return new Date((+new Date(x.start_time)) + (x.time_taken/nsPerMs));
   236              }
   237            }), nonZeroTimeFilter).sort();
   238  
   239  
   240          if(taskStartTimes.length == 0 || taskEndTimes.length == 0) {
   241            return 0;
   242          } else {
   243            var makeSpan = taskEndTimes[taskEndTimes.length-1] - taskStartTimes[0];
   244            if (makeSpan < 0) {
   245              return 0;
   246            }
   247            return makeSpan;
   248          }
   249  
   250        }
   251  
   252        function calculateTotalProcessingTime (build) {
   253          var tasks = _.filter(build.tasks, function(task){return nonZeroTimeFilter(new Date(task.start_time));})
   254          return mciTime.fromNanoseconds(_.reduce(tasks, function(sum, task){return sum + task.time_taken}, 0));
   255        }
   256  
   257  
   258        var xMap = function(task){
   259          return moment(task.create_time);
   260        }
   261  
   262        var yMap  = function(task){
   263          if ($scope.isAllTasks()){
   264           if ($scope.allTasksView.type == makespan) {
   265            return calculateMakespan(task);
   266          } else {
   267            return calculateTotalProcessingTime(task);
   268          }
   269        }
   270        var a1 = moment(task[$scope.timeDiff.diff[0]]);
   271        var a2 = moment(task[$scope.timeDiff.diff[1]]);
   272        return mciTime.fromMilliseconds(a1.diff(a2));
   273      }
   274  
   275      // isValidDate checks that none of the start, finish or scheduled times are zero
   276      var isValidDate = function(task){
   277          return nonZeroTimeFilter(+new Date(task.start_time)) && nonZeroTimeFilter(+new Date(task.finish_time)) && nonZeroTimeFilter(+new Date(task.scheduled_time)) 
   278      }
   279  
   280  
   281      $scope.load = function(before) {
   282        $scope.hoverInfo.hidden = true;
   283        $scope.locked = false;
   284          // check that task exists in build variant
   285          if (!$scope.checkTaskForGraph($scope.currentTask) && !$scope.isAllTasks()){
   286              return;
   287          }
   288  
   289          var limit = $scope.numTasks;
   290          var query = (!!before ? 'before=' + encodeURIComponent(before) : '');
   291          query += (query.length > 0 && !!limit ? '&' : '');
   292          query += (!!limit ? 'limit=' + encodeURIComponent(limit) : '');
   293          url = '/json/task_timing/' +
   294              encodeURIComponent($scope.currentProject.name) + '/' +
   295              encodeURIComponent($scope.currentBV.name) + '/' +
   296              encodeURIComponent($scope.currentRequest.requester) + '/' +
   297              encodeURIComponent($scope.currentTask) 
   298          $http.get(
   299          url + '?' + query).
   300          success(function(data) {
   301              $scope.taskData = ($scope.isAllTasks()) ? data.builds.reverse() : data.tasks.reverse();
   302              $scope.versions = ($scope.currentRequest.requester == repotracker_requester) ? data.versions.reverse() : data.patches.reverse();
   303              $scope.versions = _.filter($scope.versions, function(v, i){
   304                  return isValidDate($scope.taskData[i]);
   305              })
   306              $scope.taskData = _.filter($scope.taskData, isValidDate)
   307              setTimeout(function(){$scope.drawDetailGraph()},0);
   308          }).
   309          error(function(data) {
   310              notificationService.pushNotification("Error loading data: `" + data.error+"`", 'errorHeader', "error");
   311              $scope.taskData = [];
   312          });
   313  
   314          setTimeout( function(p, bv, t, r, l){
   315              return function(){
   316                  $locationHash.set({ project: p, buildVariant: bv, taskName: t, requester:r, limit: l});
   317              }
   318          }($scope.currentProject.name, $scope.currentBV.name, $scope.currentTask, $scope.currentRequest.requester, $scope.numTasks), 0)
   319      };
   320  
   321      // formatting function for the way the y values should show up. 
   322      // TODO: figure out how to make this look better...
   323      var formatDuration = function(d) {
   324          if (d == 0) {
   325              return "0s"
   326          }
   327          return $filter('stringifyNanoseconds')(d * nsPerMs, true, true)
   328      }
   329  
   330      $scope.drawDetailGraph = function(){
   331          var graphId = "#tt-graph" 
   332          $(graphId).empty();
   333  
   334          // get the width of the column div for the width of the graph
   335          var graph = d3.select(graphId)[0][0];
   336          var colWidth = graph.clientWidth;
   337  
   338          var margin = { top: 20, right: 60, bottom: 100, left: 110 };
   339          var width = colWidth - margin.left - margin.right;
   340          var height = (colWidth/2) - margin.top - margin.bottom;
   341          var svg = d3.select(graphId)
   342          .append("svg")
   343          .attr("width", width + margin.left + margin.right)
   344          .attr("height", height + margin.top + margin.bottom)
   345          .append("g")
   346          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
   347  
   348  
   349          // sort task data by create time
   350          $scope.taskData.sort(function(a,b){
   351              return moment(a.create_time).diff(moment(b.create_time));
   352          })
   353  
   354  
   355  
   356          var maxTime = d3.max($scope.taskData, function(task){return yMap(task);});
   357          var minTime = d3.min($scope.taskData, function(task){ return yMap(task);});
   358  
   359          var yScale = d3.scale.linear()
   360          .domain([minTime, maxTime])
   361          .range([height, 0]);
   362  
   363          var xScale = d3.scale.linear()
   364          .domain([0, $scope.taskData.length - 1 ])
   365          .range([0, width])
   366  
   367          var yAxis = d3.svg.axis()                                       
   368          .scale(yScale)
   369          .orient("left")
   370          .tickFormat(formatDuration)
   371          .ticks(10)
   372          .tickSize(-width)
   373  
   374          var xAxis = d3.svg.axis()
   375          .scale(xScale)
   376          .orient("bottom")
   377          .ticks($scope.taskData.length)
   378          .tickFormat(function(d, i){
   379              // every 5 ticks
   380              if ($scope.numTasks >= 200 && i % 5 != 0) {
   381                  return "";
   382              }
   383              // every 2 ticks
   384              if ($scope.numTasks >= 100 && i % 2 != 0){
   385                  return "";
   386              }
   387              var task = $scope.taskData[d];
   388              if (task) {
   389                  return moment(task.create_time).format("M/D")}
   390                  return ""
   391          })
   392          .tickSize(10)
   393  
   394          // Define the line
   395          var valueline = d3.svg.line()
   396          .x(function(d, i) { return xScale(i);})
   397          .y(function(d) { return yScale(yMap(d)); });
   398  
   399          svg.append("g")
   400          .attr("class", "x axis")
   401          .attr("transform", "translate(0," + height + ")")
   402          .call(xAxis);
   403          svg.append("g")
   404          .attr("class", "y axis")
   405          .call(yAxis);
   406  
   407          // now rotate text on x axis
   408          svg.selectAll(".x text")  // select all the text elements for the xaxis
   409          .attr("transform", function(d) {
   410              return "translate(" + this.getBBox().height*-2 + "," + (this.getBBox().height ) + ")rotate(-45)";
   411          })
   412          .attr("font-size", 10)
   413  
   414  
   415          var scaledX = function(x, i){return xScale(i);}
   416          var scaledY = function(y){return yScale(yMap(y));}
   417  
   418          // Add the valueline path.
   419          svg.append("path")
   420          .attr("class", "line")
   421          .attr("d", valueline($scope.taskData));
   422  
   423          // add the tooltip area to the webpage
   424          var tooltip = d3.select("body").append("div")
   425          .attr("class", "tooltip")
   426          .style("opacity", 0);
   427  
   428          var radius = $scope.numTasks >= 100 ? 1 : 3.5;
   429          // draw dots
   430          svg.selectAll(".task-circle")
   431          .data($scope.taskData)
   432          .enter().append("circle")
   433          .attr("class", "task-circle")
   434          .attr("r", radius)
   435          .attr("cx", scaledX)
   436          .attr("cy", scaledY)
   437          .style("stroke", function(task){
   438              return task.status == 'success' ? 'green' : 'red';
   439          })
   440  
   441          // create a focus circle 
   442          var focus = svg.append("circle")
   443          .attr("r", radius + 1);
   444  
   445  
   446          // create a transparent rectangle for tracking hovering
   447          svg.append("rect")
   448          .attr("class", "overlay")
   449          .attr("y", 0)
   450          .attr("width", width)
   451          .attr("height", height + margin.top)
   452          .on("mouseover", function() {
   453              focus.style("display", null);
   454          })
   455          .on("mouseout", function() {
   456              focus.style("display", "none");
   457          })
   458          // lock the scope if you click in the rectangle
   459          .on("click", function(scope){
   460              return function(){
   461                  scope.locked = !scope.locked
   462                  scope.$digest()
   463              }
   464          }($scope))
   465          .on("mousemove", function(){
   466              if($scope.locked){
   467                  return;
   468              }
   469              var i = Math.round(xScale.invert(d3.mouse(this)[0]));
   470              // if the revision is already there then return
   471              if(i == $scope.currentHover){
   472                  return
   473              }
   474  
   475              // set the revision to be the current hash
   476              $scope.currentHover = i;
   477              $scope.hoverInfo = getHoverInfo(i);
   478  
   479              // set the focus's location to be the location of the closest point
   480              focus.attr("cx", scaledX($scope.taskData[i], i))
   481              .attr("cy", scaledY($scope.taskData[i]));
   482              $scope.$digest();
   483          });          
   484      }
   485  
   486      $scope.load();
   487  });