Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c872ce726 | ||
|
|
23b4e1c453 | ||
|
|
deeef23131 | ||
|
|
4ece55b031 | ||
|
|
515494dbf0 | ||
|
|
5772c54ccd | ||
|
|
c62ba0ca25 | ||
|
|
b5a63e82d4 |
@@ -178,7 +178,9 @@ jobs:
|
||||
- name: Review PR diff with Claude Code
|
||||
env:
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
# Auto-provided per-run token from Gitea Actions (repo-scoped).
|
||||
# GITHUB_TOKEN is the documented name; GITEA_TOKEN is its alias.
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
PR_NUMBER: ${{ gitea.event.pull_request.number }}
|
||||
|
||||
+4
-1
@@ -834,7 +834,10 @@ async def get_rankings(
|
||||
request: Request,
|
||||
metric: str = Query("rwm_expected_pct", description="Metric to rank by", max_length=50),
|
||||
year: Optional[int] = Query(
|
||||
None, description="Specific year (defaults to most recent)", ge=2000, le=2100
|
||||
None,
|
||||
description="Academic year code, e.g. 201819 (defaults to most recent)",
|
||||
ge=2000,
|
||||
le=210100,
|
||||
),
|
||||
limit: int = Query(20, ge=1, le=100, description="Number of schools to return"),
|
||||
local_authority: Optional[str] = Query(
|
||||
|
||||
+4
-3
@@ -56,7 +56,7 @@ fail the E2E gate. That's the point: staging absorbs the risk.
|
||||
|
||||
| Secret | Purpose |
|
||||
|---|---|
|
||||
| `REGISTRY_TOKEN` | push images to the registry + post PR review comments (already set) |
|
||||
| `REGISTRY_TOKEN` | push images to privaterepo.sitaru.org (already set) |
|
||||
| `CLAUDE_CODE_OAUTH_TOKEN` | Claude Code subscription auth for the PR review — generate with `claude setup-token` on your machine |
|
||||
| `PORTAINER_STAGING_WEBHOOK` | staging stack redeploy webhook URL |
|
||||
| `PORTAINER_PROD_WEBHOOK` | production stack redeploy webhook URL |
|
||||
@@ -125,7 +125,8 @@ numbers, so scheduled data refreshes don't break the gate.
|
||||
|
||||
`scripts/ci/ai_review.py` pipes the PR diff through headless Claude Code
|
||||
(`claude -p`, authenticated with the subscription OAuth token — no API
|
||||
billing), posts the structured findings as a PR comment via the Gitea API
|
||||
(reusing `REGISTRY_TOKEN`), and fails the check only when a finding is rated
|
||||
billing), posts the structured findings as a PR comment using the per-run
|
||||
token Gitea Actions provides automatically (`secrets.GITEA_TOKEN` — no setup
|
||||
needed), and fails the check only when a finding is rated
|
||||
**severe** (would break prod, leak data, or corrupt data). Minor findings are
|
||||
informational and never block a merge.
|
||||
|
||||
@@ -43,8 +43,11 @@ test('school detail page renders name and performance data', async ({ page }) =>
|
||||
await firstSchool.click();
|
||||
await page.waitForURL(/\/school\//);
|
||||
await expect(page.locator('h1').first()).toBeVisible();
|
||||
// The detail page renders at least one chart canvas (performance history)
|
||||
await expect(page.locator('canvas').first()).toBeVisible({ timeout: 15_000 });
|
||||
// The detail page renders at least one *visible* chart canvas. Plain
|
||||
// .first() is wrong here: the admissions card stacks its year/trend views
|
||||
// in one grid cell and keeps the inactive view's canvas visibility:hidden
|
||||
// by design, and that canvas comes first in the DOM.
|
||||
await expect(page.locator('canvas:visible').first()).toBeVisible({ timeout: 15_000 });
|
||||
});
|
||||
|
||||
test('comparing two schools shows both side by side', async ({ page }) => {
|
||||
@@ -70,3 +73,21 @@ test('rankings page loads a populated table', async ({ page }) => {
|
||||
await expect(rows.first()).toBeVisible({ timeout: 15_000 });
|
||||
expect(await rows.count()).toBeGreaterThan(5);
|
||||
});
|
||||
|
||||
test('rankings stay populated after picking a specific year', async ({ page }) => {
|
||||
// Years are academic-year codes (e.g. 201819); the API must accept them
|
||||
// as the `year` query param rather than rejecting with a 422.
|
||||
await page.goto('/rankings');
|
||||
const yearSelect = page.locator('#year-select');
|
||||
await expect(yearSelect).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Pick the first explicit year option (index 0 is the "Latest" default).
|
||||
const yearValue = await yearSelect.locator('option').nth(1).getAttribute('value');
|
||||
expect(yearValue).toBeTruthy();
|
||||
await yearSelect.selectOption(yearValue!);
|
||||
await page.waitForURL(/year=/);
|
||||
|
||||
const rows = page.locator('table tbody tr');
|
||||
await expect(rows.first()).toBeVisible({ timeout: 15_000 });
|
||||
expect(await rows.count()).toBeGreaterThan(5);
|
||||
});
|
||||
|
||||
@@ -76,9 +76,12 @@ export default function RootLayout({
|
||||
<head>
|
||||
<link rel="preconnect" href="https://analytics.schoolcompare.co.uk" />
|
||||
<link rel="preconnect" href="https://api.postcodes.io" />
|
||||
{/* data-domains: the tracker only fires on the production hostnames,
|
||||
so staging (same image, different host) never pollutes Umami */}
|
||||
<Script
|
||||
src="https://analytics.schoolcompare.co.uk/script.js"
|
||||
data-website-id="d7fb0c95-bb6c-4336-8209-bd10077e50dd"
|
||||
data-domains="schoolcompare.co.uk,www.schoolcompare.co.uk"
|
||||
data-performance="true"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user