Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Commit 04f64c6

Browse files
Merge pull request #5902 from department-of-veterans-affairs/metrics-update-2026-03-13
📊 Update metrics dashboard data - 2026-03-13
2 parents 92c0ac0 + 6b2d413 commit 04f64c6

14 files changed

Lines changed: 34039 additions & 2038 deletions
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
# Metrics Processing Fix Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Fix component usage metrics to produce reliable counts: stabilize the CLI CSV column order upstream, and make `process-ds-components.js` robust to column order regardless.
6+
7+
**Architecture:** Two complementary fixes. (1) In `component-library`, fix the `yarn report:csv` CLI command to sort application columns alphabetically and remove the pre-calculated `total` column — this gives us a stable, predictable CSV schema. (2) In this repo, update `process-ds-components.js` to identify application columns by exclusion (`not date/component_name/uswds`) rather than by position — this is correct behavior and a good defensive fallback. The `uswds` column is excluded from summation since the USWDS v3 migration is complete; it is no longer meaningful to count separately. The `combineComponentVariants` logic (merging `va-alert` + `VaAlert``Alert`, etc.) is **intentional and correct** — React wrapper usages are real DS component usages.
8+
9+
**Tech Stack:** Node.js (built-in modules only), GitHub CLI (`gh`)
10+
11+
---
12+
13+
## Context: How the workflow is structured
14+
15+
The `yarn forms` and `yarn report:csv` CLI commands live in `component-library/packages/design-system-dashboard-cli`. They used to run in a workflow inside component-library and check results into that repo. **What moved to this repo** is where those commands are invoked: `.github/workflows/metrics-dashboard.yml` now checks out `component-library` as a sibling directory and runs the CLI from there, so all metrics updates stay within this repo. The CLI source itself still lives in component-library.
16+
17+
## Context: Why the Numbers Were Wrong
18+
19+
The CLI appends new application columns to the CSV as new VA apps get tracked. Column order is **not stable** between runs — `uswds` appeared at index 2 in older files but drifted to a later position in the 2026-03-06 and 2026-03-13 runs. `process-ds-components.js` used `uswdsIndex + 1` as the start of application columns, so everything before `uswds` was silently dropped. A pre-calculated `total` column in the middle of the header was also being summed, double-counting every component. Result: `Link` showing 88 usages when the raw CSV shows `va-link` alone at 1163.
20+
21+
**Why `combineComponentVariants` must stay:** React wrappers (`VaAlert`, `VaButton`, etc.) are real usages of DS components. Counting them separately would undercount component adoption. They are correctly merged with their `va-*` web component counterparts.
22+
23+
**What we're NOT doing in this plan:** v1 component tracking, imposter metrics changes, or va-file-input specifics. Those are deferred pending team input.
24+
25+
---
26+
27+
## Files
28+
29+
| Repo | File | Action |
30+
|---|---|---|
31+
| `component-library` | `packages/design-system-dashboard-cli/src/commands/report.js` (or equivalent) | Modify — sort app columns, drop `total` column |
32+
| `vets-design-system-documentation` | `scripts/process-ds-components.js` | Modify — exclusion-based column detection, drop uswds from sum |
33+
| `vets-design-system-documentation` | `src/assets/data/metrics/component-usage.json` | Regenerated |
34+
| `vets-design-system-documentation` | `src/_data/metrics/component-usage.json` | Regenerated |
35+
36+
---
37+
38+
## Task 1: Fix the CLI CSV output in `component-library`
39+
40+
**Repo:** `department-of-veterans-affairs/component-library`
41+
**Goal:** Stable column order so the CSV schema doesn't change between runs.
42+
43+
- [ ] **Step 1: Find the forms/report CLI source**
44+
45+
```bash
46+
find component-library/packages/design-system-dashboard-cli/src -name "*.js" | xargs grep -l "csv\|uswds\|component_name" 2>/dev/null
47+
```
48+
49+
Also check:
50+
```bash
51+
cat component-library/packages/design-system-dashboard-cli/package.json | grep -A5 '"scripts"'
52+
```
53+
54+
- [ ] **Step 2: Locate where the CSV header is built**
55+
56+
Look for where column headers are assembled. The fix is:
57+
1. Put metadata columns first in a fixed order: `date`, `component_name`, `uswds`
58+
2. Sort all application/repo columns alphabetically after that
59+
3. **Remove the pre-calculated `total` column** — it is redundant (our script calculates totals) and causes double-counting when included in sums
60+
61+
- [ ] **Step 3: Apply the sort**
62+
63+
The change should look roughly like:
64+
```js
65+
// BEFORE (unstable — insertion order depends on when apps were added)
66+
const headers = ['date', 'component_name', 'uswds', ...appColumns, 'total'];
67+
68+
// AFTER (stable — app columns sorted, total removed)
69+
const METADATA = ['date', 'component_name', 'uswds'];
70+
const sortedAppColumns = [...appColumns].sort();
71+
const headers = [...METADATA, ...sortedAppColumns];
72+
```
73+
74+
- [ ] **Step 4: Run the CLI locally to verify output**
75+
76+
```bash
77+
cd component-library/packages/design-system-dashboard-cli
78+
yarn report:csv --output output/test-stable-columns.csv
79+
head -1 output/test-stable-columns.csv | tr ',' '\n' | head -10
80+
```
81+
82+
Expected: `date`, `component_name`, `uswds` as the first three columns; app columns alphabetically after; no `total` at the end.
83+
84+
- [ ] **Step 5: Commit in component-library**
85+
86+
```bash
87+
git add packages/design-system-dashboard-cli/src/...
88+
git commit -m "fix: sort CSV app columns alphabetically, remove pre-calculated total column
89+
90+
Column order was non-deterministic, causing downstream processing in
91+
vets-design-system-documentation to misidentify application columns.
92+
The 'total' column was also redundant and caused double-counting.
93+
94+
Related: vets-design-system-documentation#4903"
95+
```
96+
97+
---
98+
99+
## Task 2: Fix `process-ds-components.js` to use exclusion-based column detection
100+
101+
**Repo:** `vets-design-system-documentation`
102+
**Files:** `scripts/process-ds-components.js`
103+
104+
This fix is correct and necessary regardless of the CLI fix in Task 1 — it makes the script robust to any future column order drift.
105+
106+
### The bug (lines ~78–132)
107+
108+
```js
109+
// Breaks when uswds is not at index 2
110+
const uswdsIndex = headers.findIndex(h => h.toLowerCase() === 'uswds');
111+
const applicationStartIndex = Math.max(uswdsIndex + 1, 3);
112+
const applicationColumns = headers.slice(applicationStartIndex);
113+
114+
// ...in row loop:
115+
for (let j = applicationStartIndex; j < Math.min(values.length, headers.length); j++) {
116+
applicationUsage[headers[j]] = parseInt(values[j]) || 0;
117+
totalUsage += parseInt(values[j]) || 0;
118+
}
119+
```
120+
121+
### The fix
122+
123+
- [ ] **Step 1: Replace the column detection block** (around lines 78–90)
124+
125+
Find:
126+
```js
127+
// Find column indices
128+
const dateIndex = headers.findIndex(h => h.toLowerCase() === 'date');
129+
const componentIndex = headers.findIndex(h => h.toLowerCase() === 'component_name');
130+
const uswdsIndex = headers.findIndex(h => h.toLowerCase() === 'uswds');
131+
132+
if (dateIndex === -1 || componentIndex === -1) {
133+
throw new Error('Required columns (date, component_name) not found in CSV');
134+
}
135+
136+
// Application columns start after the standard columns (date, component_name, uswds)
137+
const applicationStartIndex = Math.max(uswdsIndex + 1, 3);
138+
const applicationColumns = headers.slice(applicationStartIndex);
139+
140+
console.log(`Processing ${lines.length - 1} component records...`);
141+
console.log(`Application columns: ${applicationColumns.length} (${applicationColumns.slice(0, 3).join(', ')}...)`);
142+
```
143+
144+
Replace with:
145+
```js
146+
// Find column indices
147+
const dateIndex = headers.findIndex(h => h.toLowerCase() === 'date');
148+
const componentIndex = headers.findIndex(h => h.toLowerCase() === 'component_name');
149+
150+
if (dateIndex === -1 || componentIndex === -1) {
151+
throw new Error('Required columns (date, component_name) not found in CSV');
152+
}
153+
154+
// Application columns: everything except known metadata and pre-calculated totals.
155+
// Using exclusion (not position) so column order in the CSV doesn't matter.
156+
// 'uswds' is excluded — the USWDS v3 migration is complete and that count is no
157+
// longer meaningful. 'total' is excluded to prevent double-counting.
158+
const EXCLUDED_COLUMNS = new Set(['date', 'component_name', 'uswds', 'total']);
159+
const applicationColumns = headers.filter(h => !EXCLUDED_COLUMNS.has(h.toLowerCase()));
160+
const applicationColumnIndices = applicationColumns.map(col => headers.indexOf(col));
161+
162+
console.log(`Processing ${lines.length - 1} component records...`);
163+
console.log(`Application columns: ${applicationColumns.length} (${applicationColumns.slice(0, 3).join(', ')}...)`);
164+
```
165+
166+
- [ ] **Step 2: Replace the row validation and summation loop** (around lines 95–132)
167+
168+
Find:
169+
```js
170+
// Improved column count validation - handle both too few and too many columns
171+
if (values.length < Math.min(headers.length, applicationStartIndex + 1)) {
172+
console.warn(`Row ${i + 1} has ${values.length} values but expected at least ${applicationStartIndex + 1} (${headers.length} total columns), skipping`);
173+
continue;
174+
}
175+
176+
// Handle rows with more columns than headers (pad headers or truncate values)
177+
if (values.length > headers.length) {
178+
console.warn(`Row ${i + 1} has ${values.length} values but only ${headers.length} headers, truncating extra values`);
179+
values.length = headers.length; // Truncate extra values
180+
}
181+
182+
const date = values[dateIndex];
183+
const componentName = values[componentIndex];
184+
185+
// Set report date from first row
186+
if (!reportDate) {
187+
reportDate = date;
188+
}
189+
190+
if (!componentName || componentName.trim() === '') {
191+
continue;
192+
}
193+
194+
// Calculate total usage across all applications (ignoring USWDS column)
195+
let totalUsage = 0;
196+
const applicationUsage = {};
197+
198+
for (let j = applicationStartIndex; j < Math.min(values.length, headers.length); j++) {
199+
const appName = headers[j];
200+
const usageCount = parseInt(values[j]) || 0;
201+
applicationUsage[appName] = usageCount;
202+
totalUsage += usageCount;
203+
}
204+
```
205+
206+
Replace with:
207+
```js
208+
// Handle rows with more columns than headers
209+
if (values.length > headers.length) {
210+
console.warn(`Row ${i + 1} has ${values.length} values but only ${headers.length} headers, truncating extra values`);
211+
values.length = headers.length;
212+
}
213+
214+
const date = values[dateIndex];
215+
const componentName = values[componentIndex];
216+
217+
// Set report date from first row
218+
if (!reportDate) {
219+
reportDate = date;
220+
}
221+
222+
if (!componentName || componentName.trim() === '') {
223+
continue;
224+
}
225+
226+
// Sum only application columns. Iterating by pre-built index list means
227+
// column order in the CSV doesn't affect which columns are counted.
228+
let totalUsage = 0;
229+
const applicationUsage = {};
230+
231+
for (let k = 0; k < applicationColumnIndices.length; k++) {
232+
const colIndex = applicationColumnIndices[k];
233+
const appName = applicationColumns[k];
234+
const usageCount = parseInt(values[colIndex]) || 0;
235+
applicationUsage[appName] = usageCount;
236+
totalUsage += usageCount;
237+
}
238+
```
239+
240+
- [ ] **Step 3: Verify no leftover references to the old variable names**
241+
242+
```bash
243+
grep -n "applicationStartIndex\|uswdsIndex" scripts/process-ds-components.js
244+
```
245+
246+
Expected: no output.
247+
248+
- [ ] **Step 4: Commit**
249+
250+
```bash
251+
git add scripts/process-ds-components.js
252+
git commit -m "fix(metrics): detect ds-components app columns by exclusion, not position
253+
254+
The CLI does not guarantee a stable column order — 'uswds' drifted past
255+
column 2 in recent runs, causing applicationStartIndex to skip many real
256+
app columns and severely undercount component usage.
257+
258+
Also explicitly exclude 'uswds' (USWDS v3 migration is complete, no
259+
longer meaningful to count separately) and 'total' (pre-calculated sum
260+
that caused double-counting).
261+
262+
The combineComponentVariants logic is unchanged — React wrappers are
263+
real DS component usages and should be merged with their va-* counterparts.
264+
265+
Closes Copilot comments on #5902"
266+
```
267+
268+
---
269+
270+
## Task 3: Regenerate component-usage.json
271+
272+
- [ ] **Step 1: Run the fixed script**
273+
274+
```bash
275+
node scripts/process-ds-components.js
276+
```
277+
278+
- [ ] **Step 2: Sanity-check the output**
279+
280+
```bash
281+
node -e "
282+
const d = require('./src/assets/data/metrics/component-usage.json');
283+
console.log('total_usages:', d.summary_stats.total_usages);
284+
console.log('applications_tracked:', d.summary_stats.applications_tracked);
285+
console.log('Top 5:');
286+
d.top_components_overall.slice(0,5).forEach((c,i) => console.log(i+1, c.name, c.usage_count));
287+
"
288+
```
289+
290+
Expected: `total_usages` in the tens of thousands (raw CSV shows va-telephone at ~810, va-link at ~1163, many more). `applications_tracked` should be ~140. If totals are still small (~459), stop and debug before committing.
291+
292+
- [ ] **Step 3: Commit the regenerated data**
293+
294+
```bash
295+
git add src/assets/data/metrics/component-usage.json src/_data/metrics/component-usage.json
296+
git commit -m "data: regenerate component-usage.json with correct column detection"
297+
```
298+
299+
---
300+
301+
## Task 4: Resolve PR #5902 and issue #4903
302+
303+
- [ ] **Step 1: Comment on PR #5902 explaining the resolution**
304+
305+
```bash
306+
gh pr comment 5902 \
307+
--repo department-of-veterans-affairs/vets-design-system-documentation \
308+
--body "Addressing Copilot's review comments here.
309+
310+
The undercounted component usage (Link: 87 vs CSV: 1221) was caused by a column-order bug in \`process-ds-components.js\` — it used \`uswds\` column position to find where application columns start. When the CLI moved \`uswds\` past column 2 in recent runs, all earlier app columns were silently dropped.
311+
312+
Fixed in [link to your PR] by switching to exclusion-based column detection: all columns except \`date\`, \`component_name\`, \`uswds\`, and \`total\` are treated as application columns regardless of position.
313+
314+
The \`total\` column is also now excluded to prevent double-counting.
315+
316+
The \`combineComponentVariants\` behavior (merging \`va-link\` + \`VaLink\`\`Link\`) is intentional — React wrapper usages are real DS component usages.
317+
318+
The 10-10d form-apps comment is an upstream CLI formatting issue tracked in #4903."
319+
```
320+
321+
- [ ] **Step 2: Close PR #5902** (it contains stale/incorrect data; the next weekly run will generate a fresh PR with the fixed script in place)
322+
323+
```bash
324+
gh pr close 5902 \
325+
--repo department-of-veterans-affairs/vets-design-system-documentation
326+
```
327+
328+
- [ ] **Step 3: Comment on issue #4903**
329+
330+
```bash
331+
gh issue comment 4903 \
332+
--repo department-of-veterans-affairs/vets-design-system-documentation \
333+
--body "Partial progress: The column-order bug in \`process-ds-components.js\` that caused severely undercounted component usage is fixed. The form-apps CSV now has a header row in current output.
334+
335+
Remaining: form numbers are still inconsistent in the CLI output (e.g. \`10D\` instead of \`10-10D\`). That fix belongs in the \`design-system-dashboard-cli\` in component-library — filing there separately."
336+
```
337+
338+
---
339+
340+
## Summary
341+
342+
| Problem | Fix location | Task |
343+
|---|---|---|
344+
| Unstable CSV column order | component-library CLI | Task 1 |
345+
| Exclusion-based column detection | `process-ds-components.js` | Task 2 |
346+
| Wrong `component-usage.json` counts | Regenerate after script fix | Task 3 |
347+
| PR #5902 Copilot comments | Explain + close stale PR | Task 4 |
348+
| Issue #4903 status | Comment with progress | Task 4 |
349+
| v1 component / imposter tracking | **Deferred** — pending team input on va-file-input specifics ||

0 commit comments

Comments
 (0)