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

Add support multiple projects again #30381

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8229cd9
Add support multiple projects
tyroneyeh Mar 28, 2024
d677ee8
Merge branch 'go-gitea:main' into main
tyroneyeh Mar 29, 2024
e87dea4
Merge branch 'main' into main
wolfogre Apr 1, 2024
8dcdbf6
feat: make issuer indexer support multiple projects
wolfogre Apr 1, 2024
fc63953
for unit test
tyroneyeh Apr 1, 2024
91b55d1
Add s for LoadProject function
tyroneyeh Apr 11, 2024
54b5396
Update templates/repo/issue/view_content/sidebar.tmpl
tyroneyeh Apr 15, 2024
1ee9807
Update templates/repo/issue/view_content/sidebar.tmpl
tyroneyeh Apr 15, 2024
596316a
Update templates/repo/issue/view_content/sidebar.tmpl
tyroneyeh Apr 15, 2024
2ba5b9d
Remove double item class
tyroneyeh Apr 15, 2024
3d1e916
Merge branch 'main' into main_multiple_projects
tyroneyeh May 3, 2024
6fba177
Merge commit '82a0c36332824b8ab41efdf6503e86170ce92f08' into main_mul…
tyroneyeh May 20, 2024
577f9c9
Add missing merge file
tyroneyeh May 20, 2024
cc22c5f
Merge commit 'c93cbc991e99a937223844e072a054cf76e815ca' into main_mul…
tyroneyeh May 29, 2024
1bf5734
Fix conflicting
tyroneyeh Jun 5, 2024
a5f6af9
Merge commit '816222243af523316041692622be6f48ef068693' into main_mul…
tyroneyeh Jun 5, 2024
6d268cd
add missing column for args
tyroneyeh Jun 5, 2024
d00558f
Fix filter no project and attach project no work issue
tyroneyeh Jun 5, 2024
52e5f01
Adjust projectID to projectIDs
tyroneyeh Jun 6, 2024
65c2dd0
Modify for unit test
tyroneyeh Jun 6, 2024
314adb1
Merge commit '2e12343fc4ca96a215d6820c4467b619eaa5cbe9' into main_mul…
tyroneyeh Oct 9, 2024
13fa5f4
Fix
tyroneyeh Oct 16, 2024
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 @@ -24,6 +24,7 @@ const (
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
SearchOrderByTitle SearchOrderBy = "title ASC"
)

const (
Expand Down
24 changes: 12 additions & 12 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ 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:"-"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
Labels []*Label `xorm:"-"`
isLabelsLoaded bool `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
isMilestoneLoaded bool `xorm:"-"`
Project *project_model.Project `xorm:"-"`
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
RenderedContent template.HTML `xorm:"-"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
Labels []*Label `xorm:"-"`
isLabelsLoaded bool `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
isMilestoneLoaded bool `xorm:"-"`
Projects []*project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
Expand Down Expand Up @@ -332,7 +332,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
return err
}

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

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 @@ -253,14 +253,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)
}
}
}
102 changes: 54 additions & 48 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,21 @@ 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").
func (issue *Issue) LoadProjects(ctx context.Context) (err error) {
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
}

// ProjectColumnID return project column id if issue was assigned to one
Expand All @@ -51,7 +44,7 @@ func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) {
issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
o.ProjectColumnID = b.ID
o.ProjectID = b.ProjectID
o.ProjectIDs = append(o.ProjectIDs, b.ProjectID)
o.SortType = "project-column-sorting"
}))
if err != nil {
Expand All @@ -61,7 +54,7 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *Is
if b.Default {
issues, err := Issues(ctx, &IssuesOptions{
ProjectColumnID: db.NoConditionID,
ProjectID: b.ProjectID,
ProjectIDs: []int64{b.ProjectID},
SortType: "project-column-sorting",
})
if err != nil {
Expand Down Expand Up @@ -92,10 +85,10 @@ func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList,

// IssueAssignOrRemoveProject changes the project associated with an issue
// If newProjectID is 0, the issue is removed from the project
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64, action string) error {
return db.WithTx(ctx, func(ctx context.Context) error {
oldProjectID := issue.projectID(ctx)

var oldProjectIDs []int64
var err error
if err := issue.LoadRepo(ctx); err != nil {
return err
}
Expand All @@ -118,11 +111,47 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo
}
}

if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
return err
if action == "attach" || (action == "null" && newProjectID > 0) {
if newProjectID == 0 {
return nil
}
if newColumnID == 0 {
panic("newColumnID must not be zero") // shouldn't happen
}
res := struct {
IssueCount int64
}{}
if _, err := db.GetEngine(ctx).Select("count(*) as issue_count").Table("project_issue").
Where("project_id=?", newProjectID).
And("issue_id=?", issue.ID).
Get(&res); err != nil {
return err
}
if res.IssueCount == 0 {
err = db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
ProjectColumnID: newColumnID,
})
oldProjectIDs = []int64{0}
} else {
_, err = db.GetEngine(ctx).Where("issue_id=? AND project_id=?", issue.ID, newProjectID).Delete(&project_model.ProjectIssue{})
oldProjectIDs = []int64{newProjectID}
newProjectID = 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" || (action == "null" && newProjectID == 0) {
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 _, oldProjectID := range oldProjectIDs {
if _, err := CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Doer: doer,
Expand All @@ -134,29 +163,6 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo
return err
}
}
if newProjectID == 0 {
return nil
}
if newColumnID == 0 {
panic("newColumnID must not be zero") // shouldn't happen
}

res := struct {
MaxSorting int64
IssueCount int64
}{}
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").Table("project_issue").
Where("project_id=?", newProjectID).
And("project_board_id=?", newColumnID).
Get(&res); err != nil {
return err
}
newSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
ProjectColumnID: newColumnID,
Sorting: newSorting,
})
return err
})
}
16 changes: 10 additions & 6 deletions models/issues/issue_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type IssuesOptions struct { //nolint
ReviewedID int64
SubscriberID int64
MilestoneIDs []int64
ProjectID int64
ProjectIDs []int64
ProjectColumnID int64
IsClosed optional.Option[bool]
IsPull optional.Option[bool]
Expand Down Expand Up @@ -183,11 +183,13 @@ func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) {
}

func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) {
if opts.ProjectID > 0 { // specific project
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
And("project_issue.project_id=?", opts.ProjectID)
} else if opts.ProjectID == db.NoConditionID { // show those that are in no project
sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue").And(builder.Neq{"project_id": 0})))
if len(opts.ProjectIDs) > 0 { // specific project
if opts.ProjectIDs[0] == db.NoConditionID { // show those that are in no project
sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue").And(builder.Neq{"project_id": 0})))
} else {
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
In("project_issue.project_id", opts.ProjectIDs)
}
}
// opts.ProjectID == 0 means all projects,
// do not need to apply any condition
Expand All @@ -198,6 +200,8 @@ func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) {
// do not need to apply any condition
if opts.ProjectColumnID > 0 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": opts.ProjectColumnID}))
} else if len(opts.ProjectIDs) > 0 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}).And(builder.In("project_id", opts.ProjectIDs)))
} else if opts.ProjectColumnID == 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
2 changes: 2 additions & 0 deletions models/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,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
13 changes: 9 additions & 4 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 @@ -78,7 +78,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 @@ -221,8 +222,12 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
queries = append(queries, bleve.NewDisjunctionQuery(milestoneQueries...))
}

if options.ProjectID.Has() {
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectID.Value(), "project_id"))
if len(options.ProjectIDs) > 0 {
var projectQueries []query.Query
for _, projectID := range options.ProjectIDs {
projectQueries = append(projectQueries, inner_bleve.NumericEqualityQuery(projectID, "project_ids"))
}
queries = append(queries, bleve.NewDisjunctionQuery(projectQueries...))
}
if options.ProjectColumnID.Has() {
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectColumnID.Value(), "project_board_id"))
Expand Down
7 changes: 6 additions & 1 deletion modules/indexer/issues/db/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
ReviewRequestedID: convertID(options.ReviewRequestedID),
ReviewedID: convertID(options.ReviewedID),
SubscriberID: convertID(options.SubscriberID),
ProjectID: convertID(options.ProjectID),
ProjectColumnID: convertID(options.ProjectColumnID),
IsClosed: options.IsClosed,
IsPull: options.IsPull,
Expand All @@ -84,6 +83,12 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
opts.MilestoneIDs = options.MilestoneIDs
}

if len(options.ProjectIDs) == 1 && options.ProjectIDs[0] == 0 {
opts.ProjectIDs = []int64{db.NoConditionID}
} else {
opts.ProjectIDs = options.ProjectIDs
}

if options.NoLabelOnly {
opts.LabelIDs = []int64{0} // Be careful, it's zero, not db.NoConditionID
} else {
Expand Down
8 changes: 4 additions & 4 deletions modules/indexer/issues/dboptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
searchOpt.MilestoneIDs = opts.MilestoneIDs
}

if opts.ProjectID > 0 {
searchOpt.ProjectID = optional.Some(opts.ProjectID)
} else if opts.ProjectID == -1 { // FIXME: this is inconsistent from other places
searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0)
if len(opts.ProjectIDs) == 1 && opts.ProjectIDs[0] == db.NoConditionID {
searchOpt.ProjectIDs = []int64{0}
} else {
searchOpt.ProjectIDs = opts.ProjectIDs // Those issues with no project(projectid==0)
}

if opts.AssigneeID > 0 {
Expand Down
9 changes: 5 additions & 4 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 @@ -194,8 +195,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query.Must(elastic.NewTermsQuery("milestone_id", toAnySlice(options.MilestoneIDs)...))
}

if options.ProjectID.Has() {
query.Must(elastic.NewTermQuery("project_id", options.ProjectID.Value()))
if len(options.ProjectIDs) > 0 {
query.Must(elastic.NewTermsQuery("project_ids", toAnySlice(options.ProjectIDs)...))
}
if options.ProjectColumnID.Has() {
query.Must(elastic.NewTermQuery("project_board_id", options.ProjectColumnID.Value()))
Expand Down
Loading
Loading