code.gitea.io/gitea@v1.21.7/models/project/board.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package project
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"regexp"
    10  
    11  	"code.gitea.io/gitea/models/db"
    12  	"code.gitea.io/gitea/modules/setting"
    13  	"code.gitea.io/gitea/modules/timeutil"
    14  
    15  	"xorm.io/builder"
    16  )
    17  
    18  type (
    19  	// BoardType is used to represent a project board type
    20  	BoardType uint8
    21  
    22  	// CardType is used to represent a project board card type
    23  	CardType uint8
    24  
    25  	// BoardList is a list of all project boards in a repository
    26  	BoardList []*Board
    27  )
    28  
    29  const (
    30  	// BoardTypeNone is a project board type that has no predefined columns
    31  	BoardTypeNone BoardType = iota
    32  
    33  	// BoardTypeBasicKanban is a project board type that has basic predefined columns
    34  	BoardTypeBasicKanban
    35  
    36  	// BoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
    37  	BoardTypeBugTriage
    38  )
    39  
    40  const (
    41  	// CardTypeTextOnly is a project board card type that is text only
    42  	CardTypeTextOnly CardType = iota
    43  
    44  	// CardTypeImagesAndText is a project board card type that has images and text
    45  	CardTypeImagesAndText
    46  )
    47  
    48  // BoardColorPattern is a regexp witch can validate BoardColor
    49  var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
    50  
    51  // Board is used to represent boards on a project
    52  type Board struct {
    53  	ID      int64 `xorm:"pk autoincr"`
    54  	Title   string
    55  	Default bool   `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
    56  	Sorting int8   `xorm:"NOT NULL DEFAULT 0"`
    57  	Color   string `xorm:"VARCHAR(7)"`
    58  
    59  	ProjectID int64 `xorm:"INDEX NOT NULL"`
    60  	CreatorID int64 `xorm:"NOT NULL"`
    61  
    62  	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
    63  	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
    64  }
    65  
    66  // TableName return the real table name
    67  func (Board) TableName() string {
    68  	return "project_board"
    69  }
    70  
    71  // NumIssues return counter of all issues assigned to the board
    72  func (b *Board) NumIssues(ctx context.Context) int {
    73  	c, err := db.GetEngine(ctx).Table("project_issue").
    74  		Where("project_id=?", b.ProjectID).
    75  		And("project_board_id=?", b.ID).
    76  		GroupBy("issue_id").
    77  		Cols("issue_id").
    78  		Count()
    79  	if err != nil {
    80  		return 0
    81  	}
    82  	return int(c)
    83  }
    84  
    85  func init() {
    86  	db.RegisterModel(new(Board))
    87  }
    88  
    89  // IsBoardTypeValid checks if the project board type is valid
    90  func IsBoardTypeValid(p BoardType) bool {
    91  	switch p {
    92  	case BoardTypeNone, BoardTypeBasicKanban, BoardTypeBugTriage:
    93  		return true
    94  	default:
    95  		return false
    96  	}
    97  }
    98  
    99  // IsCardTypeValid checks if the project board card type is valid
   100  func IsCardTypeValid(p CardType) bool {
   101  	switch p {
   102  	case CardTypeTextOnly, CardTypeImagesAndText:
   103  		return true
   104  	default:
   105  		return false
   106  	}
   107  }
   108  
   109  func createBoardsForProjectsType(ctx context.Context, project *Project) error {
   110  	var items []string
   111  
   112  	switch project.BoardType {
   113  
   114  	case BoardTypeBugTriage:
   115  		items = setting.Project.ProjectBoardBugTriageType
   116  
   117  	case BoardTypeBasicKanban:
   118  		items = setting.Project.ProjectBoardBasicKanbanType
   119  
   120  	case BoardTypeNone:
   121  		fallthrough
   122  	default:
   123  		return nil
   124  	}
   125  
   126  	if len(items) == 0 {
   127  		return nil
   128  	}
   129  
   130  	boards := make([]Board, 0, len(items))
   131  
   132  	for _, v := range items {
   133  		boards = append(boards, Board{
   134  			CreatedUnix: timeutil.TimeStampNow(),
   135  			CreatorID:   project.CreatorID,
   136  			Title:       v,
   137  			ProjectID:   project.ID,
   138  		})
   139  	}
   140  
   141  	return db.Insert(ctx, boards)
   142  }
   143  
   144  // NewBoard adds a new project board to a given project
   145  func NewBoard(ctx context.Context, board *Board) error {
   146  	if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
   147  		return fmt.Errorf("bad color code: %s", board.Color)
   148  	}
   149  
   150  	_, err := db.GetEngine(ctx).Insert(board)
   151  	return err
   152  }
   153  
   154  // DeleteBoardByID removes all issues references to the project board.
   155  func DeleteBoardByID(ctx context.Context, boardID int64) error {
   156  	ctx, committer, err := db.TxContext(ctx)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	defer committer.Close()
   161  
   162  	if err := deleteBoardByID(ctx, boardID); err != nil {
   163  		return err
   164  	}
   165  
   166  	return committer.Commit()
   167  }
   168  
   169  func deleteBoardByID(ctx context.Context, boardID int64) error {
   170  	board, err := GetBoard(ctx, boardID)
   171  	if err != nil {
   172  		if IsErrProjectBoardNotExist(err) {
   173  			return nil
   174  		}
   175  
   176  		return err
   177  	}
   178  
   179  	if err = board.removeIssues(ctx); err != nil {
   180  		return err
   181  	}
   182  
   183  	if _, err := db.GetEngine(ctx).ID(board.ID).NoAutoCondition().Delete(board); err != nil {
   184  		return err
   185  	}
   186  	return nil
   187  }
   188  
   189  func deleteBoardByProjectID(ctx context.Context, projectID int64) error {
   190  	_, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&Board{})
   191  	return err
   192  }
   193  
   194  // GetBoard fetches the current board of a project
   195  func GetBoard(ctx context.Context, boardID int64) (*Board, error) {
   196  	board := new(Board)
   197  
   198  	has, err := db.GetEngine(ctx).ID(boardID).Get(board)
   199  	if err != nil {
   200  		return nil, err
   201  	} else if !has {
   202  		return nil, ErrProjectBoardNotExist{BoardID: boardID}
   203  	}
   204  
   205  	return board, nil
   206  }
   207  
   208  // UpdateBoard updates a project board
   209  func UpdateBoard(ctx context.Context, board *Board) error {
   210  	var fieldToUpdate []string
   211  
   212  	if board.Sorting != 0 {
   213  		fieldToUpdate = append(fieldToUpdate, "sorting")
   214  	}
   215  
   216  	if board.Title != "" {
   217  		fieldToUpdate = append(fieldToUpdate, "title")
   218  	}
   219  
   220  	if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
   221  		return fmt.Errorf("bad color code: %s", board.Color)
   222  	}
   223  	fieldToUpdate = append(fieldToUpdate, "color")
   224  
   225  	_, err := db.GetEngine(ctx).ID(board.ID).Cols(fieldToUpdate...).Update(board)
   226  
   227  	return err
   228  }
   229  
   230  // GetBoards fetches all boards related to a project
   231  // if no default board set, first board is a temporary "Uncategorized" board
   232  func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
   233  	boards := make([]*Board, 0, 5)
   234  
   235  	if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("Sorting").Find(&boards); err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	defaultB, err := p.getDefaultBoard(ctx)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	return append([]*Board{defaultB}, boards...), nil
   245  }
   246  
   247  // getDefaultBoard return default board and create a dummy if none exist
   248  func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
   249  	var board Board
   250  	exist, err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, true).Get(&board)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	if exist {
   255  		return &board, nil
   256  	}
   257  
   258  	// represents a board for issues not assigned to one
   259  	return &Board{
   260  		ProjectID: p.ID,
   261  		Title:     "Uncategorized",
   262  		Default:   true,
   263  	}, nil
   264  }
   265  
   266  // SetDefaultBoard represents a board for issues not assigned to one
   267  // if boardID is 0 unset default
   268  func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error {
   269  	_, err := db.GetEngine(ctx).Where(builder.Eq{
   270  		"project_id": projectID,
   271  		"`default`":  true,
   272  	}).Cols("`default`").Update(&Board{Default: false})
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	if boardID > 0 {
   278  		_, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}).
   279  			Cols("`default`").Update(&Board{Default: true})
   280  	}
   281  
   282  	return err
   283  }
   284  
   285  // UpdateBoardSorting update project board sorting
   286  func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
   287  	for i := range bs {
   288  		_, err := db.GetEngine(ctx).ID(bs[i].ID).Cols(
   289  			"sorting",
   290  		).Update(bs[i])
   291  		if err != nil {
   292  			return err
   293  		}
   294  	}
   295  	return nil
   296  }