Browse Source

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.
pull/3678/merge
hgaiser 8 years ago committed by 无闻
parent
commit
e6ef75204b
  1. 62
      models/issue.go
  2. 2
      routers/repo/issue.go
  3. 165
      routers/user/home.go
  4. 8
      templates/user/dashboard/issues.tmpl

62
models/issue.go

@ -1069,7 +1069,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
@ -1077,7 +1077,7 @@ type IssueStats struct {
// Filter modes. // Filter modes.
const ( const (
FM_ALL = iota FM_YOUR_REPOSITORIES = iota
FM_ASSIGN FM_ASSIGN
FM_CREATE FM_CREATE
FM_MENTION FM_MENTION
@ -1129,38 +1129,38 @@ func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
} }
switch opts.FilterMode { switch opts.FilterMode {
case FM_ALL, FM_ASSIGN: case FM_YOUR_REPOSITORIES, FM_ASSIGN:
stats.OpenCount, _ = countSession(opts). stats.OpenCount, _ = countSession(opts).
And("is_closed = ?", false). And("is_closed = ?", false).
Count(&Issue{}) Count(new(Issue))
stats.ClosedCount, _ = countSession(opts). stats.ClosedCount, _ = countSession(opts).
And("is_closed = ?", true). And("is_closed = ?", true).
Count(&Issue{}) Count(new(Issue))
case FM_CREATE: case FM_CREATE:
stats.OpenCount, _ = countSession(opts). stats.OpenCount, _ = countSession(opts).
And("poster_id = ?", opts.UserID). And("poster_id = ?", opts.UserID).
And("is_closed = ?", false). And("is_closed = ?", false).
Count(&Issue{}) Count(new(Issue))
stats.ClosedCount, _ = countSession(opts). stats.ClosedCount, _ = countSession(opts).
And("poster_id = ?", opts.UserID). And("poster_id = ?", opts.UserID).
And("is_closed = ?", true). And("is_closed = ?", true).
Count(&Issue{}) Count(new(Issue))
case FM_MENTION: case FM_MENTION:
stats.OpenCount, _ = countSession(opts). stats.OpenCount, _ = countSession(opts).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id"). Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.uid = ?", opts.UserID). And("issue_user.uid = ?", opts.UserID).
And("issue_user.is_mentioned = ?", true). And("issue_user.is_mentioned = ?", true).
And("issue.is_closed = ?", false). And("issue.is_closed = ?", false).
Count(&Issue{}) Count(new(Issue))
stats.ClosedCount, _ = countSession(opts). stats.ClosedCount, _ = countSession(opts).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id"). Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.uid = ?", opts.UserID). And("issue_user.uid = ?", opts.UserID).
And("issue_user.is_mentioned = ?", true). And("issue_user.is_mentioned = ?", true).
And("issue.is_closed = ?", true). And("issue.is_closed = ?", true).
Count(&Issue{}) Count(new(Issue))
} }
return stats return stats
} }
@ -1172,38 +1172,48 @@ func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPul
countSession := func(isClosed, isPull bool, repoID int64, repoIDs []int64) *xorm.Session { countSession := func(isClosed, isPull bool, repoID int64, repoIDs []int64) *xorm.Session {
sess := x.Where("issue.is_closed = ?", isClosed).And("issue.is_pull = ?", isPull) sess := x.Where("issue.is_closed = ?", isClosed).And("issue.is_pull = ?", isPull)
if repoID > 0 || len(repoIDs) == 0 { if repoID > 0 {
sess.And("repo_id = ?", repoID) sess.And("repo_id = ?", repoID)
} else { } else if repoIDs != nil {
sess.In("repo_id", repoIDs) sess.In("repo_id", repoIDs)
} }
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) stats.YourRepositoriesCount, _ = countSession(false, isPull, repoID, repoIDs).
closedCountSession := countSession(true, isPull, repoID, repoIDs) Count(new(Issue))
switch filterMode { switch filterMode {
case FM_YOUR_REPOSITORIES:
stats.OpenCount, _ = countSession(false, isPull, repoID, repoIDs).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, repoIDs).
Count(new(Issue))
case FM_ASSIGN: case FM_ASSIGN:
openCountSession.And("assignee_id = ?", uid) stats.OpenCount, _ = countSession(false, isPull, repoID, nil).
closedCountSession.And("assignee_id = ?", uid) And("assignee_id = ?", uid).
Count(new(Issue))
stats.ClosedCount, _ = countSession(true, isPull, repoID, nil).
And("assignee_id = ?", uid).
Count(new(Issue))
case FM_CREATE: case FM_CREATE:
openCountSession.And("poster_id = ?", uid) stats.OpenCount, _ = countSession(false, isPull, repoID, nil).
closedCountSession.And("poster_id = ?", uid) 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
} }
@ -1229,8 +1239,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{}) openResult, _ := openCountSession.Count(new(Issue))
closedResult, _ := closedCountSession.Count(&Issue{}) closedResult, _ := closedCountSession.Count(new(Issue))
return openResult, closedResult return openResult, closedResult
} }

2
routers/repo/issue.go

@ -121,7 +121,7 @@ func Issues(ctx *context.Context) {
assigneeID = ctx.QueryInt64("assignee") assigneeID = ctx.QueryInt64("assignee")
posterID int64 posterID int64
) )
filterMode := models.FM_ALL filterMode := models.FM_YOUR_REPOSITORIES
switch viewType { switch viewType {
case "assigned": case "assigned":
filterMode = models.FM_ASSIGN filterMode = models.FM_ASSIGN

165
routers/user/home.go

@ -172,35 +172,39 @@ func Issues(ctx *context.Context) {
var ( var (
viewType string viewType string
sortType = ctx.Query("sort") sortType = ctx.Query("sort")
filterMode = models.FM_ALL filterMode = models.FM_YOUR_REPOSITORIES
assigneeID int64
posterID int64
) )
if ctxUser.IsOrganization() { if ctxUser.IsOrganization() {
viewType = "all" viewType = "your_repositories"
} else { } else {
viewType = ctx.Query("type") viewType = ctx.Query("type")
types := []string{"assigned", "created_by"} types := []string{"your_repositories", "assigned", "created_by"}
if !com.IsSliceContainsStr(types, viewType) { if !com.IsSliceContainsStr(types, viewType) {
viewType = "all" viewType = "your_repositories"
} }
switch viewType { switch viewType {
case "your_repositories":
filterMode = models.FM_YOUR_REPOSITORIES
case "assigned": case "assigned":
filterMode = models.FM_ASSIGN filterMode = models.FM_ASSIGN
assigneeID = ctxUser.ID
case "created_by": case "created_by":
filterMode = models.FM_CREATE filterMode = models.FM_CREATE
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() {
repos, _, err = ctxUser.GetUserRepositories(ctx.User.ID, 1, ctxUser.NumRepos) repos, _, err = ctxUser.GetUserRepositories(ctx.User.ID, 1, ctxUser.NumRepos)
if err != nil { if err != nil {
@ -215,9 +219,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 &&
@ -225,83 +226,119 @@ func Issues(ctx *context.Context) {
continue continue
} }
repoIDs = append(repoIDs, repo.ID) userRepoIDs = append(userRepoIDs, repo.ID)
}
if isPullList { var issues []*models.Issue
allCount += repo.NumOpenPulls switch filterMode {
repo.NumOpenIssues = repo.NumOpenPulls case models.FM_YOUR_REPOSITORIES:
repo.NumClosedIssues = repo.NumClosedPulls // Get all issues from repositories from this user.
} else { issues, err = models.Issues(&models.IssuesOptions{
allCount += repo.NumOpenIssues RepoIDs: userRepoIDs,
RepoID: repoID,
Page: page,
IsClosed: isShowClosed,
IsPull: isPullList,
SortType: sortType,
})
case models.FM_ASSIGN:
// Get all issues assigned to this user.
issues, err = models.Issues(&models.IssuesOptions{
RepoID: repoID,
AssigneeID: ctxUser.ID,
Page: page,
IsClosed: isShowClosed,
IsPull: isPullList,
SortType: sortType,
})
case models.FM_CREATE:
// Get all issues created by this user.
issues, err = models.Issues(&models.IssuesOptions{
RepoID: repoID,
PosterID: ctxUser.ID,
Page: page,
IsClosed: isShowClosed,
IsPull: 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.FM_ALL { if err = repo.GetOwner(); err != nil {
// Calculate repository issue count with filter mode. ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", repoID, err))
numOpen, numClosed := repo.IssueStats(ctxUser.ID, filterMode, isPullList) return
repo.NumOpenIssues, repo.NumClosedIssues = int(numOpen), int(numClosed)
} }
if repo.ID == repoID || // Check if user has access to given repository.
(isShowClosed && repo.NumClosedIssues > 0) || if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser) {
(!isShowClosed && repo.NumOpenIssues > 0) { ctx.Handle(404, "Issues", fmt.Errorf("#%d", repoID))
showRepos = append(showRepos, repo) return
} }
showReposSet[repoID] = true
showRepos = append(showRepos, repo)
} }
ctx.Data["Repos"] = showRepos
issueStats := models.GetUserIssueStats(repoID, ctxUser.ID, repoIDs, filterMode, isPullList) for _, issue := range issues {
issueStats.AllCount = int64(allCount) // 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
}
page := ctx.QueryInt("page") // Get Owner data.
if page <= 1 { if err = issue.Repo.GetOwner(); err != nil {
page = 1 ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issue.RepoID, err))
return
}
// Append repo to list of shown repos
if filterMode == models.FM_YOUR_REPOSITORIES {
// 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{
UserID: ctxUser.ID,
AssigneeID: assigneeID,
RepoID: repoID,
PosterID: posterID,
RepoIDs: repoIDs,
Page: page,
IsClosed: isShowClosed,
IsPull: 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 {

8
templates/user/dashboard/issues.tmpl

@ -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>
{{if .NumComments}} {{if .NumComments}}

Loading…
Cancel
Save