diff options
Diffstat (limited to 'routers/api/v1/repo/issue.go')
-rw-r--r-- | routers/api/v1/repo/issue.go | 324 |
1 files changed, 175 insertions, 149 deletions
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 8a61fc9834..861e63a9b8 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -132,74 +132,73 @@ func SearchIssues(ctx *context.APIContext) { isClosed = util.OptionalBoolFalse } - // find repos user can access (for issue search) - opts := &repo_model.SearchRepoOptions{ - Private: false, - AllPublic: true, - TopicOnly: false, - Collaborate: util.OptionalBoolNone, - // This needs to be a column that is not nil in fixtures or - // MySQL will return different results when sorting by null in some cases - OrderBy: db.SearchOrderByAlphabetically, - Actor: ctx.Doer, - } - if ctx.IsSigned { - opts.Private = true - opts.AllLimited = true - } - if ctx.FormString("owner") != "" { - owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner")) - if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.Error(http.StatusBadRequest, "Owner not found", err) - } else { - ctx.Error(http.StatusInternalServerError, "GetUserByName", err) + var ( + repoIDs []int64 + allPublic bool + ) + { + // find repos user can access (for issue search) + opts := &repo_model.SearchRepoOptions{ + Private: false, + AllPublic: true, + TopicOnly: false, + Collaborate: util.OptionalBoolNone, + // This needs to be a column that is not nil in fixtures or + // MySQL will return different results when sorting by null in some cases + OrderBy: db.SearchOrderByAlphabetically, + Actor: ctx.Doer, + } + if ctx.IsSigned { + opts.Private = true + opts.AllLimited = true + } + if ctx.FormString("owner") != "" { + owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner")) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Error(http.StatusBadRequest, "Owner not found", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) + } + return } - return + opts.OwnerID = owner.ID + opts.AllLimited = false + opts.AllPublic = false + opts.Collaborate = util.OptionalBoolFalse + } + if ctx.FormString("team") != "" { + if ctx.FormString("owner") == "" { + ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team") + return + } + team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team")) + if err != nil { + if organization.IsErrTeamNotExist(err) { + ctx.Error(http.StatusBadRequest, "Team not found", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) + } + return + } + opts.TeamID = team.ID } - opts.OwnerID = owner.ID - opts.AllLimited = false - opts.AllPublic = false - opts.Collaborate = util.OptionalBoolFalse - } - if ctx.FormString("team") != "" { - if ctx.FormString("owner") == "" { - ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team") - return + + if opts.AllPublic { + allPublic = true + opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer } - team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team")) + repoIDs, _, err = repo_model.SearchRepositoryIDs(opts) if err != nil { - if organization.IsErrTeamNotExist(err) { - ctx.Error(http.StatusBadRequest, "Team not found", err) - } else { - ctx.Error(http.StatusInternalServerError, "GetUserByName", err) - } + ctx.Error(http.StatusInternalServerError, "SearchRepositoryIDs", err) return } - opts.TeamID = team.ID } - repoCond := repo_model.SearchRepositoryCondition(opts) - repoIDs, _, err := repo_model.SearchRepositoryIDs(opts) - if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchRepositoryIDs", err) - return - } - - var issues []*issues_model.Issue - var filteredCount int64 - keyword := ctx.FormTrim("q") if strings.IndexByte(keyword, 0) >= 0 { keyword = "" } - var issueIDs []int64 - if len(keyword) > 0 && len(repoIDs) > 0 { - if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword, ctx.FormString("state")); err != nil { - ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err) - return - } - } var isPull util.OptionalBool switch ctx.FormString("type") { @@ -211,16 +210,33 @@ func SearchIssues(ctx *context.APIContext) { isPull = util.OptionalBoolNone } - labels := ctx.FormTrim("labels") - var includedLabelNames []string - if len(labels) > 0 { - includedLabelNames = strings.Split(labels, ",") + var includedAnyLabels []int64 + { + + labels := ctx.FormTrim("labels") + var includedLabelNames []string + if len(labels) > 0 { + includedLabelNames = strings.Split(labels, ",") + } + includedAnyLabels, err = issues_model.GetLabelIDsByNames(ctx, includedLabelNames) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetLabelIDsByNames", err) + return + } } - milestones := ctx.FormTrim("milestones") - var includedMilestones []string - if len(milestones) > 0 { - includedMilestones = strings.Split(milestones, ",") + var includedMilestones []int64 + { + milestones := ctx.FormTrim("milestones") + var includedMilestoneNames []string + if len(milestones) > 0 { + includedMilestoneNames = strings.Split(milestones, ",") + } + includedMilestones, err = issues_model.GetMilestoneIDsByNames(ctx, includedMilestoneNames) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetMilestoneIDsByNames", err) + return + } } // this api is also used in UI, @@ -232,64 +248,64 @@ func SearchIssues(ctx *context.APIContext) { limit = setting.API.MaxResponseItems } - // Only fetch the issues if we either don't have a keyword or the search returned issues - // This would otherwise return all issues if no issues were found by the search. - if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 || len(includedMilestones) > 0 { - issuesOpt := &issues_model.IssuesOptions{ - ListOptions: db.ListOptions{ - Page: ctx.FormInt("page"), - PageSize: limit, - }, - RepoCond: repoCond, - IsClosed: isClosed, - IssueIDs: issueIDs, - IncludedLabelNames: includedLabelNames, - IncludeMilestones: includedMilestones, - SortType: "priorityrepo", - PriorityRepoID: ctx.FormInt64("priority_repo_id"), - IsPull: isPull, - UpdatedBeforeUnix: before, - UpdatedAfterUnix: since, - } - - ctxUserID := int64(0) - if ctx.IsSigned { - ctxUserID = ctx.Doer.ID - } + searchOpt := &issue_indexer.SearchOptions{ + Paginator: &db.ListOptions{ + PageSize: limit, + Page: ctx.FormInt("page"), + }, + Keyword: keyword, + RepoIDs: repoIDs, + AllPublic: allPublic, + IsPull: isPull, + IsClosed: isClosed, + IncludedAnyLabelIDs: includedAnyLabels, + MilestoneIDs: includedMilestones, + SortBy: issue_indexer.SortByCreatedDesc, + } - // Filter for: Created by User, Assigned to User, Mentioning User, Review of User Requested + if since != 0 { + searchOpt.UpdatedAfterUnix = &since + } + if before != 0 { + searchOpt.UpdatedBeforeUnix = &before + } + + if ctx.IsSigned { + ctxUserID := ctx.Doer.ID if ctx.FormBool("created") { - issuesOpt.PosterID = ctxUserID + searchOpt.PosterID = &ctxUserID } if ctx.FormBool("assigned") { - issuesOpt.AssigneeID = ctxUserID + searchOpt.AssigneeID = &ctxUserID } if ctx.FormBool("mentioned") { - issuesOpt.MentionedID = ctxUserID + searchOpt.MentionID = &ctxUserID } if ctx.FormBool("review_requested") { - issuesOpt.ReviewRequestedID = ctxUserID + searchOpt.ReviewRequestedID = &ctxUserID } if ctx.FormBool("reviewed") { - issuesOpt.ReviewedID = ctxUserID + searchOpt.ReviewedID = &ctxUserID } + } - if issues, err = issues_model.Issues(ctx, issuesOpt); err != nil { - ctx.Error(http.StatusInternalServerError, "Issues", err) - return - } + // FIXME: It's unsupported to sort by priority repo when searching by indexer, + // it's indeed an regression, but I think it is worth to support filtering by indexer first. + _ = ctx.FormInt64("priority_repo_id") - issuesOpt.ListOptions = db.ListOptions{ - Page: -1, - } - if filteredCount, err = issues_model.CountIssues(ctx, issuesOpt); err != nil { - ctx.Error(http.StatusInternalServerError, "CountIssues", err) - return - } + ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SearchIssues", err) + return + } + issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err) + return } - ctx.SetLinkHeader(int(filteredCount), limit) - ctx.SetTotalCountHeader(filteredCount) + ctx.SetLinkHeader(int(total), limit) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) } @@ -384,23 +400,12 @@ func ListIssues(ctx *context.APIContext) { isClosed = util.OptionalBoolFalse } - var issues []*issues_model.Issue - var filteredCount int64 - keyword := ctx.FormTrim("q") if strings.IndexByte(keyword, 0) >= 0 { keyword = "" } - var issueIDs []int64 - var labelIDs []int64 - if len(keyword) > 0 { - issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword, ctx.FormString("state")) - if err != nil { - ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err) - return - } - } + var labelIDs []int64 if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 { labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx.Repo.Repository.ID, splitted) if err != nil { @@ -465,40 +470,61 @@ func ListIssues(ctx *context.APIContext) { return } - // Only fetch the issues if we either don't have a keyword or the search returned issues - // This would otherwise return all issues if no issues were found by the search. - if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { - issuesOpt := &issues_model.IssuesOptions{ - ListOptions: listOptions, - RepoIDs: []int64{ctx.Repo.Repository.ID}, - IsClosed: isClosed, - IssueIDs: issueIDs, - LabelIDs: labelIDs, - MilestoneIDs: mileIDs, - IsPull: isPull, - UpdatedBeforeUnix: before, - UpdatedAfterUnix: since, - PosterID: createdByID, - AssigneeID: assignedByID, - MentionedID: mentionedByID, - } - - if issues, err = issues_model.Issues(ctx, issuesOpt); err != nil { - ctx.Error(http.StatusInternalServerError, "Issues", err) - return + searchOpt := &issue_indexer.SearchOptions{ + Paginator: &listOptions, + Keyword: keyword, + RepoIDs: []int64{ctx.Repo.Repository.ID}, + IsPull: isPull, + IsClosed: isClosed, + SortBy: issue_indexer.SortByCreatedDesc, + } + if since != 0 { + searchOpt.UpdatedAfterUnix = &since + } + if before != 0 { + searchOpt.UpdatedBeforeUnix = &before + } + if len(labelIDs) == 1 && labelIDs[0] == 0 { + searchOpt.NoLabelOnly = true + } else { + for _, labelID := range labelIDs { + if labelID > 0 { + searchOpt.IncludedLabelIDs = append(searchOpt.IncludedLabelIDs, labelID) + } else { + searchOpt.ExcludedLabelIDs = append(searchOpt.ExcludedLabelIDs, -labelID) + } } + } - issuesOpt.ListOptions = db.ListOptions{ - Page: -1, - } - if filteredCount, err = issues_model.CountIssues(ctx, issuesOpt); err != nil { - ctx.Error(http.StatusInternalServerError, "CountIssues", err) - return - } + if len(mileIDs) == 1 && mileIDs[0] == db.NoConditionID { + searchOpt.MilestoneIDs = []int64{0} + } else { + searchOpt.MilestoneIDs = mileIDs + } + + if createdByID > 0 { + searchOpt.PosterID = &createdByID + } + if assignedByID > 0 { + searchOpt.AssigneeID = &assignedByID + } + if mentionedByID > 0 { + searchOpt.MentionID = &mentionedByID + } + + ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SearchIssues", err) + return + } + issues, err := issues_model.GetIssuesByIDs(ctx, ids, true) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindIssuesByIDs", err) + return } - ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize) - ctx.SetTotalCountHeader(filteredCount) + ctx.SetLinkHeader(int(total), listOptions.PageSize) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) } |