-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Add structured close reason for issues (GitHub-compatible state_reason) #37041
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
8f27e66
38d0781
b8a1e8e
4420f5c
bb5d750
2a089bb
de63336
df7630d
1b8d370
5907148
7ade2f3
acf7503
51c336f
66eee37
bdbf3f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| // Copyright 2026 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package issues | ||
|
|
||
| import ( | ||
| "database/sql/driver" | ||
| "encoding/json" | ||
|
Check failure on line 8 in models/issues/close_reason.go
|
||
| "fmt" | ||
| ) | ||
|
|
||
| type IssueCloseReason int64 | ||
|
|
||
| const ( | ||
| IssueCloseReasonNone IssueCloseReason = iota | ||
| IssueCloseReasonCompleted | ||
| IssueCloseReasonCompletedByCommit | ||
| IssueCloseReasonCompletedByPull | ||
| IssueCloseReasonAnswered | ||
| IssueCloseReasonDuplicate | ||
| IssueCloseReasonNotPlanned | ||
| ) | ||
|
|
||
| func (r IssueCloseReason) String() string { | ||
| switch r { | ||
| case IssueCloseReasonCompleted: | ||
| return "completed" | ||
| case IssueCloseReasonCompletedByCommit: | ||
| return "completed_by_commit" | ||
| case IssueCloseReasonCompletedByPull: | ||
| return "completed_by_pull" | ||
| case IssueCloseReasonAnswered: | ||
| return "answered" | ||
| case IssueCloseReasonDuplicate: | ||
| return "duplicate" | ||
| case IssueCloseReasonNotPlanned: | ||
| return "not_planned" | ||
| default: | ||
| return "" | ||
| } | ||
| } | ||
|
|
||
| func (r IssueCloseReason) IsValid() bool { | ||
| return r >= IssueCloseReasonNone && r <= IssueCloseReasonNotPlanned | ||
| } | ||
|
|
||
| func ParseIssueCloseReason(reason string) (IssueCloseReason, error) { | ||
| switch reason { | ||
| case "": | ||
| return IssueCloseReasonNone, nil | ||
| case "completed": | ||
| return IssueCloseReasonCompleted, nil | ||
| case "completed_by_commit": | ||
| return IssueCloseReasonCompletedByCommit, nil | ||
| case "completed_by_pull": | ||
| return IssueCloseReasonCompletedByPull, nil | ||
| case "answered": | ||
| return IssueCloseReasonAnswered, nil | ||
| case "duplicate": | ||
| return IssueCloseReasonDuplicate, nil | ||
| case "not_planned": | ||
| return IssueCloseReasonNotPlanned, nil | ||
| default: | ||
| return IssueCloseReasonNone, fmt.Errorf("unknown close reason %q", reason) | ||
| } | ||
| } | ||
|
|
||
| func parseIssueCloseReasonNumber(reason int64) (IssueCloseReason, error) { | ||
| r := IssueCloseReason(reason) | ||
| if !r.IsValid() { | ||
| return IssueCloseReasonNone, fmt.Errorf("unknown close reason %d", reason) | ||
| } | ||
| return r, nil | ||
| } | ||
|
|
||
| func (r IssueCloseReason) Value() (driver.Value, error) { | ||
| if !r.IsValid() { | ||
| return nil, fmt.Errorf("unknown close reason %d", r) | ||
| } | ||
| return r.String(), nil | ||
| } | ||
|
|
||
| func (r *IssueCloseReason) Scan(src any) error { | ||
| if src == nil { | ||
| *r = IssueCloseReasonNone | ||
| return nil | ||
| } | ||
|
|
||
| switch v := src.(type) { | ||
| case string: | ||
| parsed, err := ParseIssueCloseReason(v) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *r = parsed | ||
| return nil | ||
| case []byte: | ||
| return r.Scan(string(v)) | ||
| case int64: | ||
| parsed, err := parseIssueCloseReasonNumber(v) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *r = parsed | ||
| return nil | ||
| case int: | ||
| return r.Scan(int64(v)) | ||
| default: | ||
| return fmt.Errorf("unsupported close reason type %T", src) | ||
| } | ||
| } | ||
|
|
||
| func (r IssueCloseReason) MarshalJSON() ([]byte, error) { | ||
| return json.Marshal(r.String()) | ||
| } | ||
|
|
||
| func (r *IssueCloseReason) UnmarshalJSON(data []byte) error { | ||
| if string(data) == "null" { | ||
| *r = IssueCloseReasonNone | ||
| return nil | ||
| } | ||
|
|
||
| var s string | ||
| if err := json.Unmarshal(data, &s); err == nil { | ||
| parsed, parseErr := ParseIssueCloseReason(s) | ||
| if parseErr != nil { | ||
| return parseErr | ||
| } | ||
| *r = parsed | ||
| return nil | ||
| } | ||
|
|
||
| var n int64 | ||
| if err := json.Unmarshal(data, &n); err != nil { | ||
| return err | ||
| } | ||
| parsed, err := parseIssueCloseReasonNumber(n) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| *r = parsed | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Copyright 2026 The Gitea Authors. All rights reserved. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package issues | ||
|
|
||
| import "encoding/json" | ||
|
Check failure on line 6 in models/issues/close_reason_display.go
|
||
|
|
||
| type closeReasonParam struct { | ||
| IssueIndex int64 `json:"issue_index"` | ||
| CommentID int64 `json:"comment_id"` | ||
| CommitHash string `json:"commit_hash"` | ||
| PullIndex int64 `json:"pull_index"` | ||
| } | ||
|
|
||
| func parseCloseReasonParam(param string) closeReasonParam { | ||
| if param == "" { | ||
| return closeReasonParam{} | ||
| } | ||
| var p closeReasonParam | ||
| _ = json.Unmarshal([]byte(param), &p) | ||
| return p | ||
| } | ||
|
|
||
| func normalizeCloseReason(isClosed bool, reason IssueCloseReason) string { | ||
| if isClosed && reason == IssueCloseReasonNone { | ||
| return IssueCloseReasonCompleted.String() | ||
| } | ||
| return reason.String() | ||
| } | ||
|
|
||
| func (issue *Issue) CloseReasonForDisplay() string { | ||
| return normalizeCloseReason(issue.IsClosed, issue.CloseReason) | ||
| } | ||
|
|
||
| func (issue *Issue) CloseReasonDuplicateIssueIndex() int64 { | ||
| return parseCloseReasonParam(issue.CloseReasonParam).IssueIndex | ||
| } | ||
|
|
||
| func (issue *Issue) CloseReasonAnsweredCommentID() int64 { | ||
| return parseCloseReasonParam(issue.CloseReasonParam).CommentID | ||
| } | ||
|
|
||
| func (issue *Issue) CloseReasonCommitHash() string { | ||
| return parseCloseReasonParam(issue.CloseReasonParam).CommitHash | ||
| } | ||
|
|
||
| func (issue *Issue) CloseReasonPullIndex() int64 { | ||
| return parseCloseReasonParam(issue.CloseReasonParam).PullIndex | ||
| } | ||
|
|
||
| func (comment *Comment) CloseReasonForDisplay() string { | ||
| if comment.CommentMetaData == nil { | ||
| return "" | ||
| } | ||
| return normalizeCloseReason(true, comment.CommentMetaData.CloseReason) | ||
| } | ||
|
|
||
| func (comment *Comment) CloseReasonDuplicateIssueIndex() int64 { | ||
| if comment.CommentMetaData == nil { | ||
| return 0 | ||
| } | ||
| return parseCloseReasonParam(comment.CommentMetaData.CloseReasonParam).IssueIndex | ||
| } | ||
|
|
||
| func (comment *Comment) CloseReasonAnsweredCommentID() int64 { | ||
| if comment.CommentMetaData == nil { | ||
| return 0 | ||
| } | ||
| return parseCloseReasonParam(comment.CommentMetaData.CloseReasonParam).CommentID | ||
| } | ||
|
|
||
| func (comment *Comment) CloseReasonCommitHash() string { | ||
| if comment.CommentMetaData == nil { | ||
| return "" | ||
| } | ||
| return parseCloseReasonParam(comment.CommentMetaData.CloseReasonParam).CommitHash | ||
| } | ||
|
|
||
| func (comment *Comment) CloseReasonPullIndex() int64 { | ||
| if comment.CommentMetaData == nil { | ||
| return 0 | ||
| } | ||
| return parseCloseReasonParam(comment.CommentMetaData.CloseReasonParam).PullIndex | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.