diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go index 26b93189b8..844f1d17bd 100644 --- a/models/issues/issue_list.go +++ b/models/issues/issue_list.go @@ -559,6 +559,74 @@ func (issues IssueList) LoadDiscussComments(ctx context.Context) error { return issues.loadComments(ctx, builder.Eq{"comment.type": CommentTypeComment}) } +// GetBlockedByCounts returns a map of issue ID to number of open issues that are blocking it +func (issues IssueList) GetBlockedByCount(ctx context.Context) (map[int64]int64, error) { + type BlockedByCount struct { + IssueID int64 + Count int64 + } + + bCounts := make([]*BlockedByCount, len(issues)) + ids := make([]int64, len(issues)) + for i, issue := range issues { + ids[i] = issue.ID + } + + sess := db.GetEngine(ctx).In("issue_id", ids) + err := sess.Select("issue_id, count(issue_dependency.id) as `count`"). + Join("INNER", "issue", "issue.id = issue_dependency.dependency_id"). + Where("is_closed = ?", false). + GroupBy("issue_id"). + OrderBy("issue_id"). + Table("issue_dependency"). + Find(&bCounts) + if err != nil { + return nil, err + } + + blockedByCountMap := make(map[int64]int64, len(issues)) + for _, c := range bCounts { + if c != nil { + blockedByCountMap[c.IssueID] = c.Count + } + } + + return blockedByCountMap, nil +} + +// GetBlockingCounts returns a map of issue ID to number of issues that are blocked by it +func (issues IssueList) GetBlockingCount(ctx context.Context) (map[int64]int64, error) { + type BlockingCount struct { + IssueID int64 + Count int64 + } + + bCounts := make([]*BlockingCount, 0, len(issues)) + ids := make([]int64, len(issues)) + for i, issue := range issues { + ids[i] = issue.ID + } + + sess := db.GetEngine(ctx).In("dependency_id", ids) + err := sess.Select("dependency_id as `issue_id`, count(id) as `count`"). + GroupBy("dependency_id"). + OrderBy("dependency_id"). + Table("issue_dependency"). + Find(&bCounts) + if err != nil { + return nil, err + } + + blockingCountMap := make(map[int64]int64, len(issues)) + for _, c := range bCounts { + if c != nil { + blockingCountMap[c.IssueID] = c.Count + } + } + + return blockingCountMap, nil +} + // GetApprovalCounts returns a map of issue ID to slice of approval counts // FIXME: only returns official counts due to double counting of non-official approvals func (issues IssueList) GetApprovalCounts(ctx context.Context) (map[int64][]*ReviewCount, error) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ff32c94ff9..c8ca1cb8b0 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1793,6 +1793,10 @@ issues.dependency.add_error_dep_not_exist = Dependency does not exist. issues.dependency.add_error_dep_exists = Dependency already exists. issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other. issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository. +issues.dependency.blocking_count_1 = "This issue is blocking %d other issue." +issues.dependency.blocking_count_n = "This issue is blocking %d other issues." +issues.dependency.blocked_by_count_1 = "This issue is blocked by %d issue." +issues.dependency.blocked_by_count_n = "This issue is blocked by %d issues." issues.review.self.approval = You cannot approve your own pull request. issues.review.self.rejection = You cannot request changes on your own pull request. issues.review.approve = "approved these changes %s" diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go index fd34422cfc..cbd3365c7a 100644 --- a/routers/web/repo/issue_list.go +++ b/routers/web/repo/issue_list.go @@ -654,6 +654,18 @@ func prepareIssueFilterAndList(ctx *context.Context, milestoneID, projectID int6 return } + blockingCounts, err := issues.GetBlockingCount(ctx) + if err != nil { + ctx.ServerError("BlockingCounts", err) + return + } + + blockedByCounts, err := issues.GetBlockedByCount(ctx) + if err != nil { + ctx.ServerError("BlockedByCounts", err) + return + } + if ctx.IsSigned { if err := issues.LoadIsRead(ctx, ctx.Doer.ID); err != nil { ctx.ServerError("LoadIsRead", err) @@ -718,6 +730,21 @@ func prepareIssueFilterAndList(ctx *context.Context, milestoneID, projectID int6 return 0 } + ctx.Data["BlockingCounts"] = func(issueID int64) int64 { + counts, ok := blockingCounts[issueID] + if !ok { + return 0 + } + return counts + } + ctx.Data["BlockedByCounts"] = func(issueID int64) int64 { + counts, ok := blockedByCounts[issueID] + if !ok { + return 0 + } + return counts + } + retrieveProjectsForIssueList(ctx, repo) if ctx.Written() { return diff --git a/routers/web/user/home.go b/routers/web/user/home.go index b53a3daedb..b7a0cf079b 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -627,6 +627,33 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { } return 0 } + + blockingCounts, err := issues.GetBlockingCount(ctx) + if err != nil { + ctx.ServerError("BlockingCounts", err) + return + } + + blockedByCounts, err := issues.GetBlockedByCount(ctx) + if err != nil { + ctx.ServerError("BlockedByCounts", err) + return + } + ctx.Data["BlockingCounts"] = func(issueID int64) int64 { + counts, ok := blockingCounts[issueID] + if !ok { + return 0 + } + return counts + } + ctx.Data["BlockedByCounts"] = func(issueID int64) int64 { + counts, ok := blockedByCounts[issueID] + if !ok { + return 0 + } + return counts + } + ctx.Data["CommitLastStatus"] = lastStatus ctx.Data["CommitStatuses"] = commitStatuses ctx.Data["IssueStats"] = issueStats diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index aaf9d435c0..56f8e49431 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -279,6 +279,32 @@ func NotificationSubscriptions(ctx *context.Context) { return 0 } + blockingCounts, err := issues.GetBlockingCount(ctx) + if err != nil { + ctx.ServerError("BlockingCounts", err) + return + } + + blockedByCounts, err := issues.GetBlockedByCount(ctx) + if err != nil { + ctx.ServerError("BlockedByCounts", err) + return + } + ctx.Data["BlockingCounts"] = func(issueID int64) int64 { + counts, ok := blockingCounts[issueID] + if !ok { + return 0 + } + return counts + } + ctx.Data["BlockedByCounts"] = func(issueID int64) int64 { + counts, ok := blockedByCounts[issueID] + if !ok { + return 0 + } + return counts + } + ctx.Data["Status"] = 1 ctx.Data["Title"] = ctx.Tr("notification.subscriptions") diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index 98c26b32dc..9adc6899fc 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -1,6 +1,10 @@