Add UI to delete tracked times (#14100)
Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
6a696b93b1
commit
d38ae597e1
13 changed files with 123 additions and 4 deletions
|
@ -136,6 +136,8 @@ type Comment struct {
|
||||||
MilestoneID int64
|
MilestoneID int64
|
||||||
OldMilestone *Milestone `xorm:"-"`
|
OldMilestone *Milestone `xorm:"-"`
|
||||||
Milestone *Milestone `xorm:"-"`
|
Milestone *Milestone `xorm:"-"`
|
||||||
|
TimeID int64
|
||||||
|
Time *TrackedTime `xorm:"-"`
|
||||||
AssigneeID int64
|
AssigneeID int64
|
||||||
RemovedAssignee bool
|
RemovedAssignee bool
|
||||||
Assignee *User `xorm:"-"`
|
Assignee *User `xorm:"-"`
|
||||||
|
@ -541,6 +543,16 @@ func (c *Comment) LoadDepIssueDetails() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadTime loads the associated time for a CommentTypeAddTimeManual
|
||||||
|
func (c *Comment) LoadTime() error {
|
||||||
|
if c.Time != nil || c.TimeID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
c.Time, err = GetTrackedTimeByID(c.TimeID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Comment) loadReactions(e Engine, repo *Repository) (err error) {
|
func (c *Comment) loadReactions(e Engine, repo *Repository) (err error) {
|
||||||
if c.Reactions != nil {
|
if c.Reactions != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -692,6 +704,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
||||||
MilestoneID: opts.MilestoneID,
|
MilestoneID: opts.MilestoneID,
|
||||||
OldProjectID: opts.OldProjectID,
|
OldProjectID: opts.OldProjectID,
|
||||||
ProjectID: opts.ProjectID,
|
ProjectID: opts.ProjectID,
|
||||||
|
TimeID: opts.TimeID,
|
||||||
RemovedAssignee: opts.RemovedAssignee,
|
RemovedAssignee: opts.RemovedAssignee,
|
||||||
AssigneeID: opts.AssigneeID,
|
AssigneeID: opts.AssigneeID,
|
||||||
AssigneeTeamID: opts.AssigneeTeamID,
|
AssigneeTeamID: opts.AssigneeTeamID,
|
||||||
|
@ -859,6 +872,7 @@ type CreateCommentOptions struct {
|
||||||
MilestoneID int64
|
MilestoneID int64
|
||||||
OldProjectID int64
|
OldProjectID int64
|
||||||
ProjectID int64
|
ProjectID int64
|
||||||
|
TimeID int64
|
||||||
AssigneeID int64
|
AssigneeID int64
|
||||||
AssigneeTeamID int64
|
AssigneeTeamID int64
|
||||||
RemovedAssignee bool
|
RemovedAssignee bool
|
||||||
|
|
|
@ -100,6 +100,7 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
|
||||||
Repo: issue.Repo,
|
Repo: issue.Repo,
|
||||||
Content: SecToTime(timediff),
|
Content: SecToTime(timediff),
|
||||||
Type: CommentTypeStopTracking,
|
Type: CommentTypeStopTracking,
|
||||||
|
TimeID: tt.ID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,7 @@ func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*Tracke
|
||||||
Doer: user,
|
Doer: user,
|
||||||
Content: SecToTime(amount),
|
Content: SecToTime(amount),
|
||||||
Type: CommentTypeAddTimeManual,
|
Type: CommentTypeAddTimeManual,
|
||||||
|
TimeID: t.ID,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,6 +292,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard),
|
NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard),
|
||||||
// v172 -> v173
|
// v172 -> v173
|
||||||
NewMigration("Add sessions table for go-chi/session", addSessionTable),
|
NewMigration("Add sessions table for go-chi/session", addSessionTable),
|
||||||
|
// v173 -> v174
|
||||||
|
NewMigration("Add time_id column to Comment", addTimeIDCommentColumn),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
|
22
models/migrations/v173.go
Normal file
22
models/migrations/v173.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2021 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 migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addTimeIDCommentColumn(x *xorm.Engine) error {
|
||||||
|
type Comment struct {
|
||||||
|
TimeID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := x.Sync2(new(Comment)); err != nil {
|
||||||
|
return fmt.Errorf("Sync2: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1163,6 +1163,7 @@ issues.stop_tracking_history = `stopped working %s`
|
||||||
issues.cancel_tracking = Discard
|
issues.cancel_tracking = Discard
|
||||||
issues.cancel_tracking_history = `cancelled time tracking %s`
|
issues.cancel_tracking_history = `cancelled time tracking %s`
|
||||||
issues.add_time = Manually Add Time
|
issues.add_time = Manually Add Time
|
||||||
|
issues.del_time = Delete this time log
|
||||||
issues.add_time_short = Add Time
|
issues.add_time_short = Add Time
|
||||||
issues.add_time_cancel = Cancel
|
issues.add_time_cancel = Cancel
|
||||||
issues.add_time_history = `added spent time %s`
|
issues.add_time_history = `added spent time %s`
|
||||||
|
|
|
@ -1416,6 +1416,10 @@ func ViewIssue(ctx *context.Context) {
|
||||||
ctx.ServerError("LoadPushCommits", err)
|
ctx.ServerError("LoadPushCommits", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if comment.Type == models.CommentTypeAddTimeManual ||
|
||||||
|
comment.Type == models.CommentTypeStopTracking {
|
||||||
|
// drop error since times could be pruned from DB..
|
||||||
|
_ = comment.LoadTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
auth "code.gitea.io/gitea/modules/forms"
|
"code.gitea.io/gitea/modules/forms"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddTimeManually tracks time manually
|
// AddTimeManually tracks time manually
|
||||||
func AddTimeManually(c *context.Context) {
|
func AddTimeManually(c *context.Context) {
|
||||||
form := web.GetForm(c).(*auth.AddTimeManuallyForm)
|
form := web.GetForm(c).(*forms.AddTimeManuallyForm)
|
||||||
issue := GetActionIssue(c)
|
issue := GetActionIssue(c)
|
||||||
if c.Written() {
|
if c.Written() {
|
||||||
return
|
return
|
||||||
|
@ -48,3 +48,39 @@ func AddTimeManually(c *context.Context) {
|
||||||
|
|
||||||
c.Redirect(url, http.StatusSeeOther)
|
c.Redirect(url, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteTime deletes tracked time
|
||||||
|
func DeleteTime(c *context.Context) {
|
||||||
|
issue := GetActionIssue(c)
|
||||||
|
if c.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.Repo.CanUseTimetracker(issue, c.User) {
|
||||||
|
c.NotFound("CanUseTimetracker", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := models.GetTrackedTimeByID(c.ParamsInt64(":timeid"))
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrNotExist(err) {
|
||||||
|
c.NotFound("time not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Error(http.StatusInternalServerError, "GetTrackedTimeByID", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// only OP or admin may delete
|
||||||
|
if !c.IsSigned || (!c.IsUserSiteAdmin() && c.User.ID != t.UserID) {
|
||||||
|
c.Error(http.StatusForbidden, "not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = models.DeleteTime(t); err != nil {
|
||||||
|
c.ServerError("DeleteTime", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Flash.Success(c.Tr("repo.issues.del_time_history", models.SecToTime(t.Time)))
|
||||||
|
c.Redirect(issue.HTMLURL())
|
||||||
|
}
|
||||||
|
|
|
@ -723,6 +723,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Combo("/comments").Post(repo.MustAllowUserComment, bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
|
m.Combo("/comments").Post(repo.MustAllowUserComment, bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
|
||||||
m.Group("/times", func() {
|
m.Group("/times", func() {
|
||||||
m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)
|
m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)
|
||||||
|
m.Post("/{timeid}/delete", repo.DeleteTime)
|
||||||
m.Group("/stopwatch", func() {
|
m.Group("/stopwatch", func() {
|
||||||
m.Post("/toggle", repo.IssueStopwatch)
|
m.Post("/toggle", repo.IssueStopwatch)
|
||||||
m.Post("/cancel", repo.CancelStopwatch)
|
m.Post("/cancel", repo.CancelStopwatch)
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
||||||
{{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}}
|
{{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}}
|
||||||
</span>
|
</span>
|
||||||
|
{{ template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" . }}
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
{{svg "octicon-clock"}}
|
{{svg "octicon-clock"}}
|
||||||
<span class="text grey">{{.Content}}</span>
|
<span class="text grey">{{.Content}}</span>
|
||||||
|
@ -291,6 +292,7 @@
|
||||||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
|
||||||
{{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}}
|
{{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}}
|
||||||
</span>
|
</span>
|
||||||
|
{{ template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" . }}
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
{{svg "octicon-clock"}}
|
{{svg "octicon-clock"}}
|
||||||
<span class="text grey">{{.Content}}</span>
|
<span class="text grey">{{.Content}}</span>
|
||||||
|
|
21
templates/repo/issue/view_content/comments_delete_time.tmpl
Normal file
21
templates/repo/issue/view_content/comments_delete_time.tmpl
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{{ if .comment.Time }} {{/* compatibility with time comments made before v1.14 */}}
|
||||||
|
{{ if (not .comment.Time.Deleted) }}
|
||||||
|
{{ if (or .ctx.IsAdmin (and .ctx.IsSigned (eq .ctx.SignedUserID .comment.PosterID))) }}
|
||||||
|
<span class="ui float right">
|
||||||
|
<div class="ui mini modal issue-delete-time-modal" data-id="{{.comment.Time.ID}}">
|
||||||
|
<form method="POST" class="delete-time-form" action="{{.ctx.RepoLink}}/issues/{{.ctx.Issue.Index}}/times/{{.comment.TimeID}}/delete">
|
||||||
|
{{.ctx.CsrfTokenHtml}}
|
||||||
|
</form>
|
||||||
|
<div class="header">{{.ctx.i18n.Tr "repo.issues.del_time"}}</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="ui red approve button">{{.ctx.i18n.Tr "repo.issues.context.delete"}}</div>
|
||||||
|
<div class="ui cancel button">{{.ctx.i18n.Tr "repo.issues.add_time_cancel"}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="ui icon button compact mini issue-delete-time poping up" data-id="{{.comment.Time.ID}}" data-content="{{.ctx.i18n.Tr "repo.issues.del_time"}}" data-position="top right" data-variation="tiny inverted">
|
||||||
|
{{svg "octicon-trashcan"}}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
|
@ -348,7 +348,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="ui buttons two fluid">
|
<div class="ui buttons two fluid">
|
||||||
<button class="ui button poping up issue-start-time" data-content='{{.i18n.Tr "repo.issues.start_tracking"}}' data-position="top center" data-variation="small inverted">{{.i18n.Tr "repo.issues.start_tracking_short"}}</button>
|
<button class="ui button poping up issue-start-time" data-content='{{.i18n.Tr "repo.issues.start_tracking"}}' data-position="top center" data-variation="small inverted">{{.i18n.Tr "repo.issues.start_tracking_short"}}</button>
|
||||||
<div class="ui mini modal">
|
<div class="ui mini modal issue-start-time-modal">
|
||||||
<div class="header">{{.i18n.Tr "repo.issues.add_time"}}</div>
|
<div class="header">{{.i18n.Tr "repo.issues.add_time"}}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form method="POST" id="add_time_manual_form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/times/add" class="ui action input fluid">
|
<form method="POST" id="add_time_manual_form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/times/add" class="ui action input fluid">
|
||||||
|
|
|
@ -3203,12 +3203,17 @@ function initVueApp() {
|
||||||
|
|
||||||
function initIssueTimetracking() {
|
function initIssueTimetracking() {
|
||||||
$(document).on('click', '.issue-add-time', () => {
|
$(document).on('click', '.issue-add-time', () => {
|
||||||
$('.mini.modal').modal({
|
$('.issue-start-time-modal').modal({
|
||||||
duration: 200,
|
duration: 200,
|
||||||
onApprove() {
|
onApprove() {
|
||||||
$('#add_time_manual_form').trigger('submit');
|
$('#add_time_manual_form').trigger('submit');
|
||||||
}
|
}
|
||||||
}).modal('show');
|
}).modal('show');
|
||||||
|
$('.issue-start-time-modal input').on('keydown', (e) => {
|
||||||
|
if ((e.keyCode || e.key) === 13) {
|
||||||
|
$('#add_time_manual_form').trigger('submit');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
$(document).on('click', '.issue-start-time, .issue-stop-time', () => {
|
$(document).on('click', '.issue-start-time, .issue-stop-time', () => {
|
||||||
$('#toggle_stopwatch_form').trigger('submit');
|
$('#toggle_stopwatch_form').trigger('submit');
|
||||||
|
@ -3216,6 +3221,15 @@ function initIssueTimetracking() {
|
||||||
$(document).on('click', '.issue-cancel-time', () => {
|
$(document).on('click', '.issue-cancel-time', () => {
|
||||||
$('#cancel_stopwatch_form').trigger('submit');
|
$('#cancel_stopwatch_form').trigger('submit');
|
||||||
});
|
});
|
||||||
|
$(document).on('click', 'button.issue-delete-time', function () {
|
||||||
|
const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`;
|
||||||
|
$(sel).modal({
|
||||||
|
duration: 200,
|
||||||
|
onApprove() {
|
||||||
|
$(`${sel} form`).trigger('submit');
|
||||||
|
}
|
||||||
|
}).modal('show');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initFilterBranchTagDropdown(selector) {
|
function initFilterBranchTagDropdown(selector) {
|
||||||
|
|
Loading…
Reference in a new issue