Reusable components
Compare journeys without rebuilding the chart each time.
It started with…
Teams want to compare conversion stages and cohorts in several views. The chart may change, but the underlying definitions need to stay aligned.
So we built…
A configurable funnel component. Builders map the fields and stages; analysts filter, compare and choose the chart format that answers their question.
Take a look inside
Screen 1 of 6Cohort trends
Read stage volumes and conversion trends across cohorts in a single graph. Private values are masked.
What we set out to change
Make drop-offs and cohort differences easier to explore with a component that can be reused across analytical views.
How it is builtData model, code, controls and team ownership
The user journey
- 01
Map the source table, dimensions, numeric stages and optional targets.
- 02
Filter cohorts or compare selected groups.
- 03
Reuse the component as a full-page analysis or a dashboard widget.
Base structure
| Entity / table | Key fields | Relationships |
|---|---|---|
| Cohort facts | Campus · cohort · intake type · optional year | One record represents a cohort at a campus |
| Stage measures | Numeric stage values · optional targets | Stages are mapped to the configured fields |
| View configuration | Shared stage mapping · personal or shared display settings | The analytical definition stays separate from display preferences |
Technical implementation
- Fields and stages are configurable in Airtable; the source does not assume a fixed funnel field name.
- Selections that no longer match the global filters are removed.
- Segmented output has a documented display limit rather than producing unbounded chart sections.
Engineering decision
Compose cohort filters without corrupting the totals
Adapted from the funnel’s dimension filtering and stage/objective aggregation. Dimensions may contain several values, but each record is counted once after filtering. Only finite numeric values enter the totals. An empty filter means all records in this component; a zero objective produces no attainment rate. Stable linked-record IDs should be used where labels are not unique.
const finite = value => Number.isFinite(value) ? value : 0;export function buildFunnel(records, filters, stageKeys) { const dimensions = Object.entries(filters) .map(([key, values]) => [key, new Set(values)]); const included = records.filter(record => dimensions.every(([key, allowed]) => allowed.size === 0 || (record[key] ?? []).some(value => allowed.has(value)) )); const stages = Object.fromEntries(stageKeys.map(key => [key, 0])); const objectives = Object.fromEntries(stageKeys.map(key => [key, 0])); // Multi-valued dimensions never expand a record into duplicate facts. for (const record of included) { for (const key of stageKeys) { stages[key] += finite(record.stages?.[key]); objectives[key] += finite(record.objectives?.[key]); } } return stageKeys.map((key, index) => { const previous = index > 0 ? stages[stageKeys[index - 1]] : null; return { key, value: stages[key], objective: objectives[key], attainment: objectives[key] > 0 ? stages[key] / objectives[key] : null, conversion: previous > 0 ? stages[key] / previous : null, }; });}Adapted implementation excerpt
Controls and boundaries
- The component reads the records exposed to its interface. Configure data access separately.
- Local image export avoids an extra export service; exported data still requires appropriate handling.
Who can contribute
- Analysts: filter, compare and export the required view.
- Builders: configure dimensions, stages and targets.
- Developers: add new formats while preserving the shared definitions.
What supports this case
The released extension, repository and live interface were inspected, including graph, funnel, table, Sankey and grouped views, plus analyst configuration.