aboutsummaryrefslogtreecommitdiffstats
path: root/models/project/board.go
diff options
context:
space:
mode:
Diffstat (limited to 'models/project/board.go')
-rw-r--r--models/project/board.go289
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
+}