From a928739456b78072136a1a264a68758571238aac Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 11 Nov 2024 04:07:54 +0800 Subject: [PATCH 01/57] Refactor sidebar assignee&milestone&project selectors (#32465) Follow #32460 Now the code could be much clearer than before and easier to maintain. A lot of legacy code is removed. Manually tested. This PR is large enough, that fine tunes could be deferred to the future if there is no bug found or design problem. Screenshots:
![image](https://github.com/user-attachments/assets/35f4ab7b-1bc0-4bad-a73c-a4569328303c)
--- modules/base/tool.go | 3 + modules/base/tool_test.go | 1 + modules/container/set.go | 4 +- modules/container/set_test.go | 2 + modules/templates/helper.go | 1 + routers/web/repo/compare.go | 12 +- routers/web/repo/issue.go | 474 +++++++++--------- routers/web/repo/pull.go | 2 +- services/forms/repo_form.go | 1 - .../repo/issue/milestone/select_menu.tmpl | 38 -- templates/repo/issue/new_form.tmpl | 138 +---- .../repo/issue/sidebar/assignee_list.tmpl | 69 ++- templates/repo/issue/sidebar/label_list.tmpl | 18 +- .../repo/issue/sidebar/label_list_item.tmpl | 2 +- .../repo/issue/sidebar/milestone_list.tmpl | 64 ++- .../repo/issue/sidebar/participant_list.tmpl | 2 +- .../repo/issue/sidebar/project_list.tmpl | 68 ++- .../repo/issue/sidebar/reviewer_list.tmpl | 24 +- .../repo/issue/view_content/sidebar.tmpl | 13 +- web_src/css/repo.css | 6 - .../features/repo-issue-sidebar-combolist.ts | 164 ++++-- web_src/js/features/repo-issue-sidebar.md | 6 +- web_src/js/features/repo-issue-sidebar.ts | 219 +------- 23 files changed, 503 insertions(+), 828 deletions(-) delete mode 100644 templates/repo/issue/milestone/select_menu.tmpl diff --git a/modules/base/tool.go b/modules/base/tool.go index 9e43030f40..928c80700b 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -147,6 +147,9 @@ func StringsToInt64s(strs []string) ([]int64, error) { } ints := make([]int64, 0, len(strs)) for _, s := range strs { + if s == "" { + continue + } n, err := strconv.ParseInt(s, 10, 64) if err != nil { return nil, err diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 4af8b9bc4d..86cccdf209 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -152,6 +152,7 @@ func TestStringsToInt64s(t *testing.T) { } testSuccess(nil, nil) testSuccess([]string{}, []int64{}) + testSuccess([]string{""}, []int64{}) testSuccess([]string{"-1234"}, []int64{-1234}) testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256}) diff --git a/modules/container/set.go b/modules/container/set.go index adb77dcac7..105533f203 100644 --- a/modules/container/set.go +++ b/modules/container/set.go @@ -31,8 +31,8 @@ func (s Set[T]) AddMultiple(values ...T) { } } -// Contains determines whether a set contains the specified elements. -// Returns true if the set contains the specified element; otherwise, false. +// Contains determines whether a set contains all these elements. +// Returns true if the set contains all these elements; otherwise, false. func (s Set[T]) Contains(values ...T) bool { ret := true for _, value := range values { diff --git a/modules/container/set_test.go b/modules/container/set_test.go index 1502236034..a8b7ff8190 100644 --- a/modules/container/set_test.go +++ b/modules/container/set_test.go @@ -18,7 +18,9 @@ func TestSet(t *testing.T) { assert.True(t, s.Contains("key1")) assert.True(t, s.Contains("key2")) + assert.True(t, s.Contains("key1", "key2")) assert.False(t, s.Contains("key3")) + assert.False(t, s.Contains("key1", "key3")) assert.True(t, s.Remove("key2")) assert.False(t, s.Contains("key2")) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index efaa10624b..3ef11772dc 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -31,6 +31,7 @@ func NewFuncMap() template.FuncMap { "ctx": func() any { return nil }, // template context function "DumpVar": dumpVar, + "NIL": func() any { return nil }, // ----------------------------------------------------------------- // html/template related functions diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 9a7d3dfbf6..a5fdba3fde 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -788,19 +788,11 @@ func CompareDiff(ctx *context.Context) { if !nothingToCompare { // Setup information for new form. - retrieveRepoMetasForIssueWriter(ctx, ctx.Repo.Repository, true) + pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, nil, true) if ctx.Written() { return } - labelsData := retrieveRepoLabels(ctx, ctx.Repo.Repository, 0, true) - if ctx.Written() { - return - } - RetrieveRepoReviewers(ctx, ctx.Repo.Repository, nil, true) - if ctx.Written() { - return - } - _, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates, labelsData) + _, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates, pageMetaData) if len(templateErrs) > 0 { ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index a4e2fd8cea..72f89bd27d 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -431,7 +431,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt return 0 } - retrieveProjects(ctx, repo) + retrieveProjectsForIssueList(ctx, repo) if ctx.Written() { return } @@ -556,37 +556,147 @@ func renderMilestones(ctx *context.Context) { ctx.Data["ClosedMilestones"] = closedMilestones } -// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository -func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) { +type issueSidebarMilestoneData struct { + SelectedMilestoneID int64 + OpenMilestones []*issues_model.Milestone + ClosedMilestones []*issues_model.Milestone +} + +type issueSidebarAssigneesData struct { + SelectedAssigneeIDs string + CandidateAssignees []*user_model.User +} + +type IssuePageMetaData struct { + RepoLink string + Repository *repo_model.Repository + Issue *issues_model.Issue + IsPullRequest bool + CanModifyIssueOrPull bool + + ReviewersData *issueSidebarReviewersData + LabelsData *issueSidebarLabelsData + MilestonesData *issueSidebarMilestoneData + ProjectsData *issueSidebarProjectsData + AssigneesData *issueSidebarAssigneesData +} + +func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository, issue *issues_model.Issue, isPull bool) *IssuePageMetaData { + data := &IssuePageMetaData{ + RepoLink: ctx.Repo.RepoLink, + Repository: repo, + Issue: issue, + IsPullRequest: isPull, + + ReviewersData: &issueSidebarReviewersData{}, + LabelsData: &issueSidebarLabelsData{}, + MilestonesData: &issueSidebarMilestoneData{}, + ProjectsData: &issueSidebarProjectsData{}, + AssigneesData: &issueSidebarAssigneesData{}, + } + ctx.Data["IssuePageMetaData"] = data + + if isPull { + data.retrieveReviewersData(ctx) + if ctx.Written() { + return data + } + } + data.retrieveLabelsData(ctx) + if ctx.Written() { + return data + } + + data.CanModifyIssueOrPull = ctx.Repo.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived + if !data.CanModifyIssueOrPull { + return data + } + + data.retrieveAssigneesDataForIssueWriter(ctx) + if ctx.Written() { + return data + } + + data.retrieveMilestonesDataForIssueWriter(ctx) + if ctx.Written() { + return data + } + + data.retrieveProjectsDataForIssueWriter(ctx) + if ctx.Written() { + return data + } + + PrepareBranchList(ctx) + if ctx.Written() { + return data + } + + ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, isPull) + return data +} + +func (d *IssuePageMetaData) retrieveMilestonesDataForIssueWriter(ctx *context.Context) { var err error - ctx.Data["OpenMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ - RepoID: repo.ID, + if d.Issue != nil { + d.MilestonesData.SelectedMilestoneID = d.Issue.MilestoneID + } + d.MilestonesData.OpenMilestones, err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ + RepoID: d.Repository.ID, IsClosed: optional.Some(false), }) if err != nil { ctx.ServerError("GetMilestones", err) return } - ctx.Data["ClosedMilestones"], err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ - RepoID: repo.ID, + d.MilestonesData.ClosedMilestones, err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ + RepoID: d.Repository.ID, IsClosed: optional.Some(true), }) if err != nil { ctx.ServerError("GetMilestones", err) return } +} - assigneeUsers, err := repo_model.GetRepoAssignees(ctx, repo) +func (d *IssuePageMetaData) retrieveAssigneesDataForIssueWriter(ctx *context.Context) { + var err error + d.AssigneesData.CandidateAssignees, err = repo_model.GetRepoAssignees(ctx, d.Repository) if err != nil { ctx.ServerError("GetRepoAssignees", err) return } - ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers) - + d.AssigneesData.CandidateAssignees = shared_user.MakeSelfOnTop(ctx.Doer, d.AssigneesData.CandidateAssignees) + if d.Issue != nil { + _ = d.Issue.LoadAssignees(ctx) + ids := make([]string, 0, len(d.Issue.Assignees)) + for _, a := range d.Issue.Assignees { + ids = append(ids, strconv.FormatInt(a.ID, 10)) + } + d.AssigneesData.SelectedAssigneeIDs = strings.Join(ids, ",") + } + // FIXME: this is a tricky part which writes ctx.Data["Mentionable*"] handleTeamMentions(ctx) } -func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { +func retrieveProjectsForIssueList(ctx *context.Context, repo *repo_model.Repository) { + ctx.Data["OpenProjects"], ctx.Data["ClosedProjects"] = retrieveProjectsInternal(ctx, repo) +} + +type issueSidebarProjectsData struct { + SelectedProjectID int64 + OpenProjects []*project_model.Project + ClosedProjects []*project_model.Project +} + +func (d *IssuePageMetaData) retrieveProjectsDataForIssueWriter(ctx *context.Context) { + if d.Issue != nil && d.Issue.Project != nil { + d.ProjectsData.SelectedProjectID = d.Issue.Project.ID + } + d.ProjectsData.OpenProjects, d.ProjectsData.ClosedProjects = retrieveProjectsInternal(ctx, ctx.Repo.Repository) +} + +func retrieveProjectsInternal(ctx *context.Context, repo *repo_model.Repository) (open, closed []*project_model.Project) { // Distinguish whether the owner of the repository // is an individual or an organization repoOwnerType := project_model.TypeIndividual @@ -609,7 +719,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } closedProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{ ListOptions: db.ListOptionsAll, @@ -619,7 +729,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } } @@ -632,7 +742,7 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } openProjects = append(openProjects, openProjects2...) closedProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{ @@ -643,13 +753,11 @@ func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { }) if err != nil { ctx.ServerError("GetProjects", err) - return + return nil, nil } closedProjects = append(closedProjects, closedProjects2...) } - - ctx.Data["OpenProjects"] = openProjects - ctx.Data["ClosedProjects"] = closedProjects + return openProjects, closedProjects } // repoReviewerSelection items to bee shown @@ -665,10 +773,6 @@ type repoReviewerSelection struct { } type issueSidebarReviewersData struct { - Repository *repo_model.Repository - RepoOwnerName string - RepoLink string - IssueID int64 CanChooseReviewer bool OriginalReviews issues_model.ReviewList TeamReviewers []*repoReviewerSelection @@ -677,41 +781,44 @@ type issueSidebarReviewersData struct { } // RetrieveRepoReviewers find all reviewers of a repository. If issue is nil, it means the doer is creating a new PR. -func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, issue *issues_model.Issue, canChooseReviewer bool) { - data := &issueSidebarReviewersData{} - data.RepoLink = ctx.Repo.RepoLink - data.Repository = repo - data.RepoOwnerName = repo.OwnerName - data.CanChooseReviewer = canChooseReviewer +func (d *IssuePageMetaData) retrieveReviewersData(ctx *context.Context) { + data := d.ReviewersData + repo := d.Repository + if ctx.Doer != nil && ctx.IsSigned { + if d.Issue == nil { + data.CanChooseReviewer = true + } else { + data.CanChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, d.Issue) + } + } var posterID int64 var isClosed bool var reviews issues_model.ReviewList - if issue == nil { + if d.Issue == nil { posterID = ctx.Doer.ID } else { - posterID = issue.PosterID - if issue.OriginalAuthorID > 0 { + posterID = d.Issue.PosterID + if d.Issue.OriginalAuthorID > 0 { posterID = 0 // for migrated PRs, no poster ID } - data.IssueID = issue.ID - isClosed = issue.IsClosed || issue.PullRequest.HasMerged + isClosed = d.Issue.IsClosed || d.Issue.PullRequest.HasMerged - originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, issue.ID) + originalAuthorReviews, err := issues_model.GetReviewersFromOriginalAuthorsByIssueID(ctx, d.Issue.ID) if err != nil { ctx.ServerError("GetReviewersFromOriginalAuthorsByIssueID", err) return } data.OriginalReviews = originalAuthorReviews - reviews, err = issues_model.GetReviewsByIssueID(ctx, issue.ID) + reviews, err = issues_model.GetReviewsByIssueID(ctx, d.Issue.ID) if err != nil { ctx.ServerError("GetReviewersByIssueID", err) return } - if len(reviews) == 0 && !canChooseReviewer { + if len(reviews) == 0 && !data.CanChooseReviewer { return } } @@ -724,7 +831,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is reviewers []*user_model.User ) - if canChooseReviewer { + if data.CanChooseReviewer { var err error reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID) if err != nil { @@ -760,7 +867,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is tmp.ItemID = -review.ReviewerTeamID } - if canChooseReviewer { + if data.CanChooseReviewer { // Users who can choose reviewers can also remove review requests tmp.CanChange = true } else if ctx.Doer != nil && ctx.Doer.ID == review.ReviewerID && review.Type == issues_model.ReviewTypeRequest { @@ -770,7 +877,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is pullReviews = append(pullReviews, tmp) - if canChooseReviewer { + if data.CanChooseReviewer { if tmp.IsTeam { teamReviewersResult = append(teamReviewersResult, tmp) } else { @@ -811,7 +918,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is data.CurrentPullReviewers = currentPullReviewers } - if canChooseReviewer && reviewersResult != nil { + if data.CanChooseReviewer && reviewersResult != nil { preadded := len(reviewersResult) for _, reviewer := range reviewers { found := false @@ -839,7 +946,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is data.Reviewers = reviewersResult } - if canChooseReviewer && teamReviewersResult != nil { + if data.CanChooseReviewer && teamReviewersResult != nil { preadded := len(teamReviewersResult) for _, team := range teamReviewers { found := false @@ -866,15 +973,9 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is data.TeamReviewers = teamReviewersResult } - - ctx.Data["IssueSidebarReviewersData"] = data } type issueSidebarLabelsData struct { - Repository *repo_model.Repository - RepoLink string - IssueID int64 - IsPullRequest bool AllLabels []*issues_model.Label RepoLabels []*issues_model.Label OrgLabels []*issues_model.Label @@ -922,60 +1023,30 @@ func (d *issueSidebarLabelsData) SetSelectedLabelIDs(labelIDs []int64) { ) } -func retrieveRepoLabels(ctx *context.Context, repo *repo_model.Repository, issueID int64, isPull bool) *issueSidebarLabelsData { - labelsData := &issueSidebarLabelsData{ - Repository: repo, - RepoLink: ctx.Repo.RepoLink, - IssueID: issueID, - IsPullRequest: isPull, - } - ctx.Data["IssueSidebarLabelsData"] = labelsData +func (d *IssuePageMetaData) retrieveLabelsData(ctx *context.Context) { + repo := d.Repository + labelsData := d.LabelsData labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) - return nil + return } labelsData.RepoLabels = labels if repo.Owner.IsOrganization() { orgLabels, err := issues_model.GetLabelsByOrgID(ctx, repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{}) if err != nil { - return nil + return } labelsData.OrgLabels = orgLabels } labelsData.AllLabels = append(labelsData.AllLabels, labelsData.RepoLabels...) labelsData.AllLabels = append(labelsData.AllLabels, labelsData.OrgLabels...) - return labelsData -} - -// retrieveRepoMetasForIssueWriter finds some the meta information of a repository for an issue/pr writer -func retrieveRepoMetasForIssueWriter(ctx *context.Context, repo *repo_model.Repository, isPull bool) { - if !ctx.Repo.CanWriteIssuesOrPulls(isPull) { - return - } - - RetrieveRepoMilestonesAndAssignees(ctx, repo) - if ctx.Written() { - return - } - - retrieveProjects(ctx, repo) - if ctx.Written() { - return - } - - PrepareBranchList(ctx) - if ctx.Written() { - return - } - // Contains true if the user can create issue dependencies - ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx, ctx.Doer, isPull) } // Tries to load and set an issue template. The first return value indicates if a template was loaded. -func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string, labelsData *issueSidebarLabelsData) (bool, map[string]error) { +func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string, metaData *IssuePageMetaData) (bool, map[string]error) { commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) if err != nil { return false, nil @@ -1013,24 +1084,20 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles ctx.Data["TemplateFile"] = template.FileName } - labelsData.SetSelectedLabelNames(template.Labels) + metaData.LabelsData.SetSelectedLabelNames(template.Labels) - selectedAssigneeIDs := make([]int64, 0, len(template.Assignees)) selectedAssigneeIDStrings := make([]string, 0, len(template.Assignees)) - if userIDs, err := user_model.GetUserIDsByNames(ctx, template.Assignees, false); err == nil { + if userIDs, err := user_model.GetUserIDsByNames(ctx, template.Assignees, true); err == nil { for _, userID := range userIDs { - selectedAssigneeIDs = append(selectedAssigneeIDs, userID) selectedAssigneeIDStrings = append(selectedAssigneeIDStrings, strconv.FormatInt(userID, 10)) } } + metaData.AssigneesData.SelectedAssigneeIDs = strings.Join(selectedAssigneeIDStrings, ",") if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/ template.Ref = git.BranchPrefix + template.Ref } - ctx.Data["HasSelectedAssignee"] = len(selectedAssigneeIDs) > 0 - ctx.Data["assignee_ids"] = strings.Join(selectedAssigneeIDStrings, ",") - ctx.Data["SelectedAssigneeIDs"] = selectedAssigneeIDs ctx.Data["Reference"] = template.Ref ctx.Data["RefEndName"] = git.RefName(template.Ref).ShortName() return true, templateErrs @@ -1057,42 +1124,19 @@ func NewIssue(ctx *context.Context) { ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled upload.AddUploadContext(ctx, "comment") - milestoneID := ctx.FormInt64("milestone") - if milestoneID > 0 { - milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) - if err != nil { - log.Error("GetMilestoneByID: %d: %v", milestoneID, err) - } else { - ctx.Data["milestone_id"] = milestoneID - ctx.Data["Milestone"] = milestone - } + pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, nil, false) + if ctx.Written() { + return } - projectID := ctx.FormInt64("project") - if projectID > 0 && isProjectsEnabled { - project, err := project_model.GetProjectByID(ctx, projectID) - if err != nil { - log.Error("GetProjectByID: %d: %v", projectID, err) - } else if project.RepoID != ctx.Repo.Repository.ID { - log.Error("GetProjectByID: %d: %v", projectID, fmt.Errorf("project[%d] not in repo [%d]", project.ID, ctx.Repo.Repository.ID)) - } else { - ctx.Data["project_id"] = projectID - ctx.Data["Project"] = project - } - + pageMetaData.MilestonesData.SelectedMilestoneID = ctx.FormInt64("milestone") + pageMetaData.ProjectsData.SelectedProjectID = ctx.FormInt64("project") + if pageMetaData.ProjectsData.SelectedProjectID > 0 { if len(ctx.Req.URL.Query().Get("project")) > 0 { ctx.Data["redirect_after_creation"] = "project" } } - retrieveRepoMetasForIssueWriter(ctx, ctx.Repo.Repository, false) - if ctx.Written() { - return - } - labelsData := retrieveRepoLabels(ctx, ctx.Repo.Repository, 0, false) - if ctx.Written() { - return - } tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("GetTagNamesByRepoID", err) @@ -1101,7 +1145,7 @@ func NewIssue(ctx *context.Context) { ctx.Data["Tags"] = tags ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) - templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, labelsData) + templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, pageMetaData) for k, v := range errs { ret.TemplateErrors[k] = v } @@ -1196,8 +1240,16 @@ func DeleteIssue(ctx *context.Context) { ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.Link()), http.StatusSeeOther) } -// ValidateRepoMetas check and returns repository's meta information -func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull bool) (ret struct { +func toSet[ItemType any, KeyType comparable](slice []ItemType, keyFunc func(ItemType) KeyType) container.Set[KeyType] { + s := make(container.Set[KeyType]) + for _, item := range slice { + s.Add(keyFunc(item)) + } + return s +} + +// ValidateRepoMetasForNewIssue check and returns repository's meta information +func ValidateRepoMetasForNewIssue(ctx *context.Context, form forms.CreateIssueForm, isPull bool) (ret struct { LabelIDs, AssigneeIDs []int64 MilestoneID, ProjectID int64 @@ -1205,126 +1257,76 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull TeamReviewers []*organization.Team }, ) { - var ( - repo = ctx.Repo.Repository - err error - ) - - retrieveRepoMetasForIssueWriter(ctx, ctx.Repo.Repository, isPull) - if ctx.Written() { - return ret - } - labelsData := retrieveRepoLabels(ctx, ctx.Repo.Repository, 0, isPull) + pageMetaData := retrieveRepoIssueMetaData(ctx, ctx.Repo.Repository, nil, isPull) if ctx.Written() { return ret } - var labelIDs []int64 - // Check labels. - if len(form.LabelIDs) > 0 { - labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) - if err != nil { - return ret - } - labelsData.SetSelectedLabelIDs(labelIDs) + inputLabelIDs, _ := base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) + candidateLabels := toSet(pageMetaData.LabelsData.AllLabels, func(label *issues_model.Label) int64 { return label.ID }) + if len(inputLabelIDs) > 0 && !candidateLabels.Contains(inputLabelIDs...) { + ctx.NotFound("", nil) + return ret } + pageMetaData.LabelsData.SetSelectedLabelIDs(inputLabelIDs) - // Check milestone. - milestoneID := form.MilestoneID - if milestoneID > 0 { - milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID) - if err != nil { - ctx.ServerError("GetMilestoneByID", err) - return ret - } - if milestone.RepoID != repo.ID { - ctx.ServerError("GetMilestoneByID", err) - return ret - } - ctx.Data["Milestone"] = milestone - ctx.Data["milestone_id"] = milestoneID + allMilestones := append(slices.Clone(pageMetaData.MilestonesData.OpenMilestones), pageMetaData.MilestonesData.ClosedMilestones...) + candidateMilestones := toSet(allMilestones, func(milestone *issues_model.Milestone) int64 { return milestone.ID }) + if form.MilestoneID > 0 && !candidateMilestones.Contains(form.MilestoneID) { + ctx.NotFound("", nil) + return ret } + pageMetaData.MilestonesData.SelectedMilestoneID = form.MilestoneID - if form.ProjectID > 0 { - p, err := project_model.GetProjectByID(ctx, form.ProjectID) - if err != nil { - ctx.ServerError("GetProjectByID", err) - return ret - } - if p.RepoID != ctx.Repo.Repository.ID && p.OwnerID != ctx.Repo.Repository.OwnerID { - ctx.NotFound("", nil) - return ret - } - - ctx.Data["Project"] = p - ctx.Data["project_id"] = form.ProjectID + allProjects := append(slices.Clone(pageMetaData.ProjectsData.OpenProjects), pageMetaData.ProjectsData.ClosedProjects...) + candidateProjects := toSet(allProjects, func(project *project_model.Project) int64 { return project.ID }) + if form.ProjectID > 0 && !candidateProjects.Contains(form.ProjectID) { + ctx.NotFound("", nil) + return ret } + pageMetaData.ProjectsData.SelectedProjectID = form.ProjectID - // Check assignees - var assigneeIDs []int64 - if len(form.AssigneeIDs) > 0 { - assigneeIDs, err = base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) - if err != nil { - return ret - } - - // Check if the passed assignees actually exists and is assignable - for _, aID := range assigneeIDs { - assignee, err := user_model.GetUserByID(ctx, aID) - if err != nil { - ctx.ServerError("GetUserByID", err) - return ret - } - - valid, err := access_model.CanBeAssigned(ctx, assignee, repo, isPull) - if err != nil { - ctx.ServerError("CanBeAssigned", err) - return ret - } - - if !valid { - ctx.ServerError("canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name}) - return ret - } - } + candidateAssignees := toSet(pageMetaData.AssigneesData.CandidateAssignees, func(user *user_model.User) int64 { return user.ID }) + inputAssigneeIDs, _ := base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) + if len(inputAssigneeIDs) > 0 && !candidateAssignees.Contains(inputAssigneeIDs...) { + ctx.NotFound("", nil) + return ret } + pageMetaData.AssigneesData.SelectedAssigneeIDs = form.AssigneeIDs - // Keep the old assignee id thingy for compatibility reasons - if form.AssigneeID > 0 { - assigneeIDs = append(assigneeIDs, form.AssigneeID) - } - - // Check reviewers + // Check if the passed reviewers (user/team) actually exist var reviewers []*user_model.User var teamReviewers []*organization.Team - if isPull && len(form.ReviewerIDs) > 0 { - reviewerIDs, err := base.StringsToInt64s(strings.Split(form.ReviewerIDs, ",")) - if err != nil { - return ret + reviewerIDs, _ := base.StringsToInt64s(strings.Split(form.ReviewerIDs, ",")) + if isPull && len(reviewerIDs) > 0 { + userReviewersMap := map[int64]*user_model.User{} + teamReviewersMap := map[int64]*organization.Team{} + for _, r := range pageMetaData.ReviewersData.Reviewers { + userReviewersMap[r.User.ID] = r.User + } + for _, r := range pageMetaData.ReviewersData.TeamReviewers { + teamReviewersMap[r.Team.ID] = r.Team } - // Check if the passed reviewers (user/team) actually exist for _, rID := range reviewerIDs { - // negative reviewIDs represent team requests - if rID < 0 { - teamReviewer, err := organization.GetTeamByID(ctx, -rID) - if err != nil { - ctx.ServerError("GetTeamByID", err) + if rID < 0 { // negative reviewIDs represent team requests + team, ok := teamReviewersMap[-rID] + if !ok { + ctx.NotFound("", nil) return ret } - teamReviewers = append(teamReviewers, teamReviewer) - continue + teamReviewers = append(teamReviewers, team) + } else { + user, ok := userReviewersMap[rID] + if !ok { + ctx.NotFound("", nil) + return ret + } + reviewers = append(reviewers, user) } - - reviewer, err := user_model.GetUserByID(ctx, rID) - if err != nil { - ctx.ServerError("GetUserByID", err) - return ret - } - reviewers = append(reviewers, reviewer) } } - ret.LabelIDs, ret.AssigneeIDs, ret.MilestoneID, ret.ProjectID = labelIDs, assigneeIDs, milestoneID, form.ProjectID + ret.LabelIDs, ret.AssigneeIDs, ret.MilestoneID, ret.ProjectID = inputLabelIDs, inputAssigneeIDs, form.MilestoneID, form.ProjectID ret.Reviewers, ret.TeamReviewers = reviewers, teamReviewers return ret } @@ -1344,7 +1346,7 @@ func NewIssuePost(ctx *context.Context) { attachments []string ) - validateRet := ValidateRepoMetas(ctx, *form, false) + validateRet := ValidateRepoMetasForNewIssue(ctx, *form, false) if ctx.Written() { return } @@ -1619,37 +1621,11 @@ func ViewIssue(ctx *context.Context) { } } - retrieveRepoMetasForIssueWriter(ctx, repo, issue.IsPull) + pageMetaData := retrieveRepoIssueMetaData(ctx, repo, issue, issue.IsPull) if ctx.Written() { return } - labelsData := retrieveRepoLabels(ctx, repo, issue.ID, issue.IsPull) - if ctx.Written() { - return - } - labelsData.SetSelectedLabels(issue.Labels) - - // Check milestone and assignee. - if ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { - RetrieveRepoMilestonesAndAssignees(ctx, repo) - retrieveProjects(ctx, repo) - - if ctx.Written() { - return - } - } - - if issue.IsPull { - canChooseReviewer := false - if ctx.Doer != nil && ctx.IsSigned { - canChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, issue) - } - - RetrieveRepoReviewers(ctx, repo, issue, canChooseReviewer) - if ctx.Written() { - return - } - } + pageMetaData.LabelsData.SetSelectedLabels(issue.Labels) if ctx.IsSigned { // Update issue-user. diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index dd9671efbe..bb814eab6e 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1269,7 +1269,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { return } - validateRet := ValidateRepoMetas(ctx, *form, true) + validateRet := ValidateRepoMetasForNewIssue(ctx, *form, true) if ctx.Written() { return } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 83f2dd6caa..d27bbca894 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -451,7 +451,6 @@ type CreateIssueForm struct { Ref string `form:"ref"` MilestoneID int64 ProjectID int64 - AssigneeID int64 Content string Files []string AllowMaintainerEdit bool diff --git a/templates/repo/issue/milestone/select_menu.tmpl b/templates/repo/issue/milestone/select_menu.tmpl deleted file mode 100644 index 9b0492ce52..0000000000 --- a/templates/repo/issue/milestone/select_menu.tmpl +++ /dev/null @@ -1,38 +0,0 @@ -{{if or .OpenMilestones .ClosedMilestones}} - -
-{{end}} -
{{ctx.Locale.Tr "repo.issues.new.clear_milestone"}}
-{{if and (not .OpenMilestones) (not .ClosedMilestones)}} -
- {{ctx.Locale.Tr "repo.issues.new.no_items"}} -
-{{else}} - {{if .OpenMilestones}} -
-
- {{ctx.Locale.Tr "repo.issues.new.open_milestone"}} -
- {{range .OpenMilestones}} - - {{svg "octicon-milestone" 16 "tw-mr-1"}} - {{.Name}} - - {{end}} - {{end}} - {{if .ClosedMilestones}} -
-
- {{ctx.Locale.Tr "repo.issues.new.closed_milestone"}} -
- {{range .ClosedMilestones}} - - {{svg "octicon-milestone" 16 "tw-mr-1"}} - {{.Name}} - - {{end}} - {{end}} -{{end}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 65d359e9dc..ceaaebc4d5 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -49,142 +49,22 @@
{{template "repo/issue/branch_selector_field" $}} {{if .PageIsComparePull}} - {{template "repo/issue/sidebar/reviewer_list" $.IssueSidebarReviewersData}} + {{template "repo/issue/sidebar/reviewer_list" $.IssuePageMetaData}}
{{end}} - {{template "repo/issue/sidebar/label_list" $.IssueSidebarLabelsData}} - -
- - - -
- {{ctx.Locale.Tr "repo.issues.new.no_milestone"}} - -
- + {{template "repo/issue/sidebar/label_list" $.IssuePageMetaData}} + {{template "repo/issue/sidebar/milestone_list" $.IssuePageMetaData}} {{if .IsProjectsEnabled}} -
- - - -
- {{ctx.Locale.Tr "repo.issues.new.no_projects"}} - -
+ {{template "repo/issue/sidebar/project_list" $.IssuePageMetaData}} {{end}} -
- - -
- - {{ctx.Locale.Tr "repo.issues.new.no_assignees"}} - - -
+ {{template "repo/issue/sidebar/assignee_list" $.IssuePageMetaData}} + {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
-
-
- - -
+
+ +
{{end}}
diff --git a/templates/repo/issue/sidebar/assignee_list.tmpl b/templates/repo/issue/sidebar/assignee_list.tmpl index 260f7c5be4..bee6123e52 100644 --- a/templates/repo/issue/sidebar/assignee_list.tmpl +++ b/templates/repo/issue/sidebar/assignee_list.tmpl @@ -1,46 +1,35 @@ +{{$pageMeta := .}} +{{$data := .AssigneesData}} +{{$issueAssignees := NIL}}{{if $pageMeta.Issue}}{{$issueAssignees = $pageMeta.Issue.Assignees}}{{end}}
- -