diff options
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/v1/api.go | 11 | ||||
-rw-r--r-- | routers/api/v1/repo/issue_tracked_time.go | 158 | ||||
-rw-r--r-- | routers/install.go | 2 | ||||
-rw-r--r-- | routers/repo/issue.go | 39 | ||||
-rw-r--r-- | routers/repo/issue_stopwatch.go | 50 | ||||
-rw-r--r-- | routers/repo/issue_timetrack.go | 50 | ||||
-rw-r--r-- | routers/repo/setting.go | 5 | ||||
-rw-r--r-- | routers/routes/routes.go | 13 |
8 files changed, 324 insertions, 4 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index b658c972f1..526940493f 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -350,6 +350,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Delete("", user.Unstar) }, repoAssignment()) }) + m.Get("/times", repo.ListMyTrackedTimes) m.Get("/subscriptions", user.GetMyWatchedRepos) }, reqToken()) @@ -395,6 +396,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo("/:id").Get(repo.GetDeployKey). Delete(repo.DeleteDeploykey) }, reqToken(), reqRepoWriter()) + m.Group("/times", func() { + m.Combo("").Get(repo.ListTrackedTimesByRepository) + m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser) + + }, mustEnableIssues) m.Group("/issues", func() { m.Combo("").Get(repo.ListIssues). Post(reqToken(), bind(api.CreateIssueOption{}), repo.CreateIssue) @@ -422,6 +428,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Delete("/:id", reqToken(), repo.DeleteIssueLabel) }) + m.Group("/times", func() { + m.Combo("").Get(repo.ListTrackedTimes). + Post(reqToken(), bind(api.AddTimeOption{}), repo.AddTime) + }) + }) }, mustEnableIssues) m.Group("/labels", func() { diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go new file mode 100644 index 0000000000..964fc11ddb --- /dev/null +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -0,0 +1,158 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/sdk/gitea" +) + +// ListTrackedTimes list all the tracked times of an issue +func ListTrackedTimes(ctx *context.APIContext) { + // swagger:route GET /repos/{username}/{reponame}/issues/{issue}/times repository issueTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 404: error + // 500: error + if !ctx.Repo.Repository.IsTimetrackerEnabled() { + ctx.Error(404, "IsTimetrackerEnabled", "Timetracker is diabled") + return + } + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + ctx.Error(404, "GetIssueByIndex", err) + } else { + ctx.Error(500, "GetIssueByIndex", err) + } + return + } + + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil { + ctx.Error(500, "GetTrackedTimesByIssue", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + +// AddTime adds time manual to the given issue +func AddTime(ctx *context.APIContext, form api.AddTimeOption) { + // swagger:route Post /repos/{username}/{reponame}/issues/{issue}/times repository addTime + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTime + // 400: error + // 403: error + // 404: error + // 500: error + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + ctx.Error(404, "GetIssueByIndex", err) + } else { + ctx.Error(500, "GetIssueByIndex", err) + } + return + } + + if !ctx.Repo.CanUseTimetracker(issue, ctx.User) { + if !ctx.Repo.Repository.IsTimetrackerEnabled() { + ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"}) + return + } + ctx.Status(403) + return + } + var tt *models.TrackedTime + if tt, err = models.AddTime(ctx.User, issue, form.Time); err != nil { + ctx.Error(500, "AddTime", err) + return + } + ctx.JSON(200, tt) + +} + +// ListTrackedTimesByUser lists all tracked times of the user +func ListTrackedTimesByUser(ctx *context.APIContext) { + // swagger:route GET /repos/{username}/{reponame}/times/{timetrackingusername} user userTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 400: error + // 404: error + // 500: error + if !ctx.Repo.Repository.IsTimetrackerEnabled() { + ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"}) + return + } + user, err := models.GetUserByName(ctx.Params(":timetrackingusername")) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Error(404, "GetUserByName", err) + } else { + ctx.Error(500, "GetUserByName", err) + } + return + } + if user == nil { + ctx.Status(404) + return + } + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: user.ID, RepositoryID: ctx.Repo.Repository.ID}); err != nil { + ctx.Error(500, "GetTrackedTimesByUser", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + +// ListTrackedTimesByRepository lists all tracked times of the user +func ListTrackedTimesByRepository(ctx *context.APIContext) { + // swagger:route GET /repos/{username}/{reponame}/times repository repoTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 400: error + // 500: error + if !ctx.Repo.Repository.IsTimetrackerEnabled() { + ctx.JSON(400, struct{ Message string }{Message: "time tracking disabled"}) + return + } + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{RepositoryID: ctx.Repo.Repository.ID}); err != nil { + ctx.Error(500, "GetTrackedTimesByUser", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} + +// ListMyTrackedTimes lists all tracked times of the current user +func ListMyTrackedTimes(ctx *context.APIContext) { + // swagger:route GET /user/times user userTrackedTimes + // + // Produces: + // - application/json + // + // Responses: + // 200: TrackedTimes + // 500: error + if trackedTimes, err := models.GetTrackedTimes(models.FindTrackedTimesOptions{UserID: ctx.User.ID}); err != nil { + ctx.Error(500, "GetTrackedTimesByUser", err) + } else { + ctx.JSON(200, &trackedTimes) + } +} diff --git a/routers/install.go b/routers/install.go index 08f5d80f3b..427bb960f2 100644 --- a/routers/install.go +++ b/routers/install.go @@ -115,6 +115,7 @@ func Install(ctx *context.Context) { form.RequireSignInView = setting.Service.RequireSignInView form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization + form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking form.NoReplyAddress = setting.Service.NoReplyAddress auth.AssignForm(form, ctx.Data) @@ -301,6 +302,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView)) cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(com.ToStr(form.DefaultKeepEmailPrivate)) cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(com.ToStr(form.DefaultAllowCreateOrganization)) + cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(com.ToStr(form.DefaultEnableTimetracking)) cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(com.ToStr(form.NoReplyAddress)) cfg.Section("").Key("RUN_MODE").SetValue("prod") diff --git a/routers/repo/issue.go b/routers/repo/issue.go index d1c5e1fe71..0cd4edabb6 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -589,6 +589,38 @@ func ViewIssue(ctx *context.Context) { comment *models.Comment participants = make([]*models.User, 1, 10) ) + if ctx.Repo.Repository.IsTimetrackerEnabled() { + if ctx.IsSigned { + // Deal with the stopwatch + ctx.Data["IsStopwatchRunning"] = models.StopwatchExists(ctx.User.ID, issue.ID) + if !ctx.Data["IsStopwatchRunning"].(bool) { + var exists bool + var sw *models.Stopwatch + if exists, sw, err = models.HasUserStopwatch(ctx.User.ID); err != nil { + ctx.Handle(500, "HasUserStopwatch", err) + return + } + ctx.Data["HasUserStopwatch"] = exists + if exists { + // Add warning if the user has already a stopwatch + var otherIssue *models.Issue + if otherIssue, err = models.GetIssueByID(sw.IssueID); err != nil { + ctx.Handle(500, "GetIssueByID", err) + return + } + // Add link to the issue of the already running stopwatch + ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL() + } + } + ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.User) + } else { + ctx.Data["CanUseTimetracker"] = false + } + if ctx.Data["WorkingUsers"], err = models.TotalTimes(models.FindTrackedTimesOptions{IssueID: issue.ID}); err != nil { + ctx.Handle(500, "TotalTimes", err) + return + } + } // Render comments and and fetch participants. participants[0] = issue.Poster @@ -683,7 +715,8 @@ func ViewIssue(ctx *context.Context) { ctx.HTML(200, tplIssueView) } -func getActionIssue(ctx *context.Context) *models.Issue { +// GetActionIssue will return the issue which is used in the context. +func GetActionIssue(ctx *context.Context) *models.Issue { issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrIssueNotExist(err) { @@ -720,7 +753,7 @@ func getActionIssues(ctx *context.Context) []*models.Issue { // UpdateIssueTitle change issue's title func UpdateIssueTitle(ctx *context.Context) { - issue := getActionIssue(ctx) + issue := GetActionIssue(ctx) if ctx.Written() { return } @@ -748,7 +781,7 @@ func UpdateIssueTitle(ctx *context.Context) { // UpdateIssueContent change issue's content func UpdateIssueContent(ctx *context.Context) { - issue := getActionIssue(ctx) + issue := GetActionIssue(ctx) if ctx.Written() { return } diff --git a/routers/repo/issue_stopwatch.go b/routers/repo/issue_stopwatch.go new file mode 100644 index 0000000000..7e3121da9f --- /dev/null +++ b/routers/repo/issue_stopwatch.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" +) + +// IssueStopwatch creates or stops a stopwatch for the given issue. +func IssueStopwatch(c *context.Context) { + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) + + if err != nil { + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + return + } + + if err := models.CreateOrStopIssueStopwatch(c.User, issue); err != nil { + c.Handle(http.StatusInternalServerError, "CreateOrStopIssueStopwatch", err) + return + } + + url := issue.HTMLURL() + c.Redirect(url, http.StatusSeeOther) +} + +// CancelStopwatch cancel the stopwatch +func CancelStopwatch(c *context.Context) { + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) + + if err != nil { + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + return + } + + if err := models.CancelStopwatch(c.User, issue); err != nil { + c.Handle(http.StatusInternalServerError, "CancelStopwatch", err) + return + } + + url := issue.HTMLURL() + c.Redirect(url, http.StatusSeeOther) +} diff --git a/routers/repo/issue_timetrack.go b/routers/repo/issue_timetrack.go new file mode 100644 index 0000000000..e01cd48a66 --- /dev/null +++ b/routers/repo/issue_timetrack.go @@ -0,0 +1,50 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "net/http" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/context" +) + +// AddTimeManually tracks time manually +func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) { + issueIndex := c.ParamsInt64("index") + issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, issueIndex) + if err != nil { + if models.IsErrIssueNotExist(err) { + c.Handle(http.StatusNotFound, "GetIssueByIndex", err) + return + } + c.Handle(http.StatusInternalServerError, "GetIssueByIndex", err) + return + } + url := issue.HTMLURL() + + if c.HasError() { + c.Flash.Error(c.GetErrMsg()) + c.Redirect(url) + return + } + + total := time.Duration(form.Hours)*time.Hour + time.Duration(form.Minutes)*time.Minute + + if total <= 0 { + c.Flash.Error(c.Tr("repo.issues.add_time_sum_to_small")) + c.Redirect(url, http.StatusSeeOther) + return + } + + if _, err := models.AddTime(c.User, issue, int64(total)); err != nil { + c.Handle(http.StatusInternalServerError, "AddTime", err) + return + } + + c.Redirect(url, http.StatusSeeOther) +} diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 8b38e80916..6e12c7ad6d 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -201,7 +201,10 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { RepoID: repo.ID, Type: models.UnitTypeIssues, Index: int(models.UnitTypeIssues), - Config: new(models.UnitConfig), + Config: &models.IssuesConfig{ + EnableTimetracker: form.EnableTimetracker, + AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, + }, }) } } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 5fa93fdb09..938dec1dcf 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -484,6 +484,19 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/content", repo.UpdateIssueContent) m.Post("/watch", repo.IssueWatch) m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment) + m.Group("/times", func() { + m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually) + m.Group("/stopwatch", func() { + m.Post("/toggle", repo.IssueStopwatch) + m.Post("/cancel", repo.CancelStopwatch) + }) + + }, func(ctx *context.Context) { + if !ctx.Repo.CanUseTimetracker(repo.GetActionIssue(ctx), ctx.User) { + ctx.Handle(404, ctx.Req.RequestURI, nil) + return + } + }) }) m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter) |