diff options
Diffstat (limited to 'models/project/board.go')
-rw-r--r-- | models/project/board.go | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/models/project/board.go b/models/project/board.go new file mode 100644 index 0000000000..f770a18f59 --- /dev/null +++ b/models/project/board.go @@ -0,0 +1,289 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +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 + + // 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 +) + +// 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() int { + c, err := db.GetEngine(db.DefaultContext).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 + } +} + +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 + } + + 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(board *Board) error { + if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { + return fmt.Errorf("bad color code: %s", board.Color) + } + + _, err := db.GetEngine(db.DefaultContext).Insert(board) + return err +} + +// DeleteBoardByID removes all issues references to the project board. +func DeleteBoardByID(boardID int64) error { + ctx, committer, err := db.TxContext() + 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 { + e := db.GetEngine(ctx) + board, err := getBoard(e, boardID) + if err != nil { + if IsErrProjectBoardNotExist(err) { + return nil + } + + return err + } + + if err = board.removeIssues(e); err != nil { + return err + } + + if _, err := e.ID(board.ID).Delete(board); err != nil { + return err + } + return nil +} + +func deleteBoardByProjectID(e db.Engine, projectID int64) error { + _, err := e.Where("project_id=?", projectID).Delete(&Board{}) + return err +} + +// GetBoard fetches the current board of a project +func GetBoard(boardID int64) (*Board, error) { + return getBoard(db.GetEngine(db.DefaultContext), boardID) +} + +func getBoard(e db.Engine, boardID int64) (*Board, error) { + board := new(Board) + + has, err := e.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(board *Board) error { + return updateBoard(db.GetEngine(db.DefaultContext), board) +} + +func updateBoard(e db.Engine, 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 := e.ID(board.ID).Cols(fieldToUpdate...).Update(board) + + return err +} + +// GetBoards fetches all boards related to a project +// if no default board set, first board is a temporary "Uncategorized" board +func GetBoards(projectID int64) (BoardList, error) { + return getBoards(db.GetEngine(db.DefaultContext), projectID) +} + +func getBoards(e db.Engine, projectID int64) ([]*Board, error) { + boards := make([]*Board, 0, 5) + + if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil { + return nil, err + } + + defaultB, err := getDefaultBoard(e, projectID) + if err != nil { + return nil, err + } + + return append([]*Board{defaultB}, boards...), nil +} + +// getDefaultBoard return default board and create a dummy if none exist +func getDefaultBoard(e db.Engine, projectID int64) (*Board, error) { + var board Board + exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board) + if err != nil { + return nil, err + } + if exist { + return &board, nil + } + + // represents a board for issues not assigned to one + return &Board{ + ProjectID: projectID, + Title: "Uncategorized", + Default: true, + }, nil +} + +// SetDefaultBoard represents a board for issues not assigned to one +// if boardID is 0 unset default +func SetDefaultBoard(projectID, boardID int64) error { + _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{ + "project_id": projectID, + "`default`": true, + }).Cols("`default`").Update(&Board{Default: false}) + if err != nil { + return err + } + + if boardID > 0 { + _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}). + Cols("`default`").Update(&Board{Default: true}) + } + + return err +} + +// UpdateBoardSorting update project board sorting +func UpdateBoardSorting(bs BoardList) error { + for i := range bs { + _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols( + "sorting", + ).Update(bs[i]) + if err != nil { + return err + } + } + return nil +} |