feat(api): state filter + populated num_issues on project columns #6

Merged
oleks merged 1 commits from feat/project-column-state-filter-and-counts into main 2026-05-15 22:00:51 +03:00
Owner

Two small REST-API improvements to the user/org/repo project-board endpoints we added in #2.

Closes #4
Closes #5

#4 — state filter on column-issues

GET /api/v1/{users,orgs}/{name}/-/projects/{id}/columns/{col}/issues and the repo equivalent now accept ?state=open|closed|all (default open), matching the convention used on the project-list endpoint and on /repos/.../issues.

Applied at the issues_model.IssuesOptions layer (IsClosed: common.ParseIssueFilterStateIsClosed(...)) so all three scopes inherit the filter from a single line each.

Before: every issue ever attached to the column was returned, mixing open and closed cards. Callers had to filter client-side after pulling the full page.

After: the default response only returns open issues; closed cards are reachable via state=closed / state=all.

#5 — populated num_issues (plus open/closed split)

num_issues on the column-list response was always null. Added ColumnList.LoadIssueCounts in models/project/column_list.go, which runs two grouped queries against project_issue ⋈ issue (one for open, one for closed) and populates NumOpenIssues / NumClosedIssues / NumIssues on every column. All three List*Columns handlers call it before converting.

Also added num_open_issues and num_closed_issues to the ProjectColumn API struct to mirror the shape of Project. omitempty was dropped on these three fields so a column with 0 issues serializes as 0 rather than disappearing (which would be just as unhelpful as the old null).

Files changed

  • models/project/column.go — add NumOpenIssues / NumClosedIssues (xorm -) to Column
  • models/project/column_list.goColumnList.LoadIssueCounts (+ countColumnIssuesByState helper)
  • modules/structs/project.go — expose num_open_issues / num_closed_issues on ProjectColumn, drop omitempty
  • services/convert/project.go — copy the new fields into the API payload
  • routers/api/v1/{repo,user,org}/project.go — wire state= into the column-issues handlers and call LoadIssueCounts from the column-list handlers (+ swagger param entries)
  • models/project/column_list_test.go — empty-input fast-path unit test (full count behaviour is covered by integration tests since the unit fixture set has no issue table)
  • tests/integration/api_{repo,user,org}_project_test.go — close an issue mid-test, assert default-open hides it, state=closed / state=all surface it, and the column-list response carries the correct open/closed/total split

Test plan

  • go test ./models/project/... ./services/convert/... ./modules/structs/...
  • GITEA_TEST_DATABASE=sqlite TAGS=sqlite go test -tags=sqlite -run 'TestAPIProjects|TestAPIUserProjects|TestAPIOrgProjects' ./tests/integration/... — all three project test suites green
  • go vet ./... and go build ./... clean

Deployment

Do not merge until the live Gitea instance can absorb a redeploy — this PR was opened against the same Gitea it would patch.

Two small REST-API improvements to the user/org/repo project-board endpoints we added in #2. Closes #4 Closes #5 ## #4 — state filter on column-issues `GET /api/v1/{users,orgs}/{name}/-/projects/{id}/columns/{col}/issues` and the repo equivalent now accept `?state=open|closed|all` (default `open`), matching the convention used on the project-list endpoint and on `/repos/.../issues`. Applied at the `issues_model.IssuesOptions` layer (`IsClosed: common.ParseIssueFilterStateIsClosed(...)`) so all three scopes inherit the filter from a single line each. Before: every issue ever attached to the column was returned, mixing open and closed cards. Callers had to filter client-side after pulling the full page. After: the default response only returns open issues; closed cards are reachable via `state=closed` / `state=all`. ## #5 — populated `num_issues` (plus open/closed split) `num_issues` on the column-list response was always `null`. Added `ColumnList.LoadIssueCounts` in `models/project/column_list.go`, which runs two grouped queries against `project_issue ⋈ issue` (one for open, one for closed) and populates `NumOpenIssues` / `NumClosedIssues` / `NumIssues` on every column. All three `List*Columns` handlers call it before converting. Also added `num_open_issues` and `num_closed_issues` to the `ProjectColumn` API struct to mirror the shape of `Project`. `omitempty` was dropped on these three fields so a column with 0 issues serializes as `0` rather than disappearing (which would be just as unhelpful as the old `null`). ## Files changed - `models/project/column.go` — add `NumOpenIssues` / `NumClosedIssues` (xorm `-`) to `Column` - `models/project/column_list.go` — `ColumnList.LoadIssueCounts` (+ `countColumnIssuesByState` helper) - `modules/structs/project.go` — expose `num_open_issues` / `num_closed_issues` on `ProjectColumn`, drop `omitempty` - `services/convert/project.go` — copy the new fields into the API payload - `routers/api/v1/{repo,user,org}/project.go` — wire `state=` into the column-issues handlers and call `LoadIssueCounts` from the column-list handlers (+ swagger param entries) - `models/project/column_list_test.go` — empty-input fast-path unit test (full count behaviour is covered by integration tests since the unit fixture set has no `issue` table) - `tests/integration/api_{repo,user,org}_project_test.go` — close an issue mid-test, assert default-open hides it, `state=closed` / `state=all` surface it, and the column-list response carries the correct open/closed/total split ## Test plan - [x] `go test ./models/project/... ./services/convert/... ./modules/structs/...` - [x] `GITEA_TEST_DATABASE=sqlite TAGS=sqlite go test -tags=sqlite -run 'TestAPIProjects|TestAPIUserProjects|TestAPIOrgProjects' ./tests/integration/...` — all three project test suites green - [x] `go vet ./...` and `go build ./...` clean ## Deployment Do not merge until the live Gitea instance can absorb a redeploy — this PR was opened against the same Gitea it would patch.
oleks added 1 commit 2026-05-15 21:55:03 +03:00
Adds two improvements to the user/org/repo project-board REST API:

* state filter on column-issues endpoints (issue #4)
  GET /api/v1/{users,orgs}/{name}/-/projects/{id}/columns/{col}/issues
  GET /api/v1/repos/{owner}/{repo}/projects/{id}/columns/{col}/issues
  Now accept ?state=open|closed|all (default open), matching the convention
  on the project-list endpoint and on /repos/.../issues. Applied at the
  IssuesOptions layer so all three scopes inherit the filter.

* populated num_issues / num_open_issues / num_closed_issues on column-list
  (issue #5)
  ColumnList.LoadIssueCounts runs two grouped queries against project_issue
  joined with issue (one open, one closed). All three List*Columns handlers
  call it before converting, so num_issues stops being null and consumers
  can render a kanban summary in a single round trip instead of N+1.

Tests:
* unit: empty-input fast path on ColumnList.LoadIssueCounts.
* integration: extended testAPIListProjectColumnIssues / -User / -Org to
  close an issue, then verify default=open hides it, state=closed and
  state=all return it, and the column-list response carries the correct
  open/closed/total split.

Closes #4, closes #5
oleks merged commit 15acfdb783 into main 2026-05-15 22:00:51 +03:00
oleks deleted branch feat/project-column-state-filter-and-counts 2026-05-15 22:00:51 +03:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: oleks/gitea#6