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  }