Skip to content

ci(frontend): ratchet the frontend migration metrics - #8491

Open
talissoncosta wants to merge 2 commits into
mainfrom
chore/frontend-quality-metrics
Open

ci(frontend): ratchet the frontend migration metrics#8491
talissoncosta wants to merge 2 commits into
mainfrom
chore/frontend-quality-metrics

Conversation

@talissoncosta

@talissoncosta talissoncosta commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

We have four frontend migrations in flight and none of them is measured, so a PR can add to any of them without anyone noticing. This counts them, records the trend by quarter, and fails CI only when one moves the wrong way.

Counting the last four quarters:

Metric 2025-Q4 2026-Q1 2026-Q2 2026-Q3
js_jsx_files 143 135 109 106
ts_tsx_files 523 604 816 974
any_annotations 316 358 378 383
ts_suppressions 46 47 41 42
flux_store_imports 118 125 120 118
account_store_imports 53 57 59 60
project_store_imports 32 31 28 26
dark_selectors 48 51 48 45
raw_colour_hex 342 349 285 274

Three things stand out.

The TypeScript conversion is going well but we are paying for it in any. 143 JS files down to 106, 523 TS files up to 974, while explicit any rose 316 to 383. Worth knowing when reviewing a conversion PR.

Flux is a wash, and the two halves are moving in opposite directions. Total imports are flat at 118. ProjectStore is genuinely coming down (32 to 26), and AccountStore is cancelling it out (53 to 60). That split matters, because AccountStore is blocked on its own migration rather than on anyone's willingness, so it gets its own row instead of hiding inside the total.

Colours and .dark are drifting down slowly as the token work lands, 342 to 274 and 48 to 45.

What's in it

  • scripts/quality-metrics.mjs counts each migration. --check <baseline> compares against scripts/quality-baseline.json and exits non-zero when a number moves the wrong way. ts_tsx_files is the one metric where up is good, and it is handled accordingly.
  • scripts/quality-history.mjs builds the quarterly table. Every metric except the type-check count comes from git grep against a tree-ish, so a past quarter is counted without checking it out or installing its dependencies.
  • A Migration metrics job on frontend PRs runs the check and writes the table to the job summary.

Choices worth flagging

Ceilings, not targets. A PR only fails if it makes a number worse, so nobody is blocked by debt they did not add. Same principle as the existing Lint changed files job. If an increase is intentional, explain it in the PR and move the baseline in the same commit.

Imports, not files. The first version of this counted files and made Flux look like a regression, because splitting one Flux-reading component into five triples a file count without deepening the coupling. modals/CreateFlag.js alone became four files. Anything measuring coupling counts imports; only the TS metric counts files, where the file is the unit of work.

This is the first CI job to run tsc. We had 949 type errors with nothing watching the number, which is largely how it got there. The job does not fail on the errors, only on the count rising.

Adding a fifth metric later is one entry in the METRICS table in quality-metrics.mjs.

How did you test this code?

  • npm run quality:metrics against main, and cross-checked every count by hand with git grep / git ls-tree. The colour count also lines up with the separate DS audit's raw-color-hex category (274 vs 292; the audit's scoping is slightly wider).
  • npm run quality:check passes on main with the committed baseline. Hand-edited a copy of the baseline to fake both a rise in flux_store_imports and a fall in ts_tsx_files; both were reported as WORSE and the exit code was 1.
  • npm run quality:history reproduces the table above from git grep alone, with no checkouts. Verified the Flux reading separately by diffing the importing-file lists between the Q4 2025 commit and main, which is what surfaced the file-count problem: 22 files added, 13 removed, and the removals are the pre-split originals of the additions.
  • Workflow YAML parsed with js-yaml, and npx eslint is clean on both scripts.

Four migrations are in flight at once and none of them was measured, so a
pull request could add to any of them without anybody noticing. Counting
them shows that two are going the wrong way: `any` annotations have risen
from 316 to 383 since Q4 2025, and files importing a Flux store from 82 to
91, while the TypeScript conversion itself has gone well.

`quality-metrics.mjs` counts each migration and, given a baseline, fails
when one moves the wrong way. Nothing is a target and no PR is blocked by
debt it did not add, matching the changed-lines-only rule the lint job
already follows.

Everything except the type-check count comes from `git grep` against a
tree-ish, so `quality-history.mjs` can count past quarters without checking
them out or installing their dependencies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta
talissoncosta requested review from a team as code owners September 9, 2026 11:58
@talissoncosta
talissoncosta requested review from kyle-ssg and removed request for a team September 9, 2026 11:58
@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
flagsmith-frontend-preview Ready Ready Preview Sep 9, 2026 1:12pm UTC
flagsmith-frontend-staging Ready Ready Preview Sep 9, 2026 1:12pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
docs Ignored Ignored Preview Sep 9, 2026 1:12pm UTC

Request Review

@github-actions github-actions Bot added the front-end Issue related to the React Front End Dashboard label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-api-test:pr-8491 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-e2e:pr-8491 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api:pr-8491 Finished ✅ Results
ghcr.io/flagsmith/flagsmith:pr-8491 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8491 Finished ✅ Results
ghcr.io/flagsmith/flagsmith-frontend:pr-8491 Finished ✅ Results

@github-actions github-actions Bot added the ci-cd Build, test and deployment related label Sep 9, 2026
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: a61faafa-2c21-42de-80dc-6f20d2c5282c

📥 Commits

Reviewing files that changed from the base of the PR and between 333ecb8 and f4ac320.

📒 Files selected for processing (3)
  • frontend/scripts/quality-baseline.json
  • frontend/scripts/quality-history.json
  • frontend/scripts/quality-metrics.mjs

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The change adds scripts that measure frontend migration metrics from Git trees and optionally count TypeScript errors. It adds baseline comparison and quarterly history generation. It stores baseline and historical metric data. It adds package commands, pull-request workflow validation, and documentation for the metric process.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: 🟡 Moderate · up to f4ac3

This change adds CI enforcement for frontend migration metrics, but a tooling failure can falsely report TypeScript progress and the workflow may expose its token to pull-request-controlled code. These issues should be resolved before merge.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

const count = (metric, ref) => {
if (metric.exts) {
// `ls-tree` only prefix-matches, so list the roots and filter here.
const suffixes = metric.exts.map((glob) => glob.replace('*', ''))
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor
✅ private-cloud · depot-ubuntu-latest-16 — run #20315 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  49.8 seconds
commit  f4ac320
info  🔄 Run: #20315 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #20315 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  24 passed
skipped  1 skipped

Details

stats  25 tests across 19 suites
duration  1 minute, 34 seconds
commit  f4ac320
info  🔄 Run: #20315 (attempt 1)

Skipped tests

firefox › tests/onboarding-tests.pw.ts › Onboarding › New user connects via the single-page onboarding flow @oss

✅ oss · depot-ubuntu-latest-arm-16 — run #20315 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  45.3 seconds
commit  f4ac320
info  🔄 Run: #20315 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #20315 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  34 seconds
commit  f4ac320
info  🔄 Run: #20315 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #20311 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  51.1 seconds
commit  333ecb8
info  🔄 Run: #20311 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #20311 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  38.9 seconds
commit  333ecb8
info  🔄 Run: #20311 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #20311 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  45.9 seconds
commit  333ecb8
info  🔄 Run: #20311 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #20311 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  41.7 seconds
commit  333ecb8
info  🔄 Run: #20311 (attempt 1)

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.82%. Comparing base (e54bab4) to head (f4ac320).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8491   +/-   ##
=======================================
  Coverage   98.82%   98.82%           
=======================================
  Files        1622     1622           
  Lines       66691    66691           
=======================================
  Hits        65906    65906           
  Misses        785      785           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Visual Regression

19 screenshots compared. See report for details.
View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 01109bb8-276e-48bf-af2b-6e956bb8b718

📥 Commits

Reviewing files that changed from the base of the PR and between cfdc8d3 and 333ecb8.

📒 Files selected for processing (7)
  • .github/workflows/frontend-pull-request.yml
  • frontend/README.md
  • frontend/package.json
  • frontend/scripts/quality-baseline.json
  • frontend/scripts/quality-history.json
  • frontend/scripts/quality-history.mjs
  • frontend/scripts/quality-metrics.mjs

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

working-directory: frontend

steps:
- uses: actions/checkout@v5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,125p' .github/workflows/frontend-pull-request.yml

Repository: Flagsmith/flagsmith

Length of output: 2534


🤖 get_repo_knowledge executed:

get_repo_knowledge Flagsmith/flagsmith /tmp/coderabbit-repo-knowledge/flagsmith-flagsmith-a74d157a/learnings

Length of output: 8740


🏁 Script executed:

cat -n .github/workflows/frontend-pull-request.yml | sed -n '1,125p'

Repository: Flagsmith/flagsmith

Length of output: 3199


🏁 Script executed:

printf '%s\n' '--- workflow ---'
sed -n '60,100p' .github/workflows/frontend-pull-request.yml
printf '%s\n' '--- workflow permissions and checkout options ---'
rg -n -C 3 'permissions:|pull_request|checkout@|persist-credentials|npm ci|quality:check' .github/workflows/frontend-pull-request.yml

Repository: Flagsmith/flagsmith

Length of output: 3366


Sensitive Data Exposure

Reachability: External
Exploitability: Moderate
CWE: CWE-522 — Insufficiently Protected Credentials

Disable checkout credential persistence for this job.

The workflow grants GITHUB_TOKEN contents: read permission and then runs repository-controlled npm commands. actions/checkout stores this token in the local Git configuration by default. A malicious pull request can read and exfiltrate it. Set persist-credentials: false under with; the job does not require authenticated Git operations.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 77-77: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 77-77: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

Source: Linters/SAST tools

Comment on lines +77 to +80
- uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,115p' .github/workflows/frontend-pull-request.yml

Repository: Flagsmith/flagsmith

Length of output: 2534


Security Misconfiguration

Reachability: External
Exploitability: Difficult
CWE: CWE-829 — Inclusion of Functionality from Untrusted Control Sphere

Pin the added actions to immutable commit SHAs.

actions/checkout@v5 and actions/setup-node@v4 use mutable references. Replace all occurrences with approved full commit SHAs.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 77-77: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 77-77: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 80-80: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

Source: Linters/SAST tools

Comment on lines +13 to +19
const QUARTER_STARTS = [
'2025-10-01',
'2026-01-01',
'2026-04-01',
'2026-07-01',
'2026-10-01',
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Generate quarter starts instead of hardcoding them.

When the current date passes 2027-01-01, the final entry has no successor. The script then reads HEAD and writes its metrics under the stale 2026-Q4 label. Because quality:history persists this data to the tracked history file, the trend becomes incorrect until someone updates the list and regenerates it.

Derive the list from the first quarter through the current quarter.

♻️ Proposed refactor
-// Quarter starts, from the first quarter we want to report on.
-const QUARTER_STARTS = [
-  '2025-10-01',
-  '2026-01-01',
-  '2026-04-01',
-  '2026-07-01',
-  '2026-10-01',
-]
+// First quarter we want to report on; later quarters are derived up to today.
+const FIRST_QUARTER_START = '2025-10-01'
+
+const quarterStarts = (from) => {
+  const [startYear, startMonth] = from.split('-').map(Number)
+  const now = new Date()
+  const starts = []
+  let year = startYear
+  let month = startMonth
+  while (
+    year < now.getUTCFullYear() ||
+    (year === now.getUTCFullYear() && month <= now.getUTCMonth() + 1)
+  ) {
+    starts.push(`${year}-${String(month).padStart(2, '0')}-01`)
+    month += 3
+    if (month > 12) {
+      month -= 12
+      year += 1
+    }
+  }
+  return starts
+}
+
+const QUARTER_STARTS = quarterStarts(FIRST_QUARTER_START)

Comment thread frontend/scripts/quality-metrics.mjs
Comment on lines +153 to +157
} catch (error) {
return lines(String(error.stdout ?? '')).filter((line) =>
line.includes('error TS'),
).length
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Re-throw unparsed tsc failures.

When npx tsc fails before emitting error TS diagnostics, the catch returns 0. The --check path can then treat a failed type-check as progress and pass. Re-throw when the parsed error count is zero, while retaining the count for normal TypeScript diagnostic failures.

🐛 Proposed fix
   } catch (error) {
-    return lines(String(error.stdout ?? '')).filter((line) =>
+    const errors = lines(String(error.stdout ?? '')).filter((line) =>
       line.includes('error TS'),
     ).length
+    if (errors === 0) throw error
+    return errors
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
return lines(String(error.stdout ?? '')).filter((line) =>
line.includes('error TS'),
).length
}
} catch (error) {
const errors = lines(String(error.stdout ?? '')).filter((line) =>
line.includes('error TS'),
).length
if (errors === 0) throw error
return errors
}

Counting files made the Flux migration look like a regression when it was
not. Splitting one Flux-reading component into five leaves the coupling
untouched but triples the file count, and that is most of what happened
since Q4 2025: `modals/CreateFlag.js` alone became four files.

Imports are flat over the same period, 118 to 118, and splitting them by
store shows why the total is a wash: ProjectStore is down 32 to 26 while
AccountStore is up 53 to 60, so the progress on one is being cancelled by
the other. AccountStore now gets its own row, since it is blocked on its
own migration rather than on anybody's willingness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-cd Build, test and deployment related front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants