Files
multica/server/pkg/db/generated/project_pull_request.sql.go
Claude 9df876cae2 feat(projects): project pull requests endpoint
Before: a project had no way to list the pull requests its issues delivered; the only PR list was per issue, so a project-level view would have to fan out one request per issue and could not page or count them.

After: GET /api/projects/{id}/pull-requests returns the distinct GitHub and self-hosted VCS PRs linked to the project's issues, newest update first, with the issue PR list's fields (checks rollup, mergeable, additions, deletions) plus the project issues each PR is linked to. state=open|merged|closed filters (open covers draft, closed is closed without merging), limit/offset page per PR, and the response carries total and per-group counts.

How: ListProjectPullRequestPage unions both PR tables and de-duplicates links so paging is per PR; ListProjectPullRequests / ListProjectVCSPullRequests then load full rows for the page ids with the same columns as the per-issue queries, so the existing row serializers are reused unchanged (including the page-visit snapshot refresh). Issues are attached from one extra query. CountProjectPullRequestsByState feeds total and counts. No migration: additions/deletions already exist. DB-backed handler tests are written but not run here (no PostgreSQL).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 07:44:55 +00:00

471 lines
16 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: project_pull_request.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countProjectPullRequestsByState = `-- name: CountProjectPullRequestsByState :many
WITH linked AS (
SELECT DISTINCT pr.id, pr.state
FROM issue_pull_request ipr
JOIN github_pull_request pr ON pr.id = ipr.pull_request_id
JOIN issue i ON i.id = ipr.issue_id
WHERE i.project_id = $1
AND i.workspace_id = $2
AND pr.workspace_id = $2
UNION ALL
SELECT DISTINCT pr.id, pr.state
FROM issue_vcs_pull_request ipr
JOIN vcs_pull_request pr ON pr.id = ipr.pull_request_id
JOIN issue i ON i.id = ipr.issue_id
WHERE i.project_id = $1
AND i.workspace_id = $2
AND pr.workspace_id = $2
)
SELECT state, COUNT(*)::bigint AS total
FROM linked
GROUP BY state
`
type CountProjectPullRequestsByStateParams struct {
ProjectID pgtype.UUID `json:"project_id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
type CountProjectPullRequestsByStateRow struct {
State string `json:"state"`
Total int64 `json:"total"`
}
// Distinct linked PR count per stored state, over GitHub and VCS PRs. Ignores
// any state filter so the tab can show every group's count from one request.
func (q *Queries) CountProjectPullRequestsByState(ctx context.Context, arg CountProjectPullRequestsByStateParams) ([]CountProjectPullRequestsByStateRow, error) {
rows, err := q.db.Query(ctx, countProjectPullRequestsByState, arg.ProjectID, arg.WorkspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []CountProjectPullRequestsByStateRow{}
for rows.Next() {
var i CountProjectPullRequestsByStateRow
if err := rows.Scan(&i.State, &i.Total); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectPullRequestIssues = `-- name: ListProjectPullRequestIssues :many
SELECT 'github'::text AS kind, ipr.pull_request_id AS pr_id, i.id AS issue_id, i.number, i.title
FROM issue_pull_request ipr
JOIN issue i ON i.id = ipr.issue_id
WHERE ipr.pull_request_id = ANY($1::uuid[])
AND i.project_id = $2
AND i.workspace_id = $3
UNION ALL
SELECT pr.provider AS kind, ipr.pull_request_id AS pr_id, i.id AS issue_id, i.number, i.title
FROM issue_vcs_pull_request ipr
JOIN vcs_pull_request pr ON pr.id = ipr.pull_request_id
JOIN issue i ON i.id = ipr.issue_id
WHERE ipr.pull_request_id = ANY($4::uuid[])
AND i.project_id = $2
AND i.workspace_id = $3
ORDER BY number
`
type ListProjectPullRequestIssuesParams struct {
GithubPrIds []pgtype.UUID `json:"github_pr_ids"`
ProjectID pgtype.UUID `json:"project_id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
VcsPrIds []pgtype.UUID `json:"vcs_pr_ids"`
}
type ListProjectPullRequestIssuesRow struct {
Kind string `json:"kind"`
PrID pgtype.UUID `json:"pr_id"`
IssueID pgtype.UUID `json:"issue_id"`
Number int32 `json:"number"`
Title string `json:"title"`
}
// The project's issues linked to each of the given PRs, GitHub and VCS ids
// passed separately; kind is "github" or the VCS provider.
func (q *Queries) ListProjectPullRequestIssues(ctx context.Context, arg ListProjectPullRequestIssuesParams) ([]ListProjectPullRequestIssuesRow, error) {
rows, err := q.db.Query(ctx, listProjectPullRequestIssues,
arg.GithubPrIds,
arg.ProjectID,
arg.WorkspaceID,
arg.VcsPrIds,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectPullRequestIssuesRow{}
for rows.Next() {
var i ListProjectPullRequestIssuesRow
if err := rows.Scan(
&i.Kind,
&i.PrID,
&i.IssueID,
&i.Number,
&i.Title,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectPullRequestPage = `-- name: ListProjectPullRequestPage :many
WITH linked AS (
SELECT DISTINCT 'github'::text AS kind, pr.id, pr.state, pr.pr_updated_at
FROM issue_pull_request ipr
JOIN github_pull_request pr ON pr.id = ipr.pull_request_id
JOIN issue i ON i.id = ipr.issue_id
WHERE i.project_id = $4
AND i.workspace_id = $5
AND pr.workspace_id = $5
UNION ALL
SELECT DISTINCT pr.provider AS kind, pr.id, pr.state, pr.pr_updated_at
FROM issue_vcs_pull_request ipr
JOIN vcs_pull_request pr ON pr.id = ipr.pull_request_id
JOIN issue i ON i.id = ipr.issue_id
WHERE i.project_id = $4
AND i.workspace_id = $5
AND pr.workspace_id = $5
)
SELECT kind, id
FROM linked
WHERE cardinality($1::text[]) = 0
OR state = ANY($1::text[])
ORDER BY pr_updated_at DESC, id DESC
LIMIT $3 OFFSET $2
`
type ListProjectPullRequestPageParams struct {
StateFilter []string `json:"state_filter"`
PageOffset int32 `json:"page_offset"`
PageLimit int32 `json:"page_limit"`
ProjectID pgtype.UUID `json:"project_id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
type ListProjectPullRequestPageRow struct {
Kind string `json:"kind"`
ID pgtype.UUID `json:"id"`
}
// One page of the distinct PRs (GitHub and self-hosted VCS together) linked to
// any issue of a project, newest update first. A PR linked to several issues of
// the project is one row, so limit/offset page per PR rather than per link.
// state_filter is the set of stored states to keep; empty keeps every state.
func (q *Queries) ListProjectPullRequestPage(ctx context.Context, arg ListProjectPullRequestPageParams) ([]ListProjectPullRequestPageRow, error) {
rows, err := q.db.Query(ctx, listProjectPullRequestPage,
arg.StateFilter,
arg.PageOffset,
arg.PageLimit,
arg.ProjectID,
arg.WorkspaceID,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectPullRequestPageRow{}
for rows.Next() {
var i ListProjectPullRequestPageRow
if err := rows.Scan(&i.Kind, &i.ID); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectPullRequests = `-- name: ListProjectPullRequests :many
WITH target_prs AS (
SELECT pr.id, pr.snapshot_head_sha
FROM github_pull_request pr
WHERE pr.id = ANY($1::uuid[])
AND pr.workspace_id = $2
),
checks AS (
SELECT
cr.pr_id,
COUNT(*)::bigint AS total,
SUM(CASE WHEN cr.status = 'completed' AND cr.conclusion IN
('failure','cancelled','timed_out','action_required','startup_failure','stale','error')
THEN 1 ELSE 0 END)::bigint AS failed,
SUM(CASE WHEN cr.status = 'completed' AND cr.conclusion IN
('success','neutral','skipped')
THEN 1 ELSE 0 END)::bigint AS passed,
SUM(CASE WHEN cr.status <> 'completed' OR cr.conclusion IS NULL
THEN 1 ELSE 0 END)::bigint AS running,
COALESCE(
array_agg(cr.name) FILTER (WHERE cr.status = 'completed' AND cr.conclusion IN
('failure','cancelled','timed_out','action_required','startup_failure','stale','error')),
'{}'
)::text[] AS failed_names
FROM github_pull_request_check_run cr
JOIN target_prs tp ON tp.id = cr.pr_id
WHERE cr.head_sha = tp.snapshot_head_sha AND tp.snapshot_head_sha <> ''
GROUP BY cr.pr_id
)
SELECT
pr.id, pr.workspace_id, pr.installation_id, pr.repo_owner, pr.repo_name,
pr.pr_number, pr.title, pr.state, pr.html_url, pr.branch, pr.author_login,
pr.author_avatar_url, pr.merged_at, pr.closed_at, pr.pr_created_at,
pr.pr_updated_at, pr.head_sha, pr.mergeable_state,
pr.additions, pr.deletions, pr.changed_files,
pr.api_mergeable, pr.api_merge_state_status, pr.checks_rollup_state,
pr.snapshot_head_sha, pr.snapshot_fetched_at,
pr.created_at, pr.updated_at,
'system'::text AS linked_by_type,
COALESCE(c.total, 0)::bigint AS checks_total,
COALESCE(c.passed, 0)::bigint AS checks_passed,
COALESCE(c.failed, 0)::bigint AS checks_failed,
COALESCE(c.running, 0)::bigint AS checks_running,
COALESCE(c.failed_names, '{}')::text[] AS failed_check_names
FROM github_pull_request pr
JOIN target_prs tp ON tp.id = pr.id
LEFT JOIN checks c ON c.pr_id = pr.id
`
type ListProjectPullRequestsParams struct {
PrIds []pgtype.UUID `json:"pr_ids"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
type ListProjectPullRequestsRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
InstallationID int64 `json:"installation_id"`
RepoOwner string `json:"repo_owner"`
RepoName string `json:"repo_name"`
PrNumber int32 `json:"pr_number"`
Title string `json:"title"`
State string `json:"state"`
HtmlUrl string `json:"html_url"`
Branch pgtype.Text `json:"branch"`
AuthorLogin pgtype.Text `json:"author_login"`
AuthorAvatarUrl pgtype.Text `json:"author_avatar_url"`
MergedAt pgtype.Timestamptz `json:"merged_at"`
ClosedAt pgtype.Timestamptz `json:"closed_at"`
PrCreatedAt pgtype.Timestamptz `json:"pr_created_at"`
PrUpdatedAt pgtype.Timestamptz `json:"pr_updated_at"`
HeadSha string `json:"head_sha"`
MergeableState pgtype.Text `json:"mergeable_state"`
Additions int32 `json:"additions"`
Deletions int32 `json:"deletions"`
ChangedFiles int32 `json:"changed_files"`
ApiMergeable pgtype.Text `json:"api_mergeable"`
ApiMergeStateStatus pgtype.Text `json:"api_merge_state_status"`
ChecksRollupState pgtype.Text `json:"checks_rollup_state"`
SnapshotHeadSha string `json:"snapshot_head_sha"`
SnapshotFetchedAt pgtype.Timestamptz `json:"snapshot_fetched_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
LinkedByType string `json:"linked_by_type"`
ChecksTotal int64 `json:"checks_total"`
ChecksPassed int64 `json:"checks_passed"`
ChecksFailed int64 `json:"checks_failed"`
ChecksRunning int64 `json:"checks_running"`
FailedCheckNames []string `json:"failed_check_names"`
}
// GitHub PR rows for ids picked by ListProjectPullRequestPage, with the same
// columns and snapshot / check-run aggregation as ListPullRequestsByIssue (the
// handler reuses its row serializer). There is no per-issue link here, so
// linked_by_type is a constant.
func (q *Queries) ListProjectPullRequests(ctx context.Context, arg ListProjectPullRequestsParams) ([]ListProjectPullRequestsRow, error) {
rows, err := q.db.Query(ctx, listProjectPullRequests, arg.PrIds, arg.WorkspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectPullRequestsRow{}
for rows.Next() {
var i ListProjectPullRequestsRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.InstallationID,
&i.RepoOwner,
&i.RepoName,
&i.PrNumber,
&i.Title,
&i.State,
&i.HtmlUrl,
&i.Branch,
&i.AuthorLogin,
&i.AuthorAvatarUrl,
&i.MergedAt,
&i.ClosedAt,
&i.PrCreatedAt,
&i.PrUpdatedAt,
&i.HeadSha,
&i.MergeableState,
&i.Additions,
&i.Deletions,
&i.ChangedFiles,
&i.ApiMergeable,
&i.ApiMergeStateStatus,
&i.ChecksRollupState,
&i.SnapshotHeadSha,
&i.SnapshotFetchedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.LinkedByType,
&i.ChecksTotal,
&i.ChecksPassed,
&i.ChecksFailed,
&i.ChecksRunning,
&i.FailedCheckNames,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectVCSPullRequests = `-- name: ListProjectVCSPullRequests :many
WITH checks AS (
SELECT
pr.id AS pr_id,
COUNT(*)::bigint AS total,
SUM(CASE WHEN cs.state = 'failed' THEN 1 ELSE 0 END)::bigint AS failed,
SUM(CASE WHEN cs.state = 'passed' THEN 1 ELSE 0 END)::bigint AS passed,
SUM(CASE WHEN cs.state = 'pending' THEN 1 ELSE 0 END)::bigint AS pending
FROM vcs_pull_request pr
JOIN vcs_commit_status cs
ON cs.connection_id = pr.connection_id
AND cs.sha = pr.head_sha
AND pr.head_sha <> ''
WHERE pr.id = ANY($1::uuid[])
AND pr.workspace_id = $2
GROUP BY pr.id
)
SELECT
pr.id, pr.workspace_id, pr.connection_id, pr.provider, pr.repo_owner, pr.repo_name, pr.pr_number, pr.title, pr.state, pr.html_url, pr.branch, pr.head_sha, pr.author_login, pr.author_avatar_url, pr.merged_at, pr.closed_at, pr.pr_created_at, pr.pr_updated_at, pr.additions, pr.deletions, pr.changed_files, pr.created_at, pr.updated_at,
'system'::text AS linked_by_type,
COALESCE(c.total, 0)::bigint AS checks_total,
COALESCE(c.passed, 0)::bigint AS checks_passed,
COALESCE(c.failed, 0)::bigint AS checks_failed,
COALESCE(c.pending, 0)::bigint AS checks_pending
FROM vcs_pull_request pr
LEFT JOIN checks c ON c.pr_id = pr.id
WHERE pr.id = ANY($1::uuid[])
AND pr.workspace_id = $2
`
type ListProjectVCSPullRequestsParams struct {
PrIds []pgtype.UUID `json:"pr_ids"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
type ListProjectVCSPullRequestsRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
ConnectionID pgtype.UUID `json:"connection_id"`
Provider string `json:"provider"`
RepoOwner string `json:"repo_owner"`
RepoName string `json:"repo_name"`
PrNumber int32 `json:"pr_number"`
Title string `json:"title"`
State string `json:"state"`
HtmlUrl string `json:"html_url"`
Branch pgtype.Text `json:"branch"`
HeadSha string `json:"head_sha"`
AuthorLogin pgtype.Text `json:"author_login"`
AuthorAvatarUrl pgtype.Text `json:"author_avatar_url"`
MergedAt pgtype.Timestamptz `json:"merged_at"`
ClosedAt pgtype.Timestamptz `json:"closed_at"`
PrCreatedAt pgtype.Timestamptz `json:"pr_created_at"`
PrUpdatedAt pgtype.Timestamptz `json:"pr_updated_at"`
Additions int32 `json:"additions"`
Deletions int32 `json:"deletions"`
ChangedFiles int32 `json:"changed_files"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
LinkedByType string `json:"linked_by_type"`
ChecksTotal int64 `json:"checks_total"`
ChecksPassed int64 `json:"checks_passed"`
ChecksFailed int64 `json:"checks_failed"`
ChecksPending int64 `json:"checks_pending"`
}
// Self-hosted VCS PR rows for ids picked by ListProjectPullRequestPage, shaped
// like ListVCSPullRequestsByIssue (commit statuses of the current head sha
// folded into passed / failed / pending counts).
func (q *Queries) ListProjectVCSPullRequests(ctx context.Context, arg ListProjectVCSPullRequestsParams) ([]ListProjectVCSPullRequestsRow, error) {
rows, err := q.db.Query(ctx, listProjectVCSPullRequests, arg.PrIds, arg.WorkspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectVCSPullRequestsRow{}
for rows.Next() {
var i ListProjectVCSPullRequestsRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.ConnectionID,
&i.Provider,
&i.RepoOwner,
&i.RepoName,
&i.PrNumber,
&i.Title,
&i.State,
&i.HtmlUrl,
&i.Branch,
&i.HeadSha,
&i.AuthorLogin,
&i.AuthorAvatarUrl,
&i.MergedAt,
&i.ClosedAt,
&i.PrCreatedAt,
&i.PrUpdatedAt,
&i.Additions,
&i.Deletions,
&i.ChangedFiles,
&i.CreatedAt,
&i.UpdatedAt,
&i.LinkedByType,
&i.ChecksTotal,
&i.ChecksPassed,
&i.ChecksFailed,
&i.ChecksPending,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}