From 43729085acd711558f6f6bb0f3a86972686bedfe Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 29 Mar 2024 01:24:50 -0600 Subject: [PATCH] Rename project board -> column to make the UI less confusion --- models/activities/statistic.go | 4 +- models/issues/issue_project.go | 28 +- models/issues/issue_search.go | 14 +- models/migrations/v1_22/v293_test.go | 24 +- models/project/board.go | 320 ------------------ models/project/board_test.go | 44 --- models/project/column.go | 287 ++++++++++++++++ models/project/column_test.go | 44 +++ models/project/issue.go | 12 +- models/project/project.go | 89 ++--- models/project/project_test.go | 14 +- models/project/view_board.go | 45 +++ modules/indexer/issues/bleve/bleve.go | 4 +- modules/indexer/issues/db/options.go | 2 +- modules/indexer/issues/dboptions.go | 2 +- .../issues/elasticsearch/elasticsearch.go | 4 +- modules/indexer/issues/indexer_test.go | 4 +- modules/indexer/issues/internal/model.go | 4 +- .../indexer/issues/internal/tests/tests.go | 4 +- .../indexer/issues/meilisearch/meilisearch.go | 4 +- modules/indexer/issues/util.go | 2 +- modules/metrics/collector.go | 2 +- routers/web/org/projects.go | 54 +-- routers/web/repo/issue.go | 12 +- routers/web/repo/projects.go | 58 ++-- routers/web/web.go | 12 +- services/forms/repo_form.go | 36 +- 27 files changed, 545 insertions(+), 584 deletions(-) delete mode 100644 models/project/board.go delete mode 100644 models/project/board_test.go create mode 100644 models/project/column.go create mode 100644 models/project/column_test.go create mode 100644 models/project/view_board.go diff --git a/models/activities/statistic.go b/models/activities/statistic.go index d1a459d1b2..ff81ad78a1 100644 --- a/models/activities/statistic.go +++ b/models/activities/statistic.go @@ -30,7 +30,7 @@ type Statistic struct { Mirror, Release, AuthSource, Webhook, Milestone, Label, HookTask, Team, UpdateTask, Project, - ProjectBoard, Attachment, + ProjectColumn, Attachment, Branches, Tags, CommitStatus int64 IssueByLabel []IssueByLabelCount IssueByRepository []IssueByRepositoryCount @@ -115,6 +115,6 @@ func GetStatistic(ctx context.Context) (stats Statistic) { stats.Counter.Team, _ = e.Count(new(organization.Team)) stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment)) stats.Counter.Project, _ = e.Count(new(project_model.Project)) - stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board)) + stats.Counter.ProjectColumn, _ = e.Count(new(project_model.Column)) return stats } diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index 907a5a17b9..330175afe3 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -37,22 +37,22 @@ func (issue *Issue) projectID(ctx context.Context) int64 { return ip.ProjectID } -// ProjectBoardID return project board id if issue was assigned to one -func (issue *Issue) ProjectBoardID(ctx context.Context) int64 { +// ProjectColumnID return project column id if issue was assigned to one +func (issue *Issue) ProjectColumnID(ctx context.Context) int64 { var ip project_model.ProjectIssue has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip) if err != nil || !has { return 0 } - return ip.ProjectBoardID + return ip.ProjectColumnID } -// LoadIssuesFromBoard load issues assigned to this board -func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) { +// LoadIssuesFromColumn load issues assigned to this board +func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueList, error) { issueList, err := Issues(ctx, &IssuesOptions{ - ProjectBoardID: b.ID, - ProjectID: b.ProjectID, - SortType: "project-column-sorting", + ProjectColumnID: b.ID, + ProjectID: b.ProjectID, + SortType: "project-column-sorting", }) if err != nil { return nil, err @@ -60,9 +60,9 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList if b.Default { issues, err := Issues(ctx, &IssuesOptions{ - ProjectBoardID: db.NoConditionID, - ProjectID: b.ProjectID, - SortType: "project-column-sorting", + ProjectColumnID: db.NoConditionID, + ProjectID: b.ProjectID, + SortType: "project-column-sorting", }) if err != nil { return nil, err @@ -77,11 +77,11 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList return issueList, nil } -// LoadIssuesFromBoardList load issues assigned to the boards -func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (map[int64]IssueList, error) { +// LoadIssuesFromColumnList load issues assigned to the boards +func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList) (map[int64]IssueList, error) { issuesMap := make(map[int64]IssueList, len(bs)) for i := range bs { - il, err := LoadIssuesFromBoard(ctx, bs[i]) + il, err := LoadIssuesFromColumn(ctx, bs[i]) if err != nil { return nil, err } diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 921dd9973e..491def1229 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -33,7 +33,7 @@ type IssuesOptions struct { //nolint SubscriberID int64 MilestoneIDs []int64 ProjectID int64 - ProjectBoardID int64 + ProjectColumnID int64 IsClosed optional.Option[bool] IsPull optional.Option[bool] LabelIDs []int64 @@ -169,12 +169,12 @@ func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sessio return sess } -func applyProjectBoardCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { - // opts.ProjectBoardID == 0 means all project boards, +func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { + // opts.ProjectColumnID == 0 means all project columns, // do not need to apply any condition - if opts.ProjectBoardID > 0 { - sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID})) - } else if opts.ProjectBoardID == db.NoConditionID { + if opts.ProjectColumnID > 0 { + sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectColumnID})) + } else if opts.ProjectColumnID == db.NoConditionID { sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0})) } return sess @@ -246,7 +246,7 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session { applyProjectCondition(sess, opts) - applyProjectBoardCondition(sess, opts) + applyProjectColumnCondition(sess, opts) if opts.IsPull.Has() { sess.And("issue.is_pull=?", opts.IsPull.Value()) diff --git a/models/migrations/v1_22/v293_test.go b/models/migrations/v1_22/v293_test.go index ccc92f39a6..cfe4345143 100644 --- a/models/migrations/v1_22/v293_test.go +++ b/models/migrations/v1_22/v293_test.go @@ -15,7 +15,7 @@ import ( func Test_CheckProjectColumnsConsistency(t *testing.T) { // Prepare and load the testing database - x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Board)) + x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Column)) defer deferable() if x == nil || t.Failed() { return @@ -23,22 +23,22 @@ func Test_CheckProjectColumnsConsistency(t *testing.T) { assert.NoError(t, CheckProjectColumnsConsistency(x)) - // check if default board was added - var defaultBoard project.Board - has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultBoard) + // check if default column was added + var defaultColumn project.Column + has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultColumn) assert.NoError(t, err) assert.True(t, has) - assert.Equal(t, int64(1), defaultBoard.ProjectID) - assert.True(t, defaultBoard.Default) + assert.Equal(t, int64(1), defaultColumn.ProjectID) + assert.True(t, defaultColumn.Default) // check if multiple defaults, previous were removed and last will be kept - expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2) + expectDefaultColumn, err := project.GetColumn(db.DefaultContext, 2) assert.NoError(t, err) - assert.Equal(t, int64(2), expectDefaultBoard.ProjectID) - assert.False(t, expectDefaultBoard.Default) + assert.Equal(t, int64(2), expectDefaultColumn.ProjectID) + assert.False(t, expectDefaultColumn.Default) - expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3) + expectNonDefaultColumn, err := project.GetColumn(db.DefaultContext, 3) assert.NoError(t, err) - assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID) - assert.True(t, expectNonDefaultBoard.Default) + assert.Equal(t, int64(2), expectNonDefaultColumn.ProjectID) + assert.True(t, expectNonDefaultColumn.Default) } diff --git a/models/project/board.go b/models/project/board.go deleted file mode 100644 index 5f142a356c..0000000000 --- a/models/project/board.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package project - -import ( - "context" - "fmt" - "regexp" - - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" - - "xorm.io/builder" -) - -type ( - // BoardType is used to represent a project board type - BoardType uint8 - - // CardType is used to represent a project board card type - CardType uint8 - - // BoardList is a list of all project boards in a repository - BoardList []*Board -) - -const ( - // BoardTypeNone is a project board type that has no predefined columns - BoardTypeNone BoardType = iota - - // BoardTypeBasicKanban is a project board type that has basic predefined columns - BoardTypeBasicKanban - - // BoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs - BoardTypeBugTriage -) - -const ( - // CardTypeTextOnly is a project board card type that is text only - CardTypeTextOnly CardType = iota - - // CardTypeImagesAndText is a project board card type that has images and text - CardTypeImagesAndText -) - -// BoardColorPattern is a regexp witch can validate BoardColor -var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") - -// Board is used to represent boards on a project -type Board struct { - ID int64 `xorm:"pk autoincr"` - Title string - Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board - Sorting int8 `xorm:"NOT NULL DEFAULT 0"` - Color string `xorm:"VARCHAR(7)"` - - ProjectID int64 `xorm:"INDEX NOT NULL"` - CreatorID int64 `xorm:"NOT NULL"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` -} - -// TableName return the real table name -func (Board) TableName() string { - return "project_board" -} - -// NumIssues return counter of all issues assigned to the board -func (b *Board) NumIssues(ctx context.Context) int { - c, err := db.GetEngine(ctx).Table("project_issue"). - Where("project_id=?", b.ProjectID). - And("project_board_id=?", b.ID). - GroupBy("issue_id"). - Cols("issue_id"). - Count() - if err != nil { - return 0 - } - return int(c) -} - -func init() { - db.RegisterModel(new(Board)) -} - -// IsBoardTypeValid checks if the project board type is valid -func IsBoardTypeValid(p BoardType) bool { - switch p { - case BoardTypeNone, BoardTypeBasicKanban, BoardTypeBugTriage: - return true - default: - return false - } -} - -// IsCardTypeValid checks if the project board card type is valid -func IsCardTypeValid(p CardType) bool { - switch p { - case CardTypeTextOnly, CardTypeImagesAndText: - return true - default: - return false - } -} - -func createBoardsForProjectsType(ctx context.Context, project *Project) error { - var items []string - - switch project.BoardType { - - case BoardTypeBugTriage: - items = setting.Project.ProjectBoardBugTriageType - - case BoardTypeBasicKanban: - items = setting.Project.ProjectBoardBasicKanbanType - - case BoardTypeNone: - fallthrough - default: - return nil - } - - board := Board{ - CreatedUnix: timeutil.TimeStampNow(), - CreatorID: project.CreatorID, - Title: "Backlog", - ProjectID: project.ID, - Default: true, - } - if err := db.Insert(ctx, board); err != nil { - return err - } - - if len(items) == 0 { - return nil - } - - boards := make([]Board, 0, len(items)) - - for _, v := range items { - boards = append(boards, Board{ - CreatedUnix: timeutil.TimeStampNow(), - CreatorID: project.CreatorID, - Title: v, - ProjectID: project.ID, - }) - } - - return db.Insert(ctx, boards) -} - -// NewBoard adds a new project board to a given project -func NewBoard(ctx context.Context, board *Board) error { - if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { - return fmt.Errorf("bad color code: %s", board.Color) - } - - _, err := db.GetEngine(ctx).Insert(board) - return err -} - -// DeleteBoardByID removes all issues references to the project board. -func DeleteBoardByID(ctx context.Context, boardID int64) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := deleteBoardByID(ctx, boardID); err != nil { - return err - } - - return committer.Commit() -} - -func deleteBoardByID(ctx context.Context, boardID int64) error { - board, err := GetBoard(ctx, boardID) - if err != nil { - if IsErrProjectBoardNotExist(err) { - return nil - } - - return err - } - - if board.Default { - return fmt.Errorf("deleteBoardByID: cannot delete default board") - } - - if err = board.removeIssues(ctx); err != nil { - return err - } - - if _, err := db.GetEngine(ctx).ID(board.ID).NoAutoCondition().Delete(board); err != nil { - return err - } - return nil -} - -func deleteBoardByProjectID(ctx context.Context, projectID int64) error { - _, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&Board{}) - return err -} - -// GetBoard fetches the current board of a project -func GetBoard(ctx context.Context, boardID int64) (*Board, error) { - board := new(Board) - has, err := db.GetEngine(ctx).ID(boardID).Get(board) - if err != nil { - return nil, err - } else if !has { - return nil, ErrProjectBoardNotExist{BoardID: boardID} - } - - return board, nil -} - -// UpdateBoard updates a project board -func UpdateBoard(ctx context.Context, board *Board) error { - var fieldToUpdate []string - - if board.Sorting != 0 { - fieldToUpdate = append(fieldToUpdate, "sorting") - } - - if board.Title != "" { - fieldToUpdate = append(fieldToUpdate, "title") - } - - if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { - return fmt.Errorf("bad color code: %s", board.Color) - } - fieldToUpdate = append(fieldToUpdate, "color") - - _, err := db.GetEngine(ctx).ID(board.ID).Cols(fieldToUpdate...).Update(board) - - return err -} - -// GetBoards fetches all boards related to a project -func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { - boards := make([]*Board, 0, 5) - - if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil { - return nil, err - } - - defaultB, err := p.getDefaultBoard(ctx) - if err != nil { - return nil, err - } - - return append([]*Board{defaultB}, boards...), nil -} - -// getDefaultBoard return default board and ensure only one exists -func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { - var board Board - has, err := db.GetEngine(ctx). - Where("project_id=? AND `default` = ?", p.ID, true). - Desc("id").Get(&board) - if err != nil { - return nil, err - } - - if has { - return &board, nil - } - - // create a default board if none is found - board = Board{ - ProjectID: p.ID, - Default: true, - Title: "Uncategorized", - CreatorID: p.CreatorID, - } - if _, err := db.GetEngine(ctx).Insert(&board); err != nil { - return nil, err - } - return &board, nil -} - -// SetDefaultBoard represents a board for issues not assigned to one -func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error { - return db.WithTx(ctx, func(ctx context.Context) error { - if _, err := GetBoard(ctx, boardID); err != nil { - return err - } - - if _, err := db.GetEngine(ctx).Where(builder.Eq{ - "project_id": projectID, - "`default`": true, - }).Cols("`default`").Update(&Board{Default: false}); err != nil { - return err - } - - _, err := db.GetEngine(ctx).ID(boardID). - Where(builder.Eq{"project_id": projectID}). - Cols("`default`").Update(&Board{Default: true}) - return err - }) -} - -// UpdateBoardSorting update project board sorting -func UpdateBoardSorting(ctx context.Context, bs BoardList) error { - return db.WithTx(ctx, func(ctx context.Context) error { - for i := range bs { - if _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( - "sorting", - ).Update(bs[i]); err != nil { - return err - } - } - return nil - }) -} diff --git a/models/project/board_test.go b/models/project/board_test.go deleted file mode 100644 index 71ba29a589..0000000000 --- a/models/project/board_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 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 TestGetDefaultBoard(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5) - assert.NoError(t, err) - - // check if default board was added - board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext) - assert.NoError(t, err) - assert.Equal(t, int64(5), board.ProjectID) - assert.Equal(t, "Uncategorized", board.Title) - - projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6) - assert.NoError(t, err) - - // check if multiple defaults were removed - board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext) - assert.NoError(t, err) - assert.Equal(t, int64(6), board.ProjectID) - assert.Equal(t, int64(9), board.ID) - - // set 8 as default board - assert.NoError(t, SetDefaultBoard(db.DefaultContext, board.ProjectID, 8)) - - // then 9 will become a non-default board - board, err = GetBoard(db.DefaultContext, 9) - assert.NoError(t, err) - assert.Equal(t, int64(6), board.ProjectID) - assert.False(t, board.Default) -} diff --git a/models/project/column.go b/models/project/column.go new file mode 100644 index 0000000000..b218180f58 --- /dev/null +++ b/models/project/column.go @@ -0,0 +1,287 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +import ( + "context" + "fmt" + "regexp" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/builder" +) + +type ( + + // CardType is used to represent a project column card type + CardType uint8 + + // ColumnList is a list of all project columns in a repository + ColumnList []*Column +) + +const ( + // CardTypeTextOnly is a project column card type that is text only + CardTypeTextOnly CardType = iota + + // CardTypeImagesAndText is a project column card type that has images and text + CardTypeImagesAndText +) + +// ColumnColorPattern is a regexp witch can validate ColumnColor +var ColumnColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") + +// Column is used to represent column on a project +type Column struct { + ID int64 `xorm:"pk autoincr"` + Title string + Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific column will be assigned to this column + Sorting int8 `xorm:"NOT NULL DEFAULT 0"` + Color string `xorm:"VARCHAR(7)"` + + ProjectID int64 `xorm:"INDEX NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` +} + +// TableName return the real table name +func (Column) TableName() string { + return "project_board" // FIXME: the legacy table name should be project_column +} + +// NumIssues return counter of all issues assigned to the column +func (b *Column) NumIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). + Where("project_id=?", b.ProjectID). + And("project_board_id=?", b.ID). + GroupBy("issue_id"). + Cols("issue_id"). + Count() + if err != nil { + return 0 + } + return int(c) +} + +func init() { + db.RegisterModel(new(Column)) +} + +// IsCardTypeValid checks if the project column card type is valid +func IsCardTypeValid(p CardType) bool { + switch p { + case CardTypeTextOnly, CardTypeImagesAndText: + return true + default: + return false + } +} + +func createColumnsForProjectsBoradViewType(ctx context.Context, project *Project) error { + var items []string + + switch project.BoardViewType { + case BoardViewTypeBugTriage: + items = setting.Project.ProjectBoardBugTriageType + case BoardViewTypeBasicKanban: + items = setting.Project.ProjectBoardBasicKanbanType + case BoardViewTypeNone: + fallthrough + default: + return nil + } + + return db.WithTx(ctx, func(ctx context.Context) error { + column := Column{ + CreatedUnix: timeutil.TimeStampNow(), + CreatorID: project.CreatorID, + Title: "Backlog", + ProjectID: project.ID, + Default: true, + } + if err := db.Insert(ctx, column); err != nil { + return err + } + + if len(items) == 0 { + return nil + } + + boards := make([]Column, 0, len(items)) + for _, v := range items { + boards = append(boards, Column{ + CreatedUnix: timeutil.TimeStampNow(), + CreatorID: project.CreatorID, + Title: v, + ProjectID: project.ID, + }) + } + + return db.Insert(ctx, boards) + }) +} + +// NewColumn adds a new project column to a given project +func NewColumn(ctx context.Context, column *Column) error { + if len(column.Color) != 0 && !ColumnColorPattern.MatchString(column.Color) { + return fmt.Errorf("bad color code: %s", column.Color) + } + + _, err := db.GetEngine(ctx).Insert(column) + return err +} + +// DeleteColumnByID removes all issues references to the project board. +func DeleteColumnByID(ctx context.Context, columnID int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { + return deleteColumnByID(ctx, columnID) + }) +} + +func deleteColumnByID(ctx context.Context, columnID int64) error { + column, err := GetColumn(ctx, columnID) + if err != nil { + if IsErrProjectColumnNotExist(err) { + return nil + } + + return err + } + + if column.Default { + return fmt.Errorf("deleteBoardByID: cannot delete default board") + } + + if err = column.removeIssues(ctx); err != nil { + return err + } + + if _, err := db.GetEngine(ctx).ID(column.ID).NoAutoCondition().Delete(column); err != nil { + return err + } + return nil +} + +func deleteColumnByProjectID(ctx context.Context, projectID int64) error { + _, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&Column{}) + return err +} + +// GetColumn fetches the current board of a project +func GetColumn(ctx context.Context, columnID int64) (*Column, error) { + board := new(Column) + has, err := db.GetEngine(ctx).ID(columnID).Get(board) + if err != nil { + return nil, err + } else if !has { + return nil, ErrProjectColumnNotExist{ColumnID: columnID} + } + + return board, nil +} + +// UpdateColumn updates a project column +func UpdateColumn(ctx context.Context, column *Column) error { + var fieldToUpdate []string + + if column.Sorting != 0 { + fieldToUpdate = append(fieldToUpdate, "sorting") + } + + if column.Title != "" { + fieldToUpdate = append(fieldToUpdate, "title") + } + + if len(column.Color) != 0 && !ColumnColorPattern.MatchString(column.Color) { + return fmt.Errorf("bad color code: %s", column.Color) + } + fieldToUpdate = append(fieldToUpdate, "color") + + _, err := db.GetEngine(ctx).ID(column.ID).Cols(fieldToUpdate...).Update(column) + + return err +} + +// GetColumns fetches all boards related to a project +func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) { + boards := make([]*Column, 0, 5) + + if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil { + return nil, err + } + + defaultB, err := p.getDefaultColumn(ctx) + if err != nil { + return nil, err + } + + return append([]*Column{defaultB}, boards...), nil +} + +// getDefaultColumn return default column and ensure only one exists +func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { + var column Column + has, err := db.GetEngine(ctx). + Where("project_id=? AND `default` = ?", p.ID, true). + Desc("id").Get(&column) + if err != nil { + return nil, err + } + + if has { + return &column, nil + } + + // create a default column if none is found + column = Column{ + ProjectID: p.ID, + Default: true, + Title: "Uncategorized", + CreatorID: p.CreatorID, + } + if _, err := db.GetEngine(ctx).Insert(&column); err != nil { + return nil, err + } + return &column, nil +} + +// SetDefaultColumn represents a column for issues not assigned to one +func SetDefaultColumn(ctx context.Context, projectID, columnID int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err := GetColumn(ctx, columnID); err != nil { + return err + } + + if _, err := db.GetEngine(ctx).Where(builder.Eq{ + "project_id": projectID, + "`default`": true, + }).Cols("`default`").Update(&Column{Default: false}); err != nil { + return err + } + + _, err := db.GetEngine(ctx).ID(columnID). + Where(builder.Eq{"project_id": projectID}). + Cols("`default`").Update(&Column{Default: true}) + return err + }) +} + +// 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 + }) +} diff --git a/models/project/column_test.go b/models/project/column_test.go new file mode 100644 index 0000000000..1babaea428 --- /dev/null +++ b/models/project/column_test.go @@ -0,0 +1,44 @@ +// Copyright 2020 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 TestGetDefaultcolumn(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5) + assert.NoError(t, err) + + // check if default column was added + column, err := projectWithoutDefault.getDefaultColumn(db.DefaultContext) + assert.NoError(t, err) + assert.Equal(t, int64(5), column.ProjectID) + assert.Equal(t, "Uncategorized", column.Title) + + projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6) + assert.NoError(t, err) + + // check if multiple defaults were removed + column, err = projectWithMultipleDefaults.getDefaultColumn(db.DefaultContext) + assert.NoError(t, err) + assert.Equal(t, int64(6), column.ProjectID) + assert.Equal(t, int64(9), column.ID) + + // set 8 as default column + assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8)) + + // then 9 will become a non-default column + column, err = GetColumn(db.DefaultContext, 9) + assert.NoError(t, err) + assert.Equal(t, int64(6), column.ProjectID) + assert.False(t, column.Default) +} diff --git a/models/project/issue.go b/models/project/issue.go index ebc9719de5..ba192485db 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -17,10 +17,10 @@ type ProjectIssue struct { //revive:disable-line:exported IssueID int64 `xorm:"INDEX"` ProjectID int64 `xorm:"INDEX"` - // If 0, then it has not been added to a specific board in the project - ProjectBoardID int64 `xorm:"INDEX"` + // If 0, then it has not been added to a specific column in the project + ProjectColumnID int64 `xorm:"'project_board_id' INDEX"` - // the sorting order on the board + // the sorting order on the column Sorting int64 `xorm:"NOT NULL DEFAULT 0"` } @@ -75,8 +75,8 @@ func (p *Project) NumOpenIssues(ctx context.Context) int { return int(c) } -// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column -func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error { +// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column +func MoveIssuesOnProjectColumn(ctx context.Context, board *Column, sortedIssueIDs map[int64]int64) error { return db.WithTx(ctx, func(ctx context.Context) error { sess := db.GetEngine(ctx) @@ -102,7 +102,7 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs }) } -func (b *Board) removeIssues(ctx context.Context) error { +func (b *Column) removeIssues(ctx context.Context) error { _, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID) return err } diff --git a/models/project/project.go b/models/project/project.go index 8f9ee2a99e..a6c24672f2 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -21,12 +21,6 @@ import ( ) type ( - // BoardConfig is used to identify the type of board that is being created - BoardConfig struct { - BoardType BoardType - Translation string - } - // CardConfig is used to identify the type of board card that is being used CardConfig struct { CardType CardType @@ -68,39 +62,39 @@ func (err ErrProjectNotExist) Unwrap() error { return util.ErrNotExist } -// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error. -type ErrProjectBoardNotExist struct { - BoardID int64 +// ErrProjectColumnNotExist represents a "ProjectBoardNotExist" kind of error. +type ErrProjectColumnNotExist struct { + ColumnID int64 } -// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist -func IsErrProjectBoardNotExist(err error) bool { - _, ok := err.(ErrProjectBoardNotExist) +// IsErrProjectColumnNotExist checks if an error is a ErrProjectBoardNotExist +func IsErrProjectColumnNotExist(err error) bool { + _, ok := err.(ErrProjectColumnNotExist) return ok } -func (err ErrProjectBoardNotExist) Error() string { - return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID) +func (err ErrProjectColumnNotExist) Error() string { + return fmt.Sprintf("project column does not exist [id: %d]", err.ColumnID) } -func (err ErrProjectBoardNotExist) Unwrap() error { +func (err ErrProjectColumnNotExist) Unwrap() error { return util.ErrNotExist } // Project represents a project board type Project struct { - ID int64 `xorm:"pk autoincr"` - Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"TEXT"` - OwnerID int64 `xorm:"INDEX"` - Owner *user_model.User `xorm:"-"` - RepoID int64 `xorm:"INDEX"` - Repo *repo_model.Repository `xorm:"-"` - CreatorID int64 `xorm:"NOT NULL"` - IsClosed bool `xorm:"INDEX"` - BoardType BoardType - CardType CardType - Type Type + ID int64 `xorm:"pk autoincr"` + Title string `xorm:"INDEX NOT NULL"` + Description string `xorm:"TEXT"` + OwnerID int64 `xorm:"INDEX"` + Owner *user_model.User `xorm:"-"` + RepoID int64 `xorm:"INDEX"` + Repo *repo_model.Repository `xorm:"-"` + CreatorID int64 `xorm:"NOT NULL"` + IsClosed bool `xorm:"INDEX"` + BoardViewType BoardViewType `xorm:"'board_type'"` + CardType CardType + Type Type RenderedContent template.HTML `xorm:"-"` @@ -165,15 +159,6 @@ func init() { db.RegisterModel(new(Project)) } -// GetBoardConfig retrieves the types of configurations project boards could have -func GetBoardConfig() []BoardConfig { - return []BoardConfig{ - {BoardTypeNone, "repo.projects.type.none"}, - {BoardTypeBasicKanban, "repo.projects.type.basic_kanban"}, - {BoardTypeBugTriage, "repo.projects.type.bug_triage"}, - } -} - // GetCardConfig retrieves the types of configurations project board cards could have func GetCardConfig() []CardConfig { return []CardConfig{ @@ -244,8 +229,8 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy { // NewProject creates a new Project func NewProject(ctx context.Context, p *Project) error { - if !IsBoardTypeValid(p.BoardType) { - p.BoardType = BoardTypeNone + if !IsBoardViewTypeValid(p.BoardViewType) { + p.BoardViewType = BoardViewTypeNone } if !IsCardTypeValid(p.CardType) { @@ -256,27 +241,19 @@ func NewProject(ctx context.Context, p *Project) error { return util.NewInvalidArgumentErrorf("project type is not valid") } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := db.Insert(ctx, p); err != nil { - return err - } - - if p.RepoID > 0 { - if _, err := db.Exec(ctx, "UPDATE `repository` SET num_projects = num_projects + 1 WHERE id = ?", p.RepoID); err != nil { + return db.WithTx(ctx, func(tx context.Context) error { + if err := db.Insert(ctx, p); err != nil { return err } - } - if err := createBoardsForProjectsType(ctx, p); err != nil { - return err - } + if p.RepoID > 0 { + if _, err := db.Exec(ctx, "UPDATE `repository` SET num_projects = num_projects + 1 WHERE id = ?", p.RepoID); err != nil { + return err + } + } - return committer.Commit() + return createColumnsForProjectsBoradViewType(ctx, p) + }) } // GetProjectByID returns the projects in a repository @@ -410,7 +387,7 @@ func DeleteProjectByID(ctx context.Context, id int64) error { return err } - if err := deleteBoardByProjectID(ctx, id); err != nil { + if err := deleteColumnByProjectID(ctx, id); err != nil { return err } diff --git a/models/project/project_test.go b/models/project/project_test.go index 8fbbdedecf..49188777e3 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -51,13 +51,13 @@ func TestProject(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) project := &Project{ - Type: TypeRepository, - BoardType: BoardTypeBasicKanban, - CardType: CardTypeTextOnly, - Title: "New Project", - RepoID: 1, - CreatedUnix: timeutil.TimeStampNow(), - CreatorID: 2, + Type: TypeRepository, + BoardViewType: BoardViewTypeBasicKanban, + CardType: CardTypeTextOnly, + Title: "New Project", + RepoID: 1, + CreatedUnix: timeutil.TimeStampNow(), + CreatorID: 2, } assert.NoError(t, NewProject(db.DefaultContext, project)) diff --git a/models/project/view_board.go b/models/project/view_board.go new file mode 100644 index 0000000000..973b700c2e --- /dev/null +++ b/models/project/view_board.go @@ -0,0 +1,45 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package project + +type ( + // BoardViewType is used to represent a project column type + BoardViewType uint8 + + // BoardConfig is used to identify the type of board that is being created + BoardConfig struct { + BoardType BoardViewType + Translation string + } +) + +const ( + // BoardViewTypeNone is a project board type that has no predefined columns + BoardViewTypeNone BoardViewType = iota + + // BoardViewTypeBasicKanban is a project board type that has basic predefined columns + BoardViewTypeBasicKanban + + // BoardViewTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs + BoardViewTypeBugTriage +) + +// GetBoardViewConfig retrieves the types of configurations project boards could have +func GetBoardViewConfig() []BoardConfig { + return []BoardConfig{ + {BoardViewTypeNone, "repo.projects.type.none"}, + {BoardViewTypeBasicKanban, "repo.projects.type.basic_kanban"}, + {BoardViewTypeBugTriage, "repo.projects.type.bug_triage"}, + } +} + +// IsBoardViewTypeValid checks if the project board type is valid +func IsBoardViewTypeValid(p BoardViewType) bool { + switch p { + case BoardViewTypeNone, BoardViewTypeBasicKanban, BoardViewTypeBugTriage: + return true + default: + return false + } +} diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 1f54be721b..a6d366ec1c 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -228,8 +228,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.ProjectID.Has() { queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectID.Value(), "project_id")) } - if options.ProjectBoardID.Has() { - queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectBoardID.Value(), "project_board_id")) + if options.ProjectColumnID.Has() { + queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectColumnID.Value(), "project_board_id")) } if options.PosterID.Has() { diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index eeaf1696ad..875a4ca279 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -61,7 +61,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m ReviewedID: convertID(options.ReviewedID), SubscriberID: convertID(options.SubscriberID), ProjectID: convertID(options.ProjectID), - ProjectBoardID: convertID(options.ProjectBoardID), + ProjectColumnID: convertID(options.ProjectColumnID), IsClosed: options.IsClosed, IsPull: options.IsPull, IncludedLabelNames: nil, diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go index 4a98b4588a..3bda12efb9 100644 --- a/modules/indexer/issues/dboptions.go +++ b/modules/indexer/issues/dboptions.go @@ -50,7 +50,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp } searchOpt.ProjectID = convertID(opts.ProjectID) - searchOpt.ProjectBoardID = convertID(opts.ProjectBoardID) + searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID) searchOpt.PosterID = convertID(opts.PosterID) searchOpt.AssigneeID = convertID(opts.AssigneeID) searchOpt.MentionID = convertID(opts.MentionedID) diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 53b383c8d5..979bed7f73 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -198,8 +198,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.ProjectID.Has() { query.Must(elastic.NewTermQuery("project_id", options.ProjectID.Value())) } - if options.ProjectBoardID.Has() { - query.Must(elastic.NewTermQuery("project_board_id", options.ProjectBoardID.Value())) + if options.ProjectColumnID.Has() { + query.Must(elastic.NewTermQuery("project_board_id", options.ProjectColumnID.Value())) } if options.PosterID.Has() { diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 0d0cfc8516..35d43742be 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -369,13 +369,13 @@ func searchIssueInProject(t *testing.T) { }, { SearchOptions{ - ProjectBoardID: optional.Some(int64(1)), + ProjectColumnID: optional.Some(int64(1)), }, []int64{1}, }, { SearchOptions{ - ProjectBoardID: optional.Some(int64(0)), // issue with in default board + ProjectColumnID: optional.Some(int64(0)), // issue with in default board }, []int64{2}, }, diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index e9c4eca559..5b34b50ae7 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -89,8 +89,8 @@ type SearchOptions struct { MilestoneIDs []int64 // milestones the issues have - ProjectID optional.Option[int64] // project the issues belong to - ProjectBoardID optional.Option[int64] // project board the issues belong to + ProjectID optional.Option[int64] // project the issues belong to + ProjectColumnID optional.Option[int64] // project column the issues belong to PosterID optional.Option[int64] // poster of the issues diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index 7f32876d80..eea985d872 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -343,7 +343,7 @@ var cases = []*testIndexerCase{ Paginator: &db.ListOptions{ PageSize: 5, }, - ProjectBoardID: optional.Some(int64(1)), + ProjectColumnID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { assert.Equal(t, 5, len(result.Hits)) @@ -361,7 +361,7 @@ var cases = []*testIndexerCase{ Paginator: &db.ListOptions{ PageSize: 5, }, - ProjectBoardID: optional.Some(int64(0)), + ProjectColumnID: optional.Some(int64(0)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { assert.Equal(t, 5, len(result.Hits)) diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 8a7cec6cba..9332319339 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -174,8 +174,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if options.ProjectID.Has() { query.And(inner_meilisearch.NewFilterEq("project_id", options.ProjectID.Value())) } - if options.ProjectBoardID.Has() { - query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectBoardID.Value())) + if options.ProjectColumnID.Has() { + query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectColumnID.Value())) } if options.PosterID.Has() { diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index 9861c808dc..055bbbf43b 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -105,7 +105,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD NoLabel: len(labels) == 0, MilestoneID: issue.MilestoneID, ProjectID: projectID, - ProjectBoardID: issue.ProjectBoardID(ctx), + ProjectBoardID: issue.ProjectColumnID(ctx), PosterID: issue.PosterID, AssigneeID: issue.AssigneeID, MentionIDs: mentionIDs, diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go index 1bf8f58b93..e645adeaf1 100755 --- a/modules/metrics/collector.go +++ b/modules/metrics/collector.go @@ -338,7 +338,7 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric( c.ProjectBoards, prometheus.GaugeValue, - float64(stats.Counter.ProjectBoard), + float64(stats.Counter.ProjectColumn), ) ch <- prometheus.MustNewConstMetric( c.PublicKeys, diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 596a370d2e..36f4f3ef50 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -141,7 +141,7 @@ func canWriteProjects(ctx *context.Context) bool { // RenderNewProject render creating a project page func RenderNewProject(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.projects.new") - ctx.Data["BoardTypes"] = project_model.GetBoardConfig() + ctx.Data["BoardTypes"] = project_model.GetBoardViewConfig() ctx.Data["CardTypes"] = project_model.GetCardConfig() ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["PageIsViewProjects"] = true @@ -170,12 +170,12 @@ func NewProjectPost(ctx *context.Context) { } newProject := project_model.Project{ - OwnerID: ctx.ContextUser.ID, - Title: form.Title, - Description: form.Content, - CreatorID: ctx.Doer.ID, - BoardType: form.BoardType, - CardType: form.CardType, + OwnerID: ctx.ContextUser.ID, + Title: form.Title, + Description: form.Content, + CreatorID: ctx.Doer.ID, + BoardViewType: form.BoardType, + CardType: form.CardType, } if ctx.ContextUser.IsOrganization() { @@ -327,13 +327,13 @@ func ViewProject(ctx *context.Context) { return } - boards, err := project.GetBoards(ctx) + boards, err := project.GetColumns(ctx) if err != nil { ctx.ServerError("GetProjectBoards", err) return } - issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) + issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, boards) if err != nil { ctx.ServerError("LoadIssuesOfBoards", err) return @@ -458,8 +458,8 @@ func UpdateIssueProject(ctx *context.Context) { ctx.JSONOK() } -// DeleteProjectBoard allows for the deletion of a project board -func DeleteProjectBoard(ctx *context.Context) { +// DeleteProjectColumn allows for the deletion of a project board +func DeleteProjectColumn(ctx *context.Context) { if ctx.Doer == nil { ctx.JSON(http.StatusForbidden, map[string]string{ "message": "Only signed in users are allowed to perform this action.", @@ -473,7 +473,7 @@ func DeleteProjectBoard(ctx *context.Context) { return } - pb, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + pb, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID")) if err != nil { ctx.ServerError("GetProjectBoard", err) return @@ -492,7 +492,7 @@ func DeleteProjectBoard(ctx *context.Context) { return } - if err := project_model.DeleteBoardByID(ctx, ctx.ParamsInt64(":boardID")); err != nil { + if err := project_model.DeleteColumnByID(ctx, ctx.ParamsInt64(":boardID")); err != nil { ctx.ServerError("DeleteProjectBoardByID", err) return } @@ -500,9 +500,9 @@ func DeleteProjectBoard(ctx *context.Context) { ctx.JSONOK() } -// AddBoardToProjectPost allows a new board to be added to a project. -func AddBoardToProjectPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.EditProjectBoardForm) +// AddColumnToProjectPost allows a new board to be added to a project. +func AddColumnToProjectPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.EditProjectColumnForm) project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) if err != nil { @@ -510,7 +510,7 @@ func AddBoardToProjectPost(ctx *context.Context) { return } - if err := project_model.NewBoard(ctx, &project_model.Board{ + if err := project_model.NewColumn(ctx, &project_model.Column{ ProjectID: project.ID, Title: form.Title, Color: form.Color, @@ -524,7 +524,7 @@ func AddBoardToProjectPost(ctx *context.Context) { } // CheckProjectBoardChangePermissions check permission -func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) { +func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Column) { if ctx.Doer == nil { ctx.JSON(http.StatusForbidden, map[string]string{ "message": "Only signed in users are allowed to perform this action.", @@ -538,7 +538,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr return nil, nil } - board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID")) if err != nil { ctx.ServerError("GetProjectBoard", err) return nil, nil @@ -559,9 +559,9 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr return project, board } -// EditProjectBoard allows a project board's to be updated -func EditProjectBoard(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.EditProjectBoardForm) +// EditProjectColumn allows a project board's to be updated +func EditProjectColumn(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.EditProjectColumnForm) _, board := CheckProjectBoardChangePermissions(ctx) if ctx.Written() { return @@ -577,7 +577,7 @@ func EditProjectBoard(ctx *context.Context) { board.Sorting = form.Sorting } - if err := project_model.UpdateBoard(ctx, board); err != nil { + if err := project_model.UpdateColumn(ctx, board); err != nil { ctx.ServerError("UpdateProjectBoard", err) return } @@ -592,7 +592,7 @@ func SetDefaultProjectBoard(ctx *context.Context) { return } - if err := project_model.SetDefaultBoard(ctx, project.ID, board.ID); err != nil { + if err := project_model.SetDefaultColumn(ctx, project.ID, board.ID); err != nil { ctx.ServerError("SetDefaultBoard", err) return } @@ -619,9 +619,9 @@ func MoveIssues(ctx *context.Context) { return } - board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID")) if err != nil { - ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err) + ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectColumnNotExist, err) return } @@ -671,7 +671,7 @@ func MoveIssues(ctx *context.Context) { } } - if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil { + if err = project_model.MoveIssuesOnProjectColumn(ctx, board, sortedIssueIDs); err != nil { ctx.ServerError("MoveIssuesOnProjectBoard", err) return } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 6c2d4a7390..af030213cd 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2826,12 +2826,12 @@ func ListIssues(ctx *context.Context) { Page: ctx.FormInt("page"), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), }, - Keyword: keyword, - RepoIDs: []int64{ctx.Repo.Repository.ID}, - IsPull: isPull, - IsClosed: isClosed, - ProjectBoardID: projectID, - SortBy: issue_indexer.SortByCreatedDesc, + Keyword: keyword, + RepoIDs: []int64{ctx.Repo.Repository.ID}, + IsPull: isPull, + IsClosed: isClosed, + ProjectID: projectID, + SortBy: issue_indexer.SortByCreatedDesc, } if since != 0 { searchOpt.UpdatedAfterUnix = optional.Some(since) diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index a2db1fc770..4225b89aa7 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -132,7 +132,7 @@ func Projects(ctx *context.Context) { // RenderNewProject render creating a project page func RenderNewProject(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.projects.new") - ctx.Data["BoardTypes"] = project_model.GetBoardConfig() + ctx.Data["BoardTypes"] = project_model.GetBoardViewConfig() ctx.Data["CardTypes"] = project_model.GetCardConfig() ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CancelLink"] = ctx.Repo.Repository.Link() + "/projects" @@ -150,13 +150,13 @@ func NewProjectPost(ctx *context.Context) { } if err := project_model.NewProject(ctx, &project_model.Project{ - RepoID: ctx.Repo.Repository.ID, - Title: form.Title, - Description: form.Content, - CreatorID: ctx.Doer.ID, - BoardType: form.BoardType, - CardType: form.CardType, - Type: project_model.TypeRepository, + RepoID: ctx.Repo.Repository.ID, + Title: form.Title, + Description: form.Content, + CreatorID: ctx.Doer.ID, + BoardViewType: form.BoardType, + CardType: form.CardType, + Type: project_model.TypeRepository, }); err != nil { ctx.ServerError("NewProject", err) return @@ -309,13 +309,13 @@ func ViewProject(ctx *context.Context) { return } - boards, err := project.GetBoards(ctx) + boards, err := project.GetColumns(ctx) if err != nil { ctx.ServerError("GetProjectBoards", err) return } - issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards) + issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, boards) if err != nil { ctx.ServerError("LoadIssuesOfBoards", err) return @@ -432,7 +432,7 @@ func DeleteProjectBoard(ctx *context.Context) { return } - pb, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + pb, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID")) if err != nil { ctx.ServerError("GetProjectBoard", err) return @@ -451,7 +451,7 @@ func DeleteProjectBoard(ctx *context.Context) { return } - if err := project_model.DeleteBoardByID(ctx, ctx.ParamsInt64(":boardID")); err != nil { + if err := project_model.DeleteColumnByID(ctx, ctx.ParamsInt64(":boardID")); err != nil { ctx.ServerError("DeleteProjectBoardByID", err) return } @@ -461,7 +461,7 @@ func DeleteProjectBoard(ctx *context.Context) { // AddBoardToProjectPost allows a new board to be added to a project. func AddBoardToProjectPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.EditProjectBoardForm) + form := web.GetForm(ctx).(*forms.EditProjectColumnForm) if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(perm.AccessModeWrite, unit.TypeProjects) { ctx.JSON(http.StatusForbidden, map[string]string{ "message": "Only authorized users are allowed to perform this action.", @@ -479,7 +479,7 @@ func AddBoardToProjectPost(ctx *context.Context) { return } - if err := project_model.NewBoard(ctx, &project_model.Board{ + if err := project_model.NewColumn(ctx, &project_model.Column{ ProjectID: project.ID, Title: form.Title, Color: form.Color, @@ -492,7 +492,7 @@ func AddBoardToProjectPost(ctx *context.Context) { ctx.JSONOK() } -func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) { +func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Column) { if ctx.Doer == nil { ctx.JSON(http.StatusForbidden, map[string]string{ "message": "Only signed in users are allowed to perform this action.", @@ -517,7 +517,7 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr return nil, nil } - board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID")) if err != nil { ctx.ServerError("GetProjectBoard", err) return nil, nil @@ -538,9 +538,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr return project, board } -// EditProjectBoard allows a project board's to be updated -func EditProjectBoard(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.EditProjectBoardForm) +// EditProjectColumn allows a project board's to be updated +func EditProjectColumn(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.EditProjectColumnForm) _, board := checkProjectBoardChangePermissions(ctx) if ctx.Written() { return @@ -556,7 +556,7 @@ func EditProjectBoard(ctx *context.Context) { board.Sorting = form.Sorting } - if err := project_model.UpdateBoard(ctx, board); err != nil { + if err := project_model.UpdateColumn(ctx, board); err != nil { ctx.ServerError("UpdateProjectBoard", err) return } @@ -564,14 +564,14 @@ func EditProjectBoard(ctx *context.Context) { ctx.JSONOK() } -// SetDefaultProjectBoard set default board for uncategorized issues/pulls -func SetDefaultProjectBoard(ctx *context.Context) { +// SetDefaultProjectColumn set default board for uncategorized issues/pulls +func SetDefaultProjectColumn(ctx *context.Context) { project, board := checkProjectBoardChangePermissions(ctx) if ctx.Written() { return } - if err := project_model.SetDefaultBoard(ctx, project.ID, board.ID); err != nil { + if err := project_model.SetDefaultColumn(ctx, project.ID, board.ID); err != nil { ctx.ServerError("SetDefaultBoard", err) return } @@ -609,12 +609,12 @@ func MoveIssues(ctx *context.Context) { return } - board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID")) + board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID")) if err != nil { - if project_model.IsErrProjectBoardNotExist(err) { - ctx.NotFound("ProjectBoardNotExist", nil) + if project_model.IsErrProjectColumnNotExist(err) { + ctx.NotFound("ProjectColumnNotExist", nil) } else { - ctx.ServerError("GetProjectBoard", err) + ctx.ServerError("GetProjectColumn", err) } return } @@ -664,8 +664,8 @@ func MoveIssues(ctx *context.Context) { } } - if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil { - ctx.ServerError("MoveIssuesOnProjectBoard", err) + if err = project_model.MoveIssuesOnProjectColumn(ctx, board, sortedIssueIDs); err != nil { + ctx.ServerError("MoveIssuesOnProjectColumn", err) return } diff --git a/routers/web/web.go b/routers/web/web.go index 4fff994e42..04255f26a7 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -997,7 +997,7 @@ func registerRoutes(m *web.Route) { m.Get("/new", org.RenderNewProject) m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost) m.Group("/{id}", func() { - m.Post("", web.Bind(forms.EditProjectBoardForm{}), org.AddBoardToProjectPost) + m.Post("", web.Bind(forms.EditProjectColumnForm{}), org.AddColumnToProjectPost) m.Post("/delete", org.DeleteProject) m.Get("/edit", org.RenderEditProject) @@ -1005,8 +1005,8 @@ func registerRoutes(m *web.Route) { m.Post("/{action:open|close}", org.ChangeProjectStatus) m.Group("/{boardID}", func() { - m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard) - m.Delete("", org.DeleteProjectBoard) + m.Put("", web.Bind(forms.EditProjectColumnForm{}), org.EditProjectColumn) + m.Delete("", org.DeleteProjectColumn) m.Post("/default", org.SetDefaultProjectBoard) m.Post("/move", org.MoveIssues) @@ -1336,7 +1336,7 @@ func registerRoutes(m *web.Route) { m.Get("/new", repo.RenderNewProject) m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost) m.Group("/{id}", func() { - m.Post("", web.Bind(forms.EditProjectBoardForm{}), repo.AddBoardToProjectPost) + m.Post("", web.Bind(forms.EditProjectColumnForm{}), repo.AddBoardToProjectPost) m.Post("/delete", repo.DeleteProject) m.Get("/edit", repo.RenderEditProject) @@ -1344,9 +1344,9 @@ func registerRoutes(m *web.Route) { m.Post("/{action:open|close}", repo.ChangeProjectStatus) m.Group("/{boardID}", func() { - m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard) + m.Put("", web.Bind(forms.EditProjectColumnForm{}), repo.EditProjectColumn) m.Delete("", repo.DeleteProjectBoard) - m.Post("/default", repo.SetDefaultProjectBoard) + m.Post("/default", repo.SetDefaultProjectColumn) m.Post("/move", repo.MoveIssues) }) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index e45a2a1695..7f3ee2ec52 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -504,18 +504,11 @@ func (i IssueLockForm) HasValidReason() bool { return false } -// __________ __ __ -// \______ \_______ ____ |__| ____ _____/ |_ ______ -// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/ -// | | | | \( <_> ) | \ ___/\ \___| | \___ \ -// |____| |__| \____/\__| |\___ >\___ >__| /____ > -// \______| \/ \/ \/ - // CreateProjectForm form for creating a project type CreateProjectForm struct { Title string `binding:"Required;MaxSize(100)"` Content string - BoardType project_model.BoardType + BoardType project_model.BoardViewType CardType project_model.CardType } @@ -524,25 +517,18 @@ type CreateProjectForm struct { type UserCreateProjectForm struct { Title string `binding:"Required;MaxSize(100)"` Content string - BoardType project_model.BoardType + BoardType project_model.BoardViewType CardType project_model.CardType UID int64 `binding:"Required"` } -// EditProjectBoardForm is a form for editing a project board -type EditProjectBoardForm struct { +// EditProjectColumnForm is a form for editing a project board +type EditProjectColumnForm struct { Title string `binding:"Required;MaxSize(100)"` Sorting int8 Color string `binding:"MaxSize(7)"` } -// _____ .__.__ __ -// / \ |__| | ____ _______/ |_ ____ ____ ____ -// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ -// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ -// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > -// \/ \/ \/ \/ \/ - // CreateMilestoneForm form for creating milestone type CreateMilestoneForm struct { Title string `binding:"Required;MaxSize(50)"` @@ -556,13 +542,6 @@ func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) b return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// .____ ___. .__ -// | | _____ \_ |__ ____ | | -// | | \__ \ | __ \_/ __ \| | -// | |___ / __ \| \_\ \ ___/| |__ -// |_______ (____ /___ /\___ >____/ -// \/ \/ \/ \/ - // CreateLabelForm form for creating label type CreateLabelForm struct { ID int64 @@ -590,13 +569,6 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// __________ .__ .__ __________ __ -// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ -// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ -// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | -// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| -// \/ \/ |__| \/ \/ - // MergePullRequestForm form for merging Pull Request // swagger:model MergePullRequestOption type MergePullRequestForm struct {