Critical User Journeys
public/app/core/journeys/AGENTS.mdHow to add and modify CUJ instrumentation in Grafana
AGENTS.md
Quality
73/100
Scores the file, not the repository.Length
999 words
13 headings · 5 code blocksRepository
76k
— · pushed 0 days agoLast changed
3 days ago
First indexed 3 days ago.12345678# CUJ instrumentation - Agent Configuration910This directory holds the runtime wirings for Critical User Journeys (CUJs) — multi-step user workflows tracked end-to-end as OTel traces + Faro measurements behind the `cujTracking` feature toggle.1112## Required Reading1314Always read these before adding or modifying a journey:15161. **`./journey-tracking.md`** - the canonical reference. It covers architecture, telemetry shape, the registry, parent journeys, debug logging, and a full worked example.172. **`./searchToResource.ts`** - canonical wiring file; copy this shape for new journeys.183. **`./searchToResource.test.ts`** - canonical test shape.194. **`./__test-utils__/journeyTestHarness.ts`** - the only legitimate way to mock the tracker in unit tests.2021Public framework types live in **`@grafana/runtime`** (`packages/grafana-runtime/src/services/JourneyTracker.ts`):2223- `registerJourneyTriggers` - registers the start condition (called once at module import).24- `onJourneyInstance` - registers the per-instance end-condition handler (also called once at module import).25- `JourneyMeta` - registry entry (type, description, owner, timeoutMs, optional `parents`).26- `JourneyHandle` - per-instance handle: `recordEvent`, `startStep`, `setAttributes`, `end(outcome)`.2728Never import from `JourneyTrackerImpl` or `JourneyRegistryImpl` directly - those are internal.2930## Adding a New Journey: Recipe3132> **Fast path:** `yarn cuj:new <type> [--with-smoke]` scaffolds the wiring file, test file, optional smoke driver, registry entry, and bootstrap import in one shot. Run `yarn cuj:new --help` for flags (`--owner`, `--description`, `--timeout-ms`, `--parent`, `--dry-run`). After scaffolding, fill in the TODOs marked in the generated files. The steps below describe what the script generates and why.3334This is the short version of `journey-tracking.md` Steps 0-7. Read the full version if anything's unclear.3536### 1. Decide the journey shape3738- **Type name**: `snake_case` verb-object (`alert_rule_save`, `panel_edit`).39- **Owner**: the squad whose telemetry this is (`grafana-dashboards`, `grafana-alerting`, …).40- **Timeout**: how long is "still going" plausible for? Default 5 min; multi-hour flows (datasource setup) use longer.41- **Parents** (optional): other journey types that should nest under (set `parents: ['parent_type']`). When the parent is active at start, the child's span nests in the parent's trace.4243### 2. Identify or add interactions4445The framework subscribes to `reportInteraction` events via `onInteraction(name, callback)`. Check what the relevant code already emits:4647```bash48grep -rn 'reportInteraction(' public/app/features/<your-area>/49```5051If the events you need don't exist, add them. **Use `silent: true`** for new pure-CUJ events that shouldn't pollute analytics:5253```ts54reportInteraction('grafana_<area>_<verb>', { ...attrs }, { silent: true });55```5657### 3. Register metadata5859Add an entry to `journeyRegistry.ts`:6061```ts62{63 type: 'alert_rule_save',64 description: 'User edits and saves an alert rule',65 owner: 'grafana-alerting',66 timeoutMs: 10 * 60_000,67 // parents: ['some_parent_type'], // optional68},69```7071### 4. Create the wiring file7273Path: `public/app/core/journeys/<camelCase>.ts`. Follow `searchToResource.ts` exactly:7475```ts76import { onInteraction, registerJourneyTriggers, onJourneyInstance } from '@grafana/runtime';77import { collectUnsubs, str } from './utils';7879/**80 * Journey: <type>81 * <one-line description of what the journey covers>82 *83 * Start triggers: <which interaction(s) start it>84 * Steps (duration): <list> // optional85 * Events (point-in-time): <list> // optional86 * End conditions:87 * - success: <which interaction>88 * - discarded / canceled / abandoned: <which interaction>89 * - timeout: 60s / 5min / etc.90 */9192registerJourneyTriggers('<type>', (tracker) => {93 return onInteraction('<start_event>', (props) => {94 if (!tracker.getActiveJourney('<type>')) {95 tracker.startJourney('<type>', { attributes: { ... } });96 }97 });98});99100onJourneyInstance('<type>', (handle) => {101 const { add, cleanup } = collectUnsubs();102 // wire steps + end conditions; each onInteraction handler call goes through `add(...)`103 return cleanup;104});105```106107Use the `str(value)` helper for any value going into attributes - it coerces undefined / objects to a safe string.108109### 5. Import at bootstrap110111Add the import to `public/app/app.ts`:112113```ts114await Promise.all([115 // ...existing imports...116 import('./core/journeys/<camelCase>'),117]);118```119120### 6. Write tests121122Path: `public/app/core/journeys/<camelCase>.test.ts`. Copy the shape of `searchToResource.test.ts`. Cover:123124- start condition fires with right attributes125- each step / event handler fires correctly126- each end condition (success, discarded, etc.) ends the journey with the right outcome127- doesn't double-start when journey is active128- ignores irrelevant interactions129130Run: `yarn jest --no-watch <camelCase>.test.ts`.131132### 7. Verify locally133134Enable the toggle + Faro (see `journey-tracking.md` Configuration section). Walk the workflow with `localStorage.setItem('grafana.debug.journeyTracker', 'true')` set. Confirm in console: `startJourney`, step events, `end` with right outcome.135136For automated load: see `./searchToResource.smoke.ts` for the optional smoke driver pattern (runs the journey N times via Playwright).137138## Pre-merge Checklist139140- [ ] Registry entry has owner, description, sensible `timeoutMs`.141- [ ] Wiring file follows the `searchToResource.ts` shape (no module-scope `StepHandle` that outlives a journey - keep duration-step bookkeeping inside `onJourneyInstance`'s closure).142- [ ] All `onInteraction` subscriptions inside `onJourneyInstance` are tracked through `collectUnsubs` so cleanup runs on journey end.143- [ ] Tests cover start, every end condition, and at least one negative case.144- [ ] Bootstrap import added to `app.ts`.145- [ ] If you added new `reportInteraction` calls purely for CUJ purposes, they pass `{ silent: true }`.146- [ ] If parent nesting is intended, parent's `type` listed in `parents: [...]`.147- [ ] PR titles + commits scoped to the squad area, not pan-CUJ.148149## Common Mistakes150151- **Module-scope step handles**. Causes step leak across journey instances. Always store `StepHandle` in the `onJourneyInstance` closure.152- **Forgetting `add(...)` around an `onInteraction` subscription**. The unsubscribe is lost; subscriptions outlive the journey.153- **Using `recordEvent` for things that have a duration**. Use `startStep` + `step.end()` for measured operations; `recordEvent` is point-in-time.154- **Not handling the discarded path**. A journey that only ends on success will time out for the abandoned case - explicitly map "user closed without selecting" to `handle.end('discarded')`.155- **Polluting analytics**. New CUJ-only events should be `silent: true`. Existing analytics events (`command_palette_action_selected`, etc.) stay un-silent because they have independent value.156157## Smoke Driver (optional)158159Each journey can ship a Playwright smoke driver that exercises it against local Grafana. Pattern: a `<camelCase>.smoke.ts` file alongside the wiring that exports a `JourneyDriver`. Shared helpers (typing patterns, activation styles, palette open) live in `./__smoke__/`. The orchestrator in `scripts/cuj-smoke.ts` imports and registers each driver. See `searchToResource.smoke.ts` for the canonical example.160161Smoke files import each other with explicit `.ts` extensions (Node ESM requirement) and live under their own `tsconfig.smoke.json`. Validate with `yarn typecheck:smoke` (also runs in CI + lefthook on relevant file changes).162
Also in grafana/grafana
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| grafana/grafanaAGENTS.md · 76k | AGENTS.md | setupbuildtestlint-format+6 | 89/100 | 3 days ago | |
| grafana/grafanae2e-playwright/alerting-suite/AGENTS.md · 76k | AGENTS.md | teststylearchtesting-strategy+3 | 81/100 | 3 days ago | |
| grafana/grafanae2e-playwright/dashboard-new-layouts/AGENTS.md · 76k | AGENTS.md | teststyletesting-strategydatabase+1 | 62/100 | today | |
| grafana/grafanae2e-playwright/plugin-e2e/plugin-e2e-api-tests/AGENTS.md · 76k | AGENTS.md | teststyletesting-strategygit+4 | 70/100 | 3 days ago | |
| grafana/grafanapackages/grafana-ui/AGENTS.md · 76k | AGENTS.md | uiagent-behaviour | 16/100 | 3 days ago | |
| grafana/grafanapkg/storage/unified/AGENTS.md · 76k | AGENTS.md | do-not | 46/100 | 3 days ago | |
| grafana/grafanapublic/app/features/AGENTS.md · 76k | AGENTS.md | agent-behaviour | 16/100 | 3 days ago | |
| grafana/grafanapublic/app/features/alerting/unified/AGENTS.md · 76k | AGENTS.md | setuptestlint-formatstyle+11 | 76/100 | 3 days ago | |
| grafana/grafanapublic/app/features/expressions/components/SqlExpressions/SqlEditor/AGENTS.md · 76k | AGENTS.md | styleagent-behaviour | 43/100 | 3 days ago | |
| grafana/grafanapublic/app/plugins/panel/AGENTS.md · 76k | AGENTS.md | agent-behaviour | 16/100 | 3 days ago |
Diff against AGENTS.md Diff against e2e-playwright/alerting-suite/AGENTS.md Diff against e2e-playwright/dashboard-new-layouts/AGENTS.md Diff against e2e-playwright/plugin-e2e/plugin-e2e-api-tests/AGENTS.md Diff against packages/grafana-ui/AGENTS.md Diff against pkg/storage/unified/AGENTS.md Diff against public/app/features/AGENTS.md Diff against public/app/features/alerting/unified/AGENTS.md Diff against public/app/features/expressions/components/SqlExpressions/SqlEditor/AGENTS.md Diff against public/app/plugins/panel/AGENTS.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | 3 days ago | |
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| SkeneTechnologies/skene-cookbookAGENTS.md · 51 | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 2 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| trick77/agents-md-syncAGENTS.md · 2 | AGENTS.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 2 days ago |
