chore: import upstream snapshot with attribution
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
name: Query Counts — Label
|
||||
|
||||
# Manages the "query-count changed" label and a diff comment on PRs that
|
||||
# alter per-route DB query counts. When a PR's snapshot files differ from
|
||||
# base, the label and a comparison comment are added; when they match base
|
||||
# again (e.g. the delta was reverted, or base caught up via a merge), both
|
||||
# are removed.
|
||||
#
|
||||
# Deliberately NOT path-filtered. A `paths:` filter would create a catch-22:
|
||||
# the workflow could never run to clear a stale label/comment once a
|
||||
# previously-changed snapshot matched base again, because at that point the
|
||||
# PR diff no longer touches the snapshot files. The script below is cheap
|
||||
# (a couple of API reads) and decides what to do from the actual base/head
|
||||
# diff on every run.
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Sync query-count label and diff comment
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const pr = context.payload.pull_request;
|
||||
const baseRef = pr.base.sha;
|
||||
const headRef = pr.head.sha;
|
||||
const label = 'query-count changed';
|
||||
|
||||
// Fetch a JSON file at a given ref. Returns {} if the file
|
||||
// doesn't exist on that ref (e.g. snapshot added in this PR).
|
||||
async function loadSnapshot(ref, path) {
|
||||
try {
|
||||
const { data } = await github.rest.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
ref,
|
||||
path,
|
||||
});
|
||||
if (Array.isArray(data) || data.type !== 'file' || !data.content) {
|
||||
return {};
|
||||
}
|
||||
const raw = Buffer.from(data.content, 'base64').toString('utf8');
|
||||
return JSON.parse(raw);
|
||||
} catch (err) {
|
||||
if (err.status === 404) return {};
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function diffRows(before, after) {
|
||||
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
||||
const rows = [];
|
||||
for (const key of [...keys].sort()) {
|
||||
const b = before[key];
|
||||
const a = after[key];
|
||||
if (b === a) continue;
|
||||
const bCell = b === undefined ? '—' : String(b);
|
||||
const aCell = a === undefined ? '—' : String(a);
|
||||
const delta = (a ?? 0) - (b ?? 0);
|
||||
const deltaCell =
|
||||
a === undefined
|
||||
? 'removed'
|
||||
: b === undefined
|
||||
? 'added'
|
||||
: delta > 0
|
||||
? `+${delta}`
|
||||
: String(delta);
|
||||
rows.push(`| \`${key}\` | ${bCell} | ${aCell} | ${deltaCell} |`);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
const dialects = [
|
||||
['SQLite', 'scripts/query-counts.snapshot.sqlite.json'],
|
||||
['D1', 'scripts/query-counts.snapshot.d1.json'],
|
||||
];
|
||||
|
||||
const sections = [];
|
||||
let totalDelta = 0;
|
||||
let totalChanged = 0;
|
||||
|
||||
for (const [name, path] of dialects) {
|
||||
const [before, after] = await Promise.all([
|
||||
loadSnapshot(baseRef, path),
|
||||
loadSnapshot(headRef, path),
|
||||
]);
|
||||
const rows = diffRows(before, after);
|
||||
if (rows.length === 0) continue;
|
||||
totalChanged += rows.length;
|
||||
for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
|
||||
const b = before[key] ?? 0;
|
||||
const a = after[key] ?? 0;
|
||||
if (a !== b) totalDelta += a - b;
|
||||
}
|
||||
sections.push(
|
||||
[
|
||||
`### ${name}`,
|
||||
'',
|
||||
'| Route | Before | After | Δ |',
|
||||
'| --- | ---: | ---: | --- |',
|
||||
...rows,
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
const hasChange = sections.length > 0;
|
||||
|
||||
// Label: present iff the snapshots differ from base on this run.
|
||||
if (hasChange) {
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name: label,
|
||||
color: 'fbca04',
|
||||
description: 'PR diff modifies query-count snapshot files',
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status !== 422) throw err; // 422 = already exists
|
||||
}
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
labels: [label],
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
name: label,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.status !== 404) throw err; // 404 = label not applied
|
||||
}
|
||||
}
|
||||
|
||||
// Comment: upsert when changed, delete when not.
|
||||
const marker = '<!-- query-count-diff -->';
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
per_page: 100,
|
||||
});
|
||||
const prior = comments.find(
|
||||
(c) => typeof c.body === 'string' && c.body.includes(marker),
|
||||
);
|
||||
|
||||
if (!hasChange) {
|
||||
// Snapshots match base on this run; remove any stale comment
|
||||
// (e.g. an earlier push had a delta that base has since caught
|
||||
// up to, or it was reverted).
|
||||
if (prior) {
|
||||
await github.rest.issues.deleteComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: prior.id,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const sign = totalDelta > 0 ? '+' : '';
|
||||
const summary = `**${totalChanged}** route${totalChanged === 1 ? '' : 's'} changed, total Δ ${sign}${totalDelta} quer${Math.abs(totalDelta) === 1 ? 'y' : 'ies'}.`;
|
||||
|
||||
const body = [
|
||||
marker,
|
||||
'## Query-count snapshot changes',
|
||||
'',
|
||||
summary,
|
||||
'',
|
||||
...sections,
|
||||
'',
|
||||
'<sub>Comparing snapshot files between base and head. Updated automatically on each push.</sub>',
|
||||
].join('\n');
|
||||
|
||||
if (prior) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: prior.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user