+
);
};
diff --git a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx
index 8537852eea2..2478d8adf4d 100644
--- a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx
+++ b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx
@@ -10,13 +10,17 @@ import {
GrafanaTheme2,
formattedValueToString,
getFieldDisplayName,
+ ScopedVars,
+ ValueLinkConfig,
} from '@grafana/data/src';
+import { ActionModel } from '@grafana/data/src/types/action';
import { Portal, useStyles2, VizTooltipContainer } from '@grafana/ui';
import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent';
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
import { VizTooltipHeader } from '@grafana/ui/src/components/VizTooltip/VizTooltipHeader';
import { VizTooltipItem } from '@grafana/ui/src/components/VizTooltip/types';
import { CloseButton } from '@grafana/ui/src/components/uPlot/plugins/CloseButton';
+import { getActions, getActionsDefaultField } from 'app/features/actions/utils';
import { Scene } from 'app/features/canvas/runtime/scene';
import { getRowIndex } from '../utils';
@@ -83,6 +87,45 @@ export const CanvasTooltip = ({ scene }: Props) => {
});
}
+ const actions: Array
> = [];
+ const actionLookup = new Set();
+
+ const elementHasActions = (element.options.actions?.length ?? 0) > 0;
+ const frames = scene.data?.series;
+
+ if (elementHasActions && frames) {
+ const defaultField = getActionsDefaultField(element.options.links ?? [], element.options.actions ?? []);
+ const scopedVars: ScopedVars = {
+ __dataContext: {
+ value: {
+ data: frames,
+ field: defaultField,
+ frame: frames[0],
+ frameIndex: 0,
+ },
+ },
+ };
+
+ const config: ValueLinkConfig = { valueRowIndex: getRowIndex(element.data.field, scene) };
+
+ const actionsModel = getActions(
+ frames[0],
+ defaultField,
+ scopedVars,
+ scene.panel.props.replaceVariables!,
+ element.options.actions ?? [],
+ config
+ );
+
+ actionsModel.forEach((action) => {
+ const key = `${action.title}/${Math.random()}`;
+ if (!actionLookup.has(key)) {
+ actions.push(action);
+ actionLookup.add(key);
+ }
+ });
+ }
+
return (
<>
{scene.tooltip?.element && scene.tooltip.anchorPoint && (
@@ -97,7 +140,7 @@ export const CanvasTooltip = ({ scene }: Props) => {
{scene.tooltip.isOpen && }
{element.data.text && }
- {links.length > 0 && }
+ {(links.length > 0 || actions.length > 0) && }
diff --git a/public/app/plugins/panel/canvas/editor/element/ActionsEditor.tsx b/public/app/plugins/panel/canvas/editor/element/ActionsEditor.tsx
new file mode 100644
index 00000000000..06613ffe843
--- /dev/null
+++ b/public/app/plugins/panel/canvas/editor/element/ActionsEditor.tsx
@@ -0,0 +1,20 @@
+import { StandardEditorProps, OneClickMode, Action, VariableSuggestionsScope } from '@grafana/data';
+import { CanvasElementOptions } from 'app/features/canvas/element';
+
+import { ActionsInlineEditor } from '../../../../../features/actions/ActionsInlineEditor';
+
+type Props = StandardEditorProps;
+
+export function ActionsEditor({ value, onChange, item, context }: Props) {
+ const oneClickMode = item.settings?.oneClickMode;
+
+ return (
+ (context.getSuggestions ? context.getSuggestions(VariableSuggestionsScope.Values) : [])}
+ data={[]}
+ showOneClick={oneClickMode === OneClickMode.Action}
+ />
+ );
+}
diff --git a/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx b/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx
index 14ae13b10a5..e119ce60d66 100644
--- a/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx
+++ b/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx
@@ -2,10 +2,11 @@ import { capitalize, get as lodashGet } from 'lodash';
import { OneClickMode } from '@grafana/data';
import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders';
+import { config } from '@grafana/runtime';
import { CanvasElementOptions } from 'app/features/canvas/element';
import {
- DEFAULT_CANVAS_ELEMENT_CONFIG,
canvasElementRegistry,
+ DEFAULT_CANVAS_ELEMENT_CONFIG,
defaultElementItems,
} from 'app/features/canvas/registry';
import { ElementState } from 'app/features/canvas/runtime/element';
@@ -67,6 +68,8 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<
const current = options?.type ? options.type : DEFAULT_CANVAS_ELEMENT_CONFIG.type;
const layerTypes = getElementTypes(opts.scene.shouldShowAdvancedTypes, current).options;
+ const actionsEnabled = config.featureToggles.vizActions;
+
const isUnsupported =
!opts.scene.shouldShowAdvancedTypes && !defaultElementItems.filter((item) => item.id === options?.type).length;
@@ -120,21 +123,33 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions<
optionBuilder.addBorder(builder, ctx);
}
+ const oneClickModeOptions = [
+ { value: OneClickMode.Off, label: capitalize(OneClickMode.Off) },
+ { value: OneClickMode.Link, label: capitalize(OneClickMode.Link) },
+ ];
+
+ let oneClickCategory = 'Data links';
+ let oneClickDescription = 'When enabled, a single click opens the first link';
+
+ if (actionsEnabled) {
+ oneClickModeOptions.push({ value: OneClickMode.Action, label: capitalize(OneClickMode.Action) });
+ oneClickCategory += ' and actions';
+ oneClickDescription += ' or action';
+ }
+
builder.addRadio({
- category: ['Data links'],
+ category: [oneClickCategory],
path: 'oneClickMode',
name: 'One-click',
- description: 'When enabled, a single click opens the first link',
+ description: oneClickDescription,
settings: {
- options: [
- { value: OneClickMode.Off, label: capitalize(OneClickMode.Off) },
- { value: OneClickMode.Link, label: capitalize(OneClickMode.Link) },
- ],
+ options: oneClickModeOptions,
},
defaultValue: OneClickMode.Off,
});
optionBuilder.addDataLinks(builder, ctx);
+ optionBuilder.addActions(builder, ctx);
},
};
}
diff --git a/public/app/plugins/panel/canvas/editor/options.ts b/public/app/plugins/panel/canvas/editor/options.ts
index 2e9c03614b2..954478b88dd 100644
--- a/public/app/plugins/panel/canvas/editor/options.ts
+++ b/public/app/plugins/panel/canvas/editor/options.ts
@@ -2,6 +2,7 @@ import { capitalize } from 'lodash';
import { FieldType } from '@grafana/data';
import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin';
+import { config } from '@grafana/runtime';
import { ConnectionDirection } from 'app/features/canvas/element';
import { SVGElements } from 'app/features/canvas/runtime/element';
import { ColorDimensionEditor, ResourceDimensionEditor, ScaleDimensionEditor } from 'app/features/dimensions/editors';
@@ -11,12 +12,14 @@ import { CanvasConnection, CanvasElementOptions } from '../panelcfg.gen';
import { LineStyle } from '../types';
import { LineStyleEditor } from './LineStyleEditor';
+import { ActionsEditor } from './element/ActionsEditor';
import { DataLinksEditor } from './element/DataLinksEditor';
interface OptionSuppliers {
addBackground: PanelOptionsSupplier;
addBorder: PanelOptionsSupplier;
addDataLinks: PanelOptionsSupplier;
+ addActions: PanelOptionsSupplier;
addColor: PanelOptionsSupplier;
addSize: PanelOptionsSupplier;
addRadius: PanelOptionsSupplier;
@@ -210,7 +213,7 @@ export const optionBuilder: OptionSuppliers = {
addDataLinks: (builder, context) => {
builder.addCustomEditor({
- category: ['Data links'],
+ category: config.featureToggles.vizActions ? ['Data links and actions'] : ['Data links'],
id: 'dataLinks',
path: 'links',
name: 'Links',
@@ -218,4 +221,16 @@ export const optionBuilder: OptionSuppliers = {
settings: context.options,
});
},
+
+ addActions: (builder, context) => {
+ builder.addCustomEditor({
+ category: ['Data links and actions'],
+ id: 'actions',
+ path: 'actions',
+ name: 'Actions',
+ editor: ActionsEditor,
+ settings: context.options,
+ showIf: () => config.featureToggles.vizActions,
+ });
+ },
};