diff --git a/.betterer.results b/.betterer.results
index 29a7fcff218..f80d11894df 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -601,7 +601,8 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"]
],
"packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx:5381": [
- [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"],
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "1"]
],
"packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksListItem.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
diff --git a/docs/sources/panels-visualizations/configure-data-links/index.md b/docs/sources/panels-visualizations/configure-data-links/index.md
index 1b454b46848..7a32d801c33 100644
--- a/docs/sources/panels-visualizations/configure-data-links/index.md
+++ b/docs/sources/panels-visualizations/configure-data-links/index.md
@@ -248,7 +248,9 @@ If you want to add all of the current dashboard's variables to the URL, then use
To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables.
-1. If you want the link to open in a new tab, then toggle the **Open in a new tab** switch.
+1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch.
1. Click **Save** to save changes and close the dialog box.
1. Click **Save dashboard**.
1. Click **Back to dashboard** and then **Exit edit**.
+
+If you add multiple data links, you can control the order in which they appear in the visualization. To do this, click and drag the data link to the desired position.
diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md
index 943281c9ad6..8ed73209b8d 100644
--- a/docs/sources/panels-visualizations/visualizations/canvas/index.md
+++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md
@@ -166,25 +166,36 @@ The inline editing toggle lets you lock or unlock the canvas. When turned off th
Canvases support [data links](ref:data-links) for all elements except drone and button elements. You can add a data link by following these steps:
-1. Set an element to be tied to a field value.
-1. Turn off the inline editing toggle.
-1. Create an override for **Fields with name** and select the element field name from the list.
-1. Click the **+ Add override property** button.
-1. Select `Datalinks > Datalinks` from the list.
-1. Click **+Add link** add a title and URL for the data link.
-1. Hover over the element to display the data link tooltip.
-1. Click on the element to be able to open the data link.
+1. Enable inline editing.
+1. Click the element you 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. Click **Add link**.
+1. In the dialog box that opens, enter a **Title**. This is a human-readable label for the link, which will be displayed in the UI.
+1. Enter the **URL** or variable to which you want to link.
-If multiple elements use the same field name, and you want to control which elements display the data link, you can create a unique field name using the [add field from calculation transform](ref:add-field-from-calculation-transform). The alias you create in the transformation will appear as a field you can use with an element.
+ To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables.
-1. In the panel editor for the canvas, click the **Transform** tab.
-1. Select **Add field from calculation** from the list of transformations, or click **+ Add transformation** to display the list first.
-1. Choose **Reduce row** from the dropdown and click the field name that you want to use for the element.
-1. Select **All Values** from the **Calculation** dropdown.
-1. Add an alias for the field name.
-1. Reference the new unique field alias to create the element and field override.
+1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch.
+1. Click **Save** to save changes and close the dialog box.
+1. Disable inline editing.
-{{< video-embed src="/media/docs/grafana/canvas-data-links-9-4-0.mp4" max-width="750px" caption="Data links demo" >}}
+If you add multiple data links, you can control the order in which they appear in the visualization. To do this, click and drag the data link to the desired position.
+
+#### One-click data link
+
+You can configure a canvas data link to open with a single click on the element. To enable this feature, follow these steps:
+
+1. Enable inline editing.
+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. 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" >}}
## Panel options
diff --git a/packages/grafana-data/src/field/fieldOverrides.ts b/packages/grafana-data/src/field/fieldOverrides.ts
index 549599d81b4..d450dd0390c 100644
--- a/packages/grafana-data/src/field/fieldOverrides.ts
+++ b/packages/grafana-data/src/field/fieldOverrides.ts
@@ -479,6 +479,7 @@ 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,
@@ -494,6 +495,7 @@ 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/types/dataLink.ts b/packages/grafana-data/src/types/dataLink.ts
index d577b5e1a97..0bf2b85b401 100644
--- a/packages/grafana-data/src/types/dataLink.ts
+++ b/packages/grafana-data/src/types/dataLink.ts
@@ -51,6 +51,7 @@ export interface DataLink {
internal?: InternalDataLink;
origin?: DataLinkConfigOrigin;
+ sortIndex?: number;
}
/**
@@ -98,6 +99,7 @@ export interface LinkModel {
// When a click callback exists, this is passed the raw mouse|react event
onClick?: (e: any, origin?: any) => void;
+ sortIndex?: number;
}
/**
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 955ada22eb7..ceaf847dfde 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,6 +104,7 @@ export interface CanvasElementOptions {
connections?: Array;
constraint?: Constraint;
name: string;
+ oneClickLinks?: boolean;
placement?: Placement;
type: string;
}
diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json
index 60d34096401..824f6a6451f 100644
--- a/packages/grafana-ui/package.json
+++ b/packages/grafana-ui/package.json
@@ -54,6 +54,7 @@
"@grafana/e2e-selectors": "11.2.0-pre",
"@grafana/faro-web-sdk": "^1.3.6",
"@grafana/schema": "11.2.0-pre",
+ "@hello-pangea/dnd": "16.6.0",
"@leeoniya/ufuzzy": "1.0.14",
"@monaco-editor/react": "4.6.0",
"@popperjs/core": "2.11.8",
diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx
index 509c65d1dc2..a909db615da 100644
--- a/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx
+++ b/packages/grafana-ui/src/components/DataLinks/DataLinksInlineEditor/DataLinksInlineEditor.tsx
@@ -1,11 +1,12 @@
import { css } from '@emotion/css';
+import { DragDropContext, Droppable, DropResult } from '@hello-pangea/dnd';
import { cloneDeep } from 'lodash';
-import { useState } from 'react';
+import { ReactNode, useEffect, useState } from 'react';
import { DataFrame, DataLink, GrafanaTheme2, VariableSuggestion } from '@grafana/data';
import { useStyles2 } from '../../../themes';
-import { Button } from '../../Button/Button';
+import { Button } from '../../Button';
import { Modal } from '../../Modal/Modal';
import { DataLinkEditorModalContent } from './DataLinkEditorModalContent';
@@ -16,14 +17,27 @@ interface DataLinksInlineEditorProps {
onChange: (links: DataLink[]) => void;
getSuggestions: () => VariableSuggestion[];
data: DataFrame[];
+ oneClickEnabled?: boolean;
}
-export const DataLinksInlineEditor = ({ links, onChange, getSuggestions, data }: DataLinksInlineEditorProps) => {
+export const DataLinksInlineEditor = ({
+ links,
+ onChange,
+ getSuggestions,
+ data,
+ oneClickEnabled = 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 ?? []);
+ }, [links]);
+
const styles = useStyles2(getDataLinksInlineEditorStyles);
- const linksSafe: DataLink[] = links ?? [];
const isEditing = editIndex !== null;
const onDataLinkChange = (index: number, link: DataLink) => {
@@ -62,25 +76,70 @@ export const DataLinksInlineEditor = ({ links, onChange, getSuggestions, data }:
onChange(update);
};
+ const onDragEnd = (result: DropResult) => {
+ if (!links || !result.destination) {
+ return;
+ }
+
+ const copy = [...linksSafe];
+ const link = copy[result.source.index];
+ link.sortIndex = result.destination.index;
+
+ const swapLink = copy[result.destination.index];
+ swapLink.sortIndex = result.source.index;
+
+ copy.splice(result.source.index, 1);
+ copy.splice(result.destination.index, 0, link);
+
+ setLinksSafe(copy);
+ onChange(linksSafe);
+ };
+
+ const renderFirstLink = (linkJSX: ReactNode) => {
+ if (oneClickEnabled) {
+ return (
+