Browse Source

Fix assigned issues dashboard (#920)

* Fix assigned/created issues in dashboard. (#3560)

* Fix assigned/created issues in dashboard.

* Use GetUserIssueStats for getting all Dashboard stats.

* Use gofmt to format the file properly.

* Replace &Issue{} with new(Issue).

* Check if user has access to given repository.

* Remove unnecessary filtering of issues.

* Return 404 error if invalid repository is given.

* Use correct number of issues in paginater.

* fix issues on dashboard
tags/v1.21.12.1
Lunny Xiao GitHub 8 years ago
parent
commit
7a9a5c8a69
4 changed files with 175 additions and 117 deletions
  1. +61
    -28
      models/issue.go
  2. +1
    -22
      routers/repo/issue.go
  3. +109
    -63
      routers/user/home.go
  4. +4
    -4
      templates/user/dashboard/issues.tmpl

+ 61
- 28
models/issue.go View File

@@ -1184,7 +1184,7 @@ func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error {
// IssueStats represents issue statistic information. // IssueStats represents issue statistic information.
type IssueStats struct { type IssueStats struct {
OpenCount, ClosedCount int64 OpenCount, ClosedCount int64
AllCount int64
YourRepositoriesCount int64
AssignCount int64 AssignCount int64
CreateCount int64 CreateCount int64
MentionCount int64 MentionCount int64
@@ -1210,6 +1210,7 @@ func parseCountResult(results []map[string][]byte) int64 {


// IssueStatsOptions contains parameters accepted by GetIssueStats. // IssueStatsOptions contains parameters accepted by GetIssueStats.
type IssueStatsOptions struct { type IssueStatsOptions struct {
FilterMode int
RepoID int64 RepoID int64
Labels string Labels string
MilestoneID int64 MilestoneID int64
@@ -1265,19 +1266,41 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
} }


var err error var err error
stats.OpenCount, err = countSession(opts).
And("is_closed = ?", false).
Count(&Issue{})
if err != nil {
return nil, err
}
stats.ClosedCount, err = countSession(opts).
And("is_closed = ?", true).
Count(&Issue{})
if err != nil {
return nil, err
switch opts.FilterMode {
case FilterModeAll, FilterModeAssign:
stats.OpenCount, err = countSession(opts).
And("is_closed = ?", false).
Count(new(Issue))

stats.ClosedCount, err = countSession(opts).
And("is_closed = ?", true).
Count(new(Issue))
case FilterModeCreate:
stats.OpenCount, err = countSession(opts).
And("poster_id = ?", opts.PosterID).
And("is_closed = ?", false).
Count(new(Issue))

stats.ClosedCount, err = countSession(opts).
And("poster_id = ?", opts.PosterID).
And("is_closed = ?", true).
Count(new(Issue))
case FilterModeMention:
stats.OpenCount, err = countSession(opts).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.uid = ?", opts.PosterID).
And("issue_user.is_mentioned = ?", true).
And("issue.is_closed = ?", false).
Count(new(Issue))

stats.ClosedCount, err = countSession(opts).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.uid = ?", opts.PosterID).
And("issue_user.is_mentioned = ?", true).
And("issue.is_closed = ?", true).
Count(new(Issue))
} }
return stats, nil
return stats, err
} }


// GetUserIssueStats returns issue statistic information for dashboard by given conditions. // GetUserIssueStats returns issue statistic information for dashboard by given conditions.
@@ -1298,29 +1321,39 @@ func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPul
return sess return sess
} }


stats.AssignCount, _ = countSession(false, isPull, repoID, repoIDs).
stats.AssignCount, _ = countSession(false, isPull, repoID, nil).
And("assignee_id = ?", uid). And("assignee_id = ?", uid).
Count(&Issue{})
Count(new(Issue))


stats.CreateCount, _ = countSession(false, isPull, repoID, repoIDs).
stats.CreateCount, _ = countSession(false, isPull, repoID, nil).
And("poster_id = ?", uid). And("poster_id = ?", uid).
Count(&Issue{})
Count(new(Issue))


openCountSession := countSession(false, isPull, repoID, repoIDs)
closedCountSession := countSession(true, isPull, repoID, repoIDs)
stats.YourRepositoriesCount, _ = countSession(false, isPull, repoID, repoIDs).
Count(new(Issue))


switch filterMode { switch filterMode {
case FilterModeAll:
stats.OpenCount, _ = countSession(false, isPull, repoID, repoIDs).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, repoIDs).
Count(new(Issue))
case FilterModeAssign: case FilterModeAssign:
openCountSession.And("assignee_id = ?", uid)
closedCountSession.And("assignee_id = ?", uid)
stats.OpenCount, _ = countSession(false, isPull, repoID, nil).
And("assignee_id = ?", uid).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, nil).
And("assignee_id = ?", uid).
Count(new(Issue))
case FilterModeCreate: case FilterModeCreate:
openCountSession.And("poster_id = ?", uid)
closedCountSession.And("poster_id = ?", uid)
stats.OpenCount, _ = countSession(false, isPull, repoID, nil).
And("poster_id = ?", uid).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, nil).
And("poster_id = ?", uid).
Count(new(Issue))
} }


stats.OpenCount, _ = openCountSession.Count(&Issue{})
stats.ClosedCount, _ = closedCountSession.Count(&Issue{})

return stats return stats
} }


@@ -1347,8 +1380,8 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen
closedCountSession.And("poster_id = ?", uid) closedCountSession.And("poster_id = ?", uid)
} }


openResult, _ := openCountSession.Count(&Issue{})
closedResult, _ := closedCountSession.Count(&Issue{})
openResult, _ := openCountSession.Count(new(Issue))
closedResult, _ := closedCountSession.Count(new(Issue))


return openResult, closedResult return openResult, closedResult
} }


+ 1
- 22
routers/repo/issue.go View File

@@ -10,7 +10,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/url"
"strings" "strings"
"time" "time"


@@ -108,37 +107,17 @@ func Issues(ctx *context.Context) {


viewType := ctx.Query("type") viewType := ctx.Query("type")
sortType := ctx.Query("sort") sortType := ctx.Query("sort")
types := []string{"assigned", "created_by", "mentioned"}
types := []string{"all", "assigned", "created_by", "mentioned"}
if !com.IsSliceContainsStr(types, viewType) { if !com.IsSliceContainsStr(types, viewType) {
viewType = "all" viewType = "all"
} }


// Must sign in to see issues about you.
if viewType != "all" && !ctx.IsSigned {
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubURL+ctx.Req.RequestURI), 0, setting.AppSubURL)
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}

var ( var (
assigneeID = ctx.QueryInt64("assignee") assigneeID = ctx.QueryInt64("assignee")
posterID int64 posterID int64
mentionedID int64 mentionedID int64
forceEmpty bool forceEmpty bool
) )
switch viewType {
case "assigned":
if assigneeID > 0 && ctx.User.ID != assigneeID {
// two different assignees, must be empty
forceEmpty = true
} else {
assigneeID = ctx.User.ID
}
case "created_by":
posterID = ctx.User.ID
case "mentioned":
mentionedID = ctx.User.ID
}


repo := ctx.Repo.Repository repo := ctx.Repo.Repository
selectLabels := ctx.Query("labels") selectLabels := ctx.Query("labels")


+ 109
- 63
routers/user/home.go View File

@@ -183,34 +183,39 @@ func Issues(ctx *context.Context) {
viewType string viewType string
sortType = ctx.Query("sort") sortType = ctx.Query("sort")
filterMode = models.FilterModeAll filterMode = models.FilterModeAll
assigneeID int64
posterID int64
) )

if ctxUser.IsOrganization() { if ctxUser.IsOrganization() {
viewType = "all" viewType = "all"
} else { } else {
viewType = ctx.Query("type") viewType = ctx.Query("type")
types := []string{"assigned", "created_by"}
types := []string{"all", "assigned", "created_by"}
if !com.IsSliceContainsStr(types, viewType) { if !com.IsSliceContainsStr(types, viewType) {
viewType = "all" viewType = "all"
} }


switch viewType { switch viewType {
case "all":
filterMode = models.FilterModeAll
case "assigned": case "assigned":
filterMode = models.FilterModeAssign filterMode = models.FilterModeAssign
assigneeID = ctxUser.ID
case "created_by": case "created_by":
filterMode = models.FilterModeCreate filterMode = models.FilterModeCreate
posterID = ctxUser.ID
} }
} }


page := ctx.QueryInt("page")
if page <= 1 {
page = 1
}

repoID := ctx.QueryInt64("repo") repoID := ctx.QueryInt64("repo")
isShowClosed := ctx.Query("state") == "closed" isShowClosed := ctx.Query("state") == "closed"


// Get repositories. // Get repositories.
var err error var err error
var repos []*models.Repository var repos []*models.Repository
userRepoIDs := make([]int64, 0, len(repos))
if ctxUser.IsOrganization() { if ctxUser.IsOrganization() {
env, err := ctxUser.AccessibleReposEnv(ctx.User.ID) env, err := ctxUser.AccessibleReposEnv(ctx.User.ID)
if err != nil { if err != nil {
@@ -230,9 +235,6 @@ func Issues(ctx *context.Context) {
repos = ctxUser.Repos repos = ctxUser.Repos
} }


allCount := 0
repoIDs := make([]int64, 0, len(repos))
showRepos := make([]*models.Repository, 0, len(repos))
for _, repo := range repos { for _, repo := range repos {
if (isPullList && repo.NumPulls == 0) || if (isPullList && repo.NumPulls == 0) ||
(!isPullList && (!isPullList &&
@@ -240,85 +242,129 @@ func Issues(ctx *context.Context) {
continue continue
} }


repoIDs = append(repoIDs, repo.ID)
userRepoIDs = append(userRepoIDs, repo.ID)
}


if isPullList {
allCount += repo.NumOpenPulls
repo.NumOpenIssues = repo.NumOpenPulls
repo.NumClosedIssues = repo.NumClosedPulls
} else {
allCount += repo.NumOpenIssues
var issues []*models.Issue
switch filterMode {
case models.FilterModeAll:
// Get all issues from repositories from this user.
issues, err = models.Issues(&models.IssuesOptions{
RepoIDs: userRepoIDs,
RepoID: repoID,
Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: util.OptionalBoolOf(isPullList),
SortType: sortType,
})

case models.FilterModeAssign:
// Get all issues assigned to this user.
issues, err = models.Issues(&models.IssuesOptions{
RepoID: repoID,
AssigneeID: ctxUser.ID,
Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: util.OptionalBoolOf(isPullList),
SortType: sortType,
})

case models.FilterModeCreate:
// Get all issues created by this user.
issues, err = models.Issues(&models.IssuesOptions{
RepoID: repoID,
PosterID: ctxUser.ID,
Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: util.OptionalBoolOf(isPullList),
SortType: sortType,
})
case models.FilterModeMention:
// Get all issues created by this user.
issues, err = models.Issues(&models.IssuesOptions{
RepoID: repoID,
MentionedID: ctxUser.ID,
Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: util.OptionalBoolOf(isPullList),
SortType: sortType,
})
}

if err != nil {
ctx.Handle(500, "Issues", err)
return
}

showRepos := make([]*models.Repository, 0, len(issues))
showReposSet := make(map[int64]bool)

if repoID > 0 {
repo, err := models.GetRepositoryByID(repoID)
if err != nil {
ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", repoID, err))
return
} }


if filterMode != models.FilterModeAll {
// Calculate repository issue count with filter mode.
numOpen, numClosed := repo.IssueStats(ctxUser.ID, filterMode, isPullList)
repo.NumOpenIssues, repo.NumClosedIssues = int(numOpen), int(numClosed)
if err = repo.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", repoID, err))
return
} }


if repo.ID == repoID ||
(isShowClosed && repo.NumClosedIssues > 0) ||
(!isShowClosed && repo.NumOpenIssues > 0) {
showRepos = append(showRepos, repo)
// Check if user has access to given repository.
if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser) {
ctx.Handle(404, "Issues", fmt.Errorf("#%d", repoID))
return
} }

showReposSet[repoID] = true
showRepos = append(showRepos, repo)
} }
ctx.Data["Repos"] = showRepos
if len(repoIDs) == 0 {
repoIDs = []int64{-1}
}


issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, repoIDs, filterMode, isPullList)
issueStats.AllCount = int64(allCount)
for _, issue := range issues {
// Get Repository data.
issue.Repo, err = models.GetRepositoryByID(issue.RepoID)
if err != nil {
ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", issue.RepoID, err))
return
}

// Get Owner data.
if err = issue.Repo.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issue.RepoID, err))
return
}


page := ctx.QueryInt("page")
if page <= 1 {
page = 1
// Append repo to list of shown repos
if filterMode == models.FilterModeAll {
// Use a map to make sure we don't add the same Repository twice.
_, ok := showReposSet[issue.RepoID]
if !ok {
showReposSet[issue.RepoID] = true
// Append to list of shown Repositories.
showRepos = append(showRepos, issue.Repo)
}
}
} }


issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList)

var total int var total int
if !isShowClosed { if !isShowClosed {
total = int(issueStats.OpenCount) total = int(issueStats.OpenCount)
} else { } else {
total = int(issueStats.ClosedCount) total = int(issueStats.ClosedCount)
} }
ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)

// Get issues.
issues, err := models.Issues(&models.IssuesOptions{
AssigneeID: assigneeID,
RepoID: repoID,
PosterID: posterID,
RepoIDs: repoIDs,
Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed),
IsPull: util.OptionalBoolOf(isPullList),
SortType: sortType,
})
if err != nil {
ctx.Handle(500, "Issues", err)
return
}


// Get posters and repository.
for i := range issues {
issues[i].Repo, err = models.GetRepositoryByID(issues[i].RepoID)
if err != nil {
ctx.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}

if err = issues[i].Repo.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
}
ctx.Data["Issues"] = issues ctx.Data["Issues"] = issues

ctx.Data["Repos"] = showRepos
ctx.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
ctx.Data["IssueStats"] = issueStats ctx.Data["IssueStats"] = issueStats
ctx.Data["ViewType"] = viewType ctx.Data["ViewType"] = viewType
ctx.Data["SortType"] = sortType ctx.Data["SortType"] = sortType
ctx.Data["RepoID"] = repoID ctx.Data["RepoID"] = repoID
ctx.Data["IsShowClosed"] = isShowClosed ctx.Data["IsShowClosed"] = isShowClosed

if isShowClosed { if isShowClosed {
ctx.Data["State"] = "closed" ctx.Data["State"] = "closed"
} else { } else {


+ 4
- 4
templates/user/dashboard/issues.tmpl View File

@@ -5,9 +5,9 @@
<div class="ui grid"> <div class="ui grid">
<div class="four wide column"> <div class="four wide column">
<div class="ui secondary vertical filter menu"> <div class="ui secondary vertical filter menu">
<a class="{{if eq .ViewType "all"}}ui basic blue button{{end}} item" href="{{.Link}}?repo={{.RepoID}}&sort={{$.SortType}}&state={{.State}}">
<a class="{{if eq .ViewType "your_repositories"}}ui basic blue button{{end}} item" href="{{.Link}}?type=your_repositories&repo={{.RepoID}}&sort={{$.SortType}}&state={{.State}}">
{{.i18n.Tr "home.issues.in_your_repos"}} {{.i18n.Tr "home.issues.in_your_repos"}}
<strong class="ui right">{{.IssueStats.AllCount}}</strong>
<strong class="ui right">{{.IssueStats.YourRepositoriesCount}}</strong>
</a> </a>
{{if not .ContextUser.IsOrganization}} {{if not .ContextUser.IsOrganization}}
<a class="{{if eq .ViewType "assigned"}}ui basic blue button{{end}} item" href="{{.Link}}?type=assigned&repo={{.RepoID}}&sort={{$.SortType}}&state={{.State}}"> <a class="{{if eq .ViewType "assigned"}}ui basic blue button{{end}} item" href="{{.Link}}?type=assigned&repo={{.RepoID}}&sort={{$.SortType}}&state={{.State}}">
@@ -22,7 +22,7 @@
<div class="ui divider"></div> <div class="ui divider"></div>
{{range .Repos}} {{range .Repos}}
<a class="{{if eq $.RepoID .ID}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&sort={{$.SortType}}&state={{$.State}}"> <a class="{{if eq $.RepoID .ID}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}{{if not (eq $.RepoID .ID)}}&repo={{.ID}}{{end}}&sort={{$.SortType}}&state={{$.State}}">
<span class="text truncate">{{$.ContextUser.Name}}/{{.Name}}</span>
<span class="text truncate">{{.FullName}}</span>
<div class="floating ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</div> <div class="floating ui {{if $.IsShowClosed}}red{{else}}green{{end}} label">{{if $.IsShowClosed}}{{.NumClosedIssues}}{{else}}{{.NumOpenIssues}}{{end}}</div>
</a> </a>
{{end}} {{end}}
@@ -61,7 +61,7 @@
{{range .Issues}} {{range .Issues}}
{{ $timeStr:= TimeSince .Created $.Lang }} {{ $timeStr:= TimeSince .Created $.Lang }}
<li class="item"> <li class="item">
<div class="ui label">{{if not $.RepoID}}{{.Repo.Name}}{{end}}#{{.Index}}</div>
<div class="ui label">{{if not $.RepoID}}{{.Repo.FullName}}{{end}}#{{.Index}}</div>
<a class="title has-emoji" href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Title}}</a> <a class="title has-emoji" href="{{AppSubUrl}}/{{.Repo.Owner.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Title}}</a>


{{range .Labels}} {{range .Labels}}


Loading…
Cancel
Save