mirror of https://github.com/go-gitea/gitea
Rename project board -> column to make the UI less confusing (#30170)
This PR split the `Board` into two parts. One is the struct has been renamed to `Column` and the second we have a `Template Type`. But to make it easier to review, this PR will not change the database schemas, they are just renames. The database schema changes could be in future PRs. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: yp05327 <576951401@qq.com>pull/30671/head^2
parent
072b029b33
commit
98751108b1
@ -1,389 +0,0 @@ |
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package project |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"regexp" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
"code.gitea.io/gitea/modules/setting" |
||||
"code.gitea.io/gitea/modules/timeutil" |
||||
"code.gitea.io/gitea/modules/util" |
||||
|
||||
"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 (b *Board) GetIssues(ctx context.Context) ([]*ProjectIssue, error) { |
||||
issues := make([]*ProjectIssue, 0, 5) |
||||
if err := db.GetEngine(ctx).Where("project_id=?", b.ProjectID). |
||||
And("project_board_id=?", b.ID). |
||||
OrderBy("sorting, id"). |
||||
Find(&issues); err != nil { |
||||
return nil, err |
||||
} |
||||
return issues, nil |
||||
} |
||||
|
||||
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) |
||||
} |
||||
|
||||
// maxProjectColumns max columns allowed in a project, this should not bigger than 127
|
||||
// because sorting is int8 in database
|
||||
const maxProjectColumns = 20 |
||||
|
||||
// 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) |
||||
} |
||||
res := struct { |
||||
MaxSorting int64 |
||||
ColumnCount int64 |
||||
}{} |
||||
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as column_count").Table("project_board"). |
||||
Where("project_id=?", board.ProjectID).Get(&res); err != nil { |
||||
return err |
||||
} |
||||
if res.ColumnCount >= maxProjectColumns { |
||||
return fmt.Errorf("NewBoard: maximum number of columns reached") |
||||
} |
||||
board.Sorting = int8(util.Iif(res.ColumnCount > 0, res.MaxSorting+1, 0)) |
||||
_, 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") |
||||
} |
||||
|
||||
// move all issues to the default column
|
||||
project, err := GetProjectByID(ctx, board.ProjectID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defaultColumn, err := project.GetDefaultBoard(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = board.moveIssuesToAnotherColumn(ctx, defaultColumn); 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=?", p.ID).OrderBy("sorting, id").Find(&boards); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return 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 |
||||
}) |
||||
} |
||||
|
||||
func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (BoardList, error) { |
||||
columns := make([]*Board, 0, 5) |
||||
if err := db.GetEngine(ctx). |
||||
Where("project_id =?", projectID). |
||||
In("id", columnsIDs). |
||||
OrderBy("sorting").Find(&columns); err != nil { |
||||
return nil, err |
||||
} |
||||
return columns, nil |
||||
} |
||||
|
||||
// MoveColumnsOnProject sorts columns in a project
|
||||
func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error { |
||||
return db.WithTx(ctx, func(ctx context.Context) error { |
||||
sess := db.GetEngine(ctx) |
||||
columnIDs := util.ValuesOfMap(sortedColumnIDs) |
||||
movedColumns, err := GetColumnsByIDs(ctx, project.ID, columnIDs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(movedColumns) != len(sortedColumnIDs) { |
||||
return errors.New("some columns do not exist") |
||||
} |
||||
|
||||
for _, column := range movedColumns { |
||||
if column.ProjectID != project.ID { |
||||
return fmt.Errorf("column[%d]'s projectID is not equal to project's ID [%d]", column.ProjectID, project.ID) |
||||
} |
||||
} |
||||
|
||||
for sorting, columnID := range sortedColumnIDs { |
||||
if _, err := sess.Exec("UPDATE `project_board` SET sorting=? WHERE id=?", sorting, columnID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -0,0 +1,359 @@ |
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package project |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"regexp" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
"code.gitea.io/gitea/modules/setting" |
||||
"code.gitea.io/gitea/modules/timeutil" |
||||
"code.gitea.io/gitea/modules/util" |
||||
|
||||
"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" // TODO: the legacy table name should be project_column
|
||||
} |
||||
|
||||
// NumIssues return counter of all issues assigned to the column
|
||||
func (c *Column) NumIssues(ctx context.Context) int { |
||||
total, err := db.GetEngine(ctx).Table("project_issue"). |
||||
Where("project_id=?", c.ProjectID). |
||||
And("project_board_id=?", c.ID). |
||||
GroupBy("issue_id"). |
||||
Cols("issue_id"). |
||||
Count() |
||||
if err != nil { |
||||
return 0 |
||||
} |
||||
return int(total) |
||||
} |
||||
|
||||
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) { |
||||
issues := make([]*ProjectIssue, 0, 5) |
||||
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID). |
||||
And("project_board_id=?", c.ID). |
||||
OrderBy("sorting, id"). |
||||
Find(&issues); err != nil { |
||||
return nil, err |
||||
} |
||||
return issues, nil |
||||
} |
||||
|
||||
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 createDefaultColumnsForProject(ctx context.Context, project *Project) error { |
||||
var items []string |
||||
|
||||
switch project.TemplateType { |
||||
case TemplateTypeBugTriage: |
||||
items = setting.Project.ProjectBoardBugTriageType |
||||
case TemplateTypeBasicKanban: |
||||
items = setting.Project.ProjectBoardBasicKanbanType |
||||
case TemplateTypeNone: |
||||
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 |
||||
} |
||||
|
||||
columns := make([]Column, 0, len(items)) |
||||
for _, v := range items { |
||||
columns = append(columns, Column{ |
||||
CreatedUnix: timeutil.TimeStampNow(), |
||||
CreatorID: project.CreatorID, |
||||
Title: v, |
||||
ProjectID: project.ID, |
||||
}) |
||||
} |
||||
|
||||
return db.Insert(ctx, columns) |
||||
}) |
||||
} |
||||
|
||||
// maxProjectColumns max columns allowed in a project, this should not bigger than 127
|
||||
// because sorting is int8 in database
|
||||
const maxProjectColumns = 20 |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
res := struct { |
||||
MaxSorting int64 |
||||
ColumnCount int64 |
||||
}{} |
||||
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as column_count").Table("project_board"). |
||||
Where("project_id=?", column.ProjectID).Get(&res); err != nil { |
||||
return err |
||||
} |
||||
if res.ColumnCount >= maxProjectColumns { |
||||
return fmt.Errorf("NewBoard: maximum number of columns reached") |
||||
} |
||||
column.Sorting = int8(util.Iif(res.ColumnCount > 0, res.MaxSorting+1, 0)) |
||||
_, err := db.GetEngine(ctx).Insert(column) |
||||
return err |
||||
} |
||||
|
||||
// DeleteColumnByID removes all issues references to the project column.
|
||||
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("deleteColumnByID: cannot delete default column") |
||||
} |
||||
|
||||
// move all issues to the default column
|
||||
project, err := GetProjectByID(ctx, column.ProjectID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defaultColumn, err := project.GetDefaultColumn(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = column.moveIssuesToAnotherColumn(ctx, defaultColumn); 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 column of a project
|
||||
func GetColumn(ctx context.Context, columnID int64) (*Column, error) { |
||||
column := new(Column) |
||||
has, err := db.GetEngine(ctx).ID(columnID).Get(column) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrProjectColumnNotExist{ColumnID: columnID} |
||||
} |
||||
|
||||
return column, 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 columns related to a project
|
||||
func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) { |
||||
columns := make([]*Column, 0, 5) |
||||
if err := db.GetEngine(ctx).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) { |
||||
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 |
||||
}) |
||||
} |
||||
|
||||
func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { |
||||
columns := make([]*Column, 0, 5) |
||||
if err := db.GetEngine(ctx). |
||||
Where("project_id =?", projectID). |
||||
In("id", columnsIDs). |
||||
OrderBy("sorting").Find(&columns); err != nil { |
||||
return nil, err |
||||
} |
||||
return columns, nil |
||||
} |
||||
|
||||
// MoveColumnsOnProject sorts columns in a project
|
||||
func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error { |
||||
return db.WithTx(ctx, func(ctx context.Context) error { |
||||
sess := db.GetEngine(ctx) |
||||
columnIDs := util.ValuesOfMap(sortedColumnIDs) |
||||
movedColumns, err := GetColumnsByIDs(ctx, project.ID, columnIDs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(movedColumns) != len(sortedColumnIDs) { |
||||
return errors.New("some columns do not exist") |
||||
} |
||||
|
||||
for _, column := range movedColumns { |
||||
if column.ProjectID != project.ID { |
||||
return fmt.Errorf("column[%d]'s projectID is not equal to project's ID [%d]", column.ProjectID, project.ID) |
||||
} |
||||
} |
||||
|
||||
for sorting, columnID := range sortedColumnIDs { |
||||
if _, err := sess.Exec("UPDATE `project_board` SET sorting=? WHERE id=?", sorting, columnID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -0,0 +1,45 @@ |
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package project |
||||
|
||||
type ( |
||||
// TemplateType is used to represent a project template type
|
||||
TemplateType uint8 |
||||
|
||||
// TemplateConfig is used to identify the template type of project that is being created
|
||||
TemplateConfig struct { |
||||
TemplateType TemplateType |
||||
Translation string |
||||
} |
||||
) |
||||
|
||||
const ( |
||||
// TemplateTypeNone is a project template type that has no predefined columns
|
||||
TemplateTypeNone TemplateType = iota |
||||
|
||||
// TemplateTypeBasicKanban is a project template type that has basic predefined columns
|
||||
TemplateTypeBasicKanban |
||||
|
||||
// TemplateTypeBugTriage is a project template type that has predefined columns suited to hunting down bugs
|
||||
TemplateTypeBugTriage |
||||
) |
||||
|
||||
// GetTemplateConfigs retrieves the template configs of configurations project columns could have
|
||||
func GetTemplateConfigs() []TemplateConfig { |
||||
return []TemplateConfig{ |
||||
{TemplateTypeNone, "repo.projects.type.none"}, |
||||
{TemplateTypeBasicKanban, "repo.projects.type.basic_kanban"}, |
||||
{TemplateTypeBugTriage, "repo.projects.type.bug_triage"}, |
||||
} |
||||
} |
||||
|
||||
// IsTemplateTypeValid checks if the project template type is valid
|
||||
func IsTemplateTypeValid(p TemplateType) bool { |
||||
switch p { |
||||
case TemplateTypeNone, TemplateTypeBasicKanban, TemplateTypeBugTriage: |
||||
return true |
||||
default: |
||||
return false |
||||
} |
||||
} |
Loading…
Reference in new issue