Move project files into models/project sub package (#17704)
* Move project files into models/project sub package * Fix test * Fix test * Fix test * Fix build * Fix test * Fix template bug * Fix bug * Fix lint * Fix test * Fix import * Improve codes Co-authored-by: 6543 <6543@obermui.de>tokarchuk/v1.17
parent
ea6efba9b3
commit
bd97736b9c
@ -0,0 +1,181 @@ |
|||||||
|
// Copyright 2021 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 models |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db" |
||||||
|
project_model "code.gitea.io/gitea/models/project" |
||||||
|
user_model "code.gitea.io/gitea/models/user" |
||||||
|
) |
||||||
|
|
||||||
|
// LoadProject load the project the issue was assigned to
|
||||||
|
func (i *Issue) LoadProject() (err error) { |
||||||
|
return i.loadProject(db.GetEngine(db.DefaultContext)) |
||||||
|
} |
||||||
|
|
||||||
|
func (i *Issue) loadProject(e db.Engine) (err error) { |
||||||
|
if i.Project == nil { |
||||||
|
var p project_model.Project |
||||||
|
if _, err = e.Table("project"). |
||||||
|
Join("INNER", "project_issue", "project.id=project_issue.project_id"). |
||||||
|
Where("project_issue.issue_id = ?", i.ID). |
||||||
|
Get(&p); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
i.Project = &p |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// ProjectID return project id if issue was assigned to one
|
||||||
|
func (i *Issue) ProjectID() int64 { |
||||||
|
return i.projectID(db.GetEngine(db.DefaultContext)) |
||||||
|
} |
||||||
|
|
||||||
|
func (i *Issue) projectID(e db.Engine) int64 { |
||||||
|
var ip project_model.ProjectIssue |
||||||
|
has, err := e.Where("issue_id=?", i.ID).Get(&ip) |
||||||
|
if err != nil || !has { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return ip.ProjectID |
||||||
|
} |
||||||
|
|
||||||
|
// ProjectBoardID return project board id if issue was assigned to one
|
||||||
|
func (i *Issue) ProjectBoardID() int64 { |
||||||
|
return i.projectBoardID(db.GetEngine(db.DefaultContext)) |
||||||
|
} |
||||||
|
|
||||||
|
func (i *Issue) projectBoardID(e db.Engine) int64 { |
||||||
|
var ip project_model.ProjectIssue |
||||||
|
has, err := e.Where("issue_id=?", i.ID).Get(&ip) |
||||||
|
if err != nil || !has { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return ip.ProjectBoardID |
||||||
|
} |
||||||
|
|
||||||
|
// LoadIssuesFromBoard load issues assigned to this board
|
||||||
|
func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) { |
||||||
|
issueList := make([]*Issue, 0, 10) |
||||||
|
|
||||||
|
if b.ID != 0 { |
||||||
|
issues, err := Issues(&IssuesOptions{ |
||||||
|
ProjectBoardID: b.ID, |
||||||
|
ProjectID: b.ProjectID, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
issueList = issues |
||||||
|
} |
||||||
|
|
||||||
|
if b.Default { |
||||||
|
issues, err := Issues(&IssuesOptions{ |
||||||
|
ProjectBoardID: -1, // Issues without ProjectBoardID
|
||||||
|
ProjectID: b.ProjectID, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
issueList = append(issueList, issues...) |
||||||
|
} |
||||||
|
|
||||||
|
if err := IssueList(issueList).LoadComments(); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return issueList, nil |
||||||
|
} |
||||||
|
|
||||||
|
// LoadIssuesFromBoardList load issues assigned to the boards
|
||||||
|
func LoadIssuesFromBoardList(bs project_model.BoardList) (map[int64]IssueList, error) { |
||||||
|
issuesMap := make(map[int64]IssueList, len(bs)) |
||||||
|
for i := range bs { |
||||||
|
il, err := LoadIssuesFromBoard(bs[i]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
issuesMap[bs[i].ID] = il |
||||||
|
} |
||||||
|
return issuesMap, nil |
||||||
|
} |
||||||
|
|
||||||
|
// ChangeProjectAssign changes the project associated with an issue
|
||||||
|
func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error { |
||||||
|
ctx, committer, err := db.TxContext() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer committer.Close() |
||||||
|
|
||||||
|
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return committer.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error { |
||||||
|
e := db.GetEngine(ctx) |
||||||
|
oldProjectID := issue.projectID(e) |
||||||
|
|
||||||
|
if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err := issue.loadRepo(ctx); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if oldProjectID > 0 || newProjectID > 0 { |
||||||
|
if _, err := createComment(ctx, &CreateCommentOptions{ |
||||||
|
Type: CommentTypeProject, |
||||||
|
Doer: doer, |
||||||
|
Repo: issue.Repo, |
||||||
|
Issue: issue, |
||||||
|
OldProjectID: oldProjectID, |
||||||
|
ProjectID: newProjectID, |
||||||
|
}); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_, err := e.Insert(&project_model.ProjectIssue{ |
||||||
|
IssueID: issue.ID, |
||||||
|
ProjectID: newProjectID, |
||||||
|
}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// MoveIssueAcrossProjectBoards move a card from one board to another
|
||||||
|
func MoveIssueAcrossProjectBoards(issue *Issue, board *project_model.Board) error { |
||||||
|
ctx, committer, err := db.TxContext() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer committer.Close() |
||||||
|
sess := db.GetEngine(ctx) |
||||||
|
|
||||||
|
var pis project_model.ProjectIssue |
||||||
|
has, err := sess.Where("issue_id=?", issue.ID).Get(&pis) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if !has { |
||||||
|
return fmt.Errorf("issue has to be added to a project first") |
||||||
|
} |
||||||
|
|
||||||
|
pis.ProjectBoardID = board.ID |
||||||
|
if _, err := sess.ID(pis.ID).Cols("project_board_id").Update(&pis); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return committer.Commit() |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -0,0 +1,100 @@ |
|||||||
|
// 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" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db" |
||||||
|
) |
||||||
|
|
||||||
|
// ProjectIssue saves relation from issue to a project
|
||||||
|
type ProjectIssue struct { //revive:disable-line:exported
|
||||||
|
ID int64 `xorm:"pk autoincr"` |
||||||
|
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"` |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
db.RegisterModel(new(ProjectIssue)) |
||||||
|
} |
||||||
|
|
||||||
|
func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error { |
||||||
|
_, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{}) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// NumIssues return counter of all issues assigned to a project
|
||||||
|
func (p *Project) NumIssues() int { |
||||||
|
c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). |
||||||
|
Where("project_id=?", p.ID). |
||||||
|
GroupBy("issue_id"). |
||||||
|
Cols("issue_id"). |
||||||
|
Count() |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return int(c) |
||||||
|
} |
||||||
|
|
||||||
|
// NumClosedIssues return counter of closed issues assigned to a project
|
||||||
|
func (p *Project) NumClosedIssues() int { |
||||||
|
c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). |
||||||
|
Join("INNER", "issue", "project_issue.issue_id=issue.id"). |
||||||
|
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). |
||||||
|
Cols("issue_id"). |
||||||
|
Count() |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return int(c) |
||||||
|
} |
||||||
|
|
||||||
|
// NumOpenIssues return counter of open issues assigned to a project
|
||||||
|
func (p *Project) NumOpenIssues() int { |
||||||
|
c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). |
||||||
|
Join("INNER", "issue", "project_issue.issue_id=issue.id"). |
||||||
|
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).Count("issue.id") |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return int(c) |
||||||
|
} |
||||||
|
|
||||||
|
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
|
||||||
|
func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) error { |
||||||
|
return db.WithTx(func(ctx context.Context) error { |
||||||
|
sess := db.GetEngine(ctx) |
||||||
|
|
||||||
|
issueIDs := make([]int64, 0, len(sortedIssueIDs)) |
||||||
|
for _, issueID := range sortedIssueIDs { |
||||||
|
issueIDs = append(issueIDs, issueID) |
||||||
|
} |
||||||
|
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if int(count) != len(sortedIssueIDs) { |
||||||
|
return fmt.Errorf("all issues have to be added to a project first") |
||||||
|
} |
||||||
|
|
||||||
|
for sorting, issueID := range sortedIssueIDs { |
||||||
|
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (pb *Board) removeIssues(e db.Engine) error { |
||||||
|
_, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", pb.ID) |
||||||
|
return err |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
// 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 ( |
||||||
|
"path/filepath" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest" |
||||||
|
|
||||||
|
_ "code.gitea.io/gitea/models/repo" |
||||||
|
) |
||||||
|
|
||||||
|
func TestMain(m *testing.M) { |
||||||
|
unittest.MainTest(m, filepath.Join("..", ".."), |
||||||
|
"project.yml", |
||||||
|
"project_board.yml", |
||||||
|
"project_issue.yml", |
||||||
|
"repository.yml", |
||||||
|
) |
||||||
|
} |
@ -1,321 +0,0 @@ |
|||||||
// 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 models |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"regexp" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db" |
|
||||||
"code.gitea.io/gitea/modules/setting" |
|
||||||
"code.gitea.io/gitea/modules/timeutil" |
|
||||||
|
|
||||||
"xorm.io/builder" |
|
||||||
) |
|
||||||
|
|
||||||
type ( |
|
||||||
// ProjectBoardType is used to represent a project board type
|
|
||||||
ProjectBoardType uint8 |
|
||||||
|
|
||||||
// ProjectBoardList is a list of all project boards in a repository
|
|
||||||
ProjectBoardList []*ProjectBoard |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
// ProjectBoardTypeNone is a project board type that has no predefined columns
|
|
||||||
ProjectBoardTypeNone ProjectBoardType = iota |
|
||||||
|
|
||||||
// ProjectBoardTypeBasicKanban is a project board type that has basic predefined columns
|
|
||||||
ProjectBoardTypeBasicKanban |
|
||||||
|
|
||||||
// ProjectBoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
|
|
||||||
ProjectBoardTypeBugTriage |
|
||||||
) |
|
||||||
|
|
||||||
// BoardColorPattern is a regexp witch can validate BoardColor
|
|
||||||
var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") |
|
||||||
|
|
||||||
// ProjectBoard is used to represent boards on a project
|
|
||||||
type ProjectBoard 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"` |
|
||||||
|
|
||||||
Issues []*Issue `xorm:"-"` |
|
||||||
} |
|
||||||
|
|
||||||
func init() { |
|
||||||
db.RegisterModel(new(ProjectBoard)) |
|
||||||
} |
|
||||||
|
|
||||||
// IsProjectBoardTypeValid checks if the project board type is valid
|
|
||||||
func IsProjectBoardTypeValid(p ProjectBoardType) bool { |
|
||||||
switch p { |
|
||||||
case ProjectBoardTypeNone, ProjectBoardTypeBasicKanban, ProjectBoardTypeBugTriage: |
|
||||||
return true |
|
||||||
default: |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func createBoardsForProjectsType(sess db.Engine, project *Project) error { |
|
||||||
var items []string |
|
||||||
|
|
||||||
switch project.BoardType { |
|
||||||
|
|
||||||
case ProjectBoardTypeBugTriage: |
|
||||||
items = setting.Project.ProjectBoardBugTriageType |
|
||||||
|
|
||||||
case ProjectBoardTypeBasicKanban: |
|
||||||
items = setting.Project.ProjectBoardBasicKanbanType |
|
||||||
|
|
||||||
case ProjectBoardTypeNone: |
|
||||||
fallthrough |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
if len(items) == 0 { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
boards := make([]ProjectBoard, 0, len(items)) |
|
||||||
|
|
||||||
for _, v := range items { |
|
||||||
boards = append(boards, ProjectBoard{ |
|
||||||
CreatedUnix: timeutil.TimeStampNow(), |
|
||||||
CreatorID: project.CreatorID, |
|
||||||
Title: v, |
|
||||||
ProjectID: project.ID, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
_, err := sess.Insert(boards) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// NewProjectBoard adds a new project board to a given project
|
|
||||||
func NewProjectBoard(board *ProjectBoard) 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 |
|
||||||
} |
|
||||||
|
|
||||||
// DeleteProjectBoardByID removes all issues references to the project board.
|
|
||||||
func DeleteProjectBoardByID(boardID int64) error { |
|
||||||
ctx, committer, err := db.TxContext() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer committer.Close() |
|
||||||
|
|
||||||
if err := deleteProjectBoardByID(db.GetEngine(ctx), boardID); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
return committer.Commit() |
|
||||||
} |
|
||||||
|
|
||||||
func deleteProjectBoardByID(e db.Engine, boardID int64) error { |
|
||||||
board, err := getProjectBoard(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 deleteProjectBoardByProjectID(e db.Engine, projectID int64) error { |
|
||||||
_, err := e.Where("project_id=?", projectID).Delete(&ProjectBoard{}) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// GetProjectBoard fetches the current board of a project
|
|
||||||
func GetProjectBoard(boardID int64) (*ProjectBoard, error) { |
|
||||||
return getProjectBoard(db.GetEngine(db.DefaultContext), boardID) |
|
||||||
} |
|
||||||
|
|
||||||
func getProjectBoard(e db.Engine, boardID int64) (*ProjectBoard, error) { |
|
||||||
board := new(ProjectBoard) |
|
||||||
|
|
||||||
has, err := e.ID(boardID).Get(board) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} else if !has { |
|
||||||
return nil, ErrProjectBoardNotExist{BoardID: boardID} |
|
||||||
} |
|
||||||
|
|
||||||
return board, nil |
|
||||||
} |
|
||||||
|
|
||||||
// UpdateProjectBoard updates a project board
|
|
||||||
func UpdateProjectBoard(board *ProjectBoard) error { |
|
||||||
return updateProjectBoard(db.GetEngine(db.DefaultContext), board) |
|
||||||
} |
|
||||||
|
|
||||||
func updateProjectBoard(e db.Engine, board *ProjectBoard) 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 |
|
||||||
} |
|
||||||
|
|
||||||
// GetProjectBoards fetches all boards related to a project
|
|
||||||
// if no default board set, first board is a temporary "Uncategorized" board
|
|
||||||
func GetProjectBoards(projectID int64) (ProjectBoardList, error) { |
|
||||||
return getProjectBoards(db.GetEngine(db.DefaultContext), projectID) |
|
||||||
} |
|
||||||
|
|
||||||
func getProjectBoards(e db.Engine, projectID int64) ([]*ProjectBoard, error) { |
|
||||||
boards := make([]*ProjectBoard, 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([]*ProjectBoard{defaultB}, boards...), nil |
|
||||||
} |
|
||||||
|
|
||||||
// getDefaultBoard return default board and create a dummy if none exist
|
|
||||||
func getDefaultBoard(e db.Engine, projectID int64) (*ProjectBoard, error) { |
|
||||||
var board ProjectBoard |
|
||||||
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 &ProjectBoard{ |
|
||||||
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(&ProjectBoard{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(&ProjectBoard{Default: true}) |
|
||||||
} |
|
||||||
|
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// LoadIssues load issues assigned to this board
|
|
||||||
func (b *ProjectBoard) LoadIssues() (IssueList, error) { |
|
||||||
issueList := make([]*Issue, 0, 10) |
|
||||||
|
|
||||||
if b.ID != 0 { |
|
||||||
issues, err := Issues(&IssuesOptions{ |
|
||||||
ProjectBoardID: b.ID, |
|
||||||
ProjectID: b.ProjectID, |
|
||||||
SortType: "project-column-sorting", |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
issueList = issues |
|
||||||
} |
|
||||||
|
|
||||||
if b.Default { |
|
||||||
issues, err := Issues(&IssuesOptions{ |
|
||||||
ProjectBoardID: -1, // Issues without ProjectBoardID
|
|
||||||
ProjectID: b.ProjectID, |
|
||||||
SortType: "project-column-sorting", |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
issueList = append(issueList, issues...) |
|
||||||
} |
|
||||||
|
|
||||||
if err := IssueList(issueList).LoadComments(); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
b.Issues = issueList |
|
||||||
return issueList, nil |
|
||||||
} |
|
||||||
|
|
||||||
// LoadIssues load issues assigned to the boards
|
|
||||||
func (bs ProjectBoardList) LoadIssues() (IssueList, error) { |
|
||||||
issues := make(IssueList, 0, len(bs)*10) |
|
||||||
for i := range bs { |
|
||||||
il, err := bs[i].LoadIssues() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
bs[i].Issues = il |
|
||||||
issues = append(issues, il...) |
|
||||||
} |
|
||||||
return issues, nil |
|
||||||
} |
|
||||||
|
|
||||||
// UpdateProjectBoardSorting update project board sorting
|
|
||||||
func UpdateProjectBoardSorting(bs ProjectBoardList) 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 |
|
||||||
} |
|
@ -1,218 +0,0 @@ |
|||||||
// 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 models |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db" |
|
||||||
user_model "code.gitea.io/gitea/models/user" |
|
||||||
) |
|
||||||
|
|
||||||
// ProjectIssue saves relation from issue to a project
|
|
||||||
type ProjectIssue struct { |
|
||||||
ID int64 `xorm:"pk autoincr"` |
|
||||||
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"` |
|
||||||
Sorting int64 `xorm:"NOT NULL DEFAULT 0"` |
|
||||||
} |
|
||||||
|
|
||||||
func init() { |
|
||||||
db.RegisterModel(new(ProjectIssue)) |
|
||||||
} |
|
||||||
|
|
||||||
func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error { |
|
||||||
_, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{}) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// ___
|
|
||||||
// |_ _|___ ___ _ _ ___
|
|
||||||
// | |/ __/ __| | | |/ _ \
|
|
||||||
// | |\__ \__ \ |_| | __/
|
|
||||||
// |___|___/___/\__,_|\___|
|
|
||||||
|
|
||||||
// LoadProject load the project the issue was assigned to
|
|
||||||
func (i *Issue) LoadProject() (err error) { |
|
||||||
return i.loadProject(db.GetEngine(db.DefaultContext)) |
|
||||||
} |
|
||||||
|
|
||||||
func (i *Issue) loadProject(e db.Engine) (err error) { |
|
||||||
if i.Project == nil { |
|
||||||
var p Project |
|
||||||
if _, err = e.Table("project"). |
|
||||||
Join("INNER", "project_issue", "project.id=project_issue.project_id"). |
|
||||||
Where("project_issue.issue_id = ?", i.ID). |
|
||||||
Get(&p); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
i.Project = &p |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// ProjectID return project id if issue was assigned to one
|
|
||||||
func (i *Issue) ProjectID() int64 { |
|
||||||
return i.projectID(db.GetEngine(db.DefaultContext)) |
|
||||||
} |
|
||||||
|
|
||||||
func (i *Issue) projectID(e db.Engine) int64 { |
|
||||||
var ip ProjectIssue |
|
||||||
has, err := e.Where("issue_id=?", i.ID).Get(&ip) |
|
||||||
if err != nil || !has { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return ip.ProjectID |
|
||||||
} |
|
||||||
|
|
||||||
// ProjectBoardID return project board id if issue was assigned to one
|
|
||||||
func (i *Issue) ProjectBoardID() int64 { |
|
||||||
return i.projectBoardID(db.GetEngine(db.DefaultContext)) |
|
||||||
} |
|
||||||
|
|
||||||
func (i *Issue) projectBoardID(e db.Engine) int64 { |
|
||||||
var ip ProjectIssue |
|
||||||
has, err := e.Where("issue_id=?", i.ID).Get(&ip) |
|
||||||
if err != nil || !has { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return ip.ProjectBoardID |
|
||||||
} |
|
||||||
|
|
||||||
// ____ _ _
|
|
||||||
// | _ \ _ __ ___ (_) ___ ___| |_
|
|
||||||
// | |_) | '__/ _ \| |/ _ \/ __| __|
|
|
||||||
// | __/| | | (_) | | __/ (__| |_
|
|
||||||
// |_| |_| \___// |\___|\___|\__|
|
|
||||||
// |__/
|
|
||||||
|
|
||||||
// NumIssues return counter of all issues assigned to a project
|
|
||||||
func (p *Project) NumIssues() int { |
|
||||||
c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). |
|
||||||
Where("project_id=?", p.ID). |
|
||||||
GroupBy("issue_id"). |
|
||||||
Cols("issue_id"). |
|
||||||
Count() |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return int(c) |
|
||||||
} |
|
||||||
|
|
||||||
// NumClosedIssues return counter of closed issues assigned to a project
|
|
||||||
func (p *Project) NumClosedIssues() int { |
|
||||||
c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). |
|
||||||
Join("INNER", "issue", "project_issue.issue_id=issue.id"). |
|
||||||
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). |
|
||||||
Cols("issue_id"). |
|
||||||
Count() |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return int(c) |
|
||||||
} |
|
||||||
|
|
||||||
// NumOpenIssues return counter of open issues assigned to a project
|
|
||||||
func (p *Project) NumOpenIssues() int { |
|
||||||
c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). |
|
||||||
Join("INNER", "issue", "project_issue.issue_id=issue.id"). |
|
||||||
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false). |
|
||||||
Cols("issue_id"). |
|
||||||
Count() |
|
||||||
if err != nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return int(c) |
|
||||||
} |
|
||||||
|
|
||||||
// ChangeProjectAssign changes the project associated with an issue
|
|
||||||
func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error { |
|
||||||
ctx, committer, err := db.TxContext() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
defer committer.Close() |
|
||||||
|
|
||||||
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
return committer.Commit() |
|
||||||
} |
|
||||||
|
|
||||||
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error { |
|
||||||
e := db.GetEngine(ctx) |
|
||||||
oldProjectID := issue.projectID(e) |
|
||||||
|
|
||||||
if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
if err := issue.loadRepo(ctx); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
if oldProjectID > 0 || newProjectID > 0 { |
|
||||||
if _, err := createComment(ctx, &CreateCommentOptions{ |
|
||||||
Type: CommentTypeProject, |
|
||||||
Doer: doer, |
|
||||||
Repo: issue.Repo, |
|
||||||
Issue: issue, |
|
||||||
OldProjectID: oldProjectID, |
|
||||||
ProjectID: newProjectID, |
|
||||||
}); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_, err := e.Insert(&ProjectIssue{ |
|
||||||
IssueID: issue.ID, |
|
||||||
ProjectID: newProjectID, |
|
||||||
}) |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// ____ _ _ ____ _
|
|
||||||
// | _ \ _ __ ___ (_) ___ ___| |_| __ ) ___ __ _ _ __ __| |
|
|
||||||
// | |_) | '__/ _ \| |/ _ \/ __| __| _ \ / _ \ / _` | '__/ _` |
|
|
||||||
// | __/| | | (_) | | __/ (__| |_| |_) | (_) | (_| | | | (_| |
|
|
||||||
// |_| |_| \___// |\___|\___|\__|____/ \___/ \__,_|_| \__,_|
|
|
||||||
// |__/
|
|
||||||
|
|
||||||
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
|
|
||||||
func MoveIssuesOnProjectBoard(board *ProjectBoard, sortedIssueIDs map[int64]int64) error { |
|
||||||
return db.WithTx(func(ctx context.Context) error { |
|
||||||
sess := db.GetEngine(ctx) |
|
||||||
|
|
||||||
issueIDs := make([]int64, 0, len(sortedIssueIDs)) |
|
||||||
for _, issueID := range sortedIssueIDs { |
|
||||||
issueIDs = append(issueIDs, issueID) |
|
||||||
} |
|
||||||
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if int(count) != len(sortedIssueIDs) { |
|
||||||
return fmt.Errorf("all issues have to be added to a project first") |
|
||||||
} |
|
||||||
|
|
||||||
for sorting, issueID := range sortedIssueIDs { |
|
||||||
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func (pb *ProjectBoard) removeIssues(e db.Engine) error { |
|
||||||
_, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0, sorting = 0 WHERE project_board_id = ? ", pb.ID) |
|
||||||
return err |
|
||||||
} |
|
Loading…
Reference in new issue