aboutsummaryrefslogtreecommitdiffstats
path: root/services/projects/issue.go
diff options
context:
space:
mode:
Diffstat (limited to 'services/projects/issue.go')
-rw-r--r--services/projects/issue.go156
1 files changed, 142 insertions, 14 deletions
diff --git a/services/projects/issue.go b/services/projects/issue.go
index db1621a39f..590fe960d5 100644
--- a/services/projects/issue.go
+++ b/services/projects/issue.go
@@ -5,12 +5,13 @@ package project
import (
"context"
- "fmt"
+ "errors"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/optional"
)
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
@@ -28,7 +29,7 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum
return err
}
if int(count) != len(sortedIssueIDs) {
- return fmt.Errorf("all issues have to be added to a project first")
+ return errors.New("all issues have to be added to a project first")
}
issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
@@ -55,25 +56,152 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum
continue
}
- _, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID)
+ projectColumnID, err := curIssue.ProjectColumnID(ctx)
if err != nil {
return err
}
- // add timeline to issue
- if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
- Type: issues_model.CommentTypeProjectColumn,
- Doer: doer,
- Repo: curIssue.Repo,
- Issue: curIssue,
- ProjectID: column.ProjectID,
- ProjectTitle: project.Title,
- ProjectColumnID: column.ID,
- ProjectColumnTitle: column.Title,
- }); err != nil {
+ if projectColumnID != column.ID {
+ // add timeline to issue
+ if _, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
+ Type: issues_model.CommentTypeProjectColumn,
+ Doer: doer,
+ Repo: curIssue.Repo,
+ Issue: curIssue,
+ ProjectID: column.ProjectID,
+ ProjectTitle: project.Title,
+ ProjectColumnID: column.ID,
+ ProjectColumnTitle: column.Title,
+ }); err != nil {
+ return err
+ }
+ }
+
+ _, err = db.Exec(ctx, "UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", column.ID, sorting, issueID)
+ if err != nil {
return err
}
}
return nil
})
}
+
+// LoadIssuesFromProject load issues assigned to each project column inside the given project
+func LoadIssuesFromProject(ctx context.Context, project *project_model.Project, opts *issues_model.IssuesOptions) (map[int64]issues_model.IssueList, error) {
+ issueList, err := issues_model.Issues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) {
+ o.ProjectID = project.ID
+ o.SortType = "project-column-sorting"
+ }))
+ if err != nil {
+ return nil, err
+ }
+
+ if err := issueList.LoadComments(ctx); err != nil {
+ return nil, err
+ }
+
+ defaultColumn, err := project.MustDefaultColumn(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ issueColumnMap, err := issues_model.LoadProjectIssueColumnMap(ctx, project.ID, defaultColumn.ID)
+ if err != nil {
+ return nil, err
+ }
+
+ results := make(map[int64]issues_model.IssueList)
+ for _, issue := range issueList {
+ projectColumnID, ok := issueColumnMap[issue.ID]
+ if !ok {
+ continue
+ }
+ if _, ok := results[projectColumnID]; !ok {
+ results[projectColumnID] = make(issues_model.IssueList, 0)
+ }
+ results[projectColumnID] = append(results[projectColumnID], issue)
+ }
+ return results, nil
+}
+
+// NumClosedIssues return counter of closed issues assigned to a project
+func loadNumClosedIssues(ctx context.Context, p *project_model.Project) error {
+ cnt, err := db.GetEngine(ctx).Table("project_issue").
+ Join("INNER", "issue", "project_issue.issue_id=issue.id").
+ Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return err
+ }
+ p.NumClosedIssues = cnt
+ return nil
+}
+
+// NumOpenIssues return counter of open issues assigned to a project
+func loadNumOpenIssues(ctx context.Context, p *project_model.Project) error {
+ cnt, err := db.GetEngine(ctx).Table("project_issue").
+ Join("INNER", "issue", "project_issue.issue_id=issue.id").
+ Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return err
+ }
+ p.NumOpenIssues = cnt
+ return nil
+}
+
+func LoadIssueNumbersForProjects(ctx context.Context, projects []*project_model.Project, doer *user_model.User) error {
+ for _, project := range projects {
+ if err := LoadIssueNumbersForProject(ctx, project, doer); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func LoadIssueNumbersForProject(ctx context.Context, project *project_model.Project, doer *user_model.User) error {
+ // for repository project, just get the numbers
+ if project.OwnerID == 0 {
+ if err := loadNumClosedIssues(ctx, project); err != nil {
+ return err
+ }
+ if err := loadNumOpenIssues(ctx, project); err != nil {
+ return err
+ }
+ project.NumIssues = project.NumClosedIssues + project.NumOpenIssues
+ return nil
+ }
+
+ if err := project.LoadOwner(ctx); err != nil {
+ return err
+ }
+
+ // for user or org projects, we need to check access permissions
+ opts := issues_model.IssuesOptions{
+ ProjectID: project.ID,
+ Doer: doer,
+ AllPublic: doer == nil,
+ Owner: project.Owner,
+ }
+
+ var err error
+ project.NumOpenIssues, err = issues_model.CountIssues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) {
+ o.IsClosed = optional.Some(false)
+ }))
+ if err != nil {
+ return err
+ }
+
+ project.NumClosedIssues, err = issues_model.CountIssues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) {
+ o.IsClosed = optional.Some(true)
+ }))
+ if err != nil {
+ return err
+ }
+
+ project.NumIssues = project.NumClosedIssues + project.NumOpenIssues
+
+ return nil
+}