This commit is contained in:
stuzer05 2023-02-24 17:22:59 +02:00
parent 1b7ba4189b
commit f7427d8b50
11 changed files with 230 additions and 121 deletions

View File

@ -6,9 +6,12 @@
package issues
import (
"code.gitea.io/gitea/modules/translation"
"context"
"fmt"
"math"
"strconv"
"strings"
"unicode/utf8"
"code.gitea.io/gitea/models/db"
@ -305,8 +308,7 @@ type Comment struct {
CommitsNum int64 `xorm:"-"`
IsForcePush bool `xorm:"-"`
TimeEstimateHours int
TimeEstimateMinutes int
TimeEstimate int64
}
func init() {
@ -825,8 +827,7 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
RefIsPull: opts.RefIsPull,
IsForcePush: opts.IsForcePush,
Invalidated: opts.Invalidated,
TimeEstimateHours: opts.TimeEstimateHours,
TimeEstimateMinutes: opts.TimeEstimateMinutes,
TimeEstimate: opts.TimeEstimate,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
@ -998,8 +999,7 @@ type CreateCommentOptions struct {
RefIsPull bool
IsForcePush bool
Invalidated bool
TimeEstimateHours int
TimeEstimateMinutes int
TimeEstimate int64
}
// GetCommentByID returns the comment by given ID.
@ -1259,3 +1259,40 @@ func FixCommentTypeLabelWithOutsideLabels(ctx context.Context) (int64, error) {
func (c *Comment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
}
// TimeEstimateStr returns formatted time estimate string from seconds (e.g. "2 weeks 4 days 12 hours 5 minutes")
func (c *Comment) TimeEstimateToStrTranslated(lang translation.Locale) string {
var timeParts []string
timeSeconds := float64(c.TimeEstimate)
// Format weeks
weeks := math.Floor(timeSeconds / (60 * 60 * 24 * 7))
if weeks > 0 {
timeParts = append(timeParts, lang.Tr("tool.hours", int64(weeks)))
}
timeSeconds -= weeks * (60 * 60 * 24 * 7)
// Format days
days := math.Floor(timeSeconds / (60 * 60 * 24))
if days > 0 {
timeParts = append(timeParts, lang.Tr("tool.days", int64(days)))
}
timeSeconds -= days * (60 * 60 * 24)
// Format hours
hours := math.Floor(timeSeconds / (60 * 60))
if hours > 0 {
timeParts = append(timeParts, lang.Tr("tool.hours", int64(hours)))
}
timeSeconds -= hours * (60 * 60)
// Format minutes
minutes := math.Floor(timeSeconds / (60))
if minutes > 0 {
timeParts = append(timeParts, lang.Tr("tool.minutes", int64(minutes)))
}
timeSeconds -= minutes * (60)
return strings.Join(timeParts[:], " ")
}

View File

@ -7,8 +7,10 @@ package issues
import (
"context"
"fmt"
"math"
"regexp"
"sort"
"strconv"
"strings"
"code.gitea.io/gitea/models/db"
@ -148,8 +150,7 @@ type Issue struct {
ShowRole RoleDescriptor `xorm:"-"`
// Time estimate
TimeEstimateHours int
TimeEstimateMinutes int
TimeEstimate int64
}
var (
@ -779,14 +780,14 @@ func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err
}
// ChangeIssueTimeEstimate changes the plan time of this issue, as the given user.
func ChangeIssueTimeEstimate(issue *Issue, doer *user_model.User, timeEstimateHours, timeEstimateMinutes int) (err error) {
func ChangeIssueTimeEstimate(issue *Issue, doer *user_model.User, timeEstimate int64) (err error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
if err != nil {
return err
}
defer committer.Close()
if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, TimeEstimateHours: timeEstimateHours, TimeEstimateMinutes: timeEstimateMinutes}, "time_estimate_hours", "time_estimate_minutes"); err != nil {
if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, TimeEstimate: timeEstimate}, "time_estimate"); err != nil {
return fmt.Errorf("updateIssueCols: %w", err)
}
@ -799,8 +800,8 @@ func ChangeIssueTimeEstimate(issue *Issue, doer *user_model.User, timeEstimateHo
Doer: doer,
Repo: issue.Repo,
Issue: issue,
TimeEstimateHours: timeEstimateHours,
TimeEstimateMinutes: timeEstimateMinutes,
Content: util.SecToTime(timeEstimate),
TimeEstimate: timeEstimate,
}
if _, err = CreateComment(ctx, opts); err != nil {
return fmt.Errorf("createComment: %w", err)
@ -2472,3 +2473,81 @@ func DeleteOrphanedIssues(ctx context.Context) error {
func (issue *Issue) HasOriginalAuthor() bool {
return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0
}
// TimeEstimateFromStr returns time estimate in seconds from formatted string
func (issue *Issue) TimeEstimateFromStr(timeStr string) int64 {
timeTotal := 0
// Time match regex
rWeeks, _ := regexp.Compile("([\\d]+)w")
rDays, _ := regexp.Compile("([\\d]+)d")
rHours, _ := regexp.Compile("([\\d]+)h")
rMinutes, _ := regexp.Compile("([\\d]+)m")
// Find time weeks
timeStrMatches := rWeeks.FindStringSubmatch(timeStr)
if len(timeStrMatches) > 0 {
raw, _ := strconv.Atoi(timeStrMatches[1])
timeTotal += raw * (60 * 60 * 24 * 7)
}
// Find time days
timeStrMatches = rDays.FindStringSubmatch(timeStr)
if len(timeStrMatches) > 0 {
raw, _ := strconv.Atoi(timeStrMatches[1])
timeTotal += raw * (60 * 60 * 24)
}
// Find time hours
timeStrMatches = rHours.FindStringSubmatch(timeStr)
if len(timeStrMatches) > 0 {
raw, _ := strconv.Atoi(timeStrMatches[1])
timeTotal += raw * (60 * 60)
}
// Find time minutes
timeStrMatches = rMinutes.FindStringSubmatch(timeStr)
if len(timeStrMatches) > 0 {
raw, _ := strconv.Atoi(timeStrMatches[1])
timeTotal += raw * (60)
}
return int64(timeTotal)
}
// TimeEstimateStr returns formatted time estimate string from seconds (e.g. "2w 4d 12h 5m")
func (issue *Issue) TimeEstimateToStr() string {
var timeParts []string
timeSeconds := float64(issue.TimeEstimate)
// Format weeks
weeks := math.Floor(timeSeconds / (60 * 60 * 24 * 7))
if weeks > 0 {
timeParts = append(timeParts, fmt.Sprintf("%dw", int64(weeks)))
}
timeSeconds -= weeks * (60 * 60 * 24 * 7)
// Format days
days := math.Floor(timeSeconds / (60 * 60 * 24))
if days > 0 {
timeParts = append(timeParts, fmt.Sprintf("%dd", int64(days)))
}
timeSeconds -= days * (60 * 60 * 24)
// Format hours
hours := math.Floor(timeSeconds / (60 * 60))
if hours > 0 {
timeParts = append(timeParts, fmt.Sprintf("%dh", int64(hours)))
}
timeSeconds -= hours * (60 * 60)
// Format minutes
minutes := math.Floor(timeSeconds / (60))
if minutes > 0 {
timeParts = append(timeParts, fmt.Sprintf("%dm", int64(minutes)))
}
timeSeconds -= minutes * (60)
return strings.Join(timeParts[:], " ")
}

View File

@ -54,8 +54,7 @@ type Issue struct {
Attachments []*Attachment `json:"assets"`
Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"`
TimeEstimateHours int64 `json:"time_estimate_hours"`
TimeEstimateMinutes int64 `json:"time_estimate_minutes"`
TimeEstimate int `json:"time_estimate"`
// deprecated
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
@ -110,8 +109,7 @@ type EditIssueOption struct {
// swagger:strfmt date-time
Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
TimeEstimateHours int `json:"time_estimate_hours"`
TimeEstimateMinutes int `json:"time_estimate_minutes"`
TimeEstimate *string `json:"time_estimate"`
}
// EditDeadlineOption options for creating a deadline

View File

@ -1302,7 +1302,8 @@ issues.remove_assignee_at = `was unassigned by <b>%s</b> %s`
issues.remove_self_assignment = `removed their assignment %s`
issues.change_title_at = `changed title from <b><strike>%s</strike></b> to <b>%s</b> %s`
issues.time_estimate = `Time Estimate`
issues.change_time_estimate_at = `changed time estimate to <b>%d hour %d minutes</b> %s`
issues.add_time_estimate = `3w 4d 12h`
issues.change_time_estimate_at = `changed time estimate to <b>%s</b> %s`
issues.remove_time_estimate = `removed time estimate %s`
issues.change_ref_at = `changed reference from <b><strike>%s</strike></b> to <b>%s</b> %s`
issues.remove_ref_at = `removed reference <b>%s</b> %s`

View File

@ -1982,17 +1982,16 @@ func UpdateIssueTimeEstimate(ctx *context.Context) {
return
}
timeEstimateHours := ctx.FormInt("time_estimate_hours")
timeEstimateMinutes := ctx.FormInt("time_estimate_minutes")
total := issue.TimeEstimateFromStr(ctx.FormString("time_estimate"))
if issue.TimeEstimateHours == timeEstimateHours && issue.TimeEstimateMinutes == timeEstimateMinutes {
if issue.TimeEstimate == total {
ctx.JSON(http.StatusOK, map[string]interface{}{
"status": "ok",
})
return
}
if err := issue_service.ChangeTimeEstimate(issue, ctx.Doer, timeEstimateHours, timeEstimateMinutes); err != nil {
if err := issue_service.ChangeTimeEstimate(issue, ctx.Doer, total); err != nil {
ctx.ServerError("ChangeTimeEstimate", err)
return
}

View File

@ -34,7 +34,7 @@ func AddTimeManually(c *context.Context) {
return
}
total := time.Duration(form.Hours)*time.Hour + time.Duration(form.Minutes)*time.Minute
total := issue.TimeEstimateFromStr(form.TimeString)
if total <= 0 {
c.Flash.Error(c.Tr("repo.issues.add_time_sum_to_small"))
@ -42,7 +42,7 @@ func AddTimeManually(c *context.Context) {
return
}
if _, err := issues_model.AddTime(c.Doer, issue, int64(total.Seconds()), time.Now()); err != nil {
if _, err := issues_model.AddTime(c.Doer, issue, total, time.Now()); err != nil {
c.ServerError("AddTime", err)
return
}

View File

@ -872,8 +872,7 @@ func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) bi
// AddTimeManuallyForm form that adds spent time manually.
type AddTimeManuallyForm struct {
Hours int `binding:"Range(0,1000)"`
Minutes int `binding:"Range(0,1000)"`
TimeString string
}
// Validate validates the fields

View File

@ -61,12 +61,11 @@ func ChangeTitle(issue *issues_model.Issue, doer *user_model.User, title string)
return nil
}
// ChangeTitle changes the title of this issue, as the given user.
func ChangeTimeEstimate(issue *issues_model.Issue, doer *user_model.User, timeEstimateHours, timeEstimateMinutes int) (err error) {
issue.TimeEstimateHours = timeEstimateHours
issue.TimeEstimateMinutes = timeEstimateMinutes
// ChangeTimeEstimate changes the time estimate of this issue, as the given user.
func ChangeTimeEstimate(issue *issues_model.Issue, doer *user_model.User, timeEstimate int64) (err error) {
issue.TimeEstimate = timeEstimate
if err = issues_model.ChangeIssueTimeEstimate(issue, doer, timeEstimateHours, timeEstimateMinutes); err != nil {
if err = issues_model.ChangeIssueTimeEstimate(issue, doer, timeEstimate); err != nil {
return
}

View File

@ -799,10 +799,11 @@
{{template "shared/user/avatarlink" Dict "Context" $.Context "user" .Poster}}
<span class="text grey muted-links">
{{template "shared/user/authorlink" .Poster}}
{{if and (eq .TimeEstimateHours 0) (eq .TimeEstimateMinutes 0)}}
{{if and (eq .TimeEstimate 0)}}
{{$.locale.Tr "repo.issues.remove_time_estimate" $createdStr | Safe}}
{{else}}
{{$.locale.Tr "repo.issues.change_time_estimate_at" (.TimeEstimateHours) (.TimeEstimateMinutes) $createdStr | Safe}}
{{$timeStr := .TimeEstimateToStrTranslated $.locale}}
{{$.locale.Tr "repo.issues.change_time_estimate_at" $timeStr $createdStr | Safe}}
{{end}}
</span>
</div>

View File

@ -364,9 +364,8 @@
<form method="POST" id="set_time_estimate_form" class="gt-mt-3" action="{{.Issue.Link}}/time_estimate">
{{$.CsrfTokenHtml}}
<div class="ui action input fluid">
<input placeholder='{{.locale.Tr "repo.issues.add_time_hours"}}' type="number" min="0" value="{{ ($.Issue.TimeEstimateHours) }}" name="time_estimate_hours">
<input placeholder='{{.locale.Tr "repo.issues.add_time_minutes"}}' type="number" min="0" max="59" value="{{ ($.Issue.TimeEstimateMinutes) }}" name="time_estimate_minutes" class="ui compact">
<div class="ui input fluid">
<input name="time_estimate" placeholder='{{.locale.Tr "repo.issues.add_time_estimate"}}' value="{{ ($.Issue.TimeEstimateToStr) }}" type="text" >
</div>
<button class="ui fluid button green tooltip gt-mt-3">
{{.locale.Tr "repo.issues.save"}}
@ -396,10 +395,9 @@
<div class="ui mini modal issue-start-time-modal">
<div class="header">{{.locale.Tr "repo.issues.add_time"}}</div>
<div class="content">
<form method="POST" id="add_time_manual_form" action="{{.Issue.Link}}/times/add" class="ui action input fluid">
<form method="POST" id="add_time_manual_form" action="{{.Issue.Link}}/times/add" class="ui input fluid">
{{$.CsrfTokenHtml}}
<input placeholder='{{.locale.Tr "repo.issues.add_time_hours"}}' type="number" name="hours">
<input placeholder='{{.locale.Tr "repo.issues.add_time_minutes"}}' type="number" name="minutes" class="ui compact">
<input placeholder='{{.locale.Tr "repo.issues.add_time_estimate"}}' type="text" name="time_string">
</form>
</div>
<div class="actions">

View File

@ -643,13 +643,11 @@ export function initRepoIssueTimeEstimateEdit() {
$('#set_time_estimate_form').on('submit', function(e) {
e.preventDefault();
const timeEstimateHours = $(this).find('[name=time_estimate_hours]').val();
const timeEstimateMinutes = $(this).find('[name=time_estimate_minutes]').val();
const timeEstimate = $(this).find('[name=time_estimate]').val();
$.post($(this).attr('action'), {
_csrf: csrfToken,
time_estimate_hours: timeEstimateHours,
time_estimate_minutes: timeEstimateMinutes,
time_estimate: timeEstimate,
}).always(() => {
window.location.reload();
});