Skip to content

Add structured close reason for issues (GitHub-compatible state_reason)#37041

Draft
a1012112796 wants to merge 15 commits intogo-gitea:mainfrom
a1012112796:zzc/dev/issue_close_reason
Draft

Add structured close reason for issues (GitHub-compatible state_reason)#37041
a1012112796 wants to merge 15 commits intogo-gitea:mainfrom
a1012112796:zzc/dev/issue_close_reason

Conversation

@a1012112796
Copy link
Copy Markdown
Member

@a1012112796 a1012112796 commented Mar 30, 2026

Closes can now carry one of six structured reasons: completed, not_planned, duplicate, answered, completed_by_commit (system), and completed_by_pull (system). The last two are written automatically by the commit-reference and PR-merge paths respectively; the first four are user-selectable via the issue detail page and REST API.

shotcuts

0bad62189305978fd98ed6d5c55dbddd
c64745ab4a79aaa4a5777f6b7ab8a0cd
bf3df6b1d7753e23eac88f12164bfa43
56936010cef3013563695c3058061652
5db14d5684bdcfb39a730928916ae9de
12348c6456af9d6dd1a9f8bde30889dd
4022606538db79075686e907ecf782dd
d3d36beb393ea6846b3b5d7f8ab45b12
22d5571ff63a9a5978c50ca111451496
ee9f09796781aabf5ebf0a45f1ac5826
83817f4e616e4ec54c5bf12c76533c27

ux updates:

close-issue-reason-example

Closes can now carry one of six structured reasons: `completed`,
`not_planned`, `duplicate`, `answered`, `completed_by_commit` (system),
and `completed_by_pull` (system).  The last two are written automatically
by the commit-reference and PR-merge paths respectively; the first four
are user-selectable via the issue detail page and REST API.

## Changes

### Data model
- `Issue` gains `close_reason` (int enum, indexed) and
  `close_reason_param` (TEXT, JSON-serialised per-reason params).
- `CommentMetaData` gains matching `close_reason` / `close_reason_param`
  fields so each close event in the timeline retains its own reason
  snapshot, even after reopen.
- New `CommentTypeCloseWithReason` (39) keeps old `CommentTypeClose`
  intact for pre-existing history.
- Migration v330 adds the two columns to the `issue` table.

### Service / validation layer (`services/issue/close_reason.go`)
- `IssueCloseOptions` struct with `Reason` + `ReasonParam`.
- `Normalize()` fills the default (`completed`) when reason is absent.
- `Validate()` enforces: duplicate → same-repo, non-PR, non-self issue;
  answered → comment must belong to the current issue; system reasons →
  validated internally.
- `IsSystemOnly()` blocks `completed_by_commit` / `completed_by_pull`
  from being written by external API or Web requests.
- Constructor helpers: `CloseOptionsCompleted()`, `CloseOptionsDuplicate()`,
  `CloseOptionsAnswered()`, `CloseOptionsCompletedByCommit()`,
  `CloseOptionsCompletedByPull()`.

### CloseIssue / ReopenIssue call chain
- `services/issue/status.go`: `CloseIssue` gains an `IssueCloseOptions`
  param; PRs are unconditionally stripped of any reason (design choice).
- `models/issues/issue_update.go`: `SetIssueAsClosed` persists reason
  into the issue row and selects the right comment type; `setIssueAsReopen`
  clears both fields; `IsMergePull` legacy flag preserved for
  `CommentTypeMergePull`.

### REST API
- `modules/structs/issue.go`: `Issue` gets `state_reason` /
  `state_reason_param`; `EditIssueOption` gets `state_reason` /
  `state_reason_param`.
- `services/convert/issue.go`: closed issues with empty `close_reason`
  default to `"completed"` in the API response.
- `routers/api/v1/repo/issue.go`: `EditIssue` parses the new fields,
  rejects system-only reasons, and forwards to the service layer.

### Web UI
- Split-button close group in `view_content.tmpl` for open, non-PR
  issues: main button + dropdown (Completed / Not planned / Duplicate /
  Answered).
- `Duplicate` opens a `<dialog>` modal with a debounced issue-search
  input; results exclude PRs and the current issue; confirmation writes
  the serialised `{"issue_index": N}` param.
- `Answered` is shown only when the comment editor is non-empty; the
  backend auto-binds the newly created comment's ID.
- `routers/web/repo/issue_comment.go`: parses `state_reason` /
  `state_reason_param` from the form; rejects system-only reasons.
- `services/forms/repo_form.go`: `CreateCommentForm` gains `StateReason`
  and `StateReasonParam`.

### Display
- `view_title.tmpl`: closed-issue badge uses reason text and toggles
  `octicon-skip` + neutral grey background for `not_planned` /
  `duplicate`, `octicon-issue-closed` + purple for the rest.
- `view_content/comments.tmpl`: `CommentTypeCloseWithReason` renders a
  single-sentence close phrase with links (issue link for duplicate,
  comment anchor for answered, SHA link for commit, PR link for pull).
- `templates/shared/issueicon.tmpl`: issue list icons follow the same
  icon/colour rules.
- `web_src/css/repo.css`: `issue-state-label-closed-completed` (purple),
  `issue-state-label-closed-neutral` (grey), duplicate-modal and
  duplicate-list item styles.

### Locale
- 34 new keys under `repo.issues.close_reason.*` covering button labels,
  timeline phrases, page-header phrases, and validation messages.

### Auto-close paths
- `services/issue/commit.go`: commit-reference close passes
  `CloseOptionsCompletedByCommit(sha)`.
- `services/pull/merge.go`: PR-merge cross-reference close passes
  `CloseOptionsCompletedByPull(pr.Index)`.
- `routers/web/repo/issue_list.go`: bulk close passes
  `CloseOptionsCompleted()`.

### Tests
- `services/issue/close_reason_test.go`: 25 sub-tests covering
  Normalize, IsSystemOnly, constructors, no-DB validation paths, and
  DB-backed duplicate / answered validation.
- `services/issue/status_test.go`: full close/reopen cycle for all six
  reasons; PR close leaves no reason stored.
- `services/convert/issue_test.go`: legacy-closed (empty reason) →
  `state_reason=completed`; param pass-through.
- `tests/integration/api_issue_test.go`: API set/read of `duplicate`
  reason; system-only reason rejection.
- `tests/integration/issue_test.go`: Web form close with `duplicate` and
  `answered` (auto-bound comment).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Mar 30, 2026
@github-actions github-actions bot added modifies/api This PR adds API routes or modifies them modifies/go Pull requests that update Go code modifies/templates This PR modifies the template files modifies/migrations modifies/frontend labels Mar 30, 2026
@silverwind
Copy link
Copy Markdown
Member

silverwind commented Mar 30, 2026

  1. Button text should stay at "Close issue" ideally
  2. UI dropdown entries should include "Close as ..." like GitHub
  3. Color of icons needs to be fixed
  4. Would also include the second line for clarity
image

Copy link
Copy Markdown
Contributor

@wxiaoguang wxiaoguang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Horrible AI slop .......

Please review it by yourself line-by-line first

@GiteaBot GiteaBot added lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Mar 30, 2026
@wxiaoguang wxiaoguang marked this pull request as draft March 30, 2026 13:05
@silverwind
Copy link
Copy Markdown
Member

It seems many people wrongly expect AI to be able to one-shot complex issues, but it's far from that as of today. Still needs careful review. With Claude Code, I can strongly recommend running /simplify after every change.

@wxiaoguang
Copy link
Copy Markdown
Contributor

Another problem (even without AI) is, people don't fix their own bugs ....

So when using AI, there will be far more bugs become "nobody cares"

a1012112796 and others added 6 commits March 31, 2026 08:16
Badge and label colours introduced in 8f27e66 are reverted to the
original red (`tw-bg-red tw-text-white` / `ui red label`) so that
the UI stays visually consistent with existing closed-issue style.

The icon-type switch (octicon-skip for duplicate/not_planned vs
octicon-issue-closed for other reasons) is intentionally preserved.left color related design in future.

Changes:
- comments.tmpl: drop issue-close-reason-badge-neutral/completed,
  badge colour stays tw-bg-red tw-text-white
- view_title.tmpl: drop issue-state-label-closed-completed/neutral
  class assignments, label colour stays ui red label issue-state-label
- issueicon.tmpl: tw-text-text-light / tw-text-purple → tw-text-red
- issue.ts: revert getIssueColorClass to original (returns tw-text-red
  for all closed issues)
- repo.css: remove issue-state-label-closed-completed/neutral rules

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Add clearer action labels and descriptions, show the selected close reason in the dropdown, and keep the completed state selected when answered becomes unavailable.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Use dedicated locale keys for fallback close-reason text so rendered labels stay natural across the issue title view.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Wrap the issue action footer and make the close-reason button group responsive so the controls stay usable on narrow screens.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@a1012112796
Copy link
Copy Markdown
Member Author

  1. Button text should stay at "Close issue" ideally

I think curent design is better, because it will show user curent choice more clearly.
c64745ab4a79aaa4a5777f6b7ab8a0cd

And in fact github used same logic for duplicate operation also, I just extened it :)

7bff649341faca98c81d6128615b841f

Color of icons needs to be fixed

looks color related design is hard for me alongth helped by ai, so I has revert all color related desgind and kept it as an TODO.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds GitHub-compatible, structured close reasons for issues (state_reason + optional params), persisting them on the issue and on close-event timeline entries, and exposing them via REST API and the web UI.

Changes:

  • Adds close_reason / close_reason_param persistence on issues and close timeline comments, plus migration v330.
  • Introduces service-layer close reason options + validation, and wires reasons through close/reopen flows (API, web, commit auto-close, PR merge xrefs, bulk close).
  • Updates UI/UX to select close reason (including duplicate modal/search), updates API structs + swagger, and adds unit/integration tests.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
web_src/js/types.ts Adds state_reason to the frontend Issue type.
web_src/js/features/repo-issue.ts Implements close-reason split button, duplicate modal/search, and submit handling.
web_src/js/features/issue.ts Adjusts issue icon selection based on state_reason.
web_src/css/repo.css Adds styling for close-reason split button and duplicate modal.
tests/integration/issue_test.go Adds web integration coverage for duplicate/answered/system-only close reasons.
tests/integration/api_issue_test.go Adds API integration coverage for setting/reading duplicate close reason.
tests/integration/actions_trigger_test.go Updates CloseIssue calls for new signature.
templates/swagger/v1_json.tmpl Documents state_reason / state_reason_param in swagger output.
templates/shared/issueicon.tmpl Updates issue icon rendering to use skip icon for duplicate/not planned.
templates/repo/issue/view_title.tmpl Displays close state label text/links based on close reason.
templates/repo/issue/view_content/comments.tmpl Renders close timeline entry with reason-specific phrases/links for new comment type.
templates/repo/issue/view_content.tmpl Adds close-reason split button UI and duplicate <dialog> modal.
services/pull/pull.go Updates PR-closing paths for new CloseIssue signature.
services/pull/merge.go Passes completed_by_pull reason for cross-ref closes; adapts merge close path to new model API.
services/issue/status.go Extends CloseIssue to accept close-reason options (strips for PRs).
services/issue/status_test.go Adds service-level close/reopen cycle tests for all reasons + PR exclusion.
services/issue/commit.go Uses completed_by_commit reason for commit-reference auto-close.
services/issue/close_reason.go Introduces close-reason options, normalization, validation, and constructors.
services/issue/close_reason_test.go Adds unit tests for normalize/system-only/constructors/validation.
services/forms/repo_form.go Extends comment form to accept state_reason / state_reason_param.
services/convert/issue.go Exposes state_reason (defaults legacy to completed) and parses params for API output.
services/convert/issue_test.go Tests API conversion defaulting + param pass-through.
routers/web/repo/issue_list.go Updates bulk close to pass completed close reason.
routers/web/repo/issue_comment.go Parses/validates close reason from web form; auto-binds answered comment id.
routers/api/v1/repo/pull.go Updates helper call for new close/reopen signature.
routers/api/v1/repo/issue.go Accepts/validates state_reason + param on edit/close operations.
options/locale/locale_en-US.json Adds locale strings for close reason UI and timeline phrases.
modules/structs/issue.go Adds API fields state_reason / state_reason_param and edit options.
models/migrations/v1_26/v330.go Adds migration to create close reason columns on issue.
models/migrations/migrations.go Registers migration 330.
models/issues/issue.go Adds CloseReason + CloseReasonParam fields to Issue model.
models/issues/issue_xref_test.go Updates model CloseIssue calls for new signature.
models/issues/issue_update.go Persists close reason on close, clears on reopen, adds new close comment type.
models/issues/dependency_test.go Updates model CloseIssue calls for new signature.
models/issues/comment.go Adds CommentTypeCloseWithReason and stores close reason snapshot in comment metadata.
models/issues/close_reason.go Adds enum + parsing for structured close reasons.
models/issues/close_reason_display.go Adds helpers for displaying reasons and extracting params from JSON.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +80 to +83
switch o.Reason {
case CloseReasonCompleted, CloseReasonNotPlanned:
// no param required

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completed / not_planned, Validate() currently accepts any non-empty ReasonParam and will allow storing arbitrary JSON in issue.close_reason_param for reasons that don’t use params. Consider rejecting non-empty params for these reasons (or normalizing by clearing ReasonParam) to keep persisted data consistent.

Copilot uses AI. Check for mistakes.
}
opts.Normalize()
if opts.IsSystemOnly() {
ctx.JSONError("this close reason is system-only")
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user-facing JSON error "this close reason is system-only" is hard-coded English and isn’t consistent with the other localized close-reason errors in this handler. Consider using a locale key via ctx.Tr(...) (and/or returning a structured error code) so the UI can display a translated message.

Suggested change
ctx.JSONError("this close reason is system-only")
ctx.JSONError(ctx.Tr("repo.issues.close_reason.system_only"))

Copilot uses AI. Check for mistakes.
@silverwind
Copy link
Copy Markdown
Member

silverwind commented Mar 31, 2026

I think curent design is better, because it will show user curent choice more clearly.

Being used to the GitHub UI, I personally dislike buttons that have so much text in them, the pull request merge button has the same issue. Therefor I recommend making the button look and behave like GitHub's.

looks color related design is hard for me alongth helped by ai, so I has revert all color related desgind and kept it as an TODO.

You can just use the existing color variables from the states, no need to define any new colors. And if you want to do browser testing with an AI agent, I recommend using https://github.com/microsoft/playwright-mcp.

a1012112796 and others added 3 commits April 2, 2026 17:25
Make close-reason dropdown items submit immediately, remove the persistent selected-state UI, and align the duplicate confirm flow and styling with the main close action.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged modifies/api This PR adds API routes or modifies them modifies/frontend modifies/go Pull requests that update Go code modifies/migrations modifies/templates This PR modifies the template files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants