Skip to content

Close issue as archived/resolved/stale #32176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 78 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
aadd437
temp
sillyguodong Jun 8, 2023
acca287
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 15, 2023
e0a4589
dropdown button
sillyguodong Jun 16, 2023
8ffa87d
form struct add closed status
sillyguodong Jun 16, 2023
8d0d28f
delete console log
sillyguodong Jun 19, 2023
dd71557
update ChangeStatus
sillyguodong Jun 19, 2023
74e2af8
update
sillyguodong Jun 20, 2023
fa4ff21
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 20, 2023
23d466d
fix
sillyguodong Jun 21, 2023
361b8fd
delete
sillyguodong Jun 21, 2023
cde821b
delete useless id
sillyguodong Jun 21, 2023
d42d25f
duplicate ui modal
sillyguodong Jun 21, 2023
42de795
lint fix
sillyguodong Jun 21, 2023
cebbe9a
temp
sillyguodong Jun 21, 2023
7942140
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 21, 2023
28cac1a
migration
sillyguodong Jun 21, 2023
c5b34e7
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 25, 2023
fcbedc4
duplicate issue
sillyguodong Jun 25, 2023
183cd82
lint&fmt
sillyguodong Jun 25, 2023
a031a6e
transfer subscriber of original issue to duplicate issue
sillyguodong Jun 26, 2023
033e330
fix
sillyguodong Jun 26, 2023
a4257c9
default issue email template
sillyguodong Jun 26, 2023
83463c7
fix issue filter in duplicate modal
sillyguodong Jun 26, 2023
dace5ce
update
sillyguodong Jun 27, 2023
fdb2977
api
sillyguodong Jun 27, 2023
76730d3
api
sillyguodong Jun 27, 2023
369f995
unuse form in duplicate modal
sillyguodong Jun 27, 2023
3cff52d
fix
sillyguodong Jun 27, 2023
ead945b
generate swagger
sillyguodong Jun 27, 2023
28e861a
lint fix
sillyguodong Jun 27, 2023
c1d088c
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 27, 2023
322a460
Revert "Merge branch 'main' into feature/close_issue_with_state"
sillyguodong Jun 27, 2023
517073b
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 27, 2023
1514c44
fix api
sillyguodong Jun 28, 2023
6fcc8f7
delete merged
sillyguodong Jun 28, 2023
a2a2c74
fix lint
sillyguodong Jun 28, 2023
27b4722
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jun 29, 2023
944287b
fix
sillyguodong Jun 29, 2023
28b8934
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jul 4, 2023
f427385
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jul 10, 2023
1757d03
pass element instead of selector
sillyguodong Jul 10, 2023
0f5be3a
do not use keycode
sillyguodong Jul 10, 2023
edd9408
add test for excludeLabel func
sillyguodong Jul 10, 2023
ded55c9
fix
sillyguodong Jul 10, 2023
2725dd2
lint
sillyguodong Jul 10, 2023
a698533
update the ui of select duplicate issue
sillyguodong Jul 11, 2023
b20c4a2
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jul 17, 2023
9f433b9
the subscribers of the orignal issue won't cancel subscribe
sillyguodong Jul 17, 2023
059d9f2
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jul 24, 2023
88d9f11
clean
sillyguodong Jul 24, 2023
a4a458c
delete comment
sillyguodong Jul 24, 2023
11ca184
lint
sillyguodong Jul 24, 2023
0890e4b
frontend lint
sillyguodong Jul 27, 2023
0c5d196
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jul 27, 2023
c6aa993
fix
sillyguodong Jul 27, 2023
795818b
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Jul 31, 2023
5bf1e17
fix
sillyguodong Aug 1, 2023
2e2bbca
fix
sillyguodong Aug 1, 2023
a239bee
fix
sillyguodong Aug 1, 2023
ef2318a
fix misalignment
sillyguodong Aug 1, 2023
e93ece6
fix misalignment
sillyguodong Aug 1, 2023
72a1e78
not save trans key in db any more
sillyguodong Aug 1, 2023
b0a0678
restore the input of type
sillyguodong Aug 1, 2023
4a5a5aa
close_status column is no longer indexed
sillyguodong Aug 1, 2023
976e306
lint
sillyguodong Aug 1, 2023
52d654b
fix api
sillyguodong Aug 1, 2023
ed1cc0c
Merge branch 'main' into feature/close_issue_with_state
wxiaoguang Aug 3, 2023
fbb1bdd
close_status accept string in api
sillyguodong Aug 3, 2023
512023e
Merge branch 'main' into feature/close_issue_with_state
sillyguodong Aug 7, 2023
8ee7d25
Merge branch 'main' into feature/close_issue_with_state
wxiaoguang Aug 13, 2023
062e2c4
various fix
wxiaoguang Aug 13, 2023
bd6b17f
various fix
wxiaoguang Aug 13, 2023
28284ad
revert unrelated changes
wxiaoguang Aug 13, 2023
3979279
fix
wxiaoguang Aug 13, 2023
139599e
revert unrelated changes
wxiaoguang Aug 13, 2023
595af89
fine tune
wxiaoguang Aug 13, 2023
0a4c7f2
fine tune
wxiaoguang Aug 13, 2023
c845f3a
Merge branch 'feature/close_issue_with_state' of github.com:sillyguod…
lunny Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions models/issues/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,11 @@ func (c *Comment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
}

func (c *Comment) GetIssueClosedStatus() int {
n, _ := strconv.Atoi(c.Content)
return n
}

// InsertIssueComments inserts many comments of issues.
func InsertIssueComments(ctx context.Context, comments []*Comment) error {
if len(comments) == 0 {
Expand Down
3 changes: 2 additions & 1 deletion models/issues/dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func TestCreateIssueDependency(t *testing.T) {
assert.False(t, left)

// Close #2 and check again
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true)
issue2.IsClosed = true
_, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1)
assert.NoError(t, err)

left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
Expand Down
10 changes: 10 additions & 0 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ func (err ErrIssueWasClosed) Error() string {
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
}

type IssueClosedStatus int8

const (
IssueClosedStatusCommon IssueClosedStatus = iota // 0 close issue without any state.
IssueClosedStatusArchived // 1
IssueClosedStatusResolved // 2
IssueClosedStatusStale // 3
)

var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")

// Issue represents an issue or pull request of repository.
Expand Down Expand Up @@ -121,6 +130,7 @@ type Issue struct {
Assignee *user_model.User `xorm:"-"`
isAssigneeLoaded bool `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
ClosedStatus IssueClosedStatus `xorm:"NOT NULL DEFAULT 0"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
Expand Down
101 changes: 87 additions & 14 deletions models/issues/issue_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package issues
import (
"context"
"fmt"
"strconv"
"strings"

"code.gitea.io/gitea/models/db"
Expand Down Expand Up @@ -33,26 +34,27 @@ func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
return nil
}

func changeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
func changeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
// Reload the issue
currentIssue, err := GetIssueByID(ctx, issue.ID)
if err != nil {
return nil, err
}

// Nothing should be performed if current status is same as target status
if currentIssue.IsClosed == isClosed {
if !issue.IsPull {
return nil, ErrIssueWasClosed{
if currentIssue.IsClosed == issue.IsClosed {
if issue.IsPull {
return nil, ErrPullWasClosed{
ID: issue.ID,
}
}
return nil, ErrPullWasClosed{
ID: issue.ID,
if currentIssue.ClosedStatus == issue.ClosedStatus {
return nil, ErrIssueWasClosed{
ID: issue.ID,
}
}
}

issue.IsClosed = isClosed
return doChangeIssueStatus(ctx, issue, doer, isMergePull)
}

Expand All @@ -76,7 +78,7 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
issue.ClosedUnix = 0
}

if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil {
if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_status", "closed_unix"); err != nil {
return nil, err
}

Expand Down Expand Up @@ -104,30 +106,36 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use

// New action comment
cmtType := CommentTypeClose
var content string
if !issue.IsPull && issue.IsClosed {
content = strconv.Itoa(int(issue.ClosedStatus))
}

if !issue.IsClosed {
cmtType = CommentTypeReopen
} else if isMergePull {
cmtType = CommentTypeMergePull
}

return CreateComment(ctx, &CreateCommentOptions{
Type: cmtType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
Type: cmtType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
Content: content,
})
}

// ChangeIssueStatus changes issue status to open or closed.
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) {
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
if err := issue.LoadRepo(ctx); err != nil {
return nil, err
}
if err := issue.LoadPoster(ctx); err != nil {
return nil, err
}

return changeIssueStatus(ctx, issue, doer, isClosed, false)
return changeIssueStatus(ctx, issue, doer, false)
}

// ChangeIssueTitle changes the title of this issue, as the given user.
Expand Down Expand Up @@ -434,6 +442,71 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
return nil
}

// UpdateIssueByAPI updates all allowed fields of given issue.
// If the issue status is changed a statusChangeComment is returned
// similarly if the title is changed the titleChanged bool is set to true
func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
if err != nil {
return nil, false, err
}
defer committer.Close()

if err := issue.LoadRepo(ctx); err != nil {
return nil, false, fmt.Errorf("loadRepo: %w", err)
}

// Reload the issue
currentIssue, err := GetIssueByID(ctx, issue.ID)
if err != nil {
return nil, false, err
}

if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
"name", "content", "milestone_id", "priority",
"deadline_unix", "updated_unix", "is_locked").
Update(issue); err != nil {
return nil, false, err
}

titleChanged = currentIssue.Title != issue.Title
if titleChanged {
opts := &CreateCommentOptions{
Type: CommentTypeChangeTitle,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldTitle: currentIssue.Title,
NewTitle: issue.Title,
}
_, err := CreateComment(ctx, opts)
if err != nil {
return nil, false, fmt.Errorf("createComment: %w", err)
}
}

if issue.IsPull {
if currentIssue.IsClosed != issue.IsClosed {
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
if err != nil {
return nil, false, err
}
}
} else {
if currentIssue.IsClosed != issue.IsClosed || currentIssue.ClosedStatus != issue.ClosedStatus {
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
if err != nil {
return nil, false, err
}
}
}

if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
return nil, false, err
}
return statusChangeComment, titleChanged, committer.Commit()
}

// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
// if the deadline hasn't changed do nothing
Expand Down
3 changes: 2 additions & 1 deletion models/issues/issue_xref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
i3 := testCreateIssue(t, 1, 2, "title3", "content3", false)
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true)
i3.IsClosed = true
_, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d)
assert.NoError(t, err)

pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))
Expand Down
3 changes: 2 additions & 1 deletion models/issues/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,8 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
return false, err
}

if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
pr.Issue.IsClosed = true
if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true); err != nil {
return false, fmt.Errorf("Issue.changeStatus: %w", err)
}

Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@ var migrations = []Migration{
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
// v304 -> v305
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
// v305 -> v306
NewMigration("Add column of closed_status to issue table", v1_23.AddClosedStatusToIssue),
}

// GetCurrentDBVersion returns the current db version
Expand Down
18 changes: 18 additions & 0 deletions models/migrations/v1_23/v305.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_23 //nolint

import (
issues_model "code.gitea.io/gitea/models/issues"

"xorm.io/xorm"
)

func AddClosedStatusToIssue(x *xorm.Engine) error {
type Issue struct {
ClosedStatus issues_model.IssueClosedStatus `xorm:"NOT NULL DEFAULT 0"`
}

return x.Sync(new(Issue))
}
7 changes: 7 additions & 0 deletions modules/structs/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ const (
StateAll StateType = "all"
)

const (
ClosedStatusArchived = "archived"
ClosedStatusResolved = "resolved"
ClosedStatusStale = "stale"
)

// PullRequestMeta PR info if an issue is a PR
type PullRequestMeta struct {
HasMerged bool `json:"merged"`
Expand Down Expand Up @@ -113,6 +119,7 @@ type EditIssueOption struct {
// swagger:strfmt date-time
Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
ClosedStatus *string `json:"closed_status"`
}

// EditDeadlineOption options for creating a deadline
Expand Down
20 changes: 20 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,9 @@ issue.action.force_push = <b>%[1]s</b> force-pushed the <b>%[2]s</b> from %[3]s
issue.action.push_1 = <b>@%[1]s</b> pushed %[3]d commit to %[2]s
issue.action.push_n = <b>@%[1]s</b> pushed %[3]d commits to %[2]s
issue.action.close = <b>@%[1]s</b> closed #%[2]d.
issue.action.close_as_archived = <b>@%[1]s</b> closed as archived #%[2]d.
issue.action.close_as_resolved = <b>@%[1]s</b> closed as resolved #%[2]d.
issue.action.close_as_stale = <b>@%[1]s</b> closed as stale #%[2]d.
issue.action.reopen = <b>@%[1]s</b> reopened #%[2]d.
issue.action.merge = <b>@%[1]s</b> merged #%[2]d into %[3]s.
issue.action.approve = <b>@%[1]s</b> approved this pull request.
Expand Down Expand Up @@ -1575,6 +1578,9 @@ issues.reopen_comment_issue = Reopen with Comment
issues.create_comment = Comment
issues.comment.blocked_user = Cannot create or edit comment because you are blocked by the poster or repository owner.
issues.closed_at = `closed this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_as_archived_at = `closed this issue as archived <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_as_resolved_at = `closed this issue as resolved <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.closed_as_stale_at = `closed this issue as stale <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.reopened_at = `reopened this issue <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.commit_ref_at = `referenced this issue from a commit <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.ref_issue_from = `<a href="%[3]s">referenced this issue %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>`
Expand All @@ -1596,6 +1602,19 @@ issues.role.first_time_contributor = First-time contributor
issues.role.first_time_contributor_helper = This is the first contribution of this user to the repository.
issues.role.contributor = Contributor
issues.role.contributor_helper = This user has previously committed to the repository.
issues.close_as.reopen = Reopen
issues.close_as.common = Close Issue
issues.close_as.archived = Close as archived
issues.close_as.resolved = Close as resolved
issues.close_as.stale = Close as stale
issues.comment_and_close_as.reopen = Comment and Reopen
issues.comment_and_close_as.common = Comment and Close Issue
issues.comment_and_close_as.archived = Comment and Close as archived
issues.comment_and_close_as.resolved = Comment and Close as resolved
issues.comment_and_close_as.stale = Comment and Close as stale
issues.poster = Poster
issues.collaborator = Collaborator
issues.owner = Owner
issues.re_request_review=Re-request review
issues.is_stale = There have been changes to this PR since this review
issues.remove_request_review=Remove review request
Expand Down Expand Up @@ -1898,6 +1917,7 @@ pulls.update_branch_success = Branch update was successful
pulls.update_not_allowed = You are not allowed to update branch
pulls.outdated_with_base_branch = This branch is out-of-date with the base branch
pulls.close = Close Pull Request
pulls.comment_and_close = Comment and close Pull Request
pulls.closed_at = `closed this pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.reopened_at = `reopened this pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.cmd_instruction_hint = `View <a class="show-instruction">command line instructions</a>.`
Expand Down
23 changes: 22 additions & 1 deletion routers/api/v1/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,8 @@ func CreateIssue(ctx *context.APIContext) {
}

if form.Closed {
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", true); err != nil {
issue.IsClosed = form.Closed
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
Expand Down Expand Up @@ -906,6 +907,26 @@ func EditIssue(ctx *context.APIContext) {
isClosed = true
default:
ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
}

issue.ClosedStatus = issues_model.IssueClosedStatusCommon
if issue.IsClosed && form.ClosedStatus != nil {
switch *form.ClosedStatus {
case api.ClosedStatusArchived:
issue.ClosedStatus = issues_model.IssueClosedStatusArchived
case api.ClosedStatusResolved:
issue.ClosedStatus = issues_model.IssueClosedStatusResolved
case api.ClosedStatusStale:
issue.ClosedStatus = issues_model.IssueClosedStatusStale
default:
issue.ClosedStatus = issues_model.IssueClosedStatusCommon
}
}
}
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(issue, ctx.Doer)
if err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
}

Expand Down
Loading