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

Fix various problems around projects board view #30696

Merged
merged 74 commits into from May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ad1095a
Fix moving column start multiple POST requests and default column wil…
lunny Apr 25, 2024
50a573f
Fix lint
lunny Apr 25, 2024
75d1031
Merge branch 'main' into lunny/fix_move_column
lunny Apr 25, 2024
753608a
Use star as defult column's icon but not a pin and add a star icon on…
lunny Apr 25, 2024
c041e36
Fix repository project sorting
lunny Apr 25, 2024
42bfa04
Fix lint
lunny Apr 25, 2024
715ec4c
Update web_src/js/features/repo-projects.js
lunny Apr 25, 2024
0610385
Update templates/projects/view.tmpl
lunny Apr 26, 2024
1c67fe6
Remove default column icon
lunny Apr 26, 2024
0613d7f
Add the issue to default column when adding to a project
lunny Apr 26, 2024
aeb0bd8
Merge branch 'main' into lunny/fix_move_column
lunny Apr 26, 2024
7bd212a
Merge branch 'lunny/fix_move_column' of github.com:lunny/gitea into l…
lunny Apr 26, 2024
53aea60
Fix lint
lunny Apr 26, 2024
8229bed
Merge branch 'main' into lunny/fix_move_column
lunny Apr 26, 2024
2d078a7
Merge branch 'main' into lunny/fix_move_column
silverwind Apr 26, 2024
d300bfd
Follow maintainers' suggestions
lunny Apr 27, 2024
da7eb69
Merge branch 'main' into lunny/fix_move_column
lunny Apr 27, 2024
1f4852e
Merge branch 'lunny/fix_move_column' of github.com:lunny/gitea into l…
lunny Apr 27, 2024
29d4008
Update models/project/issue.go
lunny Apr 27, 2024
c215806
Fix bug
lunny Apr 28, 2024
b987f00
Merge branch 'main' into lunny/fix_move_column
lunny Apr 28, 2024
3fb24e5
Merge branch 'lunny/fix_move_column' of github.com:lunny/gitea into l…
lunny Apr 28, 2024
6844f6f
Fix function name
lunny Apr 28, 2024
235c9d4
Fix bug
lunny Apr 28, 2024
cca2e95
Follow delvh's suggestion
lunny Apr 29, 2024
f280c9b
Merge branch 'main' into lunny/fix_move_column
lunny Apr 29, 2024
b6fccc0
Follow delvh's suggestion
lunny Apr 30, 2024
833ec56
Merge branch 'main' into lunny/fix_move_column
lunny Apr 30, 2024
81d455f
Use json return but not 500 directly
lunny Apr 30, 2024
03562c7
Add limitation for a project's column to 20
lunny Apr 30, 2024
24c2c13
Reuse method JSONError
lunny Apr 30, 2024
3710ea2
Merge branch 'main' into lunny/fix_move_column
lunny Apr 30, 2024
e3d5531
Fix possible sorting overflow
lunny Apr 30, 2024
b8a8f41
Merge branch 'main' into lunny/fix_move_column
lunny May 1, 2024
db9362d
rename PROJECT_BOARD_CHANGED -> PROJECT_COLUMN_CHANGED
lunny May 2, 2024
891881a
Merge branch 'main' into lunny/fix_move_column
lunny May 2, 2024
2bb832e
Revert "rename PROJECT_BOARD_CHANGED -> PROJECT_COLUMN_CHANGED"
lunny May 2, 2024
e9f58eb
Revert "Reuse method JSONError"
lunny May 2, 2024
85d9fe6
Revert "Use json return but not 500 directly"
lunny May 2, 2024
699c9e6
Follow yp05327's suggestion
lunny May 2, 2024
792ef38
Merge branch 'main' into lunny/fix_move_column
lunny May 2, 2024
f4dd172
Add tests
lunny May 3, 2024
eadf3bb
Merge branch 'main' into lunny/fix_move_column
lunny May 3, 2024
da28c1c
Revert unnecessary change
lunny May 3, 2024
ed56a0f
Add test for NewBoard
lunny May 3, 2024
b27dde9
recover the changes on test
lunny May 4, 2024
e8e934c
Merge branch 'main' into lunny/fix_move_column
lunny May 4, 2024
cf9ef10
Fix move issues sorting problem and possible tests failure
lunny May 4, 2024
f7f36dd
remove unnecessary wrap
lunny May 4, 2024
cd2350e
Fix bug
lunny May 4, 2024
0aa451b
Fix bug
lunny May 4, 2024
be61c91
Fix bug
lunny May 4, 2024
e0a7867
revert unnecessary change
lunny May 4, 2024
79994ea
Merge branch 'main' into lunny/fix_move_column
lunny May 4, 2024
4ce383f
Use another user
lunny May 4, 2024
e56f89f
Create a project dynamically to avoid override
lunny May 4, 2024
76465a4
Fix tests
lunny May 5, 2024
5514594
Add repo unit for repo4 dynamically in test
lunny May 5, 2024
969cc14
Merge branch 'main' into lunny/fix_move_column
lunny May 5, 2024
21f019b
Fix tests
lunny May 5, 2024
94ea1be
Add some permissions check
lunny May 5, 2024
ca73f6c
Fix project test to recover the changes of global variable
lunny May 5, 2024
f586577
fix sorting
wxiaoguang May 6, 2024
94cb84d
fix
wxiaoguang May 6, 2024
6ecdbb6
fix
wxiaoguang May 6, 2024
68c3fdc
fix
wxiaoguang May 6, 2024
ed67149
Merge branch 'main' into lunny/fix_move_column
wxiaoguang May 6, 2024
104c29b
fix
wxiaoguang May 6, 2024
703d35e
Merge branch 'main' into lunny/fix_move_column
lunny May 7, 2024
b38ebce
Merge branch 'main' into lunny/fix_move_column
lunny May 7, 2024
59b4739
move perm check, fix incorrect column type
wxiaoguang May 8, 2024
2eb705c
Merge branch 'main' into lunny/fix_move_column
GiteaBot May 8, 2024
116bdbc
Merge branch 'main' into lunny/fix_move_column
GiteaBot May 8, 2024
0e6f68f
Merge branch 'main' into lunny/fix_move_column
GiteaBot May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions models/db/engine.go
Expand Up @@ -57,6 +57,7 @@ type Engine interface {
SumInt(bean any, columnName string) (res int64, err error)
Sync(...any) error
Select(string) *xorm.Session
SetExpr(string, any) *xorm.Session
NotIn(string, ...any) *xorm.Session
OrderBy(any, ...any) *xorm.Session
Exist(...any) (bool, error)
Expand Down
105 changes: 60 additions & 45 deletions models/issues/issue_project.go
Expand Up @@ -5,11 +5,11 @@ package issues

import (
"context"
"fmt"

"code.gitea.io/gitea/models/db"
project_model "code.gitea.io/gitea/models/project"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util"
)

// LoadProject load the project the issue was assigned to
Expand Down Expand Up @@ -90,58 +90,73 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m
return issuesMap, nil
}

// ChangeProjectAssign changes the project associated with an issue
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()

if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
return err
}

return committer.Commit()
}
// IssueAssignOrRemoveProject changes the project associated with an issue
// If newProjectID is 0, the issue is removed from the project
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
lunny marked this conversation as resolved.
Show resolved Hide resolved
return db.WithTx(ctx, func(ctx context.Context) error {
oldProjectID := issue.projectID(ctx)

func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
oldProjectID := issue.projectID(ctx)
if err := issue.LoadRepo(ctx); err != nil {
return err
}

if err := issue.LoadRepo(ctx); err != nil {
return err
}
// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
if err != nil {
return err
}
if newColumnID == 0 {
newDefaultColumn, err := newProject.GetDefaultBoard(ctx)
if err != nil {
return err
}
newColumnID = newDefaultColumn.ID
}
if !newProject.CanBeAccessedByOwnerRepo(issue.Repo.OwnerID, issue.Repo) {
return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
}
}

// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
if err != nil {
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
return err
}
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
return fmt.Errorf("issue's repository is not the same as project's repository")
}
}

if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
return err
}
if oldProjectID > 0 || newProjectID > 0 {
if _, err := CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldProjectID: oldProjectID,
ProjectID: newProjectID,
}); err != nil {
return err
}
}
if newProjectID == 0 {
return nil
}
if newColumnID == 0 {
panic("newColumnID must not be zero") // shouldn't happen
}

if oldProjectID > 0 || newProjectID > 0 {
if _, err := CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldProjectID: oldProjectID,
ProjectID: newProjectID,
}); err != nil {
res := struct {
MaxSorting int64
IssueCount int64
}{}
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").Table("project_issue").
Where("project_id=?", newProjectID).
And("project_board_id=?", newColumnID).
Get(&res); err != nil {
return err
}
}

return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
newSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
ProjectBoardID: newColumnID,
Sorting: newSorting,
})
})
}
95 changes: 83 additions & 12 deletions models/project/board.go
Expand Up @@ -5,12 +5,14 @@ package project

import (
"context"
"errors"
"fmt"
"regexp"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
)
Expand Down Expand Up @@ -82,6 +84,17 @@ func (b *Board) NumIssues(ctx context.Context) int {
return int(c)
}

func (b *Board) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
issues := make([]*ProjectIssue, 0, 5)
if err := db.GetEngine(ctx).Where("project_id=?", b.ProjectID).
And("project_board_id=?", b.ID).
OrderBy("sorting, id").
Find(&issues); err != nil {
return nil, err
}
return issues, nil
}

func init() {
db.RegisterModel(new(Board))
}
Expand Down Expand Up @@ -150,12 +163,27 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
return db.Insert(ctx, boards)
}

// maxProjectColumns max columns allowed in a project, this should not bigger than 127
// because sorting is int8 in database
const maxProjectColumns = 20
lunny marked this conversation as resolved.
Show resolved Hide resolved

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

res := struct {
MaxSorting int64
ColumnCount int64
}{}
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as column_count").Table("project_board").
Where("project_id=?", board.ProjectID).Get(&res); err != nil {
return err
}
if res.ColumnCount >= maxProjectColumns {
return fmt.Errorf("NewBoard: maximum number of columns reached")
}
board.Sorting = int8(util.Iif(res.ColumnCount > 0, res.MaxSorting+1, 0))
_, err := db.GetEngine(ctx).Insert(board)
return err
}
Expand Down Expand Up @@ -189,7 +217,17 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
return fmt.Errorf("deleteBoardByID: cannot delete default board")
}

if err = board.removeIssues(ctx); err != nil {
// move all issues to the default column
project, err := GetProjectByID(ctx, board.ProjectID)
if err != nil {
return err
}
defaultColumn, err := project.GetDefaultBoard(ctx)
if err != nil {
return err
}

if err = board.moveIssuesToAnotherColumn(ctx, defaultColumn); err != nil {
return err
}

Expand Down Expand Up @@ -242,21 +280,15 @@ func UpdateBoard(ctx context.Context, board *Board) error {
// 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 {
if err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Find(&boards); err != nil {
return nil, err
}

defaultB, err := p.getDefaultBoard(ctx)
if err != nil {
return nil, err
}

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

// getDefaultBoard return default board and ensure only one exists
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
// 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).
Expand Down Expand Up @@ -316,3 +348,42 @@ func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
return nil
})
}

func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (BoardList, error) {
columns := make([]*Board, 0, 5)
if err := db.GetEngine(ctx).
Where("project_id =?", projectID).
In("id", columnsIDs).
OrderBy("sorting").Find(&columns); err != nil {
return nil, err
}
return columns, nil
}

// MoveColumnsOnProject sorts columns in a project
func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error {
return db.WithTx(ctx, func(ctx context.Context) error {
sess := db.GetEngine(ctx)
columnIDs := util.ValuesOfMap(sortedColumnIDs)
movedColumns, err := GetColumnsByIDs(ctx, project.ID, columnIDs)
if err != nil {
return err
}
if len(movedColumns) != len(sortedColumnIDs) {
return errors.New("some columns do not exist")
}

for _, column := range movedColumns {
if column.ProjectID != project.ID {
return fmt.Errorf("column[%d]'s projectID is not equal to project's ID [%d]", column.ProjectID, project.ID)
}
}

for sorting, columnID := range sortedColumnIDs {
if _, err := sess.Exec("UPDATE `project_board` SET sorting=? WHERE id=?", sorting, columnID); err != nil {
return err
}
}
return nil
})
}