From 622c75af6d8663da865d815871368a968bb23942 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:19:26 -0600 Subject: [PATCH] Suggestions: Add analytics (#115904) --- .../panel-edit/PanelOptionsPane.tsx | 2 + .../panel-edit/PanelVizTypePicker.tsx | 16 ++++++- .../dashboard-scene/panel-edit/interaction.ts | 1 + .../VisualizationSuggestions.tsx | 46 +++++++++++++++---- .../components/VizTypePicker/interactions.ts | 28 +++++++++++ .../panel/components/VizTypePicker/types.ts | 1 + 6 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 public/app/features/panel/components/VizTypePicker/interactions.ts diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx index b863bf2fc78..290e298c88c 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx @@ -71,6 +71,7 @@ export class PanelOptionsPane extends SceneObjectBase { reportInteraction(INTERACTION_EVENT_NAME, { item: INTERACTION_ITEM.SELECT_PANEL_PLUGIN, plugin_id: pluginId, + from_suggestions: options.fromSuggestions ?? false, }); // clear custom options @@ -236,6 +237,7 @@ function PanelOptionsPaneComponent({ model }: SceneComponentProps )} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx index acc4ff208e6..71c82bcf6ab 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx @@ -26,6 +26,7 @@ export interface Props { editPreview: VizPanel; onChange: (options: VizTypeChangeDetails, panel?: VizPanel) => void; onClose: () => void; + isNewPanel?: boolean; } const getTabs = (): Array<{ label: string; value: VisualizationSelectPaneTab }> => { @@ -42,7 +43,7 @@ const getTabs = (): Array<{ label: string; value: VisualizationSelectPaneTab }> : [allVisualizationsTab, suggestionsTab]; }; -export function PanelVizTypePicker({ panel, editPreview, data, onChange, onClose, showBackButton }: Props) { +export function PanelVizTypePicker({ panel, editPreview, data, onChange, onClose, showBackButton, isNewPanel }: Props) { const styles = useStyles2(getStyles); const panelModel = useMemo(() => new PanelModelCompatibilityWrapper(panel), [panel]); const filterId = useId(); @@ -83,6 +84,16 @@ export function PanelVizTypePicker({ panel, editPreview, data, onChange, onClose [setListMode] ); + const handleBackButtonClick = useCallback(() => { + reportInteraction(INTERACTION_EVENT_NAME, { + item: INTERACTION_ITEM.BACK_BUTTON, + tab: VisualizationSelectPaneTab[listMode], + creator_team: 'grafana_plugins_catalog', + schema_version: '1.0.0', + }); + onClose(); + }, [listMode, onClose]); + return (
@@ -114,7 +125,7 @@ export function PanelVizTypePicker({ panel, editPreview, data, onChange, onClose variant="secondary" icon="arrow-left" data-testid={selectors.components.PanelEditor.toggleVizPicker} - onClick={onClose} + onClick={handleBackButtonClick} > Back @@ -136,6 +147,7 @@ export function PanelVizTypePicker({ panel, editPreview, data, onChange, onClose editPreview={editPreview} data={data} searchQuery={searchQuery} + isNewPanel={isNewPanel} /> )} {listMode === VisualizationSelectPaneTab.Visualizations && ( diff --git a/public/app/features/dashboard-scene/panel-edit/interaction.ts b/public/app/features/dashboard-scene/panel-edit/interaction.ts index 4ba515b5223..da147a60807 100644 --- a/public/app/features/dashboard-scene/panel-edit/interaction.ts +++ b/public/app/features/dashboard-scene/panel-edit/interaction.ts @@ -4,4 +4,5 @@ export const INTERACTION_ITEM = { SELECT_PANEL_PLUGIN: 'select_panel_plugin', CHANGE_TAB: 'change_tab', // for ref - PanelVizTypePicker SEARCH: 'search', // for ref - PanelVizTypePicker + BACK_BUTTON: 'back_button', // for ref - PanelVizTypePicker }; diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index 924b5f3b6bf..c29f1b60a21 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -22,6 +22,7 @@ import { getAllSuggestions } from '../../suggestions/getAllSuggestions'; import { hasData } from '../../suggestions/utils'; import { VisualizationSuggestionCard } from './VisualizationSuggestionCard'; +import { VizSuggestionsInteractions, PANEL_STATES, type PanelState } from './interactions'; import { VizTypeChangeDetails } from './types'; export interface Props { @@ -30,6 +31,7 @@ export interface Props { data?: PanelData; panel?: PanelModel; searchQuery?: string; + isNewPanel?: boolean; } const useSuggestions = (data: PanelData | undefined, searchQuery: string | undefined) => { @@ -62,7 +64,7 @@ const useSuggestions = (data: PanelData | undefined, searchQuery: string | undef return { value: filteredValue, loading, error, retry }; }; -export function VisualizationSuggestions({ onChange, editPreview, data, panel, searchQuery }: Props) { +export function VisualizationSuggestions({ onChange, editPreview, data, panel, searchQuery, isNewPanel }: Props) { const styles = useStyles2(getStyles); const { value: result, loading, error, retry } = useSuggestions(data, searchQuery); @@ -75,6 +77,18 @@ export function VisualizationSuggestions({ onChange, editPreview, data, panel, s const isNewVizSuggestionsEnabled = config.featureToggles.newVizSuggestions; const isUnconfiguredPanel = panel?.type === UNCONFIGURED_PANEL_PLUGIN_ID; + const panelState = useMemo((): PanelState => { + if (isUnconfiguredPanel) { + return PANEL_STATES.UNCONFIGURED_PANEL; + } + + if (isNewPanel) { + return PANEL_STATES.NEW_PANEL; + } + + return PANEL_STATES.EXISTING_PANEL; + }, [isUnconfiguredPanel, isNewPanel]); + const suggestionsByVizType = useMemo(() => { const meta = getAllPanelPluginMeta(); const record: Record = {}; @@ -96,22 +110,36 @@ export function VisualizationSuggestions({ onChange, editPreview, data, panel, s }, [suggestions]); const applySuggestion = useCallback( - (suggestion: PanelPluginVisualizationSuggestion, isPreview?: boolean) => { + (suggestion: PanelPluginVisualizationSuggestion, isPreview: boolean, isAutoSelected = false) => { + if (isPreview) { + VizSuggestionsInteractions.suggestionPreviewed({ + pluginId: suggestion.pluginId, + suggestionName: suggestion.name, + panelState, + isAutoSelected, + }); + + setSuggestionHash(suggestion.hash); + } else { + VizSuggestionsInteractions.suggestionAccepted({ + pluginId: suggestion.pluginId, + suggestionName: suggestion.name, + panelState, + }); + } + onChange( { pluginId: suggestion.pluginId, options: suggestion.options, fieldConfig: suggestion.fieldConfig, withModKey: isPreview, + fromSuggestions: true, }, isPreview ? editPreview : undefined ); - - if (isPreview) { - setSuggestionHash(suggestion.hash); - } }, - [onChange, editPreview] + [onChange, editPreview, panelState] ); useEffect(() => { @@ -124,7 +152,7 @@ export function VisualizationSuggestions({ onChange, editPreview, data, panel, s // the previously selected suggestion is no longer present in the list. const newFirstCardHash = suggestions?.[0]?.hash ?? null; if (firstCardHash !== newFirstCardHash || suggestions.every((s) => s.hash !== suggestionHash)) { - applySuggestion(suggestions[0], true); + applySuggestion(suggestions[0], true, true); setFirstCardHash(newFirstCardHash); return; } @@ -243,7 +271,7 @@ export function VisualizationSuggestions({ onChange, editPreview, data, panel, s suggestion={suggestion} width={width} tabIndex={index} - onClick={() => applySuggestion(suggestion)} + onClick={() => applySuggestion(suggestion, false)} />
))} diff --git a/public/app/features/panel/components/VizTypePicker/interactions.ts b/public/app/features/panel/components/VizTypePicker/interactions.ts new file mode 100644 index 00000000000..3062919c93a --- /dev/null +++ b/public/app/features/panel/components/VizTypePicker/interactions.ts @@ -0,0 +1,28 @@ +import { reportInteraction } from '@grafana/runtime'; + +export const PANEL_STATES = { + UNCONFIGURED_PANEL: 'unconfigured_panel', + NEW_PANEL: 'new_panel', + EXISTING_PANEL: 'existing_panel', +} as const; + +export type PanelState = (typeof PANEL_STATES)[keyof typeof PANEL_STATES]; + +export const VizSuggestionsInteractions = { + suggestionPreviewed: (properties: { + pluginId: string; + suggestionName: string; + panelState: PanelState; + isAutoSelected?: boolean; + }) => { + reportVizSuggestionsInteraction('suggestion_previewed', properties); + }, + + suggestionAccepted: (properties: { pluginId: string; suggestionName: string; panelState: PanelState }) => { + reportVizSuggestionsInteraction('suggestion_accepted', properties); + }, +}; + +const reportVizSuggestionsInteraction = (name: string, properties?: Record) => { + reportInteraction(`grafana_viz_suggestions_${name}`, properties); +}; diff --git a/public/app/features/panel/components/VizTypePicker/types.ts b/public/app/features/panel/components/VizTypePicker/types.ts index 4dbab692faf..6eac9011184 100644 --- a/public/app/features/panel/components/VizTypePicker/types.ts +++ b/public/app/features/panel/components/VizTypePicker/types.ts @@ -5,4 +5,5 @@ export interface VizTypeChangeDetails { options?: Record; fieldConfig?: FieldConfigSource; withModKey?: boolean; + fromSuggestions?: boolean; }