Skip to content

feat(analytics): privacy-aware export preview with backend-gated personal data - #1206

Merged
llinsss merged 1 commit into
DogStark:mainfrom
esegbueadam-sys:feat/analytics-export-preview
Oct 30, 2026
Merged

llinsss merged 1 commit into
DogStark:mainfrom
esegbueadam-sys:feat/analytics-export-preview

Conversation

@esegbueadam-sys

@esegbueadam-sys esegbueadam-sys commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1135

What this does

Exporting an analytics report now goes through a privacy-aware preview so the user sees exactly what will download before downloading it.

  • One structure for preview and download — src/lib/analytics/exportBuilder.ts builds the field manifest (key, label, personal-data flag), the exact date range derived from the data, the masked sample rows, and the CSV itself, so the preview always matches the file.
  • Identifiers masked by default — src/lib/privacy/maskIdentifiers.ts deterministically and idempotently masks region, account_id and account_email (j•••@e•••.com, usr_••••9b31, Nor•••) unless personal data is explicitly authorised.
  • Authorization stays with the backend — src/pages/api/analytics/export/personal-data-access.ts validates the range (400 malformed/reversed, 403 scope > 12 months), requires a session (401) and asks the real backend, failing closed (503) if the decision is unavailable. The client (src/lib/api/exportAccessAPI.ts) flips personalDataIncluded only on a positive server answer; denials and transport failures always fall back to the masked export, including an in-flight approval that arrives after the user opts back out.
  • UI — src/components/analytics/AnalyticsExportPreview.tsx, opened by "Export Report" on /analytics: accessible dialog (focus trap, Escape, focus restore), exact range readout, fields table, masked sample rows, a separate opt-in checkbox described as a distinct step from downloading, a role="status" live region for grant/denied/error, and a disabled download for empty exports.

Verification

  • 58 tests across 7 suites pass (builder, masking, API client, route, hook, component incl. jest-axe, analytics page) covering the primary flow, empty/no-data, denied, transport failure, malformed/reversed/oversized ranges, and the opt-out race.
  • tsc --noEmit: 0 errors in the added/changed files (49 pre-existing errors elsewhere are untouched).
  • ESLint: no errors or warnings on the added/changed files beyond pre-existing import-order style in analytics.tsx.
  • Full jest run vs. a clean-checkout baseline: failed suites 54 → 53, failed tests 119 → 117 — this PR adds 6 passing suites and fixes the 2 pre-existing failures in the analytics page suite (rendered inside DataModeProvider); no regressions.
  • Real-browser smoke test (dev server + Playwright/Chrome): preview shows the exact range and 18 fields; identifiers masked with raw values absent by default; opt-in without a session → backend 401 → stays masked and the checkbox reverts; with a server-side grant the CSV downloads with Personal Data,included and raw values; Escape closes; no page errors.

Notes / limitations

  • The upstream backend endpoint /analytics/export/personal-data-access does not exist yet, so non-demo environments currently receive a fail-closed denial (503) — the UI handles and displays that.
  • src/lib/analyticsUserData.test.ts and the 49 repo-wide tsc errors fail on a clean checkout too; they are pre-existing and unrelated, so they are left alone.

…onal data

Build the analytics CSV export from one structure so the preview exactly
matches the downloaded file. Personal identifiers (region, account_id,
account_email) are masked by default via deterministic masking, and the
whole pipeline is wired behind a server-gated access check plus an
accessible preview dialog.

New pieces:
- src/lib/privacy/maskIdentifiers.{ts,test.ts}
- src/lib/analytics/exportBuilder.{ts,test.ts}
- src/lib/api/exportAccessAPI.{ts,test.ts}
- src/pages/api/analytics/export/personal-data-access.{ts,test.ts}
- src/hooks/useAnalyticsExportPreview.{ts,test.tsx}
- src/components/analytics/AnalyticsExportPreview.{tsx,test.tsx}
- wire export button -> preview dialog on src/pages/analytics.tsx
- src/pages/__tests__/analytics.test.tsx now renders inside DataModeProvider

Verification:
- 58 tests across 7 suites pass (builder, masking, access API client,
  route, hook, component incl. jest-axe, analytics page).
- tsc --noEmit: 0 errors in touched files (49 pre-existing errors elsewhere).
- ESLint clean on touched files (warnings only, mostly import ordering).
- Full-suite diff vs clean checkout: failed suites 54 -> 53, failed tests
  119 -> 117 (6 new passing suites; analytics page 2 pre-existing failures
  fixed by rendering inside DataModeProvider); no regressions.
- Real-browser smoke: preview shows exact range + field manifest; masked by
  default; backend denial keeps it masked and reverts opt-in; server-side
  grant lets the CSV download with raw values; Escape closes; no page errors.

Note: upstream backend endpoint for personal-data access does not exist yet,
so non-demo environments fail closed (503). That is handled and displayed by
the UI; preview/download are still built client-side from dashboard data.
@llinsss
llinsss merged commit fd9b5bc into DogStark:main Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Frontend] Add privacy-aware analytics download preview

2 participants