diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md index 8ed73209b8d..3b47c750403 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md @@ -190,12 +190,12 @@ You can configure a canvas data link to open with a single click on the element. 1. Click the element to which you want to add the data link. 1. In either the inline editor or panel editor, expand the **Selected element** editor. 1. Scroll down to the **Data links** section and expand it. -1. Toggle the **One-click** switch in the element's data links section. +1. In the **One-click** section, choose **Link**. 1. Disable inline editing. The first data link in the list will be configured as your one-click data link. If you want to change the one-click data link, simply drag the desired data link to the top of the list. -{{< video-embed src="/media/docs/grafana/panels-visualizations/canvas-one-click-data-link.mp4" >}} +{{< video-embed src="/media/docs/grafana/panels-visualizations/canvas-one-click-datalink-.mp4" >}} ## Panel options diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts index d450dd0390c..549599d81b4 100644 --- a/packages/grafana-data/src/field/fieldOverrides.ts +++ b/packages/grafana-data/src/field/fieldOverrides.ts @@ -479,7 +479,6 @@ export const getLinksSupplier = href, title: replaceVariables(link.title || '', dataLinkScopedVars), target: link.targetBlank ? '_blank' : undefined, - sortIndex: link.sortIndex, onClick: (evt: MouseEvent, origin: Field) => { link.onClick!({ origin: origin ?? field, @@ -495,7 +494,6 @@ export const getLinksSupplier = title: replaceVariables(link.title || '', dataLinkScopedVars), target: link.targetBlank ? '_blank' : undefined, origin: field, - sortIndex: link.sortIndex, }; } diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index c45b86fe1ba..bf16d4e093a 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -793,6 +793,7 @@ export { VariableOrigin, type VariableSuggestion, VariableSuggestionsScope, + OneClickMode, } from './types/dataLink'; export { DataFrameType } from './types/dataFrameTypes'; export { diff --git a/packages/grafana-data/src/types/dataLink.ts b/packages/grafana-data/src/types/dataLink.ts index 0bf2b85b401..47805993095 100644 --- a/packages/grafana-data/src/types/dataLink.ts +++ b/packages/grafana-data/src/types/dataLink.ts @@ -99,7 +99,6 @@ export interface LinkModel { // When a click callback exists, this is passed the raw mouse|react event onClick?: (e: any, origin?: any) => void; - sortIndex?: number; } /** @@ -130,3 +129,8 @@ export interface VariableSuggestion { export enum VariableSuggestionsScope { Values = 'values', } + +export enum OneClickMode { + Link = 'link', + Off = 'off', +} diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index ceaf847dfde..955ada22eb7 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -104,7 +104,6 @@ export interface CanvasElementOptions { connections?: Array; constraint?: Constraint; name: string; - oneClickLinks?: boolean; placement?: Placement; type: string; } diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx index a909db615da..2a949194a1e 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx @@ -17,7 +17,7 @@ interface DataLinksInlineEditorProps { onChange: (links: DataLink[]) => void; getSuggestions: () => VariableSuggestion[]; data: DataFrame[]; - oneClickEnabled?: boolean; + showOneClick?: boolean; } export const DataLinksInlineEditor = ({ @@ -25,13 +25,12 @@ export const DataLinksInlineEditor = ({ onChange, getSuggestions, data, - oneClickEnabled = false, + showOneClick = false, }: DataLinksInlineEditorProps) => { const [editIndex, setEditIndex] = useState(null); const [isNew, setIsNew] = useState(false); const [linksSafe, setLinksSafe] = useState([]); - links?.sort((a, b) => (a.sortIndex ?? 0) - (b.sortIndex ?? 0)); useEffect(() => { setLinksSafe(links ?? []); @@ -81,24 +80,20 @@ export const DataLinksInlineEditor = ({ return; } - const copy = [...linksSafe]; - const link = copy[result.source.index]; - link.sortIndex = result.destination.index; + const update = cloneDeep(linksSafe); + const link = update[result.source.index]; - const swapLink = copy[result.destination.index]; - swapLink.sortIndex = result.source.index; + update.splice(result.source.index, 1); + update.splice(result.destination.index, 0, link); - copy.splice(result.source.index, 1); - copy.splice(result.destination.index, 0, link); - - setLinksSafe(copy); - onChange(linksSafe); + setLinksSafe(update); + onChange(update); }; - const renderFirstLink = (linkJSX: ReactNode) => { - if (oneClickEnabled) { + const renderFirstLink = (linkJSX: ReactNode, key: string) => { + if (showOneClick) { return ( -
+
One-click {linkJSX}
@@ -130,7 +125,7 @@ export const DataLinksInlineEditor = ({ ); if (idx === 0) { - return renderFirstLink(linkJSX); + return renderFirstLink(linkJSX, key); } return linkJSX; diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index 23cd9ee137c..a1ea697641b 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -1,6 +1,6 @@ import { ComponentType } from 'react'; -import { DataLink, RegistryItem } from '@grafana/data'; +import { DataLink, RegistryItem, OneClickMode } from '@grafana/data'; import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; import { ColorDimensionConfig, ScaleDimensionConfig } from '@grafana/schema'; import { config } from 'app/core/config'; @@ -33,7 +33,7 @@ export interface CanvasElementOptions { border?: LineConfig; connections?: CanvasConnection[]; links?: DataLink[]; - oneClickLinks?: boolean; + oneClickMode?: OneClickMode; } // Unit is percentage from the middle of the element diff --git a/public/app/features/canvas/elements/cloud.tsx b/public/app/features/canvas/elements/cloud.tsx index cff34c95580..56695ef63b4 100644 --- a/public/app/features/canvas/elements/cloud.tsx +++ b/public/app/features/canvas/elements/cloud.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { v4 as uuidv4 } from 'uuid'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { config } from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; @@ -99,6 +99,7 @@ export const cloudItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/droneFront.tsx b/public/app/features/canvas/elements/droneFront.tsx index ab46e80214f..b535ea52739 100644 --- a/public/app/features/canvas/elements/droneFront.tsx +++ b/public/app/features/canvas/elements/droneFront.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions'; @@ -94,6 +94,8 @@ export const droneFrontItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, + links: options?.links ?? [], }), // Called when data changes diff --git a/public/app/features/canvas/elements/droneSide.tsx b/public/app/features/canvas/elements/droneSide.tsx index 49ca76ca309..ec6d82082c1 100644 --- a/public/app/features/canvas/elements/droneSide.tsx +++ b/public/app/features/canvas/elements/droneSide.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions'; @@ -93,6 +93,8 @@ export const droneSideItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, + links: options?.links ?? [], }), // Called when data changes diff --git a/public/app/features/canvas/elements/droneTop.tsx b/public/app/features/canvas/elements/droneTop.tsx index fefb56ea841..dcdc96ef264 100644 --- a/public/app/features/canvas/elements/droneTop.tsx +++ b/public/app/features/canvas/elements/droneTop.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions'; @@ -98,6 +98,8 @@ export const droneTopItem: CanvasElementItem = { fixed: 'transparent', }, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, + links: options?.links ?? [], }), // Called when data changes diff --git a/public/app/features/canvas/elements/ellipse.tsx b/public/app/features/canvas/elements/ellipse.tsx index 11de4f65e2f..fe8a4c286ee 100644 --- a/public/app/features/canvas/elements/ellipse.tsx +++ b/public/app/features/canvas/elements/ellipse.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { v4 as uuidv4 } from 'uuid'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { config } from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; @@ -106,6 +106,7 @@ export const ellipseItem: CanvasElementItem = { left: options?.placement?.left ?? 100, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/metricValue.tsx b/public/app/features/canvas/elements/metricValue.tsx index 0ddec47734a..ec84f383f18 100644 --- a/public/app/features/canvas/elements/metricValue.tsx +++ b/public/app/features/canvas/elements/metricValue.tsx @@ -3,7 +3,13 @@ import { useCallback } from 'react'; import { useObservable } from 'react-use'; import { of } from 'rxjs'; -import { DataFrame, FieldNamePickerConfigSettings, GrafanaTheme2, StandardEditorsRegistryItem } from '@grafana/data'; +import { + DataFrame, + FieldNamePickerConfigSettings, + GrafanaTheme2, + OneClickMode, + StandardEditorsRegistryItem, +} from '@grafana/data'; import { TextDimensionMode } from '@grafana/schema'; import { usePanelContext, useStyles2 } from '@grafana/ui'; import { FieldNamePicker } from '@grafana/ui/src/components/MatchersUI/FieldNamePicker'; @@ -175,6 +181,7 @@ export const metricValueItem: CanvasElementItem = { left: options?.placement?.left ?? 100, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/parallelogram.tsx b/public/app/features/canvas/elements/parallelogram.tsx index 3a163dd5503..fc46b2f09fd 100644 --- a/public/app/features/canvas/elements/parallelogram.tsx +++ b/public/app/features/canvas/elements/parallelogram.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { v4 as uuidv4 } from 'uuid'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { config } from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; @@ -99,6 +99,7 @@ export const parallelogramItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/rectangle.tsx b/public/app/features/canvas/elements/rectangle.tsx index 08265b8deda..d7455b450dd 100644 --- a/public/app/features/canvas/elements/rectangle.tsx +++ b/public/app/features/canvas/elements/rectangle.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { PureComponent } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions/context'; @@ -72,6 +72,7 @@ export const rectangleItem: CanvasElementItem = { fixed: defaultBgColor, }, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/server/server.tsx b/public/app/features/canvas/elements/server/server.tsx index b0650dbc068..32665e9c0b8 100644 --- a/public/app/features/canvas/elements/server/server.tsx +++ b/public/app/features/canvas/elements/server/server.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2, LinkModel } from '@grafana/data'; +import { GrafanaTheme2, LinkModel, OneClickMode } from '@grafana/data'; import { ColorDimensionConfig, ScalarDimensionConfig } from '@grafana/schema'; import config from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions'; @@ -83,6 +83,7 @@ export const serverItem: CanvasElementItem = { config: { type: ServerType.Single, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/text.tsx b/public/app/features/canvas/elements/text.tsx index e524163b472..ef76d3f385e 100644 --- a/public/app/features/canvas/elements/text.tsx +++ b/public/app/features/canvas/elements/text.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { useObservable } from 'react-use'; import { of } from 'rxjs'; -import { DataFrame, GrafanaTheme2 } from '@grafana/data'; +import { DataFrame, GrafanaTheme2, OneClickMode } from '@grafana/data'; import { Input, usePanelContext, useStyles2 } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions/context'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; @@ -148,6 +148,7 @@ export const textItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/triangle.tsx b/public/app/features/canvas/elements/triangle.tsx index cf640d21a37..b709eab1e99 100644 --- a/public/app/features/canvas/elements/triangle.tsx +++ b/public/app/features/canvas/elements/triangle.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { v4 as uuidv4 } from 'uuid'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, OneClickMode } from '@grafana/data'; import { config } from 'app/core/config'; import { DimensionContext } from 'app/features/dimensions'; import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; @@ -100,6 +100,7 @@ export const triangleItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/elements/windTurbine.tsx b/public/app/features/canvas/elements/windTurbine.tsx index ed3be434249..179644b4e7b 100644 --- a/public/app/features/canvas/elements/windTurbine.tsx +++ b/public/app/features/canvas/elements/windTurbine.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2, LinkModel } from '@grafana/data'; +import { GrafanaTheme2, LinkModel, OneClickMode } from '@grafana/data'; import { ScalarDimensionConfig } from '@grafana/schema'; import { useStyles2 } from '@grafana/ui'; import { DimensionContext } from 'app/features/dimensions'; @@ -90,6 +90,7 @@ export const windTurbineItem: CanvasElementItem = { left: options?.placement?.left, rotation: options?.placement?.rotation ?? 0, }, + oneClickMode: options?.oneClickMode ?? OneClickMode.Off, links: options?.links ?? [], }), diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index 43206d6d049..f16daaddee1 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -1,8 +1,8 @@ -import { CSSProperties } from 'react'; import * as React from 'react'; +import { CSSProperties } from 'react'; import { OnDrag, OnResize, OnRotate } from 'react-moveable/declaration/types'; -import { FieldType, getLinksSupplier, LinkModel, ValueLinkConfig } from '@grafana/data'; +import { FieldType, getLinksSupplier, LinkModel, OneClickMode, ValueLinkConfig } from '@grafana/data'; import { LayerElement } from 'app/core/components/Layers/types'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { DimensionContext } from 'app/features/dimensions'; @@ -62,6 +62,7 @@ export class ElementState implements LayerElement { options.placement = options.placement ?? { width: 100, height: 100, top: 0, left: 0, rotation: 0 }; options.background = options.background ?? { color: { fixed: 'transparent' } }; options.border = options.border ?? { color: { fixed: 'dark-green' } }; + options.oneClickMode = options.oneClickMode ?? OneClickMode.Off; const scene = this.getScene(); if (!options.name) { const newName = scene?.getNextElementName(); @@ -584,13 +585,17 @@ export class ElementState implements LayerElement { handleMouseEnter = (event: React.MouseEvent, isSelected: boolean | undefined) => { const scene = this.getScene(); - if (!scene?.isEditingEnabled && !scene?.tooltip?.isOpen && !this.options.oneClickLinks) { + const shouldHandleTooltip = + !scene?.isEditingEnabled && !scene?.tooltip?.isOpen && this.options.oneClickMode === OneClickMode.Off; + if (shouldHandleTooltip) { this.handleTooltip(event); } else if (!isSelected) { scene?.connections.handleMouseEnter(event); } - if (this.options.oneClickLinks && this.div && this.options.links && this.options.links.length > 0) { + const shouldHandleOneClickLink = + this.options.oneClickMode === OneClickMode.Link && this.options.links && this.options.links.length > 0; + if (shouldHandleOneClickLink && this.div) { const primaryDataLink = this.getPrimaryDataLink(); if (primaryDataLink) { this.div.style.cursor = 'pointer'; @@ -602,8 +607,7 @@ export class ElementState implements LayerElement { getPrimaryDataLink = () => { if (this.getLinks) { const links = this.getLinks({ valueRowIndex: getRowIndex(this.data.field, this.getScene()!) }); - const primaryDataLink = links.find((link: LinkModel) => link.sortIndex === 0); - return primaryDataLink ?? links[0]; + return links[0]; } return undefined; @@ -623,11 +627,11 @@ export class ElementState implements LayerElement { handleMouseLeave = (event: React.MouseEvent) => { const scene = this.getScene(); - if (scene?.tooltipCallback && !scene?.tooltip?.isOpen && !this.options.oneClickLinks) { + if (scene?.tooltipCallback && !scene?.tooltip?.isOpen && this.options.oneClickMode === OneClickMode.Off) { scene.tooltipCallback(undefined); } - if (this.options.oneClickLinks && this.div) { + if (this.options.oneClickMode !== OneClickMode.Off && this.div) { this.div.style.cursor = 'auto'; this.div.title = ''; } @@ -635,7 +639,7 @@ export class ElementState implements LayerElement { onElementClick = (event: React.MouseEvent) => { // If one-click access is enabled, open the primary link - if (this.options.oneClickLinks) { + if (this.options.oneClickMode === OneClickMode.Link) { let primaryDataLink = this.getPrimaryDataLink(); if (primaryDataLink) { window.open(primaryDataLink.href, primaryDataLink.target); diff --git a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx index 4142becbf4e..8537852eea2 100644 --- a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx +++ b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx @@ -83,9 +83,6 @@ export const CanvasTooltip = ({ scene }: Props) => { }); } - // sort element data links - links.sort((a, b) => (a.sortIndex ?? 0) - (b.sortIndex ?? 0)); - return ( <> {scene.tooltip?.element && scene.tooltip.anchorPoint && ( diff --git a/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx b/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx index 5df3052762c..95779a4b7a1 100644 --- a/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/element/DataLinksEditor.tsx @@ -1,16 +1,11 @@ -import { StandardEditorProps, DataLink, VariableSuggestionsScope } from '@grafana/data'; +import { StandardEditorProps, DataLink, VariableSuggestionsScope, OneClickMode } from '@grafana/data'; import { DataLinksInlineEditor } from '@grafana/ui'; - -import { CanvasElementOptions } from '../../panelcfg.gen'; +import { CanvasElementOptions } from 'app/features/canvas/element'; type Props = StandardEditorProps; export function DataLinksEditor({ value, onChange, item, context }: Props) { - if (!value) { - value = []; - } - - const settings = item.settings; + const oneClickMode = item.settings?.oneClickMode; return ( (context.getSuggestions ? context.getSuggestions(VariableSuggestionsScope.Values) : [])} data={[]} - oneClickEnabled={settings?.oneClickLinks} + showOneClick={oneClickMode === OneClickMode.Link} /> ); } diff --git a/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx b/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx index 6133ff8bcac..14ae13b10a5 100644 --- a/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/element/elementEditor.tsx @@ -1,5 +1,6 @@ -import { get as lodashGet } from 'lodash'; +import { capitalize, get as lodashGet } from 'lodash'; +import { OneClickMode } from '@grafana/data'; import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders'; import { CanvasElementOptions } from 'app/features/canvas/element'; import { @@ -119,6 +120,20 @@ export function getElementEditor(opts: CanvasEditorOptions): NestedPanelOptions< optionBuilder.addBorder(builder, ctx); } + builder.addRadio({ + category: ['Data links'], + path: 'oneClickMode', + name: 'One-click', + description: 'When enabled, a single click opens the first link', + settings: { + options: [ + { value: OneClickMode.Off, label: capitalize(OneClickMode.Off) }, + { value: OneClickMode.Link, label: capitalize(OneClickMode.Link) }, + ], + }, + defaultValue: OneClickMode.Off, + }); + optionBuilder.addDataLinks(builder, ctx); }, }; diff --git a/public/app/plugins/panel/canvas/editor/options.ts b/public/app/plugins/panel/canvas/editor/options.ts index 085e369eca2..2e9c03614b2 100644 --- a/public/app/plugins/panel/canvas/editor/options.ts +++ b/public/app/plugins/panel/canvas/editor/options.ts @@ -1,6 +1,6 @@ import { capitalize } from 'lodash'; -import { FieldType, standardEditorsRegistry } from '@grafana/data'; +import { FieldType } from '@grafana/data'; import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; import { ConnectionDirection } from 'app/features/canvas/element'; import { SVGElements } from 'app/features/canvas/runtime/element'; @@ -209,23 +209,13 @@ export const optionBuilder: OptionSuppliers = { }, addDataLinks: (builder, context) => { - const category = ['Data links']; - builder - .addCustomEditor({ - category, - id: 'enableOneClick', - path: 'oneClickLinks', - name: 'One-click', - description: 'When enabled, the top link in the list below works with a single click', - editor: standardEditorsRegistry.get('boolean').editor, - }) - .addCustomEditor({ - category, - id: 'dataLinks', - path: 'links', - name: '', - editor: DataLinksEditor, - settings: context.options, - }); + builder.addCustomEditor({ + category: ['Data links'], + id: 'dataLinks', + path: 'links', + name: 'Links', + editor: DataLinksEditor, + settings: context.options, + }); }, }; diff --git a/public/app/plugins/panel/canvas/migrations.ts b/public/app/plugins/panel/canvas/migrations.ts index 2415b212d65..527782bd142 100644 --- a/public/app/plugins/panel/canvas/migrations.ts +++ b/public/app/plugins/panel/canvas/migrations.ts @@ -1,4 +1,4 @@ -import { DataLink, DynamicConfigValue, FieldMatcherID, PanelModel } from '@grafana/data'; +import { DataLink, DynamicConfigValue, FieldMatcherID, PanelModel, OneClickMode } from '@grafana/data'; import { CanvasElementOptions } from 'app/features/canvas/element'; import { Options } from './panelcfg.gen'; @@ -43,7 +43,8 @@ export const canvasMigrationHandler = (panel: PanelModel): Partial => { } } - if (parseFloat(pluginVersion) <= 11.2) { + if (parseFloat(pluginVersion) <= 11.3) { + // migrate links from field name overrides to elements for (let idx = 0; idx < panel.fieldConfig.overrides.length; idx++) { const override = panel.fieldConfig.overrides[idx]; @@ -66,6 +67,17 @@ export const canvasMigrationHandler = (panel: PanelModel): Partial => { } } } + + // migrate oneClickLinks to oneClickMode + const root = panel.options?.root; + if (root?.elements) { + for (const element of root.elements) { + if (element.oneClickLinks) { + element.oneClickMode = OneClickMode.Link; + delete element.oneClickLinks; + } + } + } } return panel.options; diff --git a/public/app/plugins/panel/canvas/panelcfg.cue b/public/app/plugins/panel/canvas/panelcfg.cue index 16c72f86f6a..6da7cebacc4 100644 --- a/public/app/plugins/panel/canvas/panelcfg.cue +++ b/public/app/plugins/panel/canvas/panelcfg.cue @@ -87,7 +87,6 @@ composableKinds: PanelCfg: { background?: BackgroundConfig border?: LineConfig connections?: [...CanvasConnection] - oneClickLinks?: bool } @cuetsy(kind="interface") Options: { diff --git a/public/app/plugins/panel/canvas/panelcfg.gen.ts b/public/app/plugins/panel/canvas/panelcfg.gen.ts index 215f1f65162..9706c886f6d 100644 --- a/public/app/plugins/panel/canvas/panelcfg.gen.ts +++ b/public/app/plugins/panel/canvas/panelcfg.gen.ts @@ -102,7 +102,6 @@ export interface CanvasElementOptions { connections?: Array; constraint?: Constraint; name: string; - oneClickLinks?: boolean; placement?: Placement; type: string; }