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

create issue comment for project board move operation #30703

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion models/fixtures/project_issue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
id: 2
issue_id: 2
project_id: 1
project_board_id: 0 # no board assigned
project_board_id: 5

-
id: 3
Expand Down
29 changes: 29 additions & 0 deletions models/issues/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
return lang.TrString("repo.issues.role." + string(r) + "_helper")
}

// CommentProjectBoardExtendData extend data of CommentTypeProjectBoard,
// will be store in `Comment.Content` as json format
type CommentProjectBoardExtendData struct {
FromBoardTitle string
ToBoardTitle string
}

// Comment represents a comment in commit and issue page.
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Expand Down Expand Up @@ -301,6 +308,8 @@ type Comment struct {
NewCommit string `xorm:"-"`
CommitsNum int64 `xorm:"-"`
IsForcePush bool `xorm:"-"`

ProjectBoard *CommentProjectBoardExtendData `xorm:"-"`
}

func init() {
Expand Down Expand Up @@ -539,6 +548,15 @@ func (c *Comment) LoadProject(ctx context.Context) error {
return nil
}

func (c *Comment) LoadProjectBoard() error {
if c.Type != CommentTypeProjectBoard || c.ProjectBoard != nil {
return nil
}

c.ProjectBoard = &CommentProjectBoardExtendData{}
return json.Unmarshal([]byte(c.Content), c.ProjectBoard)
}

// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
func (c *Comment) LoadMilestone(ctx context.Context) error {
if c.OldMilestoneID > 0 {
Expand Down Expand Up @@ -828,6 +846,15 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
IsForcePush: opts.IsForcePush,
Invalidated: opts.Invalidated,
}
if comment.Type == CommentTypeProjectBoard {
extDataJSON, err := json.Marshal(opts.ProjectBoard)
if err != nil {
return nil, err
}
comment.Content = string(extDataJSON)
comment.ProjectBoard = opts.ProjectBoard
}

if _, err = e.Insert(comment); err != nil {
return nil, err
}
Expand Down Expand Up @@ -1007,6 +1034,8 @@ type CreateCommentOptions struct {
RefIsPull bool
IsForcePush bool
Invalidated bool

ProjectBoard *CommentProjectBoardExtendData
}

// GetCommentByID returns the comment by given ID.
Expand Down
6 changes: 6 additions & 0 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ type Issue struct {

// For view issue page.
ShowRole RoleDescriptor `xorm:"-"`

ProjectIssue *project_model.ProjectIssue `xorm:"-"`
}

var (
Expand Down Expand Up @@ -315,6 +317,10 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
return err
}

if err = issue.LoadProjectIssue(ctx); err != nil {
return err
}

if err = issue.LoadAssignees(ctx); err != nil {
return err
}
Expand Down
78 changes: 73 additions & 5 deletions models/issues/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,15 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {

func (issues IssueList) LoadProjects(ctx context.Context) error {
issueIDs := issues.getIssueIDs()
projectMaps := make(map[int64]*project_model.Project, len(issues))
left := len(issueIDs)

type projectWithIssueID struct {
*project_model.Project `xorm:"extends"`
IssueID int64
ProjectIssue *project_model.ProjectIssue `xorm:"extends"`
}

projectMaps := make(map[int64]*projectWithIssueID, len(issues))

for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
Expand All @@ -243,22 +244,28 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
projects := make([]*projectWithIssueID, 0, limit)
err := db.GetEngine(ctx).
Table("project").
Select("project.*, project_issue.issue_id").
Select("project.*, project_issue.*").
Join("INNER", "project_issue", "project.id = project_issue.project_id").
In("project_issue.issue_id", issueIDs[:limit]).
Find(&projects)
if err != nil {
return err
}
for _, project := range projects {
projectMaps[project.IssueID] = project.Project
projectMaps[project.ProjectIssue.IssueID] = project
}
left -= limit
issueIDs = issueIDs[limit:]
}

for _, issue := range issues {
issue.Project = projectMaps[issue.ID]
item, exist := projectMaps[issue.ID]
if !exist {
continue
}

issue.Project = item.Project
issue.ProjectIssue = item.ProjectIssue
}
return nil
}
Expand Down Expand Up @@ -554,6 +561,10 @@ func (issues IssueList) LoadAttributes(ctx context.Context) error {
return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
}

if err := issues.LoadProjectIssueBoards(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: LoadProjectIssueBoards: %w", err)
}

if err := issues.loadAssignees(ctx); err != nil {
return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
}
Expand Down Expand Up @@ -626,3 +637,60 @@ func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error {

return nil
}

func (issues IssueList) getProjectIssueBoardIDs() []int64 {
boardIDmap := make(map[int64]bool, 5)

for _, issue := range issues {
if issue.ProjectIssue != nil {
boardIDmap[issue.ProjectIssue.ProjectBoardID] = true
}
}

bordIDs := make([]int64, 0, len(boardIDmap))
for id := range boardIDmap {
bordIDs = append(bordIDs, id)
}

return bordIDs
}

func (issues IssueList) LoadProjectIssueBoards(ctx context.Context) error {
boardIDs := issues.getProjectIssueBoardIDs()
if len(boardIDs) == 0 {
return nil
}

boardMaps := make(map[int64]*project_model.Board, len(boardIDs))
left := len(boardIDs)
for left > 0 {
limit := db.DefaultMaxInSize
if left < limit {
limit = left
}
err := db.GetEngine(ctx).
In("id", boardIDs[:limit]).
Find(&boardMaps)
if err != nil {
return err
}
left -= limit
boardIDs = boardIDs[limit:]
}

for _, issue := range issues {
if issue.ProjectIssue != nil {
board, exist := boardMaps[issue.ProjectIssue.ProjectBoardID]
if exist {
issue.ProjectIssue.ProjectBoard = board
} else {
issue.ProjectIssue.ProjectBoard = &project_model.Board{
ID: -1,
Title: "Deleted",
}
}
}
}

return nil
}
4 changes: 4 additions & 0 deletions models/issues/issue_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
assert.Equal(t, int64(400), issue.TotalTrackedTime)
assert.NotNil(t, issue.Project)
assert.Equal(t, int64(1), issue.Project.ID)
assert.NotNil(t, issue.ProjectIssue)
assert.Equal(t, int64(1), issue.ProjectIssue.IssueID)
assert.NotNil(t, issue.ProjectIssue.ProjectBoard)
assert.Equal(t, int64(1), issue.ProjectIssue.ProjectBoard.ID)
} else {
assert.Nil(t, issue.Project)
}
Expand Down
29 changes: 27 additions & 2 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ func (issue *Issue) LoadProject(ctx context.Context) (err error) {
return err
}

func (issue *Issue) LoadProjectIssue(ctx context.Context) (err error) {
if issue.Project == nil {
return nil
}

if issue.ProjectIssue != nil {
return nil
}

issue.ProjectIssue, err = project_model.GetProjectIssueByIssueID(ctx, issue.ID)
if err != nil {
return err
}

return issue.ProjectIssue.LoadProjectBoard(ctx)
}

func (issue *Issue) projectID(ctx context.Context) int64 {
var ip project_model.ProjectIssue
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
Expand Down Expand Up @@ -107,6 +124,7 @@ func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.Use

func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
oldProjectID := issue.projectID(ctx)
newBoardID := int64(0)

if err := issue.LoadRepo(ctx); err != nil {
return err
Expand All @@ -121,6 +139,12 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
return fmt.Errorf("issue's repository is not the same as project's repository")
}

newBoard, err := newProject.GetDefaultBoard(ctx)
if err != nil {
return err
}
newBoardID = newBoard.ID
}

if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
Expand All @@ -141,7 +165,8 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
}

return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
IssueID: issue.ID,
ProjectID: newProjectID,
ProjectBoardID: newBoardID,
})
}
95 changes: 95 additions & 0 deletions models/issues/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package issues

import (
"context"
"errors"
"sort"

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

type ProjectMovedIssuesFormItem struct {
IssueID int64 `json:"issueID"`
Sorting int64 `json:"sorting"`
}

type ProjectMovedIssuesForm struct {
Issues []ProjectMovedIssuesFormItem `json:"issues"`
}

func (p *ProjectMovedIssuesForm) ToSortedIssueIDs() (issueIDs, issueSorts []int64) {
sort.Slice(p.Issues, func(i, j int) bool { return p.Issues[i].Sorting < p.Issues[j].Sorting })

issueIDs = make([]int64, 0, len(p.Issues))
issueSorts = make([]int64, 0, len(p.Issues))

for _, issue := range p.Issues {
issueIDs = append(issueIDs, issue.IssueID)
issueSorts = append(issueSorts, issue.Sorting)
}

return issueIDs, issueSorts
}

func MoveIssuesOnProjectBoard(ctx context.Context, doer *user_model.User, form *ProjectMovedIssuesForm, project *project_model.Project, board *project_model.Board) error {
issueIDs, issueSorts := form.ToSortedIssueIDs()

movedIssues, err := GetIssuesByIDs(ctx, issueIDs)
if err != nil {
return err
}

if len(movedIssues) != len(form.Issues) {
return errors.New("some issues do not exist")
}

if _, err = movedIssues.LoadRepositories(ctx); err != nil {
return err
}
if err = movedIssues.LoadProjects(ctx); err != nil {
return err
}
if err = movedIssues.LoadProjectIssueBoards(ctx); err != nil {
return err
}

for _, issue := range movedIssues {
if issue.RepoID != project.RepoID && issue.Repo.OwnerID != project.OwnerID {
return errors.New("Some issue's repoID is not equal to project's repoID")
}
}

return db.WithTx(ctx, func(ctx context.Context) error {
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, issueIDs, issueSorts); err != nil {
return err
}

for _, issue := range movedIssues {
if issue.ProjectIssue.ProjectBoardID == board.ID {
continue
}

_, err = CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProjectBoard,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
ProjectID: project.ID,
ProjectBoard: &CommentProjectBoardExtendData{
FromBoardTitle: issue.ProjectIssue.ProjectBoard.Title,
ToBoardTitle: board.Title,
},
})
if err != nil {
return err
}
}

return nil
})
}
Loading