Actions: Add analytics (#111200)

This commit is contained in:
Adela Almasan
2025-10-22 13:38:25 +00:00
committed by GitHub
parent 9587f9374b
commit a011ec29fe
11 changed files with 74 additions and 15 deletions
@@ -25,6 +25,7 @@ export interface Action {
*/
export interface ActionModel<T = any> {
title: string;
type?: ActionType;
onClick: (event: any, origin?: any, actionVars?: ActionVariableInput) => void;
confirmation: (actionVars?: ActionVariableInput) => ReactNode;
oneClick?: boolean;
+12
View File
@@ -0,0 +1,12 @@
import { reportInteraction } from '@grafana/runtime';
export function reportActionTrigger(actionType: string, isOneClick: boolean, visualizationType: string) {
reportInteraction('dashboards_action_interaction', {
event: 'dashboards_action_interaction',
action: 'trigger_action',
action_type: actionType,
visualization_type: visualizationType,
trigger_type: isOneClick ? 'one_click' : 'button',
context: 'panel_runtime',
});
}
+9 -1
View File
@@ -24,6 +24,8 @@ import { createAbsoluteUrl, RelativeUrl } from '../alerting/unified/utils/url';
import { getTimeSrv } from '../dashboard/services/TimeSrv';
import { getNextRequestId } from '../query/state/PanelQueryRunner';
import { reportActionTrigger } from './analytics';
/** @internal */
export const isInfinityActionWithAuth = (action: Action): boolean => {
return (grafanaConfig.featureToggles.vizActionsAuth ?? false) && action.type === ActionType.Infinity;
@@ -59,7 +61,8 @@ export const getActions = (
fieldScopedVars: ScopedVars,
replaceVariables: InterpolateFunction,
actions: Action[],
config: ValueLinkConfig
config: ValueLinkConfig,
visualizationType?: string
): Array<ActionModel<Field>> => {
if (!actions || actions.length === 0) {
return [];
@@ -89,6 +92,7 @@ export const getActions = (
const actionModel: ActionModel<Field> = {
title: replaceVariables(action.title, actionScopedVars),
type: action.type,
confirmation: (actionVars?: ActionVariableInput) =>
genReplaceActionVars(
boundReplaceVariables,
@@ -96,6 +100,10 @@ export const getActions = (
actionVars
)(action.confirmation || `Are you sure you want to ${action.title}?`),
onClick: (evt: MouseEvent, origin: Field, actionVars?: ActionVariableInput) => {
if (visualizationType) {
reportActionTrigger(action.type, action.oneClick ?? false, visualizationType);
}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
let request = {} as BackendSrvRequest;
if (isInfinityActionWithAuth(action)) {
+10 -1
View File
@@ -35,6 +35,7 @@ import {
removeStyles,
} from 'app/plugins/panel/canvas/utils';
import { reportActionTrigger } from '../../actions/analytics';
import { getActions, getActionsDefaultField, isInfinityActionWithAuth } from '../../actions/utils';
import { CanvasElementItem, CanvasElementOptions } from '../element';
import { canvasElementRegistry } from '../registry';
@@ -1049,6 +1050,9 @@ export class ElementState implements LayerElement {
onConfirm={() => {
this.showActionConfirmation = false;
action.onClick(new MouseEvent('click'), null, this.actionVars);
if (action.type) {
reportActionTrigger(action.type, true, 'canvas');
}
this.forceUpdate();
}}
onDismiss={() => {
@@ -1104,7 +1108,12 @@ export class ElementState implements LayerElement {
tabIndex={0}
style={{ userSelect: 'none' }}
>
<item.display key={this.UID} config={this.options.config} data={this.data} isSelected={isSelected} />
<item.display
key={`${this.UID}/${this.revId}`}
config={this.options.config}
data={this.data}
isSelected={isSelected}
/>
</div>
{this.showActionConfirmation && this.renderActionsConfirmModal(this.getPrimaryAction())}
{this.showActionVarsModal && this.renderVariablesInputModal(this.getPrimaryAction())}
@@ -127,7 +127,8 @@ export const CanvasTooltip = ({ scene }: Props) => {
scopedVars,
scene.panel.props.replaceVariables!,
element.options.actions ?? [],
config
config,
'canvas'
);
actionsModel.forEach((action) => {
@@ -325,7 +325,9 @@ const HeatmapHoverCell = ({
links = getDataLinks(linksField, xValueIdx);
}
actions = canExecuteActions ? getFieldActions(data.series!, linksField, replaceVariables, xValueIdx) : [];
actions = canExecuteActions
? getFieldActions(data.series!, linksField, replaceVariables, xValueIdx, 'heatmap')
: [];
}
footer = <VizTooltipFooter dataLinks={links} annotate={annotate} actions={actions} />;
@@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import { FieldType, TimeRange } from '@grafana/data';
import { FieldType, TimeRange, usePluginContext } from '@grafana/data';
import { SortOrder } from '@grafana/schema/dist/esm/common/common.gen';
import { TooltipDisplayMode } from '@grafana/ui';
import {
@@ -36,6 +36,7 @@ export const StateTimelineTooltip = ({
replaceVariables,
dataLinks,
}: StateTimelineTooltipProps) => {
const pluginContext = usePluginContext();
const xField = series.fields[0];
const dataIdx = seriesIdx != null ? dataIdxs[seriesIdx] : dataIdxs.find((idx) => idx != null);
@@ -78,8 +79,9 @@ export const StateTimelineTooltip = ({
const hasOneClickLink = dataLinks.some((dataLink) => dataLink.oneClick === true);
if (isPinned || hasOneClickLink) {
const visualizationType = pluginContext?.meta?.id ?? 'state-timeline';
const dataIdx = dataIdxs[seriesIdx]!;
const actions = getFieldActions(series, field, replaceVariables!, dataIdx);
const actions = getFieldActions(series, field, replaceVariables!, dataIdx, visualizationType);
footer = <VizTooltipFooter dataLinks={dataLinks} actions={actions} annotate={annotate} />;
}
@@ -26,16 +26,25 @@ export const getFieldActions = (
dataFrame: DataFrame,
field: Field,
replaceVars: InterpolateFunction,
rowIndex: number
rowIndex: number,
visualizationType?: string
) => {
const actions: Array<ActionModel<Field>> = [];
if (field.state?.scopedVars) {
const actionLookup = new Set<string>();
const actionsModel = getActions(dataFrame, field, field.state.scopedVars, replaceVars, field.config.actions ?? [], {
valueRowIndex: rowIndex,
});
const actionsModel = getActions(
dataFrame,
field,
field.state.scopedVars,
replaceVars,
field.config.actions ?? [],
{
valueRowIndex: rowIndex,
},
visualizationType
);
actionsModel.forEach((action) => {
const key = `${action.title}`;
@@ -180,7 +180,8 @@ const getCellActions = (
field.state!.scopedVars!,
replaceVariables ?? replaceVars,
field.config.actions ?? [],
{ valueRowIndex: rowIndex }
{ valueRowIndex: rowIndex },
'table'
);
if (actions.length === 1) {
@@ -1,7 +1,15 @@
import { ReactNode } from 'react';
import { DataFrame, Field, FieldType, formattedValueToString, InterpolateFunction, LinkModel } from '@grafana/data';
import { SortOrder, TooltipDisplayMode } from '@grafana/schema/dist/esm/common/common.gen';
import {
DataFrame,
Field,
FieldType,
formattedValueToString,
InterpolateFunction,
LinkModel,
usePluginContext,
} from '@grafana/data';
import { SortOrder, TooltipDisplayMode } from '@grafana/schema';
import {
VizTooltipContent,
VizTooltipFooter,
@@ -63,6 +71,7 @@ export const TimeSeriesTooltip = ({
canExecuteActions,
compareDiffMs,
}: TimeSeriesTooltipProps) => {
const pluginContext = usePluginContext();
const xField = series.fields[0];
let xVal = xField.values[dataIdxs[0]!];
@@ -91,8 +100,11 @@ export const TimeSeriesTooltip = ({
const hasOneClickLink = dataLinks.some((dataLink) => dataLink.oneClick === true);
if (isPinned || hasOneClickLink) {
const visualizationType = pluginContext?.meta?.id ?? 'timeseries';
const dataIdx = dataIdxs[seriesIdx]!;
const actions = canExecuteActions ? getFieldActions(series, field, replaceVariables, dataIdx) : [];
const actions = canExecuteActions
? getFieldActions(series, field, replaceVariables, dataIdx, visualizationType)
: [];
footer = (
<VizTooltipFooter dataLinks={dataLinks} actions={actions} annotate={annotate} adHocFilters={adHocFilters} />
@@ -132,7 +132,9 @@ export const XYChartTooltip = ({
if (isPinned || hasOneClickLink) {
const yFieldFrame = data.find((frame) => frame.fields.includes(yField))!;
const actions = canExecuteActions ? getFieldActions(yFieldFrame, yField, replaceVariables, rowIndex) : [];
const actions = canExecuteActions
? getFieldActions(yFieldFrame, yField, replaceVariables, rowIndex, 'xychart')
: [];
footer = <VizTooltipFooter dataLinks={dataLinks} actions={actions} />;
}