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

Support multiple projects #27984

Closed
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
16 changes: 8 additions & 8 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,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 string `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 string `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
16 changes: 9 additions & 7 deletions models/issues/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,13 @@ 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))
issueMap := make(map[int64]*Issue, len(issues))
left := len(issueIDs)

for _, issue := range issues {
issueMap[issue.ID] = issue;
}

type projectWithIssueID struct {
*project_model.Project `xorm:"extends"`
IssueID int64
Expand All @@ -255,16 +259,14 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
if err != nil {
return err
}
for _, project := range projects {
projectMaps[project.IssueID] = project.Project
}
left -= limit
issueIDs = issueIDs[limit:]
for _, projectIssue := range projects {
issue := issueMap[projectIssue.IssueID]
issue.Projects = append(issue.Projects, projectIssue.Project)
Copy link
Contributor

@yp05327 yp05327 Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need to check whether projectIssue.Project is already in issue.Projects here.

}
}

for _, issue := range issues {
issue.Project = projectMaps[issue.ID]
}
return nil
}

Expand Down
133 changes: 87 additions & 46 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,24 @@ 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).OrderBy("title").
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to order the result here?
And maybe it is better to convert the function name into LoadProjects?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is just a holdover from @tyroneyeh 's original thing. All I did was try to resolve the conflicts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry. I was just reviewing this PR and didn't notice your comment above.
Are you planning to continually improve this PR or just resolve the conflicts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sorting applies to the sidebar menu list order

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

err should not be ignored. I know the previous code should still be corrected.

}
return ip.ProjectID

return ips
}

// ProjectBoardID return project board id if issue was assigned to one
Expand Down Expand Up @@ -96,57 +93,101 @@ 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 {
if err := issue.LoadRepo(ctx); err != nil {
return err
}

// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
if err != nil {
return err
oldProjectIDs := issue.projectIDs(ctx)

if len(oldProjectIDs) > 0 {
for _, i := range oldProjectIDs {
// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
if err != nil {
return err
}
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
return fmt.Errorf("issue's repository is not the same as project's repository")
}
}

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

if i > 0 || newProjectID > 0 {
if _, err := CreateComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldProjectID: i,
ProjectID: newProjectID,
}); err != nil {
return err
}
}
if action != "clear" && newProjectID == 0 || newProjectID > 0 {
break
}
}
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
return fmt.Errorf("issue's repository is not the same as project's repository")
} else {
if action == "attach" || action == "" {
if err := db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
}); err != nil {
return err
}
}
}

if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
return err
}

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

return db.Insert(ctx, &project_model.ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
})
return nil
}
11 changes: 8 additions & 3 deletions models/issues/issue_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,14 @@ func applyProjectBoardCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.S
// opts.ProjectBoardID == 0 means all project boards,
// 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.ProjectBoardID == db.NoConditionID {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 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 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").
Where(builder.Eq{"project_board_id": 0}))
}
return sess
}
Expand Down
2 changes: 1 addition & 1 deletion models/project/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, board.ProjectID)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion modules/indexer/issues/internal/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ 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_id"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ProjectIDs []int64 `json:"project_id"`
ProjectIDs []int64 `json:"project_ids"`

Other places also need to be modified.

ProjectBoardID int64 `json:"project_board_id"`
PosterID int64 `json:"poster_id"`
AssigneeID int64 `json:"assignee_id"`
Expand Down
8 changes: 4 additions & 4 deletions modules/indexer/issues/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
return nil, false, err
}

var projectID int64
if issue.Project != nil {
projectID = issue.Project.ID
var projectIDs []int64
for _, project := range issue.Projects {
projectIDs = append(projectIDs, project.ID)
}
Comment on lines +92 to 95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a function is better? Just a suggestion. Then we can use it in other places if necessary.


return &internal.IndexerData{
Expand All @@ -106,7 +106,7 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD
LabelIDs: labels,
NoLabel: len(labels) == 0,
MilestoneID: issue.MilestoneID,
ProjectID: projectID,
ProjectIDs: projectIDs,
ProjectBoardID: issue.ProjectBoardID(ctx),
PosterID: issue.PosterID,
AssigneeID: issue.AssigneeID,
Expand Down
9 changes: 2 additions & 7 deletions routers/web/org/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,14 +461,9 @@ func UpdateIssueProject(ctx *context.Context) {
}

projectID := ctx.FormInt64("id")
action := ctx.FormString("action")
for _, issue := range issues {
if issue.Project != nil {
if issue.Project.ID == projectID {
continue
}
}

if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, action); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,7 @@ func NewIssuePost(ctx *context.Context) {
ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects")
return
}
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, ctx.FormString("action")); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
Expand Down
8 changes: 2 additions & 6 deletions routers/web/repo/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,14 +385,10 @@ func UpdateIssueProject(ctx *context.Context) {
}

projectID := ctx.FormInt64("id")
action := ctx.FormString("action")
for _, issue := range issues {
if issue.Project != nil {
if issue.Project.ID == projectID {
continue
}
}

if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID, action); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
Expand Down
Loading