github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/service/stats.go (about)

     1  package service
     2  
     3  import (
     4  	"net/http"
     5  	"time"
     6  
     7  	"github.com/evergreen-ci/evergreen"
     8  	"github.com/evergreen-ci/evergreen/model/build"
     9  	"github.com/evergreen-ci/evergreen/model/patch"
    10  	"github.com/evergreen-ci/evergreen/model/task"
    11  	"github.com/evergreen-ci/evergreen/model/user"
    12  	"github.com/evergreen-ci/evergreen/model/version"
    13  	"github.com/gorilla/mux"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // UIBuildVariant contains the name of the build variant and the tasks associated with that build variant.
    18  type UIBuildVariant struct {
    19  	Name      string   `json:"name"`
    20  	TaskNames []string `json:"task_names"`
    21  }
    22  
    23  // UIProject has all the tasks that are in a project and all the BuildVariants.
    24  type UIProject struct {
    25  	Name          string           `json:"name"`
    26  	BuildVariants []UIBuildVariant `json:"build_variants"`
    27  	TaskNames     []string         `json:"task_names"`
    28  }
    29  
    30  //UITask has the fields that are necessary to send over the wire for tasks
    31  type UITask struct {
    32  	Id            string    `json:"id"`
    33  	CreateTime    time.Time `json:"create_time"`
    34  	DispatchTime  time.Time `json:"dispatch_time"`
    35  	PushTime      time.Time `json:"push_time"`
    36  	ScheduledTime time.Time `json:"scheduled_time"`
    37  	StartTime     time.Time `json:"start_time"`
    38  	FinishTime    time.Time `json:"finish_time"`
    39  	Version       string    `json:"version"`
    40  	Status        string    `json:"status"`
    41  	Host          string    `json:"host"`
    42  	Distro        string    `json:"distro"`
    43  }
    44  
    45  //UIBuild has the fields that are necessary to send over the wire for builds
    46  type UIBuild struct {
    47  	Id         string            `json:"id"`
    48  	CreateTime time.Time         `json:"create_time"`
    49  	StartTime  time.Time         `json:"start_time"`
    50  	FinishTime time.Time         `json:"finish_time"`
    51  	Version    string            `json:"version"`
    52  	Status     string            `json:"status"`
    53  	Tasks      []build.TaskCache `json:"tasks"`
    54  	TimeTaken  int64             `json:"time_taken"`
    55  }
    56  
    57  // UIStats is all of the data that the stats page might need.
    58  type UIStats struct {
    59  	Tasks    []*UITask         `json:"tasks"`
    60  	Builds   []*UIBuild        `json:"builds"`
    61  	Versions []version.Version `json:"versions"`
    62  	Patches  []patch.Patch     `json:"patches"`
    63  }
    64  
    65  func (uis *UIServer) taskTimingPage(w http.ResponseWriter, r *http.Request) {
    66  	projCtx := MustHaveProjectContext(r)
    67  
    68  	if projCtx.Project == nil {
    69  		uis.ProjectNotFound(projCtx, w, r)
    70  		return
    71  	}
    72  
    73  	currentProject := UIProject{projCtx.Project.Identifier, []UIBuildVariant{}, []string{}}
    74  
    75  	// populate buildVariants by iterating over the build variants tasks
    76  	for _, bv := range projCtx.Project.BuildVariants {
    77  		newBv := UIBuildVariant{bv.Name, []string{}}
    78  		for _, task := range bv.Tasks {
    79  			newBv.TaskNames = append(newBv.TaskNames, task.Name)
    80  		}
    81  		currentProject.BuildVariants = append(currentProject.BuildVariants, newBv)
    82  	}
    83  	for _, task := range projCtx.Project.Tasks {
    84  		currentProject.TaskNames = append(currentProject.TaskNames, task.Name)
    85  	}
    86  
    87  	data := struct {
    88  		ProjectData projectContext
    89  		User        *user.DBUser
    90  		Project     UIProject
    91  	}{projCtx, GetUser(r), currentProject}
    92  
    93  	uis.WriteHTML(w, http.StatusOK, data, "base", "task_timing.html", "base_angular.html", "menu.html")
    94  }
    95  
    96  // taskTimingJSON sends over the task data for a certain task of a certain build variant
    97  func (uis *UIServer) taskTimingJSON(w http.ResponseWriter, r *http.Request) {
    98  	projCtx := MustHaveProjectContext(r)
    99  	beforeTaskId := r.FormValue("before")
   100  
   101  	limit, err := getIntValue(r, "limit", 50)
   102  	if err != nil {
   103  		uis.LoggedError(w, r, http.StatusBadRequest, err)
   104  		return
   105  	}
   106  
   107  	buildVariant := mux.Vars(r)["build_variant"]
   108  	taskName := mux.Vars(r)["task_name"]
   109  	request := mux.Vars(r)["request"]
   110  
   111  	if projCtx.Project == nil {
   112  		uis.LoggedError(w, r, http.StatusNotFound, errors.New("not found"))
   113  		return
   114  	}
   115  
   116  	bv := projCtx.Project.FindBuildVariant(buildVariant)
   117  	if bv == nil {
   118  		uis.LoggedError(w, r, http.StatusNotFound, errors.Errorf("build variant %v not found", buildVariant))
   119  		return
   120  	}
   121  	var versionIds []string
   122  	data := UIStats{}
   123  
   124  	// if its all tasks find the build
   125  	if taskName == "" || taskName == "All Tasks" {
   126  		// TODO: switch this to be a query on the builds TaskCache
   127  		builds, err := build.Find(build.ByProjectAndVariant(projCtx.Project.Identifier, buildVariant, request).
   128  			WithFields(build.IdKey, build.CreateTimeKey, build.VersionKey,
   129  				build.TimeTakenKey, build.TasksKey, build.FinishTimeKey, build.StartTimeKey, build.StatusKey).
   130  			Sort([]string{"-" + build.RevisionOrderNumberKey}).
   131  			Limit(limit))
   132  		if err != nil {
   133  			uis.LoggedError(w, r, http.StatusBadRequest, err)
   134  			return
   135  		}
   136  		versionIds = make([]string, 0, len(builds))
   137  		uiBuilds := []*UIBuild{}
   138  		// get the versions for every single task that was returned
   139  		for _, build := range builds {
   140  
   141  			// create a UITask
   142  			b := &UIBuild{
   143  				Id:         build.Id,
   144  				CreateTime: build.CreateTime,
   145  				StartTime:  build.StartTime,
   146  				FinishTime: build.FinishTime,
   147  				Version:    build.Version,
   148  				Status:     build.Status,
   149  				TimeTaken:  int64(build.TimeTaken),
   150  				Tasks:      build.Tasks,
   151  			}
   152  			uiBuilds = append(uiBuilds, b)
   153  			versionIds = append(versionIds, b.Version)
   154  		}
   155  
   156  		data.Builds = uiBuilds
   157  
   158  	} else {
   159  		foundTask := false
   160  
   161  		for _, t := range bv.Tasks {
   162  			if t.Name == taskName {
   163  				foundTask = true
   164  				break
   165  			}
   166  		}
   167  
   168  		if !foundTask {
   169  			uis.LoggedError(w, r, http.StatusNotFound, errors.Errorf("no task named '%v'", taskName))
   170  			return
   171  		}
   172  		var tasks []task.Task
   173  
   174  		fields := []string{task.CreateTimeKey, task.DispatchTimeKey, task.PushTimeKey,
   175  			task.ScheduledTimeKey, task.StartTimeKey, task.FinishTimeKey,
   176  			task.VersionKey, task.HostIdKey, task.StatusKey, task.HostIdKey,
   177  			task.DistroIdKey}
   178  
   179  		if beforeTaskId != "" {
   180  			t, err := task.FindOne(task.ById(beforeTaskId))
   181  			if err != nil {
   182  				uis.LoggedError(w, r, http.StatusNotFound, err)
   183  				return
   184  			}
   185  			if t == nil {
   186  				uis.LoggedError(w, r, http.StatusNotFound, errors.Errorf("Task %v not found", beforeTaskId))
   187  				return
   188  			}
   189  
   190  			tasks, err = task.Find(task.ByBeforeRevisionWithStatusesAndRequester(t.RevisionOrderNumber, evergreen.CompletedStatuses,
   191  				buildVariant, taskName, projCtx.Project.Identifier, request).Limit(limit).WithFields(fields...))
   192  			if err != nil {
   193  				uis.LoggedError(w, r, http.StatusNotFound, err)
   194  				return
   195  			}
   196  
   197  		} else {
   198  			tasks, err = task.Find(task.ByStatuses(evergreen.CompletedStatuses,
   199  				buildVariant, taskName, projCtx.Project.Identifier, request).Limit(limit).WithFields(fields...).Sort([]string{"-" + task.RevisionOrderNumberKey}))
   200  			if err != nil {
   201  				uis.LoggedError(w, r, http.StatusNotFound, err)
   202  				return
   203  			}
   204  		}
   205  
   206  		uiTasks := []*UITask{}
   207  		versionIds = make([]string, 0, len(tasks))
   208  		// get the versions for every single task that was returned
   209  		for _, t := range tasks {
   210  			// create a UITask
   211  			uiTask := &UITask{
   212  				Id:            t.Id,
   213  				CreateTime:    t.CreateTime,
   214  				DispatchTime:  t.DispatchTime,
   215  				PushTime:      t.PushTime,
   216  				ScheduledTime: t.ScheduledTime,
   217  				StartTime:     t.StartTime,
   218  				FinishTime:    t.FinishTime,
   219  				Version:       t.Version,
   220  				Status:        t.Status,
   221  				Host:          t.HostId,
   222  				Distro:        t.DistroId,
   223  			}
   224  			uiTasks = append(uiTasks, uiTask)
   225  			versionIds = append(versionIds, t.Version)
   226  		}
   227  		data.Tasks = uiTasks
   228  	}
   229  
   230  	// Populate the versions field if with commits, otherwise patches field
   231  	if request == evergreen.RepotrackerVersionRequester {
   232  		versions, err := version.Find(version.ByIds(versionIds).
   233  			WithFields(version.IdKey, version.CreateTimeKey, version.MessageKey,
   234  				version.AuthorKey, version.RevisionKey, version.RevisionOrderNumberKey).
   235  			Sort([]string{"-" + version.RevisionOrderNumberKey}))
   236  		if err != nil {
   237  			uis.LoggedError(w, r, http.StatusBadRequest, err)
   238  			return
   239  		}
   240  		data.Versions = versions
   241  	} else {
   242  		// patches
   243  		patches, err := patch.Find(patch.ByVersions(versionIds).
   244  			WithFields(patch.IdKey, patch.CreateTimeKey, patch.DescriptionKey, patch.AuthorKey))
   245  		if err != nil {
   246  			uis.LoggedError(w, r, http.StatusBadRequest, err)
   247  			return
   248  		}
   249  		data.Patches = patches
   250  	}
   251  
   252  	uis.WriteJSON(w, http.StatusOK, data)
   253  }