Repository navigation
feat(analytics): privacy-aware export preview with backend-gated personal data - #1206
Merged
llinsss merged 1 commit intoOct 30, 2026
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
src/lib/analytics/exportBuilder.tsbuilds 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.src/lib/privacy/maskIdentifiers.tsdeterministically and idempotently masksregion,account_idandaccount_email(j•••@e•••.com,usr_••••9b31,Nor•••) unless personal data is explicitly authorised.src/pages/api/analytics/export/personal-data-access.tsvalidates 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) flipspersonalDataIncludedonly 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.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, arole="status"live region for grant/denied/error, and a disabled download for empty exports.Verification
tsc --noEmit: 0 errors in the added/changed files (49 pre-existing errors elsewhere are untouched).analytics.tsx.DataModeProvider); no regressions.Personal Data,includedand raw values; Escape closes; no page errors.Notes / limitations
/analytics/export/personal-data-accessdoes not exist yet, so non-demo environments currently receive a fail-closed denial (503) — the UI handles and displays that.src/lib/analyticsUserData.test.tsand the 49 repo-widetscerrors fail on a clean checkout too; they are pre-existing and unrelated, so they are left alone.