mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 21:08:25 +00:00 
			
		
		
		
	Rename project board -> column to make the UI less confusion
This commit is contained in:
		@@ -30,7 +30,7 @@ type Statistic struct {
 | 
			
		||||
		Mirror, Release, AuthSource, Webhook,
 | 
			
		||||
		Milestone, Label, HookTask,
 | 
			
		||||
		Team, UpdateTask, Project,
 | 
			
		||||
		ProjectBoard, Attachment,
 | 
			
		||||
		ProjectColumn, Attachment,
 | 
			
		||||
		Branches, Tags, CommitStatus int64
 | 
			
		||||
		IssueByLabel      []IssueByLabelCount
 | 
			
		||||
		IssueByRepository []IssueByRepositoryCount
 | 
			
		||||
@@ -115,6 +115,6 @@ func GetStatistic(ctx context.Context) (stats Statistic) {
 | 
			
		||||
	stats.Counter.Team, _ = e.Count(new(organization.Team))
 | 
			
		||||
	stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
 | 
			
		||||
	stats.Counter.Project, _ = e.Count(new(project_model.Project))
 | 
			
		||||
	stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board))
 | 
			
		||||
	stats.Counter.ProjectColumn, _ = e.Count(new(project_model.Column))
 | 
			
		||||
	return stats
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,20 +37,20 @@ func (issue *Issue) projectID(ctx context.Context) int64 {
 | 
			
		||||
	return ip.ProjectID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProjectBoardID return project board id if issue was assigned to one
 | 
			
		||||
func (issue *Issue) ProjectBoardID(ctx context.Context) int64 {
 | 
			
		||||
// ProjectColumnID return project column id if issue was assigned to one
 | 
			
		||||
func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
 | 
			
		||||
	var ip project_model.ProjectIssue
 | 
			
		||||
	has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 | 
			
		||||
	if err != nil || !has {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return ip.ProjectBoardID
 | 
			
		||||
	return ip.ProjectColumnID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadIssuesFromBoard load issues assigned to this board
 | 
			
		||||
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
 | 
			
		||||
// LoadIssuesFromColumn load issues assigned to this board
 | 
			
		||||
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueList, error) {
 | 
			
		||||
	issueList, err := Issues(ctx, &IssuesOptions{
 | 
			
		||||
		ProjectBoardID: b.ID,
 | 
			
		||||
		ProjectColumnID: b.ID,
 | 
			
		||||
		ProjectID:       b.ProjectID,
 | 
			
		||||
		SortType:        "project-column-sorting",
 | 
			
		||||
	})
 | 
			
		||||
@@ -60,7 +60,7 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList
 | 
			
		||||
 | 
			
		||||
	if b.Default {
 | 
			
		||||
		issues, err := Issues(ctx, &IssuesOptions{
 | 
			
		||||
			ProjectBoardID: db.NoConditionID,
 | 
			
		||||
			ProjectColumnID: db.NoConditionID,
 | 
			
		||||
			ProjectID:       b.ProjectID,
 | 
			
		||||
			SortType:        "project-column-sorting",
 | 
			
		||||
		})
 | 
			
		||||
@@ -77,11 +77,11 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList
 | 
			
		||||
	return issueList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadIssuesFromBoardList load issues assigned to the boards
 | 
			
		||||
func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (map[int64]IssueList, error) {
 | 
			
		||||
// LoadIssuesFromColumnList load issues assigned to the boards
 | 
			
		||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList) (map[int64]IssueList, error) {
 | 
			
		||||
	issuesMap := make(map[int64]IssueList, len(bs))
 | 
			
		||||
	for i := range bs {
 | 
			
		||||
		il, err := LoadIssuesFromBoard(ctx, bs[i])
 | 
			
		||||
		il, err := LoadIssuesFromColumn(ctx, bs[i])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ type IssuesOptions struct { //nolint
 | 
			
		||||
	SubscriberID       int64
 | 
			
		||||
	MilestoneIDs       []int64
 | 
			
		||||
	ProjectID          int64
 | 
			
		||||
	ProjectBoardID     int64
 | 
			
		||||
	ProjectColumnID    int64
 | 
			
		||||
	IsClosed           optional.Option[bool]
 | 
			
		||||
	IsPull             optional.Option[bool]
 | 
			
		||||
	LabelIDs           []int64
 | 
			
		||||
@@ -169,12 +169,12 @@ func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sessio
 | 
			
		||||
	return sess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func applyProjectBoardCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
 | 
			
		||||
	// opts.ProjectBoardID == 0 means all project boards,
 | 
			
		||||
func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
 | 
			
		||||
	// opts.ProjectColumnID == 0 means all project columns,
 | 
			
		||||
	// do not need to apply any condition
 | 
			
		||||
	if opts.ProjectBoardID > 0 {
 | 
			
		||||
		sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID}))
 | 
			
		||||
	} else if opts.ProjectBoardID == db.NoConditionID {
 | 
			
		||||
	if opts.ProjectColumnID > 0 {
 | 
			
		||||
		sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectColumnID}))
 | 
			
		||||
	} else if opts.ProjectColumnID == db.NoConditionID {
 | 
			
		||||
		sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
 | 
			
		||||
	}
 | 
			
		||||
	return sess
 | 
			
		||||
@@ -246,7 +246,7 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
 | 
			
		||||
 | 
			
		||||
	applyProjectCondition(sess, opts)
 | 
			
		||||
 | 
			
		||||
	applyProjectBoardCondition(sess, opts)
 | 
			
		||||
	applyProjectColumnCondition(sess, opts)
 | 
			
		||||
 | 
			
		||||
	if opts.IsPull.Has() {
 | 
			
		||||
		sess.And("issue.is_pull=?", opts.IsPull.Value())
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import (
 | 
			
		||||
 | 
			
		||||
func Test_CheckProjectColumnsConsistency(t *testing.T) {
 | 
			
		||||
	// Prepare and load the testing database
 | 
			
		||||
	x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Board))
 | 
			
		||||
	x, deferable := base.PrepareTestEnv(t, 0, new(project.Project), new(project.Column))
 | 
			
		||||
	defer deferable()
 | 
			
		||||
	if x == nil || t.Failed() {
 | 
			
		||||
		return
 | 
			
		||||
@@ -23,22 +23,22 @@ func Test_CheckProjectColumnsConsistency(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, CheckProjectColumnsConsistency(x))
 | 
			
		||||
 | 
			
		||||
	// check if default board was added
 | 
			
		||||
	var defaultBoard project.Board
 | 
			
		||||
	has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultBoard)
 | 
			
		||||
	// check if default column was added
 | 
			
		||||
	var defaultColumn project.Column
 | 
			
		||||
	has, err := x.Where("project_id=? AND `default` = ?", 1, true).Get(&defaultColumn)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, has)
 | 
			
		||||
	assert.Equal(t, int64(1), defaultBoard.ProjectID)
 | 
			
		||||
	assert.True(t, defaultBoard.Default)
 | 
			
		||||
	assert.Equal(t, int64(1), defaultColumn.ProjectID)
 | 
			
		||||
	assert.True(t, defaultColumn.Default)
 | 
			
		||||
 | 
			
		||||
	// check if multiple defaults, previous were removed and last will be kept
 | 
			
		||||
	expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2)
 | 
			
		||||
	expectDefaultColumn, err := project.GetColumn(db.DefaultContext, 2)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(2), expectDefaultBoard.ProjectID)
 | 
			
		||||
	assert.False(t, expectDefaultBoard.Default)
 | 
			
		||||
	assert.Equal(t, int64(2), expectDefaultColumn.ProjectID)
 | 
			
		||||
	assert.False(t, expectDefaultColumn.Default)
 | 
			
		||||
 | 
			
		||||
	expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3)
 | 
			
		||||
	expectNonDefaultColumn, err := project.GetColumn(db.DefaultContext, 3)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID)
 | 
			
		||||
	assert.True(t, expectNonDefaultBoard.Default)
 | 
			
		||||
	assert.Equal(t, int64(2), expectNonDefaultColumn.ProjectID)
 | 
			
		||||
	assert.True(t, expectNonDefaultColumn.Default)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,320 +0,0 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
	// 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 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, 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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = board.removeIssues(ctx); 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=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defaultB, err := p.getDefaultBoard(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return append([]*Board{defaultB}, 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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package project
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetDefaultBoard(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if default board was added
 | 
			
		||||
	board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(5), board.ProjectID)
 | 
			
		||||
	assert.Equal(t, "Uncategorized", board.Title)
 | 
			
		||||
 | 
			
		||||
	projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if multiple defaults were removed
 | 
			
		||||
	board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(6), board.ProjectID)
 | 
			
		||||
	assert.Equal(t, int64(9), board.ID)
 | 
			
		||||
 | 
			
		||||
	// set 8 as default board
 | 
			
		||||
	assert.NoError(t, SetDefaultBoard(db.DefaultContext, board.ProjectID, 8))
 | 
			
		||||
 | 
			
		||||
	// then 9 will become a non-default board
 | 
			
		||||
	board, err = GetBoard(db.DefaultContext, 9)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(6), board.ProjectID)
 | 
			
		||||
	assert.False(t, board.Default)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										287
									
								
								models/project/column.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								models/project/column.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
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 (
 | 
			
		||||
 | 
			
		||||
	// 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" // FIXME: the legacy table name should be project_column
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumIssues return counter of all issues assigned to the column
 | 
			
		||||
func (b *Column) 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 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 createColumnsForProjectsBoradViewType(ctx context.Context, project *Project) error {
 | 
			
		||||
	var items []string
 | 
			
		||||
 | 
			
		||||
	switch project.BoardViewType {
 | 
			
		||||
	case BoardViewTypeBugTriage:
 | 
			
		||||
		items = setting.Project.ProjectBoardBugTriageType
 | 
			
		||||
	case BoardViewTypeBasicKanban:
 | 
			
		||||
		items = setting.Project.ProjectBoardBasicKanbanType
 | 
			
		||||
	case BoardViewTypeNone:
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		boards := make([]Column, 0, len(items))
 | 
			
		||||
		for _, v := range items {
 | 
			
		||||
			boards = append(boards, Column{
 | 
			
		||||
				CreatedUnix: timeutil.TimeStampNow(),
 | 
			
		||||
				CreatorID:   project.CreatorID,
 | 
			
		||||
				Title:       v,
 | 
			
		||||
				ProjectID:   project.ID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return db.Insert(ctx, boards)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := db.GetEngine(ctx).Insert(column)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteColumnByID removes all issues references to the project board.
 | 
			
		||||
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("deleteBoardByID: cannot delete default board")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = column.removeIssues(ctx); 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 board of a project
 | 
			
		||||
func GetColumn(ctx context.Context, columnID int64) (*Column, error) {
 | 
			
		||||
	board := new(Column)
 | 
			
		||||
	has, err := db.GetEngine(ctx).ID(columnID).Get(board)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrProjectColumnNotExist{ColumnID: columnID}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return board, 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 boards related to a project
 | 
			
		||||
func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) {
 | 
			
		||||
	boards := make([]*Column, 0, 5)
 | 
			
		||||
 | 
			
		||||
	if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defaultB, err := p.getDefaultColumn(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return append([]*Column{defaultB}, boards...), 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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								models/project/column_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								models/project/column_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package project
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetDefaultcolumn(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	projectWithoutDefault, err := GetProjectByID(db.DefaultContext, 5)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if default column was added
 | 
			
		||||
	column, err := projectWithoutDefault.getDefaultColumn(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(5), column.ProjectID)
 | 
			
		||||
	assert.Equal(t, "Uncategorized", column.Title)
 | 
			
		||||
 | 
			
		||||
	projectWithMultipleDefaults, err := GetProjectByID(db.DefaultContext, 6)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// check if multiple defaults were removed
 | 
			
		||||
	column, err = projectWithMultipleDefaults.getDefaultColumn(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(6), column.ProjectID)
 | 
			
		||||
	assert.Equal(t, int64(9), column.ID)
 | 
			
		||||
 | 
			
		||||
	// set 8 as default column
 | 
			
		||||
	assert.NoError(t, SetDefaultColumn(db.DefaultContext, column.ProjectID, 8))
 | 
			
		||||
 | 
			
		||||
	// then 9 will become a non-default column
 | 
			
		||||
	column, err = GetColumn(db.DefaultContext, 9)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, int64(6), column.ProjectID)
 | 
			
		||||
	assert.False(t, column.Default)
 | 
			
		||||
}
 | 
			
		||||
@@ -17,10 +17,10 @@ type ProjectIssue struct { //revive:disable-line:exported
 | 
			
		||||
	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"`
 | 
			
		||||
	// If 0, then it has not been added to a specific column in the project
 | 
			
		||||
	ProjectColumnID int64 `xorm:"'project_board_id' INDEX"`
 | 
			
		||||
 | 
			
		||||
	// the sorting order on the board
 | 
			
		||||
	// the sorting order on the column
 | 
			
		||||
	Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -75,8 +75,8 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
 | 
			
		||||
	return int(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
 | 
			
		||||
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
 | 
			
		||||
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
 | 
			
		||||
func MoveIssuesOnProjectColumn(ctx context.Context, board *Column, sortedIssueIDs map[int64]int64) error {
 | 
			
		||||
	return db.WithTx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		sess := db.GetEngine(ctx)
 | 
			
		||||
 | 
			
		||||
@@ -102,7 +102,7 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Board) removeIssues(ctx context.Context) error {
 | 
			
		||||
func (b *Column) removeIssues(ctx context.Context) error {
 | 
			
		||||
	_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,12 +21,6 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	// BoardConfig is used to identify the type of board that is being created
 | 
			
		||||
	BoardConfig struct {
 | 
			
		||||
		BoardType   BoardType
 | 
			
		||||
		Translation string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// CardConfig is used to identify the type of board card that is being used
 | 
			
		||||
	CardConfig struct {
 | 
			
		||||
		CardType    CardType
 | 
			
		||||
@@ -68,22 +62,22 @@ func (err ErrProjectNotExist) Unwrap() error {
 | 
			
		||||
	return util.ErrNotExist
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
 | 
			
		||||
type ErrProjectBoardNotExist struct {
 | 
			
		||||
	BoardID int64
 | 
			
		||||
// ErrProjectColumnNotExist represents a "ProjectBoardNotExist" kind of error.
 | 
			
		||||
type ErrProjectColumnNotExist struct {
 | 
			
		||||
	ColumnID int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
 | 
			
		||||
func IsErrProjectBoardNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrProjectBoardNotExist)
 | 
			
		||||
// IsErrProjectColumnNotExist checks if an error is a ErrProjectBoardNotExist
 | 
			
		||||
func IsErrProjectColumnNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrProjectColumnNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrProjectBoardNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
 | 
			
		||||
func (err ErrProjectColumnNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("project column does not exist [id: %d]", err.ColumnID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrProjectBoardNotExist) Unwrap() error {
 | 
			
		||||
func (err ErrProjectColumnNotExist) Unwrap() error {
 | 
			
		||||
	return util.ErrNotExist
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -98,7 +92,7 @@ type Project struct {
 | 
			
		||||
	Repo          *repo_model.Repository `xorm:"-"`
 | 
			
		||||
	CreatorID     int64                  `xorm:"NOT NULL"`
 | 
			
		||||
	IsClosed      bool                   `xorm:"INDEX"`
 | 
			
		||||
	BoardType   BoardType
 | 
			
		||||
	BoardViewType BoardViewType          `xorm:"'board_type'"`
 | 
			
		||||
	CardType      CardType
 | 
			
		||||
	Type          Type
 | 
			
		||||
 | 
			
		||||
@@ -165,15 +159,6 @@ func init() {
 | 
			
		||||
	db.RegisterModel(new(Project))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetBoardConfig retrieves the types of configurations project boards could have
 | 
			
		||||
func GetBoardConfig() []BoardConfig {
 | 
			
		||||
	return []BoardConfig{
 | 
			
		||||
		{BoardTypeNone, "repo.projects.type.none"},
 | 
			
		||||
		{BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
 | 
			
		||||
		{BoardTypeBugTriage, "repo.projects.type.bug_triage"},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCardConfig retrieves the types of configurations project board cards could have
 | 
			
		||||
func GetCardConfig() []CardConfig {
 | 
			
		||||
	return []CardConfig{
 | 
			
		||||
@@ -244,8 +229,8 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
 | 
			
		||||
 | 
			
		||||
// NewProject creates a new Project
 | 
			
		||||
func NewProject(ctx context.Context, p *Project) error {
 | 
			
		||||
	if !IsBoardTypeValid(p.BoardType) {
 | 
			
		||||
		p.BoardType = BoardTypeNone
 | 
			
		||||
	if !IsBoardViewTypeValid(p.BoardViewType) {
 | 
			
		||||
		p.BoardViewType = BoardViewTypeNone
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !IsCardTypeValid(p.CardType) {
 | 
			
		||||
@@ -256,12 +241,7 @@ func NewProject(ctx context.Context, p *Project) error {
 | 
			
		||||
		return util.NewInvalidArgumentErrorf("project type is not valid")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx, committer, err := db.TxContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer committer.Close()
 | 
			
		||||
 | 
			
		||||
	return db.WithTx(ctx, func(tx context.Context) error {
 | 
			
		||||
		if err := db.Insert(ctx, p); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@@ -272,11 +252,8 @@ func NewProject(ctx context.Context, p *Project) error {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	if err := createBoardsForProjectsType(ctx, p); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
		return createColumnsForProjectsBoradViewType(ctx, p)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetProjectByID returns the projects in a repository
 | 
			
		||||
@@ -410,7 +387,7 @@ func DeleteProjectByID(ctx context.Context, id int64) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := deleteBoardByProjectID(ctx, id); err != nil {
 | 
			
		||||
		if err := deleteColumnByProjectID(ctx, id); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ func TestProject(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	project := &Project{
 | 
			
		||||
		Type:          TypeRepository,
 | 
			
		||||
		BoardType:   BoardTypeBasicKanban,
 | 
			
		||||
		BoardViewType: BoardViewTypeBasicKanban,
 | 
			
		||||
		CardType:      CardTypeTextOnly,
 | 
			
		||||
		Title:         "New Project",
 | 
			
		||||
		RepoID:        1,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								models/project/view_board.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								models/project/view_board.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package project
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	// BoardViewType is used to represent a project column type
 | 
			
		||||
	BoardViewType uint8
 | 
			
		||||
 | 
			
		||||
	// BoardConfig is used to identify the type of board that is being created
 | 
			
		||||
	BoardConfig struct {
 | 
			
		||||
		BoardType   BoardViewType
 | 
			
		||||
		Translation string
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// BoardViewTypeNone is a project board type that has no predefined columns
 | 
			
		||||
	BoardViewTypeNone BoardViewType = iota
 | 
			
		||||
 | 
			
		||||
	// BoardViewTypeBasicKanban is a project board type that has basic predefined columns
 | 
			
		||||
	BoardViewTypeBasicKanban
 | 
			
		||||
 | 
			
		||||
	// BoardViewTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
 | 
			
		||||
	BoardViewTypeBugTriage
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetBoardViewConfig retrieves the types of configurations project boards could have
 | 
			
		||||
func GetBoardViewConfig() []BoardConfig {
 | 
			
		||||
	return []BoardConfig{
 | 
			
		||||
		{BoardViewTypeNone, "repo.projects.type.none"},
 | 
			
		||||
		{BoardViewTypeBasicKanban, "repo.projects.type.basic_kanban"},
 | 
			
		||||
		{BoardViewTypeBugTriage, "repo.projects.type.bug_triage"},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsBoardViewTypeValid checks if the project board type is valid
 | 
			
		||||
func IsBoardViewTypeValid(p BoardViewType) bool {
 | 
			
		||||
	switch p {
 | 
			
		||||
	case BoardViewTypeNone, BoardViewTypeBasicKanban, BoardViewTypeBugTriage:
 | 
			
		||||
		return true
 | 
			
		||||
	default:
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -228,8 +228,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
 | 
			
		||||
	if options.ProjectID.Has() {
 | 
			
		||||
		queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectID.Value(), "project_id"))
 | 
			
		||||
	}
 | 
			
		||||
	if options.ProjectBoardID.Has() {
 | 
			
		||||
		queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectBoardID.Value(), "project_board_id"))
 | 
			
		||||
	if options.ProjectColumnID.Has() {
 | 
			
		||||
		queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectColumnID.Value(), "project_board_id"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if options.PosterID.Has() {
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
 | 
			
		||||
		ReviewedID:         convertID(options.ReviewedID),
 | 
			
		||||
		SubscriberID:       convertID(options.SubscriberID),
 | 
			
		||||
		ProjectID:          convertID(options.ProjectID),
 | 
			
		||||
		ProjectBoardID:     convertID(options.ProjectBoardID),
 | 
			
		||||
		ProjectColumnID:    convertID(options.ProjectColumnID),
 | 
			
		||||
		IsClosed:           options.IsClosed,
 | 
			
		||||
		IsPull:             options.IsPull,
 | 
			
		||||
		IncludedLabelNames: nil,
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	searchOpt.ProjectID = convertID(opts.ProjectID)
 | 
			
		||||
	searchOpt.ProjectBoardID = convertID(opts.ProjectBoardID)
 | 
			
		||||
	searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID)
 | 
			
		||||
	searchOpt.PosterID = convertID(opts.PosterID)
 | 
			
		||||
	searchOpt.AssigneeID = convertID(opts.AssigneeID)
 | 
			
		||||
	searchOpt.MentionID = convertID(opts.MentionedID)
 | 
			
		||||
 
 | 
			
		||||
@@ -198,8 +198,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
 | 
			
		||||
	if options.ProjectID.Has() {
 | 
			
		||||
		query.Must(elastic.NewTermQuery("project_id", options.ProjectID.Value()))
 | 
			
		||||
	}
 | 
			
		||||
	if options.ProjectBoardID.Has() {
 | 
			
		||||
		query.Must(elastic.NewTermQuery("project_board_id", options.ProjectBoardID.Value()))
 | 
			
		||||
	if options.ProjectColumnID.Has() {
 | 
			
		||||
		query.Must(elastic.NewTermQuery("project_board_id", options.ProjectColumnID.Value()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if options.PosterID.Has() {
 | 
			
		||||
 
 | 
			
		||||
@@ -369,13 +369,13 @@ func searchIssueInProject(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			SearchOptions{
 | 
			
		||||
				ProjectBoardID: optional.Some(int64(1)),
 | 
			
		||||
				ProjectColumnID: optional.Some(int64(1)),
 | 
			
		||||
			},
 | 
			
		||||
			[]int64{1},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			SearchOptions{
 | 
			
		||||
				ProjectBoardID: optional.Some(int64(0)), // issue with in default board
 | 
			
		||||
				ProjectColumnID: optional.Some(int64(0)), // issue with in default board
 | 
			
		||||
			},
 | 
			
		||||
			[]int64{2},
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ type SearchOptions struct {
 | 
			
		||||
	MilestoneIDs []int64 // milestones the issues have
 | 
			
		||||
 | 
			
		||||
	ProjectID       optional.Option[int64] // project the issues belong to
 | 
			
		||||
	ProjectBoardID optional.Option[int64] // project board the issues belong to
 | 
			
		||||
	ProjectColumnID optional.Option[int64] // project column the issues belong to
 | 
			
		||||
 | 
			
		||||
	PosterID optional.Option[int64] // poster of the issues
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -343,7 +343,7 @@ var cases = []*testIndexerCase{
 | 
			
		||||
			Paginator: &db.ListOptions{
 | 
			
		||||
				PageSize: 5,
 | 
			
		||||
			},
 | 
			
		||||
			ProjectBoardID: optional.Some(int64(1)),
 | 
			
		||||
			ProjectColumnID: optional.Some(int64(1)),
 | 
			
		||||
		},
 | 
			
		||||
		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
 | 
			
		||||
			assert.Equal(t, 5, len(result.Hits))
 | 
			
		||||
@@ -361,7 +361,7 @@ var cases = []*testIndexerCase{
 | 
			
		||||
			Paginator: &db.ListOptions{
 | 
			
		||||
				PageSize: 5,
 | 
			
		||||
			},
 | 
			
		||||
			ProjectBoardID: optional.Some(int64(0)),
 | 
			
		||||
			ProjectColumnID: optional.Some(int64(0)),
 | 
			
		||||
		},
 | 
			
		||||
		Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
 | 
			
		||||
			assert.Equal(t, 5, len(result.Hits))
 | 
			
		||||
 
 | 
			
		||||
@@ -174,8 +174,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
 | 
			
		||||
	if options.ProjectID.Has() {
 | 
			
		||||
		query.And(inner_meilisearch.NewFilterEq("project_id", options.ProjectID.Value()))
 | 
			
		||||
	}
 | 
			
		||||
	if options.ProjectBoardID.Has() {
 | 
			
		||||
		query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectBoardID.Value()))
 | 
			
		||||
	if options.ProjectColumnID.Has() {
 | 
			
		||||
		query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectColumnID.Value()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if options.PosterID.Has() {
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
 | 
			
		||||
		NoLabel:            len(labels) == 0,
 | 
			
		||||
		MilestoneID:        issue.MilestoneID,
 | 
			
		||||
		ProjectID:          projectID,
 | 
			
		||||
		ProjectBoardID:     issue.ProjectBoardID(ctx),
 | 
			
		||||
		ProjectBoardID:     issue.ProjectColumnID(ctx),
 | 
			
		||||
		PosterID:           issue.PosterID,
 | 
			
		||||
		AssigneeID:         issue.AssigneeID,
 | 
			
		||||
		MentionIDs:         mentionIDs,
 | 
			
		||||
 
 | 
			
		||||
@@ -338,7 +338,7 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
 | 
			
		||||
	ch <- prometheus.MustNewConstMetric(
 | 
			
		||||
		c.ProjectBoards,
 | 
			
		||||
		prometheus.GaugeValue,
 | 
			
		||||
		float64(stats.Counter.ProjectBoard),
 | 
			
		||||
		float64(stats.Counter.ProjectColumn),
 | 
			
		||||
	)
 | 
			
		||||
	ch <- prometheus.MustNewConstMetric(
 | 
			
		||||
		c.PublicKeys,
 | 
			
		||||
 
 | 
			
		||||
@@ -141,7 +141,7 @@ func canWriteProjects(ctx *context.Context) bool {
 | 
			
		||||
// RenderNewProject render creating a project page
 | 
			
		||||
func RenderNewProject(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.projects.new")
 | 
			
		||||
	ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
 | 
			
		||||
	ctx.Data["BoardTypes"] = project_model.GetBoardViewConfig()
 | 
			
		||||
	ctx.Data["CardTypes"] = project_model.GetCardConfig()
 | 
			
		||||
	ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
 | 
			
		||||
	ctx.Data["PageIsViewProjects"] = true
 | 
			
		||||
@@ -174,7 +174,7 @@ func NewProjectPost(ctx *context.Context) {
 | 
			
		||||
		Title:         form.Title,
 | 
			
		||||
		Description:   form.Content,
 | 
			
		||||
		CreatorID:     ctx.Doer.ID,
 | 
			
		||||
		BoardType:   form.BoardType,
 | 
			
		||||
		BoardViewType: form.BoardType,
 | 
			
		||||
		CardType:      form.CardType,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -327,13 +327,13 @@ func ViewProject(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	boards, err := project.GetBoards(ctx)
 | 
			
		||||
	boards, err := project.GetColumns(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectBoards", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
 | 
			
		||||
	issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, boards)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("LoadIssuesOfBoards", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -458,8 +458,8 @@ func UpdateIssueProject(ctx *context.Context) {
 | 
			
		||||
	ctx.JSONOK()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteProjectBoard allows for the deletion of a project board
 | 
			
		||||
func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
// DeleteProjectColumn allows for the deletion of a project board
 | 
			
		||||
func DeleteProjectColumn(ctx *context.Context) {
 | 
			
		||||
	if ctx.Doer == nil {
 | 
			
		||||
		ctx.JSON(http.StatusForbidden, map[string]string{
 | 
			
		||||
			"message": "Only signed in users are allowed to perform this action.",
 | 
			
		||||
@@ -473,7 +473,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pb, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	pb, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -492,7 +492,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.DeleteBoardByID(ctx, ctx.ParamsInt64(":boardID")); err != nil {
 | 
			
		||||
	if err := project_model.DeleteColumnByID(ctx, ctx.ParamsInt64(":boardID")); err != nil {
 | 
			
		||||
		ctx.ServerError("DeleteProjectBoardByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -500,9 +500,9 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
	ctx.JSONOK()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddBoardToProjectPost allows a new board to be added to a project.
 | 
			
		||||
func AddBoardToProjectPost(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectBoardForm)
 | 
			
		||||
// AddColumnToProjectPost allows a new board to be added to a project.
 | 
			
		||||
func AddColumnToProjectPost(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectColumnForm)
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -510,7 +510,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.NewBoard(ctx, &project_model.Board{
 | 
			
		||||
	if err := project_model.NewColumn(ctx, &project_model.Column{
 | 
			
		||||
		ProjectID: project.ID,
 | 
			
		||||
		Title:     form.Title,
 | 
			
		||||
		Color:     form.Color,
 | 
			
		||||
@@ -524,7 +524,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckProjectBoardChangePermissions check permission
 | 
			
		||||
func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) {
 | 
			
		||||
func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Column) {
 | 
			
		||||
	if ctx.Doer == nil {
 | 
			
		||||
		ctx.JSON(http.StatusForbidden, map[string]string{
 | 
			
		||||
			"message": "Only signed in users are allowed to perform this action.",
 | 
			
		||||
@@ -538,7 +538,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectBoard", err)
 | 
			
		||||
		return nil, nil
 | 
			
		||||
@@ -559,9 +559,9 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
 | 
			
		||||
	return project, board
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditProjectBoard allows a project board's to be updated
 | 
			
		||||
func EditProjectBoard(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectBoardForm)
 | 
			
		||||
// EditProjectColumn allows a project board's to be updated
 | 
			
		||||
func EditProjectColumn(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectColumnForm)
 | 
			
		||||
	_, board := CheckProjectBoardChangePermissions(ctx)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
@@ -577,7 +577,7 @@ func EditProjectBoard(ctx *context.Context) {
 | 
			
		||||
		board.Sorting = form.Sorting
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.UpdateBoard(ctx, board); err != nil {
 | 
			
		||||
	if err := project_model.UpdateColumn(ctx, board); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateProjectBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -592,7 +592,7 @@ func SetDefaultProjectBoard(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.SetDefaultBoard(ctx, project.ID, board.ID); err != nil {
 | 
			
		||||
	if err := project_model.SetDefaultColumn(ctx, project.ID, board.ID); err != nil {
 | 
			
		||||
		ctx.ServerError("SetDefaultBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -619,9 +619,9 @@ func MoveIssues(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err)
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectColumnNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -671,7 +671,7 @@ func MoveIssues(ctx *context.Context) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
 | 
			
		||||
	if err = project_model.MoveIssuesOnProjectColumn(ctx, board, sortedIssueIDs); err != nil {
 | 
			
		||||
		ctx.ServerError("MoveIssuesOnProjectBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -2830,7 +2830,7 @@ func ListIssues(ctx *context.Context) {
 | 
			
		||||
		RepoIDs:   []int64{ctx.Repo.Repository.ID},
 | 
			
		||||
		IsPull:    isPull,
 | 
			
		||||
		IsClosed:  isClosed,
 | 
			
		||||
		ProjectBoardID: projectID,
 | 
			
		||||
		ProjectID: projectID,
 | 
			
		||||
		SortBy:    issue_indexer.SortByCreatedDesc,
 | 
			
		||||
	}
 | 
			
		||||
	if since != 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -132,7 +132,7 @@ func Projects(ctx *context.Context) {
 | 
			
		||||
// RenderNewProject render creating a project page
 | 
			
		||||
func RenderNewProject(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("repo.projects.new")
 | 
			
		||||
	ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
 | 
			
		||||
	ctx.Data["BoardTypes"] = project_model.GetBoardViewConfig()
 | 
			
		||||
	ctx.Data["CardTypes"] = project_model.GetCardConfig()
 | 
			
		||||
	ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
			
		||||
	ctx.Data["CancelLink"] = ctx.Repo.Repository.Link() + "/projects"
 | 
			
		||||
@@ -154,7 +154,7 @@ func NewProjectPost(ctx *context.Context) {
 | 
			
		||||
		Title:         form.Title,
 | 
			
		||||
		Description:   form.Content,
 | 
			
		||||
		CreatorID:     ctx.Doer.ID,
 | 
			
		||||
		BoardType:   form.BoardType,
 | 
			
		||||
		BoardViewType: form.BoardType,
 | 
			
		||||
		CardType:      form.CardType,
 | 
			
		||||
		Type:          project_model.TypeRepository,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
@@ -309,13 +309,13 @@ func ViewProject(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	boards, err := project.GetBoards(ctx)
 | 
			
		||||
	boards, err := project.GetColumns(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectBoards", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
 | 
			
		||||
	issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, boards)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("LoadIssuesOfBoards", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -432,7 +432,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pb, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	pb, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -451,7 +451,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.DeleteBoardByID(ctx, ctx.ParamsInt64(":boardID")); err != nil {
 | 
			
		||||
	if err := project_model.DeleteColumnByID(ctx, ctx.ParamsInt64(":boardID")); err != nil {
 | 
			
		||||
		ctx.ServerError("DeleteProjectBoardByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -461,7 +461,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
			
		||||
 | 
			
		||||
// AddBoardToProjectPost allows a new board to be added to a project.
 | 
			
		||||
func AddBoardToProjectPost(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectBoardForm)
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectColumnForm)
 | 
			
		||||
	if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(perm.AccessModeWrite, unit.TypeProjects) {
 | 
			
		||||
		ctx.JSON(http.StatusForbidden, map[string]string{
 | 
			
		||||
			"message": "Only authorized users are allowed to perform this action.",
 | 
			
		||||
@@ -479,7 +479,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.NewBoard(ctx, &project_model.Board{
 | 
			
		||||
	if err := project_model.NewColumn(ctx, &project_model.Column{
 | 
			
		||||
		ProjectID: project.ID,
 | 
			
		||||
		Title:     form.Title,
 | 
			
		||||
		Color:     form.Color,
 | 
			
		||||
@@ -492,7 +492,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
			
		||||
	ctx.JSONOK()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) {
 | 
			
		||||
func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Column) {
 | 
			
		||||
	if ctx.Doer == nil {
 | 
			
		||||
		ctx.JSON(http.StatusForbidden, map[string]string{
 | 
			
		||||
			"message": "Only signed in users are allowed to perform this action.",
 | 
			
		||||
@@ -517,7 +517,7 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectBoard", err)
 | 
			
		||||
		return nil, nil
 | 
			
		||||
@@ -538,9 +538,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
 | 
			
		||||
	return project, board
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditProjectBoard allows a project board's to be updated
 | 
			
		||||
func EditProjectBoard(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectBoardForm)
 | 
			
		||||
// EditProjectColumn allows a project board's to be updated
 | 
			
		||||
func EditProjectColumn(ctx *context.Context) {
 | 
			
		||||
	form := web.GetForm(ctx).(*forms.EditProjectColumnForm)
 | 
			
		||||
	_, board := checkProjectBoardChangePermissions(ctx)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
@@ -556,7 +556,7 @@ func EditProjectBoard(ctx *context.Context) {
 | 
			
		||||
		board.Sorting = form.Sorting
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.UpdateBoard(ctx, board); err != nil {
 | 
			
		||||
	if err := project_model.UpdateColumn(ctx, board); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateProjectBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -564,14 +564,14 @@ func EditProjectBoard(ctx *context.Context) {
 | 
			
		||||
	ctx.JSONOK()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDefaultProjectBoard set default board for uncategorized issues/pulls
 | 
			
		||||
func SetDefaultProjectBoard(ctx *context.Context) {
 | 
			
		||||
// SetDefaultProjectColumn set default board for uncategorized issues/pulls
 | 
			
		||||
func SetDefaultProjectColumn(ctx *context.Context) {
 | 
			
		||||
	project, board := checkProjectBoardChangePermissions(ctx)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.SetDefaultBoard(ctx, project.ID, board.ID); err != nil {
 | 
			
		||||
	if err := project_model.SetDefaultColumn(ctx, project.ID, board.ID); err != nil {
 | 
			
		||||
		ctx.ServerError("SetDefaultBoard", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -609,12 +609,12 @@ func MoveIssues(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	board, err := project_model.GetColumn(ctx, ctx.ParamsInt64(":boardID"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if project_model.IsErrProjectBoardNotExist(err) {
 | 
			
		||||
			ctx.NotFound("ProjectBoardNotExist", nil)
 | 
			
		||||
		if project_model.IsErrProjectColumnNotExist(err) {
 | 
			
		||||
			ctx.NotFound("ProjectColumnNotExist", nil)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError("GetProjectBoard", err)
 | 
			
		||||
			ctx.ServerError("GetProjectColumn", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -664,8 +664,8 @@ func MoveIssues(ctx *context.Context) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
 | 
			
		||||
		ctx.ServerError("MoveIssuesOnProjectBoard", err)
 | 
			
		||||
	if err = project_model.MoveIssuesOnProjectColumn(ctx, board, sortedIssueIDs); err != nil {
 | 
			
		||||
		ctx.ServerError("MoveIssuesOnProjectColumn", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -997,7 +997,7 @@ func registerRoutes(m *web.Route) {
 | 
			
		||||
				m.Get("/new", org.RenderNewProject)
 | 
			
		||||
				m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
 | 
			
		||||
				m.Group("/{id}", func() {
 | 
			
		||||
					m.Post("", web.Bind(forms.EditProjectBoardForm{}), org.AddBoardToProjectPost)
 | 
			
		||||
					m.Post("", web.Bind(forms.EditProjectColumnForm{}), org.AddColumnToProjectPost)
 | 
			
		||||
					m.Post("/delete", org.DeleteProject)
 | 
			
		||||
 | 
			
		||||
					m.Get("/edit", org.RenderEditProject)
 | 
			
		||||
@@ -1005,8 +1005,8 @@ func registerRoutes(m *web.Route) {
 | 
			
		||||
					m.Post("/{action:open|close}", org.ChangeProjectStatus)
 | 
			
		||||
 | 
			
		||||
					m.Group("/{boardID}", func() {
 | 
			
		||||
						m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard)
 | 
			
		||||
						m.Delete("", org.DeleteProjectBoard)
 | 
			
		||||
						m.Put("", web.Bind(forms.EditProjectColumnForm{}), org.EditProjectColumn)
 | 
			
		||||
						m.Delete("", org.DeleteProjectColumn)
 | 
			
		||||
						m.Post("/default", org.SetDefaultProjectBoard)
 | 
			
		||||
 | 
			
		||||
						m.Post("/move", org.MoveIssues)
 | 
			
		||||
@@ -1336,7 +1336,7 @@ func registerRoutes(m *web.Route) {
 | 
			
		||||
				m.Get("/new", repo.RenderNewProject)
 | 
			
		||||
				m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost)
 | 
			
		||||
				m.Group("/{id}", func() {
 | 
			
		||||
					m.Post("", web.Bind(forms.EditProjectBoardForm{}), repo.AddBoardToProjectPost)
 | 
			
		||||
					m.Post("", web.Bind(forms.EditProjectColumnForm{}), repo.AddBoardToProjectPost)
 | 
			
		||||
					m.Post("/delete", repo.DeleteProject)
 | 
			
		||||
 | 
			
		||||
					m.Get("/edit", repo.RenderEditProject)
 | 
			
		||||
@@ -1344,9 +1344,9 @@ func registerRoutes(m *web.Route) {
 | 
			
		||||
					m.Post("/{action:open|close}", repo.ChangeProjectStatus)
 | 
			
		||||
 | 
			
		||||
					m.Group("/{boardID}", func() {
 | 
			
		||||
						m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard)
 | 
			
		||||
						m.Put("", web.Bind(forms.EditProjectColumnForm{}), repo.EditProjectColumn)
 | 
			
		||||
						m.Delete("", repo.DeleteProjectBoard)
 | 
			
		||||
						m.Post("/default", repo.SetDefaultProjectBoard)
 | 
			
		||||
						m.Post("/default", repo.SetDefaultProjectColumn)
 | 
			
		||||
 | 
			
		||||
						m.Post("/move", repo.MoveIssues)
 | 
			
		||||
					})
 | 
			
		||||
 
 | 
			
		||||
@@ -504,18 +504,11 @@ func (i IssueLockForm) HasValidReason() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________                   __               __
 | 
			
		||||
// \______   \_______  ____    |__| ____   _____/  |_  ______
 | 
			
		||||
//  |     ___/\_  __ \/  _ \   |  |/ __ \_/ ___\   __\/  ___/
 | 
			
		||||
//  |    |     |  | \(  <_> )  |  \  ___/\  \___|  |  \___ \
 | 
			
		||||
//  |____|     |__|   \____/\__|  |\___  >\___  >__| /____  >
 | 
			
		||||
//                         \______|    \/     \/          \/
 | 
			
		||||
 | 
			
		||||
// CreateProjectForm form for creating a project
 | 
			
		||||
type CreateProjectForm struct {
 | 
			
		||||
	Title     string `binding:"Required;MaxSize(100)"`
 | 
			
		||||
	Content   string
 | 
			
		||||
	BoardType project_model.BoardType
 | 
			
		||||
	BoardType project_model.BoardViewType
 | 
			
		||||
	CardType  project_model.CardType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -524,25 +517,18 @@ type CreateProjectForm struct {
 | 
			
		||||
type UserCreateProjectForm struct {
 | 
			
		||||
	Title     string `binding:"Required;MaxSize(100)"`
 | 
			
		||||
	Content   string
 | 
			
		||||
	BoardType project_model.BoardType
 | 
			
		||||
	BoardType project_model.BoardViewType
 | 
			
		||||
	CardType  project_model.CardType
 | 
			
		||||
	UID       int64 `binding:"Required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditProjectBoardForm is a form for editing a project board
 | 
			
		||||
type EditProjectBoardForm struct {
 | 
			
		||||
// EditProjectColumnForm is a form for editing a project board
 | 
			
		||||
type EditProjectColumnForm struct {
 | 
			
		||||
	Title   string `binding:"Required;MaxSize(100)"`
 | 
			
		||||
	Sorting int8
 | 
			
		||||
	Color   string `binding:"MaxSize(7)"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//    _____  .__.__                   __
 | 
			
		||||
//   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
 | 
			
		||||
//  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
 | 
			
		||||
// /    Y    \  |  |_\  ___/ \___ \  |  | (  <_> )   |  \  ___/
 | 
			
		||||
// \____|__  /__|____/\___  >____  > |__|  \____/|___|  /\___  >
 | 
			
		||||
//         \/             \/     \/                   \/     \/
 | 
			
		||||
 | 
			
		||||
// CreateMilestoneForm form for creating milestone
 | 
			
		||||
type CreateMilestoneForm struct {
 | 
			
		||||
	Title    string `binding:"Required;MaxSize(50)"`
 | 
			
		||||
@@ -556,13 +542,6 @@ func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) b
 | 
			
		||||
	return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// .____          ___.          .__
 | 
			
		||||
// |    |   _____ \_ |__   ____ |  |
 | 
			
		||||
// |    |   \__  \ | __ \_/ __ \|  |
 | 
			
		||||
// |    |___ / __ \| \_\ \  ___/|  |__
 | 
			
		||||
// |_______ (____  /___  /\___  >____/
 | 
			
		||||
//         \/    \/    \/     \/
 | 
			
		||||
 | 
			
		||||
// CreateLabelForm form for creating label
 | 
			
		||||
type CreateLabelForm struct {
 | 
			
		||||
	ID          int64
 | 
			
		||||
@@ -590,13 +569,6 @@ func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors)
 | 
			
		||||
	return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// __________      .__  .__    __________                                     __
 | 
			
		||||
// \______   \__ __|  | |  |   \______   \ ____  ________ __   ____   _______/  |_
 | 
			
		||||
//  |     ___/  |  \  | |  |    |       _// __ \/ ____/  |  \_/ __ \ /  ___/\   __\
 | 
			
		||||
//  |    |   |  |  /  |_|  |__  |    |   \  ___< <_|  |  |  /\  ___/ \___ \  |  |
 | 
			
		||||
//  |____|   |____/|____/____/  |____|_  /\___  >__   |____/  \___  >____  > |__|
 | 
			
		||||
//                                     \/     \/   |__|           \/     \/
 | 
			
		||||
 | 
			
		||||
// MergePullRequestForm form for merging Pull Request
 | 
			
		||||
// swagger:model MergePullRequestOption
 | 
			
		||||
type MergePullRequestForm struct {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user