Skip to content

Commit a5173f5

Browse files
authored
Merge pull request #1337 from Open-Source-Legal/claude/fix-codecov-badge-gEMAE
Fix Codecov frontend badge by aggregating flags server-side
2 parents 6d65afa + eb11a3f commit a5173f5

7 files changed

Lines changed: 34 additions & 180 deletions

File tree

.codecov.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ flags:
4545
paths:
4646
- frontend/src/
4747
carryforward: true
48-
# Merged frontend flag uploaded by codecov-notify.yml after combining the
49-
# three per-suite lcovs (frontend-unit, frontend-component, frontend-e2e).
50-
# This is what the README badge reads so it reflects the union of all
51-
# frontend test suites, not just the Vitest slice. Intentionally does NOT
48+
# Merged frontend flag that the README badge reads. Populated by tagging
49+
# each per-suite upload (`frontend-unit`, `frontend-component`,
50+
# `frontend-e2e`) with `frontend` as an additional flag in its workflow,
51+
# so Codecov aggregates the union of the three uploads server-side.
52+
# `carryforward: true` is load-bearing — a backend-only commit path-filters
53+
# all three producing workflows out, so without carryforward the badge
54+
# would drop to "unknown" on those commits; with it, the flag reuses the
55+
# last known coverage from the parent commit. Intentionally does NOT
5256
# match the `frontend-.*` regex used by the component_management block
5357
# below, so the `frontend` component keeps aggregating only the three
54-
# per-suite flags and doesn't double-count this merged upload.
58+
# per-suite flags and doesn't double-count this merged flag.
5559
frontend:
5660
paths:
5761
- frontend/src/

.github/workflows/codecov-notify.yml

Lines changed: 5 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ name: Codecov Notify
88
# the triggering SHA. Workflows that skipped due to path filters don't block
99
# the notify step.
1010
#
11-
# This workflow also merges the three per-suite frontend lcov reports
12-
# (`frontend-unit-lcov`, `frontend-ct-lcov`, `frontend-e2e-lcov`, each
13-
# published as a GitHub Actions artifact by its producing job) into a
14-
# single lcov and uploads it to Codecov under the `frontend` flag. That
15-
# gives one authoritative Frontend coverage number — the README badge
16-
# reads it, and PR comments show the merged total alongside the per-suite
17-
# flags. The three individual flag uploads still happen in their own
18-
# workflows, so drill-in by suite continues to work.
11+
# The merged `frontend` flag that the README badge reads is produced
12+
# server-side: each per-suite frontend upload (`frontend-unit`,
13+
# `frontend-component`, `frontend-e2e`) is tagged with `frontend` as an
14+
# additional flag in its own workflow, so Codecov aggregates the union
15+
# of all three without any cross-workflow lcov merging here.
1916

2017
on:
2118
workflow_run:
@@ -60,7 +57,6 @@ jobs:
6057
);
6158
6259
let allDone = true;
63-
const runIds = {};
6460
for (const name of expected) {
6561
const matching = runs
6662
.filter((r) => r.name === name)
@@ -77,102 +73,13 @@ jobs:
7773
core.info(
7874
`${name}: status=${latest.status} conclusion=${latest.conclusion} id=${latest.id}`
7975
);
80-
runIds[name] = latest.id;
8176
if (latest.status !== "completed") {
8277
allDone = false;
8378
}
8479
}
8580
8681
core.setOutput("all_done", allDone ? "true" : "false");
8782
core.setOutput("sha", sha);
88-
core.setOutput(
89-
"frontend_ci_run_id",
90-
runIds["Frontend CI"] || ""
91-
);
92-
core.setOutput(
93-
"frontend_e2e_run_id",
94-
runIds["Frontend E2E Integration"] || ""
95-
);
96-
97-
# ────────────────────────────────────────────────────────────────
98-
# Download per-suite lcov artifacts from each producing workflow.
99-
#
100-
# Each download is guarded by the producing workflow's run id (empty
101-
# string when that workflow was path-filtered for this SHA), and
102-
# `continue-on-error: true` tolerates a completed run that did not
103-
# publish the artifact (for example, when the suite failed before
104-
# the upload step ran).
105-
# ────────────────────────────────────────────────────────────────
106-
- name: Download unit lcov artifact
107-
if: steps.check.outputs.all_done == 'true' && steps.check.outputs.frontend_ci_run_id != ''
108-
continue-on-error: true
109-
uses: actions/download-artifact@v7
110-
with:
111-
github-token: ${{ secrets.GITHUB_TOKEN }}
112-
run-id: ${{ steps.check.outputs.frontend_ci_run_id }}
113-
name: frontend-unit-lcov
114-
path: coverage/unit
115-
116-
- name: Download component lcov artifact
117-
if: steps.check.outputs.all_done == 'true' && steps.check.outputs.frontend_ci_run_id != ''
118-
continue-on-error: true
119-
uses: actions/download-artifact@v7
120-
with:
121-
github-token: ${{ secrets.GITHUB_TOKEN }}
122-
run-id: ${{ steps.check.outputs.frontend_ci_run_id }}
123-
name: frontend-ct-lcov
124-
path: coverage/ct
125-
126-
- name: Download e2e lcov artifact
127-
if: steps.check.outputs.all_done == 'true' && steps.check.outputs.frontend_e2e_run_id != ''
128-
continue-on-error: true
129-
uses: actions/download-artifact@v7
130-
with:
131-
github-token: ${{ secrets.GITHUB_TOKEN }}
132-
run-id: ${{ steps.check.outputs.frontend_e2e_run_id }}
133-
name: frontend-e2e-lcov
134-
path: coverage/e2e
135-
136-
- name: Check for downloaded lcov files
137-
id: lcov
138-
if: steps.check.outputs.all_done == 'true'
139-
run: |
140-
shopt -s nullglob
141-
files=(coverage/*/lcov.info)
142-
if [ ${#files[@]} -eq 0 ]; then
143-
echo "No lcov artifacts retrieved; skipping merged frontend upload."
144-
echo "has_lcov=false" >> "$GITHUB_OUTPUT"
145-
else
146-
echo "Found ${#files[@]} lcov file(s):"
147-
printf ' - %s\n' "${files[@]}"
148-
echo "has_lcov=true" >> "$GITHUB_OUTPUT"
149-
fi
150-
151-
- name: Setup Node.js
152-
if: steps.lcov.outputs.has_lcov == 'true'
153-
uses: actions/setup-node@v6
154-
with:
155-
node-version: "20"
156-
157-
- name: Merge frontend lcov reports
158-
if: steps.lcov.outputs.has_lcov == 'true'
159-
run: |
160-
mkdir -p coverage/merged
161-
npx --yes lcov-result-merger@5 'coverage/*/lcov.info' coverage/merged/lcov.info
162-
echo "Merged lcov source-file count:"
163-
grep -c "^SF:" coverage/merged/lcov.info || true
164-
165-
- name: Upload merged frontend coverage to Codecov
166-
if: steps.lcov.outputs.has_lcov == 'true'
167-
uses: codecov/codecov-action@v6
168-
with:
169-
token: ${{ secrets.CODECOV_TOKEN }}
170-
files: coverage/merged/lcov.info
171-
flags: frontend
172-
name: frontend-merged-coverage
173-
fail_ci_if_error: false
174-
disable_search: true
175-
override_commit: ${{ steps.check.outputs.sha }}
17683
17784
- name: Send notifications to Codecov
17885
if: steps.check.outputs.all_done == 'true'

.github/workflows/frontend-e2e.yml

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,29 +208,20 @@ jobs:
208208
path: frontend/playwright-report-e2e/
209209
if-no-files-found: ignore
210210

211+
# The `frontend` flag rides along so the same upload feeds both the
212+
# per-suite drill-in and the merged Frontend coverage total that the
213+
# README badge reads. See `frontend.yml` for the companion uploads.
211214
- name: Upload frontend E2E coverage to Codecov
212215
if: success() || failure()
213216
uses: codecov/codecov-action@v6
214217
with:
215218
token: ${{ secrets.CODECOV_TOKEN }}
216219
files: frontend/coverage/e2e/lcov.info
217-
flags: frontend-e2e
220+
flags: frontend-e2e,frontend
218221
name: frontend-e2e-coverage
219222
fail_ci_if_error: false
220223
disable_search: true
221224

222-
# Publish the raw lcov so the cross-workflow merge job in
223-
# `codecov-notify.yml` can pull it via `actions/download-artifact` and
224-
# combine it with the unit + component lcovs into a single `frontend` flag.
225-
- name: Upload frontend E2E lcov artifact
226-
if: success() || failure()
227-
uses: actions/upload-artifact@v7
228-
with:
229-
name: frontend-e2e-lcov
230-
path: frontend/coverage/e2e/lcov.info
231-
if-no-files-found: warn
232-
retention-days: 7
233-
234225
- name: Upload backend E2E coverage to Codecov
235226
if: success() || failure()
236227
uses: codecov/codecov-action@v6

.github/workflows/frontend.yml

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -79,27 +79,22 @@ jobs:
7979
yarn run test:ct --reporter=list tests/*Metadata*.ct.tsx
8080
continue-on-error: true
8181

82+
# The `frontend` flag is intentionally listed alongside
83+
# `frontend-component` so the same upload feeds both the per-suite
84+
# drill-in and the merged Frontend coverage total. Codecov aggregates
85+
# all uploads sharing a flag server-side, so the README `flag=frontend`
86+
# badge reflects the union of unit + component + e2e without any
87+
# cross-workflow lcov merging on our side.
8288
- name: Upload CT Coverage to Codecov
8389
if: success() || failure()
8490
uses: codecov/codecov-action@v6
8591
with:
8692
token: ${{ secrets.CODECOV_TOKEN }}
8793
files: frontend/coverage/ct/lcov.info
88-
flags: frontend-component
94+
flags: frontend-component,frontend
8995
name: frontend-ct-coverage
9096
fail_ci_if_error: false
91-
92-
# Publish the raw lcov so the cross-workflow merge job in
93-
# `codecov-notify.yml` can pull it via `actions/download-artifact` and
94-
# combine it with the unit + e2e lcovs into a single `frontend` flag.
95-
- name: Upload CT lcov artifact
96-
if: success() || failure()
97-
uses: actions/upload-artifact@v7
98-
with:
99-
name: frontend-ct-lcov
100-
path: frontend/coverage/ct/lcov.info
101-
if-no-files-found: warn
102-
retention-days: 7
97+
disable_search: true
10398

10499
build:
105100
name: Build
@@ -144,27 +139,17 @@ jobs:
144139
run: yarn install --frozen-lockfile
145140
- name: Run Unit Tests with Coverage
146141
run: yarn run test:coverage:unit
142+
# See the CT upload above for why the `frontend` flag is listed here too.
147143
- name: Upload Unit Coverage to Codecov
148144
if: success() || failure()
149145
uses: codecov/codecov-action@v6
150146
with:
151147
token: ${{ secrets.CODECOV_TOKEN }}
152148
files: frontend/coverage/unit/lcov.info
153-
flags: frontend-unit
149+
flags: frontend-unit,frontend
154150
name: frontend-unit-coverage
155151
fail_ci_if_error: false
156-
157-
# Publish the raw lcov so the cross-workflow merge job in
158-
# `codecov-notify.yml` can pull it via `actions/download-artifact` and
159-
# combine it with the component + e2e lcovs into a single `frontend` flag.
160-
- name: Upload Unit lcov artifact
161-
if: success() || failure()
162-
uses: actions/upload-artifact@v7
163-
with:
164-
name: frontend-unit-lcov
165-
path: frontend/coverage/unit/lcov.info
166-
if-no-files-found: warn
167-
retention-days: 7
152+
disable_search: true
168153

169154
docker-build:
170155
name: Build Docker Image

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- **Frontend coverage badge stuck on "unknown"** (`README.md:12`, `.github/workflows/codecov-notify.yml`, `.github/workflows/frontend.yml`, `.github/workflows/frontend-e2e.yml`, `frontend/package.json`, `frontend/yarn.lock`): PR #1322 pointed the README badge at a new merged `frontend` flag fed by a cross-workflow `lcov-result-merger@5` step inside `codecov-notify.yml`. Every `frontend-merged-coverage` upload since has landed at Codecov with `state: error` / `totals: null`, so the badge rendered "unknown" even though `frontend-unit` (31%), `frontend-component` (61%), and `frontend-e2e` (24%) were all processing correctly. Two defects in the merged lcov confirmed by local repro of the CI merge step: (1) `lcov-result-merger@5` emits a stripped lcov containing only `SF:`, `DA:`, `BRDA:`, `end_of_record` — it drops `TN:`, `FN`, `FNDA`, `FNF`, `FNH`, `LF`, `LH`, `BRF`, `BRH`, so line-summary fields required by Codecov's parser are absent; (2) Vitest v8 emits `src/...` (relative to `frontend/`) while `vite-plugin-istanbul` + `nyc report` emit `/home/runner/work/OpenContracts/OpenContracts/frontend/src/...` (absolute), and the merger keys on the literal path string so the same file appears as two records with conflicting hit counts. `codecov-notify.yml` also ran without `actions/checkout`, which Codecov's action docs explicitly recommend against. Fix: stop merging client-side and let Codecov aggregate server-side, since that is what flags are for. Each per-suite upload now declares two flags — `frontend-unit,frontend`, `frontend-component,frontend`, `frontend-e2e,frontend` — so the `frontend` flag total is the union of the three uploads computed by Codecov. `codecov-notify.yml` is reduced to its original gate-and-notify role (no artifact downloads, no `lcov-result-merger`, no merged upload). Deleted the `frontend-{unit,ct,e2e}-lcov` artifact publishes in `frontend.yml` / `frontend-e2e.yml`, removed the `lcov-result-merger@^5.0.1` devDep and the `coverage:merge` script from `frontend/package.json`, and pruned the orphaned entries from `frontend/yarn.lock`. README badge URL unchanged (`flag=frontend`).
13+
1014
### Changed
1115

1216
- **PR #1297 follow-up — tighten component tests and unit-test coverage** (Issue #1321):

frontend/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@
101101
"test:e2e:coverage": "COVERAGE=true playwright test; E2E_EXIT=$?; mkdir -p coverage/e2e/.nyc_output && nyc report --all --reporter=lcov --reporter=text --report-dir=coverage/e2e --temp-dir=coverage/e2e/.nyc_output || echo 'No coverage data to report'; exit $E2E_EXIT",
102102
"test:coverage:unit": "vitest run --coverage --watch=false",
103103
"test:coverage:ct": "COVERAGE=true playwright test -c playwright-ct.config.ts --reporter=list && mkdir -p coverage/ct/.nyc_output && nyc report --all --reporter=lcov --reporter=text --report-dir=coverage/ct --temp-dir=coverage/ct/.nyc_output",
104-
"coverage:merge": "mkdir -p coverage/merged && lcov-result-merger 'coverage/*/lcov.info' coverage/merged/lcov.info",
105104
"lint": "prettier . --check --ignore-unknown",
106105
"fix-styles": "prettier . --check --write --ignore-unknown",
107106
"prepare": "cd .. && husky install frontend/.husky",
@@ -156,7 +155,6 @@
156155
"fake-indexeddb": "^6.2.2",
157156
"husky": "^8.0.1",
158157
"jsdom": "^26.1.0",
159-
"lcov-result-merger": "^5.0.1",
160158
"nyc": "^18.0.0",
161159
"prettier": "^2.7.1",
162160
"vite-plugin-istanbul": "^8.0.0",

frontend/yarn.lock

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3479,15 +3479,6 @@ cliui@^6.0.0:
34793479
strip-ansi "^6.0.0"
34803480
wrap-ansi "^6.2.0"
34813481

3482-
cliui@^7.0.2:
3483-
version "7.0.4"
3484-
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
3485-
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
3486-
dependencies:
3487-
string-width "^4.2.0"
3488-
strip-ansi "^6.0.0"
3489-
wrap-ansi "^7.0.0"
3490-
34913482
cliui@^8.0.1:
34923483
version "8.0.1"
34933484
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
@@ -4390,7 +4381,7 @@ fast-deep-equal@^3.1.3:
43904381
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
43914382
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
43924383

4393-
fast-glob@^3.2.11, fast-glob@^3.2.9:
4384+
fast-glob@^3.2.9:
43944385
version "3.3.3"
43954386
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
43964387
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
@@ -5512,14 +5503,6 @@ jsonpointer@^5.0.1:
55125503
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
55135504
integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==
55145505

5515-
lcov-result-merger@^5.0.1:
5516-
version "5.0.1"
5517-
resolved "https://registry.yarnpkg.com/lcov-result-merger/-/lcov-result-merger-5.0.1.tgz#39e3cfef3f84dc1a73736c139e735b58fbb2eeed"
5518-
integrity sha512-i53RjTYfqbHgerqGtuJjDfARDU340zNxXrJudQZU3o8ak9rrx8FDQUKf38Cjm6MtbqonqiDFmoKuUe++uZbvOg==
5519-
dependencies:
5520-
fast-glob "^3.2.11"
5521-
yargs "^16.2.0"
5522-
55235506
lines-and-columns@^1.1.6:
55245507
version "1.2.4"
55255508
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@@ -8663,11 +8646,6 @@ yargs-parser@^18.1.2:
86638646
camelcase "^5.0.0"
86648647
decamelize "^1.2.0"
86658648

8666-
yargs-parser@^20.2.2:
8667-
version "20.2.9"
8668-
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
8669-
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
8670-
86718649
yargs-parser@^21.1.1:
86728650
version "21.1.1"
86738651
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
@@ -8690,19 +8668,6 @@ yargs@^15.0.2:
86908668
y18n "^4.0.0"
86918669
yargs-parser "^18.1.2"
86928670

8693-
yargs@^16.2.0:
8694-
version "16.2.0"
8695-
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
8696-
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
8697-
dependencies:
8698-
cliui "^7.0.2"
8699-
escalade "^3.1.1"
8700-
get-caller-file "^2.0.5"
8701-
require-directory "^2.1.1"
8702-
string-width "^4.2.0"
8703-
y18n "^5.0.5"
8704-
yargs-parser "^20.2.2"
8705-
87068671
yargs@^17.0.0:
87078672
version "17.7.2"
87088673
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"

0 commit comments

Comments
 (0)