1
1
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:
Denys Konovalov
2024-03-27 21:54:32 +01:00
committed by GitHub
parent 4eb86d6823
commit e5160185ed
17 changed files with 400 additions and 196 deletions

View File

@@ -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
}

View 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)
}

View File

@@ -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)
}