Skip to content

Commit

Permalink
create issue comment for project board move operation
Browse files Browse the repository at this point in the history
- as title
- remove logic about zero `project_board_id` in `project_issue`
  table and fix some related test data. because after go-gitea#29874,
  `project_board_id` willn't be zero
- some small refactoring

Signed-off-by: a1012112796 <1012112796@qq.com>
  • Loading branch information
a1012112796 committed Apr 25, 2024
1 parent c685eef commit 04bae31
Show file tree
Hide file tree
Showing 17 changed files with 414 additions and 115 deletions.
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

0 comments on commit 04bae31

Please sign in to comment.