Actions: Add analytics (#111200)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user