github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/service/version.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "net/http" 6 "sort" 7 8 "github.com/evergreen-ci/evergreen" 9 "github.com/evergreen-ci/evergreen/model" 10 "github.com/evergreen-ci/evergreen/model/build" 11 "github.com/evergreen-ci/evergreen/model/task" 12 "github.com/evergreen-ci/evergreen/model/user" 13 "github.com/evergreen-ci/evergreen/model/version" 14 "github.com/evergreen-ci/evergreen/plugin" 15 "github.com/evergreen-ci/evergreen/util" 16 "github.com/gorilla/mux" 17 "github.com/mongodb/grip" 18 "github.com/pkg/errors" 19 ) 20 21 func (uis *UIServer) versionPage(w http.ResponseWriter, r *http.Request) { 22 projCtx := MustHaveProjectContext(r) 23 if projCtx.Project == nil || projCtx.Version == nil { 24 http.Error(w, "not found", http.StatusNotFound) 25 return 26 } 27 28 // Set the config to blank to avoid writing it to the UI unnecessarily. 29 projCtx.Version.Config = "" 30 31 versionAsUI := uiVersion{ 32 Version: *projCtx.Version, 33 RepoOwner: projCtx.ProjectRef.Owner, 34 Repo: projCtx.ProjectRef.Repo, 35 } 36 37 dbBuilds, err := build.Find(build.ByIds(projCtx.Version.BuildIds)) 38 if err != nil { 39 http.Error(w, err.Error(), http.StatusInternalServerError) 40 return 41 } 42 var canEditPatch bool 43 currentUser := GetUser(r) 44 if projCtx.Patch != nil { 45 canEditPatch = uis.canEditPatch(currentUser, projCtx.Patch) 46 versionAsUI.PatchInfo = &uiPatch{Patch: *projCtx.Patch} 47 // diff builds for each build in the version 48 var baseBuilds []build.Build 49 baseBuilds, err = build.Find(build.ByRevision(projCtx.Version.Revision)) 50 if err != nil { 51 http.Error(w, 52 fmt.Sprintf("error loading base builds for patch: %v", err), 53 http.StatusInternalServerError) 54 return 55 } 56 baseBuildsByVariant := map[string]*build.Build{} 57 for i := range baseBuilds { 58 baseBuildsByVariant[baseBuilds[i].BuildVariant] = &baseBuilds[i] 59 } 60 // diff all patch builds with their original build 61 diffs := []model.TaskStatusDiff{} 62 for i := range dbBuilds { 63 diff := model.StatusDiffBuilds( 64 baseBuildsByVariant[dbBuilds[i].BuildVariant], 65 &dbBuilds[i], 66 ) 67 if diff.Name != "" { 68 // append the tasks instead of the build for better usability 69 diffs = append(diffs, diff.Tasks...) 70 } 71 } 72 var baseVersion *version.Version 73 baseVersion, err = version.FindOne(version.BaseVersionFromPatch(projCtx.Version.Identifier, projCtx.Version.Revision)) 74 if err != nil { 75 http.Error(w, err.Error(), http.StatusInternalServerError) 76 return 77 } 78 if baseVersion == nil { 79 grip.Warningln("Could not find version for base commmit of patch build: ", projCtx.Version.Id) 80 } 81 baseId := "" 82 if baseVersion != nil { 83 baseId = baseVersion.Id 84 } 85 versionAsUI.PatchInfo.BaseVersionId = baseId 86 versionAsUI.PatchInfo.StatusDiffs = diffs 87 } 88 89 failedTaskIds := []string{} 90 uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) 91 for _, build := range dbBuilds { 92 buildAsUI := uiBuild{Build: build} 93 94 uiTasks := make([]uiTask, 0, len(build.Tasks)) 95 for _, t := range build.Tasks { 96 uiTasks = append(uiTasks, 97 uiTask{ 98 Task: task.Task{ 99 Id: t.Id, Activated: t.Activated, StartTime: t.StartTime, TimeTaken: t.TimeTaken, 100 Status: t.Status, Details: t.StatusDetails, DisplayName: t.DisplayName, 101 }}) 102 buildAsUI.TaskStatusCount.incrementStatus(t.Status, t.StatusDetails) 103 if t.Status == evergreen.TaskFailed { 104 failedTaskIds = append(failedTaskIds, t.Id) 105 } 106 if t.Activated { 107 versionAsUI.ActiveTasks++ 108 } 109 } 110 buildAsUI.Tasks = uiTasks 111 uiBuilds = append(uiBuilds, buildAsUI) 112 } 113 err = addFailedTests(failedTaskIds, uiBuilds) 114 if err != nil { 115 uis.LoggedError(w, r, http.StatusInternalServerError, err) 116 return 117 } 118 versionAsUI.Builds = uiBuilds 119 120 pluginContext := projCtx.ToPluginContext(uis.Settings, GetUser(r)) 121 pluginContent := getPluginDataAndHTML(uis, plugin.VersionPage, pluginContext) 122 123 flashes := PopFlashes(uis.CookieStore, r, w) 124 uis.WriteHTML(w, http.StatusOK, struct { 125 ProjectData projectContext 126 User *user.DBUser 127 Flashes []interface{} 128 Version *uiVersion 129 PluginContent pluginData 130 CanEdit bool 131 JiraHost string 132 }{projCtx, currentUser, flashes, &versionAsUI, pluginContent, canEditPatch, 133 uis.Settings.Jira.Host}, "base", "version.html", "base_angular.html", "menu.html") 134 } 135 136 func (uis *UIServer) modifyVersion(w http.ResponseWriter, r *http.Request) { 137 var err error 138 139 projCtx := MustHaveProjectContext(r) 140 user := MustHaveUser(r) 141 if projCtx.Project == nil || projCtx.Version == nil { 142 http.Error(w, "not found", http.StatusNotFound) 143 return 144 } 145 146 jsonMap := struct { 147 Action string `json:"action"` 148 Active bool `json:"active"` 149 Abort bool `json:"abort"` 150 Priority int64 `json:"priority"` 151 TaskIds []string `json:"task_ids"` 152 }{} 153 154 if err = util.ReadJSONInto(util.NewRequestReader(r), &jsonMap); err != nil { 155 http.Error(w, err.Error(), http.StatusBadRequest) 156 return 157 } 158 159 // determine what action needs to be taken 160 switch jsonMap.Action { 161 case "restart": 162 if err = model.RestartVersion(projCtx.Version.Id, jsonMap.TaskIds, jsonMap.Abort, user.Id); err != nil { 163 http.Error(w, err.Error(), http.StatusInternalServerError) 164 return 165 } 166 case "set_active": 167 if jsonMap.Abort { 168 if err = model.AbortVersion(projCtx.Version.Id); err != nil { 169 http.Error(w, err.Error(), http.StatusInternalServerError) 170 return 171 } 172 } 173 if err = model.SetVersionActivation(projCtx.Version.Id, jsonMap.Active, user.Id); err != nil { 174 http.Error(w, err.Error(), http.StatusInternalServerError) 175 return 176 } 177 case "set_priority": 178 if jsonMap.Priority > evergreen.MaxTaskPriority { 179 if !uis.isSuperUser(user) { 180 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), 181 http.StatusBadRequest) 182 return 183 } 184 } 185 if err = model.SetVersionPriority(projCtx.Version.Id, jsonMap.Priority); err != nil { 186 http.Error(w, err.Error(), http.StatusInternalServerError) 187 return 188 } 189 default: 190 uis.WriteJSON(w, http.StatusBadRequest, fmt.Sprintf("Unrecognized action: %v", jsonMap.Action)) 191 return 192 } 193 194 // After the version has been modified, re-load it from DB and send back the up-to-date view 195 // to the client. 196 projCtx.Version, err = version.FindOne(version.ById(projCtx.Version.Id)) 197 if err != nil { 198 uis.LoggedError(w, r, http.StatusInternalServerError, err) 199 return 200 } 201 202 versionAsUI := uiVersion{ 203 Version: *projCtx.Version, 204 RepoOwner: projCtx.ProjectRef.Owner, 205 Repo: projCtx.ProjectRef.Repo, 206 } 207 dbBuilds, err := build.Find(build.ByIds(projCtx.Version.BuildIds)) 208 if err != nil { 209 uis.LoggedError(w, r, http.StatusInternalServerError, err) 210 return 211 } 212 213 uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) 214 for _, build := range dbBuilds { 215 buildAsUI := uiBuild{Build: build} 216 uiTasks := make([]uiTask, 0, len(build.Tasks)) 217 for _, t := range build.Tasks { 218 uiTasks = append(uiTasks, 219 uiTask{ 220 Task: task.Task{Id: t.Id, Activated: t.Activated, 221 StartTime: t.StartTime, TimeTaken: t.TimeTaken, Status: t.Status, 222 Details: t.StatusDetails, DisplayName: t.DisplayName}, 223 }) 224 if t.Activated { 225 versionAsUI.ActiveTasks++ 226 } 227 } 228 buildAsUI.Tasks = uiTasks 229 uiBuilds = append(uiBuilds, buildAsUI) 230 } 231 versionAsUI.Builds = uiBuilds 232 uis.WriteJSON(w, http.StatusOK, versionAsUI) 233 } 234 235 // addFailedTests fetches the tasks that failed from the database and attaches 236 // the associated failed tests to the uiBuilds. 237 func addFailedTests(failedTaskIds []string, uiBuilds []uiBuild) error { 238 if len(failedTaskIds) == 0 { 239 return nil 240 } 241 failedTasks, err := task.Find(task.ByIds(failedTaskIds)) 242 if err != nil { 243 return errors.Wrap(err, "error fetching failed tasks") 244 } 245 246 failedTestsByTaskId := map[string][]string{} 247 for _, t := range failedTasks { 248 failedTests := []string{} 249 for _, r := range t.TestResults { 250 if r.Status == evergreen.TestFailedStatus { 251 failedTests = append(failedTests, r.TestFile) 252 } 253 } 254 failedTestsByTaskId[t.Id] = failedTests 255 } 256 for i, build := range uiBuilds { 257 for j, t := range build.Tasks { 258 if len(failedTestsByTaskId[t.Task.Id]) != 0 { 259 uiBuilds[i].Tasks[j].FailedTestNames = append(uiBuilds[i].Tasks[j].FailedTestNames, failedTestsByTaskId[t.Task.Id]...) 260 sort.Strings(uiBuilds[i].Tasks[j].FailedTestNames) 261 } 262 } 263 } 264 return nil 265 } 266 267 func (uis *UIServer) versionHistory(w http.ResponseWriter, r *http.Request) { 268 projCtx := MustHaveProjectContext(r) 269 data, err := getVersionHistory(projCtx.Version.Id, 5) 270 if err != nil { 271 http.Error(w, err.Error(), http.StatusInternalServerError) 272 return 273 } 274 275 user := GetUser(r) 276 versions := make([]*uiVersion, 0, len(data)) 277 278 for _, version := range data { 279 // Check whether the project associated with the particular version 280 // is accessible to this user. If not, we exclude it from the version 281 // history. This is done to hide the existence of the private project. 282 if projCtx.ProjectRef.Private && user == nil { 283 continue 284 } 285 286 versionAsUI := uiVersion{ 287 Version: version, 288 RepoOwner: projCtx.ProjectRef.Owner, 289 Repo: projCtx.ProjectRef.Repo, 290 } 291 versions = append(versions, &versionAsUI) 292 293 dbBuilds, err := build.Find(build.ByIds(version.BuildIds)) 294 if err != nil { 295 http.Error(w, err.Error(), http.StatusInternalServerError) 296 return 297 } 298 299 uiBuilds := make([]uiBuild, 0, len(projCtx.Version.BuildIds)) 300 for _, b := range dbBuilds { 301 buildAsUI := uiBuild{Build: b} 302 uiTasks := make([]uiTask, 0, len(b.Tasks)) 303 for _, t := range b.Tasks { 304 uiTasks = append(uiTasks, 305 uiTask{ 306 Task: task.Task{ 307 Id: t.Id, 308 Status: t.Status, 309 Activated: t.Activated, 310 DisplayName: t.DisplayName, 311 }, 312 }) 313 if t.Activated { 314 versionAsUI.ActiveTasks++ 315 } 316 } 317 buildAsUI.Tasks = uiTasks 318 uiBuilds = append(uiBuilds, buildAsUI) 319 } 320 versionAsUI.Builds = uiBuilds 321 } 322 uis.WriteJSON(w, http.StatusOK, versions) 323 } 324 325 //versionFind redirects to the correct version page based on the gitHash and versionId given. 326 //It finds the version associated with the versionId and gitHash and redirects to /version/{version_id}. 327 func (uis *UIServer) versionFind(w http.ResponseWriter, r *http.Request) { 328 id := mux.Vars(r)["project_id"] 329 revision := mux.Vars(r)["revision"] 330 if len(revision) < 5 { 331 http.Error(w, "revision not long enough: must be at least 5 characters", http.StatusBadRequest) 332 return 333 } 334 foundVersions, err := version.Find(version.ByProjectIdAndRevisionPrefix(id, revision).Limit(2)) 335 if err != nil { 336 uis.LoggedError(w, r, http.StatusInternalServerError, err) 337 return 338 } 339 if len(foundVersions) == 0 { 340 uis.WriteJSON(w, http.StatusNotFound, fmt.Sprintf("Version Not Found: %v - %v", id, revision)) 341 return 342 } 343 if len(foundVersions) > 1 { 344 uis.WriteJSON(w, http.StatusBadRequest, fmt.Sprintf("Multiple versions found: %v - %v", id, revision)) 345 return 346 } 347 http.Redirect(w, r, fmt.Sprintf("/version/%v", foundVersions[0].Id), http.StatusFound) 348 }