Skip to content

Commit 791d7fc

Browse files
lunnywxiaoguangyp05327
authored
Add issue comment when moving issues from one column to another of the project (#29311)
Fix #27278 Replace #27816 This PR adds a meta-comment for an issue when dragging an issue from one column to another of a project. <img width="600" alt="image" src="https://github.com/go-gitea/gitea/assets/81045/5fc1d954-430e-4db0-aaee-a00006fa91f5"> --------- Co-authored-by: wxiaoguang <[email protected]> Co-authored-by: yp05327 <[email protected]>
1 parent aa1055f commit 791d7fc

File tree

11 files changed

+181
-54
lines changed

11 files changed

+181
-54
lines changed

models/issues/comment.go

+50-28
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
222222
return lang.TrString("repo.issues.role." + string(r) + "_helper")
223223
}
224224

225+
// CommentMetaData stores metadata for a comment, these data will not be changed once inserted into database
226+
type CommentMetaData struct {
227+
ProjectColumnID int64 `json:"project_column_id,omitempty"`
228+
ProjectColumnTitle string `json:"project_column_title,omitempty"`
229+
ProjectTitle string `json:"project_title,omitempty"`
230+
}
231+
225232
// Comment represents a comment in commit and issue page.
226233
type Comment struct {
227234
ID int64 `xorm:"pk autoincr"`
@@ -295,6 +302,8 @@ type Comment struct {
295302
RefAction references.XRefAction `xorm:"SMALLINT"` // What happens if RefIssueID resolves
296303
RefIsPull bool
297304

305+
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
306+
298307
RefRepo *repo_model.Repository `xorm:"-"`
299308
RefIssue *Issue `xorm:"-"`
300309
RefComment *Comment `xorm:"-"`
@@ -797,6 +806,15 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
797806
LabelID = opts.Label.ID
798807
}
799808

809+
var commentMetaData *CommentMetaData
810+
if opts.ProjectColumnTitle != "" {
811+
commentMetaData = &CommentMetaData{
812+
ProjectColumnID: opts.ProjectColumnID,
813+
ProjectColumnTitle: opts.ProjectColumnTitle,
814+
ProjectTitle: opts.ProjectTitle,
815+
}
816+
}
817+
800818
comment := &Comment{
801819
Type: opts.Type,
802820
PosterID: opts.Doer.ID,
@@ -830,6 +848,7 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
830848
RefIsPull: opts.RefIsPull,
831849
IsForcePush: opts.IsForcePush,
832850
Invalidated: opts.Invalidated,
851+
CommentMetaData: commentMetaData,
833852
}
834853
if _, err = e.Insert(comment); err != nil {
835854
return nil, err
@@ -982,34 +1001,37 @@ type CreateCommentOptions struct {
9821001
Issue *Issue
9831002
Label *Label
9841003

985-
DependentIssueID int64
986-
OldMilestoneID int64
987-
MilestoneID int64
988-
OldProjectID int64
989-
ProjectID int64
990-
TimeID int64
991-
AssigneeID int64
992-
AssigneeTeamID int64
993-
RemovedAssignee bool
994-
OldTitle string
995-
NewTitle string
996-
OldRef string
997-
NewRef string
998-
CommitID int64
999-
CommitSHA string
1000-
Patch string
1001-
LineNum int64
1002-
TreePath string
1003-
ReviewID int64
1004-
Content string
1005-
Attachments []string // UUIDs of attachments
1006-
RefRepoID int64
1007-
RefIssueID int64
1008-
RefCommentID int64
1009-
RefAction references.XRefAction
1010-
RefIsPull bool
1011-
IsForcePush bool
1012-
Invalidated bool
1004+
DependentIssueID int64
1005+
OldMilestoneID int64
1006+
MilestoneID int64
1007+
OldProjectID int64
1008+
ProjectID int64
1009+
ProjectTitle string
1010+
ProjectColumnID int64
1011+
ProjectColumnTitle string
1012+
TimeID int64
1013+
AssigneeID int64
1014+
AssigneeTeamID int64
1015+
RemovedAssignee bool
1016+
OldTitle string
1017+
NewTitle string
1018+
OldRef string
1019+
NewRef string
1020+
CommitID int64
1021+
CommitSHA string
1022+
Patch string
1023+
LineNum int64
1024+
TreePath string
1025+
ReviewID int64
1026+
Content string
1027+
Attachments []string // UUIDs of attachments
1028+
RefRepoID int64
1029+
RefIssueID int64
1030+
RefCommentID int64
1031+
RefAction references.XRefAction
1032+
RefIsPull bool
1033+
IsForcePush bool
1034+
Invalidated bool
10131035
}
10141036

10151037
// GetCommentByID returns the comment by given ID.

models/issues/issue_list.go

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
441441
Join("INNER", "issue", "issue.id = comment.issue_id").
442442
In("issue.id", issuesIDs[:limit]).
443443
Where(cond).
444+
NoAutoCondition().
444445
Rows(new(Comment))
445446
if err != nil {
446447
return err

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,8 @@ var migrations = []Migration{
597597
NewMigration("Add skip_secondary_authorization option to oauth2 application table", v1_23.AddSkipSecondaryAuthColumnToOAuth2ApplicationTable),
598598
// v302 -> v303
599599
NewMigration("Add index to action_task stopped log_expired", v1_23.AddIndexToActionTaskStoppedLogExpired),
600+
// v303 -> v304
601+
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
600602
}
601603

602604
// GetCurrentDBVersion returns the current db version

models/migrations/v1_23/v303.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_23 //nolint
5+
6+
import (
7+
"xorm.io/xorm"
8+
)
9+
10+
// CommentMetaData stores metadata for a comment, these data will not be changed once inserted into database
11+
type CommentMetaData struct {
12+
ProjectColumnID int64 `json:"project_column_id"`
13+
ProjectColumnTitle string `json:"project_column_title"`
14+
ProjectTitle string `json:"project_title"`
15+
}
16+
17+
func AddCommentMetaDataColumn(x *xorm.Engine) error {
18+
type Comment struct {
19+
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
20+
}
21+
22+
return x.Sync(new(Comment))
23+
}

models/project/issue.go

-24
Original file line numberDiff line numberDiff line change
@@ -76,30 +76,6 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
7676
return int(c)
7777
}
7878

79-
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
80-
func MoveIssuesOnProjectColumn(ctx context.Context, column *Column, sortedIssueIDs map[int64]int64) error {
81-
return db.WithTx(ctx, func(ctx context.Context) error {
82-
sess := db.GetEngine(ctx)
83-
issueIDs := util.ValuesOfMap(sortedIssueIDs)
84-
85-
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", column.ProjectID).In("issue_id", issueIDs).Count()
86-
if err != nil {
87-
return err
88-
}
89-
if int(count) != len(sortedIssueIDs) {
90-
return fmt.Errorf("all issues have to be added to a project first")
91-
}
92-
93-
for sorting, issueID := range sortedIssueIDs {
94-
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID)
95-
if err != nil {
96-
return err
97-
}
98-
}
99-
return nil
100-
})
101-
}
102-
10379
func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error {
10480
if c.ProjectID != newColumn.ProjectID {
10581
return fmt.Errorf("columns have to be in the same project")

options/locale/locale_en-US.ini

+1
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,7 @@ issues.remove_labels = removed the %s labels %s
14761476
issues.add_remove_labels = added %s and removed %s labels %s
14771477
issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
14781478
issues.add_project_at = `added this to the <b>%s</b> project %s`
1479+
issues.move_to_column_of_project = `moved this to %s in %s on %s`
14791480
issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
14801481
issues.change_project_at = `modified the project from <b>%s</b> to <b>%s</b> %s`
14811482
issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`

routers/web/org/projects.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
shared_user "code.gitea.io/gitea/routers/web/shared/user"
2424
"code.gitea.io/gitea/services/context"
2525
"code.gitea.io/gitea/services/forms"
26+
project_service "code.gitea.io/gitea/services/projects"
2627
)
2728

2829
const (
@@ -601,7 +602,7 @@ func MoveIssues(ctx *context.Context) {
601602
}
602603
}
603604

604-
if err = project_model.MoveIssuesOnProjectColumn(ctx, column, sortedIssueIDs); err != nil {
605+
if err = project_service.MoveIssuesOnProjectColumn(ctx, ctx.Doer, column, sortedIssueIDs); err != nil {
605606
ctx.ServerError("MoveIssuesOnProjectColumn", err)
606607
return
607608
}

routers/web/repo/issue.go

+5
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,11 @@ func ViewIssue(ctx *context.Context) {
16871687
if comment.ProjectID > 0 && comment.Project == nil {
16881688
comment.Project = ghostProject
16891689
}
1690+
} else if comment.Type == issues_model.CommentTypeProjectColumn {
1691+
if err = comment.LoadProject(ctx); err != nil {
1692+
ctx.ServerError("LoadProject", err)
1693+
return
1694+
}
16901695
} else if comment.Type == issues_model.CommentTypeAssignees || comment.Type == issues_model.CommentTypeReviewRequest {
16911696
if err = comment.LoadAssigneeUserAndTeam(ctx); err != nil {
16921697
ctx.ServerError("LoadAssigneeUserAndTeam", err)

routers/web/repo/projects.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"code.gitea.io/gitea/modules/web"
2626
"code.gitea.io/gitea/services/context"
2727
"code.gitea.io/gitea/services/forms"
28+
project_service "code.gitea.io/gitea/services/projects"
2829
)
2930

3031
const (
@@ -664,7 +665,7 @@ func MoveIssues(ctx *context.Context) {
664665
}
665666
}
666667

667-
if err = project_model.MoveIssuesOnProjectColumn(ctx, column, sortedIssueIDs); err != nil {
668+
if err = project_service.MoveIssuesOnProjectColumn(ctx, ctx.Doer, column, sortedIssueIDs); err != nil {
668669
ctx.ServerError("MoveIssuesOnProjectColumn", err)
669670
return
670671
}

services/projects/issue.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package project
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"code.gitea.io/gitea/models/db"
11+
issues_model "code.gitea.io/gitea/models/issues"
12+
project_model "code.gitea.io/gitea/models/project"
13+
user_model "code.gitea.io/gitea/models/user"
14+
)
15+
16+
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
17+
func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, column *project_model.Column, sortedIssueIDs map[int64]int64) error {
18+
return db.WithTx(ctx, func(ctx context.Context) error {
19+
issueIDs := make([]int64, 0, len(sortedIssueIDs))
20+
for _, issueID := range sortedIssueIDs {
21+
issueIDs = append(issueIDs, issueID)
22+
}
23+
count, err := db.GetEngine(ctx).
24+
Where("project_id=?", column.ProjectID).
25+
In("issue_id", issueIDs).
26+
Count(new(project_model.ProjectIssue))
27+
if err != nil {
28+
return err
29+
}
30+
if int(count) != len(sortedIssueIDs) {
31+
return fmt.Errorf("all issues have to be added to a project first")
32+
}
33+
34+
issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
35+
if err != nil {
36+
return err
37+
}
38+
if _, err := issues.LoadRepositories(ctx); err != nil {
39+
return err
40+
}
41+
42+
project, err := project_model.GetProjectByID(ctx, column.ProjectID)
43+
if err != nil {
44+
return err
45+
}
46+
47+
issuesMap := make(map[int64]*issues_model.Issue, len(issues))
48+
for _, issue := range issues {
49+
issuesMap[issue.ID] = issue
50+
}
51+
52+
for sorting, issueID := range sortedIssueIDs {
53+
curIssue := issuesMap[issueID]
54+
if curIssue == nil {
55+
continue
56+
}
57+
58+
_, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID)
59+
if err != nil {
60+
return err
61+
}
62+
63+
// add timeline to issue
64+
if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
65+
Type: issues_model.CommentTypeProjectColumn,
66+
Doer: doer,
67+
Repo: curIssue.Repo,
68+
Issue: curIssue,
69+
ProjectID: column.ProjectID,
70+
ProjectTitle: project.Title,
71+
ProjectColumnID: column.ID,
72+
ProjectColumnTitle: column.Title,
73+
}); err != nil {
74+
return err
75+
}
76+
}
77+
return nil
78+
})
79+
}

templates/repo/issue/view_content/comments.tmpl

+16
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,22 @@
604604
{{end}}
605605
</span>
606606
</div>
607+
{{else if eq .Type 31}}
608+
{{if not $.UnitProjectsGlobalDisabled}}
609+
<div class="timeline-item event" id="{{.HashTag}}">
610+
<span class="badge">{{svg "octicon-project"}}</span>
611+
{{template "shared/user/avatarlink" dict "user" .Poster}}
612+
<span class="text grey muted-links">
613+
{{template "shared/user/authorlink" .Poster}}
614+
{{$newProjectDisplay := .CommentMetaData.ProjectTitle}}
615+
{{if .Project}}
616+
{{$trKey := printf "projects.type-%d.display_name" .Project.Type}}
617+
{{$newProjectDisplay = HTMLFormat `%s <a href="%s"><span data-tooltip-content="%s">%s</span></a>` (svg .Project.IconName) (.Project.Link ctx) (ctx.Locale.Tr $trKey) .Project.Title}}
618+
{{end}}
619+
{{ctx.Locale.Tr "repo.issues.move_to_column_of_project" .CommentMetaData.ProjectColumnTitle $newProjectDisplay $createdStr}}
620+
</span>
621+
</div>
622+
{{end}}
607623
{{else if eq .Type 32}}
608624
<div class="timeline-item-group">
609625
<div class="timeline-item event" id="{{.HashTag}}">

0 commit comments

Comments
 (0)