scripts/cli/analytics/README.md
The analytics framework is a typed, self-documenting system for defining and tracking product analytics events in Grafana. It is built around defineFeatureEvents, a factory exported from @grafana/runtime/unstable, which produces strongly-typed event reporters. Events defined with this factory are automatically discoverable by the report script in this folder.
The framework is split into two parts with distinct responsibilities:
packages/grafana-runtime/src/analyticsFramework/): production code that ships with Grafana. It exports the defineFeatureEvents factory and the EventProperty base type.scripts/cli/analytics/): a developer tool that runs offline and is never bundled with the application. It reads the TypeScript AST to find every event declared with the factory and writes a human-readable catalogue.The framework ensures that:
{repo}_{feature}_{eventName}, e.g. grafana_dashboard_library_loaded).define-feature-events)..github/CODEOWNERS based on the file's location.Create a events.ts file next to your feature. Ownership of events is resolved from .github/CODEOWNERS, so it's important this file has the same code owners as the feature itself.
Each interface must extend EventProperty from @grafana/runtime/unstable, and every property must have a JSDoc comment:
import { type EventProperty } from '@grafana/runtime/unstable';
export interface ThemeChanged extends EventProperty {
/** Whether the preference being changed belongs to an org, team, or individual user. */
preferenceType: 'org' | 'team' | 'user';
/** The theme the user switched to. */
toTheme: string;
}
Call defineFeatureEvents with two required string literal arguments — the repo name and the feature name. There is an optional third argument to set default properties (merged into every event in the namespace):
// Without default properties
const createPreferencesEvent = defineFeatureEvents('grafana', 'preferences');
// With default properties (e.g. a schema version sent with every event)
const createLibraryEvent = defineFeatureEvents('grafana', 'dashboard_library', {
/** Version of the event schema, used to handle breaking changes in the properties contract. */
schema_version: 1,
});
This produces event names of the form grafana_{feature}_{eventName}.
Events can be declared in two ways. Both are fully supported by the report script.
Each event is exported as its own const. This works well for small, cohesive sets of events. The JSDoc comment above each export becomes its description in the report.
import { defineFeatureEvents } from '@grafana/runtime/unstable';
import { type ThemeChanged, type LanguageChanged } from './types';
const createSharedPreferencesEvents = defineFeatureEvents('grafana', 'preferences');
/** Fired immediately when the user selects a new theme from the theme picker, before saving. */
export const themeChanged = createSharedPreferencesEvents<ThemeChanged>('theme_changed');
/** Fired immediately when the user selects a new language from the language picker, before saving. */
export const languageChanged = createSharedPreferencesEvents<LanguageChanged>('language_changed');
All events are collected into a single exported const object. This is useful for larger feature areas where events are used together. Each property of the object must have a JSDoc comment. The object can also spread another events object to inherit and override entries:
import { defineFeatureEvents } from '@grafana/runtime/unstable';
import { type LoadedProperties, type ItemClickedProperties } from './types';
const newDashboardLibraryInteraction = defineFeatureEvents('grafana', 'dashboard_library', {
/** Version of the event schema. */
schema_version: 1,
});
/**
* Analytics events for the Dashboard Library feature.
*/
export const NewDashboardLibraryInteractions = {
/** Fired when the library panel finishes rendering and its items are visible. */
loaded: newDashboardLibraryInteraction<LoadedProperties>('loaded'),
/** Fired when the user selects an item from the library list. */
itemClicked: newDashboardLibraryInteraction<ItemClickedProperties>('item_clicked'),
};
/**
* Dashboard Library events scoped to the Template Dashboards variant.
*/
export const NewTemplateDashboardInteractions = {
...NewDashboardLibraryInteractions,
/** Fired when the Template Dashboards view finishes loading. */
loaded: newDashboardLibraryInteraction<LoadedProperties>('loaded'),
};
Direct property assignments override spread entries with the same event name, mirroring JavaScript spread semantics.
Event files must live inside the feature's own folder, not in a shared or generic location. Ownership is resolved automatically from .github/CODEOWNERS using the file path, so placing events inside the feature folder is all that is needed — no manual owner tag required.
The recommended structure is:
public/app/features/my-feature/
analytics/
main.ts ← factory + event declarations
types.ts ← EventProperty interfaces
MyFeatureComponent.tsx
...
Import the events and call them when the action occurs:
// Flat export
import { themeChanged } from './analytics/main';
themeChanged({ preferenceType: 'user', toTheme: 'dark' });
// Grouped object export
import { NewDashboardLibraryInteractions } from './analytics/main';
NewDashboardLibraryInteractions.loaded({ numberOfItems: 42, datasourceTypes: ['prometheus'] });
Each call internally invokes reportInteraction from @grafana/runtime, forwarding all properties plus any default properties defined on the factory (e.g. schema_version).
Run the following command from the repo root:
yarn analytics-report
Example:
# Analytics report
This report contains all the analytics events that are defined in the project.
## Events
### dashboard_library
#### `grafana_dashboard_library_loaded`
**Description**: Fired when the library panel finishes rendering and its items are visible.
**Owner:** @grafana/sharing
**Properties**:
| name | type | description |
| --------------- | ---------- | ---------------------------------------------------------- |
| schema_version | `number` | Version of the event schema. |
| numberOfItems | `number` | Total number of items visible in the library at load time. |
| datasourceTypes | `string[]` | Plugin IDs of data sources referenced by the loaded items. |
The output is a Markdown file grouped by feature. Each event section contains:
grafana_dashboard_library_loaded)..github/CODEOWNERS based on the path of the file that declares the events.schema_version) are listed first.