Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KanBan: be able to set default board #14147

Merged
merged 34 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2cca177
Make Backend Support Default Bords
6543 Dec 27, 2020
5285b30
routers
6543 Dec 27, 2020
0a4be1a
Frontend
6543 Dec 27, 2020
ea1a87f
refactor
6543 Dec 27, 2020
99f0798
Apply suggestions from code review
6543 Dec 27, 2020
6d9b0fd
Merge branch 'master' into default-board
6543 Dec 27, 2020
3bea260
format xorm query code
6543 Dec 27, 2020
bc3f578
fix&lint frontend
6543 Dec 27, 2020
4e2978a
Merge branch 'master' into default-board
6543 Dec 27, 2020
b3d6367
Merge branch 'master' into default-board
6543 Dec 27, 2020
7c9a4ae
Merge branch 'master' into default-board
6543 Dec 27, 2020
82f77b7
Merge branch 'master' into default-board
6543 Dec 28, 2020
78e4a2f
indentation fixes
zeripath Dec 28, 2020
89ebac6
Merge branch 'master' into default-board
6543 Dec 28, 2020
21db43d
add test
6543 Dec 29, 2020
812b1ec
Merge branch 'master' into default-board
6543 Dec 29, 2020
27587c3
Merge branch 'master' into default-board
6543 Dec 30, 2020
21ec9da
Merge branch 'master' into default-board
6543 Jan 2, 2021
96db8b5
Merge branch 'master' into default-board
6543 Jan 2, 2021
20220ff
Merge branch 'master' into default-board
6543 Jan 3, 2021
e012a59
use "reload()" in web_src/js/features/projects.js
6543 Jan 3, 2021
0b5a613
Merge branch 'master' into default-board
6543 Jan 3, 2021
92be760
Merge branch 'master' into default-board
6543 Jan 3, 2021
92ce946
fix lint
6543 Jan 3, 2021
0b108e3
use "window.location.reload();"
6543 Jan 3, 2021
cffb248
Merge branch 'master' into default-board
6543 Jan 4, 2021
fd2ed40
Merge branch 'master' into default-board
6543 Jan 5, 2021
f7557ad
Merge branch 'master' into default-board
6543 Jan 6, 2021
a967a57
Merge branch 'master' into default-board
6543 Jan 6, 2021
2ac6ca6
Merge branch 'master' into default-board
6543 Jan 8, 2021
e6d7238
Merge branch 'master' into default-board
6543 Jan 10, 2021
7184ad8
Merge branch 'master' into default-board
6543 Jan 10, 2021
123b8c6
Merge branch 'master' into default-board
6543 Jan 14, 2021
0987586
dont use InternalServerError()
6543 Jan 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 75 additions & 20 deletions models/project_board.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/builder"
"xorm.io/xorm"
)

Expand Down Expand Up @@ -164,45 +165,99 @@ func UpdateProjectBoard(board *ProjectBoard) error {
func updateProjectBoard(e Engine, board *ProjectBoard) error {
_, err := e.ID(board.ID).Cols(
"title",
"default",
).Update(board)
return err
}

// GetProjectBoards fetches all boards related to a project
func GetProjectBoards(projectID int64) ([]*ProjectBoard, error) {
// if no default board set, first board is a temporary "Uncategorized" board
func GetProjectBoards(projectID int64) (ProjectBoardList, error) {
return getProjectBoards(x, projectID)
}

func getProjectBoards(e Engine, projectID int64) ([]*ProjectBoard, error) {
var boards = make([]*ProjectBoard, 0, 5)

sess := x.Where("project_id=?", projectID)
return boards, sess.Find(&boards)
if err := e.Where("project_id=? AND `default`=?", projectID, false).Find(&boards); err != nil {
return nil, err
}

defaultB, err := getDefaultBoard(e, projectID)
if err != nil {
return nil, err
}

return append([]*ProjectBoard{defaultB}, boards...), nil
}

// GetUncategorizedBoard represents a board for issues not assigned to one
func GetUncategorizedBoard(projectID int64) (*ProjectBoard, error) {
// getDefaultBoard return default board and create a dummy if none exist
func getDefaultBoard(e Engine, projectID int64) (*ProjectBoard, error) {
var board ProjectBoard
exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board)
if err != nil {
return nil, err
}
if exist {
return &board, nil
}

// represents a board for issues not assigned to one
return &ProjectBoard{
ProjectID: projectID,
Title: "Uncategorized",
Default: true,
}, nil
}

// SetDefaultBoard represents a board for issues not assigned to one
// if boardID is 0 unset default
func SetDefaultBoard(projectID, boardID int64) error {
sess := x

_, err := sess.Where(builder.Eq{
"project_id": projectID,
"`default`": true,
}).Cols("`default`").Update(&ProjectBoard{Default: false})
if err != nil {
return err
}

if boardID > 0 {
_, err = sess.ID(boardID).Where(builder.Eq{"project_id": projectID}).
Cols("`default`").Update(&ProjectBoard{Default: true})
}

return err
}

// LoadIssues load issues assigned to this board
func (b *ProjectBoard) LoadIssues() (IssueList, error) {
var boardID int64
if !b.Default {
boardID = b.ID

} else {
// Issues without ProjectBoardID
boardID = -1
}
issues, err := Issues(&IssuesOptions{
ProjectBoardID: boardID,
ProjectID: b.ProjectID,
})
b.Issues = issues
return issues, err
issueList := make([]*Issue, 0, 10)

if b.ID != 0 {
issues, err := Issues(&IssuesOptions{
ProjectBoardID: b.ID,
ProjectID: b.ProjectID,
})
if err != nil {
return nil, err
}
issueList = issues
}

if b.Default {
issues, err := Issues(&IssuesOptions{
ProjectBoardID: -1, // Issues without ProjectBoardID
ProjectID: b.ProjectID,
})
if err != nil {
return nil, err
}
issueList = append(issueList, issues...)
}

b.Issues = issueList
return issueList, nil
}

// LoadIssues load issues assigned to the boards
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@ projects.board.edit_title = "New Board Name"
projects.board.new_title = "New Board Name"
projects.board.new_submit = "Submit"
projects.board.new = "New Board"
projects.board.set_default = "Set Default"
projects.board.set_default_desc = "Set this board as default for uncategorized issues and pulls"
projects.board.delete = "Delete Board"
projects.board.deletion_desc = "Deleting a project board moves all related issues to 'Uncategorized'. Continue?"
projects.open = Open
Expand Down
58 changes: 39 additions & 19 deletions routers/repo/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,23 +270,17 @@ func ViewProject(ctx *context.Context) {
return
}

uncategorizedBoard, err := models.GetUncategorizedBoard(project.ID)
uncategorizedBoard.Title = ctx.Tr("repo.projects.type.uncategorized")
if err != nil {
ctx.ServerError("GetUncategorizedBoard", err)
return
}

boards, err := models.GetProjectBoards(project.ID)
if err != nil {
ctx.ServerError("GetProjectBoards", err)
return
}

allBoards := models.ProjectBoardList{uncategorizedBoard}
allBoards = append(allBoards, boards...)
if boards[0].ID == 0 {
boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
}

if ctx.Data["Issues"], err = allBoards.LoadIssues(); err != nil {
if ctx.Data["Issues"], err = boards.LoadIssues(); err != nil {
ctx.ServerError("LoadIssuesOfBoards", err)
return
}
Expand All @@ -295,7 +289,7 @@ func ViewProject(ctx *context.Context) {

ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(models.UnitTypeProjects)
ctx.Data["Project"] = project
ctx.Data["Boards"] = allBoards
ctx.Data["Boards"] = boards
ctx.Data["PageIsProjects"] = true
ctx.Data["RequiresDraggable"] = true

Expand Down Expand Up @@ -416,21 +410,19 @@ func AddBoardToProjectPost(ctx *context.Context, form auth.EditProjectBoardTitle
})
}

// EditProjectBoardTitle allows a project board's title to be updated
func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitleForm) {

func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project, *models.ProjectBoard) {
6543 marked this conversation as resolved.
Show resolved Hide resolved
if ctx.User == nil {
ctx.JSON(403, map[string]string{
"message": "Only signed in users are allowed to perform this action.",
})
return
return nil, nil
}

if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) {
ctx.JSON(403, map[string]string{
"message": "Only authorized users are allowed to perform this action.",
})
return
return nil, nil
}

project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
Expand All @@ -440,25 +432,35 @@ func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitle
} else {
ctx.ServerError("GetProjectByID", err)
}
return
return nil, nil
}

board, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
if err != nil {
ctx.InternalServerError(err)
return
return nil, nil
}
if board.ProjectID != ctx.ParamsInt64(":id") {
ctx.JSON(422, map[string]string{
"message": fmt.Sprintf("ProjectBoard[%d] is not in Project[%d] as expected", board.ID, project.ID),
})
return
return nil, nil
}

if project.RepoID != ctx.Repo.Repository.ID {
ctx.JSON(422, map[string]string{
"message": fmt.Sprintf("ProjectBoard[%d] is not in Repository[%d] as expected", board.ID, ctx.Repo.Repository.ID),
})
return nil, nil
}
return project, board
}

// EditProjectBoardTitle allows a project board's title to be updated
func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitleForm) {

_, board := checkProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
}

Expand All @@ -476,6 +478,24 @@ func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitle
})
}

// SetDefaultProjectBoard set default board for uncategorized issues/pulls
func SetDefaultProjectBoard(ctx *context.Context) {

project, board := checkProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
}

if err := models.SetDefaultBoard(project.ID, board.ID); err != nil {
ctx.InternalServerError(err)
return
}

ctx.JSON(200, map[string]interface{}{
"ok": true,
})
}

// MoveIssueAcrossBoards move a card from one board to another in a project
func MoveIssueAcrossBoards(ctx *context.Context) {

Expand Down
28 changes: 28 additions & 0 deletions routers/repo/projects_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repo

import (
"testing"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/test"

"github.com/stretchr/testify/assert"
)

func TestCheckProjectBoardChangePermissions(t *testing.T) {
models.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1/projects/1/2")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
ctx.SetParams(":id", "1")
ctx.SetParams(":boardID", "2")

project, board := checkProjectBoardChangePermissions(ctx)
assert.NotNil(t, project)
assert.NotNil(t, board)
assert.False(t, ctx.Written())
}
1 change: 1 addition & 0 deletions routers/routes/macaron.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ func RegisterMacaronRoutes(m *macaron.Macaron) {
m.Group("/:boardID", func() {
m.Put("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.EditProjectBoardTitle)
m.Delete("", repo.DeleteProjectBoard)
m.Post("/default", repo.SetDefaultProjectBoard)

m.Post("/:index", repo.MoveIssueAcrossBoards)
})
Expand Down
40 changes: 28 additions & 12 deletions templates/repo/projects/view.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@
{{svg "octicon-pencil"}}
{{$.i18n.Tr "repo.projects.board.edit"}}
</a>
{{if not .Default}}
<a class="item show-modal button" data-modal="#set-default-project-board-modal-{{.ID}}">
{{svg "octicon-pin"}}
{{$.i18n.Tr "repo.projects.board.set_default"}}
</a>
{{end}}
<a class="item show-modal button" data-modal="#delete-board-modal-{{.ID}}">
{{svg "octicon-trashcan"}}
{{$.i18n.Tr "repo.projects.board.delete"}}
Expand All @@ -109,24 +115,34 @@
</div>
</div>

<div class="ui basic modal" id="set-default-project-board-modal-{{.ID}}">
<div class="ui icon header">
{{$.i18n.Tr "repo.projects.board.set_default"}}
</div>
<div class="content center">
<label>
{{$.i18n.Tr "repo.projects.board.set_default_desc"}}
</label>
</div>
<div class="text right actions">
<div class="ui cancel button">{{$.i18n.Tr "settings.cancel"}}</div>
<button class="ui red button set-default-project-board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}/default">{{$.i18n.Tr "repo.projects.board.set_default"}}</button>
</div>
</div>

<div class="ui basic modal" id="delete-board-modal-{{.ID}}">
<div class="ui icon header">
{{$.i18n.Tr "repo.projects.board.delete"}}
</div>
<div class="content center">
<input type="hidden" name="action" value="delete">
<div class="field">
<label>
{{$.i18n.Tr "repo.projects.board.deletion_desc"}}
</label>
</div>
<label>
{{$.i18n.Tr "repo.projects.board.deletion_desc"}}
</label>
</div>
<div class="text right actions">
<div class="ui cancel button">{{$.i18n.Tr "settings.cancel"}}</div>
<button class="ui red button delete-project-board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}">{{$.i18n.Tr "repo.projects.board.delete"}}</button>
</div>
<form class="ui form" method="post">
<div class="text right actions">
<div class="ui cancel button">{{$.i18n.Tr "settings.cancel"}}</div>
<button class="ui red button delete-project-board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}">{{$.i18n.Tr "repo.projects.board.delete"}}</button>
</div>
</form>
</div>
</div>
</div>
Expand Down
19 changes: 17 additions & 2 deletions web_src/js/features/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export default async function initProject() {
},
});
},
}
},
);
}

$('.edit-project-board').each(function () {
const projectTitleLabel = $(this).closest('.board-column-header').find('.board-label');
const projectTitleInput = $(this).find(
'.content > .form > .field > .project-board-title'
'.content > .form > .field > .project-board-title',
);

$(this)
Expand All @@ -59,6 +59,21 @@ export default async function initProject() {
});
});

$(document).on('click', '.set-default-project-board', async function (e) {
e.preventDefault();

await $.ajax({
method: 'POST',
url: $(this).data('url'),
headers: {
'X-Csrf-Token': csrf,
'X-Remote': true,
},
contentType: 'application/json',
});

setTimeout(window.location.reload(true), 2000);
6543 marked this conversation as resolved.
Show resolved Hide resolved
});
$('.delete-project-board').each(function () {
$(this).click(function (e) {
e.preventDefault();
Expand Down