Analytics configuration, schema viewer, and the Report Builder: what to review โ
| Field | Value |
|---|---|
| Branch | feat/admin-and-merchant-portals, draft PR #976: feat(admin): portal navigation, financials and monitoring, plus the agent-process work behind them, not merged |
| Project | README.md |
| Issue | #987: feat(admin/analytics): a configurations page, a BigQuery-sourced schema viewer, and the BigQuery page becomes the Report Builder |
| Servers you need | Admin portal on http://localhost:3001 (npm run admin:dev). Admin Storybook on http://localhost:6008 (npm run storybook:admin) |
| Local origins | Required for the Configurations page to show real retention data. See the warning below |
Why does the Configurations row say "needs the local origins"? โ
The retention panel reads GET /analytics/admin/bq-schema, and this branch is what taught that endpoint to return partitioning, clustering and expiration. โ
- Against the deployed analytics-api the endpoint still returns columns only, so the panel renders its "the endpoint does not carry these fields yet" state rather than the real table. That state is correct behaviour, not a bug.
- Against a local analytics-api you get 19 rows of live warehouse metadata.
- Restart the admin dev server after the
.env.localorigin change lands. Tracked as#1002: fix(dev-env): local dev proxies venues and analytics to Cloud Run, so branch backend changes are invisible locally.
What changed here? โ
Analytics got a Configurations page, and it is LIVE rather than a move. โ
- It is a new sidebar entry under Analytics: Configurations, at
/admin/analytics/config. The oldbigquery/retentionpath redirects to it. - It was rebuilt live rather than moved, because the panel it replaces rendered a hand-written
RETENTION_POLICIESconstant that checked wrong against BigQuery in three ways: it omitteduser_idfromanalytics.eventsclustering, claimedevent_counts_dailyhas no clustering when it is clustered onevent_name, and leftad_delivery_dailyout of the table entirely. It also never showed thatanalytics.eventsrequires a partition filter, which is the difference between a slow query and a failed one. - The page states its own scope, so a future addition has a test to pass rather than a gap to fill.
A BigQuery schema viewer exists as a component, built against a real warehouse capture. It is NOT wired. โ
- Storybook only, no route, deliberately. The endpoint that would feed it was still returning the old payload when it was built, so wiring it would have shown the not-wired state and nothing else.
- Its fixture came out of the real schema service running against
lantern-app-dev. Nothing in it was typed by hand.
A Report Builder exists as a component. It is NOT wired either. โ
- Storybook only, no route, deliberately. Its whole value is in what its metric list refuses, and the refusals are kept as data in the component so the next person adding a metric reads them.
The three Analytics > Dashboards placeholders now declare their real age. โ
- They render through the new
Scaffoldcomponent and say 123 days out loud in warning amber. The dates are read from git, not stamped.
Where do I look, and what am I looking for? โ
Configurations http://localhost:3001/admin/analytics/config โ
- Needs the local origins to show the real retention table.
- The sidebar should carry Configurations as a fifth Analytics sub-item, after Query Console.
/admin/analytics/bigquery/retentionshould redirect here.- Four states that must never look alike: loading, the service did not answer, the endpoint does not carry the fields, and genuinely no base tables. Against the deployed origin you should get the third of those, worded plainly, not an empty table.
- The scope statement at the top should say what belongs on this page. Nothing speculative should be on it.
Configurations in Storybook http://localhost:6008 โ
- Default is the real data state.
- Endpoint does not carry retention yet is what you will see against the deployed origin.
- Service did not answer, Loading, No base tables.
- The check that matters: can you tell these four apart at a glance? An empty table reads as a fact about the warehouse, so a failed request must not render as one.
The schema viewer, Storybook only http://localhost:6008 โ
- Default, Embedded.
- A view is not undocumented: BigQuery views do not inherit column descriptions, and both analytics views read from
events, whose twelve columns are complete. Reporting them as a documentation gap would be wrong, and the viewer says so in the result rather than in a tooltip. - A genuine gap: what a real missing description looks like. It never invents a definition, never falls back to a prettified column name, and never renders a missing description as an empty cell.
- Endpoint does not carry descriptions yet, Service did not answer, Loading.
- Keyboard check: the column tables scroll, and a scrolling region has to be reachable by keyboard. Tab into one and confirm you can scroll it without a mouse.
The Report Builder, Storybook only http://localhost:6008 โ
- Default, With a result.
- The four guardrails, each with its own story, and each is the thing to judge:
- Both event lenses are offered by name, neither as a default. Raw
countand server-signedcount_trustedmean different things, and Events with no lens is a number nobody can interpret. - A null window is named in the result, above the table, not in a tooltip. The person who most needs that caveat is the one who screenshots the table and never hovers.
- A zero denominator renders undefined, not 0%. 0% says nobody redeemed when the truth is nobody was offered the chance. A real zero still renders 0%.
- A cohort of one is visible and deliberately not suppressed. The basis renders beside the number.
- Both event lenses are offered by name, neither as a default. Raw
- The last one is a deliberate non-decision. Suppression is the k-anonymity question, and that belongs to
#874: design(privacy): one data-classification and egress policy, before search, the assistant, and the apps platform each invent their own, not to a builder. - No joins. The builder picks a source first and offers only that source's metrics and dimensions. The two rollups share no key beyond day and environment, so joining them fans out and the totals still look real.
The three Dashboards placeholders http://localhost:3001/admin/analytics/dashboards/engagement โ
- Also Platform and Device and Conversion Funnels.
- Each should state 123 days in amber, plus what will finish it. The old copy was passive and dateless, which is how three product routes sat untouched for four months.
/admin/analytics/dashboardsshould redirect to Engagement, and/admin/analyticsshould land on Event Tracking, not on a placeholder.
What is deliberately NOT done? โ
The schema viewer and the Report Builder have no route and no nav entry. Both are Storybook-only on purpose. โ
- This is the whole reason the admin Storybook exists on this branch. Without
npm run storybook:adminon port 6008, neither of these two surfaces renders anywhere at all. - The rule they are following is the one
#990: process(agents): the scaffold-then-wire rule exists and did not fire, because the prompt asked for a running page is about: scaffold, get the design approved, then wire.
The BigQuery page has NOT become the Report Builder yet. โ
/admin/analytics/bigquery/exportand/bigquery/reportsstill render the oldBigQueryWorkspace. The third of the three changes in#987: feat(admin/analytics): a configurations page, a BigQuery-sourced schema viewer, and the BigQuery page becomes the Report Builder is the component, not the swap.
The merchant-facing report creator is downstream and out of scope. โ
- It carries the k-anonymity gate, and
#874: design(privacy): one data-classification and egress policy, before search, the assistant, and the apps platform each invent their own is the open policy question behind it. It must not be answered ad hoc here. - Its home is
merchant-report-creator.
The bounded metric set has not been agreed with you. โ
- The builder is only as good as the metrics it offers. The list in the component is the builder's proposal, not a decision.
What open findings touch this project? โ
Cross-referenced from ../merchant-and-venue-dashboards/branch-review-0828.md.
A non-JSON 200 is returned as the payload. shared/lib/analyticsApi.js:50 โ
- An HTML shell becomes
{detail, raw:true}, and the retention panel reports "BigQuery returned no base tables" as a finding when what actually happened is that the request did not reach the service. merchantsApi.jswas hardened for exactly this in the same PR.analyticsApi.jswas not. Open, not fixed.- If the Configurations page tells you the warehouse is empty, check this before believing it.
Finding 8 was DOWNGRADED by the reviewer, and the correction is worth reading. analytics/config/retentionModel.js:61 โ
- The
??mechanism is real: it only falls through on null, while the function's own comment says it must read both places. - Its trigger is unevidenced. Against the branch's own real-warehouse fixture, BigQuery echoes the top-level flag and it agrees with the nested one in all eight tables captured.
- A fourth reviewer called it moot because the server normalizes the field first. It does not:
bqSchema.service.js:130uses the same??. The honest statement is that the flaw exists in two layers and its trigger is unevidenced. - Nothing to look at on screen. It is here so you know the row was considered and downgraded rather than dropped.