// getBuildVariantHistory returns a slice of builds that surround a given build. // As many as 'before' builds (less recent builds) plus as many as 'after' builds // (more recent builds) are returned. func getBuildVariantHistory(buildId string, before int, after int) ([]build.Build, error) { b, err := build.FindOne(build.ById(buildId)) if err != nil { return nil, err } if b == nil { return nil, fmt.Errorf("no build with id %v", buildId) } lessRecentBuilds, err := build.Find( build.ByBeforeRevision(b.Project, b.BuildVariant, b.RevisionOrderNumber). WithFields(build.IdKey, build.TasksKey, build.StatusKey, build.VersionKey, build.ActivatedKey). Limit(before)) if err != nil { return nil, err } moreRecentBuilds, err := build.Find( build.ByAfterRevision(b.Project, b.BuildVariant, b.RevisionOrderNumber). WithFields(build.IdKey, build.TasksKey, build.StatusKey, build.VersionKey, build.ActivatedKey). Limit(after)) if err != nil { return nil, err } builds := make([]build.Build, 0, len(lessRecentBuilds)+len(moreRecentBuilds)) for i := len(moreRecentBuilds); i > 0; i-- { builds = append(builds, moreRecentBuilds[i-1]) } builds = append(builds, lessRecentBuilds...) return builds, nil }
func TestFinalizePatch(t *testing.T) { testutil.ConfigureIntegrationTest(t, patchTestConfig, "TestFinalizePatch") Convey("With FinalizePatch on a project and commit event generated from GetPatchedProject path", t, func() { configPatch := resetPatchSetup(t, configFilePath) Convey("a patched config should drive version creation", func() { project, err := GetPatchedProject(configPatch, patchTestConfig) So(err, ShouldBeNil) yamlBytes, err := yaml.Marshal(project) So(err, ShouldBeNil) configPatch.PatchedConfig = string(yamlBytes) version, err := model.FinalizePatch(configPatch, patchTestConfig) So(err, ShouldBeNil) So(version, ShouldNotBeNil) // ensure the relevant builds/tasks were created builds, err := build.Find(build.All) So(err, ShouldBeNil) So(len(builds), ShouldEqual, 1) So(len(builds[0].Tasks), ShouldEqual, 2) tasks, err := task.Find(task.All) So(err, ShouldBeNil) So(len(tasks), ShouldEqual, 2) }) Convey("a patch that does not include the remote config should not "+ "drive version creation", func() { patchedConfigFile := "fakeInPatchSoNotPatched" configPatch := resetPatchSetup(t, patchedConfigFile) project, err := GetPatchedProject(configPatch, patchTestConfig) So(err, ShouldBeNil) yamlBytes, err := yaml.Marshal(project) So(err, ShouldBeNil) configPatch.PatchedConfig = string(yamlBytes) version, err := model.FinalizePatch(configPatch, patchTestConfig) So(err, ShouldBeNil) So(version, ShouldNotBeNil) So(err, ShouldBeNil) So(version, ShouldNotBeNil) // ensure the relevant builds/tasks were created builds, err := build.Find(build.All) So(err, ShouldBeNil) So(len(builds), ShouldEqual, 1) So(len(builds[0].Tasks), ShouldEqual, 1) tasks, err := task.Find(task.All) So(err, ShouldBeNil) So(len(tasks), ShouldEqual, 1) }) Reset(func() { db.Clear(distro.Collection) }) }) }
// 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) }
// MarkVersionCompleted updates the status of a completed version to reflect its correct state by // checking the status of its individual builds. func MarkVersionCompleted(versionId string, finishTime time.Time) error { status := evergreen.VersionSucceeded // Find the statuses for all builds in the version so we can figure out the version's status builds, err := build.Find( build.ByVersion(versionId).WithFields(build.StatusKey), ) if err != nil { return err } for _, b := range builds { if !b.IsFinished() { return nil } if b.Status != evergreen.BuildSucceeded { status = evergreen.VersionFailed } } return version.UpdateOne( bson.M{version.IdKey: versionId}, bson.M{"$set": bson.M{ version.FinishTimeKey: finishTime, version.StatusKey: status, }}, ) }
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 }
// 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 }
// Returns a JSON response with the status of the specified version // grouped on the build variants. The keys of the object are the build // variant name, with each key in the nested object representing a // particular task. func (restapi restAPI) getVersionStatusByBuild(versionId string, w http.ResponseWriter, r *http.Request) { // Get all of the builds corresponding to this version builds, err := build.Find( build.ByVersion(versionId).WithFields(build.BuildVariantKey, build.TasksKey), ) if err != nil { msg := fmt.Sprintf("Error finding status for version '%v'", versionId) evergreen.Logger.Logf(slogger.ERROR, "%v: %v", msg, err) restapi.WriteJSON(w, http.StatusInternalServerError, responseError{Message: msg}) return } result := versionStatusByBuildContent{ Id: versionId, Builds: make(map[string]versionStatusByBuild, len(builds)), } for _, build := range builds { statuses := make(versionStatusByBuild, len(build.Tasks)) for _, task := range build.Tasks { status := versionStatus{ Id: task.Id, Status: task.Status, TimeTaken: task.TimeTaken, } statuses[task.DisplayName] = status } result.Builds[build.BuildVariant] = statuses } restapi.WriteJSON(w, http.StatusOK, result) return }
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") }
// This is used to pull recently finished builds func getRecentlyFinishedBuilds(notificationKey *NotificationKey) (builds []build.Build, err error) { if cachedProjectRecords[notificationKey.String()] == nil { builds, err = build.Find(build.ByFinishedAfter(lastProjectNotificationTime[notificationKey.Project], notificationKey.Project, notificationKey.NotificationRequester)) if err != nil { return nil, err } cachedProjectRecords[notificationKey.String()] = builds return builds, err } return cachedProjectRecords[notificationKey.String()].([]build.Build), nil }
// Given a patch version and a list of variant/task pairs, creates the set of new builds that // do not exist yet out of the set of pairs. No tasks are added for builds which already exist // (see AddNewTasksForPatch). func AddNewBuildsForPatch(p *patch.Patch, patchVersion *version.Version, project *Project, pairs TVPairSet) error { tt := NewPatchTaskIdTable(project, patchVersion, pairs) newBuildIds := make([]string, 0) newBuildStatuses := make([]version.BuildStatus, 0) existingBuilds, err := build.Find(build.ByVersion(patchVersion.Id).WithFields(build.BuildVariantKey, build.IdKey)) if err != nil { return err } variantsProcessed := map[string]bool{} for _, b := range existingBuilds { variantsProcessed[b.BuildVariant] = true } for _, pair := range pairs { if _, ok := variantsProcessed[pair.Variant]; ok { // skip variant that was already processed continue } variantsProcessed[pair.Variant] = true // Extract the unique set of task names for the variant we're about to create taskNames := pairs.TaskNames(pair.Variant) if len(taskNames) == 0 { continue } buildId, err := CreateBuildFromVersion(project, patchVersion, tt, pair.Variant, p.Activated, taskNames) evergreen.Logger.Logf(slogger.INFO, "Creating build for version %v, buildVariant %v, activated = %v", patchVersion.Id, pair.Variant, p.Activated) if err != nil { return err } newBuildIds = append(newBuildIds, buildId) newBuildStatuses = append(newBuildStatuses, version.BuildStatus{ BuildVariant: pair.Variant, BuildId: buildId, Activated: p.Activated, }, ) } return version.UpdateOne( bson.M{version.IdKey: patchVersion.Id}, bson.M{ "$push": bson.M{ version.BuildIdsKey: bson.M{"$each": newBuildIds}, version.BuildVariantsKey: bson.M{"$each": newBuildStatuses}, }, }, ) return nil }
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 }
// getMakespanRatios returns a list of MakespanRatio structs that contain // the actual and predicted makespans for a certain number of recent builds. func getMakespanRatios(numberBuilds int) ([]restMakespanStats, error) { builds, err := build.Find(build.ByRecentlyFinished(numberBuilds)) if err != nil { return nil, err } makespanRatios := []restMakespanStats{} for _, b := range builds { makespanRatios = append(makespanRatios, restMakespanStats{ BuildId: b.Id, PredictedMakespan: int(b.PredictedMakespan), ActualMakespan: int(b.ActualMakespan), }) } return makespanRatios, nil }
// SetVersionActivation updates the "active" state of all builds and tasks associated with a // version to the given setting. It also updates the task cache for all builds affected. func SetVersionActivation(versionId string, active bool, caller string) error { builds, err := build.Find( build.ByVersion(versionId).WithFields(build.IdKey), ) if err != nil { return err } for _, b := range builds { err = SetBuildActivation(b.Id, active, caller) if err != nil { return err } } return nil }
// Helper function to fetch a group of versions and their associated builds. // Returns the versions themselves, as well as a map of version id -> the // builds that are a part of the version (unsorted). func fetchVersionsAndAssociatedBuilds(project *model.Project, skip int, numVersions int) ([]version.Version, map[string][]build.Build, error) { // fetch the versions from the db versionsFromDB, err := version.Find(version.ByProjectId(project.Identifier). WithFields( version.RevisionKey, version.ErrorsKey, version.WarningsKey, version.IgnoredKey, version.MessageKey, version.AuthorKey, version.RevisionOrderNumberKey, version.CreateTimeKey, ).Sort([]string{"-" + version.RevisionOrderNumberKey}).Skip(skip).Limit(numVersions)) if err != nil { return nil, nil, fmt.Errorf("error fetching versions from database: %v", err) } // create a slice of the version ids (used to fetch the builds) versionIds := make([]string, 0, len(versionsFromDB)) for _, v := range versionsFromDB { versionIds = append(versionIds, v.Id) } // fetch all of the builds (with only relevant fields) buildsFromDb, err := build.Find( build.ByVersions(versionIds). WithFields(build.BuildVariantKey, build.TasksKey, build.VersionKey)) if err != nil { return nil, nil, fmt.Errorf("error fetching builds from database: %v", err) } // group the builds by version buildsByVersion := map[string][]build.Build{} for _, build := range buildsFromDb { buildsByVersion[build.Version] = append(buildsByVersion[build.Version], build) } return versionsFromDB, buildsByVersion, 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) }
func TestFinalize(t *testing.T) { testutil.ConfigureIntegrationTest(t, patchTestConfig, "TestFinalize") Convey("With calling ValidateAndFinalize with a config and remote configuration "+ "path", t, func() { testutil.HandleTestingErr(db.ClearCollections( model.ProjectRefCollection, patch.Collection, version.Collection, build.Collection, model.TasksCollection), t, "Error clearing test collection") Convey("a patched config should drive version creation", func() { configFilePath := "testing/mci.yml" // insert distros to be used distros := []distro.Distro{ distro.Distro{Id: "d1"}, distro.Distro{Id: "d2"}, } for _, d := range distros { So(d.Insert(), ShouldBeNil) } projectRef := &model.ProjectRef{ Identifier: patchedProject, RemotePath: configFilePath, Owner: patchOwner, Repo: patchRepo, Branch: patchBranch, } err := projectRef.Insert() testutil.HandleTestingErr(err, t, "Couldn't insert test project ref: "+ "%v", err) fileBytes, err := ioutil.ReadFile(patchFile) So(err, ShouldBeNil) // this patch adds a new task to the existing build configPatch := &patch.Patch{ Id: "52549c143122", Project: patchedProject, BuildVariants: []string{"all"}, Githash: patchedRevision, Patches: []patch.ModulePatch{ patch.ModulePatch{ Githash: "revision", PatchSet: patch.PatchSet{ Patch: fmt.Sprintf(string(fileBytes), configFilePath, configFilePath, configFilePath, configFilePath), Summary: []thirdparty.Summary{ thirdparty.Summary{ Name: configFilePath, Additions: 4, Deletions: 80, }, thirdparty.Summary{ Name: "random.txt", Additions: 6, Deletions: 0, }, }, }, }, }, } err = configPatch.Insert() testutil.HandleTestingErr(err, t, "Couldn't insert test patch: %v", err) version, err := ValidateAndFinalize(configPatch, patchTestConfig) So(err, ShouldBeNil) So(version, ShouldNotBeNil) // ensure the relevant builds/tasks were created builds, err := build.Find(build.All) So(err, ShouldBeNil) So(len(builds), ShouldEqual, 1) So(len(builds[0].Tasks), ShouldEqual, 2) tasks, err := model.FindAllTasks(bson.M{}, db.NoProjection, db.NoSort, db.NoSkip, db.NoLimit, ) So(err, ShouldBeNil) So(len(tasks), ShouldEqual, 2) }) Convey("a patch that does not include the remote config should not "+ "drive version creation", func() { configFilePath := "testing/mci.yml" projectRef := &model.ProjectRef{ Identifier: patchedProject, RemotePath: configFilePath, Owner: patchOwner, Repo: patchRepo, Branch: patchBranch, } err := projectRef.Insert() testutil.HandleTestingErr(err, t, "Couldn't insert test project ref: "+ "%v", err) patchedConfigFile := "fakeInPatchSoNotPatched" fileBytes, err := ioutil.ReadFile(patchFile) So(err, ShouldBeNil) // insert distros to be used distros := []distro.Distro{ distro.Distro{Id: "d1"}, distro.Distro{Id: "d2"}, } for _, d := range distros { So(d.Insert(), ShouldBeNil) } // this patch adds a new task to the existing build configPatch := &patch.Patch{ Id: "52549c143122", Project: patchedProject, BuildVariants: []string{"all"}, Githash: patchedRevision, Patches: []patch.ModulePatch{ patch.ModulePatch{ Githash: "revision", PatchSet: patch.PatchSet{ Patch: fmt.Sprintf(string(fileBytes), patchedConfigFile, patchedConfigFile, patchedConfigFile, patchedConfigFile), Summary: []thirdparty.Summary{ thirdparty.Summary{ Name: configFilePath, Additions: 4, Deletions: 80, }, thirdparty.Summary{ Name: patchedProject, Additions: 6, Deletions: 0, }, }, }, }, }, } err = configPatch.Insert() testutil.HandleTestingErr(err, t, "Couldn't insert test patch: %v", err) version, err := ValidateAndFinalize(configPatch, patchTestConfig) So(err, ShouldBeNil) So(version, ShouldNotBeNil) // ensure the relevant builds/tasks were created builds, err := build.Find(build.All) So(err, ShouldBeNil) So(len(builds), ShouldEqual, 1) So(len(builds[0].Tasks), ShouldEqual, 1) tasks, err := model.FindAllTasks(bson.M{}, db.NoProjection, db.NoSort, db.NoSkip, db.NoLimit, ) So(err, ShouldBeNil) So(len(tasks), ShouldEqual, 1) }) Reset(func() { db.Clear(distro.Collection) }) }) }
// Returns a JSON response of an array with the NumRecentVersions // most recent versions (sorted on commit order number descending). func (restapi restAPI) getRecentVersions(w http.ResponseWriter, r *http.Request) { projectId := mux.Vars(r)["project_id"] versions, err := version.Find(version.ByMostRecentForRequester(projectId, evergreen.RepotrackerVersionRequester).Limit(10)) if err != nil { msg := fmt.Sprintf("Error finding recent versions of project '%v'", projectId) evergreen.Logger.Logf(slogger.ERROR, "%v: %v", msg, err) restapi.WriteJSON(w, http.StatusInternalServerError, responseError{Message: msg}) return } // Create a slice of version ids to find all relevant builds versionIds := make([]string, 0, len(versions)) // Cache the order of versions in a map for lookup by their id versionIdx := make(map[string]int, len(versions)) for i, version := range versions { versionIds = append(versionIds, version.Id) versionIdx[version.Id] = i } // Find all builds corresponding the set of version ids builds, err := build.Find( build.ByVersions(versionIds). WithFields(build.BuildVariantKey, build.DisplayNameKey, build.TasksKey, build.VersionKey)) if err != nil { msg := fmt.Sprintf("Error finding recent versions of project '%v'", projectId) evergreen.Logger.Logf(slogger.ERROR, "%v: %v", msg, err) restapi.WriteJSON(w, http.StatusInternalServerError, responseError{Message: msg}) return } result := recentVersionsContent{ Project: projectId, Versions: make([]versionLessInfo, 0, len(versions)), } for _, version := range versions { versionInfo := versionLessInfo{ Id: version.Id, Author: version.Author, Revision: version.Revision, Message: version.Message, Builds: make(versionByBuild), } result.Versions = append(result.Versions, versionInfo) } for _, build := range builds { buildInfo := versionBuildInfo{ Id: build.Id, Name: build.DisplayName, Tasks: make(versionByBuildByTask, len(build.Tasks)), } for _, task := range build.Tasks { buildInfo.Tasks[task.DisplayName] = versionStatus{ Id: task.Id, Status: task.Status, TimeTaken: task.TimeTaken, } } versionInfo := result.Versions[versionIdx[build.Version]] versionInfo.Builds[build.BuildVariant] = buildInfo } restapi.WriteJSON(w, http.StatusOK, result) return }
// taskTimingJSON sends over the task data for a certain task of a certain build variant func (uis *UIServer) taskTimingJSON(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) beforeTaskId := r.FormValue("before") limit, err := getIntValue(r, "limit", 50) if err != nil { uis.LoggedError(w, r, http.StatusBadRequest, err) return } buildVariant := mux.Vars(r)["build_variant"] taskName := mux.Vars(r)["task_name"] request := mux.Vars(r)["request"] if projCtx.Project == nil { uis.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("not found")) return } bv := projCtx.Project.FindBuildVariant(buildVariant) if bv == nil { uis.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("build variant %v not found", buildVariant)) return } var versionIds []string data := UIStats{} // if its all tasks find the build if taskName == "" || taskName == "All Tasks" { // TODO: switch this to be a query on the builds TaskCache builds, err := build.Find(build.ByProjectAndVariant(projCtx.Project.Identifier, buildVariant, request). WithFields(build.IdKey, build.CreateTimeKey, build.VersionKey, build.TimeTakenKey, build.TasksKey, build.FinishTimeKey, build.StartTimeKey). Sort([]string{"-" + build.RevisionOrderNumberKey}). Limit(limit)) if err != nil { uis.LoggedError(w, r, http.StatusBadRequest, err) return } versionIds = make([]string, 0, len(builds)) uiBuilds := []*UIBuild{} // get the versions for every single task that was returned for _, build := range builds { // create a UITask b := &UIBuild{ Id: build.Id, CreateTime: build.CreateTime, StartTime: build.StartTime, FinishTime: build.FinishTime, Version: build.Version, Status: build.Status, TimeTaken: int64(build.TimeTaken), Tasks: build.Tasks, } uiBuilds = append(uiBuilds, b) versionIds = append(versionIds, b.Version) } data.Builds = uiBuilds } else { foundTask := false for _, task := range bv.Tasks { if task.Name == taskName { foundTask = true break } } if !foundTask { uis.LoggedError(w, r, http.StatusNotFound, fmt.Errorf("no task named '%v'", taskName)) return } tasks, err := model.FindCompletedTasksByVariantAndName(projCtx.Project.Identifier, buildVariant, taskName, request, limit, beforeTaskId) if err != nil { uis.LoggedError(w, r, http.StatusNotFound, err) return } uiTasks := []*UITask{} versionIds = make([]string, 0, len(tasks)) // get the versions for every single task that was returned for _, task := range tasks { // create a UITask t := &UITask{ Id: task.Id, CreateTime: task.CreateTime, DispatchTime: task.DispatchTime, PushTime: task.PushTime, ScheduledTime: task.ScheduledTime, StartTime: task.StartTime, FinishTime: task.FinishTime, Version: task.Version, Status: task.Status, Host: task.HostId, Distro: task.DistroId, } uiTasks = append(uiTasks, t) versionIds = append(versionIds, task.Version) } data.Tasks = uiTasks } // Populate the versions field if with commits, otherwise patches field if request == evergreen.RepotrackerVersionRequester { versions, err := version.Find(version.ByIds(versionIds). WithFields(version.IdKey, version.CreateTimeKey, version.MessageKey, version.AuthorKey, version.RevisionKey, version.RevisionOrderNumberKey). Sort([]string{"-" + version.RevisionOrderNumberKey})) if err != nil { uis.LoggedError(w, r, http.StatusBadRequest, err) return } data.Versions = versions } else { // patches patches, err := patch.Find(patch.ByVersions(versionIds). WithFields(patch.IdKey, patch.CreateTimeKey, patch.DescriptionKey, patch.AuthorKey)) if err != nil { uis.LoggedError(w, r, http.StatusBadRequest, err) return } data.Patches = patches } uis.WriteJSON(w, http.StatusOK, data) }