Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/storage/dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,47 @@ export function recordPR(ctx: PRContext, extras: { state?: string; closedAt?: st
}
}

/**
* Record a PR close/merge from the webhook `pull_request.closed` event.
*
* This is the ONLY live path that persists `prs.merged_at`. The review path
* calls `recordPR(ctx, { state: "open" })` with no `mergedAt`, and that upsert
* always writes `merged_at = excluded.merged_at` (i.e. NULL), so a merge is
* never captured during review. A merge emits no further `synchronize` events,
* so setting `merged_at` here on close persists — which is what the Impact /
* Overview "caught before merge" and "merged PRs" metrics (they filter on
* `merged_at IS NOT NULL`) and cross-PR memory depend on.
*
* UPDATE-only by design: those metrics JOIN reviews → prs, so any PR that
* matters here already has a row from `recordPR` during its review. A PR the
* bot never reviewed no-ops harmlessly (it carries no findings anyway).
*/
export function markPRClosed(
owner: string,
repo: string,
number: number,
info: { merged: boolean; mergedAt?: string | null; closedAt?: string | null },
): void {
const db = openDatabase();
if (!db) return;
try {
db.prepare(
`UPDATE prs
SET state = ?, closed_at = ?, merged_at = ?
WHERE owner = ? AND repo = ? AND number = ?`,
).run(
info.merged ? "merged" : "closed",
info.closedAt ?? null,
info.merged ? info.mergedAt ?? null : null,
owner,
repo,
number,
);
} catch (err) {
logger.debug({ err }, "dao.markPRClosed failed");
}
}

/** Insert a review row, return its rowid. Returns null when persistence is disabled. */
export function recordReview(opts: {
ctx: PRContext;
Expand Down
15 changes: 12 additions & 3 deletions src/webhook/dispatch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { logger } from "../logger.js";
import { recordEvent } from "../storage/dao.js";
import { markPRClosed, recordEvent } from "../storage/dao.js";
import { bus } from "../realtime/bus.js";
import { runReviewJob } from "../realtime/jobs.js";
import { isPauseAll, isAutoReviewEnabled } from "../settings/overrides.js";
Expand Down Expand Up @@ -187,9 +187,18 @@ export async function dispatchWebhookEvent(
return { status: 202, body: { status: "accepted" } };
}

// closed — abort in-flight reviews
// closed — record the close/merge, then abort in-flight reviews
if (action === "closed") {
logger.info({ owner, repo, pr: number, action }, "PR closed, aborting any in-flight review");
// Persist merge/close status. This is the only live path that sets
// prs.merged_at, which the Impact/Overview "caught before merge" and
// "merged PRs" metrics (and cross-PR memory) are gated on.
const pr = payload.pull_request;
const merged = !!pr.merged;
markPRClosed(owner, repo, number, { merged, mergedAt: pr.merged_at, closedAt: pr.closed_at });
logger.info(
{ owner, repo, pr: number, action, merged },
merged ? "PR merged, recording merge and aborting any in-flight review" : "PR closed, aborting any in-flight review",
);
reviewer.handlePRClose(owner, repo, number);
return { status: 200, body: { status: "ok" } };
}
Expand Down
11 changes: 9 additions & 2 deletions web/src/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1169,8 +1169,15 @@ label.field {
display: flex;
align-items: flex-end;
gap: 3px;
flex: 1;
min-height: 140px;
/* `height` (not just min-height) so the bar has a *definite* height in any
layout context: percentage-based column/segment heights only resolve
against a definite ancestor. `flex: 1 1 auto` still lets the bar grow to
fill a taller stretched card (e.g. the Overview grid), but the explicit
height is the definite fallback when the card body is content-sized (e.g.
the standalone Impact "Findings by severity over time" card), which
otherwise collapsed every column to 0. */
flex: 1 1 auto;
height: 200px;
padding: 6px 0;
}
.chart-bar .col {
Expand Down
Loading