Dashboard interactions reporting refactor, DashboardScene interactions handling (#79078)

* Refactor dashboard interactions tracking

* Local scenes tmp

* Use latest scenes

* Review refactor

* Fix enterprise tests

* Reporting parity for panel description
This commit is contained in:
Dominik Prokop 2023-12-06 16:24:15 +01:00 committed by GitHub
parent a9a18a4b6d
commit bfde6f2c8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 385 additions and 185 deletions

View File

@ -256,7 +256,7 @@
"@grafana/lezer-traceql": "0.0.11", "@grafana/lezer-traceql": "0.0.11",
"@grafana/monaco-logql": "^0.0.7", "@grafana/monaco-logql": "^0.0.7",
"@grafana/runtime": "workspace:*", "@grafana/runtime": "workspace:*",
"@grafana/scenes": "^1.26.0", "@grafana/scenes": "1.27.0",
"@grafana/schema": "workspace:*", "@grafana/schema": "workspace:*",
"@grafana/ui": "workspace:*", "@grafana/ui": "workspace:*",
"@kusto/monaco-kusto": "^7.4.0", "@kusto/monaco-kusto": "^7.4.0",

View File

@ -13,14 +13,25 @@ interface LibraryVizPanelState extends SceneObjectState {
title: string; title: string;
uid: string; uid: string;
name: string; name: string;
panel?: VizPanel; panel: VizPanel;
} }
export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> { export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
static Component = LibraryPanelRenderer; static Component = LibraryPanelRenderer;
constructor({ uid, title, key, name }: Pick<LibraryVizPanelState, 'uid' | 'title' | 'key' | 'name'>) { constructor({ uid, title, key, name }: Pick<LibraryVizPanelState, 'uid' | 'title' | 'key' | 'name'>) {
super({ uid, title, key, name }); super({
uid,
title,
key,
name,
panel: new VizPanel({
title,
menu: new VizPanelMenu({
$behaviors: [panelMenuBehavior],
}),
}),
});
this.addActivationHandler(this._onActivate); this.addActivationHandler(this._onActivate);
} }
@ -30,8 +41,7 @@ export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
}; };
private async loadLibraryPanelFromPanelModel() { private async loadLibraryPanelFromPanelModel() {
const { title } = this.state; const vizPanel = this.state.panel;
let vizPanel = new VizPanel({ title });
try { try {
const libPanel = await getLibraryPanel(this.state.uid, true); const libPanel = await getLibraryPanel(this.state.uid, true);
const libPanelModel = new PanelModel(libPanel.model); const libPanelModel = new PanelModel(libPanel.model);
@ -40,10 +50,8 @@ export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
fieldConfig: libPanelModel.fieldConfig, fieldConfig: libPanelModel.fieldConfig,
pluginVersion: libPanelModel.pluginVersion, pluginVersion: libPanelModel.pluginVersion,
displayMode: libPanelModel.transparent ? 'transparent' : undefined, displayMode: libPanelModel.transparent ? 'transparent' : undefined,
description: libPanelModel.description,
$data: createPanelDataProvider(libPanelModel), $data: createPanelDataProvider(libPanelModel),
menu: new VizPanelMenu({
$behaviors: [panelMenuBehavior],
}),
}); });
} catch (err) { } catch (err) {
vizPanel.setState({ vizPanel.setState({

View File

@ -8,6 +8,7 @@ import { t } from 'app/core/internationalization';
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton'; import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
import { ShareModal } from '../sharing/ShareModal'; import { ShareModal } from '../sharing/ShareModal';
import { DashboardInteractions } from '../utils/interactions';
import { DashboardScene } from './DashboardScene'; import { DashboardScene } from './DashboardScene';
@ -33,6 +34,7 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
iconType={meta.isStarred ? 'mono' : 'default'} iconType={meta.isStarred ? 'mono' : 'default'}
iconSize="lg" iconSize="lg"
onClick={() => { onClick={() => {
DashboardInteractions.toolbarFavoritesClick();
dashboard.onStarDashboard(); dashboard.onStarDashboard();
}} }}
/> />
@ -45,6 +47,7 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
icon="share-alt" icon="share-alt"
iconSize="lg" iconSize="lg"
onClick={() => { onClick={() => {
DashboardInteractions.toolbarShareClick();
dashboard.showModal(new ShareModal({ dashboardRef: dashboard.getRef() })); dashboard.showModal(new ShareModal({ dashboardRef: dashboard.getRef() }));
}} }}
/> />
@ -65,7 +68,9 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
if (viewPanelScene) { if (viewPanelScene) {
toolbarActions.push( toolbarActions.push(
<Button <Button
onClick={() => locationService.partial({ viewPanel: null })} onClick={() => {
locationService.partial({ viewPanel: null });
}}
tooltip="" tooltip=""
key="back" key="back"
variant="primary" variant="primary"
@ -82,7 +87,9 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
if (dashboard.canEditDashboard()) { if (dashboard.canEditDashboard()) {
toolbarActions.push( toolbarActions.push(
<Button <Button
onClick={dashboard.onEnterEditMode} onClick={() => {
dashboard.onEnterEditMode();
}}
tooltip="Enter edit mode" tooltip="Enter edit mode"
key="edit" key="edit"
variant="primary" variant="primary"
@ -96,17 +103,40 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
} else { } else {
if (dashboard.canEditDashboard()) { if (dashboard.canEditDashboard()) {
toolbarActions.push( toolbarActions.push(
<Button onClick={dashboard.onSave} tooltip="Save as copy" fill="text" key="save-as"> <Button
onClick={() => {
dashboard.onSave();
}}
tooltip="Save as copy"
fill="text"
key="save-as"
>
Save as Save as
</Button> </Button>
); );
toolbarActions.push( toolbarActions.push(
<Button onClick={dashboard.onDiscard} tooltip="Discard changes" fill="text" key="discard" variant="destructive"> <Button
onClick={() => {
dashboard.onDiscard();
}}
tooltip="Discard changes"
fill="text"
key="discard"
variant="destructive"
>
Discard Discard
</Button> </Button>
); );
toolbarActions.push( toolbarActions.push(
<Button onClick={dashboard.onSave} tooltip="Save changes" key="save" disabled={!isDirty}> <Button
onClick={() => {
DashboardInteractions.toolbarSaveClick();
dashboard.onSave();
}}
tooltip="Save changes"
key="save"
disabled={!isDirty}
>
Save Save
</Button> </Button>
); );

View File

@ -5,7 +5,7 @@ import {
PluginExtensionPoints, PluginExtensionPoints,
getTimeZone, getTimeZone,
} from '@grafana/data'; } from '@grafana/data';
import { config, getPluginLinkExtensions, locationService, reportInteraction } from '@grafana/runtime'; import { config, getPluginLinkExtensions, locationService } from '@grafana/runtime';
import { import {
LocalValueVariable, LocalValueVariable,
SceneDataTransformer, SceneDataTransformer,
@ -24,6 +24,7 @@ import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils';
import { addDataTrailPanelAction } from 'app/features/trails/dashboardIntegration'; import { addDataTrailPanelAction } from 'app/features/trails/dashboardIntegration';
import { ShareModal } from '../sharing/ShareModal'; import { ShareModal } from '../sharing/ShareModal';
import { DashboardInteractions } from '../utils/interactions';
import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders'; import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getPanelIdForVizPanel } from '../utils/utils'; import { getPanelIdForVizPanel } from '../utils/utils';
@ -54,7 +55,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
text: t('panel.header-menu.view', `View`), text: t('panel.header-menu.view', `View`),
iconClassName: 'eye', iconClassName: 'eye',
shortcut: 'v', shortcut: 'v',
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'view' }), onClick: () => DashboardInteractions.panelMenuItemClicked('view'),
href: getViewPanelUrl(panel), href: getViewPanelUrl(panel),
}); });
@ -65,7 +66,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
text: t('panel.header-menu.edit', `Edit`), text: t('panel.header-menu.edit', `Edit`),
iconClassName: 'eye', iconClassName: 'eye',
shortcut: 'e', shortcut: 'e',
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'edit' }), onClick: () => () => DashboardInteractions.panelMenuItemClicked('edit'),
href: getDashboardUrl({ href: getDashboardUrl({
uid: dashboard.state.uid, uid: dashboard.state.uid,
subPath: `/panel-edit/${panelId}`, subPath: `/panel-edit/${panelId}`,
@ -79,7 +80,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
text: t('panel.header-menu.share', `Share`), text: t('panel.header-menu.share', `Share`),
iconClassName: 'share-alt', iconClassName: 'share-alt',
onClick: () => { onClick: () => {
reportInteraction('dashboards_panelheader_menu', { item: 'share' }); DashboardInteractions.panelMenuItemClicked('share');
dashboard.showModal(new ShareModal({ panelRef: panel.getRef(), dashboardRef: dashboard.getRef() })); dashboard.showModal(new ShareModal({ panelRef: panel.getRef(), dashboardRef: dashboard.getRef() }));
}, },
shortcut: 'p s', shortcut: 'p s',
@ -92,7 +93,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
text: t('panel.header-menu.create-library-panel', `Create library panel`), text: t('panel.header-menu.create-library-panel', `Create library panel`),
iconClassName: 'share-alt', iconClassName: 'share-alt',
onClick: () => { onClick: () => {
reportInteraction('dashboards_panelheader_menu', { item: 'createLibraryPanel' }); DashboardInteractions.panelMenuItemClicked('createLibraryPanel');
dashboard.showModal( dashboard.showModal(
new ShareModal({ new ShareModal({
panelRef: panel.getRef(), panelRef: panel.getRef(),
@ -115,7 +116,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
text: t('panel.header-menu.explore', `Explore`), text: t('panel.header-menu.explore', `Explore`),
iconClassName: 'compass', iconClassName: 'compass',
shortcut: 'p x', shortcut: 'p x',
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'explore' }), onClick: () => DashboardInteractions.panelMenuItemClicked('explore'),
href: exploreUrl, href: exploreUrl,
}); });
} }
@ -127,7 +128,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
onClick: (e) => { onClick: (e) => {
e.preventDefault(); e.preventDefault();
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data }); locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Data }); DashboardInteractions.panelMenuInspectClicked(InspectTab.Data);
}, },
}); });
@ -138,7 +139,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
onClick: (e) => { onClick: (e) => {
e.preventDefault(); e.preventDefault();
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Query }); locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Query });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Query }); DashboardInteractions.panelMenuInspectClicked(InspectTab.Query);
}, },
}); });
} }
@ -150,7 +151,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
onClick: (e) => { onClick: (e) => {
e.preventDefault(); e.preventDefault();
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.JSON }); locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.JSON });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.JSON }); DashboardInteractions.panelMenuInspectClicked(InspectTab.JSON);
}, },
}); });
@ -162,7 +163,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
onClick: (e) => { onClick: (e) => {
if (!e.isDefaultPrevented()) { if (!e.isDefaultPrevented()) {
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data }); locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Data }); DashboardInteractions.panelMenuInspectClicked(InspectTab.Data);
} }
}, },
subMenu: inspectSubMenu.length > 0 ? inspectSubMenu : undefined, subMenu: inspectSubMenu.length > 0 ? inspectSubMenu : undefined,
@ -223,7 +224,7 @@ export function getPanelLinksBehavior(panel: PanelModel) {
const links = panelLinks.map((panelLink) => ({ const links = panelLinks.map((panelLink) => ({
...panelLink, ...panelLink,
onClick: (e: any, origin: any) => { onClick: (e: any, origin: any) => {
reportInteraction('dashboards_panelheader_datalink_clicked', { has_multiple_links: panelLinks.length > 1 }); DashboardInteractions.panelLinkClicked({ has_multiple_links: panelLinks.length > 1 });
panelLink.onClick?.(e, origin); panelLink.onClick?.(e, origin);
}, },
})); }));

View File

@ -118,7 +118,7 @@ describe('transformSaveModelToScene', () => {
const scene = createDashboardSceneFromDashboardModel(oldModel); const scene = createDashboardSceneFromDashboardModel(oldModel);
expect(scene.state.$behaviors).toHaveLength(2); expect(scene.state.$behaviors).toHaveLength(3);
expect(scene.state.$behaviors![1]).toBeInstanceOf(behaviors.CursorSync); expect(scene.state.$behaviors![1]).toBeInstanceOf(behaviors.CursorSync);
expect((scene.state.$behaviors![1] as behaviors.CursorSync).state.sync).toEqual(DashboardCursorSync.Crosshair); expect((scene.state.$behaviors![1] as behaviors.CursorSync).state.sync).toEqual(DashboardCursorSync.Crosshair);
}); });

View File

@ -26,6 +26,7 @@ import {
SceneDataLayerControls, SceneDataLayerControls,
AdHocFilterSet, AdHocFilterSet,
TextBoxVariable, TextBoxVariable,
UserActionEvent,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardDTO } from 'app/types'; import { DashboardDTO } from 'app/types';
@ -44,6 +45,7 @@ import { PanelTimeRange } from '../scene/PanelTimeRange';
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
import { createPanelDataProvider } from '../utils/createPanelDataProvider'; import { createPanelDataProvider } from '../utils/createPanelDataProvider';
import { DashboardInteractions } from '../utils/interactions';
import { import {
getCurrentValueForOldIntervalModel, getCurrentValueForOldIntervalModel,
getIntervalsFromOldIntervalModel, getIntervalsFromOldIntervalModel,
@ -223,7 +225,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
); );
} }
return new DashboardScene({ const dashboardScene = new DashboardScene({
title: oldModel.title, title: oldModel.title,
tags: oldModel.tags || [], tags: oldModel.tags || [],
links: oldModel.links || [], links: oldModel.links || [],
@ -249,6 +251,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
new behaviors.CursorSync({ new behaviors.CursorSync({
sync: oldModel.graphTooltip, sync: oldModel.graphTooltip,
}), }),
registerPanelInteractionsReporter,
], ],
$data: $data:
layers.length > 0 layers.length > 0
@ -272,6 +275,8 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
}), }),
], ],
}); });
return dashboardScene;
} }
export function createSceneVariableFromVariableModel(variable: TypedVariableModel): SceneVariable { export function createSceneVariableFromVariableModel(variable: TypedVariableModel): SceneVariable {
@ -368,13 +373,15 @@ export function buildGridItemForLibPanel(panel: PanelModel) {
return null; return null;
} }
const body = new LibraryVizPanel({
title: panel.title,
uid: panel.libraryPanel.uid,
name: panel.libraryPanel.name,
key: getVizPanelKeyForPanelId(panel.id),
});
return new SceneGridItem({ return new SceneGridItem({
body: new LibraryVizPanel({ body,
title: panel.title,
uid: panel.libraryPanel.uid,
name: panel.libraryPanel.name,
key: getVizPanelKeyForPanelId(panel.id),
}),
y: panel.gridPos.y, y: panel.gridPos.y,
x: panel.gridPos.x, x: panel.gridPos.x,
width: panel.gridPos.w, width: panel.gridPos.w,
@ -438,14 +445,51 @@ export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike {
}); });
} }
const body = new VizPanel(vizPanelState);
return new SceneGridItem({ return new SceneGridItem({
key: `grid-item-${panel.id}`, key: `grid-item-${panel.id}`,
x: panel.gridPos.x, x: panel.gridPos.x,
y: panel.gridPos.y, y: panel.gridPos.y,
width: panel.gridPos.w, width: panel.gridPos.w,
height: panel.gridPos.h, height: panel.gridPos.h,
body: new VizPanel(vizPanelState), body,
}); });
} }
const isAdhocVariable = (v: VariableModel): v is AdHocVariableModel => v.type === 'adhoc'; const isAdhocVariable = (v: VariableModel): v is AdHocVariableModel => v.type === 'adhoc';
const getLimitedDescriptionReporter = () => {
const reportedPanels: string[] = [];
return (key: string) => {
if (reportedPanels.includes(key)) {
return;
}
reportedPanels.push(key);
DashboardInteractions.panelDescriptionShown();
};
};
function registerPanelInteractionsReporter(scene: DashboardScene) {
const descriptionReporter = getLimitedDescriptionReporter();
// Subscriptions set with subscribeToEvent are automatically unsubscribed when the scene deactivated
scene.subscribeToEvent(UserActionEvent, (e) => {
const { interaction } = e.payload;
switch (interaction) {
case 'panel-description-shown':
descriptionReporter(e.payload.origin.state.key || '');
break;
case 'panel-status-message-clicked':
DashboardInteractions.panelStatusMessageClicked();
break;
case 'panel-cancel-query-clicked':
DashboardInteractions.panelCancelQueryClicked();
break;
case 'panel-menu-shown':
DashboardInteractions.panelMenuShown();
break;
}
});
}

View File

@ -3,15 +3,16 @@ import React from 'react';
import { useAsync } from 'react-use'; import { useAsync } from 'react-use';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { reportInteraction } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, SceneObjectRef } from '@grafana/scenes'; import { SceneComponentProps, SceneObjectBase, SceneObjectRef } from '@grafana/scenes';
import { Button, ClipboardButton, CodeEditor, Field, Modal, Switch, VerticalGroup } from '@grafana/ui'; import { Button, ClipboardButton, CodeEditor, Field, Modal, Switch, VerticalGroup } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
import { DashboardInteractions } from '../utils/interactions';
import { SceneShareTabState } from './types'; import { SceneShareTabState } from './types';
@ -24,7 +25,7 @@ interface ShareExportTabState extends SceneShareTabState {
} }
export class ShareExportTab extends SceneObjectBase<ShareExportTabState> { export class ShareExportTab extends SceneObjectBase<ShareExportTabState> {
public tabId = 'Export'; public tabId = shareDashboardType.export;
static Component = ShareExportTabRenderer; static Component = ShareExportTabRenderer;
private _exporter = new DashboardExporter(); private _exporter = new DashboardExporter();
@ -83,7 +84,9 @@ export class ShareExportTab extends SceneObjectBase<ShareExportTabState> {
title = dashboardJson.title; title = dashboardJson.title;
} }
saveAs(blob, `${title}-${time}.json`); saveAs(blob, `${title}-${time}.json`);
reportInteraction('dashboards_sharing_export_download_json_clicked', { externally: isSharingExternally }); DashboardInteractions.exportDownloadJsonClicked({
externally: isSharingExternally,
});
} }
} }

View File

@ -3,6 +3,7 @@ import React from 'react';
import { SceneComponentProps, SceneGridItem, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; import { SceneComponentProps, SceneGridItem, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel'; import { ShareLibraryPanel } from 'app/features/dashboard/components/ShareModal/ShareLibraryPanel';
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
@ -17,7 +18,7 @@ export interface ShareLibraryPanelTabState extends SceneShareTabState {
} }
export class ShareLibraryPanelTab extends SceneObjectBase<ShareLibraryPanelTabState> { export class ShareLibraryPanelTab extends SceneObjectBase<ShareLibraryPanelTabState> {
public tabId = 'Library panel'; public tabId = shareDashboardType.libraryPanel;
static Component = ShareLibraryPanelTabRenderer; static Component = ShareLibraryPanelTabRenderer;
public getTabLabel() { public getTabLabel() {

View File

@ -2,15 +2,17 @@ import React from 'react';
import { dateTime, UrlQueryMap } from '@grafana/data'; import { dateTime, UrlQueryMap } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { config, locationService, reportInteraction } from '@grafana/runtime'; import { config, locationService } from '@grafana/runtime';
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel, sceneGraph } from '@grafana/scenes'; import { SceneComponentProps, SceneObjectBase, SceneObjectRef, VizPanel, sceneGraph } from '@grafana/scenes';
import { TimeZone } from '@grafana/schema'; import { TimeZone } from '@grafana/schema';
import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, Switch } from '@grafana/ui'; import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, Switch } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { createShortLink } from 'app/core/utils/shortLinks'; import { createShortLink } from 'app/core/utils/shortLinks';
import { ThemePicker } from 'app/features/dashboard/components/ShareModal/ThemePicker'; import { ThemePicker } from 'app/features/dashboard/components/ShareModal/ThemePicker';
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { DashboardInteractions } from '../utils/interactions';
import { getDashboardUrl } from '../utils/urlBuilders'; import { getDashboardUrl } from '../utils/urlBuilders';
import { SceneShareTabState } from './types'; import { SceneShareTabState } from './types';
@ -28,7 +30,7 @@ interface ShareOptions {
} }
export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> { export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
public tabId = 'Link'; public tabId = shareDashboardType.link;
static Component = ShareLinkTabRenderer; static Component = ShareLinkTabRenderer;
@ -119,7 +121,7 @@ export class ShareLinkTab extends SceneObjectBase<ShareLinkTabState> {
}; };
onCopy() { onCopy() {
reportInteraction('dashboards_sharing_link_copy_clicked', { DashboardInteractions.shareLinkCopied({
currentTimeRange: this.state.useLockedTime, currentTimeRange: this.state.useLockedTime,
theme: this.state.selectedTheme, theme: this.state.selectedTheme,
shortenURL: this.state.useShortUrl, shortenURL: this.state.useShortUrl,

View File

@ -7,6 +7,7 @@ import { contextSrv } from 'app/core/core';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { DashboardInteractions } from '../utils/interactions';
import { getDashboardSceneFor } from '../utils/utils'; import { getDashboardSceneFor } from '../utils/utils';
import { ShareExportTab } from './ShareExportTab'; import { ShareExportTab } from './ShareExportTab';
@ -32,7 +33,7 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
constructor(state: Omit<ShareModalState, 'activeTab'> & { activeTab?: string }) { constructor(state: Omit<ShareModalState, 'activeTab'> & { activeTab?: string }) {
super({ super({
activeTab: 'Link', activeTab: 'link',
...state, ...state,
}); });
@ -73,6 +74,7 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
}; };
onChangeTab: ComponentProps<typeof ModalTabsHeader>['onChangeTab'] = (tab) => { onChangeTab: ComponentProps<typeof ModalTabsHeader>['onChangeTab'] = (tab) => {
DashboardInteractions.sharingTabChanged({ item: tab.value });
this.setState({ activeTab: tab.value }); this.setState({ activeTab: tab.value });
}; };
} }

View File

@ -4,7 +4,7 @@ import { TimeRange } from '@grafana/data';
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { ShareEmbed } from 'app/features/dashboard/components/ShareModal/ShareEmbed'; import { ShareEmbed } from 'app/features/dashboard/components/ShareModal/ShareEmbed';
import { buildParams } from 'app/features/dashboard/components/ShareModal/utils'; import { buildParams, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { getDashboardUrl } from '../utils/urlBuilders'; import { getDashboardUrl } from '../utils/urlBuilders';
@ -18,7 +18,7 @@ export interface SharePanelEmbedTabState extends SceneShareTabState {
} }
export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState> { export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState> {
public tabId = 'Embed'; public tabId = shareDashboardType.embed;
static Component = SharePanelEmbedTabRenderer; static Component = SharePanelEmbedTabRenderer;
public constructor(state: SharePanelEmbedTabState) { public constructor(state: SharePanelEmbedTabState) {

View File

@ -2,14 +2,16 @@ import React from 'react';
import useAsyncFn from 'react-use/lib/useAsyncFn'; import useAsyncFn from 'react-use/lib/useAsyncFn';
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { getBackendSrv, reportInteraction } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
import { Button, ClipboardButton, Field, Input, Modal, RadioButtonGroup } from '@grafana/ui'; import { Button, ClipboardButton, Field, Input, Modal, RadioButtonGroup } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv'; import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv';
import { DashboardScene } from '../scene/DashboardScene'; import { DashboardScene } from '../scene/DashboardScene';
import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel'; import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel';
import { DashboardInteractions } from '../utils/interactions';
import { SceneShareTabState } from './types'; import { SceneShareTabState } from './types';
@ -48,7 +50,7 @@ export interface ShareSnapshotTabState extends SceneShareTabState {
} }
export class ShareSnapshotTab extends SceneObjectBase<ShareSnapshotTabState> { export class ShareSnapshotTab extends SceneObjectBase<ShareSnapshotTabState> {
public tabId = 'Snapshot'; public tabId = shareDashboardType.snapshot;
static Component = ShareSnapshoTabRenderer; static Component = ShareSnapshoTabRenderer;
public constructor(state: ShareSnapshotTabState) { public constructor(state: ShareSnapshotTabState) {
@ -123,13 +125,9 @@ export class ShareSnapshotTab extends SceneObjectBase<ShareSnapshotTabState> {
return results; return results;
} finally { } finally {
if (external) { if (external) {
reportInteraction('dashboards_sharing_snapshot_publish_clicked', { DashboardInteractions.publishSnapshotClicked({ expires: cmdData.expires });
expires: cmdData.expires,
});
} else { } else {
reportInteraction('dashboards_sharing_snapshot_local_clicked', { DashboardInteractions.publishSnapshotLocalClicked({ expires: cmdData.expires });
expires: cmdData.expires,
});
} }
} }
}; };

View File

@ -5,6 +5,7 @@ import { t } from 'app/core/internationalization';
import { useGetPublicDashboardQuery } from 'app/features/dashboard/api/publicDashboardApi'; import { useGetPublicDashboardQuery } from 'app/features/dashboard/api/publicDashboardApi';
import { Loader } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard'; import { Loader } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard';
import { publicDashboardPersisted } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils'; import { publicDashboardPersisted } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils';
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { DashboardScene } from '../../scene/DashboardScene'; import { DashboardScene } from '../../scene/DashboardScene';
import { SceneShareTabState } from '../types'; import { SceneShareTabState } from '../types';
@ -17,7 +18,7 @@ export interface SharePublicDashboardTabState extends SceneShareTabState {
} }
export class SharePublicDashboardTab extends SceneObjectBase<SharePublicDashboardTabState> { export class SharePublicDashboardTab extends SceneObjectBase<SharePublicDashboardTabState> {
public tabId = 'Public dashboard'; public tabId = shareDashboardType.publicDashboard;
static Component = SharePublicDashboardTabRenderer; static Component = SharePublicDashboardTabRenderer;
public getTabLabel() { public getTabLabel() {

View File

@ -0,0 +1,148 @@
import { reportInteraction } from '@grafana/runtime';
import { InspectTab } from 'app/features/inspector/types';
export const DashboardInteractions = {
// Dashboard interactions:
dashboardInitialized: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('init_dashboard_completed', { ...properties });
},
// Panel interactions:
panelMenuShown: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('panelheader_menu', { ...properties, item: 'menu' });
},
panelMenuItemClicked: (
item:
| 'view'
| 'edit'
| 'share'
| 'createLibraryPanel'
| 'unlinkLibraryPanel'
| 'duplicate'
| 'copy'
| 'remove'
| 'explore'
| 'toggleLegend'
| 'create-alert'
) => {
reportDashboardInteraction('panelheader_menu', { item });
},
panelMenuInspectClicked(tab: InspectTab) {
reportDashboardInteraction('panelheader_menu', { item: 'inspect', tab });
},
panelLinkClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('panelheader_datalink_clicked', properties);
},
panelStatusMessageClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('panelheader_statusmessage_clicked', properties);
},
panelCancelQueryClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('panelheader_cancelquery_clicked', properties);
},
panelDescriptionShown: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('panelheader_description_displayed', properties);
},
// Sharing interactions:
sharingTabChanged: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_category_clicked', properties);
},
shareLinkCopied: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_link_copy_clicked', properties);
},
embedSnippetCopy: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_embed_copy_clicked', properties);
},
publishSnapshotClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_snapshot_publish_clicked', properties);
},
publishSnapshotLocalClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_snapshot_local_clicked', properties);
},
exportDownloadJsonClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_export_download_json_clicked', properties);
},
exportCopyJsonClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_export_copy_json_clicked', properties);
},
exportSaveJsonClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_export_save_json_clicked', properties);
},
exportViewJsonClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_export_view_json_clicked', properties);
},
generatePublicDashboardUrlClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_generate_url_clicked', properties);
},
revokePublicDashboardEmailClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_email_revoke_clicked', properties);
},
resendPublicDashboardEmailClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_email_resend_clicked', properties);
},
publicDashboardEmailInviteClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_email_invite_clicked', properties);
},
publicDashboardShareTypeChange: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_can_view_clicked', properties);
},
publicDashboardTimeSelectionChanged: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_time_picker_clicked', properties);
},
publicDashboardAnnotationsSelectionChanged: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_annotations_clicked', properties);
},
publicDashboardUrlCopied: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_copy_url_clicked', properties);
},
publicDashboardPauseSharingClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_pause_clicked', properties);
},
revokePublicDashboardClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('sharing_public_revoke_clicked', properties);
},
// Empty dashboard state interactions:
emptyDashboardButtonClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('emptydashboard_clicked', properties);
},
// Toolbar interactions
toolbarAddButtonClicked: (properties?: Record<string, unknown>) => {
reportDashboardInteraction('toolbar_add_clicked', properties);
},
toolbarFavoritesClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'favorites' });
},
toolbarSettingsClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'settings' });
},
toolbarRefreshClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'refresh' });
},
toolbarTimePickerClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'time_picker' });
},
toolbarZoomClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'zoom_out_time_range' });
},
toolbarShareClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'share' });
},
toolbarSaveClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'save' });
},
toolbarAddClick: () => {
reportDashboardInteraction('toolbar_actions_clicked', { item: 'add' });
},
};
const reportDashboardInteraction: typeof reportInteraction = (name, properties) => {
if (properties) {
reportInteraction(`dashboards_${name}`, properties);
} else {
reportInteraction(`dashboards_${name}`);
}
};

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { config, locationService, reportInteraction } from '@grafana/runtime'; import { config, locationService } from '@grafana/runtime';
import { Menu } from '@grafana/ui'; import { Menu } from '@grafana/ui';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
@ -12,6 +12,7 @@ import {
onCreateNewRow, onCreateNewRow,
onPasteCopiedPanel, onPasteCopiedPanel,
} from 'app/features/dashboard/utils/dashboard'; } from 'app/features/dashboard/utils/dashboard';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { useDispatch, useSelector } from 'app/types'; import { useDispatch, useSelector } from 'app/types';
import { setInitialDatasource } from '../../state/reducers'; import { setInitialDatasource } from '../../state/reducers';
@ -33,7 +34,7 @@ const AddPanelMenu = ({ dashboard }: Props) => {
label={t('dashboard.add-menu.visualization', 'Visualization')} label={t('dashboard.add-menu.visualization', 'Visualization')}
onClick={() => { onClick={() => {
const id = onCreateNewPanel(dashboard, initialDatasource); const id = onCreateNewPanel(dashboard, initialDatasource);
reportInteraction('dashboards_toolbar_add_clicked', { item: 'add_visualization' }); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' });
locationService.partial({ editPanel: id }); locationService.partial({ editPanel: id });
dispatch(setInitialDatasource(undefined)); dispatch(setInitialDatasource(undefined));
}} }}
@ -44,7 +45,7 @@ const AddPanelMenu = ({ dashboard }: Props) => {
testId={selectors.pages.AddDashboard.itemButton('Add new widget menu item')} testId={selectors.pages.AddDashboard.itemButton('Add new widget menu item')}
label={t('dashboard.add-menu.widget', 'Widget')} label={t('dashboard.add-menu.widget', 'Widget')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'add_widget' }); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_widget' });
locationService.partial({ addWidget: true }); locationService.partial({ addWidget: true });
}} }}
/> />
@ -54,7 +55,7 @@ const AddPanelMenu = ({ dashboard }: Props) => {
testId={selectors.pages.AddDashboard.itemButton('Add new row menu item')} testId={selectors.pages.AddDashboard.itemButton('Add new row menu item')}
label={t('dashboard.add-menu.row', 'Row')} label={t('dashboard.add-menu.row', 'Row')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'add_row' }); DashboardInteractions.toolbarAddButtonClicked({ item: 'add_row' });
onCreateNewRow(dashboard); onCreateNewRow(dashboard);
}} }}
/> />
@ -63,7 +64,7 @@ const AddPanelMenu = ({ dashboard }: Props) => {
testId={selectors.pages.AddDashboard.itemButton('Add new panel from panel library menu item')} testId={selectors.pages.AddDashboard.itemButton('Add new panel from panel library menu item')}
label={t('dashboard.add-menu.import', 'Import from library')} label={t('dashboard.add-menu.import', 'Import from library')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'import_from_library' }); DashboardInteractions.toolbarAddButtonClicked({ item: 'import_from_library' });
onAddLibraryPanel(dashboard); onAddLibraryPanel(dashboard);
}} }}
/> />
@ -72,7 +73,7 @@ const AddPanelMenu = ({ dashboard }: Props) => {
testId={selectors.pages.AddDashboard.itemButton('Add new panel from clipboard menu item')} testId={selectors.pages.AddDashboard.itemButton('Add new panel from clipboard menu item')}
label={t('dashboard.add-menu.paste-panel', 'Paste panel')} label={t('dashboard.add-menu.paste-panel', 'Paste panel')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'paste_panel' }); DashboardInteractions.toolbarAddButtonClicked({ item: 'paste_panel' });
onPasteCopiedPanel(dashboard, copiedPanelPlugin); onPasteCopiedPanel(dashboard, copiedPanelPlugin);
}} }}
disabled={!copiedPanelPlugin} disabled={!copiedPanelPlugin}

View File

@ -27,6 +27,7 @@ import AddPanelButton from 'app/features/dashboard/components/AddPanelButton/Add
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer'; import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
import { KioskMode } from 'app/types'; import { KioskMode } from 'app/types';
@ -35,15 +36,6 @@ import { DashboardMetaChangedEvent, ShowModalReactEvent } from 'app/types/events
import { DashNavButton } from './DashNavButton'; import { DashNavButton } from './DashNavButton';
import { DashNavTimeControls } from './DashNavTimeControls'; import { DashNavTimeControls } from './DashNavTimeControls';
import { ShareButton } from './ShareButton'; import { ShareButton } from './ShareButton';
import {
trackToolbarFavoritesClick,
trackToolbarRefreshClick,
trackToolbarSettingsClick,
trackToolbarTimePickerClick,
trackToolbarZoomClick,
trackToolbarSaveClick,
trackToolbarAddClick,
} from './analytics';
const mapDispatchToProps = { const mapDispatchToProps = {
setStarred, setStarred,
@ -131,7 +123,7 @@ export const DashNav = React.memo<Props>((props) => {
}; };
const onStarDashboard = () => { const onStarDashboard = () => {
trackToolbarFavoritesClick(); DashboardInteractions.toolbarFavoritesClick();
const dashboardSrv = getDashboardSrv(); const dashboardSrv = getDashboardSrv();
const { dashboard, setStarred } = props; const { dashboard, setStarred } = props;
@ -143,7 +135,7 @@ export const DashNav = React.memo<Props>((props) => {
}; };
const onOpenSettings = () => { const onOpenSettings = () => {
trackToolbarSettingsClick(); DashboardInteractions.toolbarSettingsClick();
locationService.partial({ editview: 'settings' }); locationService.partial({ editview: 'settings' });
}; };
@ -257,9 +249,9 @@ export const DashNav = React.memo<Props>((props) => {
<DashNavTimeControls <DashNavTimeControls
dashboard={dashboard} dashboard={dashboard}
onChangeTimeZone={updateTimeZoneForSession} onChangeTimeZone={updateTimeZoneForSession}
onToolbarRefreshClick={trackToolbarRefreshClick} onToolbarRefreshClick={DashboardInteractions.toolbarRefreshClick}
onToolbarZoomClick={trackToolbarZoomClick} onToolbarZoomClick={DashboardInteractions.toolbarZoomClick}
onToolbarTimePickerClick={trackToolbarTimePickerClick} onToolbarTimePickerClick={DashboardInteractions.toolbarTimePickerClick}
key="time-controls" key="time-controls"
/> />
); );
@ -283,7 +275,11 @@ export const DashNav = React.memo<Props>((props) => {
if (canEdit && !isFullscreen) { if (canEdit && !isFullscreen) {
if (config.featureToggles.emptyDashboardPage) { if (config.featureToggles.emptyDashboardPage) {
buttons.push( buttons.push(
<AddPanelButton dashboard={dashboard} onToolbarAddMenuOpen={trackToolbarAddClick} key="panel-add-dropdown" /> <AddPanelButton
dashboard={dashboard}
onToolbarAddMenuOpen={DashboardInteractions.toolbarAddClick}
key="panel-add-dropdown"
/>
); );
} else { } else {
buttons.push( buttons.push(
@ -306,7 +302,7 @@ export const DashNav = React.memo<Props>((props) => {
tooltip={t('dashboard.toolbar.save', 'Save dashboard')} tooltip={t('dashboard.toolbar.save', 'Save dashboard')}
icon="save" icon="save"
onClick={() => { onClick={() => {
trackToolbarSaveClick(); DashboardInteractions.toolbarSaveClick();
showModal(SaveDashboardDrawer, { showModal(SaveDashboardDrawer, {
dashboard, dashboard,
onDismiss: hideModal, onDismiss: hideModal,

View File

@ -4,11 +4,11 @@ import { ModalsContext } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { ShareModal } from '../ShareModal'; import { ShareModal } from '../ShareModal';
import { DashNavButton } from './DashNavButton'; import { DashNavButton } from './DashNavButton';
import { trackToolbarShareClick } from './analytics';
export const ShareButton = ({ dashboard }: { dashboard: DashboardModel }) => { export const ShareButton = ({ dashboard }: { dashboard: DashboardModel }) => {
const [queryParams] = useQueryParams(); const [queryParams] = useQueryParams();
@ -33,7 +33,7 @@ export const ShareButton = ({ dashboard }: { dashboard: DashboardModel }) => {
icon="share-alt" icon="share-alt"
iconSize="lg" iconSize="lg"
onClick={() => { onClick={() => {
trackToolbarShareClick(); DashboardInteractions.toolbarShareClick();
showModal(ShareModal, { showModal(ShareModal, {
dashboard, dashboard,
onDismiss: hideModal, onDismiss: hideModal,

View File

@ -1,33 +0,0 @@
import { reportInteraction } from '@grafana/runtime';
export const shareAnalyticsEventNames: {
[key: string]: string;
} = {
dashboardsToolbarActionsClicked: 'dashboards_toolbar_actions_clicked',
};
export function trackToolbarFavoritesClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'favorites' });
}
export function trackToolbarSettingsClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'settings' });
}
export function trackToolbarRefreshClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'refresh' });
}
export function trackToolbarTimePickerClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'time_picker' });
}
export function trackToolbarZoomClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'zoom_out_time_range' });
}
export function trackToolbarShareClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'share' });
}
export function trackToolbarSaveClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'save' });
}
export function trackToolbarAddClick() {
reportInteraction(shareAnalyticsEventNames.dashboardsToolbarActionsClicked, { item: 'add' });
}

View File

@ -5,6 +5,7 @@ import { RawTimeRange, TimeRange } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { ClipboardButton, Field, Modal, Switch, TextArea } from '@grafana/ui'; import { ClipboardButton, Field, Modal, Switch, TextArea } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { ThemePicker } from './ThemePicker'; import { ThemePicker } from './ThemePicker';
import { ShareModalTabProps } from './types'; import { ShareModalTabProps } from './types';
@ -81,7 +82,7 @@ export function ShareEmbed({ panel, dashboard, range, buildIframe = buildIframeH
variant="primary" variant="primary"
getText={() => iframeHtml} getText={() => iframeHtml}
onClipboardCopy={() => { onClipboardCopy={() => {
reportInteraction('dashboards_sharing_embed_copy_clicked', { DashboardInteractions.embedSnippetCopy({
currentTimeRange: useCurrentTimeRange, currentTimeRange: useCurrentTimeRange,
theme: selectedTheme, theme: selectedTheme,
}); });

View File

@ -1,11 +1,11 @@
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { reportInteraction } from '@grafana/runtime';
import { Button, Field, Modal, Switch } from '@grafana/ui'; import { Button, Field, Modal, Switch } from '@grafana/ui';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { ShowModalReactEvent } from 'app/types/events'; import { ShowModalReactEvent } from 'app/types/events';
import { ViewJsonModal } from './ViewJsonModal'; import { ViewJsonModal } from './ViewJsonModal';
@ -39,7 +39,7 @@ export class ShareExport extends PureComponent<Props, State> {
const { dashboard } = this.props; const { dashboard } = this.props;
const { shareExternally } = this.state; const { shareExternally } = this.state;
reportInteraction('dashboards_sharing_export_save_json_clicked', { externally: shareExternally }); DashboardInteractions.exportSaveJsonClicked();
if (shareExternally) { if (shareExternally) {
this.exporter.makeExportable(dashboard).then((dashboardJson) => { this.exporter.makeExportable(dashboard).then((dashboardJson) => {
@ -53,8 +53,7 @@ export class ShareExport extends PureComponent<Props, State> {
onViewJson = () => { onViewJson = () => {
const { dashboard } = this.props; const { dashboard } = this.props;
const { shareExternally } = this.state; const { shareExternally } = this.state;
DashboardInteractions.exportViewJsonClicked();
reportInteraction('dashboards_sharing_export_view_json_clicked', { externally: shareExternally });
if (shareExternally) { if (shareExternally) {
this.exporter.makeExportable(dashboard).then((dashboardJson) => { this.exporter.makeExportable(dashboard).then((dashboardJson) => {

View File

@ -1,10 +1,10 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime';
import { Alert, ClipboardButton, Field, FieldSet, Input, Switch, TextLink } from '@grafana/ui'; import { Alert, ClipboardButton, Field, FieldSet, Input, Switch, TextLink } from '@grafana/ui';
import config from 'app/core/config'; import config from 'app/core/config';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { ThemePicker } from './ThemePicker'; import { ThemePicker } from './ThemePicker';
import { ShareModalTabProps } from './types'; import { ShareModalTabProps } from './types';
@ -75,7 +75,7 @@ export class ShareLink extends PureComponent<Props, State> {
}; };
onCopy() { onCopy() {
reportInteraction('dashboards_sharing_link_copy_clicked', { DashboardInteractions.shareLinkCopied({
currentTimeRange: this.state.useCurrentTimeRange, currentTimeRange: this.state.useCurrentTimeRange,
theme: this.state.selectedTheme, theme: this.state.selectedTheme,
shortenURL: this.state.useShortUrl, shortenURL: this.state.useShortUrl,

View File

@ -6,6 +6,7 @@ import { contextSrv } from 'app/core/core';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { SharePublicDashboard } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard'; import { SharePublicDashboard } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard'; import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard';
import { ShareEmbed } from './ShareEmbed'; import { ShareEmbed } from './ShareEmbed';
@ -13,7 +14,6 @@ import { ShareExport } from './ShareExport';
import { ShareLibraryPanel } from './ShareLibraryPanel'; import { ShareLibraryPanel } from './ShareLibraryPanel';
import { ShareLink } from './ShareLink'; import { ShareLink } from './ShareLink';
import { ShareSnapshot } from './ShareSnapshot'; import { ShareSnapshot } from './ShareSnapshot';
import { trackDashboardSharingTypeOpen } from './analytics';
import { ShareModalTabModel } from './types'; import { ShareModalTabModel } from './types';
import { shareDashboardType } from './utils'; import { shareDashboardType } from './utils';
@ -102,7 +102,9 @@ class UnthemedShareModal extends React.Component<Props, State> {
onSelectTab: React.ComponentProps<typeof ModalTabsHeader>['onChangeTab'] = (t) => { onSelectTab: React.ComponentProps<typeof ModalTabsHeader>['onChangeTab'] = (t) => {
this.setState((prevState) => ({ ...prevState, activeTab: t.value })); this.setState((prevState) => ({ ...prevState, activeTab: t.value }));
trackDashboardSharingTypeOpen(t.value); DashboardInteractions.sharingTabChanged({
item: t.value,
});
}; };
getActiveTab() { getActiveTab() {

View File

@ -4,7 +4,6 @@ import { useForm } from 'react-hook-form';
import { GrafanaTheme2, TimeRange } from '@grafana/data/src'; import { GrafanaTheme2, TimeRange } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { reportInteraction } from '@grafana/runtime';
import { config, featureEnabled } from '@grafana/runtime/src'; import { config, featureEnabled } from '@grafana/runtime/src';
import { import {
Button, Button,
@ -25,6 +24,7 @@ import {
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { getTimeRange } from 'app/features/dashboard/utils/timeRange'; import { getTimeRange } from 'app/features/dashboard/utils/timeRange';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { DeletePublicDashboardModal } from 'app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal'; import { DeletePublicDashboardModal } from 'app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardModal';
import { contextSrv } from '../../../../../../core/services/context_srv'; import { contextSrv } from '../../../../../../core/services/context_srv';
@ -111,7 +111,7 @@ export function ConfigPublicDashboardBase({
}; };
function onCopyURL() { function onCopyURL() {
reportInteraction('dashboards_sharing_public_copy_url_clicked'); DashboardInteractions.publicDashboardUrlCopied();
} }
return ( return (
@ -151,7 +151,7 @@ export function ConfigPublicDashboardBase({
{...register('isPaused')} {...register('isPaused')}
disabled={disableInputs} disabled={disableInputs}
onChange={(e) => { onChange={(e) => {
reportInteraction('dashboards_sharing_public_pause_clicked', { DashboardInteractions.publicDashboardPauseSharingClicked({
paused: e.currentTarget.checked, paused: e.currentTarget.checked,
}); });
onChange('isPaused', e.currentTarget.checked); onChange('isPaused', e.currentTarget.checked);
@ -242,7 +242,7 @@ export function ConfigPublicDashboard({ publicDashboard, unsupportedDatasources
showSaveChangesAlert={hasWritePermissions && dashboard.hasUnsavedChanges()} showSaveChangesAlert={hasWritePermissions && dashboard.hasUnsavedChanges()}
hasTemplateVariables={hasTemplateVariables} hasTemplateVariables={hasTemplateVariables}
onRevoke={() => { onRevoke={() => {
reportInteraction('dashboards_sharing_public_revoke_clicked'); DashboardInteractions.revokePublicDashboardClicked();
showModal(DeletePublicDashboardModal, { showModal(DeletePublicDashboardModal, {
dashboardTitle: dashboard.title, dashboardTitle: dashboard.title,
onConfirm: () => onDeletePublicDashboardClick(hideModal), onConfirm: () => onDeletePublicDashboardClick(hideModal),

View File

@ -3,9 +3,9 @@ import { UseFormRegister } from 'react-hook-form';
import { TimeRange } from '@grafana/data/src'; import { TimeRange } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { reportInteraction } from '@grafana/runtime';
import { FieldSet, Label, Switch, TimeRangeInput, VerticalGroup } from '@grafana/ui/src'; import { FieldSet, Label, Switch, TimeRangeInput, VerticalGroup } from '@grafana/ui/src';
import { Layout } from '@grafana/ui/src/components/Layout/Layout'; import { Layout } from '@grafana/ui/src/components/Layout/Layout';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { ConfigPublicDashboardForm } from './ConfigPublicDashboard'; import { ConfigPublicDashboardForm } from './ConfigPublicDashboard';
@ -38,7 +38,7 @@ export const Configuration = ({
data-testid={selectors.EnableTimeRangeSwitch} data-testid={selectors.EnableTimeRangeSwitch}
onChange={(e) => { onChange={(e) => {
onChange('isTimeSelectionEnabled', e.currentTarget.checked); onChange('isTimeSelectionEnabled', e.currentTarget.checked);
reportInteraction('dashboards_sharing_public_time_picker_clicked', { DashboardInteractions.publicDashboardTimeSelectionChanged({
enabled: e.currentTarget.checked, enabled: e.currentTarget.checked,
}); });
}} }}
@ -50,7 +50,7 @@ export const Configuration = ({
{...register('isAnnotationsEnabled')} {...register('isAnnotationsEnabled')}
onChange={(e) => { onChange={(e) => {
onChange('isAnnotationsEnabled', e.currentTarget.checked); onChange('isAnnotationsEnabled', e.currentTarget.checked);
reportInteraction('dashboards_sharing_public_annotations_clicked', { DashboardInteractions.publicDashboardAnnotationsSelectionChanged({
enabled: e.currentTarget.checked, enabled: e.currentTarget.checked,
}); });
}} }}

View File

@ -5,7 +5,6 @@ import { useWindowSize } from 'react-use';
import { GrafanaTheme2, SelectableValue } from '@grafana/data/src'; import { GrafanaTheme2, SelectableValue } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { reportInteraction } from '@grafana/runtime';
import { FieldSet } from '@grafana/ui'; import { FieldSet } from '@grafana/ui';
import { import {
Button, Button,
@ -25,6 +24,7 @@ import {
useReshareAccessToRecipientMutation, useReshareAccessToRecipientMutation,
useUpdatePublicDashboardMutation, useUpdatePublicDashboardMutation,
} from 'app/features/dashboard/api/publicDashboardApi'; } from 'app/features/dashboard/api/publicDashboardApi';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { AccessControlAction, useSelector } from 'app/types'; import { AccessControlAction, useSelector } from 'app/types';
import { PublicDashboard, PublicDashboardShareType, validEmailRegex } from '../SharePublicDashboardUtils'; import { PublicDashboard, PublicDashboardShareType, validEmailRegex } from '../SharePublicDashboardUtils';
@ -57,12 +57,12 @@ const EmailList = ({
const isLoading = isDeleteLoading || isReshareLoading; const isLoading = isDeleteLoading || isReshareLoading;
const onDeleteEmail = (recipientUid: string) => { const onDeleteEmail = (recipientUid: string) => {
reportInteraction('dashboards_sharing_public_email_revoke_clicked'); DashboardInteractions.revokePublicDashboardEmailClicked();
deleteEmail({ recipientUid, dashboardUid: dashboardUid, uid: publicDashboardUid }); deleteEmail({ recipientUid, dashboardUid: dashboardUid, uid: publicDashboardUid });
}; };
const onReshare = (recipientUid: string) => { const onReshare = (recipientUid: string) => {
reportInteraction('dashboards_sharing_public_email_resend_clicked'); DashboardInteractions.resendPublicDashboardEmailClicked();
reshareAccess({ recipientUid, uid: publicDashboardUid }); reshareAccess({ recipientUid, uid: publicDashboardUid });
}; };
@ -150,7 +150,7 @@ export const EmailSharingConfiguration = () => {
}; };
const onSubmit = async (data: EmailSharingConfigurationForm) => { const onSubmit = async (data: EmailSharingConfigurationForm) => {
reportInteraction('dashboards_sharing_public_email_invite_clicked'); DashboardInteractions.publicDashboardEmailInviteClicked();
await addEmail({ recipient: data.email, uid: publicDashboard!.uid, dashboardUid: dashboard.uid }).unwrap(); await addEmail({ recipient: data.email, uid: publicDashboard!.uid, dashboardUid: dashboard.uid }).unwrap();
reset({ email: '', shareType: PublicDashboardShareType.EMAIL }); reset({ email: '', shareType: PublicDashboardShareType.EMAIL });
}; };
@ -170,7 +170,7 @@ export const EmailSharingConfiguration = () => {
size={width < 480 ? 'sm' : 'md'} size={width < 480 ? 'sm' : 'md'}
options={options} options={options}
onChange={(shareType: PublicDashboardShareType) => { onChange={(shareType: PublicDashboardShareType) => {
reportInteraction('dashboards_sharing_public_can_view_clicked', { DashboardInteractions.publicDashboardShareTypeChange({
shareType: shareType === PublicDashboardShareType.EMAIL ? 'email' : 'public', shareType: shareType === PublicDashboardShareType.EMAIL ? 'email' : 'public',
}); });
setValue('shareType', shareType); setValue('shareType', shareType);

View File

@ -4,11 +4,11 @@ import { FormState, UseFormRegister } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data/src'; import { GrafanaTheme2 } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { reportInteraction } from '@grafana/runtime';
import { Button, Form, Spinner, useStyles2 } from '@grafana/ui/src'; import { Button, Form, Spinner, useStyles2 } from '@grafana/ui/src';
import { useCreatePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi'; import { useCreatePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { contextSrv } from '../../../../../../core/services/context_srv'; import { contextSrv } from '../../../../../../core/services/context_srv';
import { AccessControlAction, useSelector } from '../../../../../../types'; import { AccessControlAction, useSelector } from '../../../../../../types';
@ -46,7 +46,7 @@ export const CreatePublicDashboardBase = ({
const [createPublicDashboard, { isLoading, isError }] = useCreatePublicDashboardMutation(); const [createPublicDashboard, { isLoading, isError }] = useCreatePublicDashboardMutation();
const onCreate = () => { const onCreate = () => {
createPublicDashboard({ dashboard, payload: { isEnabled: true } }); createPublicDashboard({ dashboard, payload: { isEnabled: true } });
reportInteraction('dashboards_sharing_public_generate_url_clicked', {}); DashboardInteractions.generatePublicDashboardUrlClicked({});
}; };
const disableInputs = !hasWritePermissions || isLoading || isError || hasError; const disableInputs = !hasWritePermissions || isLoading || isError || hasError;

View File

@ -13,8 +13,8 @@ import { backendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
import { Echo } from 'app/core/services/echo/Echo'; import { Echo } from 'app/core/services/echo/Echo';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures'; import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { trackDashboardSharingTypeOpen } from '../analytics';
import { shareDashboardType } from '../utils'; import { shareDashboardType } from '../utils';
import * as sharePublicDashboardUtils from './SharePublicDashboardUtils'; import * as sharePublicDashboardUtils from './SharePublicDashboardUtils';
@ -33,9 +33,11 @@ jest.mock('@grafana/runtime', () => ({
reportInteraction: jest.fn(), reportInteraction: jest.fn(),
})); }));
jest.mock('../analytics', () => ({ jest.mock('app/features/dashboard-scene/utils/interactions', () => ({
...jest.requireActual('../analytics'), DashboardInteractions: {
trackDashboardSharingTypeOpen: jest.fn(), ...jest.requireActual('app/features/dashboard-scene/utils/interactions').DashboardInteractions,
sharingTabChanged: jest.fn(),
},
})); }));
const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard; const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;
@ -345,8 +347,8 @@ describe('SharePublic - Report interactions', () => {
await renderSharePublicDashboard(); await renderSharePublicDashboard();
await waitFor(() => { await waitFor(() => {
expect(trackDashboardSharingTypeOpen).toHaveBeenCalledTimes(1); expect(DashboardInteractions.sharingTabChanged).toHaveBeenCalledTimes(1);
expect(trackDashboardSharingTypeOpen).lastCalledWith(shareDashboardType.publicDashboard); expect(DashboardInteractions.sharingTabChanged).lastCalledWith({ item: shareDashboardType.publicDashboard });
}); });
}); });

View File

@ -1,11 +1,12 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { isEmptyObject, SelectableValue } from '@grafana/data'; import { isEmptyObject, SelectableValue } from '@grafana/data';
import { getBackendSrv, reportInteraction } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { Button, ClipboardButton, Field, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui'; import { Button, ClipboardButton, Field, Input, LinkButton, Modal, Select, Spinner } from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { VariableRefresh } from '../../../variables/types'; import { VariableRefresh } from '../../../variables/types';
import { getDashboardSnapshotSrv } from '../../services/SnapshotSrv'; import { getDashboardSnapshotSrv } from '../../services/SnapshotSrv';
@ -116,12 +117,12 @@ export class ShareSnapshot extends PureComponent<Props, State> {
}); });
} finally { } finally {
if (external) { if (external) {
reportInteraction('dashboards_sharing_snapshot_publish_clicked', { DashboardInteractions.publishSnapshotClicked({
expires: snapshotExpires, expires: snapshotExpires,
timeout: timeoutSeconds, timeout: timeoutSeconds,
}); });
} else { } else {
reportInteraction('dashboards_sharing_snapshot_local_clicked', { DashboardInteractions.publishSnapshotLocalClicked({
expires: snapshotExpires, expires: snapshotExpires,
timeout: timeoutSeconds, timeout: timeoutSeconds,
}); });

View File

@ -1,9 +1,9 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { reportInteraction } from '@grafana/runtime';
import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui'; import { ClipboardButton, CodeEditor, Modal } from '@grafana/ui';
import { Trans } from 'app/core/internationalization'; import { Trans } from 'app/core/internationalization';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
export interface ViewJsonModalProps { export interface ViewJsonModalProps {
json: string; json: string;
@ -22,7 +22,7 @@ export function ViewJsonModal({ json, onDismiss }: ViewJsonModalProps): JSX.Elem
icon="copy" icon="copy"
getText={getClipboardText} getText={getClipboardText}
onClipboardCopy={() => { onClipboardCopy={() => {
reportInteraction('dashboards_sharing_export_copy_json_clicked'); DashboardInteractions.exportCopyJsonClicked();
}} }}
> >
<Trans i18nKey="share-modal.view-json.copy-button">Copy to Clipboard</Trans> <Trans i18nKey="share-modal.view-json.copy-button">Copy to Clipboard</Trans>

View File

@ -1,11 +0,0 @@
import { reportInteraction } from '@grafana/runtime';
export const shareAnalyticsEventNames: {
[key: string]: string;
} = {
sharingCategoryClicked: 'dashboards_sharing_category_clicked',
};
export function trackDashboardSharingTypeOpen(sharingType: string) {
reportInteraction(shareAnalyticsEventNames.sharingCategoryClicked, { item: sharingType });
}

View File

@ -3,11 +3,12 @@ import React from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { config, locationService, reportInteraction } from '@grafana/runtime'; import { config, locationService } from '@grafana/runtime';
import { Button, useStyles2, Text, Box, Stack } from '@grafana/ui'; import { Button, useStyles2, Text, Box, Stack } from '@grafana/ui';
import { Trans } from 'app/core/internationalization'; import { Trans } from 'app/core/internationalization';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { onAddLibraryPanel, onCreateNewPanel, onImportDashboard } from 'app/features/dashboard/utils/dashboard'; import { onAddLibraryPanel, onCreateNewPanel, onImportDashboard } from 'app/features/dashboard/utils/dashboard';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { useDispatch, useSelector } from 'app/types'; import { useDispatch, useSelector } from 'app/types';
import { setInitialDatasource } from '../state/reducers'; import { setInitialDatasource } from '../state/reducers';
@ -47,7 +48,8 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')} data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')}
onClick={() => { onClick={() => {
const id = onCreateNewPanel(dashboard, initialDatasource); const id = onCreateNewPanel(dashboard, initialDatasource);
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_visualization' }); DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_visualization' });
locationService.partial({ editPanel: id, firstPanel: true }); locationService.partial({ editPanel: id, firstPanel: true });
dispatch(setInitialDatasource(undefined)); dispatch(setInitialDatasource(undefined));
}} }}
@ -74,7 +76,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
fill="outline" fill="outline"
data-testid={selectors.pages.AddDashboard.itemButton('Create new widget button')} data-testid={selectors.pages.AddDashboard.itemButton('Create new widget button')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_widget' }); DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_widget' });
locationService.partial({ addWidget: true }); locationService.partial({ addWidget: true });
}} }}
disabled={!canCreate} disabled={!canCreate}
@ -101,7 +103,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
fill="outline" fill="outline"
data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')} data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_from_library' }); DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_from_library' });
onAddLibraryPanel(dashboard); onAddLibraryPanel(dashboard);
}} }}
disabled={!canCreate} disabled={!canCreate}
@ -128,7 +130,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
fill="outline" fill="outline"
data-testid={selectors.pages.AddDashboard.itemButton('Import dashboard button')} data-testid={selectors.pages.AddDashboard.itemButton('Import dashboard button')}
onClick={() => { onClick={() => {
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_dashboard' }); DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_dashboard' });
onImportDashboard(); onImportDashboard();
}} }}
disabled={!canCreate} disabled={!canCreate}

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import { LinkModel, PanelData, PanelPlugin, renderMarkdown } from '@grafana/data'; import { LinkModel, PanelData, PanelPlugin, renderMarkdown } from '@grafana/data';
import { config, getTemplateSrv, locationService, reportInteraction } from '@grafana/runtime'; import { config, getTemplateSrv, locationService } from '@grafana/runtime';
import { PanelPadding } from '@grafana/ui'; import { PanelPadding } from '@grafana/ui';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { InspectTab } from 'app/features/inspector/types'; import { InspectTab } from 'app/features/inspector/types';
import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
import { isAngularDatasourcePluginAndNotHidden } from 'app/features/plugins/angularDeprecation/utils'; import { isAngularDatasourcePluginAndNotHidden } from 'app/features/plugins/angularDeprecation/utils';
@ -42,7 +43,7 @@ export function getPanelChromeProps(props: CommonProps) {
if (!descriptionInteractionReported) { if (!descriptionInteractionReported) {
// Description rendering function can be called multiple times due to re-renders but we want to report the interaction once. // Description rendering function can be called multiple times due to re-renders but we want to report the interaction once.
reportInteraction('dashboards_panelheader_description_displayed'); DashboardInteractions.panelDescriptionShown();
descriptionInteractionReported = true; descriptionInteractionReported = true;
} }
@ -59,7 +60,7 @@ export function getPanelChromeProps(props: CommonProps) {
return panelLinks.map((panelLink) => ({ return panelLinks.map((panelLink) => ({
...panelLink, ...panelLink,
onClick: (...args) => { onClick: (...args) => {
reportInteraction('dashboards_panelheader_datalink_clicked', { has_multiple_links: panelLinks.length > 1 }); DashboardInteractions.panelLinkClicked({ has_multiple_links: panelLinks.length > 1 });
panelLink.onClick?.(...args); panelLink.onClick?.(...args);
}, },
})); }));
@ -73,12 +74,12 @@ export function getPanelChromeProps(props: CommonProps) {
const onOpenErrorInspect = (e: React.SyntheticEvent) => { const onOpenErrorInspect = (e: React.SyntheticEvent) => {
e.stopPropagation(); e.stopPropagation();
locationService.partial({ inspect: props.panel.id, inspectTab: InspectTab.Error }); locationService.partial({ inspect: props.panel.id, inspectTab: InspectTab.Error });
reportInteraction('dashboards_panelheader_statusmessage_clicked'); DashboardInteractions.panelStatusMessageClicked();
}; };
const onCancelQuery = () => { const onCancelQuery = () => {
props.panel.getQueryRunner().cancelQuery(); props.panel.getQueryRunner().cancelQuery();
reportInteraction('dashboards_panelheader_cancelquery_clicked', { data_state: props.data.state }); DashboardInteractions.panelCancelQueryClicked();
}; };
const padding: PanelPadding = props.plugin.noPadding ? 'none' : 'md'; const padding: PanelPadding = props.plugin.noPadding ? 'none' : 'md';
@ -121,7 +122,7 @@ export function getPanelChromeProps(props: CommonProps) {
const title = props.panel.getDisplayTitle(); const title = props.panel.getDisplayTitle();
const onOpenMenu = () => { const onOpenMenu = () => {
reportInteraction('dashboards_panelheader_menu', { item: 'menu' }); DashboardInteractions.panelMenuShown();
}; };
return { return {

View File

@ -5,7 +5,7 @@ import {
urlUtil, urlUtil,
type PluginExtensionPanelContext, type PluginExtensionPanelContext,
} from '@grafana/data'; } from '@grafana/data';
import { AngularComponent, getPluginLinkExtensions, locationService, reportInteraction } from '@grafana/runtime'; import { AngularComponent, getPluginLinkExtensions, locationService } from '@grafana/runtime';
import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
import config from 'app/core/config'; import config from 'app/core/config';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
@ -23,6 +23,7 @@ import {
toggleLegend, toggleLegend,
unlinkLibraryPanel, unlinkLibraryPanel,
} from 'app/features/dashboard/utils/panel'; } from 'app/features/dashboard/utils/panel';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { InspectTab } from 'app/features/inspector/types'; import { InspectTab } from 'app/features/inspector/types';
import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard'; import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard';
import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils'; import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils';
@ -43,7 +44,7 @@ export function getPanelMenu(
locationService.partial({ locationService.partial({
viewPanel: panel.id, viewPanel: panel.id,
}); });
reportInteraction('dashboards_panelheader_menu', { item: 'view' }); DashboardInteractions.panelMenuItemClicked('view');
}; };
const onEditPanel = (event: React.MouseEvent) => { const onEditPanel = (event: React.MouseEvent) => {
@ -52,25 +53,25 @@ export function getPanelMenu(
editPanel: panel.id, editPanel: panel.id,
}); });
reportInteraction('dashboards_panelheader_menu', { item: 'edit' }); DashboardInteractions.panelMenuItemClicked('edit');
}; };
const onSharePanel = (event: React.MouseEvent) => { const onSharePanel = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
sharePanel(dashboard, panel); sharePanel(dashboard, panel);
reportInteraction('dashboards_panelheader_menu', { item: 'share' }); DashboardInteractions.panelMenuItemClicked('share');
}; };
const onAddLibraryPanel = (event: React.MouseEvent) => { const onAddLibraryPanel = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
addLibraryPanel(dashboard, panel); addLibraryPanel(dashboard, panel);
reportInteraction('dashboards_panelheader_menu', { item: 'createLibraryPanel' }); DashboardInteractions.panelMenuItemClicked('createLibraryPanel');
}; };
const onUnlinkLibraryPanel = (event: React.MouseEvent) => { const onUnlinkLibraryPanel = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
unlinkLibraryPanel(panel); unlinkLibraryPanel(panel);
reportInteraction('dashboards_panelheader_menu', { item: 'unlinkLibraryPanel' }); DashboardInteractions.panelMenuItemClicked('unlinkLibraryPanel');
}; };
const onInspectPanel = (tab?: InspectTab) => { const onInspectPanel = (tab?: InspectTab) => {
@ -78,7 +79,7 @@ export function getPanelMenu(
inspect: panel.id, inspect: panel.id,
inspectTab: tab, inspectTab: tab,
}); });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: tab ?? InspectTab.Data }); DashboardInteractions.panelMenuInspectClicked(tab ?? InspectTab.Data);
}; };
const onMore = (event: React.MouseEvent) => { const onMore = (event: React.MouseEvent) => {
@ -88,19 +89,19 @@ export function getPanelMenu(
const onDuplicatePanel = (event: React.MouseEvent) => { const onDuplicatePanel = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
duplicatePanel(dashboard, panel); duplicatePanel(dashboard, panel);
reportInteraction('dashboards_panelheader_menu', { item: 'duplicate' }); DashboardInteractions.panelMenuItemClicked('duplicate');
}; };
const onCopyPanel = (event: React.MouseEvent) => { const onCopyPanel = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
copyPanel(panel); copyPanel(panel);
reportInteraction('dashboards_panelheader_menu', { item: 'copy' }); DashboardInteractions.panelMenuItemClicked('copy');
}; };
const onRemovePanel = (event: React.MouseEvent) => { const onRemovePanel = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
removePanel(dashboard, panel, true); removePanel(dashboard, panel, true);
reportInteraction('dashboards_panelheader_menu', { item: 'remove' }); DashboardInteractions.panelMenuItemClicked('remove');
}; };
const onNavigateToExplore = (event: React.MouseEvent) => { const onNavigateToExplore = (event: React.MouseEvent) => {
@ -114,13 +115,13 @@ export function getPanelMenu(
openInNewWindow, openInNewWindow,
}) as any }) as any
); );
reportInteraction('dashboards_panelheader_menu', { item: 'explore' }); DashboardInteractions.panelMenuItemClicked('explore');
}; };
const onToggleLegend = (event: React.MouseEvent) => { const onToggleLegend = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
toggleLegend(panel); toggleLegend(panel);
reportInteraction('dashboards_panelheader_menu', { item: 'toggleLegend' }); DashboardInteractions.panelMenuItemClicked('toggleLegend');
}; };
const menu: PanelMenuItem[] = []; const menu: PanelMenuItem[] = [];
@ -218,7 +219,7 @@ export function getPanelMenu(
const onCreateAlert = (event: React.MouseEvent) => { const onCreateAlert = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
createAlert(); createAlert();
reportInteraction('dashboards_panelheader_menu', { item: 'create-alert' }); DashboardInteractions.panelMenuItemClicked('create-alert');
}; };
const subMenu: PanelMenuItem[] = []; const subMenu: PanelMenuItem[] = [];

View File

@ -1,4 +1,4 @@
import { reportInteraction } from '@grafana/runtime'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
import { DashboardModel } from '../state'; import { DashboardModel } from '../state';
@ -11,7 +11,7 @@ export function trackDashboardLoaded(dashboard: DashboardModel, versionBeforeMig
return r; return r;
}, {}); }, {});
reportInteraction('dashboards_init_dashboard_completed', { DashboardInteractions.dashboardInitialized({
uid: dashboard.uid, uid: dashboard.uid,
title: dashboard.title, title: dashboard.title,
theme: dashboard.style, theme: dashboard.style,

View File

@ -3302,9 +3302,9 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"@grafana/scenes@npm:^1.26.0": "@grafana/scenes@npm:1.27.0":
version: 1.26.0 version: 1.27.0
resolution: "@grafana/scenes@npm:1.26.0" resolution: "@grafana/scenes@npm:1.27.0"
dependencies: dependencies:
"@grafana/e2e-selectors": "npm:10.0.2" "@grafana/e2e-selectors": "npm:10.0.2"
react-grid-layout: "npm:1.3.4" react-grid-layout: "npm:1.3.4"
@ -3316,7 +3316,7 @@ __metadata:
"@grafana/runtime": 10.0.3 "@grafana/runtime": 10.0.3
"@grafana/schema": 10.0.3 "@grafana/schema": 10.0.3
"@grafana/ui": 10.0.3 "@grafana/ui": 10.0.3
checksum: a4d00396552ef048c28a51d4a4d36a7c9a9fb3ca193641002b279a33a5e85c10b1e63b859c0f42c6cc34992016fb18c10566817ac04c637dc48a0e85d21a93ab checksum: e19aa28d6297316676cb4805dde9bc3a52d2d28a3231e2fcdedf610356fc5c1b24eecfec36e8089cf4defc388705d47fce32736f8a86c9176e5bd4e4b7da9acb
languageName: node languageName: node
linkType: hard linkType: hard
@ -17332,7 +17332,7 @@ __metadata:
"@grafana/lezer-traceql": "npm:0.0.11" "@grafana/lezer-traceql": "npm:0.0.11"
"@grafana/monaco-logql": "npm:^0.0.7" "@grafana/monaco-logql": "npm:^0.0.7"
"@grafana/runtime": "workspace:*" "@grafana/runtime": "workspace:*"
"@grafana/scenes": "npm:^1.26.0" "@grafana/scenes": "npm:1.27.0"
"@grafana/schema": "workspace:*" "@grafana/schema": "workspace:*"
"@grafana/tsconfig": "npm:^1.3.0-rc1" "@grafana/tsconfig": "npm:^1.3.0-rc1"
"@grafana/ui": "workspace:*" "@grafana/ui": "workspace:*"