From 0ab222faaae642b028df8dfd5f7db80b078f0214 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 24 Oct 2024 19:18:45 -0700 Subject: [PATCH 1/7] Fix milestone no deadline problem. Use zero instead of 9999-12-31 --- models/issues/milestone.go | 14 ++++++++-- routers/api/v1/repo/milestone.go | 9 +++---- routers/web/repo/milestone.go | 46 +++++++++++++++++--------------- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/models/issues/milestone.go b/models/issues/milestone.go index db0312adf0057..edad2081e03f5 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -11,12 +11,14 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm" ) // ErrMilestoneNotExist represents a "MilestoneNotExist" kind of error. @@ -82,9 +84,17 @@ func (m *Milestone) BeforeUpdate() { // AfterLoad is invoked from XORM after setting the value of a field of // this object. -func (m *Milestone) AfterLoad() { +func (m *Milestone) AfterLoad(session *xorm.Session) { m.NumOpenIssues = m.NumIssues - m.NumClosedIssues - if m.DeadlineUnix.Year() == 9999 { + if m.DeadlineUnix == 0 { + return + } + if m.DeadlineUnix.Year() > 9000 { + m.DeadlineUnix = 0 + m.IsOverdue = false + if _, err := session.ID(m.ID).NoAutoTime().Cols("deadline_unix", "is_overdue").Update(m); err != nil { + log.Error("AfterLoad update legacy deadline failed: %v", err) + } return } diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index abe9e4006a8e4..67e3ed829efee 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -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" @@ -155,16 +154,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" { diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index e4ee025875ef6..a6d0df919c4d7 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -134,22 +134,24 @@ 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) - if err != nil { - ctx.Data["Err_Deadline"] = true - ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) - return + var deadlineUnix int64 + if len(form.Deadline) > 0 { + deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) + 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()) + deadlineUnix = deadline.Unix() } - 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: timeutil.TimeStamp(deadlineUnix), }); err != nil { ctx.ServerError("NewMilestone", err) return @@ -194,17 +196,19 @@ 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) - if err != nil { - ctx.Data["Err_Deadline"] = true - ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) - return + var deadlineUnix int64 + if len(form.Deadline) > 0 { + deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) + 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()) + deadlineUnix = deadline.Unix() } - 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 +220,7 @@ func EditMilestonePost(ctx *context.Context) { } m.Name = form.Title m.Content = form.Content - m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix()) + m.DeadlineUnix = timeutil.TimeStamp(deadlineUnix) if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil { ctx.ServerError("UpdateMilestone", err) return From bd74d3555cbe1596530aec410ab97f7a3527a7c2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 25 Oct 2024 14:11:59 -0700 Subject: [PATCH 2/7] Add test and fix problem for API routers --- models/issues/milestone.go | 1 + services/convert/issue.go | 5 ++++- tests/integration/api_issue_milestone_test.go | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/models/issues/milestone.go b/models/issues/milestone.go index edad2081e03f5..7c9f6b564a9b7 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -89,6 +89,7 @@ func (m *Milestone) AfterLoad(session *xorm.Session) { if m.DeadlineUnix == 0 { return } + // for legacy reasons, all years after 9000 are considered as no deadline if m.DeadlineUnix.Year() > 9000 { m.DeadlineUnix = 0 m.IsOverdue = false diff --git a/services/convert/issue.go b/services/convert/issue.go index f514dc431351e..524d3cda5336b 100644 --- a/services/convert/issue.go +++ b/services/convert/issue.go @@ -260,7 +260,10 @@ func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone { if m.IsClosed { apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr() } - if m.DeadlineUnix.Year() < 9999 { + // for legacy reasons, all years after 9000 are considered as no deadline + if m.DeadlineUnix.Year() > 9000 || m.DeadlineUnix == 0 { + apiMilestone.Deadline = nil + } else { apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr() } return apiMilestone diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go index 32ac56298fab4..38f36d27a74c1 100644 --- a/tests/integration/api_issue_milestone_test.go +++ b/tests/integration/api_issue_milestone_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" issues_model "code.gitea.io/gitea/models/issues" @@ -59,6 +60,7 @@ func TestAPIIssuesMilestone(t *testing.T) { DecodeJSON(t, resp, &apiMilestone) assert.Equal(t, "wow", apiMilestone.Title) assert.Equal(t, structs.StateClosed, apiMilestone.State) + assert.Equal(t, (*time.Time)(nil), 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 +68,7 @@ func TestAPIIssuesMilestone(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestones) assert.Len(t, apiMilestones, 4) + assert.Equal(t, (*time.Time)(nil), 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) From 6d0f84029ca6af991b8a161c4834688d07596df9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 29 Oct 2024 22:01:59 -0700 Subject: [PATCH 3/7] Add migration for milestone deadline_unix column --- models/issues/milestone.go | 14 +------------- models/migrations/migrations.go | 1 + models/migrations/v1_23/v307.go | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 models/migrations/v1_23/v307.go diff --git a/models/issues/milestone.go b/models/issues/milestone.go index 7c9f6b564a9b7..4c9bae58f7d40 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -11,14 +11,12 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "xorm.io/builder" - "xorm.io/xorm" ) // ErrMilestoneNotExist represents a "MilestoneNotExist" kind of error. @@ -84,21 +82,11 @@ func (m *Milestone) BeforeUpdate() { // AfterLoad is invoked from XORM after setting the value of a field of // this object. -func (m *Milestone) AfterLoad(session *xorm.Session) { +func (m *Milestone) AfterLoad() { m.NumOpenIssues = m.NumIssues - m.NumClosedIssues if m.DeadlineUnix == 0 { return } - // for legacy reasons, all years after 9000 are considered as no deadline - if m.DeadlineUnix.Year() > 9000 { - m.DeadlineUnix = 0 - m.IsOverdue = false - if _, err := session.ID(m.ID).NoAutoTime().Cols("deadline_unix", "is_overdue").Update(m); err != nil { - log.Error("AfterLoad update legacy deadline failed: %v", err) - } - return - } - m.DeadlineString = m.DeadlineUnix.FormatDate() if m.IsClosed { m.IsOverdue = m.ClosedDateUnix >= m.DeadlineUnix diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index ddf20d9542cfe..41f337b838ad6 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -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 } diff --git a/models/migrations/v1_23/v307.go b/models/migrations/v1_23/v307.go new file mode 100644 index 0000000000000..ef7f5f2c3f486 --- /dev/null +++ b/models/migrations/v1_23/v307.go @@ -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 +} From efc2d201fb737beeac7962242127375dccfb2d29 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 29 Oct 2024 22:05:32 -0700 Subject: [PATCH 4/7] Fix legacy detection --- services/convert/issue.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/convert/issue.go b/services/convert/issue.go index 524d3cda5336b..e3124efd6402e 100644 --- a/services/convert/issue.go +++ b/services/convert/issue.go @@ -260,10 +260,7 @@ func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone { if m.IsClosed { apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr() } - // for legacy reasons, all years after 9000 are considered as no deadline - if m.DeadlineUnix.Year() > 9000 || m.DeadlineUnix == 0 { - apiMilestone.Deadline = nil - } else { + if m.DeadlineUnix > 0 { apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr() } return apiMilestone From 4d7d0051e12af7934d8945bdf9f45fc629bf5fec Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 5 Nov 2024 12:00:12 +0800 Subject: [PATCH 5/7] fix --- models/actions/schedule_spec_test.go | 8 ++- routers/api/v1/repo/issue.go | 12 ++--- routers/api/v1/repo/milestone.go | 5 +- routers/common/deadline.go | 31 +++++++++++ routers/web/repo/issue.go | 15 ++---- routers/web/repo/milestone.go | 39 +++++--------- routers/web/web.go | 2 +- templates/repo/issue/milestone_new.tmpl | 4 +- .../repo/issue/view_content/sidebar.tmpl | 53 +++++++------------ tests/integration/api_issue_milestone_test.go | 5 +- web_src/js/features/repo-issue-sidebar.ts | 15 ++++++ web_src/js/features/repo-issue.ts | 46 ---------------- web_src/js/features/repo-milestone.ts | 16 +++--- web_src/js/index.ts | 2 - 14 files changed, 102 insertions(+), 151 deletions(-) create mode 100644 routers/common/deadline.go diff --git a/models/actions/schedule_spec_test.go b/models/actions/schedule_spec_test.go index 0c26fce4b2c5e..57221461dfec5 100644 --- a/models/actions/schedule_spec_test.go +++ b/models/actions/schedule_spec_test.go @@ -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) diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index e86fb3ccb1c52..cbe709c030db4 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -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()}) } diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go index 67e3ed829efee..78907c85a5847 100644 --- a/routers/api/v1/repo/milestone.go +++ b/routers/api/v1/repo/milestone.go @@ -15,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" ) @@ -224,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 { diff --git a/routers/common/deadline.go b/routers/common/deadline.go new file mode 100644 index 0000000000000..152e94597bc6a --- /dev/null +++ b/routers/common/deadline.go @@ -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 +} diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 507b5af9d904a..21eb71e9f3393 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -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 diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index a6d0df919c4d7..5c0972188cecf 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -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,24 +133,18 @@ func NewMilestonePost(ctx *context.Context) { return } - var deadlineUnix int64 - if len(form.Deadline) > 0 { - deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) - 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()) - deadlineUnix = deadline.Unix() + 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 } if err := issues_model.NewMilestone(ctx, &issues_model.Milestone{ RepoID: ctx.Repo.Repository.ID, Name: form.Title, Content: form.Content, - DeadlineUnix: timeutil.TimeStamp(deadlineUnix), + DeadlineUnix: deadlineUnix, }); err != nil { ctx.ServerError("NewMilestone", err) return @@ -196,17 +189,11 @@ func EditMilestonePost(ctx *context.Context) { return } - var deadlineUnix int64 - if len(form.Deadline) > 0 { - deadline, err := time.ParseInLocation("2006-01-02", form.Deadline, time.Local) - 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()) - deadlineUnix = deadline.Unix() + 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 } m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id")) @@ -220,7 +207,7 @@ func EditMilestonePost(ctx *context.Context) { } m.Name = form.Title m.Content = form.Content - m.DeadlineUnix = timeutil.TimeStamp(deadlineUnix) + m.DeadlineUnix = deadlineUnix if err = issues_model.UpdateMilestone(ctx, m, m.IsClosed); err != nil { ctx.ServerError("UpdateMilestone", err) return diff --git a/routers/web/web.go b/routers/web/web.go index 83d116babd769..ebedea73f1907 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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) diff --git a/templates/repo/issue/milestone_new.tmpl b/templates/repo/issue/milestone_new.tmpl index 9f32df00e39f0..736a75d73a1b3 100644 --- a/templates/repo/issue/milestone_new.tmpl +++ b/templates/repo/issue/milestone_new.tmpl @@ -30,9 +30,9 @@
- +
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index c168e98785eda..a89a2bb17e439 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -358,44 +358,31 @@
{{ctx.Locale.Tr "repo.issues.due_date"}} -
-
- {{svg "octicon-x" 16 "close icon"}} - {{ctx.Locale.Tr "repo.issues.due_date_invalid"}} -
- {{if ne .Issue.DeadlineUnix 0}} -

-

-
- {{svg "octicon-calendar" 16 "tw-mr-2"}} - {{DateUtils.AbsoluteLong .Issue.DeadlineUnix}} -
-
- {{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}} - {{svg "octicon-pencil" 16 "tw-mr-1"}} - {{svg "octicon-trash"}} - {{end}} -
+
+ {{if .Issue.DeadlineUnix}} +
+
+ {{svg "octicon-calendar"}} {{DateUtils.AbsoluteLong .Issue.DeadlineUnix}} +
+
+ {{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}} + {{svg "octicon-pencil"}} + {{svg "octicon-trash"}} + {{end}}
-

+
{{else}} -

{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}

+ {{ctx.Locale.Tr "repo.issues.due_date_not_set"}} {{end}} {{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}} -
-
- {{$.CsrfTokenHtml}} - - -
-
+
+ {{$.CsrfTokenHtml}} + + +
{{end}}
diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go index 38f36d27a74c1..2d00752302ff2 100644 --- a/tests/integration/api_issue_milestone_test.go +++ b/tests/integration/api_issue_milestone_test.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" "testing" - "time" auth_model "code.gitea.io/gitea/models/auth" issues_model "code.gitea.io/gitea/models/issues" @@ -60,7 +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.Equal(t, (*time.Time)(nil), apiMilestone.Deadline) + 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")). @@ -68,7 +67,7 @@ func TestAPIIssuesMilestone(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestones) assert.Len(t, apiMilestones, 4) - assert.Equal(t, (*time.Time)(nil), apiMilestones[0].Deadline) + 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) diff --git a/web_src/js/features/repo-issue-sidebar.ts b/web_src/js/features/repo-issue-sidebar.ts index f33e192f291a4..0d30d8103c19f 100644 --- a/web_src/js/features/repo-issue-sidebar.ts +++ b/web_src/js/features/repo-issue-sidebar.ts @@ -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('.issue-due-form'); + if (!form) return; + const deadline = form.querySelector('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'); diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index a9a65cdc819bc..392af776f8804 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -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 */ diff --git a/web_src/js/features/repo-milestone.ts b/web_src/js/features/repo-milestone.ts index ddef723b48c85..ee704ea2e06a4 100644 --- a/web_src/js/features/repo-milestone.ts +++ b/web_src/js/features/repo-milestone.ts @@ -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('form input[name=deadline]'); + document.querySelector('#milestone-clear-deadline').addEventListener('click', () => { + deadline.value = ''; + }); } diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 08d8997fd1816..487aac97aa3a5 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -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, From 54e515742512b9176fe36e6f38596e154fb74f9c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 5 Nov 2024 13:32:21 +0800 Subject: [PATCH 6/7] fix test --- tests/integration/issue_test.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index df45da84a55de..4617c5f89a149 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -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) - - 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) + 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()) - 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) { From 9c8ce0251a993dd8620e749a7a31e7a9c694f012 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 5 Nov 2024 13:49:27 +0800 Subject: [PATCH 7/7] fix time --- modules/gitgraph/graph.go | 2 +- modules/gitgraph/graph_models.go | 13 +++++++++++-- modules/templates/util_date.go | 2 +- templates/repo/graph/commits.tmpl | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/modules/gitgraph/graph.go b/modules/gitgraph/graph.go index 331ad6b218de9..7e12be030fb35 100644 --- a/modules/gitgraph/graph.go +++ b/modules/gitgraph/graph.go @@ -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) diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go index e48fef8b9d0cc..191b0b3afce9b 100644 --- a/modules/gitgraph/graph_models.go +++ b/modules/gitgraph/graph_models.go @@ -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 } diff --git a/modules/templates/util_date.go b/modules/templates/util_date.go index b9e04401f15f5..f521b39b79dc7 100644 --- a/modules/templates/util_date.go +++ b/modules/templates/util_date.go @@ -28,7 +28,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 diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl index 1691fb8d45084..dc306d5ddb6fc 100644 --- a/templates/repo/graph/commits.tmpl +++ b/templates/repo/graph/commits.tmpl @@ -56,7 +56,7 @@ {{end}} {{end}} - + {{$userName := $commit.Commit.Author.Name}} {{if $commit.User}} {{if and $commit.User.FullName DefaultShowFullName}}