diff --git a/models/issues/milestone_list.go b/models/issues/milestone_list.go index 3b4bdc6e9f6..6cd5a63a1b3 100644 --- a/models/issues/milestone_list.go +++ b/models/issues/milestone_list.go @@ -39,12 +39,13 @@ func (milestones MilestoneList) SplitByOpenClosed() (open, closed MilestoneList) // FindMilestoneOptions contain options to get milestones type FindMilestoneOptions struct { db.ListOptions - RepoID int64 - IsClosed optional.Option[bool] - Name string - SortType string - RepoCond builder.Cond - RepoIDs []int64 + RepoID int64 + IsClosed optional.Option[bool] + Name string + SortType string + RepoCond builder.Cond + RepoIDs []int64 + ProjectID int64 } func (opts FindMilestoneOptions) ToConds() builder.Cond { @@ -64,6 +65,11 @@ func (opts FindMilestoneOptions) ToConds() builder.Cond { if len(opts.Name) != 0 { cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name)) } + if opts.ProjectID != 0 { + cond = cond.And(builder.In("id", builder.Select("issue.milestone_id").From("issue"). + InnerJoin("project_issue", "project_issue.issue_id = issue.id"). + Where(builder.Eq{"project_issue.project_id": opts.ProjectID}))) + } return cond } diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 4cf346aef84..0554d18bab2 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -438,7 +438,8 @@ func ViewProject(ctx *context.Context) { accessCond, ) milestones, err = db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ - RepoCond: repoCond, + RepoCond: repoCond, + ProjectID: project.ID, }) if err != nil { ctx.ServerError("GetOrgMilestones", err) diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go index 0f12c478c6f..3bda048a1d6 100644 --- a/routers/web/repo/issue_list.go +++ b/routers/web/repo/issue_list.go @@ -380,10 +380,11 @@ func prepareIssueFilterExclusiveOrderScopes(ctx *context.Context, allLabels []*i ctx.Data["ExclusiveLabelScopes"] = scopes } -func renderMilestones(ctx *context.Context) { +func renderMilestones(ctx *context.Context, projectID int64) { // Get milestones milestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ - RepoID: ctx.Repo.Repository.ID, + RepoID: ctx.Repo.Repository.ID, + ProjectID: projectID, }) if err != nil { ctx.ServerError("GetAllRepoMilestones", err) @@ -679,7 +680,7 @@ func Issues(ctx *context.Context) { return } - renderMilestones(ctx) + renderMilestones(ctx, 0) if ctx.Written() { return } diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index ab1ec7bbd64..48990320e87 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -412,7 +412,7 @@ func ViewProject(ctx *context.Context) { ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers) ctx.Data["AssigneeID"] = assigneeID - renderMilestones(ctx) + renderMilestones(ctx, project.ID) if ctx.Written() { return } diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 0ace73ea4ac..6ef3cb6157d 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -228,6 +228,12 @@ func getProjectIssueIDs(t *testing.T, htmlDoc *HTMLDoc) map[int64]struct{} { return ids } +func getProjectMilestoneNames(htmlDoc *HTMLDoc) []string { + return htmlDoc.Find(".list-header-filters a:has(.octicon-milestone)").Map(func(_ int, s *goquery.Selection) string { + return strings.TrimSpace(s.Text()) + }) +} + func TestRepoProjectFilterByMilestone(t *testing.T) { // Project 1 is on repo 1 (user2/repo1) and has issues: // issue 1 (milestone_id=0), issue 2 (milestone_id=1), issue 3 (milestone_id=3), issue 5 (milestone_id=0) @@ -245,6 +251,7 @@ func TestRepoProjectFilterByMilestone(t *testing.T) { assert.Contains(t, issueIDs, int64(2)) assert.Contains(t, issueIDs, int64(3)) assert.Contains(t, issueIDs, int64(5)) + assert.Equal(t, []string{"milestone1", "milestone3"}, getProjectMilestoneNames(htmlDoc)) }) t.Run("FilterByMilestone", func(t *testing.T) { @@ -300,6 +307,7 @@ func TestOrgProjectFilterByMilestone(t *testing.T) { Name: "org-test-milestone", } require.NoError(t, issues_model.NewMilestone(t.Context(), milestone)) + require.NoError(t, issues_model.NewMilestone(t.Context(), &issues_model.Milestone{RepoID: repo.ID, Name: "unlinked-milestone"})) issue16.MilestoneID = milestone.ID require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue16, "milestone_id")) @@ -327,6 +335,7 @@ func TestOrgProjectFilterByMilestone(t *testing.T) { issueIDs := getProjectIssueIDs(t, htmlDoc) assert.Contains(t, issueIDs, issue16.ID) assert.Contains(t, issueIDs, issue17.ID) + assert.Equal(t, []string{milestone.Name}, getProjectMilestoneNames(htmlDoc)) }) t.Run("FilterByMilestone", func(t *testing.T) {