Skip to content

Commit c4ac821

Browse files
committed
chore: add badge-category-selection plan file
1 parent 752fa9e commit c4ac821

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Add category selection to the "Create status badge" modal
2+
3+
## Context
4+
5+
Coverage/complexity/duplication data can already be tracked per **category** — a free-form
6+
label (`coverage_runs.category` / `coverage_daily.category`, migration `0003_categories.sql`)
7+
that partitions independent report series within one project (e.g. "backend" vs "frontend").
8+
The main project dashboard already renders one trend card per discovered category
9+
(`dashboard/src/routes/[owner]/[repo]/+page.svelte`), and the PR-check/baseline endpoint
10+
already reads a `?category=` query param (`src/routes/baseline.ts`).
11+
12+
The "Create status badge" modal (`BadgeModal.svelte`) and its backend
13+
(`src/routes/badge.ts`) were never updated for this: the modal only lets a user pick a
14+
**metric** (coverage, complexity, duplication, etc.) and always produces a badge URL with no
15+
category, and the badge endpoint always reads the `'default'` category regardless of what's
16+
requested. Users with categorized projects can't get a status badge for anything but the
17+
implicit default series. This plan adds a category picker to the modal and wires it through
18+
to the badge endpoint, including the generated badge's label, for all metric types.
19+
20+
## Backend: `src/routes/badge.ts`
21+
22+
`getLatestCoverage(db, projectId, branch, category)` already accepts a `category` param
23+
(default `'default'`) — `src/lib/db.ts:274`. `badge.ts` just never passes it through. Change:
24+
25+
- Read `const category = c.req.query('category') ?? 'default';` (same pattern as
26+
`src/routes/api.ts:35` and `baseline.ts`).
27+
- Pass it into `getLatestCoverage(c.env.DB, project.id, project.default_branch, category)`.
28+
- Include the category in the returned `label` when it isn't the default, so the badge itself
29+
communicates which series it's showing:
30+
```ts
31+
label: category === 'default' ? metricName : `${category} ${metricName}`,
32+
```
33+
- No new validation needed — an unknown/malformed category simply yields no matching row and
34+
`getLatestCoverage` returns `null`, which already 404s (consistent with how `baseline.ts`/
35+
`api.ts` treat category as an unvalidated read-side filter).
36+
37+
This applies uniformly to every metric type (`coverage`, `branch_coverage`, `complexity`,
38+
`cognitive`, `duplication`, `maintainability`) since category is orthogonal to metric — one
39+
category row carries all metric columns.
40+
41+
## Frontend: `dashboard/src/lib/components/BadgeModal.svelte`
42+
43+
1. Add a `defaultBranch: string` prop (needed to query categories for the right branch; the
44+
badge endpoint itself always uses the project's default branch, so the picker must match).
45+
2. Add state:
46+
- `categories = $state<string[]>(['default'])`
47+
- `selectedCategory = $state('default')`
48+
3. Fetch categories whenever the selected metric changes, reusing the existing helper
49+
`fetchTrendByCategory(owner, repo, selectedMetric, defaultBranch, 1)` from
50+
`dashboard/src/lib/api.ts:25` (already used by the project page to discover categories) —
51+
no new endpoint required. On response, take `result.categories.map(c => c.category)`;
52+
fall back to `['default']` on an empty list or fetch error. If the current
53+
`selectedCategory` isn't in the new list, reset it to `categories[0] ?? 'default'`.
54+
- Doing this per-metric (rather than once) is intentional: a category with no data for the
55+
currently selected metric wouldn't produce a working badge anyway (`getLatestCoverage`
56+
would return a null value for that column), so scoping the picker to
57+
metric+category combinations that actually have data avoids offering dead combinations.
58+
4. Add a second `<select>` next to the existing metric select (mirroring its markup/styles at
59+
lines 151–158), labeled "Category", bound to `selectedCategory`, listing `categories`.
60+
5. Update the derived badge URL (`badgeEndpointUrl`, lines 32–34) to append
61+
`?category=${selectedCategory}` only when `selectedCategory !== 'default'`, so existing
62+
badges for uncategorized projects keep their current clean URL.
63+
6. Update the alt text / markdown snippet (line 38) to mention the category when non-default,
64+
e.g. `` `${selectedMetric}${selectedCategory !== 'default' ? ` (${selectedCategory})` : ''} badge` ``,
65+
matching the label convention used server-side.
66+
67+
## Wiring: `dashboard/src/routes/[owner]/[repo]/+page.svelte`
68+
69+
Pass the new prop at the existing `<BadgeModal ... />` call (lines 143–149):
70+
```svelte
71+
<BadgeModal
72+
owner={data.project.owner_login}
73+
repo={data.project.repo_name}
74+
projectId={data.project.id}
75+
badgeEnabled={data.project.badge_enabled}
76+
defaultBranch={data.project.default_branch}
77+
onclose={() => (badgeModalOpen = false)}
78+
/>
79+
```
80+
81+
## Tests: `test/badge.test.ts`
82+
83+
Extend the existing helpers and add cases:
84+
- Extend `seedCoverage()` to accept a `category` field (default `'default'`), inserting into
85+
the `category` column.
86+
- Extend `getBadge()` to accept an optional `category` and append it as a `?category=`
87+
query param.
88+
- New cases:
89+
- A badge for a non-default category returns the correct value when that category has its
90+
own coverage row (distinct from the `'default'` row for the same project).
91+
- The label includes the category name when non-default (e.g. `"backend coverage"`), and
92+
stays as just the metric name when `category` is omitted/`'default'`.
93+
- Requesting a category with no data 404s even when the `'default'` category has data for
94+
that metric.
95+
- Do this for at least one non-coverage metric (e.g. `complexity`/`cyclomatic` or
96+
`duplication`) to confirm category plumbing isn't coverage-specific.
97+
98+
## Verification
99+
100+
- `npm test` (badge.test.ts additions) to confirm backend category filtering + label logic.
101+
- `npm run dev` (dashboard + worker together): seed a project with two categories via the CI
102+
ingest endpoint (or local seed SQL), open "Create status badge", switch the category
103+
dropdown, and confirm:
104+
- The metric/category combinations with data render badge previews and correct shields.io
105+
URLs (`?category=` present only for non-default).
106+
- Switching metric re-fetches/re-scopes the category list appropriately.
107+
- Existing single-category (`'default'`) projects are unaffected — URL has no `category`
108+
query param and label is unchanged from current behavior.

0 commit comments

Comments
 (0)