Skip to content

Commit 8229cd9

Browse files
committed
Add support multiple projects
1 parent 0d5abe3 commit 8229cd9

File tree

20 files changed

+135
-87
lines changed

20 files changed

+135
-87
lines changed

models/db/search.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const (
3030
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
3131
SearchOrderByForks SearchOrderBy = "num_forks ASC"
3232
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
33+
SearchOrderByTitle SearchOrderBy = "title ASC"
3334
)
3435

3536
const (

models/issues/issue.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ type Issue struct {
103103
PosterID int64 `xorm:"INDEX"`
104104
Poster *user_model.User `xorm:"-"`
105105
OriginalAuthor string
106-
OriginalAuthorID int64 `xorm:"index"`
107-
Title string `xorm:"name"`
108-
Content string `xorm:"LONGTEXT"`
109-
RenderedContent template.HTML `xorm:"-"`
110-
Labels []*Label `xorm:"-"`
111-
MilestoneID int64 `xorm:"INDEX"`
112-
Milestone *Milestone `xorm:"-"`
113-
Project *project_model.Project `xorm:"-"`
106+
OriginalAuthorID int64 `xorm:"index"`
107+
Title string `xorm:"name"`
108+
Content string `xorm:"LONGTEXT"`
109+
RenderedContent template.HTML `xorm:"-"`
110+
Labels []*Label `xorm:"-"`
111+
MilestoneID int64 `xorm:"INDEX"`
112+
Milestone *Milestone `xorm:"-"`
113+
Projects []*project_model.Project `xorm:"-"`
114114
Priority int
115115
AssigneeID int64 `xorm:"-"`
116116
Assignee *user_model.User `xorm:"-"`

models/issues/issue_list.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,19 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
256256
return err
257257
}
258258
for _, project := range projects {
259-
projectMaps[project.IssueID] = project.Project
259+
projectMaps[project.ID] = project.Project
260260
}
261261
left -= limit
262262
issueIDs = issueIDs[limit:]
263263
}
264264

265265
for _, issue := range issues {
266-
issue.Project = projectMaps[issue.ID]
266+
projectIDs := issue.projectIDs(ctx)
267+
for _, i := range projectIDs {
268+
if projectMaps[i] != nil {
269+
issue.Projects = append(issue.Projects, projectMaps[i])
270+
}
271+
}
267272
}
268273
return nil
269274
}

models/issues/issue_list_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
6666
}
6767
if issue.ID == int64(1) {
6868
assert.Equal(t, int64(400), issue.TotalTrackedTime)
69-
assert.NotNil(t, issue.Project)
70-
assert.Equal(t, int64(1), issue.Project.ID)
69+
assert.NotNil(t, issue.Projects)
70+
assert.Equal(t, int64(1), issue.Projects[0].ID)
7171
} else {
72-
assert.Nil(t, issue.Project)
72+
assert.Nil(t, issue.Projects)
7373
}
7474
}
7575
}

models/issues/issue_project.go

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,20 @@ import (
1414

1515
// LoadProject load the project the issue was assigned to
1616
func (issue *Issue) LoadProject(ctx context.Context) (err error) {
17-
if issue.Project == nil {
18-
var p project_model.Project
19-
has, err := db.GetEngine(ctx).Table("project").
17+
if issue.Projects == nil {
18+
err = db.GetEngine(ctx).Table("project").
2019
Join("INNER", "project_issue", "project.id=project_issue.project_id").
21-
Where("project_issue.issue_id = ?", issue.ID).Get(&p)
22-
if err != nil {
23-
return err
24-
} else if has {
25-
issue.Project = &p
26-
}
20+
Where("project_issue.issue_id = ?", issue.ID).Find(&issue.Projects)
2721
}
2822
return err
2923
}
3024

31-
func (issue *Issue) projectID(ctx context.Context) int64 {
32-
var ip project_model.ProjectIssue
33-
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
34-
if err != nil || !has {
35-
return 0
25+
func (issue *Issue) projectIDs(ctx context.Context) []int64 {
26+
var ips []int64
27+
if err := db.GetEngine(ctx).Table("project_issue").Select("project_id").Where("issue_id=?", issue.ID).Find(&ips); err != nil {
28+
return nil
3629
}
37-
return ip.ProjectID
30+
return ips
3831
}
3932

4033
// ProjectBoardID return project board id if issue was assigned to one
@@ -91,24 +84,25 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m
9184
}
9285

9386
// ChangeProjectAssign changes the project associated with an issue
94-
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
87+
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64, action string) error {
9588
ctx, committer, err := db.TxContext(ctx)
9689
if err != nil {
9790
return err
9891
}
9992
defer committer.Close()
10093

101-
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
94+
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID, action); err != nil {
10295
return err
10396
}
10497

10598
return committer.Commit()
10699
}
107100

108-
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
109-
oldProjectID := issue.projectID(ctx)
101+
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64, action string) error {
102+
var oldProjectIDs []int64
103+
var err error
110104

111-
if err := issue.LoadRepo(ctx); err != nil {
105+
if err = issue.LoadRepo(ctx); err != nil {
112106
return err
113107
}
114108

@@ -123,25 +117,51 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
123117
}
124118
}
125119

126-
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
127-
return err
120+
if action == "null" {
121+
if newProjectID == 0 {
122+
action = "clear"
123+
} else {
124+
action = "attach"
125+
count, err := db.GetEngine(ctx).Table("project_issue").Where("issue_id=? AND project_id=?", issue.ID, newProjectID).Count()
126+
if err != nil {
127+
return err
128+
}
129+
if count > 0 {
130+
action = "detach"
131+
}
132+
}
133+
}
134+
135+
if action == "attach" {
136+
err = db.Insert(ctx, &project_model.ProjectIssue{
137+
IssueID: issue.ID,
138+
ProjectID: newProjectID,
139+
})
140+
oldProjectIDs = append(oldProjectIDs, 0)
141+
} else if action == "detach" {
142+
_, err = db.GetEngine(ctx).Where("issue_id=? AND project_id=?", issue.ID, newProjectID).Delete(&project_model.ProjectIssue{})
143+
oldProjectIDs = append(oldProjectIDs, newProjectID)
144+
newProjectID = 0
145+
} else if action == "clear" {
146+
if err = db.GetEngine(ctx).Table("project_issue").Select("project_id").Where("issue_id=?", issue.ID).Find(&oldProjectIDs); err != nil {
147+
return err
148+
}
149+
_, err = db.GetEngine(ctx).Where("issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{})
150+
newProjectID = 0
128151
}
129152

130-
if oldProjectID > 0 || newProjectID > 0 {
153+
for i := range oldProjectIDs {
131154
if _, err := CreateComment(ctx, &CreateCommentOptions{
132155
Type: CommentTypeProject,
133156
Doer: doer,
134157
Repo: issue.Repo,
135158
Issue: issue,
136-
OldProjectID: oldProjectID,
159+
OldProjectID: oldProjectIDs[i],
137160
ProjectID: newProjectID,
138161
}); err != nil {
139162
return err
140163
}
141164
}
142165

143-
return db.Insert(ctx, &project_model.ProjectIssue{
144-
IssueID: issue.ID,
145-
ProjectID: newProjectID,
146-
})
166+
return err
147167
}

models/issues/issue_search.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ func applyProjectBoardCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.S
174174
// do not need to apply any condition
175175
if opts.ProjectBoardID > 0 {
176176
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID}))
177+
} else if opts.ProjectID > 0 {
178+
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0, "project_id": opts.ProjectID}))
177179
} else if opts.ProjectBoardID == db.NoConditionID {
178180
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
179181
}

models/issues/issue_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,10 +418,10 @@ func TestIssueLoadAttributes(t *testing.T) {
418418
}
419419
if issue.ID == int64(1) {
420420
assert.Equal(t, int64(400), issue.TotalTrackedTime)
421-
assert.NotNil(t, issue.Project)
422-
assert.Equal(t, int64(1), issue.Project.ID)
421+
assert.NotNil(t, issue.Projects)
422+
assert.Equal(t, int64(1), issue.Projects[0].ID)
423423
} else {
424-
assert.Nil(t, issue.Project)
424+
assert.Nil(t, issue.Projects)
425425
}
426426
}
427427
}

models/project/issue.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
7676
}
7777

7878
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
79-
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
79+
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64, projectID int64) error {
8080
return db.WithTx(ctx, func(ctx context.Context) error {
8181
sess := db.GetEngine(ctx)
8282

@@ -93,7 +93,7 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs
9393
}
9494

9595
for sorting, issueID := range sortedIssueIDs {
96-
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
96+
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=? AND project_id=?", board.ID, sorting, issueID, projectID)
9797
if err != nil {
9898
return err
9999
}

models/project/project.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
237237
return db.SearchOrderByRecentUpdated
238238
case "leastupdate":
239239
return db.SearchOrderByLeastUpdated
240+
case "title":
241+
return db.SearchOrderByTitle
240242
default:
241243
return db.SearchOrderByNewest
242244
}

modules/indexer/issues/internal/model.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type IndexerData struct {
2626
LabelIDs []int64 `json:"label_ids"`
2727
NoLabel bool `json:"no_label"` // True if LabelIDs is empty
2828
MilestoneID int64 `json:"milestone_id"`
29-
ProjectID int64 `json:"project_id"`
29+
ProjectIDs []int64 `json:"project_ids"`
3030
ProjectBoardID int64 `json:"project_board_id"`
3131
PosterID int64 `json:"poster_id"`
3232
AssigneeID int64 `json:"assignee_id"`

modules/indexer/issues/internal/tests/tests.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,10 @@ var cases = []*testIndexerCase{
312312
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
313313
assert.Equal(t, 5, len(result.Hits))
314314
for _, v := range result.Hits {
315-
assert.Equal(t, int64(1), data[v.ID].ProjectID)
315+
assert.Equal(t, int64(1), data[v.ID].ProjectIDs)
316316
}
317317
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
318-
return v.ProjectID == 1
318+
return v.ProjectIDs[0] == 1
319319
}), result.Total)
320320
},
321321
},
@@ -330,10 +330,10 @@ var cases = []*testIndexerCase{
330330
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
331331
assert.Equal(t, 5, len(result.Hits))
332332
for _, v := range result.Hits {
333-
assert.Equal(t, int64(0), data[v.ID].ProjectID)
333+
assert.Equal(t, int64(0), data[v.ID].ProjectIDs)
334334
}
335335
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
336-
return v.ProjectID == 0
336+
return v.ProjectIDs[0] == 0
337337
}), result.Total)
338338
},
339339
},
@@ -692,6 +692,10 @@ func generateDefaultIndexerData() []*internal.IndexerData {
692692
for i := range subscriberIDs {
693693
subscriberIDs[i] = int64(i) + 1 // SubscriberID should not be 0
694694
}
695+
projectIDs := make([]int64, id%5)
696+
for i := range projectIDs {
697+
projectIDs[i] = int64(i) + 1
698+
}
695699

696700
data = append(data, &internal.IndexerData{
697701
ID: id,
@@ -705,7 +709,7 @@ func generateDefaultIndexerData() []*internal.IndexerData {
705709
LabelIDs: labelIDs,
706710
NoLabel: len(labelIDs) == 0,
707711
MilestoneID: issueIndex % 4,
708-
ProjectID: issueIndex % 5,
712+
ProjectIDs: projectIDs,
709713
ProjectBoardID: issueIndex % 6,
710714
PosterID: id%10 + 1, // PosterID should not be 0
711715
AssigneeID: issueIndex % 10,

modules/indexer/issues/util.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
8787
return nil, false, err
8888
}
8989

90-
var projectID int64
91-
if issue.Project != nil {
92-
projectID = issue.Project.ID
90+
projectIDs := make([]int64, 0, len(issue.Projects))
91+
for _, project := range issue.Projects {
92+
projectIDs = append(projectIDs, project.ID)
9393
}
9494

9595
return &internal.IndexerData{
@@ -104,7 +104,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
104104
LabelIDs: labels,
105105
NoLabel: len(labels) == 0,
106106
MilestoneID: issue.MilestoneID,
107-
ProjectID: projectID,
107+
ProjectIDs: projectIDs,
108108
ProjectBoardID: issue.ProjectBoardID(ctx),
109109
PosterID: issue.PosterID,
110110
AssigneeID: issue.AssigneeID,

routers/web/org/projects.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -442,14 +442,9 @@ func UpdateIssueProject(ctx *context.Context) {
442442
}
443443

444444
projectID := ctx.FormInt64("id")
445+
action := ctx.FormString("action")
445446
for _, issue := range issues {
446-
if issue.Project != nil {
447-
if issue.Project.ID == projectID {
448-
continue
449-
}
450-
}
451-
452-
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
447+
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, action); err != nil {
453448
ctx.ServerError("ChangeProjectAssign", err)
454449
return
455450
}
@@ -671,7 +666,7 @@ func MoveIssues(ctx *context.Context) {
671666
}
672667
}
673668

674-
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
669+
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs, project.ID); err != nil {
675670
ctx.ServerError("MoveIssuesOnProjectBoard", err)
676671
return
677672
}

routers/web/repo/projects.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -390,14 +390,9 @@ func UpdateIssueProject(ctx *context.Context) {
390390
}
391391

392392
projectID := ctx.FormInt64("id")
393+
action := ctx.FormString("action")
393394
for _, issue := range issues {
394-
if issue.Project != nil {
395-
if issue.Project.ID == projectID {
396-
continue
397-
}
398-
}
399-
400-
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
395+
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, action); err != nil {
401396
ctx.ServerError("ChangeProjectAssign", err)
402397
return
403398
}
@@ -664,7 +659,7 @@ func MoveIssues(ctx *context.Context) {
664659
}
665660
}
666661

667-
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs); err != nil {
662+
if err = project_model.MoveIssuesOnProjectBoard(ctx, board, sortedIssueIDs, project.ID); err != nil {
668663
ctx.ServerError("MoveIssuesOnProjectBoard", err)
669664
return
670665
}

routers/web/repo/pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
13381338
ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
13391339
return
13401340
}
1341-
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
1341+
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID, "attach"); err != nil {
13421342
ctx.ServerError("ChangeProjectAssign", err)
13431343
return
13441344
}

routers/web/user/home.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,6 @@ func Issues(ctx *context.Context) {
358358
ctx.Status(http.StatusNotFound)
359359
return
360360
}
361-
362361
ctx.Data["Title"] = ctx.Tr("issues")
363362
ctx.Data["PageIsIssues"] = true
364363
buildIssueOverview(ctx, unit.TypeIssues)

services/issue/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
4242
}
4343
}
4444
if projectID > 0 {
45-
if err := issues_model.ChangeProjectAssign(ctx, issue, issue.Poster, projectID); err != nil {
45+
if err := issues_model.ChangeProjectAssign(ctx, issue, issue.Poster, projectID, "attach"); err != nil {
4646
return err
4747
}
4848
}

0 commit comments

Comments
 (0)