mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
datatrails: track user interactions (#85909)
* chore: add interaction tracking
This commit is contained in:
@@ -10,6 +10,9 @@ import {
|
||||
} from '@grafana/scenes';
|
||||
import { Button } from '@grafana/ui';
|
||||
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
import { getTrailFor } from '../utils';
|
||||
|
||||
export interface AddToFiltersGraphActionState extends SceneObjectState {
|
||||
frame: DataFrame;
|
||||
}
|
||||
@@ -27,17 +30,14 @@ export class AddToFiltersGraphAction extends SceneObjectBase<AddToFiltersGraphAc
|
||||
}
|
||||
|
||||
const labelName = Object.keys(labels)[0];
|
||||
|
||||
variable.setState({
|
||||
filters: [
|
||||
...variable.state.filters,
|
||||
{
|
||||
key: labelName,
|
||||
operator: '=',
|
||||
value: labels[labelName],
|
||||
},
|
||||
],
|
||||
});
|
||||
reportExploreMetrics('label_filter_changed', { label: labelName, action: 'added', cause: 'breakdown' });
|
||||
const trail = getTrailFor(this);
|
||||
const filter = {
|
||||
key: labelName,
|
||||
operator: '=',
|
||||
value: labels[labelName],
|
||||
};
|
||||
trail.addFilterWithoutReportingInteraction(filter);
|
||||
};
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<AddToFiltersGraphAction>) => {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { AutoQueryDef } from '../AutomaticMetricQueries/types';
|
||||
import { BreakdownLabelSelector } from '../BreakdownLabelSelector';
|
||||
import { MetricScene } from '../MetricScene';
|
||||
import { StatusWrapper } from '../StatusWrapper';
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
import { trailDS, VAR_FILTERS, VAR_GROUP_BY, VAR_GROUP_BY_EXP } from '../shared';
|
||||
import { getColorByIndex, getTrailFor } from '../utils';
|
||||
|
||||
@@ -202,6 +203,7 @@ export class BreakdownScene extends SceneObjectBase<BreakdownSceneState> {
|
||||
return;
|
||||
}
|
||||
|
||||
reportExploreMetrics('label_selected', { label: value, cause: 'selector' });
|
||||
const variable = this.getVariable();
|
||||
|
||||
variable.changeValueTo(value);
|
||||
@@ -429,7 +431,9 @@ interface SelectLabelActionState extends SceneObjectState {
|
||||
}
|
||||
export class SelectLabelAction extends SceneObjectBase<SelectLabelActionState> {
|
||||
public onClick = () => {
|
||||
getBreakdownSceneFor(this).onChange(this.state.labelName);
|
||||
const label = this.state.labelName;
|
||||
reportExploreMetrics('label_selected', { label, cause: 'breakdown_panel' });
|
||||
getBreakdownSceneFor(this).onChange(label);
|
||||
};
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<AddToFiltersGraphAction>) => {
|
||||
|
||||
@@ -5,14 +5,15 @@ import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObj
|
||||
import { Field, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { MetricScene } from '../MetricScene';
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
|
||||
import { LayoutType } from './types';
|
||||
|
||||
export interface LayoutSwitcherState extends SceneObjectState {
|
||||
layouts: SceneObject[];
|
||||
options: Array<SelectableValue<LayoutType>>;
|
||||
}
|
||||
|
||||
export type LayoutType = 'single' | 'grid' | 'rows';
|
||||
|
||||
export class LayoutSwitcher extends SceneObjectBase<LayoutSwitcherState> {
|
||||
private getMetricScene() {
|
||||
return sceneGraph.getAncestor(this, MetricScene);
|
||||
@@ -37,8 +38,9 @@ export class LayoutSwitcher extends SceneObjectBase<LayoutSwitcherState> {
|
||||
return activeLayout;
|
||||
}
|
||||
|
||||
public onLayoutChange = (active: LayoutType) => {
|
||||
this.getMetricScene().setState({ layout: active });
|
||||
public onLayoutChange = (layout: LayoutType) => {
|
||||
reportExploreMetrics('breakdown_layout_changed', { layout });
|
||||
this.getMetricScene().setState({ layout });
|
||||
};
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<LayoutSwitcher>) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Stack, Text, TextLink } from '@grafana/ui';
|
||||
import { ALL_VARIABLE_VALUE } from '../../variables/constants';
|
||||
import { MetricScene } from '../MetricScene';
|
||||
import { StatusWrapper } from '../StatusWrapper';
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
import { VAR_DATASOURCE_EXPR, VAR_GROUP_BY } from '../shared';
|
||||
import { getMetricSceneFor, getTrailFor } from '../utils';
|
||||
|
||||
@@ -102,7 +103,8 @@ export class MetricOverviewScene extends SceneObjectBase<MetricOverviewSceneStat
|
||||
event.stopPropagation();
|
||||
sceneGraph.getAncestor(model, MetricScene).setActionView('breakdown');
|
||||
const groupByVar = sceneGraph.lookupVariable(VAR_GROUP_BY, model);
|
||||
if (groupByVar instanceof QueryVariable) {
|
||||
if (groupByVar instanceof QueryVariable && l.label != null) {
|
||||
reportExploreMetrics('label_selected', { label: l.label, cause: 'overview_link' });
|
||||
groupByVar.setState({ value: l.value });
|
||||
}
|
||||
return false;
|
||||
|
||||
1
public/app/features/trails/ActionTabs/types.ts
Normal file
1
public/app/features/trails/ActionTabs/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type LayoutType = 'single' | 'grid' | 'rows';
|
||||
@@ -32,6 +32,7 @@ import { MetricSelectScene } from './MetricSelectScene';
|
||||
import { MetricsHeader } from './MetricsHeader';
|
||||
import { getTrailStore } from './TrailStore/TrailStore';
|
||||
import { MetricDatasourceHelper } from './helpers/MetricDatasourceHelper';
|
||||
import { reportChangeInLabelFilters } from './interactions';
|
||||
import { MetricSelectedEvent, trailDS, VAR_DATASOURCE, VAR_FILTERS } from './shared';
|
||||
|
||||
export interface DataTrailState extends SceneObjectState {
|
||||
@@ -111,10 +112,22 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
|
||||
this.goBackToStep(step);
|
||||
});
|
||||
|
||||
const filtersVariable = sceneGraph.lookupVariable(VAR_FILTERS, this);
|
||||
const stateSubscription =
|
||||
filtersVariable instanceof AdHocFiltersVariable &&
|
||||
filtersVariable?.subscribeToState((newState, prevState) => {
|
||||
if (!this._addingFilterWithoutReportingInteraction) {
|
||||
reportChangeInLabelFilters(newState.filters, prevState.filters);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (!this.state.embedded) {
|
||||
getTrailStore().setRecentTrail(this);
|
||||
}
|
||||
if (stateSubscription) {
|
||||
stateSubscription?.unsubscribe();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,6 +141,25 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Assuming that the change in filter was already reported with a cause other than `'adhoc_filter'`,
|
||||
* this will modify the adhoc filter variable and prevent the automatic reporting which would
|
||||
* normally occur through the call to `reportChangeInLabelFilters`.
|
||||
*/
|
||||
public addFilterWithoutReportingInteraction(filter: AdHocVariableFilter) {
|
||||
const variable = sceneGraph.lookupVariable('filters', this);
|
||||
if (!(variable instanceof AdHocFiltersVariable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._addingFilterWithoutReportingInteraction = true;
|
||||
variable.setState({
|
||||
filters: [...variable.state.filters, filter],
|
||||
});
|
||||
this._addingFilterWithoutReportingInteraction = false;
|
||||
}
|
||||
private _addingFilterWithoutReportingInteraction = false;
|
||||
|
||||
private datasourceHelper = new MetricDatasourceHelper(this);
|
||||
|
||||
public getMetricMetadata(metric?: string) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||
import { Dropdown, Switch, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
|
||||
export interface DataTrailSettingsState extends SceneObjectState {
|
||||
stickyMainGraph?: boolean;
|
||||
isOpen?: boolean;
|
||||
@@ -19,7 +21,9 @@ export class DataTrailSettings extends SceneObjectBase<DataTrailSettingsState> {
|
||||
}
|
||||
|
||||
public onToggleStickyMainGraph = () => {
|
||||
this.setState({ stickyMainGraph: !this.state.stickyMainGraph });
|
||||
const stickyMainGraph = !this.state.stickyMainGraph;
|
||||
reportExploreMetrics('settings_changed', { stickyMainGraph });
|
||||
this.setState({ stickyMainGraph });
|
||||
};
|
||||
|
||||
public onToggleOpen = (isOpen: boolean) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { useStyles2, Tooltip, Stack } from '@grafana/ui';
|
||||
|
||||
import { DataTrail, DataTrailState, getTopSceneFor } from './DataTrail';
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
import { VAR_FILTERS } from './shared';
|
||||
import { getTrailFor, isSceneTimeRangeState } from './utils';
|
||||
|
||||
@@ -124,6 +125,10 @@ export class DataTrailHistory extends SceneObjectBase<DataTrailsHistoryState> {
|
||||
}
|
||||
|
||||
this.stepTransitionInProgress = true;
|
||||
const step = this.state.steps[stepIndex];
|
||||
const type = step.type === 'metric' && step.trailState.metric === undefined ? 'metric-clear' : step.type;
|
||||
reportExploreMetrics('history_step_clicked', { type });
|
||||
|
||||
this.setState({ currentStep: stepIndex });
|
||||
// The URL will update
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { DataTrail } from './DataTrail';
|
||||
import { DataTrailCard } from './DataTrailCard';
|
||||
import { DataTrailsApp } from './DataTrailsApp';
|
||||
import { getTrailStore } from './TrailStore/TrailStore';
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
import { getDatasourceForNewTrail, getUrlForTrail, newMetricsTrail } from './utils';
|
||||
|
||||
export interface DataTrailsHomeState extends SceneObjectState {}
|
||||
@@ -23,14 +24,14 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
|
||||
public onNewMetricsTrail = () => {
|
||||
const app = getAppFor(this);
|
||||
const trail = newMetricsTrail(getDatasourceForNewTrail());
|
||||
|
||||
reportExploreMetrics('exploration_started', { cause: 'new_clicked' });
|
||||
getTrailStore().setRecentTrail(trail);
|
||||
app.goToUrlForTrail(trail);
|
||||
};
|
||||
|
||||
public onSelectTrail = (trail: DataTrail) => {
|
||||
public onSelectTrail = (trail: DataTrail, isBookmark: boolean) => {
|
||||
const app = getAppFor(this);
|
||||
|
||||
reportExploreMetrics('exploration_started', { cause: isBookmark ? 'bookmark_clicked' : 'recent_clicked' });
|
||||
getTrailStore().setRecentTrail(trail);
|
||||
app.goToUrlForTrail(trail);
|
||||
};
|
||||
@@ -41,6 +42,7 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
|
||||
|
||||
const onDelete = (index: number) => {
|
||||
getTrailStore().removeBookmark(index);
|
||||
reportExploreMetrics('bookmark_changed', { action: 'deleted' });
|
||||
setLastDelete(Date.now()); // trigger re-render
|
||||
};
|
||||
|
||||
@@ -50,6 +52,9 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
|
||||
return <Redirect to={getUrlForTrail(trail)} />;
|
||||
}
|
||||
|
||||
const onSelectRecent = (trail: DataTrail) => model.onSelectTrail(trail, false);
|
||||
const onSelectBookmark = (trail: DataTrail) => model.onSelectTrail(trail, true);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Stack direction={'column'} gap={1} alignItems={'start'}>
|
||||
@@ -68,7 +73,7 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
|
||||
<DataTrailCard
|
||||
key={(resolvedTrail.state.key || '') + index}
|
||||
trail={resolvedTrail}
|
||||
onSelect={model.onSelectTrail}
|
||||
onSelect={onSelectRecent}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -84,7 +89,7 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
|
||||
<DataTrailCard
|
||||
key={(resolvedTrail.state.key || '') + index}
|
||||
trail={resolvedTrail}
|
||||
onSelect={model.onSelectTrail}
|
||||
onSelect={onSelectBookmark}
|
||||
onDelete={() => onDelete(index)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DataSourceRef } from '@grafana/schema';
|
||||
import { DashboardModel } from '../../dashboard/state';
|
||||
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
|
||||
import { MetricScene } from '../MetricScene';
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
|
||||
import { DataTrailEmbedded, DataTrailEmbeddedState } from './DataTrailEmbedded';
|
||||
import { SceneDrawerAsScene, launchSceneDrawerInGlobalModal } from './SceneDrawer';
|
||||
@@ -118,9 +119,13 @@ function createClickHandler(item: QueryMetric, dashboard: DashboardScene | Dashb
|
||||
...commonProps,
|
||||
onDismiss: () => dashboard.closeModal(),
|
||||
});
|
||||
reportExploreMetrics('exploration_started', { cause: 'dashboard_panel' });
|
||||
dashboard.showModal(drawerScene);
|
||||
};
|
||||
} else {
|
||||
return () => launchSceneDrawerInGlobalModal(createCommonEmbeddedTrailStateProps(item, dashboard, ds));
|
||||
return () => {
|
||||
reportExploreMetrics('exploration_started', { cause: 'dashboard_panel' });
|
||||
launchSceneDrawerInGlobalModal(createCommonEmbeddedTrailStateProps(item, dashboard, ds));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,15 @@ import { ToolbarButton, Box, Stack, Icon, TabsBar, Tab, useStyles2, LinkButton,
|
||||
import { getExploreUrl } from '../../core/utils/explore';
|
||||
|
||||
import { buildBreakdownActionScene } from './ActionTabs/BreakdownScene';
|
||||
import { LayoutType } from './ActionTabs/LayoutSwitcher';
|
||||
import { buildMetricOverviewScene } from './ActionTabs/MetricOverviewScene';
|
||||
import { buildRelatedMetricsScene } from './ActionTabs/RelatedMetricsScene';
|
||||
import { LayoutType } from './ActionTabs/types';
|
||||
import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine';
|
||||
import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types';
|
||||
import { MAIN_PANEL_MAX_HEIGHT, MAIN_PANEL_MIN_HEIGHT, MetricGraphScene } from './MetricGraphScene';
|
||||
import { ShareTrailButton } from './ShareTrailButton';
|
||||
import { useBookmarkState } from './TrailStore/useBookmarkState';
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
import {
|
||||
ActionViewDefinition,
|
||||
ActionViewType,
|
||||
@@ -148,6 +149,7 @@ export class MetricActionBar extends SceneObjectBase<MetricActionBarState> {
|
||||
};
|
||||
|
||||
public openExploreLink = async () => {
|
||||
reportExploreMetrics('selected_metric_action_clicked', { action: 'open_in_explore' });
|
||||
this.getLinkToExplore().then((link) => {
|
||||
// We use window.open instead of a Link or <a> because we want to compute the explore link when clicking,
|
||||
// if we precompute it we have to keep track of a lot of dependencies
|
||||
@@ -169,7 +171,10 @@ export class MetricActionBar extends SceneObjectBase<MetricActionBarState> {
|
||||
<ToolbarButton
|
||||
variant={'canvas'}
|
||||
tooltip="Remove existing metric and choose a new metric"
|
||||
onClick={() => trail.publishEvent(new MetricSelectedEvent(undefined))}
|
||||
onClick={() => {
|
||||
reportExploreMetrics('selected_metric_action_clicked', { action: 'unselect' });
|
||||
trail.publishEvent(new MetricSelectedEvent(undefined));
|
||||
}}
|
||||
>
|
||||
Select new metric
|
||||
</ToolbarButton>
|
||||
@@ -193,7 +198,11 @@ export class MetricActionBar extends SceneObjectBase<MetricActionBarState> {
|
||||
onClick={toggleBookmark}
|
||||
/>
|
||||
{trail.state.embedded && (
|
||||
<LinkButton href={getUrlForTrail(trail)} variant={'secondary'}>
|
||||
<LinkButton
|
||||
href={getUrlForTrail(trail)}
|
||||
variant={'secondary'}
|
||||
onClick={() => reportExploreMetrics('selected_metric_action_clicked', { action: 'open_from_embedded' })}
|
||||
>
|
||||
Open
|
||||
</LinkButton>
|
||||
)}
|
||||
@@ -207,7 +216,10 @@ export class MetricActionBar extends SceneObjectBase<MetricActionBarState> {
|
||||
key={index}
|
||||
label={tab.displayName}
|
||||
active={actionView === tab.value}
|
||||
onChangeTab={() => metricScene.setActionView(tab.value)}
|
||||
onChangeTab={() => {
|
||||
reportExploreMetrics('metric_action_view_changed', { view: tab.value });
|
||||
metricScene.setActionView(tab.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -27,8 +27,16 @@ import { MetricScene } from './MetricScene';
|
||||
import { SelectMetricAction } from './SelectMetricAction';
|
||||
import { StatusWrapper } from './StatusWrapper';
|
||||
import { getMetricDescription } from './helpers/MetricDatasourceHelper';
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
import { sortRelatedMetrics } from './relatedMetrics';
|
||||
import { getVariablesWithMetricConstant, trailDS, VAR_DATASOURCE, VAR_FILTERS_EXPR, VAR_METRIC_NAMES } from './shared';
|
||||
import {
|
||||
getVariablesWithMetricConstant,
|
||||
MetricSelectedEvent,
|
||||
trailDS,
|
||||
VAR_DATASOURCE,
|
||||
VAR_FILTERS_EXPR,
|
||||
VAR_METRIC_NAMES,
|
||||
} from './shared';
|
||||
import { getFilters, getTrailFor } from './utils';
|
||||
|
||||
interface MetricPanel {
|
||||
@@ -96,6 +104,27 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
||||
// Temp hack when going back to select metric scene and variable updates
|
||||
this.ignoreNextUpdate = true;
|
||||
}
|
||||
|
||||
const trail = getTrailFor(this);
|
||||
|
||||
const metricChangeSubscription = trail.subscribeToEvent(MetricSelectedEvent, (event) => {
|
||||
const { steps, currentStep } = trail.state.history.state;
|
||||
const prevStep = steps[currentStep].parentIndex;
|
||||
const previousMetric = steps[prevStep].trailState.metric;
|
||||
const isRelatedMetricSelector = previousMetric !== undefined;
|
||||
|
||||
const terms = this.state.searchQuery?.split(splitSeparator).filter((part) => part.length > 0);
|
||||
if (event.payload !== undefined) {
|
||||
reportExploreMetrics('metric_selected', {
|
||||
from: isRelatedMetricSelector ? 'related_metrics' : 'metric_list',
|
||||
searchTermCount: terms?.length || 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
metricChangeSubscription.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
private sortedPreviewMetrics() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useLocation } from 'react-use';
|
||||
import { ToolbarButton } from '@grafana/ui';
|
||||
|
||||
import { DataTrail } from './DataTrail';
|
||||
import { reportExploreMetrics } from './interactions';
|
||||
import { getUrlForTrail } from './utils';
|
||||
|
||||
interface ShareTrailButtonState {
|
||||
@@ -17,6 +18,7 @@ export const ShareTrailButton = ({ trail }: ShareTrailButtonState) => {
|
||||
const onShare = () => {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(origin + getUrlForTrail(trail));
|
||||
reportExploreMetrics('selected_metric_action_clicked', { action: 'share_url' });
|
||||
setTooltip('Copied!');
|
||||
setTimeout(() => {
|
||||
setTooltip('Copy url');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { DataTrail } from '../DataTrail';
|
||||
import { reportExploreMetrics } from '../interactions';
|
||||
|
||||
import { getTrailStore } from './TrailStore';
|
||||
|
||||
@@ -21,6 +22,7 @@ export function useBookmarkState(trail: DataTrail) {
|
||||
const isBookmarked = bookmarkIndex != null;
|
||||
|
||||
const toggleBookmark = () => {
|
||||
reportExploreMetrics('bookmark_changed', { action: isBookmarked ? 'toggled_off' : 'toggled_on' });
|
||||
if (isBookmarked) {
|
||||
let indexToRemove = getBookmarkIndex();
|
||||
while (indexToRemove != null) {
|
||||
|
||||
149
public/app/features/trails/interactions.ts
Normal file
149
public/app/features/trails/interactions.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { AdHocVariableFilter } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
|
||||
import { LayoutType } from './ActionTabs/types';
|
||||
import { TrailStepType } from './DataTrailsHistory';
|
||||
import { ActionViewType } from './shared';
|
||||
|
||||
// prettier-ignore
|
||||
type Interactions = {
|
||||
// User selected a label to view its breakdown.
|
||||
label_selected: {
|
||||
label: string;
|
||||
cause: (
|
||||
// By clicking the "select" button on that label's breakdown panel
|
||||
| 'breakdown_panel'
|
||||
// By clicking the label link on the overview
|
||||
| 'overview_link'
|
||||
// By clicking on the label selector at the top of the breakdown
|
||||
| 'selector'
|
||||
);
|
||||
};
|
||||
// User changed a label filter.
|
||||
label_filter_changed: {
|
||||
label: string;
|
||||
action: 'added' | 'removed' | 'changed';
|
||||
cause: 'breakdown' | 'adhoc_filter';
|
||||
};
|
||||
// User changed the breakdown layout
|
||||
breakdown_layout_changed: { layout: LayoutType };
|
||||
// A metric exploration has started due to one of the following causes
|
||||
exploration_started: {
|
||||
cause: (
|
||||
// a bookmark was clicked from the home page
|
||||
| 'bookmark_clicked'
|
||||
// a recent exploration was clicked from the home page
|
||||
| 'recent_clicked'
|
||||
// "new exploration" was clicked from the home page
|
||||
| 'new_clicked'
|
||||
// the page was loaded (or reloaded) from a URL which matches one of the recent explorations
|
||||
| 'loaded_local_recent_url'
|
||||
// the page was loaded from a URL which did not match one of the recent explorations, and is assumed shared
|
||||
| 'loaded_shared_url'
|
||||
// the exploration was opened from the dashboard panel menu and is embedded in a drawer
|
||||
| 'dashboard_panel'
|
||||
);
|
||||
};
|
||||
// A user has changed a bookmark
|
||||
bookmark_changed: {
|
||||
action: (
|
||||
// Toggled on or off from the bookmark icon
|
||||
| 'toggled_on'
|
||||
| 'toggled_off'
|
||||
// Deleted from the homepage bookmarks list
|
||||
| 'deleted'
|
||||
);
|
||||
};
|
||||
// User changes metric explore settings
|
||||
settings_changed: { stickyMainGraph?: boolean };
|
||||
// User clicks on history nodes to navigate exploration history
|
||||
history_step_clicked: {
|
||||
type: (
|
||||
// One of the the standard step types
|
||||
| TrailStepType
|
||||
// The special metric step type that is created when the user de-selects the current metric
|
||||
| 'metric-clear'
|
||||
);
|
||||
};
|
||||
// User clicks on tab to change the action view
|
||||
metric_action_view_changed: { view: ActionViewType };
|
||||
// User clicks on one of the action buttons associated with a selected metric
|
||||
selected_metric_action_clicked: {
|
||||
action: (
|
||||
// Opens the metric queries in Explore
|
||||
| 'open_in_explore'
|
||||
// Clicks on the share URL button
|
||||
| 'share_url'
|
||||
// Deselects the current selected metrics by clicking the "Select new metric" button
|
||||
| 'unselect'
|
||||
// When in embedded mode, clicked to open the exploration from the embedded view
|
||||
| 'open_from_embedded'
|
||||
);
|
||||
};
|
||||
// User selects a metric
|
||||
metric_selected: {
|
||||
from: (
|
||||
// By clicking "Select" on a metric panel when on the no-metric-selected metrics list view
|
||||
| 'metric_list'
|
||||
// By clicking "Select" on a metric panel when on the related metrics tab
|
||||
| 'related_metrics'
|
||||
);
|
||||
// The number of search terms activated when the selection was made
|
||||
searchTermCount: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
const PREFIX = 'grafana_explore_metrics_';
|
||||
|
||||
export function reportExploreMetrics<E extends keyof Interactions, P extends Interactions[E]>(event: E, payload: P) {
|
||||
reportInteraction(`${PREFIX}${event}`, payload);
|
||||
}
|
||||
|
||||
/** Detect the single change in filters and report the event, assuming it came from manipulating the adhoc filter */
|
||||
export function reportChangeInLabelFilters(newFilters: AdHocVariableFilter[], oldFilters: AdHocVariableFilter[]) {
|
||||
if (newFilters.length === oldFilters.length) {
|
||||
for (const oldFilter of oldFilters) {
|
||||
for (const newFilter of newFilters) {
|
||||
if (oldFilter.key === newFilter.key) {
|
||||
if (oldFilter.value !== newFilter.value) {
|
||||
reportExploreMetrics('label_filter_changed', {
|
||||
label: oldFilter.key,
|
||||
action: 'changed',
|
||||
cause: 'adhoc_filter',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (newFilters.length < oldFilters.length) {
|
||||
for (const oldFilter of oldFilters) {
|
||||
let foundOldLabel = false;
|
||||
for (const newFilter of newFilters) {
|
||||
if (oldFilter.key === newFilter.key) {
|
||||
foundOldLabel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundOldLabel) {
|
||||
reportExploreMetrics('label_filter_changed', {
|
||||
label: oldFilter.key,
|
||||
action: 'removed',
|
||||
cause: 'adhoc_filter',
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const newFilter of newFilters) {
|
||||
let foundNewLabel = false;
|
||||
for (const oldFilter of oldFilters) {
|
||||
if (oldFilter.key === newFilter.key) {
|
||||
foundNewLabel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundNewLabel) {
|
||||
reportExploreMetrics('label_filter_changed', { label: newFilter.key, action: 'added', cause: 'adhoc_filter' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user