refactor
This commit is contained in:
@@ -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 {
|
||||
|
||||
42
models/project/column_list.go
Normal file
42
models/project/column_list.go
Normal file
@@ -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
|
||||
}
|
||||
49
models/project/column_list_test.go
Normal file
49
models/project/column_list_test.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
func (d *IssuePageMetaData) retrieveProjectData(ctx *context.Context) {
|
||||
if d.Issue == nil || d.Issue.Project == nil {
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
var selectedColumn *project_model.Column
|
||||
columnID := projectColumnMap[project.ID]
|
||||
d.ProjectsData.SelectedProjectColumns = columns
|
||||
columnID, err := d.Issue.ProjectColumnID(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("ProjectColumnID", err)
|
||||
return
|
||||
}
|
||||
for _, col := range columns {
|
||||
if col.ID == columnID {
|
||||
selectedColumn = col
|
||||
d.ProjectsData.SelectedProjectColumn = 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 {
|
||||
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
|
||||
}
|
||||
for _, p := range d.ProjectsData.ClosedProjects {
|
||||
allProjects[p.ID] = p
|
||||
}
|
||||
for _, id := range ids {
|
||||
if project, ok := allProjects[id]; ok {
|
||||
d.ProjectsData.ProjectCards = append(d.ProjectsData.ProjectCards, &issueSidebarProjectCardData{Project: project})
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user