aboutsummaryrefslogtreecommitdiffstats
path: root/routers/user
diff options
context:
space:
mode:
authorBrad Albright <32200834+bhalbright@users.noreply.github.com>2019-12-15 08:20:08 -0600
committerzeripath <art27@cantab.net>2019-12-15 14:20:08 +0000
commitf6b29012e09d5f7770a0b1ea8659da5172e155b3 (patch)
treecf78ded22aee572c91e93e2a2a3db8a7f9fd903c /routers/user
parent7cc16740a56072465b3938cbb9cd326d40bd7ba9 (diff)
downloadgitea-f6b29012e09d5f7770a0b1ea8659da5172e155b3.tar.gz
gitea-f6b29012e09d5f7770a0b1ea8659da5172e155b3.zip
Add /milestones endpoint (#8733)
Create a /milestones endpoint which basically serves as a dashboard view for milestones, very similar to the /issues or /pulls page. Closes #8232
Diffstat (limited to 'routers/user')
-rw-r--r--routers/user/home.go193
-rw-r--r--routers/user/home_test.go39
2 files changed, 229 insertions, 3 deletions
diff --git a/routers/user/home.go b/routers/user/home.go
index a1060f371f..426f15bfa7 100644
--- a/routers/user/home.go
+++ b/routers/user/home.go
@@ -18,17 +18,20 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/keybase/go-crypto/openpgp"
"github.com/keybase/go-crypto/openpgp/armor"
+ "github.com/unknwon/com"
)
const (
- tplDashboard base.TplName = "user/dashboard/dashboard"
- tplIssues base.TplName = "user/dashboard/issues"
- tplProfile base.TplName = "user/profile"
+ tplDashboard base.TplName = "user/dashboard/dashboard"
+ tplIssues base.TplName = "user/dashboard/issues"
+ tplMilestones base.TplName = "user/dashboard/milestones"
+ tplProfile base.TplName = "user/profile"
)
// getDashboardContextUser finds out dashboard is viewing as which context user.
@@ -150,6 +153,190 @@ func Dashboard(ctx *context.Context) {
ctx.HTML(200, tplDashboard)
}
+// Milestones render the user milestones page
+func Milestones(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("milestones")
+ ctx.Data["PageIsMilestonesDashboard"] = true
+
+ ctxUser := getDashboardContextUser(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ sortType := ctx.Query("sort")
+ page := ctx.QueryInt("page")
+ if page <= 1 {
+ page = 1
+ }
+
+ reposQuery := ctx.Query("repos")
+ isShowClosed := ctx.Query("state") == "closed"
+
+ // Get repositories.
+ var err error
+ var userRepoIDs []int64
+ if ctxUser.IsOrganization() {
+ env, err := ctxUser.AccessibleReposEnv(ctx.User.ID)
+ if err != nil {
+ ctx.ServerError("AccessibleReposEnv", err)
+ return
+ }
+ userRepoIDs, err = env.RepoIDs(1, ctxUser.NumRepos)
+ if err != nil {
+ ctx.ServerError("env.RepoIDs", err)
+ return
+ }
+ } else {
+ unitType := models.UnitTypeIssues
+ userRepoIDs, err = ctxUser.GetAccessRepoIDs(unitType)
+ if err != nil {
+ ctx.ServerError("ctxUser.GetAccessRepoIDs", err)
+ return
+ }
+ }
+ if len(userRepoIDs) == 0 {
+ userRepoIDs = []int64{-1}
+ }
+
+ var repoIDs []int64
+ if issueReposQueryPattern.MatchString(reposQuery) {
+ // remove "[" and "]" from string
+ reposQuery = reposQuery[1 : len(reposQuery)-1]
+ //for each ID (delimiter ",") add to int to repoIDs
+ reposSet := false
+ for _, rID := range strings.Split(reposQuery, ",") {
+ // Ensure nonempty string entries
+ if rID != "" && rID != "0" {
+ reposSet = true
+ rIDint64, err := strconv.ParseInt(rID, 10, 64)
+ if err == nil && com.IsSliceContainsInt64(userRepoIDs, rIDint64) {
+ repoIDs = append(repoIDs, rIDint64)
+ }
+ }
+ }
+ if reposSet && len(repoIDs) == 0 {
+ // force an empty result
+ repoIDs = []int64{-1}
+ }
+ } else {
+ log.Error("issueReposQueryPattern not match with query")
+ }
+
+ if len(repoIDs) == 0 {
+ repoIDs = userRepoIDs
+ }
+
+ counts, err := models.CountMilestonesByRepoIDs(userRepoIDs, isShowClosed)
+ if err != nil {
+ ctx.ServerError("CountMilestonesByRepoIDs", err)
+ return
+ }
+
+ milestones, err := models.GetMilestonesByRepoIDs(repoIDs, page, isShowClosed, sortType)
+ if err != nil {
+ ctx.ServerError("GetMilestonesByRepoIDs", err)
+ return
+ }
+
+ showReposMap := make(map[int64]*models.Repository, len(counts))
+ for rID := range counts {
+ if rID == -1 {
+ break
+ }
+ repo, err := models.GetRepositoryByID(rID)
+ if err != nil {
+ if models.IsErrRepoNotExist(err) {
+ ctx.NotFound("GetRepositoryByID", err)
+ return
+ } else if err != nil {
+ ctx.ServerError("GetRepositoryByID", fmt.Errorf("[%d]%v", rID, err))
+ return
+ }
+ }
+ showReposMap[rID] = repo
+
+ // Check if user has access to given repository.
+ perm, err := models.GetUserRepoPermission(repo, ctxUser)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", fmt.Errorf("[%d]%v", rID, err))
+ return
+ }
+
+ if !perm.CanRead(models.UnitTypeIssues) {
+ if log.IsTrace() {
+ log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
+ "User in repo has Permissions: %-+v",
+ ctxUser,
+ models.UnitTypeIssues,
+ repo,
+ perm)
+ }
+ ctx.Status(404)
+ return
+ }
+ }
+
+ showRepos := models.RepositoryListOfMap(showReposMap)
+ sort.Sort(showRepos)
+ if err = showRepos.LoadAttributes(); err != nil {
+ ctx.ServerError("LoadAttributes", err)
+ return
+ }
+
+ for _, m := range milestones {
+ m.Repo = showReposMap[m.RepoID]
+ m.RenderedContent = string(markdown.Render([]byte(m.Content), m.Repo.Link(), m.Repo.ComposeMetas()))
+ if m.Repo.IsTimetrackerEnabled() {
+ err := m.LoadTotalTrackedTime()
+ if err != nil {
+ ctx.ServerError("LoadTotalTrackedTime", err)
+ return
+ }
+ }
+ }
+
+ milestoneStats, err := models.GetMilestonesStats(repoIDs)
+ if err != nil {
+ ctx.ServerError("GetMilestoneStats", err)
+ return
+ }
+
+ totalMilestoneStats, err := models.GetMilestonesStats(userRepoIDs)
+ if err != nil {
+ ctx.ServerError("GetMilestoneStats", err)
+ return
+ }
+
+ var pagerCount int
+ if isShowClosed {
+ ctx.Data["State"] = "closed"
+ ctx.Data["Total"] = totalMilestoneStats.ClosedCount
+ pagerCount = int(milestoneStats.ClosedCount)
+ } else {
+ ctx.Data["State"] = "open"
+ ctx.Data["Total"] = totalMilestoneStats.OpenCount
+ pagerCount = int(milestoneStats.OpenCount)
+ }
+
+ ctx.Data["Milestones"] = milestones
+ ctx.Data["Repos"] = showRepos
+ ctx.Data["Counts"] = counts
+ ctx.Data["MilestoneStats"] = milestoneStats
+ ctx.Data["SortType"] = sortType
+ if len(repoIDs) != len(userRepoIDs) {
+ ctx.Data["RepoIDs"] = repoIDs
+ }
+ ctx.Data["IsShowClosed"] = isShowClosed
+
+ pager := context.NewPagination(pagerCount, setting.UI.IssuePagingNum, page, 5)
+ pager.AddParam(ctx, "repos", "RepoIDs")
+ pager.AddParam(ctx, "sort", "SortType")
+ pager.AddParam(ctx, "state", "State")
+ ctx.Data["Page"] = pager
+
+ ctx.HTML(200, tplMilestones)
+}
+
// Regexp for repos query
var issueReposQueryPattern = regexp.MustCompile(`^\[\d+(,\d+)*,?\]$`)
diff --git a/routers/user/home_test.go b/routers/user/home_test.go
index 9d4136ac8c..e5bbd0e98e 100644
--- a/routers/user/home_test.go
+++ b/routers/user/home_test.go
@@ -31,3 +31,42 @@ func TestIssues(t *testing.T) {
assert.Len(t, ctx.Data["Issues"], 1)
assert.Len(t, ctx.Data["Repos"], 1)
}
+
+func TestMilestones(t *testing.T) {
+ setting.UI.IssuePagingNum = 1
+ assert.NoError(t, models.LoadFixtures())
+
+ ctx := test.MockContext(t, "milestones")
+ test.LoadUser(t, ctx, 2)
+ ctx.SetParams("sort", "issues")
+ ctx.Req.Form.Set("state", "closed")
+ ctx.Req.Form.Set("sort", "furthestduedate")
+ Milestones(ctx)
+ assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"])
+ assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
+ assert.EqualValues(t, "furthestduedate", ctx.Data["SortType"])
+ assert.EqualValues(t, 1, ctx.Data["Total"])
+ assert.Len(t, ctx.Data["Milestones"], 1)
+ assert.Len(t, ctx.Data["Repos"], 1)
+}
+
+func TestMilestonesForSpecificRepo(t *testing.T) {
+ setting.UI.IssuePagingNum = 1
+ assert.NoError(t, models.LoadFixtures())
+
+ ctx := test.MockContext(t, "milestones")
+ test.LoadUser(t, ctx, 2)
+ ctx.SetParams("sort", "issues")
+ ctx.SetParams("repo", "1")
+ ctx.Req.Form.Set("state", "closed")
+ ctx.Req.Form.Set("sort", "furthestduedate")
+ Milestones(ctx)
+ assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
+ assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"])
+ assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
+ assert.EqualValues(t, "furthestduedate", ctx.Data["SortType"])
+ assert.EqualValues(t, 1, ctx.Data["Total"])
+ assert.Len(t, ctx.Data["Milestones"], 1)
+ assert.Len(t, ctx.Data["Repos"], 1)
+}