mirror of
https://github.com/go-gitea/gitea
synced 2024-11-14 01:35:54 +01:00
Fix milestone deadline and date related problems (#32339)
Use zero instead of 9999-12-31 for deadline Fix #32291 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
parent
1887c75c35
commit
24b83ff63e
@ -7,19 +7,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestActionScheduleSpec_Parse(t *testing.T) {
|
||||
// Mock the local timezone is not UTC
|
||||
local := time.Local
|
||||
tz, err := time.LoadLocation("Asia/Shanghai")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
time.Local = local
|
||||
}()
|
||||
time.Local = tz
|
||||
defer test.MockVariableValue(&time.Local, tz)()
|
||||
|
||||
now, err := time.Parse(time.RFC3339, "2024-07-31T15:47:55+08:00")
|
||||
require.NoError(t, err)
|
||||
|
@ -84,10 +84,9 @@ func (m *Milestone) BeforeUpdate() {
|
||||
// this object.
|
||||
func (m *Milestone) AfterLoad() {
|
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||
if m.DeadlineUnix.Year() == 9999 {
|
||||
if m.DeadlineUnix == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
m.DeadlineString = m.DeadlineUnix.FormatDate()
|
||||
if m.IsClosed {
|
||||
m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix
|
||||
|
@ -364,6 +364,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(304, "Add index for release sha1", v1_23.AddIndexForReleaseSha1),
|
||||
newMigration(305, "Add Repository Licenses", v1_23.AddRepositoryLicenses),
|
||||
newMigration(306, "Add BlockAdminMergeOverride to ProtectedBranch", v1_23.AddBlockAdminMergeOverrideBranchProtection),
|
||||
newMigration(307, "Fix milestone deadline_unix when there is no due date", v1_23.FixMilestoneNoDueDate),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
21
models/migrations/v1_23/v307.go
Normal file
21
models/migrations/v1_23/v307.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func FixMilestoneNoDueDate(x *xorm.Engine) error {
|
||||
type Milestone struct {
|
||||
DeadlineUnix timeutil.TimeStamp
|
||||
}
|
||||
// Wednesday, December 1, 9999 12:00:00 AM GMT+00:00
|
||||
_, err := x.Table("milestone").Where("deadline_unix > 253399622400").
|
||||
Cols("deadline_unix").
|
||||
Update(&Milestone{DeadlineUnix: 0})
|
||||
return err
|
||||
}
|
@ -32,7 +32,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
graphCmd.AddArguments("--all")
|
||||
}
|
||||
|
||||
graphCmd.AddArguments("-C", "-M", "--date=iso").
|
||||
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").
|
||||
AddOptionFormat("-n %d", setting.UI.GraphMaxCommitNum*page).
|
||||
AddOptionFormat("--pretty=format:%s", format)
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -192,6 +193,14 @@ var RelationCommit = &Commit{
|
||||
Row: -1,
|
||||
}
|
||||
|
||||
func parseGitTime(timeStr string) time.Time {
|
||||
t, err := time.Parse(time.RFC3339, timeStr)
|
||||
if err != nil {
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// NewCommit creates a new commit from a provided line
|
||||
func NewCommit(row, column int, line []byte) (*Commit, error) {
|
||||
data := bytes.SplitN(line, []byte("|"), 5)
|
||||
@ -206,7 +215,7 @@ func NewCommit(row, column int, line []byte) (*Commit, error) {
|
||||
// 1 matches git log --pretty=format:%H => commit hash
|
||||
Rev: string(data[1]),
|
||||
// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
|
||||
Date: string(data[2]),
|
||||
Date: parseGitTime(string(data[2])),
|
||||
// 3 matches git log --pretty=format:%h => abbreviated commit hash
|
||||
ShortRev: string(data[3]),
|
||||
// 4 matches git log --pretty=format:%s => subject
|
||||
@ -245,7 +254,7 @@ type Commit struct {
|
||||
Column int
|
||||
Refs []git.Reference
|
||||
Rev string
|
||||
Date string
|
||||
Date time.Time
|
||||
ShortRev string
|
||||
Subject string
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func (du *DateUtils) AbsoluteShort(time any) template.HTML {
|
||||
|
||||
// AbsoluteLong renders in "January 01, 2006" format
|
||||
func (du *DateUtils) AbsoluteLong(time any) template.HTML {
|
||||
return dateTimeFormat("short", time)
|
||||
return dateTimeFormat("long", time)
|
||||
}
|
||||
|
||||
// FullTime renders in "Jan 01, 2006 20:33:44" format
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
@ -1046,18 +1047,11 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var deadlineUnix timeutil.TimeStamp
|
||||
var deadline time.Time
|
||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
||||
deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||
23, 59, 59, 0, time.Local)
|
||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
}
|
||||
|
||||
deadlineUnix, _ := common.ParseAPIDeadlineToEndOfDay(form.Deadline)
|
||||
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
|
||||
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: deadlineUnix.AsTimePtr()})
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
@ -16,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
@ -155,16 +155,16 @@ func CreateMilestone(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
form := web.GetForm(ctx).(*api.CreateMilestoneOption)
|
||||
|
||||
if form.Deadline == nil {
|
||||
defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
|
||||
form.Deadline = &defaultDeadline
|
||||
var deadlineUnix int64
|
||||
if form.Deadline != nil {
|
||||
deadlineUnix = form.Deadline.Unix()
|
||||
}
|
||||
|
||||
milestone := &issues_model.Milestone{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: form.Title,
|
||||
Content: form.Description,
|
||||
DeadlineUnix: timeutil.TimeStamp(form.Deadline.Unix()),
|
||||
DeadlineUnix: timeutil.TimeStamp(deadlineUnix),
|
||||
}
|
||||
|
||||
if form.State == "closed" {
|
||||
@ -225,9 +225,7 @@ func EditMilestone(ctx *context.APIContext) {
|
||||
if form.Description != nil {
|
||||
milestone.Content = *form.Description
|
||||
}
|
||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
||||
milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
|
||||
}
|
||||
milestone.DeadlineUnix, _ = common.ParseAPIDeadlineToEndOfDay(form.Deadline)
|
||||
|
||||
oldIsClosed := milestone.IsClosed
|
||||
if form.State != nil {
|
||||
|
31
routers/common/deadline.go
Normal file
31
routers/common/deadline.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
func ParseDeadlineDateToEndOfDay(date string) (timeutil.TimeStamp, error) {
|
||||
if date == "" {
|
||||
return 0, nil
|
||||
}
|
||||
deadline, err := time.ParseInLocation("2006-01-02", date, setting.DefaultUILocation)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
|
||||
return timeutil.TimeStamp(deadline.Unix()), nil
|
||||
}
|
||||
|
||||
func ParseAPIDeadlineToEndOfDay(t *time.Time) (timeutil.TimeStamp, error) {
|
||||
if t == nil || t.IsZero() || t.Unix() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
deadline := time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, setting.DefaultUILocation)
|
||||
return timeutil.TimeStamp(deadline.Unix()), nil
|
||||
}
|
@ -17,7 +17,6 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -45,9 +44,9 @@ import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/templates/vars"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
@ -2329,7 +2328,6 @@ func UpdateIssueContent(ctx *context.Context) {
|
||||
|
||||
// UpdateIssueDeadline updates an issue deadline
|
||||
func UpdateIssueDeadline(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*api.EditDeadlineOption)
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrIssueNotExist(err) {
|
||||
@ -2345,20 +2343,13 @@ func UpdateIssueDeadline(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var deadlineUnix timeutil.TimeStamp
|
||||
var deadline time.Time
|
||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
||||
deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||
23, 59, 59, 0, time.Local)
|
||||
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
}
|
||||
|
||||
deadlineUnix, _ := common.ParseDeadlineDateToEndOfDay(ctx.FormString("deadline"))
|
||||
if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
|
||||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
||||
// UpdateIssueMilestone change issue's milestone
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
@ -16,8 +15,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/issue"
|
||||
@ -134,22 +133,18 @@ func NewMilestonePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Deadline) == 0 {
|
||||
form.Deadline = "9999-12-31"
|
||||
}
|
||||
deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
|
||||
deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline)
|
||||
if err != nil {
|
||||
ctx.Data["Err_Deadline"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
|
||||
return
|
||||
}
|
||||
|
||||
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
|
||||
if err = issues_model.NewMilestone(ctx, &issues_model.Milestone{
|
||||
if err := issues_model.NewMilestone(ctx, &issues_model.Milestone{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: form.Title,
|
||||
Content: form.Content,
|
||||
DeadlineUnix: timeutil.TimeStamp(deadline.Unix()),
|
||||
DeadlineUnix: deadlineUnix,
|
||||
}); err != nil {
|
||||
ctx.ServerError("NewMilestone", err)
|
||||
return
|
||||
@ -194,17 +189,13 @@ func EditMilestonePost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Deadline) == 0 {
|
||||
form.Deadline = "9999-12-31"
|
||||
}
|
||||
deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local)
|
||||
deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline)
|
||||
if err != nil {
|
||||
ctx.Data["Err_Deadline"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form)
|
||||
return
|
||||
}
|
||||
|
||||
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
|
||||
m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrMilestoneNotExist(err) {
|
||||
@ -216,7 +207,7 @@ func EditMilestonePost(ctx *context.Context) {
|
||||
}
|
||||
m.Name = form.Title
|
||||
m.Content = form.Content
|
||||
m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||
m.DeadlineUnix = deadlineUnix
|
||||
if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil {
|
||||
ctx.ServerError("UpdateMilestone", err)
|
||||
return
|
||||
|
@ -1208,7 +1208,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Group("/{index}", func() {
|
||||
m.Post("/title", repo.UpdateIssueTitle)
|
||||
m.Post("/content", repo.UpdateIssueContent)
|
||||
m.Post("/deadline", web.Bind(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
||||
m.Post("/deadline", repo.UpdateIssueDeadline)
|
||||
m.Post("/watch", repo.IssueWatch)
|
||||
m.Post("/ref", repo.UpdateIssueRef)
|
||||
m.Post("/pin", reqRepoAdmin, repo.IssuePinOrUnpin)
|
||||
|
@ -260,7 +260,7 @@ func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone {
|
||||
if m.IsClosed {
|
||||
apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr()
|
||||
}
|
||||
if m.DeadlineUnix.Year() < 9999 {
|
||||
if m.DeadlineUnix > 0 {
|
||||
apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr()
|
||||
}
|
||||
return apiMilestone
|
||||
|
@ -56,7 +56,7 @@
|
||||
{{end}}
|
||||
{{end}}
|
||||
</span>
|
||||
<span class="author tw-flex tw-items-center tw-mr-2 tw-gap-[1px]">
|
||||
<span class="author tw-flex tw-items-center tw-mr-2 tw-gap-1">
|
||||
{{$userName := $commit.Commit.Author.Name}}
|
||||
{{if $commit.User}}
|
||||
{{if and $commit.User.FullName DefaultShowFullName}}
|
||||
|
@ -30,9 +30,9 @@
|
||||
<div class="field {{if .Err_Deadline}}error{{end}}">
|
||||
<label>
|
||||
{{ctx.Locale.Tr "repo.milestones.due_date"}}
|
||||
<a id="clear-date">{{ctx.Locale.Tr "repo.milestones.clear"}}</a>
|
||||
<a id="milestone-clear-deadline">{{ctx.Locale.Tr "repo.milestones.clear"}}</a>
|
||||
</label>
|
||||
<input type="date" id="deadline" name="deadline" value="{{.deadline}}" placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}">
|
||||
<input type="date" name="deadline" class="tw-w-auto" value="{{.deadline}}" placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.milestones.desc"}}</label>
|
||||
|
@ -358,44 +358,31 @@
|
||||
|
||||
<div class="divider"></div>
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.due_date"}}</strong></span>
|
||||
<div class="ui form" id="deadline-loader">
|
||||
<div class="ui negative message tw-hidden" id="deadline-err-invalid-date">
|
||||
{{svg "octicon-x" 16 "close icon"}}
|
||||
{{ctx.Locale.Tr "repo.issues.due_date_invalid"}}
|
||||
</div>
|
||||
{{if ne .Issue.DeadlineUnix 0}}
|
||||
<p>
|
||||
<div class="tw-flex tw-justify-between tw-items-center">
|
||||
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
||||
{{svg "octicon-calendar" 16 "tw-mr-2"}}
|
||||
{{DateUtils.AbsoluteLong .Issue.DeadlineUnix}}
|
||||
</div>
|
||||
<div>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil" 16 "tw-mr-1"}}</a>
|
||||
<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui form tw-mt-2">
|
||||
{{if .Issue.DeadlineUnix}}
|
||||
<div class="tw-flex tw-justify-between tw-items-center tw-gap-2">
|
||||
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
||||
{{svg "octicon-calendar"}} {{DateUtils.AbsoluteLong .Issue.DeadlineUnix}}
|
||||
</div>
|
||||
</p>
|
||||
<div class="flex-text-block">
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil"}}</a>
|
||||
<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<p>{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}</p>
|
||||
{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}
|
||||
{{end}}
|
||||
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<div {{if ne .Issue.DeadlineUnix 0}} class="tw-hidden"{{end}} id="deadlineForm">
|
||||
<form class="ui fluid action input issue-due-form" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline" method="post" id="update-issue-deadline-form">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input required placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
|
||||
<button class="ui icon button">
|
||||
{{if ne .Issue.DeadlineUnix 0}}
|
||||
{{svg "octicon-pencil"}}
|
||||
{{else}}
|
||||
{{svg "octicon-plus"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<form class="ui fluid action input issue-due-form form-fetch-action tw-mt-2 {{if .Issue.DeadlineUnix}}tw-hidden{{end}}"
|
||||
method="post" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline"
|
||||
>
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input required type="date" name="deadline" placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if .Issue.DeadlineUnix}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}}>
|
||||
<button class="ui icon button">{{Iif .Issue.DeadlineUnix (svg "octicon-pencil") (svg "octicon-plus")}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
@ -59,6 +59,7 @@ func TestAPIIssuesMilestone(t *testing.T) {
|
||||
DecodeJSON(t, resp, &apiMilestone)
|
||||
assert.Equal(t, "wow", apiMilestone.Title)
|
||||
assert.Equal(t, structs.StateClosed, apiMilestone.State)
|
||||
assert.Nil(t, apiMilestone.Deadline)
|
||||
|
||||
var apiMilestones []structs.Milestone
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s", owner.Name, repo.Name, "all")).
|
||||
@ -66,6 +67,7 @@ func TestAPIIssuesMilestone(t *testing.T) {
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &apiMilestones)
|
||||
assert.Len(t, apiMilestones, 4)
|
||||
assert.Nil(t, apiMilestones[0].Deadline)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%s", owner.Name, repo.Name, apiMilestones[2].Title)).
|
||||
AddTokenAuth(token)
|
||||
|
@ -657,26 +657,21 @@ func TestUpdateIssueDeadline(t *testing.T) {
|
||||
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
|
||||
assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
|
||||
assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
|
||||
assert.Equal(t, "2002-04-20", issueBefore.DeadlineUnix.FormatDate())
|
||||
assert.Equal(t, api.StateOpen, issueBefore.State())
|
||||
|
||||
session := loginUser(t, owner.Name)
|
||||
urlStr := fmt.Sprintf("%s/%s/issues/%d/deadline?_csrf=%s", owner.Name, repoBefore.Name, issueBefore.Index, GetUserCSRFToken(t, session))
|
||||
|
||||
issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
|
||||
req := NewRequest(t, "GET", issueURL)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
|
||||
assert.EqualValues(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate())
|
||||
|
||||
urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF()
|
||||
req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{
|
||||
"due_date": "2022-04-06T00:00:00.000Z",
|
||||
})
|
||||
|
||||
resp = session.MakeRequest(t, req, http.StatusCreated)
|
||||
var apiIssue api.IssueDeadline
|
||||
DecodeJSON(t, resp, &apiIssue)
|
||||
|
||||
assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
|
||||
req = NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": ""})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
issueAfter = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
|
||||
assert.True(t, issueAfter.DeadlineUnix.IsZero())
|
||||
}
|
||||
|
||||
func TestIssueReferenceURL(t *testing.T) {
|
||||
|
@ -3,6 +3,7 @@ import {POST} from '../modules/fetch.ts';
|
||||
import {updateIssuesMeta} from './repo-common.ts';
|
||||
import {svg} from '../svg.ts';
|
||||
import {htmlEscape} from 'escape-goat';
|
||||
import {toggleElem} from '../utils/dom.ts';
|
||||
|
||||
// if there are draft comments, confirm before reloading, to avoid losing comments
|
||||
function reloadConfirmDraftComment() {
|
||||
@ -258,8 +259,22 @@ function selectItem(select_id, input_id) {
|
||||
});
|
||||
}
|
||||
|
||||
function initRepoIssueDue() {
|
||||
const form = document.querySelector<HTMLFormElement>('.issue-due-form');
|
||||
if (!form) return;
|
||||
const deadline = form.querySelector<HTMLInputElement>('input[name=deadline]');
|
||||
document.querySelector('.issue-due-edit')?.addEventListener('click', () => {
|
||||
toggleElem(form);
|
||||
});
|
||||
document.querySelector('.issue-due-remove')?.addEventListener('click', () => {
|
||||
deadline.value = '';
|
||||
form.dispatchEvent(new Event('submit', {cancelable: true, bubbles: true}));
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoIssueSidebar() {
|
||||
initBranchSelector();
|
||||
initRepoIssueDue();
|
||||
|
||||
// Init labels and assignees
|
||||
initListSubmits('select-label', 'labels');
|
||||
|
@ -43,52 +43,6 @@ export function initRepoIssueTimeTracking() {
|
||||
});
|
||||
}
|
||||
|
||||
async function updateDeadline(deadlineString) {
|
||||
hideElem('#deadline-err-invalid-date');
|
||||
document.querySelector('#deadline-loader')?.classList.add('is-loading');
|
||||
|
||||
let realDeadline = null;
|
||||
if (deadlineString !== '') {
|
||||
const newDate = Date.parse(deadlineString);
|
||||
|
||||
if (Number.isNaN(newDate)) {
|
||||
document.querySelector('#deadline-loader')?.classList.remove('is-loading');
|
||||
showElem('#deadline-err-invalid-date');
|
||||
return false;
|
||||
}
|
||||
realDeadline = new Date(newDate);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await POST(document.querySelector('#update-issue-deadline-form').getAttribute('action'), {
|
||||
data: {due_date: realDeadline},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
throw new Error('Invalid response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
document.querySelector('#deadline-loader').classList.remove('is-loading');
|
||||
showElem('#deadline-err-invalid-date');
|
||||
}
|
||||
}
|
||||
|
||||
export function initRepoIssueDue() {
|
||||
$(document).on('click', '.issue-due-edit', () => {
|
||||
toggleElem('#deadlineForm');
|
||||
});
|
||||
$(document).on('click', '.issue-due-remove', () => {
|
||||
updateDeadline('');
|
||||
});
|
||||
$(document).on('submit', '.issue-due-form', () => {
|
||||
updateDeadline($('#deadlineDate').val());
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} item
|
||||
*/
|
||||
|
@ -1,11 +1,9 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
export function initRepoMilestone() {
|
||||
// Milestones
|
||||
if ($('.repository.new.milestone').length > 0) {
|
||||
$('#clear-date').on('click', () => {
|
||||
$('#deadline').val('');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
const page = document.querySelector('.repository.new.milestone');
|
||||
if (!page) return;
|
||||
|
||||
const deadline = page.querySelector<HTMLInputElement>('form input[name=deadline]');
|
||||
document.querySelector('#milestone-clear-deadline').addEventListener('click', () => {
|
||||
deadline.value = '';
|
||||
});
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import {initPdfViewer} from './render/pdf.ts';
|
||||
|
||||
import {initUserAuthOauth2, initUserCheckAppUrl} from './features/user-auth.ts';
|
||||
import {
|
||||
initRepoIssueDue,
|
||||
initRepoIssueReferenceRepositorySearch,
|
||||
initRepoIssueTimeTracking,
|
||||
initRepoIssueWipTitle,
|
||||
@ -181,7 +180,6 @@ onDomReady(() => {
|
||||
initRepoEditor,
|
||||
initRepoGraphGit,
|
||||
initRepoIssueContentHistory,
|
||||
initRepoIssueDue,
|
||||
initRepoIssueList,
|
||||
initRepoIssueSidebarList,
|
||||
initArchivedLabelHandler,
|
||||
|
Loading…
Reference in New Issue
Block a user