Co-authored-by: 6543 <6543@obermui.de>tags/v1.15.0-dev
@@ -136,6 +136,8 @@ type Comment struct { | |||
MilestoneID int64 | |||
OldMilestone *Milestone `xorm:"-"` | |||
Milestone *Milestone `xorm:"-"` | |||
TimeID int64 | |||
Time *TrackedTime `xorm:"-"` | |||
AssigneeID int64 | |||
RemovedAssignee bool | |||
Assignee *User `xorm:"-"` | |||
@@ -541,6 +543,16 @@ func (c *Comment) LoadDepIssueDetails() (err error) { | |||
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) { | |||
if c.Reactions != nil { | |||
return nil | |||
@@ -692,6 +704,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||
MilestoneID: opts.MilestoneID, | |||
OldProjectID: opts.OldProjectID, | |||
ProjectID: opts.ProjectID, | |||
TimeID: opts.TimeID, | |||
RemovedAssignee: opts.RemovedAssignee, | |||
AssigneeID: opts.AssigneeID, | |||
AssigneeTeamID: opts.AssigneeTeamID, | |||
@@ -859,6 +872,7 @@ type CreateCommentOptions struct { | |||
MilestoneID int64 | |||
OldProjectID int64 | |||
ProjectID int64 | |||
TimeID int64 | |||
AssigneeID int64 | |||
AssigneeTeamID int64 | |||
RemovedAssignee bool |
@@ -100,6 +100,7 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error { | |||
Repo: issue.Repo, | |||
Content: SecToTime(timediff), | |||
Type: CommentTypeStopTracking, | |||
TimeID: tt.ID, | |||
}); err != nil { | |||
return err | |||
} |
@@ -162,6 +162,7 @@ func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*Tracke | |||
Doer: user, | |||
Content: SecToTime(amount), | |||
Type: CommentTypeAddTimeManual, | |||
TimeID: t.ID, | |||
}); err != nil { | |||
return nil, err | |||
} |
@@ -292,6 +292,8 @@ var migrations = []Migration{ | |||
NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard), | |||
// v172 -> v173 | |||
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 |
@@ -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_history = `cancelled time tracking %s` | |||
issues.add_time = Manually Add Time | |||
issues.del_time = Delete this time log | |||
issues.add_time_short = Add Time | |||
issues.add_time_cancel = Cancel | |||
issues.add_time_history = `added spent time %s` |
@@ -1416,6 +1416,10 @@ func ViewIssue(ctx *context.Context) { | |||
ctx.ServerError("LoadPushCommits", err) | |||
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/modules/context" | |||
auth "code.gitea.io/gitea/modules/forms" | |||
"code.gitea.io/gitea/modules/forms" | |||
"code.gitea.io/gitea/modules/web" | |||
) | |||
// AddTimeManually tracks time manually | |||
func AddTimeManually(c *context.Context) { | |||
form := web.GetForm(c).(*auth.AddTimeManuallyForm) | |||
form := web.GetForm(c).(*forms.AddTimeManuallyForm) | |||
issue := GetActionIssue(c) | |||
if c.Written() { | |||
return | |||
@@ -48,3 +48,39 @@ func AddTimeManually(c *context.Context) { | |||
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.Group("/times", func() { | |||
m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually) | |||
m.Post("/{timeid}/delete", repo.DeleteTime) | |||
m.Group("/stopwatch", func() { | |||
m.Post("/toggle", repo.IssueStopwatch) | |||
m.Post("/cancel", repo.CancelStopwatch) |
@@ -276,6 +276,7 @@ | |||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a> | |||
{{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}} | |||
</span> | |||
{{ template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" . }} | |||
<div class="detail"> | |||
{{svg "octicon-clock"}} | |||
<span class="text grey">{{.Content}}</span> | |||
@@ -291,6 +292,7 @@ | |||
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a> | |||
{{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}} | |||
</span> | |||
{{ template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" . }} | |||
<div class="detail"> | |||
{{svg "octicon-clock"}} | |||
<span class="text grey">{{.Content}}</span> |
@@ -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}} | |||
<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> | |||
<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="content"> | |||
<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() { | |||
$(document).on('click', '.issue-add-time', () => { | |||
$('.mini.modal').modal({ | |||
$('.issue-start-time-modal').modal({ | |||
duration: 200, | |||
onApprove() { | |||
$('#add_time_manual_form').trigger('submit'); | |||
} | |||
}).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', () => { | |||
$('#toggle_stopwatch_form').trigger('submit'); | |||
@@ -3216,6 +3221,15 @@ function initIssueTimetracking() { | |||
$(document).on('click', '.issue-cancel-time', () => { | |||
$('#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) { |