mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
A bare body mention of an issue key claimed nothing, yet auto-link wrote a link row for it and flagged it reference_only — filtered out of the PR list, the close aggregate and the review dedup head SHA. The row had no reader, and once the PR went terminal the preserve gate froze the flag, so adding `Closes MUL-1` to a merged PR's body could never surface the PR on its issue. With no unlink surface anywhere, the one repair available from GitHub was the one that could not work. Write no row for a passing mention, delete the historical hidden rows (migration 458), and drop the column's reads and writes; the column itself goes in a follow-up release that re-runs the delete as a mop-up. The visible set of links is unchanged, except that a post-merge `Closes` now links the PR (without retroactively completing the issue). An open PR whose claim is downgraded in place loses its link, which is what hiding used to accomplish. Docs, the four locales, and the agent-facing platform skill are updated in step.
818 lines
28 KiB
Go
818 lines
28 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: github.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const createGitHubInstallation = `-- name: CreateGitHubInstallation :one
|
|
INSERT INTO github_installation (
|
|
workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5, $6
|
|
)
|
|
ON CONFLICT (workspace_id, installation_id) DO UPDATE SET
|
|
account_login = EXCLUDED.account_login,
|
|
account_type = EXCLUDED.account_type,
|
|
account_avatar_url = EXCLUDED.account_avatar_url,
|
|
connected_by_id = EXCLUDED.connected_by_id,
|
|
updated_at = now()
|
|
RETURNING id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at
|
|
`
|
|
|
|
type CreateGitHubInstallationParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
InstallationID int64 `json:"installation_id"`
|
|
AccountLogin string `json:"account_login"`
|
|
AccountType string `json:"account_type"`
|
|
AccountAvatarUrl pgtype.Text `json:"account_avatar_url"`
|
|
ConnectedByID pgtype.UUID `json:"connected_by_id"`
|
|
}
|
|
|
|
func (q *Queries) CreateGitHubInstallation(ctx context.Context, arg CreateGitHubInstallationParams) (GithubInstallation, error) {
|
|
row := q.db.QueryRow(ctx, createGitHubInstallation,
|
|
arg.WorkspaceID,
|
|
arg.InstallationID,
|
|
arg.AccountLogin,
|
|
arg.AccountType,
|
|
arg.AccountAvatarUrl,
|
|
arg.ConnectedByID,
|
|
)
|
|
var i GithubInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ConnectedByID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deleteGitHubInstallation = `-- name: DeleteGitHubInstallation :exec
|
|
DELETE FROM github_installation WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type DeleteGitHubInstallationParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteGitHubInstallation(ctx context.Context, arg DeleteGitHubInstallationParams) error {
|
|
_, err := q.db.Exec(ctx, deleteGitHubInstallation, arg.ID, arg.WorkspaceID)
|
|
return err
|
|
}
|
|
|
|
const deleteGitHubInstallationByInstallationID = `-- name: DeleteGitHubInstallationByInstallationID :many
|
|
DELETE FROM github_installation WHERE installation_id = $1
|
|
RETURNING id, workspace_id
|
|
`
|
|
|
|
type DeleteGitHubInstallationByInstallationIDRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// GitHub-side uninstall/suspend removes trust in the installation entirely, so
|
|
// drop every workspace binding. Returns one row per deleted binding so the
|
|
// handler can broadcast to each affected workspace.
|
|
func (q *Queries) DeleteGitHubInstallationByInstallationID(ctx context.Context, installationID int64) ([]DeleteGitHubInstallationByInstallationIDRow, error) {
|
|
rows, err := q.db.Query(ctx, deleteGitHubInstallationByInstallationID, installationID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []DeleteGitHubInstallationByInstallationIDRow{}
|
|
for rows.Next() {
|
|
var i DeleteGitHubInstallationByInstallationIDRow
|
|
if err := rows.Scan(&i.ID, &i.WorkspaceID); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const deletePendingGitHubInstallation = `-- name: DeletePendingGitHubInstallation :exec
|
|
DELETE FROM github_pending_installation WHERE installation_id = $1
|
|
`
|
|
|
|
func (q *Queries) DeletePendingGitHubInstallation(ctx context.Context, installationID int64) error {
|
|
_, err := q.db.Exec(ctx, deletePendingGitHubInstallation, installationID)
|
|
return err
|
|
}
|
|
|
|
const getGitHubInstallationByID = `-- name: GetGitHubInstallationByID :one
|
|
SELECT id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at FROM github_installation
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetGitHubInstallationByID(ctx context.Context, id pgtype.UUID) (GithubInstallation, error) {
|
|
row := q.db.QueryRow(ctx, getGitHubInstallationByID, id)
|
|
var i GithubInstallation
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ConnectedByID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getGitHubPullRequest = `-- name: GetGitHubPullRequest :one
|
|
SELECT id, workspace_id, installation_id, repo_owner, repo_name, pr_number, title, state, html_url, branch, author_login, author_avatar_url, merged_at, closed_at, pr_created_at, pr_updated_at, created_at, updated_at, head_sha, mergeable_state, additions, deletions, changed_files, api_mergeable, api_merge_state_status, checks_rollup_state, snapshot_head_sha, snapshot_fetched_at FROM github_pull_request
|
|
WHERE workspace_id = $1 AND repo_owner = $2 AND repo_name = $3 AND pr_number = $4
|
|
`
|
|
|
|
type GetGitHubPullRequestParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
RepoOwner string `json:"repo_owner"`
|
|
RepoName string `json:"repo_name"`
|
|
PrNumber int32 `json:"pr_number"`
|
|
}
|
|
|
|
func (q *Queries) GetGitHubPullRequest(ctx context.Context, arg GetGitHubPullRequestParams) (GithubPullRequest, error) {
|
|
row := q.db.QueryRow(ctx, getGitHubPullRequest,
|
|
arg.WorkspaceID,
|
|
arg.RepoOwner,
|
|
arg.RepoName,
|
|
arg.PrNumber,
|
|
)
|
|
var i GithubPullRequest
|
|
err := row.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.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.HeadSha,
|
|
&i.MergeableState,
|
|
&i.Additions,
|
|
&i.Deletions,
|
|
&i.ChangedFiles,
|
|
&i.ApiMergeable,
|
|
&i.ApiMergeStateStatus,
|
|
&i.ChecksRollupState,
|
|
&i.SnapshotHeadSha,
|
|
&i.SnapshotFetchedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getIssuePullRequestCloseAggregate = `-- name: GetIssuePullRequestCloseAggregate :one
|
|
SELECT
|
|
COALESCE(SUM(CASE WHEN pr.state IN ('open', 'draft') THEN 1 ELSE 0 END), 0)::bigint AS open_count,
|
|
COALESCE(SUM(CASE WHEN pr.state = 'merged' AND ipr.close_intent THEN 1 ELSE 0 END), 0)::bigint AS merged_with_close_intent_count
|
|
FROM github_pull_request pr
|
|
JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id
|
|
WHERE ipr.issue_id = $1
|
|
`
|
|
|
|
type GetIssuePullRequestCloseAggregateRow struct {
|
|
OpenCount int64 `json:"open_count"`
|
|
MergedWithCloseIntentCount int64 `json:"merged_with_close_intent_count"`
|
|
}
|
|
|
|
// Aggregates the issue's linked PRs into the two counts that gate
|
|
// auto-advance: how many are still in flight (`open` or `draft`) and how
|
|
// many merged PRs declared explicit closing intent on the link row. The
|
|
// webhook auto-advances the issue when open_count = 0 AND
|
|
// merged_with_close_intent_count > 0. Both the PR state and the link row
|
|
// (with close_intent) are persisted before this query runs, so the result
|
|
// is event-agnostic — a link-only sibling closing after a closing-keyword
|
|
// PR has already merged still resolves the issue. A bare body mention is not
|
|
// linked at all, so a passing reference can never keep open_count > 0.
|
|
func (q *Queries) GetIssuePullRequestCloseAggregate(ctx context.Context, issueID pgtype.UUID) (GetIssuePullRequestCloseAggregateRow, error) {
|
|
row := q.db.QueryRow(ctx, getIssuePullRequestCloseAggregate, issueID)
|
|
var i GetIssuePullRequestCloseAggregateRow
|
|
err := row.Scan(&i.OpenCount, &i.MergedWithCloseIntentCount)
|
|
return i, err
|
|
}
|
|
|
|
const getIssueReviewHeadSha = `-- name: GetIssueReviewHeadSha :one
|
|
SELECT head_sha FROM (
|
|
SELECT pr.head_sha AS head_sha, pr.state AS state, pr.pr_updated_at AS pr_updated_at
|
|
FROM github_pull_request pr
|
|
JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id
|
|
WHERE ipr.issue_id = $1 AND pr.head_sha <> ''
|
|
UNION ALL
|
|
SELECT pr.head_sha AS head_sha, pr.state AS state, pr.pr_updated_at AS pr_updated_at
|
|
FROM vcs_pull_request pr
|
|
JOIN issue_vcs_pull_request ipr ON ipr.pull_request_id = pr.id
|
|
WHERE ipr.issue_id = $1 AND pr.head_sha <> ''
|
|
) combined
|
|
ORDER BY (state IN ('open', 'draft')) DESC, pr_updated_at DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
// Returns the head SHA of the commit currently "under review" for an issue:
|
|
// the most-recently-updated linked PR that still has an open/draft state and a
|
|
// non-empty head_sha. Used by the reviewer-loop dedup (TEN-356) so a pending
|
|
// review task pinned to an old head does not satisfy a request after HEAD
|
|
// advanced. Prefers in-flight PRs (open/draft) over merged/closed ones so a
|
|
// stale merged sibling can't shadow the live review target; falls back to the
|
|
// newest linked PR with a head_sha when none are open. Returns no rows (empty
|
|
// string) when the issue has no linked PR — callers treat that as "no SHA key"
|
|
// and dedup on (issue_id, agent_id) alone, preserving pre-TEN-356 behavior.
|
|
//
|
|
// Spans both GitHub and self-hosted VCS PRs: a self-hosted PR pushing a new
|
|
// commit must move the dedup head SHA the same way a GitHub PR does, otherwise
|
|
// a fresh review round could be merged away against a stale key.
|
|
func (q *Queries) GetIssueReviewHeadSha(ctx context.Context, issueID pgtype.UUID) (string, error) {
|
|
row := q.db.QueryRow(ctx, getIssueReviewHeadSha, issueID)
|
|
var head_sha string
|
|
err := row.Scan(&head_sha)
|
|
return head_sha, err
|
|
}
|
|
|
|
const getPendingGitHubInstallation = `-- name: GetPendingGitHubInstallation :one
|
|
SELECT installation_id, account_login, account_type, account_avatar_url, received_at, updated_at FROM github_pending_installation WHERE installation_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetPendingGitHubInstallation(ctx context.Context, installationID int64) (GithubPendingInstallation, error) {
|
|
row := q.db.QueryRow(ctx, getPendingGitHubInstallation, installationID)
|
|
var i GithubPendingInstallation
|
|
err := row.Scan(
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ReceivedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const linkIssueToPullRequest = `-- name: LinkIssueToPullRequest :exec
|
|
|
|
INSERT INTO issue_pull_request (
|
|
issue_id, pull_request_id, linked_by_type, linked_by_id, close_intent
|
|
) VALUES (
|
|
$1, $2, $4, $5, $3
|
|
)
|
|
ON CONFLICT (issue_id, pull_request_id) DO UPDATE SET
|
|
close_intent = CASE
|
|
WHEN $6 THEN issue_pull_request.close_intent
|
|
ELSE EXCLUDED.close_intent
|
|
END
|
|
`
|
|
|
|
type LinkIssueToPullRequestParams struct {
|
|
IssueID pgtype.UUID `json:"issue_id"`
|
|
PullRequestID pgtype.UUID `json:"pull_request_id"`
|
|
CloseIntent bool `json:"close_intent"`
|
|
LinkedByType pgtype.Text `json:"linked_by_type"`
|
|
LinkedByID pgtype.UUID `json:"linked_by_id"`
|
|
PreserveCloseIntent bool `json:"preserve_close_intent"`
|
|
}
|
|
|
|
// =====================
|
|
// Issue ↔ Pull Request link
|
|
// =====================
|
|
// close_intent reflects the PR's explicit close declaration at the moment
|
|
// the webhook is allowed to update that intent. Open/edit/merge webhooks use
|
|
// the current title/body parse result so authors can remove a closing keyword
|
|
// before merge. Post-terminal edits can opt into preserving the stored value,
|
|
// keeping the merge-time decision stable.
|
|
func (q *Queries) LinkIssueToPullRequest(ctx context.Context, arg LinkIssueToPullRequestParams) error {
|
|
_, err := q.db.Exec(ctx, linkIssueToPullRequest,
|
|
arg.IssueID,
|
|
arg.PullRequestID,
|
|
arg.CloseIntent,
|
|
arg.LinkedByType,
|
|
arg.LinkedByID,
|
|
arg.PreserveCloseIntent,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const listGitHubInstallationsByInstallationID = `-- name: ListGitHubInstallationsByInstallationID :many
|
|
SELECT id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at FROM github_installation
|
|
WHERE installation_id = $1
|
|
ORDER BY created_at ASC, id ASC
|
|
`
|
|
|
|
// One installation_id can be bound to several workspaces; webhook routing lists
|
|
// every binding and fans the event out to each bound workspace. Ordered oldest
|
|
// first so processing is deterministic and replay-stable.
|
|
func (q *Queries) ListGitHubInstallationsByInstallationID(ctx context.Context, installationID int64) ([]GithubInstallation, error) {
|
|
rows, err := q.db.Query(ctx, listGitHubInstallationsByInstallationID, installationID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GithubInstallation{}
|
|
for rows.Next() {
|
|
var i GithubInstallation
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ConnectedByID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listGitHubInstallationsByWorkspace = `-- name: ListGitHubInstallationsByWorkspace :many
|
|
|
|
SELECT id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at FROM github_installation
|
|
WHERE workspace_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
// =====================
|
|
// GitHub Installation
|
|
// =====================
|
|
func (q *Queries) ListGitHubInstallationsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]GithubInstallation, error) {
|
|
rows, err := q.db.Query(ctx, listGitHubInstallationsByWorkspace, workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GithubInstallation{}
|
|
for rows.Next() {
|
|
var i GithubInstallation
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ConnectedByID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listIssueIDsForPullRequest = `-- name: ListIssueIDsForPullRequest :many
|
|
SELECT issue_id FROM issue_pull_request
|
|
WHERE pull_request_id = $1
|
|
`
|
|
|
|
func (q *Queries) ListIssueIDsForPullRequest(ctx context.Context, pullRequestID pgtype.UUID) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listIssueIDsForPullRequest, pullRequestID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []pgtype.UUID{}
|
|
for rows.Next() {
|
|
var issue_id pgtype.UUID
|
|
if err := rows.Scan(&issue_id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, issue_id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listPullRequestsByIssue = `-- name: ListPullRequestsByIssue :many
|
|
WITH issue_prs AS (
|
|
SELECT pr.id, pr.snapshot_head_sha
|
|
FROM github_pull_request pr
|
|
JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id
|
|
WHERE ipr.issue_id = $1
|
|
),
|
|
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 issue_prs ip ON ip.id = cr.pr_id
|
|
WHERE cr.head_sha = ip.snapshot_head_sha AND ip.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,
|
|
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 issue_pull_request ipr ON ipr.pull_request_id = pr.id
|
|
LEFT JOIN checks c ON c.pr_id = pr.id
|
|
WHERE ipr.issue_id = $1
|
|
ORDER BY pr.pr_created_at DESC
|
|
`
|
|
|
|
type ListPullRequestsByIssueRow 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"`
|
|
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"`
|
|
}
|
|
|
|
// Returns the issue's linked PRs with the GitHub API snapshot (MUL-5265): the
|
|
// mergeability verdict, the CI rollup, and per-check counts for the PR's
|
|
// CURRENT snapshot head SHA. Checks are aggregated from
|
|
// github_pull_request_check_run — the run-level snapshot written by the API
|
|
// refresh pipeline — NOT the legacy suite-level webhook aggregation, which is
|
|
// removed. The `issue_prs` CTE narrows to this issue's PR ids first so the
|
|
// aggregation only touches check rows for those PRs. Rows for an OLD head are
|
|
// excluded by the snapshot_head_sha filter. Every link row is a working PR:
|
|
// the webhook only links an identifier it read from the PR title, the branch
|
|
// name, or a body closing keyword, so a bare body mention never lands here
|
|
// (MUL-7072).
|
|
func (q *Queries) ListPullRequestsByIssue(ctx context.Context, issueID pgtype.UUID) ([]ListPullRequestsByIssueRow, error) {
|
|
rows, err := q.db.Query(ctx, listPullRequestsByIssue, issueID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListPullRequestsByIssueRow{}
|
|
for rows.Next() {
|
|
var i ListPullRequestsByIssueRow
|
|
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.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 unlinkIssueFromPullRequest = `-- name: UnlinkIssueFromPullRequest :exec
|
|
DELETE FROM issue_pull_request
|
|
WHERE issue_id = $1 AND pull_request_id = $2
|
|
`
|
|
|
|
type UnlinkIssueFromPullRequestParams struct {
|
|
IssueID pgtype.UUID `json:"issue_id"`
|
|
PullRequestID pgtype.UUID `json:"pull_request_id"`
|
|
}
|
|
|
|
func (q *Queries) UnlinkIssueFromPullRequest(ctx context.Context, arg UnlinkIssueFromPullRequestParams) error {
|
|
_, err := q.db.Exec(ctx, unlinkIssueFromPullRequest, arg.IssueID, arg.PullRequestID)
|
|
return err
|
|
}
|
|
|
|
const updateGitHubInstallationAccountByInstallationID = `-- name: UpdateGitHubInstallationAccountByInstallationID :many
|
|
UPDATE github_installation
|
|
SET account_login = $2,
|
|
account_type = $3,
|
|
account_avatar_url = $4,
|
|
updated_at = now()
|
|
WHERE installation_id = $1
|
|
RETURNING id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at
|
|
`
|
|
|
|
type UpdateGitHubInstallationAccountByInstallationIDParams struct {
|
|
InstallationID int64 `json:"installation_id"`
|
|
AccountLogin string `json:"account_login"`
|
|
AccountType string `json:"account_type"`
|
|
AccountAvatarUrl pgtype.Text `json:"account_avatar_url"`
|
|
}
|
|
|
|
// Refresh the GitHub account display metadata across every workspace binding of
|
|
// an installation (fired by installation.created/new_permissions_accepted/
|
|
// unsuspend). Leaves workspace_id and connected_by_id untouched.
|
|
func (q *Queries) UpdateGitHubInstallationAccountByInstallationID(ctx context.Context, arg UpdateGitHubInstallationAccountByInstallationIDParams) ([]GithubInstallation, error) {
|
|
rows, err := q.db.Query(ctx, updateGitHubInstallationAccountByInstallationID,
|
|
arg.InstallationID,
|
|
arg.AccountLogin,
|
|
arg.AccountType,
|
|
arg.AccountAvatarUrl,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GithubInstallation{}
|
|
for rows.Next() {
|
|
var i GithubInstallation
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ConnectedByID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const upsertGitHubPullRequest = `-- name: UpsertGitHubPullRequest :one
|
|
|
|
INSERT INTO github_pull_request (
|
|
workspace_id, installation_id, repo_owner, repo_name, pr_number,
|
|
title, state, html_url, branch, author_login, author_avatar_url,
|
|
merged_at, closed_at, pr_created_at, pr_updated_at,
|
|
head_sha, mergeable_state,
|
|
additions, deletions, changed_files
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5,
|
|
$6, $7, $8, $15, $16, $17,
|
|
$18, $19, $9, $10,
|
|
$11, $20,
|
|
$12, $13, $14
|
|
)
|
|
ON CONFLICT (workspace_id, repo_owner, repo_name, pr_number) DO UPDATE SET
|
|
installation_id = EXCLUDED.installation_id,
|
|
title = EXCLUDED.title,
|
|
state = EXCLUDED.state,
|
|
html_url = EXCLUDED.html_url,
|
|
branch = EXCLUDED.branch,
|
|
author_login = EXCLUDED.author_login,
|
|
author_avatar_url = EXCLUDED.author_avatar_url,
|
|
merged_at = EXCLUDED.merged_at,
|
|
closed_at = EXCLUDED.closed_at,
|
|
pr_updated_at = EXCLUDED.pr_updated_at,
|
|
head_sha = EXCLUDED.head_sha,
|
|
mergeable_state = CASE
|
|
WHEN COALESCE($21::boolean, FALSE) THEN NULL
|
|
WHEN EXCLUDED.mergeable_state IS NOT NULL THEN EXCLUDED.mergeable_state
|
|
ELSE github_pull_request.mergeable_state
|
|
END,
|
|
additions = EXCLUDED.additions,
|
|
deletions = EXCLUDED.deletions,
|
|
changed_files = EXCLUDED.changed_files,
|
|
updated_at = now()
|
|
RETURNING id, workspace_id, installation_id, repo_owner, repo_name, pr_number, title, state, html_url, branch, author_login, author_avatar_url, merged_at, closed_at, pr_created_at, pr_updated_at, created_at, updated_at, head_sha, mergeable_state, additions, deletions, changed_files, api_mergeable, api_merge_state_status, checks_rollup_state, snapshot_head_sha, snapshot_fetched_at
|
|
`
|
|
|
|
type UpsertGitHubPullRequestParams struct {
|
|
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"`
|
|
PrCreatedAt pgtype.Timestamptz `json:"pr_created_at"`
|
|
PrUpdatedAt pgtype.Timestamptz `json:"pr_updated_at"`
|
|
HeadSha string `json:"head_sha"`
|
|
Additions int32 `json:"additions"`
|
|
Deletions int32 `json:"deletions"`
|
|
ChangedFiles int32 `json:"changed_files"`
|
|
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"`
|
|
MergeableState pgtype.Text `json:"mergeable_state"`
|
|
ClearMergeableState pgtype.Bool `json:"clear_mergeable_state"`
|
|
}
|
|
|
|
// =====================
|
|
// GitHub Pull Request
|
|
// =====================
|
|
// mergeable_state has three-state semantics on UPDATE:
|
|
// 1. clear_mergeable_state=true → write NULL (state-changing actions like
|
|
// opened/synchronize/reopened/edited(base) invalidate the prior verdict).
|
|
// 2. clear_mergeable_state=false, mergeable_state non-null → write the value.
|
|
// 3. clear_mergeable_state=false, mergeable_state null → preserve existing
|
|
// column. Metadata events (labeled/assigned/etc.) ship payloads without
|
|
// mergeability, and silently clobbering a known clean/dirty would lose
|
|
// information that GitHub only re-computes lazily.
|
|
//
|
|
// INSERT path always writes the incoming value (NULL acceptable for a new row).
|
|
func (q *Queries) UpsertGitHubPullRequest(ctx context.Context, arg UpsertGitHubPullRequestParams) (GithubPullRequest, error) {
|
|
row := q.db.QueryRow(ctx, upsertGitHubPullRequest,
|
|
arg.WorkspaceID,
|
|
arg.InstallationID,
|
|
arg.RepoOwner,
|
|
arg.RepoName,
|
|
arg.PrNumber,
|
|
arg.Title,
|
|
arg.State,
|
|
arg.HtmlUrl,
|
|
arg.PrCreatedAt,
|
|
arg.PrUpdatedAt,
|
|
arg.HeadSha,
|
|
arg.Additions,
|
|
arg.Deletions,
|
|
arg.ChangedFiles,
|
|
arg.Branch,
|
|
arg.AuthorLogin,
|
|
arg.AuthorAvatarUrl,
|
|
arg.MergedAt,
|
|
arg.ClosedAt,
|
|
arg.MergeableState,
|
|
arg.ClearMergeableState,
|
|
)
|
|
var i GithubPullRequest
|
|
err := row.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.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.HeadSha,
|
|
&i.MergeableState,
|
|
&i.Additions,
|
|
&i.Deletions,
|
|
&i.ChangedFiles,
|
|
&i.ApiMergeable,
|
|
&i.ApiMergeStateStatus,
|
|
&i.ChecksRollupState,
|
|
&i.SnapshotHeadSha,
|
|
&i.SnapshotFetchedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertPendingGitHubInstallation = `-- name: UpsertPendingGitHubInstallation :one
|
|
INSERT INTO github_pending_installation (
|
|
installation_id, account_login, account_type, account_avatar_url
|
|
) VALUES (
|
|
$1, $2, $3, $4
|
|
)
|
|
ON CONFLICT (installation_id) DO UPDATE SET
|
|
account_login = EXCLUDED.account_login,
|
|
account_type = EXCLUDED.account_type,
|
|
account_avatar_url = EXCLUDED.account_avatar_url,
|
|
updated_at = now()
|
|
RETURNING installation_id, account_login, account_type, account_avatar_url, received_at, updated_at
|
|
`
|
|
|
|
type UpsertPendingGitHubInstallationParams struct {
|
|
InstallationID int64 `json:"installation_id"`
|
|
AccountLogin string `json:"account_login"`
|
|
AccountType string `json:"account_type"`
|
|
AccountAvatarUrl pgtype.Text `json:"account_avatar_url"`
|
|
}
|
|
|
|
func (q *Queries) UpsertPendingGitHubInstallation(ctx context.Context, arg UpsertPendingGitHubInstallationParams) (GithubPendingInstallation, error) {
|
|
row := q.db.QueryRow(ctx, upsertPendingGitHubInstallation,
|
|
arg.InstallationID,
|
|
arg.AccountLogin,
|
|
arg.AccountType,
|
|
arg.AccountAvatarUrl,
|
|
)
|
|
var i GithubPendingInstallation
|
|
err := row.Scan(
|
|
&i.InstallationID,
|
|
&i.AccountLogin,
|
|
&i.AccountType,
|
|
&i.AccountAvatarUrl,
|
|
&i.ReceivedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|