mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Add default board to new projects, remove uncategorized pseudo-board (#29874)
On creation of an empty project (no template) a default board will be created instead of falling back to the uneditable pseudo-board. Every project now has to have exactly one default boards. As a consequence, you cannot unset a board as default, instead you have to set another board as default. Existing projects will be modified using a cron job, additionally this check will run every midnight by default. Deleting the default board is not allowed, you have to set another board as default to do it. Fixes #29873 Fixes #14679 along the way Fixes #29853 Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
@@ -123,6 +123,17 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
|
||||
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
|
||||
}
|
||||
@@ -176,6 +187,10 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if board.Default {
|
||||
return fmt.Errorf("deleteBoardByID: cannot delete default board")
|
||||
}
|
||||
|
||||
if err = board.removeIssues(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -228,7 +243,6 @@ func UpdateBoard(ctx context.Context, board *Board) error {
|
||||
}
|
||||
|
||||
// GetBoards fetches all boards related to a project
|
||||
// if no default board set, first board is a temporary "Uncategorized" board
|
||||
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||
boards := make([]*Board, 0, 5)
|
||||
|
||||
@@ -244,41 +258,61 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||
return append([]*Board{defaultB}, boards...), nil
|
||||
}
|
||||
|
||||
// getDefaultBoard return default board and create a dummy if none exist
|
||||
// getDefaultBoard return default board and ensure only one exists
|
||||
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
|
||||
var board Board
|
||||
exist, err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, true).Get(&board)
|
||||
if err != nil {
|
||||
var boards []Board
|
||||
if err := db.GetEngine(ctx).Where("project_id=? AND `default` = ?", p.ID, true).OrderBy("sorting").Find(&boards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
|
||||
// create a default board if none is found
|
||||
if len(boards) == 0 {
|
||||
board := Board{
|
||||
ProjectID: p.ID,
|
||||
Default: true,
|
||||
Title: "Uncategorized",
|
||||
CreatorID: p.CreatorID,
|
||||
}
|
||||
if _, err := db.GetEngine(ctx).Insert(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &board, nil
|
||||
}
|
||||
|
||||
// represents a board for issues not assigned to one
|
||||
return &Board{
|
||||
ProjectID: p.ID,
|
||||
Title: "Uncategorized",
|
||||
Default: true,
|
||||
}, nil
|
||||
// unset default boards where too many default boards exist
|
||||
if len(boards) > 1 {
|
||||
var boardsToUpdate []int64
|
||||
for id, b := range boards {
|
||||
if id > 0 {
|
||||
boardsToUpdate = append(boardsToUpdate, b.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where(builder.Eq{"project_id": p.ID}.And(builder.In("id", boardsToUpdate))).
|
||||
Cols("`default`").Update(&Board{Default: false}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &boards[0], nil
|
||||
}
|
||||
|
||||
// SetDefaultBoard represents a board for issues not assigned to one
|
||||
// if boardID is 0 unset default
|
||||
func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where(builder.Eq{
|
||||
"project_id": projectID,
|
||||
"`default`": true,
|
||||
}).Cols("`default`").Update(&Board{Default: false})
|
||||
if err != nil {
|
||||
if _, err := GetBoard(ctx, boardID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if boardID > 0 {
|
||||
_, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}).
|
||||
Cols("`default`").Update(&Board{Default: true})
|
||||
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
|
||||
}
|
||||
|
||||
|
40
models/project/board_test.go
Normal file
40
models/project/board_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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(8), board.ID)
|
||||
|
||||
board, err = GetBoard(db.DefaultContext, 9)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(6), board.ProjectID)
|
||||
assert.False(t, board.Default)
|
||||
}
|
@@ -92,19 +92,19 @@ func TestProjectsSort(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
sortType: "default",
|
||||
wants: []int64{1, 3, 2, 4},
|
||||
wants: []int64{1, 3, 2, 6, 5, 4},
|
||||
},
|
||||
{
|
||||
sortType: "oldest",
|
||||
wants: []int64{4, 2, 3, 1},
|
||||
wants: []int64{4, 5, 6, 2, 3, 1},
|
||||
},
|
||||
{
|
||||
sortType: "recentupdate",
|
||||
wants: []int64{1, 3, 2, 4},
|
||||
wants: []int64{1, 3, 2, 6, 5, 4},
|
||||
},
|
||||
{
|
||||
sortType: "leastupdate",
|
||||
wants: []int64{4, 2, 3, 1},
|
||||
wants: []int64{4, 5, 6, 2, 3, 1},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ func TestProjectsSort(t *testing.T) {
|
||||
OrderBy: GetSearchOrderByBySortType(tt.sortType),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, int64(4), count)
|
||||
if assert.Len(t, projects, 4) {
|
||||
assert.EqualValues(t, int64(6), count)
|
||||
if assert.Len(t, projects, 6) {
|
||||
for i := range projects {
|
||||
assert.EqualValues(t, tt.wants[i], projects[i].ID)
|
||||
}
|
||||
|
Reference in New Issue
Block a user