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 multiple projects per issues function #25164

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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 @@ -100,14 +100,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
14 changes: 10 additions & 4 deletions models/issues/issue_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
}

func (issues IssueList) getProjectIDs() []int64 {
ids := make(container.Set[int64], len(issues))
var ids []int64
for _, issue := range issues {
ids.Add(issue.ProjectID())
ids = append(ids, issue.ProjectIDs()...)
}
return ids.Values()
return ids
tyroneyeh marked this conversation as resolved.
Show resolved Hide resolved
}

func (issues IssueList) loadProjects(ctx context.Context) error {
Expand All @@ -261,8 +261,14 @@ func (issues IssueList) loadProjects(ctx context.Context) error {
}

for _, issue := range issues {
issue.Project = projectMaps[issue.ProjectID()]
projectIDs := issue.ProjectIDs()
for _, i := range projectIDs {
if projectMaps[i] != nil {
issue.Projects = append(issue.Projects, projectMaps[i])
}
}
}

return nil
}

Expand Down
138 changes: 92 additions & 46 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,33 @@ 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
if _, 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); err != nil {
return err
}
issue.Project = &p
Find(&issue.Projects)
}
return err
}

// ProjectID return project id if issue was assigned to one
func (issue *Issue) ProjectID() int64 {
return issue.projectID(db.DefaultContext)
func (issue *Issue) ProjectIDs() []int64 {
return issue.projectIDs(db.DefaultContext)
}

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 ip []project_model.ProjectIssue
var ips []int64
err := db.GetEngine(ctx).Select("project_id").Where("issue_id=?", issue.ID).Find(&ip)
tyroneyeh marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil
}

for _, i := range ip {
ips = append(ips, i.ProjectID)
}
return ip.ProjectID

return ips
}

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

// ChangeProjectAssign changes the project associated with an issue
func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64, action string) error {
ctx, committer, err := db.TxContext(db.DefaultContext)
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 if i > 0 && i == newProjectID {
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
}
newProjectID = 0
}
}

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 || action == "attach" && 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
}

// MoveIssueAcrossProjectBoards move a card from one board to another
Expand Down
2 changes: 2 additions & 0 deletions models/issues/issue_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
if opts.ProjectBoardID != 0 {
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 {
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
}
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() int {
}

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

Expand All @@ -93,7 +93,7 @@ func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) erro
}

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
10 changes: 3 additions & 7 deletions routers/web/org/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,13 +430,9 @@ func UpdateIssueProject(ctx *context.Context) {
}

projectID := ctx.FormInt64("id")
action := ctx.FormString("action")
for _, issue := range issues {
oldProjectID := issue.ProjectID()
if oldProjectID == projectID {
continue
}

if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID, action); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
Expand Down Expand Up @@ -718,7 +714,7 @@ func MoveIssues(ctx *context.Context) {
}
}

if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs, project.ID); err != nil {
ctx.ServerError("MoveIssuesOnProjectBoard", 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 @@ -1176,7 +1176,7 @@ func NewIssuePost(ctx *context.Context) {
ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects")
return
}
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID, ctx.FormString("action")); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
Expand Down
10 changes: 3 additions & 7 deletions routers/web/repo/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,9 @@ func UpdateIssueProject(ctx *context.Context) {
}

projectID := ctx.FormInt64("id")
action := ctx.FormString("action")
for _, issue := range issues {
oldProjectID := issue.ProjectID()
if oldProjectID == projectID {
continue
}

if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID, action); err != nil {
ctx.ServerError("ChangeProjectAssign", err)
return
}
Expand Down Expand Up @@ -688,7 +684,7 @@ func MoveIssues(ctx *context.Context) {
}
}

if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs, project.ID); err != nil {
ctx.ServerError("MoveIssuesOnProjectBoard", err)
return
}
Expand Down
1 change: 1 addition & 0 deletions templates/repo/issue/new_form.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
{{range .OpenProjects}}
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link}}">
{{svg .IconName 18 "gt-mr-3"}}{{.Title}}
</span>
</a>
{{end}}
{{end}}
Expand Down
Loading