コード例 #1
0
ファイル: task_history.go プロジェクト: bjori/evergreen
// Helper to make the appropriate query to the versions collection for what
// we will need.  "before" indicates whether to fetch versions before or
// after the passed-in task.
func makeVersionsQuery(anchorOrderNum int, projectId string, versionsToFetch int, before bool) ([]version.Version, error) {
	// decide how the versions we want relative to the task's revision order number
	ronQuery := bson.M{"$gt": anchorOrderNum}
	if before {
		ronQuery = bson.M{"$lt": anchorOrderNum}
	}

	// switch how to sort the versions
	sortVersions := []string{version.RevisionOrderNumberKey}
	if before {
		sortVersions = []string{"-" + version.RevisionOrderNumberKey}
	}

	// fetch the versions
	return version.Find(
		db.Query(bson.M{
			version.IdentifierKey:          projectId,
			version.RevisionOrderNumberKey: ronQuery,
		}).WithFields(
			version.RevisionOrderNumberKey,
			version.RevisionKey,
			version.MessageKey,
			version.CreateTimeKey,
		).Sort(sortVersions).Limit(versionsToFetch))

}
コード例 #2
0
ファイル: task_history.go プロジェクト: himanshugpt/evergreen
func (iter *taskHistoryIterator) findAllVersions(v *version.Version, numRevisions int, before, include bool) ([]version.Version, bool, error) {
	versionQuery := bson.M{
		version.RequesterKey:  evergreen.RepotrackerVersionRequester,
		version.IdentifierKey: iter.ProjectName,
	}

	// If including the specified version in the result, then should
	// get an additional revision
	if include {
		numRevisions++
	}

	// Determine the comparator to use based on whether the revisions
	// come before/after the specified version
	compare, order := "$gt", version.RevisionOrderNumberKey
	if before {
		compare, order = "$lt", fmt.Sprintf("-%v", version.RevisionOrderNumberKey)
		if include {
			compare = "$lte"
		}
	} else if include {
		compare = "$gte"
	}

	if v != nil {
		versionQuery[version.RevisionOrderNumberKey] = bson.M{compare: v.RevisionOrderNumber}
	}

	// Get the next numRevisions, plus an additional one to check if have
	// reached the beginning/end of history
	versions, err := version.Find(
		db.Query(versionQuery).WithFields(
			version.IdKey,
			version.RevisionOrderNumberKey,
			version.RevisionKey,
			version.MessageKey,
			version.CreateTimeKey,
		).Sort([]string{order}).Limit(numRevisions + 1))

	// Check if there were fewer results returned by the query than what
	// the limit was set as
	exhausted := len(versions) <= numRevisions
	if !exhausted {
		// Exclude the last version because we actually only wanted
		// `numRevisions` number of commits
		versions = versions[:len(versions)-1]
	}

	// The iterator can only be exhausted if an actual version was specified
	exhausted = exhausted || (v == nil && numRevisions == 0)

	if !before {
		// Reverse the order so that the most recent version is first
		for i, j := 0, len(versions)-1; i < j; i, j = i+1, j-1 {
			versions[i], versions[j] = versions[j], versions[i]
		}
	}
	return versions, exhausted, err
}
コード例 #3
0
ファイル: models.go プロジェクト: markbenvenuto/evergreen
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
}
コード例 #4
0
ファイル: waterfall.go プロジェクト: tychoish/evergreen
// 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
}
コード例 #5
0
ファイル: version.go プロジェクト: tychoish/evergreen
//versionFind redirects to the correct version page based on the gitHash and versionId given.
//It finds the version associated with the versionId and gitHash and redirects to /version/{version_id}.
func (uis *UIServer) versionFind(w http.ResponseWriter, r *http.Request) {
	id := mux.Vars(r)["project_id"]
	revision := mux.Vars(r)["revision"]
	if len(revision) < 5 {
		http.Error(w, "revision not long enough: must be at least 5 characters", http.StatusBadRequest)
		return
	}
	foundVersions, err := version.Find(version.ByProjectIdAndRevisionPrefix(id, revision).Limit(2))
	if err != nil {
		uis.LoggedError(w, r, http.StatusInternalServerError, err)
		return
	}
	if len(foundVersions) == 0 {
		uis.WriteJSON(w, http.StatusNotFound, fmt.Sprintf("Version Not Found: %v - %v", id, revision))
		return
	}
	if len(foundVersions) > 1 {
		uis.WriteJSON(w, http.StatusBadRequest, fmt.Sprintf("Multiple versions found: %v - %v", id, revision))
		return
	}
	http.Redirect(w, r, fmt.Sprintf("/version/%v", foundVersions[0].Id), http.StatusFound)
}
コード例 #6
0
ファイル: models.go プロジェクト: markbenvenuto/evergreen
func getVersionHistory(versionId string, N int) ([]version.Version, error) {
	v, err := version.FindOne(version.ById(versionId))
	if err != nil {
		return nil, err
	} else if v == nil {
		return nil, fmt.Errorf("Version '%v' not found", versionId)
	}

	// Versions in the same push event, assuming that no two push events happen at the exact same time
	// Never want more than 2N+1 versions, so make sure we add a limit

	siblingVersions, err := version.Find(db.Query(
		bson.M{
			"order":  v.RevisionOrderNumber,
			"r":      evergreen.RepotrackerVersionRequester,
			"branch": v.Project,
		}).WithoutFields(version.ConfigKey).Sort([]string{"order"}).Limit(2*N + 1))
	if err != nil {
		return nil, err
	}

	versionIndex := -1
	for i := 0; i < len(siblingVersions); i++ {
		if siblingVersions[i].Id == v.Id {
			versionIndex = i
		}
	}

	numSiblings := len(siblingVersions) - 1
	versions := siblingVersions

	if versionIndex < N {
		// There are less than N later versions from the same push event
		// N subsequent versions plus the specified one
		subsequentVersions, err := version.Find(
			//TODO encapsulate this query in version pkg
			db.Query(bson.M{
				"order":  bson.M{"$gt": v.RevisionOrderNumber},
				"r":      evergreen.RepotrackerVersionRequester,
				"branch": v.Project,
			}).WithoutFields(version.ConfigKey).Sort([]string{"order"}).Limit(N - versionIndex))
		if err != nil {
			return nil, err
		}

		// Reverse the second array so we have the versions ordered "newest one first"
		for i := 0; i < len(subsequentVersions)/2; i++ {
			subsequentVersions[i], subsequentVersions[len(subsequentVersions)-1-i] = subsequentVersions[len(subsequentVersions)-1-i], subsequentVersions[i]
		}

		versions = append(subsequentVersions, versions...)
	}

	if numSiblings-versionIndex < N {
		previousVersions, err := version.Find(db.Query(bson.M{
			"order":  bson.M{"$lt": v.RevisionOrderNumber},
			"r":      evergreen.RepotrackerVersionRequester,
			"branch": v.Project,
		}).WithoutFields(version.ConfigKey).Sort([]string{"-order"}).Limit(N))
		if err != nil {
			return nil, err
		}
		versions = append(versions, previousVersions...)
	}

	return versions, nil
}
コード例 #7
0
ファイル: stats.go プロジェクト: pritten/evergreen
// 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)
}
コード例 #8
0
// Returns versions and tasks grouped by gitspec, newest first (sorted by order number desc)
func (self *buildVariantHistoryIterator) GetItems(beforeCommit *version.Version, numRevisions int) ([]bson.M, []version.Version, error) {
	session, dbobj, err := db.GetGlobalSessionFactory().GetSession()
	defer session.Close()
	if err != nil {
		return nil, nil, err
	}

	var versionQuery db.Q
	if beforeCommit != nil {
		versionQuery = db.Query(bson.M{
			version.RequesterKey:           evergreen.RepotrackerVersionRequester,
			version.RevisionOrderNumberKey: bson.M{"$lt": beforeCommit.RevisionOrderNumber},
			version.IdentifierKey:          self.ProjectName,
			version.BuildVariantsKey: bson.M{
				"$elemMatch": bson.M{
					version.BuildStatusVariantKey: self.BuildVariantInVersion,
				},
			},
		})
	} else {
		versionQuery = db.Query(bson.M{
			version.RequesterKey:  evergreen.RepotrackerVersionRequester,
			version.IdentifierKey: self.ProjectName,
			version.BuildVariantsKey: bson.M{
				"$elemMatch": bson.M{
					version.BuildStatusVariantKey: self.BuildVariantInVersion,
				},
			},
		})
	}
	versionQuery = versionQuery.WithFields(
		version.IdKey,
		version.RevisionOrderNumberKey,
		version.RevisionKey,
		version.MessageKey,
		version.CreateTimeKey,
	).Sort([]string{"-" + version.RevisionOrderNumberKey}).Limit(numRevisions)

	//Get the next numCommits
	versions, err := version.Find(versionQuery)

	if err != nil {
		return nil, nil, err
	}

	if len(versions) == 0 {
		return nil, []version.Version{}, nil
	}

	//versionEndBoundary is the *earliest* version which should be included in results
	versionEndBoundary := versions[len(versions)-1]

	matchFilter := bson.M{
		TaskRequesterKey:    evergreen.RepotrackerVersionRequester,
		TaskBuildVariantKey: self.BuildVariantInTask,
		TaskProjectKey:      self.ProjectName,
	}

	if beforeCommit != nil {
		matchFilter[TaskRevisionOrderNumberKey] = bson.M{
			"$gte": versionEndBoundary.RevisionOrderNumber,
			"$lt":  beforeCommit.RevisionOrderNumber,
		}
	} else {
		matchFilter[TaskRevisionOrderNumberKey] = bson.M{
			"$gte": versionEndBoundary.RevisionOrderNumber,
		}
	}

	pipeline := dbobj.C(TasksCollection).Pipe(
		[]bson.M{
			{"$match": matchFilter},
			bson.M{"$sort": bson.D{{TaskRevisionOrderNumberKey, 1}}},
			bson.M{
				"$group": bson.M{
					"_id":   "$" + TaskRevisionKey,
					"order": bson.M{"$first": "$" + TaskRevisionOrderNumberKey},
					"tasks": bson.M{
						"$push": bson.M{
							"_id":              "$" + TaskIdKey,
							"status":           "$" + TaskStatusKey,
							"task_end_details": "$" + TaskDetailsKey,
							"activated":        "$" + TaskActivatedKey,
							"time_taken":       "$" + TaskTimeTakenKey,
							"display_name":     "$" + TaskDisplayNameKey,
						},
					},
				},
			},
			bson.M{"$sort": bson.M{TaskRevisionOrderNumberKey: -1, TaskDisplayNameKey: 1}},
		},
	)

	var output []bson.M
	err = pipeline.All(&output)
	if err != nil {
		return nil, nil, err
	}

	return output, versions, nil
}
コード例 #9
0
ファイル: version.go プロジェクト: MauroJr/evergreen
// 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
}
コード例 #10
0
ファイル: grid.go プロジェクト: MauroJr/evergreen
func (uis *UIServer) grid(w http.ResponseWriter, r *http.Request) {
	projCtx := MustHaveProjectContext(r)
	if projCtx.Project == nil {
		http.Error(w, "Project not found", http.StatusNotFound)
	}

	// If no version was specified in the URL, grab the latest version on the project
	if projCtx.Version == nil {
		v, err := version.Find(version.ByMostRecentForRequester(projCtx.Project.Identifier, evergreen.RepotrackerVersionRequester).Limit(1))
		if err != nil {
			http.Error(w, fmt.Sprintf("Error finding version: %v", err), http.StatusInternalServerError)
			return
		}
		if len(v) > 0 {
			projCtx.Version = &v[0]
		}
	}

	var versions map[string]version.Version
	var cells grid.Grid
	var failures grid.Failures
	var depth int
	var err error

	d := mux.Vars(r)["depth"]
	if d == "" {
		depth = defaultGridDepth
	} else {
		depth, err = strconv.Atoi(d)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error converting depth: %v", err), http.StatusBadRequest)
			return
		}
		if depth < 0 {
			http.Error(w, fmt.Sprintf("Depth must be non-negative, got %v", depth), http.StatusBadRequest)
			return
		}
	}

	if projCtx.Version != nil {
		recentVersions, err := version.Find(version.
			ByProjectIdAndOrder(projCtx.Version.Project, projCtx.Version.RevisionOrderNumber).
			WithFields(version.IdKey, version.RevisionKey, version.RevisionOrderNumberKey, version.MessageKey).
			Sort([]string{"-" + version.RevisionOrderNumberKey}).
			Limit(depth + 1))
		if err != nil {
			http.Error(w, fmt.Sprintf("Error fetching versions: %v", err), http.StatusInternalServerError)
			return
		}

		versions = make(map[string]version.Version, len(recentVersions))
		for _, v := range recentVersions {
			versions[v.Revision] = v
		}

		cells, err = grid.FetchCells(*projCtx.Version, depth)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error fetching builds: %v", err), http.StatusInternalServerError)
			return
		}

		failures, err = grid.FetchFailures(*projCtx.Version, depth)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error fetching builds: %v", err), http.StatusInternalServerError)
			return
		}
	} else {
		versions = make(map[string]version.Version)
		cells = make(grid.Grid, 0)
		failures = make(grid.Failures, 0)
	}
	uis.WriteHTML(w, http.StatusOK, struct {
		ProjectData projectContext
		Versions    map[string]version.Version
		GridCells   grid.Grid
		Failures    grid.Failures
		User        *user.DBUser
	}{projCtx, versions, cells, failures, GetUser(r)}, "base", "grid.html", "base_angular.html", "menu.html")
}
コード例 #11
0
ファイル: timeline.go プロジェクト: himanshugpt/evergreen
func (uis *UIServer) patchTimelineJson(w http.ResponseWriter, r *http.Request) {
	projCtx := MustHaveProjectContext(r)
	pageNum, err := strconv.Atoi(r.FormValue("page"))
	if err != nil {
		pageNum = 0
	}
	skip := pageNum * DefaultLimit

	user := mux.Vars(r)["user_id"]
	var patches []patch.Patch
	if len(user) > 0 {
		patches, err = patch.Find(patch.ByUser(user).
			Project(patch.ExcludePatchDiff).
			Sort([]string{"-" + patch.CreateTimeKey}).
			Skip(skip).Limit(DefaultLimit))
	} else {
		patches, err = patch.Find(patch.ByProject(projCtx.Project.Identifier).
			Sort([]string{"-" + patch.CreateTimeKey}).
			Project(patch.ExcludePatchDiff).
			Skip(skip).Limit(DefaultLimit))
	}
	if err != nil {
		uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error fetching patches for %v: %v", projCtx.Project.Identifier, err))
		return
	}

	versionIds := make([]string, 0, len(patches))
	uiPatches := make([]uiPatch, 0, len(patches))
	for _, patch := range patches {
		if patch.Version != "" {
			versionIds = append(versionIds, patch.Version)
		}
		baseVersion, err := version.FindOne(version.ByProjectIdAndRevision(patch.Project, patch.Githash))
		if err != nil {
			uis.LoggedError(w, r, http.StatusInternalServerError, err)
			return
		}
		var baseVersionId string
		if baseVersion != nil {
			baseVersionId = baseVersion.Id
		}
		patch.Patches = nil
		uiPatches = append(uiPatches, uiPatch{Patch: patch, BaseVersionId: baseVersionId})
	}
	versions, err := version.Find(version.ByIds(versionIds).WithoutFields(version.ConfigKey))
	if err != nil {
		uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error fetching versions for patches: %v", err))
		return
	}

	versionsMap := map[string]*uiVersion{}
	for _, version := range versions {
		versionUI, err := PopulateUIVersion(&version)
		if err != nil {
			uis.LoggedError(w, r, http.StatusInternalServerError, err)
			return
		}
		versionsMap[version.Id] = versionUI
	}

	data := struct {
		VersionsMap map[string]*uiVersion
		UIPatches   []uiPatch
		PageNum     int
	}{versionsMap, uiPatches, pageNum}

	uis.WriteJSON(w, http.StatusOK, data)
}