From 6827575af8c1a0ca599a8d16a77416a144ab004e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 2 Apr 2026 20:12:14 -0700 Subject: [PATCH] refactor --- models/project/column.go | 76 +++++---------- models/project/column_list.go | 42 +++++++++ models/project/column_list_test.go | 49 ++++++++++ models/project/column_test.go | 42 +-------- routers/api/v1/repo/project.go | 4 +- routers/web/org/projects.go | 2 +- routers/web/repo/issue_page_meta.go | 97 +++++-------------- routers/web/repo/projects.go | 2 +- tests/integration/project_test.go | 138 +++------------------------- 9 files changed, 158 insertions(+), 294 deletions(-) create mode 100644 models/project/column_list.go create mode 100644 models/project/column_list_test.go diff --git a/models/project/column.go b/models/project/column.go index 31f541ae8b..9c9abb4599 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -185,7 +185,7 @@ func deleteColumnByID(ctx context.Context, columnID int64) error { return err } - if err = column.moveIssuesToAnotherColumn(ctx, defaultColumn); err != nil { + if err = moveIssuesToAnotherColumn(ctx, column, defaultColumn); err != nil { return err } @@ -257,26 +257,12 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) { return columns, nil } -// CountColumns returns the total number of columns for a project -func (p *Project) CountColumns(ctx context.Context) (int64, error) { - return db.GetEngine(ctx).Where("project_id=?", p.ID).Count(&Column{}) -} - -// GetColumnsPaginated fetches a page of columns for a project -func (p *Project) GetColumnsPaginated(ctx context.Context, opts db.ListOptions) (ColumnList, error) { - columns := make([]*Column, 0, opts.PageSize) - if err := db.SetSessionPagination(db.GetEngine(ctx), &opts). - Where("project_id=?", p.ID). - OrderBy("sorting, id"). - Find(&columns); err != nil { - return nil, err - } - return columns, nil -} - -// getDefaultColumn return default column and ensure only one exists -func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { +// getDefaultColumnWithFallback return default column if one exists +// otherwise return the first column by sorting and set it as default column +func (p *Project) getDefaultColumnWithFallback(ctx context.Context) (*Column, error) { var column Column + + // try to find a column "default=true" has, err := db.GetEngine(ctx). Where("project_id=? AND `default` = ?", p.ID, true). Desc("id").Get(&column) @@ -287,23 +273,9 @@ func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { if has { return &column, nil } - return nil, ErrProjectColumnNotExist{ColumnID: 0} -} -// MustDefaultColumn returns the default column for a project. -// If one exists, it is returned -// If none exists, the first column will be elevated to the default column of this project -func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { - c, err := p.getDefaultColumn(ctx) - if err != nil && !IsErrProjectColumnNotExist(err) { - return nil, err - } - if c != nil { - return c, nil - } - - var column Column - has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column) + // try to find the first column by sorting + has, err = db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column) if err != nil { return nil, err } @@ -315,8 +287,24 @@ func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { return &column, nil } + return nil, ErrProjectColumnNotExist{ColumnID: 0} +} + +// MustDefaultColumn returns the default column for a project. +// If one exists, it is returned +// If none exists, the first column will be elevated to the default column of this project +// If there is no column, it creates a default column and returns it +func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { + c, err := p.getDefaultColumnWithFallback(ctx) + if err != nil && !IsErrProjectColumnNotExist(err) { + return nil, err + } + if c != nil { + return c, nil + } + // create a default column if none is found - column = Column{ + column := Column{ ProjectID: p.ID, Default: true, Title: "Uncategorized", @@ -349,20 +337,6 @@ func SetDefaultColumn(ctx context.Context, projectID, columnID int64) error { }) } -// UpdateColumnSorting update project column sorting -func UpdateColumnSorting(ctx context.Context, cl ColumnList) error { - return db.WithTx(ctx, func(ctx context.Context) error { - for i := range cl { - if _, err := db.GetEngine(ctx).ID(cl[i].ID).Cols( - "sorting", - ).Update(cl[i]); err != nil { - return err - } - } - return nil - }) -} - func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { columns := make([]*Column, 0, 5) if len(columnsIDs) == 0 { diff --git a/models/project/column_list.go b/models/project/column_list.go new file mode 100644 index 0000000000..b3041a15e1 --- /dev/null +++ b/models/project/column_list.go @@ -0,0 +1,42 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +import ( + "context" + + "code.gitea.io/gitea/models/db" +) + +// CountColumns returns the total number of columns for a project +func CountProjectColumns(ctx context.Context, projectID int64) (int64, error) { + return db.GetEngine(ctx).Where("project_id=?", projectID).Count(&Column{}) +} + +// GetProjectColumns returns a list of columns for a project with pagination +func GetProjectColumns(ctx context.Context, projectID int64, opts db.ListOptions) (ColumnList, error) { + columns := make([]*Column, 0, opts.PageSize) + s := db.GetEngine(ctx).Where("project_id=?", projectID).OrderBy("sorting, id") + if !opts.IsListAll() { + db.SetSessionPagination(s, &opts) + } + if err := s.Find(&columns); err != nil { + return nil, err + } + return columns, nil +} + +func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { + columns := make([]*Column, 0, 5) + if len(columnsIDs) == 0 { + return columns, nil + } + if err := db.GetEngine(ctx). + Where("project_id =?", projectID). + In("id", columnsIDs). + OrderBy("sorting").Find(&columns); err != nil { + return nil, err + } + return columns, nil +} diff --git a/models/project/column_list_test.go b/models/project/column_list_test.go new file mode 100644 index 0000000000..1c296dd64d --- /dev/null +++ b/models/project/column_list_test.go @@ -0,0 +1,49 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestCountColumns(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + project, err := GetProjectByID(t.Context(), 1) + assert.NoError(t, err) + + count, err := CountProjectColumns(t.Context(), project.ID) + assert.NoError(t, err) + assert.EqualValues(t, 3, count) +} + +func TestGetColumnsPaginated(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + project, err := GetProjectByID(t.Context(), 1) + assert.NoError(t, err) + + // Page 1, limit 2 — returns first 2 columns + page1, err := GetProjectColumns(t.Context(), project.ID, db.ListOptions{Page: 1, PageSize: 2}) + assert.NoError(t, err) + assert.Len(t, page1, 2) + + // Page 2, limit 2 — returns remaining column + page2, err := GetProjectColumns(t.Context(), project.ID, db.ListOptions{Page: 2, PageSize: 2}) + assert.NoError(t, err) + assert.Len(t, page2, 1) + + // Page 1 and page 2 together cover all columns with no overlap + allIDs := make(map[int64]bool) + for _, c := range append(page1, page2...) { + assert.False(t, allIDs[c.ID], "duplicate column ID %d across pages", c.ID) + allIDs[c.ID] = true + } + assert.Len(t, allIDs, 3) +} diff --git a/models/project/column_test.go b/models/project/column_test.go index 13cdced642..d619698965 100644 --- a/models/project/column_test.go +++ b/models/project/column_test.go @@ -80,7 +80,7 @@ func Test_MoveColumnsOnProject(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) project1 := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1}) - columns, err := project1.GetColumns(t.Context()) + columns, err := GetProjectColumns(t.Context(), project1.ID, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, columns, 3) assert.EqualValues(t, 0, columns[0].Sorting) // even if there is no default sorting, the code should also work @@ -94,7 +94,7 @@ func Test_MoveColumnsOnProject(t *testing.T) { }) assert.NoError(t, err) - columnsAfter, err := project1.GetColumns(t.Context()) + columnsAfter, err := GetProjectColumns(t.Context(), project1.ID, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, columnsAfter, 3) assert.Equal(t, columns[1].ID, columnsAfter[0].ID) @@ -106,7 +106,7 @@ func Test_NewColumn(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) project1 := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1}) - columns, err := project1.GetColumns(t.Context()) + columns, err := GetProjectColumns(t.Context(), project1.ID, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, columns, 3) @@ -124,39 +124,3 @@ func Test_NewColumn(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "maximum number of columns reached") } - -func TestCountColumns(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - project, err := GetProjectByID(t.Context(), 1) - assert.NoError(t, err) - - count, err := project.CountColumns(t.Context()) - assert.NoError(t, err) - assert.EqualValues(t, 3, count) -} - -func TestGetColumnsPaginated(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - project, err := GetProjectByID(t.Context(), 1) - assert.NoError(t, err) - - // Page 1, limit 2 — returns first 2 columns - page1, err := project.GetColumnsPaginated(t.Context(), db.ListOptions{Page: 1, PageSize: 2}) - assert.NoError(t, err) - assert.Len(t, page1, 2) - - // Page 2, limit 2 — returns remaining column - page2, err := project.GetColumnsPaginated(t.Context(), db.ListOptions{Page: 2, PageSize: 2}) - assert.NoError(t, err) - assert.Len(t, page2, 1) - - // Page 1 and page 2 together cover all columns with no overlap - allIDs := make(map[int64]bool) - for _, c := range append(page1, page2...) { - assert.False(t, allIDs[c.ID], "duplicate column ID %d across pages", c.ID) - allIDs[c.ID] = true - } - assert.Len(t, allIDs, 3) -} diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 6ae5554bee..619ecde57e 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -385,14 +385,14 @@ func ListProjectColumns(ctx *context.APIContext) { return } - total, err := project.CountColumns(ctx) + total, err := project_model.CountProjectColumns(ctx, project.ID) if err != nil { ctx.APIErrorInternal(err) return } listOptions := utils.GetListOptions(ctx) - columns, err := project.GetColumnsPaginated(ctx, listOptions) + columns, err := project_model.GetProjectColumns(ctx, project.ID, listOptions) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index ae32be0575..f3cc970162 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -309,7 +309,7 @@ func ViewProject(ctx *context.Context) { return } - columns, err := project.GetColumns(ctx) + columns, err := project_model.GetProjectColumns(ctx, project.ID, db.ListOptionsAll) if err != nil { ctx.ServerError("GetProjectColumns", err) return diff --git a/routers/web/repo/issue_page_meta.go b/routers/web/repo/issue_page_meta.go index 428171dd0e..635ae8ef9c 100644 --- a/routers/web/repo/issue_page_meta.go +++ b/routers/web/repo/issue_page_meta.go @@ -33,15 +33,12 @@ type issueSidebarAssigneesData struct { CandidateAssignees []*user_model.User } -type issueSidebarProjectCardData struct { - Project *project_model.Project - Columns []*project_model.Column - SelectedColumn *project_model.Column -} - type issueSidebarProjectsData struct { - SelectedProjectIDs []int64 - ProjectCards []*issueSidebarProjectCardData + SelectedProjectIDs []int64 // TODO: support multiple projects in the future + + // the "selected" fields are only valid when len(SelectedProjectIDs)==1 + SelectedProjectColumns []*project_model.Column + SelectedProjectColumn *project_model.Column OpenProjects []*project_model.Project ClosedProjects []*project_model.Project @@ -110,7 +107,7 @@ func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository // A reader(creator) could update some meta (eg: target branch), but can't change assignees anymore. // For non-creator users, only writers could update some meta (eg: assignees, milestone, project) // Need to clarify the logic and add some tests in the future - data.CanModifyIssueOrPull = ctx.Repo.Permission.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived + data.CanModifyIssueOrPull = ctx.Repo.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived if !data.CanModifyIssueOrPull { return data } @@ -171,80 +168,34 @@ func (d *IssuePageMetaData) retrieveAssigneesData(ctx *context.Context) { ctx.Data["Assignees"] = d.AssigneesData.CandidateAssignees } -func (d *IssuePageMetaData) retrieveProjectCardsForExistingIssue(ctx *context.Context) { - if err := d.Issue.LoadProjects(ctx); err != nil { - ctx.ServerError("LoadProjects", err) - return - } - - // Load column mappings for all projects - projectColumnMap, err := d.Issue.ProjectColumnMap(ctx) - if err != nil { - ctx.ServerError("ProjectColumnMap", err) - return - } - - // Build project cards for each project - d.ProjectsData.ProjectCards = make([]*issueSidebarProjectCardData, 0, len(d.Issue.Projects)) - for _, project := range d.Issue.Projects { - columns, err := project.GetColumns(ctx) - if err != nil { - ctx.ServerError("GetProjectColumns", err) - return - } - - var selectedColumn *project_model.Column - columnID := projectColumnMap[project.ID] - for _, col := range columns { - if col.ID == columnID { - selectedColumn = col - break - } - } - - if selectedColumn == nil { - selectedColumn, err = project.MustDefaultColumn(ctx) - if err != nil { - ctx.ServerError("MustDefaultColumn", err) - return - } - } - d.ProjectsData.ProjectCards = append(d.ProjectsData.ProjectCards, &issueSidebarProjectCardData{ - Project: project, - Columns: columns, - SelectedColumn: selectedColumn, - }) - } - d.ProjectsData.SelectedProjectIDs = make([]int64, 0, len(d.ProjectsData.ProjectCards)) - for _, card := range d.ProjectsData.ProjectCards { - d.ProjectsData.SelectedProjectIDs = append(d.ProjectsData.SelectedProjectIDs, card.Project.ID) - } -} - func (d *IssuePageMetaData) retrieveProjectData(ctx *context.Context) { - if d.Issue == nil { + if d.Issue == nil || d.Issue.Project == nil { return } - d.retrieveProjectCardsForExistingIssue(ctx) -} - -func (d *IssuePageMetaData) SetSelectedProjectIDs(ids []int64) { - allProjects := map[int64]*project_model.Project{} - for _, p := range d.ProjectsData.OpenProjects { - allProjects[p.ID] = p + d.ProjectsData.SelectedProjectIDs = []int64{d.Issue.Project.ID} + columns, err := project_model.GetProjectColumns(ctx, d.Issue.Project.ID, db.ListOptionsAll) + if err != nil { + ctx.ServerError("GetProjectColumns", err) + return } - for _, p := range d.ProjectsData.ClosedProjects { - allProjects[p.ID] = p + d.ProjectsData.SelectedProjectColumns = columns + columnID, err := d.Issue.ProjectColumnID(ctx) + if err != nil { + ctx.ServerError("ProjectColumnID", err) + return } - for _, id := range ids { - if project, ok := allProjects[id]; ok { - d.ProjectsData.ProjectCards = append(d.ProjectsData.ProjectCards, &issueSidebarProjectCardData{Project: project}) + for _, col := range columns { + if col.ID == columnID { + d.ProjectsData.SelectedProjectColumn = col + break } } - d.ProjectsData.SelectedProjectIDs = ids } func (d *IssuePageMetaData) retrieveProjectsDataForIssueWriter(ctx *context.Context) { + if d.Issue != nil && d.Issue.Project != nil { + d.ProjectsData.SelectedProjectIDs = []int64{d.Issue.Project.ID} + } d.ProjectsData.OpenProjects, d.ProjectsData.ClosedProjects = retrieveProjectsInternal(ctx, ctx.Repo.Repository) } diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 8690e75463..e623bff752 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -293,7 +293,7 @@ func ViewProject(ctx *context.Context) { return } - columns, err := project.GetColumns(ctx) + columns, err := project_model.GetProjectColumns(ctx, project.ID, db.ListOptionsAll) if err != nil { ctx.ServerError("GetProjectColumns", err) return diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 516f2acec7..46254ea44e 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -7,9 +7,9 @@ import ( "fmt" "net/http" "strconv" - "strings" "testing" + "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" @@ -60,7 +60,7 @@ func TestMoveRepoProjectColumns(t *testing.T) { assert.NoError(t, err) } - columns, err := project1.GetColumns(t.Context()) + columns, err := project_model.GetProjectColumns(t.Context(), project1.ID, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, columns, 3) assert.EqualValues(t, 0, columns[0].Sorting) @@ -80,7 +80,7 @@ func TestMoveRepoProjectColumns(t *testing.T) { }) sess.MakeRequest(t, req, http.StatusOK) - columnsAfter, err := project1.GetColumns(t.Context()) + columnsAfter, err := project_model.GetProjectColumns(t.Context(), project1.ID, db.ListOptionsAll) assert.NoError(t, err) assert.Len(t, columnsAfter, 3) assert.Equal(t, columns[1].ID, columnsAfter[0].ID) @@ -90,128 +90,6 @@ func TestMoveRepoProjectColumns(t *testing.T) { assert.NoError(t, project_model.DeleteProjectByID(t.Context(), project1.ID)) } -func TestUpdateIssueProject(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - sess := loginUser(t, "user2") - - t.Run("AssignAndRemove", func(t *testing.T) { - req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects?issue_ids=2", map[string]string{ - "id": "1", - }) - sess.MakeRequest(t, req, http.StatusOK) - unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: 2, ProjectID: 1}) - - req = NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects?issue_ids=2", map[string]string{ - "id": "", - }) - sess.MakeRequest(t, req, http.StatusOK) - unittest.AssertNotExistsBean(t, &project_model.ProjectIssue{IssueID: 2, ProjectID: 1}) - }) -} - -func TestUpdateIssueProjectColumn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // fixture: issue 3 is in project 1 of repo user2/repo1, column "In Progress" (id=2) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) - assert.EqualValues(t, 1, issue.RepoID) - - sess := loginUser(t, "user2") - - t.Run("MoveColumn", func(t *testing.T) { - req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{ - "issue_id": "3", - "id": "3", - }) - sess.MakeRequest(t, req, http.StatusOK) - - pi := unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: 3}) - assert.EqualValues(t, 3, pi.ProjectColumnID) - }) - - t.Run("InvalidIssueID", func(t *testing.T) { - req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{ - "issue_id": "0", - "id": "3", - }) - sess.MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("WrongRepo", func(t *testing.T) { - req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{ - "issue_id": "6", - "id": "3", - }) - sess.MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("WrongProject", func(t *testing.T) { - project2 := project_model.Project{ - Title: "second project on repo1", - RepoID: 1, - Type: project_model.TypeRepository, - TemplateType: project_model.TemplateTypeNone, - } - require.NoError(t, project_model.NewProject(t.Context(), &project2)) - require.NoError(t, project_model.NewColumn(t.Context(), &project_model.Column{ - Title: "other column", - ProjectID: project2.ID, - })) - columns, err := project2.GetColumns(t.Context()) - require.NoError(t, err) - require.NotEmpty(t, columns) - - req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{ - "issue_id": "1", - "id": strconv.FormatInt(columns[0].ID, 10), - }) - sess.MakeRequest(t, req, http.StatusNotFound) - }) -} - -func TestIssueSidebarProjectColumn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // fixture: issue 5 (index=4) is in project 1 of repo user2/repo1, column "Done" (id=3) - sess := loginUser(t, "user2") - - req := NewRequest(t, "GET", "/user2/repo1/issues/4") - resp := sess.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - - cards := htmlDoc.Find(".flex-relaxed-list > .item.sidebar-project-card") - assert.Equal(t, 1, cards.Length()) - - title := cards.Find("a span.gt-ellipsis") - assert.Contains(t, strings.TrimSpace(title.Text()), "First project") - - columnCombo := cards.Find(".issue-sidebar-combo.sidebar-project-column-combo") - assert.Equal(t, 1, columnCombo.Length()) - - defaultItem := columnCombo.Find(`.menu .item[data-value="1"]`) - assert.Equal(t, 1, defaultItem.Length()) - - inProgressItem := columnCombo.Find(`.menu .item[data-value="2"]`) - assert.Equal(t, 1, inProgressItem.Length()) - doneItem := columnCombo.Find(`.menu .item[data-value="3"]`) - assert.Equal(t, 1, doneItem.Length()) - - comboVal, exists := columnCombo.Find("input.combo-value").Attr("value") - assert.True(t, exists) - assert.Equal(t, "3", comboVal) - - req = NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects?issue_ids=5", map[string]string{"id": ""}) - sess.MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "GET", "/user2/repo1/issues/4") - resp = sess.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - cards = htmlDoc.Find(".flex-relaxed-list > .item.sidebar-project-card") - assert.Equal(t, 0, cards.Length()) -} - // getProjectIssueIDs returns the set of issue IDs rendered as cards on the project board page. func getProjectIssueIDs(t *testing.T, htmlDoc *HTMLDoc) map[int64]struct{} { t.Helper() @@ -311,9 +189,15 @@ func TestOrgProjectFilterByMilestone(t *testing.T) { } require.NoError(t, project_model.NewProject(t.Context(), &project)) + // Get the default column + columns, err := project_model.GetProjectColumns(t.Context(), project.ID, db.ListOptionsAll) + require.NoError(t, err) + require.NotEmpty(t, columns) + defaultColumnID := columns[0].ID + // Add issues to the project - require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue16, user1, []int64{project.ID})) - require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue17, user1, []int64{project.ID})) + require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue16, user1, project.ID, defaultColumnID)) + require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue17, user1, project.ID, defaultColumnID)) sess := loginUser(t, "user1") projectURL := fmt.Sprintf("/org3/-/projects/%d", project.ID)