func PopulateUIVersion(version *version.Version) (*uiVersion, error) { buildIds := version.BuildIds dbBuilds, err := build.Find(build.ByIds(buildIds)) if err != nil { return nil, err } buildsMap := make(map[string]build.Build) for _, dbBuild := range dbBuilds { buildsMap[dbBuild.Id] = dbBuild } uiBuilds := make([]uiBuild, len(dbBuilds)) for buildIdx, buildId := range buildIds { build := buildsMap[buildId] buildAsUI := uiBuild{Build: build} //Use the build's task cache, instead of querying for each individual task. uiTasks := make([]uiTask, len(build.Tasks)) for taskIdx, task := range build.Tasks { uiTasks[taskIdx] = uiTask{Task: model.Task{Id: task.Id, Status: task.Status, DisplayName: task.DisplayName}} } uiTasks = sortUiTasks(uiTasks) buildAsUI.Tasks = uiTasks uiBuilds[buildIdx] = buildAsUI } return &uiVersion{Version: (*version), Builds: uiBuilds}, nil }
// TryMarkPatchBuildFinished attempts to mark a patch as finished if all // the builds for the patch are finished as well func TryMarkPatchBuildFinished(b *build.Build, finishTime time.Time) error { v, err := version.FindOne(version.ById(b.Version)) if err != nil { return err } if v == nil { return fmt.Errorf("Cannot find version for build %v with version %v", b.Id, b.Version) } // ensure all builds for this patch are finished as well builds, err := build.Find(build.ByIds(v.BuildIds).WithFields(build.StatusKey)) if err != nil { return err } patchCompleted := true status := evergreen.PatchSucceeded for _, build := range builds { if !build.IsFinished() { patchCompleted = false } if build.Status != evergreen.BuildSucceeded { status = evergreen.PatchFailed } } // nothing to do if the patch isn't completed if !patchCompleted { return nil } return patch.TryMarkFinished(v.Id, finishTime, status) }
// Given a patch version and a list of task names, creates a new task with // the given name for each variant, if applicable. func AddNewTasksForPatch(p *patch.Patch, patchVersion *version.Version, project *Project, taskNames []string) error { // create new tasks for all of the added patch tasks var newTasks []string for _, taskName := range taskNames { if !util.SliceContains(p.Tasks, taskName) { newTasks = append(newTasks, taskName) } } // add tasks to the patch in the db if err := p.AddTasks(taskNames); err != nil { return err } // add new tasks to the build, if they exist if len(newTasks) > 0 { builds, err := build.Find(build.ByIds(patchVersion.BuildIds)) if err != nil { return err } for _, b := range builds { if _, err = AddTasksToBuild(&b, project, patchVersion, newTasks); err != nil { return err } } } return nil }
func (uis *UIServer) versionHistory(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) data, err := getVersionHistory(projCtx.Version.Id, 5) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } user := GetUser(r) versions := make([]*uiVersion, 0, len(data)) for _, version := range data { // Check whether the project associated with the particular version // is accessible to this user. If not, we exclude it from the version // history. This is done to hide the existence of the private project. if projCtx.ProjectRef.Private && user == nil { continue } versionAsUI := uiVersion{ Version: version, RepoOwner: projCtx.ProjectRef.Owner, Repo: projCtx.ProjectRef.Repo, } versions = append(versions, &versionAsUI) dbBuilds, err := build.Find(build.ByIds(version.BuildIds)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) for _, b := range dbBuilds { buildAsUI := uiBuild{Build: b} uiTasks := make([]uiTask, 0, len(b.Tasks)) for _, task := range b.Tasks { uiTasks = append(uiTasks, uiTask{ Task: model.Task{ Id: task.Id, Status: task.Status, Activated: task.Activated, DisplayName: task.DisplayName, }, }) if task.Activated { versionAsUI.ActiveTasks++ } } uiTasks = sortUiTasks(uiTasks) buildAsUI.Tasks = uiTasks uiBuilds = append(uiBuilds, buildAsUI) } versionAsUI.Builds = uiBuilds } uis.WriteJSON(w, http.StatusOK, versions) }
func (uis *UIServer) versionPage(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Project == nil || projCtx.Version == nil { http.Error(w, "not found", http.StatusNotFound) return } // Set the config to blank to avoid writing it to the UI unnecessarily. projCtx.Version.Config = "" versionAsUI := uiVersion{ Version: *projCtx.Version, RepoOwner: projCtx.ProjectRef.Owner, Repo: projCtx.ProjectRef.Repo, } if projCtx.Patch != nil { versionAsUI.PatchInfo = &uiPatch{Patch: *projCtx.Patch} } dbBuilds, err := build.Find(build.ByIds(projCtx.Version.BuildIds)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) for _, build := range dbBuilds { buildAsUI := uiBuild{Build: build} uiTasks := make([]uiTask, 0, len(build.Tasks)) for _, task := range build.Tasks { uiTasks = append(uiTasks, uiTask{Task: model.Task{Id: task.Id, Activated: task.Activated, Status: task.Status, DisplayName: task.DisplayName}}) if task.Activated { versionAsUI.ActiveTasks++ } } uiTasks = sortUiTasks(uiTasks) buildAsUI.Tasks = uiTasks uiBuilds = append(uiBuilds, buildAsUI) } versionAsUI.Builds = uiBuilds pluginContext := projCtx.ToPluginContext(uis.Settings, GetUser(r)) pluginContent := getPluginDataAndHTML(uis, plugin.VersionPage, pluginContext) flashes := PopFlashes(uis.CookieStore, r, w) uis.WriteHTML(w, http.StatusOK, struct { ProjectData projectContext User *user.DBUser Flashes []interface{} Version *uiVersion PluginContent pluginData }{projCtx, GetUser(r), flashes, &versionAsUI, pluginContent}, "base", "version.html", "base_angular.html", "menu.html") }
func getTimelineData(projectName, requester string, versionsToSkip, versionsPerPage int) (*timelineData, error) { data := &timelineData{} // get the total number of versions in the database (used for pagination) totalVersions, err := version.Count(version.ByProjectId(projectName)) if err != nil { return nil, err } data.TotalVersions = totalVersions q := version.ByMostRecentForRequester(projectName, requester).WithoutFields(version.ConfigKey). Skip(versionsToSkip * versionsPerPage).Limit(versionsPerPage) // get the most recent versions, to display in their entirety on the page versionsFromDB, err := version.Find(q) if err != nil { return nil, err } // create the necessary uiVersion struct for each version uiVersions := make([]uiVersion, len(versionsFromDB)) for versionIdx, version := range versionsFromDB { versionAsUI := uiVersion{Version: version} uiVersions[versionIdx] = versionAsUI buildIds := version.BuildIds dbBuilds, err := build.Find(build.ByIds(buildIds)) if err != nil { evergreen.Logger.Errorf(slogger.ERROR, "Ids: %v", buildIds) } buildsMap := make(map[string]build.Build) for _, dbBuild := range dbBuilds { buildsMap[dbBuild.Id] = dbBuild } uiBuilds := make([]uiBuild, len(dbBuilds)) for buildIdx, buildId := range buildIds { build := buildsMap[buildId] buildAsUI := uiBuild{Build: build} uiBuilds[buildIdx] = buildAsUI } versionAsUI.Builds = uiBuilds uiVersions[versionIdx] = versionAsUI } data.Versions = uiVersions return data, nil }
// Given a patch version and set of variant/task pairs, creates any tasks that don't exist yet, // within the set of already existing builds. func AddNewTasksForPatch(p *patch.Patch, patchVersion *version.Version, project *Project, pairs TVPairSet) error { builds, err := build.Find(build.ByIds(patchVersion.BuildIds).WithFields(build.IdKey, build.BuildVariantKey)) if err != nil { return err } for _, b := range builds { // Find the set of task names that already exist for the given build tasksInBuild, err := task.Find(task.ByBuildId(b.Id).WithFields(task.DisplayNameKey)) if err != nil { return err } // build an index to keep track of which tasks already exist existingTasksIndex := map[string]bool{} for _, t := range tasksInBuild { existingTasksIndex[t.DisplayName] = true } // if the patch is activated, treat the build as activated b.Activated = p.Activated // build a list of tasks that haven't been created yet for the given variant, but have // a record in the TVPairSet indicating that it should exist tasksToAdd := []string{} for _, taskname := range pairs.TaskNames(b.BuildVariant) { if _, ok := existingTasksIndex[taskname]; ok { continue } tasksToAdd = append(tasksToAdd, taskname) } if len(tasksToAdd) == 0 { // no tasks to add, so we do nothing. continue } // Add the new set of tasks to the build. if _, err = AddTasksToBuild(&b, project, patchVersion, tasksToAdd); err != nil { return err } } return nil }
func (uis *UIServer) versionPage(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Project == nil || projCtx.Version == nil { http.Error(w, "not found", http.StatusNotFound) return } // Set the config to blank to avoid writing it to the UI unnecessarily. projCtx.Version.Config = "" versionAsUI := uiVersion{ Version: *projCtx.Version, RepoOwner: projCtx.ProjectRef.Owner, Repo: projCtx.ProjectRef.Repo, } dbBuilds, err := build.Find(build.ByIds(projCtx.Version.BuildIds)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var canEditPatch bool currentUser := GetUser(r) if projCtx.Patch != nil { canEditPatch = uis.canEditPatch(currentUser, projCtx.Patch) versionAsUI.PatchInfo = &uiPatch{Patch: *projCtx.Patch} // diff builds for each build in the version baseBuilds, err := build.Find(build.ByRevision(projCtx.Version.Revision)) if err != nil { http.Error(w, fmt.Sprintf("error loading base builds for patch: %v", err), http.StatusInternalServerError) return } baseBuildsByVariant := map[string]*build.Build{} for i := range baseBuilds { baseBuildsByVariant[baseBuilds[i].BuildVariant] = &baseBuilds[i] } // diff all patch builds with their original build diffs := []model.TaskStatusDiff{} for i := range dbBuilds { diff := model.StatusDiffBuilds( baseBuildsByVariant[dbBuilds[i].BuildVariant], &dbBuilds[i], ) if diff.Name != "" { // append the tasks instead of the build for better usability diffs = append(diffs, diff.Tasks...) } } versionAsUI.PatchInfo.StatusDiffs = diffs } uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) for _, build := range dbBuilds { buildAsUI := uiBuild{Build: build} uiTasks := make([]uiTask, 0, len(build.Tasks)) for _, t := range build.Tasks { uiTasks = append(uiTasks, uiTask{ Task: task.Task{ Id: t.Id, Activated: t.Activated, StartTime: t.StartTime, TimeTaken: t.TimeTaken, Status: t.Status, Details: t.StatusDetails, DisplayName: t.DisplayName, }}) if t.Activated { versionAsUI.ActiveTasks++ } } buildAsUI.Tasks = uiTasks uiBuilds = append(uiBuilds, buildAsUI) } versionAsUI.Builds = uiBuilds pluginContext := projCtx.ToPluginContext(uis.Settings, GetUser(r)) pluginContent := getPluginDataAndHTML(uis, plugin.VersionPage, pluginContext) flashes := PopFlashes(uis.CookieStore, r, w) uis.WriteHTML(w, http.StatusOK, struct { ProjectData projectContext User *user.DBUser Flashes []interface{} Version *uiVersion PluginContent pluginData CanEdit bool }{projCtx, currentUser, flashes, &versionAsUI, pluginContent, canEditPatch}, "base", "version.html", "base_angular.html", "menu.html") }
func (uis *UIServer) modifyVersion(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) user := MustHaveUser(r) if projCtx.Project == nil || projCtx.Version == nil { http.Error(w, "not found", http.StatusNotFound) return } jsonMap := struct { Action string `json:"action"` Active bool `json:"active"` Abort bool `json:"abort"` Priority int64 `json:"priority"` TaskIds []string `json:"task_ids"` }{} err := util.ReadJSONInto(r.Body, &jsonMap) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // determine what action needs to be taken switch jsonMap.Action { case "restart": if err = model.RestartVersion(projCtx.Version.Id, jsonMap.TaskIds, jsonMap.Abort, user.Id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } case "set_active": if jsonMap.Abort { if err = model.AbortVersion(projCtx.Version.Id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } if err = model.SetVersionActivation(projCtx.Version.Id, jsonMap.Active, user.Id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } case "set_priority": if jsonMap.Priority > evergreen.MaxTaskPriority { if !uis.isSuperUser(user) { http.Error(w, fmt.Sprintf("Insufficient access to set priority %v, can only set priority less than or equal to %v", jsonMap.Priority, evergreen.MaxTaskPriority), http.StatusBadRequest) return } } if err = model.SetVersionPriority(projCtx.Version.Id, jsonMap.Priority); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } default: uis.WriteJSON(w, http.StatusBadRequest, fmt.Sprintf("Unrecognized action: %v", jsonMap.Action)) return } // After the version has been modified, re-load it from DB and send back the up-to-date view // to the client. projCtx.Version, err = version.FindOne(version.ById(projCtx.Version.Id)) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } versionAsUI := uiVersion{ Version: *projCtx.Version, RepoOwner: projCtx.ProjectRef.Owner, Repo: projCtx.ProjectRef.Repo, } dbBuilds, err := build.Find(build.ByIds(projCtx.Version.BuildIds)) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) for _, build := range dbBuilds { buildAsUI := uiBuild{Build: build} uiTasks := make([]uiTask, 0, len(build.Tasks)) for _, t := range build.Tasks { uiTasks = append(uiTasks, uiTask{ Task: task.Task{Id: t.Id, Activated: t.Activated, StartTime: t.StartTime, TimeTaken: t.TimeTaken, Status: t.Status, Details: t.StatusDetails, DisplayName: t.DisplayName}, }) if t.Activated { versionAsUI.ActiveTasks++ } } buildAsUI.Tasks = uiTasks uiBuilds = append(uiBuilds, buildAsUI) } versionAsUI.Builds = uiBuilds uis.WriteJSON(w, http.StatusOK, versionAsUI) }