Skip to content

Add support multiple projects again #30163

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

Closed
wants to merge 5 commits into from
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
1 change: 1 addition & 0 deletions models/db/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
SearchOrderByTitle SearchOrderBy = "title ASC"
)

const (
Expand Down
16 changes: 8 additions & 8 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,14 @@ type Issue struct {
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
RenderedContent template.HTML `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Project *project_model.Project `xorm:"-"`
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
RenderedContent template.HTML `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Projects []*project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
Expand Down
9 changes: 7 additions & 2 deletions models/issues/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,19 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
return err
}
for _, project := range projects {
projectMaps[project.IssueID] = project.Project
projectMaps[project.ID] = project.Project
}
left -= limit
issueIDs = issueIDs[limit:]
}

for _, issue := range issues {
issue.Project = projectMaps[issue.ID]
projectIDs := issue.projectIDs(ctx)
for _, i := range projectIDs {
if projectMaps[i] != nil {
issue.Projects = append(issue.Projects, projectMaps[i])
}
}
}
return nil
}
Expand Down
6 changes: 3 additions & 3 deletions models/issues/issue_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
}
if issue.ID == int64(1) {
assert.Equal(t, int64(400), issue.TotalTrackedTime)
assert.NotNil(t, issue.Project)
assert.Equal(t, int64(1), issue.Project.ID)
assert.NotNil(t, issue.Projects)
assert.Equal(t, int64(1), issue.Projects[0].ID)
} else {
assert.Nil(t, issue.Project)
assert.Nil(t, issue.Projects)
}
}
}
76 changes: 48 additions & 28 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,20 @@ import (

// LoadProject load the project the issue was assigned to
func (issue *Issue) LoadProject(ctx context.Context) (err error) {
if issue.Project == nil {
var p project_model.Project
has, err := db.GetEngine(ctx).Table("project").
if issue.Projects == nil {
err = db.GetEngine(ctx).Table("project").
Join("INNER", "project_issue", "project.id=project_issue.project_id").
Where("project_issue.issue_id = ?", issue.ID).Get(&p)
if err != nil {
return err
} else if has {
issue.Project = &p
}
Where("project_issue.issue_id = ?", issue.ID).Find(&issue.Projects)
}
return err
}

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)
if err != nil || !has {
return 0
func (issue *Issue) projectIDs(ctx context.Context) []int64 {
var ips []int64
if err := db.GetEngine(ctx).Table("project_issue").Select("project_id").Where("issue_id=?", issue.ID).Find(&ips); err != nil {
return nil
}
return ip.ProjectID
return ips
}

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

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

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

return committer.Commit()
}

func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
oldProjectID := issue.projectID(ctx)
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64, action string) error {
var oldProjectIDs []int64
var err error

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

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

if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
return err
if action == "null" {
if newProjectID == 0 {
action = "clear"
} else {
action = "attach"
count, err := db.GetEngine(ctx).Table("project_issue").Where("issue_id=? AND project_id=?", issue.ID, newProjectID).Count()
if err != nil {
return err
}
if count > 0 {
action = "detach"
}
}
}

if action == "attach" {
err = db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
})
oldProjectIDs = append(oldProjectIDs, 0)
} else if action == "detach" {
_, err = db.GetEngine(ctx).Where("issue_id=? AND project_id=?", issue.ID, newProjectID).Delete(&project_model.ProjectIssue{})
oldProjectIDs = append(oldProjectIDs, newProjectID)
newProjectID = 0
} else if action == "clear" {
if err = db.GetEngine(ctx).Table("project_issue").Select("project_id").Where("issue_id=?", issue.ID).Find(&oldProjectIDs); err != nil {
return err
}
_, err = db.GetEngine(ctx).Where("issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{})
newProjectID = 0
}

if oldProjectID > 0 || newProjectID > 0 {
for i := range oldProjectIDs {
if _, err := CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldProjectID: oldProjectID,
OldProjectID: oldProjectIDs[i],
ProjectID: newProjectID,
}); err != nil {
return err
}
}

return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
})
return err
}
2 changes: 2 additions & 0 deletions models/issues/issue_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ func applyProjectBoardCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.S
// do not need to apply any condition
if opts.ProjectBoardID > 0 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectBoardID}))
} else if opts.ProjectID > 0 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0, "project_id": opts.ProjectID}))
} else if opts.ProjectBoardID == db.NoConditionID {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
}
Expand Down
6 changes: 3 additions & 3 deletions models/issues/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,10 @@ func TestIssueLoadAttributes(t *testing.T) {
}
if issue.ID == int64(1) {
assert.Equal(t, int64(400), issue.TotalTrackedTime)
assert.NotNil(t, issue.Project)
assert.Equal(t, int64(1), issue.Project.ID)
assert.NotNil(t, issue.Projects)
assert.Equal(t, int64(1), issue.Projects[0].ID)
} else {
assert.Nil(t, issue.Project)
assert.Nil(t, issue.Projects)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions models/project/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
}

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

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

for sorting, issueID := range sortedIssueIDs {
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=? AND project_id=?", board.ID, sorting, issueID, projectID)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions models/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
return db.SearchOrderByRecentUpdated
case "leastupdate":
return db.SearchOrderByLeastUpdated
case "title":
return db.SearchOrderByTitle
default:
return db.SearchOrderByNewest
}
Expand Down
11 changes: 8 additions & 3 deletions modules/indexer/issues/bleve/bleve.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
const (
issueIndexerAnalyzer = "issueIndexer"
issueIndexerDocType = "issueIndexerDocType"
issueIndexerLatestVersion = 4
issueIndexerLatestVersion = 5
)

const unicodeNormalizeName = "unicodeNormalize"
Expand Down Expand Up @@ -82,7 +82,8 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) {
docMapping.AddFieldMappingsAt("label_ids", numberFieldMapping)
docMapping.AddFieldMappingsAt("no_label", boolFieldMapping)
docMapping.AddFieldMappingsAt("milestone_id", numberFieldMapping)
docMapping.AddFieldMappingsAt("project_id", numberFieldMapping)
docMapping.AddFieldMappingsAt("project_ids", numberFieldMapping)
docMapping.AddFieldMappingsAt("no_project", boolFieldMapping)
docMapping.AddFieldMappingsAt("project_board_id", numberFieldMapping)
docMapping.AddFieldMappingsAt("poster_id", numberFieldMapping)
docMapping.AddFieldMappingsAt("assignee_id", numberFieldMapping)
Expand Down Expand Up @@ -226,7 +227,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
}

if options.ProjectID.Has() {
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectID.Value(), "project_id"))
if v := options.ProjectID.Value(); v != 0 {
queries = append(queries, inner_bleve.NumericEqualityQuery(v, "project_ids"))
} else {
queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_project"))
}
}
if options.ProjectBoardID.Has() {
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectBoardID.Value(), "project_board_id"))
Expand Down
11 changes: 8 additions & 3 deletions modules/indexer/issues/elasticsearch/elasticsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
)

const (
issueIndexerLatestVersion = 1
issueIndexerLatestVersion = 2
// multi-match-types, currently only 2 types are used
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
esMultiMatchTypeBestFields = "best_fields"
Expand Down Expand Up @@ -61,7 +61,8 @@ const (
"label_ids": { "type": "integer", "index": true },
"no_label": { "type": "boolean", "index": true },
"milestone_id": { "type": "integer", "index": true },
"project_id": { "type": "integer", "index": true },
"project_ids": { "type": "integer", "index": true },
"no_project": { "type": "boolean", "index": true },
"project_board_id": { "type": "integer", "index": true },
"poster_id": { "type": "integer", "index": true },
"assignee_id": { "type": "integer", "index": true },
Expand Down Expand Up @@ -196,7 +197,11 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
}

if options.ProjectID.Has() {
query.Must(elastic.NewTermQuery("project_id", options.ProjectID.Value()))
if v := options.ProjectID.Value(); v != 0 {
query.Must(elastic.NewTermQuery("project_ids", v))
} else {
query.Must(elastic.NewTermQuery("no_project", true))
}
}
if options.ProjectBoardID.Has() {
query.Must(elastic.NewTermQuery("project_board_id", options.ProjectBoardID.Value()))
Expand Down
6 changes: 0 additions & 6 deletions modules/indexer/issues/indexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,6 @@ func searchIssueInProject(t *testing.T) {
opts SearchOptions
expectedIDs []int64
}{
{
SearchOptions{
ProjectID: optional.Some(int64(1)),
},
[]int64{5, 3, 2, 1},
},
{
SearchOptions{
ProjectBoardID: optional.Some(int64(1)),
Expand Down
5 changes: 3 additions & 2 deletions modules/indexer/issues/internal/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type IndexerData struct {
LabelIDs []int64 `json:"label_ids"`
NoLabel bool `json:"no_label"` // True if LabelIDs is empty
MilestoneID int64 `json:"milestone_id"`
ProjectID int64 `json:"project_id"`
ProjectIDs []int64 `json:"project_ids"`
NoProject bool `json:"no_project"` // True if ProjectIDs is empty
ProjectBoardID int64 `json:"project_board_id"`
PosterID int64 `json:"poster_id"`
AssigneeID int64 `json:"assignee_id"`
Expand Down Expand Up @@ -89,7 +90,7 @@ type SearchOptions struct {

MilestoneIDs []int64 // milestones the issues have

ProjectID optional.Option[int64] // project the issues belong to
ProjectID optional.Option[int64] // project the issues belong to, zero means no project
ProjectBoardID optional.Option[int64] // project board the issues belong to

PosterID optional.Option[int64] // poster of the issues
Expand Down
15 changes: 10 additions & 5 deletions modules/indexer/issues/internal/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,10 @@ var cases = []*testIndexerCase{
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
for _, v := range result.Hits {
assert.Equal(t, int64(1), data[v.ID].ProjectID)
assert.Contains(t, data[v.ID].ProjectIDs, int64(1))
}
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
return v.ProjectID == 1
return slices.Contains(v.ProjectIDs, 1)
}), result.Total)
},
},
Expand All @@ -330,10 +330,10 @@ var cases = []*testIndexerCase{
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
assert.Equal(t, 5, len(result.Hits))
for _, v := range result.Hits {
assert.Equal(t, int64(0), data[v.ID].ProjectID)
assert.Empty(t, data[v.ID].ProjectIDs)
}
assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool {
return v.ProjectID == 0
return len(v.ProjectIDs) == 0
}), result.Total)
},
},
Expand Down Expand Up @@ -692,6 +692,10 @@ func generateDefaultIndexerData() []*internal.IndexerData {
for i := range subscriberIDs {
subscriberIDs[i] = int64(i) + 1 // SubscriberID should not be 0
}
projectIDs := make([]int64, id%5)
for i := range projectIDs {
projectIDs[i] = int64(i) + 1 // ProjectID should not be 0
}

data = append(data, &internal.IndexerData{
ID: id,
Expand All @@ -705,7 +709,8 @@ func generateDefaultIndexerData() []*internal.IndexerData {
LabelIDs: labelIDs,
NoLabel: len(labelIDs) == 0,
MilestoneID: issueIndex % 4,
ProjectID: issueIndex % 5,
ProjectIDs: projectIDs,
NoProject: len(projectIDs) == 0,
ProjectBoardID: issueIndex % 6,
PosterID: id%10 + 1, // PosterID should not be 0
AssigneeID: issueIndex % 10,
Expand Down
Loading