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>
(cherry picked from commit e5160185ed65fd1c2bcb2fc7dc7e0b5514ddb299)

Conflicts:
	options/locale/locale_en-US.ini
	trivial conflict because Forgejo strings do not have
	surrounding double quotes
This commit is contained in:
Denys Konovalov 2024-03-27 21:54:32 +01:00 committed by Earl Warren
parent b019ecce89
commit 8ffb9c6fb1
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
17 changed files with 400 additions and 196 deletions

View file

@ -207,11 +207,7 @@ func ChangeProjectStatus(ctx *context.Context) {
id := ctx.ParamsInt64(":id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", err)
} else {
ctx.ServerError("ChangeProjectStatusByRepoIDAndID", err)
}
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return
}
ctx.Redirect(ctx.ContextUser.HomeLink() + "/-/projects?state=" + url.QueryEscape(ctx.Params(":action")))
@ -221,11 +217,7 @@ func ChangeProjectStatus(ctx *context.Context) {
func DeleteProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if p.OwnerID != ctx.ContextUser.ID {
@ -254,11 +246,7 @@ func RenderEditProject(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if p.OwnerID != ctx.ContextUser.ID {
@ -303,11 +291,7 @@ func EditProjectPost(ctx *context.Context) {
p, err := project_model.GetProjectByID(ctx, projectID)
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if p.OwnerID != ctx.ContextUser.ID {
@ -335,11 +319,7 @@ func EditProjectPost(ctx *context.Context) {
func ViewProject(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if project.OwnerID != ctx.ContextUser.ID {
@ -353,10 +333,6 @@ func ViewProject(ctx *context.Context) {
return
}
if boards[0].ID == 0 {
boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err)
@ -493,11 +469,7 @@ func DeleteProjectBoard(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
@ -534,11 +506,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
@ -566,11 +534,7 @@ func CheckProjectBoardChangePermissions(ctx *context.Context) (*project_model.Pr
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return nil, nil
}
@ -636,21 +600,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
ctx.JSONOK()
}
// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls
func UnsetDefaultProjectBoard(ctx *context.Context) {
project, _ := CheckProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
}
if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
ctx.ServerError("SetDefaultBoard", err)
return
}
ctx.JSONOK()
}
// MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues(ctx *context.Context) {
if ctx.Doer == nil {
@ -662,11 +611,7 @@ func MoveIssues(ctx *context.Context) {
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("ProjectNotExist", nil)
} else {
ctx.ServerError("GetProjectByID", err)
}
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
if project.OwnerID != ctx.ContextUser.ID {
@ -674,28 +619,15 @@ func MoveIssues(ctx *context.Context) {
return
}
var board *project_model.Board
board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectBoard", project_model.IsErrProjectBoardNotExist, err)
return
}
if ctx.ParamsInt64(":boardID") == 0 {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil {
if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil)
} else {
ctx.ServerError("GetProjectBoard", err)
}
return
}
if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil)
return
}
if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil)
return
}
type movedIssuesForm struct {
@ -718,11 +650,7 @@ func MoveIssues(ctx *context.Context) {
}
movedIssues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IssueNotExisting", nil)
} else {
ctx.ServerError("GetIssueByID", err)
}
ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, err)
return
}

View file

@ -314,10 +314,6 @@ func ViewProject(ctx *context.Context) {
return
}
if boards[0].ID == 0 {
boards[0].Title = ctx.Locale.TrString("repo.projects.type.uncategorized")
}
issuesMap, err := issues_model.LoadIssuesFromBoardList(ctx, boards)
if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err)
@ -582,21 +578,6 @@ func SetDefaultProjectBoard(ctx *context.Context) {
ctx.JSONOK()
}
// UnSetDefaultProjectBoard unset default board for uncategorized issues/pulls
func UnSetDefaultProjectBoard(ctx *context.Context) {
project, _ := checkProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
}
if err := project_model.SetDefaultBoard(ctx, project.ID, 0); err != nil {
ctx.ServerError("SetDefaultBoard", err)
return
}
ctx.JSONOK()
}
// MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues(ctx *context.Context) {
if ctx.Doer == nil {
@ -627,28 +608,19 @@ func MoveIssues(ctx *context.Context) {
return
}
var board *project_model.Board
board, err := project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil {
if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil)
} else {
ctx.ServerError("GetProjectBoard", err)
}
return
}
if ctx.ParamsInt64(":boardID") == 0 {
board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
Title: ctx.Locale.TrString("repo.projects.type.uncategorized"),
}
} else {
board, err = project_model.GetBoard(ctx, ctx.ParamsInt64(":boardID"))
if err != nil {
if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil)
} else {
ctx.ServerError("GetProjectBoard", err)
}
return
}
if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil)
return
}
if board.ProjectID != project.ID {
ctx.NotFound("BoardNotInProject", nil)
return
}
type movedIssuesForm struct {

View file

@ -986,7 +986,6 @@ func registerRoutes(m *web.Route) {
m.Put("", web.Bind(forms.EditProjectBoardForm{}), org.EditProjectBoard)
m.Delete("", org.DeleteProjectBoard)
m.Post("/default", org.SetDefaultProjectBoard)
m.Post("/unsetdefault", org.UnsetDefaultProjectBoard)
m.Post("/move", org.MoveIssues)
})
@ -1360,7 +1359,6 @@ func registerRoutes(m *web.Route) {
m.Put("", web.Bind(forms.EditProjectBoardForm{}), repo.EditProjectBoard)
m.Delete("", repo.DeleteProjectBoard)
m.Post("/default", repo.SetDefaultProjectBoard)
m.Post("/unsetdefault", repo.UnSetDefaultProjectBoard)
m.Post("/move", repo.MoveIssues)
})