From 89636b0d4daff155a61087c25d8460ea7231775d Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 26 Feb 2024 12:41:00 +0100 Subject: [PATCH 01/39] added field for url and necessary variables still not able to save even though the code is a copy of the one i had before, still looking into it --- .../src/selectors/components.ts | 1 + .../datasource/prometheus/dataquery.gen.ts | 4 ++ .../components/PromQueryBuilderOptions.tsx | 11 ++++- .../components/PromQueryLegendUrlEditor.tsx | 44 +++++++++++++++++++ .../plugins/datasource/prometheus/tracking.ts | 1 + 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 82c905e8c4eb..eb2b2a6d5dcf 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -85,6 +85,7 @@ export const Components = { editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle options: 'data-testid prometheus options', // wrapper for options group legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents + legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents format: 'data-testid prometheus format', step: 'prometheus-step', // id for autosize component type: 'data-testid prometheus type', //wrapper for radio button group diff --git a/public/app/plugins/datasource/prometheus/dataquery.gen.ts b/public/app/plugins/datasource/prometheus/dataquery.gen.ts index 3ba622cecffa..ca31666cc831 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.gen.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + /** + * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname + */ + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 27beaafb4d84..42c803f15141 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,6 +12,7 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; +import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; export interface UIOptions { exemplars: boolean; @@ -19,6 +20,7 @@ export interface UIOptions { format: boolean; minStep: boolean; legend: boolean; + legendUrl: boolean; resolution: boolean; } @@ -59,7 +61,6 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0]; const queryTypeValue = getQueryTypeValue(query); const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryTypeValue)!.label; - return (
@@ -72,6 +73,11 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> + onChange({ ...query, legendUrlFormat })} + onRunQuery={onRunQuery} + /> void; + onRunQuery: () => void; +} + +/** + * Tests for this component are on the parent level (PromQueryBuilderOptions). + */ +export const PromQueryLegendUrlEditor = React.memo(({ legendFormat, onChange, onRunQuery }) => { + const inputRef = useRef(null); + const onLegendFormatChanged = (evt: React.FormEvent) => { + onRunQuery(); + } + + return ( + + + + ); +}); + +PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; + +export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { + return legendUrlFormat; +} diff --git a/public/app/plugins/datasource/prometheus/tracking.ts b/public/app/plugins/datasource/prometheus/tracking.ts index c604f34c6f3f..3a047efa8d2a 100644 --- a/public/app/plugins/datasource/prometheus/tracking.ts +++ b/public/app/plugins/datasource/prometheus/tracking.ts @@ -32,6 +32,7 @@ export function trackQuery( intervalFactor: query.intervalFactor, utcOffsetSec: query.utcOffsetSec, legend: query.legendFormat, + legendUrl: query.legendUrlFormat, valueWithRefId: query.valueWithRefId, requestId: request.requestId, showingGraph: query.showingGraph, From 48e7d9470ea8164567acf3e0f5ca9049a5a41211 Mon Sep 17 00:00:00 2001 From: Eduard Date: Tue, 27 Feb 2024 12:41:54 +0100 Subject: [PATCH 02/39] ability to save has been restored i thought i fixed it yesterday morning but aparently i didn't, now i have for sure --- packages/grafana-prometheus/src/dataquery.cue | 2 ++ packages/grafana-prometheus/src/dataquery.gen.ts | 4 ++++ .../rule-editor/RecordingRuleEditor.tsx | 1 + .../app/plugins/datasource/loki/dataquery.gen.ts | 4 ++++ .../plugins/datasource/prometheus/dataquery.cue | 2 ++ .../components/PromQueryBuilderOptions.tsx | 6 ++---- .../components/PromQueryLegendUrlEditor.tsx | 15 ++++++++------- 7 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/grafana-prometheus/src/dataquery.cue b/packages/grafana-prometheus/src/dataquery.cue index 177af1929c69..6c1a4764476d 100644 --- a/packages/grafana-prometheus/src/dataquery.cue +++ b/packages/grafana-prometheus/src/dataquery.cue @@ -41,6 +41,8 @@ composableKinds: DataQuery: { format?: #PromQueryFormat // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname legendFormat?: string + // Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + legendUrlFormat?: string // @deprecated Used to specify how many times to divide max data points by. We use max data points under query options // See https://github.com/grafana/grafana/issues/48081 intervalFactor?: number diff --git a/packages/grafana-prometheus/src/dataquery.gen.ts b/packages/grafana-prometheus/src/dataquery.gen.ts index 3ba622cecffa..9c6f343a6465 100644 --- a/packages/grafana-prometheus/src/dataquery.gen.ts +++ b/packages/grafana-prometheus/src/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + /** + * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + */ + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx index 3fe2feb640f2..c52df76554b4 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx @@ -71,6 +71,7 @@ export const RecordingRuleEditor: FC = ({ instant: Boolean(changedQuery.instant), range: Boolean(changedQuery.range), legendFormat: changedQuery.legendFormat, + legendUrlFormat: changedQuery.legendUrlFormat, }, }; onChangeQuery([merged]); diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index e9f78618566b..3e782ab1f883 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface Loki extends common.DataQuery { * Used to override the name of the series. */ legendFormat?: string; + /** + * Used to override the name of the series. + */ + legendUrlFormat?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/prometheus/dataquery.cue b/public/app/plugins/datasource/prometheus/dataquery.cue index 177af1929c69..6c1a4764476d 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.cue +++ b/public/app/plugins/datasource/prometheus/dataquery.cue @@ -41,6 +41,8 @@ composableKinds: DataQuery: { format?: #PromQueryFormat // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname legendFormat?: string + // Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + legendUrlFormat?: string // @deprecated Used to specify how many times to divide max data points by. We use max data points under query options // See https://github.com/grafana/grafana/issues/48081 intervalFactor?: number diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 42c803f15141..7d083e8cce1f 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -74,7 +74,7 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onRunQuery={onRunQuery} /> onChange({ ...query, legendUrlFormat })} onRunQuery={onRunQuery} /> @@ -151,9 +151,7 @@ function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: str const items: string[] = []; items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`); - console.log(query.legendUrlFormat); - console.log(getLegendUrlModeLabel(query.legendUrlFormat)); + items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`) items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx index 86dd1ce39af5..eef9b53381f2 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx @@ -5,17 +5,18 @@ import { EditorField } from '@grafana/experimental'; import { AutoSizeInput } from '@grafana/ui'; export interface Props { - legendFormat: string | undefined; - onChange: (legendFormat: string) => void; + legendUrlFormat: string | undefined; + onChange: (legendUrlFormat: string) => void; onRunQuery: () => void; } /** * Tests for this component are on the parent level (PromQueryBuilderOptions). */ -export const PromQueryLegendUrlEditor = React.memo(({ legendFormat, onChange, onRunQuery }) => { +export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { const inputRef = useRef(null); - const onLegendFormatChanged = (evt: React.FormEvent) => { + const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + onChange(evt.currentTarget.value); onRunQuery(); } @@ -26,11 +27,11 @@ export const PromQueryLegendUrlEditor = React.memo(({ legendFormat, onCha data-testid={selectors.components.DataSource.Prometheus.queryEditor.legendUrl} > From 417d6cdb772c4551477cd4e907382f1c02414dc2 Mon Sep 17 00:00:00 2001 From: Eduard Date: Tue, 27 Feb 2024 13:53:52 +0100 Subject: [PATCH 03/39] log renderlegendformat --- packages/grafana-data/src/utils/legend.ts | 2 ++ .../querybuilder/components/PromQueryBuilderOptions.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grafana-data/src/utils/legend.ts b/packages/grafana-data/src/utils/legend.ts index 407e4fb874aa..abcadae76e79 100644 --- a/packages/grafana-data/src/utils/legend.ts +++ b/packages/grafana-data/src/utils/legend.ts @@ -3,5 +3,7 @@ import { Labels } from '../types'; /** replace labels in a string. Used for loki+prometheus legend formats */ export function renderLegendFormat(aliasPattern: string, aliasData: Labels): string { const aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; + console.log(aliasPattern); + console.log(aliasData); return aliasPattern.replace(aliasRegex, (_, g1) => (aliasData[g1] ? aliasData[g1] : g1)); } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 7d083e8cce1f..c1f90b0b26c1 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -151,7 +151,7 @@ function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: str const items: string[] = []; items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`) + items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`); items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); From 1d47bce5cbe6ceecf5c4e869d839bdf991809730 Mon Sep 17 00:00:00 2001 From: Eduard Date: Thu, 7 Mar 2024 10:27:05 +0100 Subject: [PATCH 04/39] added MD Url as label [Your New Legend Label with {{job}} variables](https://your.url/{{instance}}/for/example) --- .../VizLegend/VizLegendListItem.tsx | 28 ++++++++++++++++--- .../src/components/VizLegend/types.ts | 1 + .../datasource/prometheus/dataquery.gen.ts | 2 +- .../components/PromQueryLegendUrlEditor.tsx | 3 -- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 0518ae5ac5d9..9171d35ad8df 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -56,14 +56,34 @@ export const VizLegendListItem = ({ [item, onLabelMouseOut] ); + const [labelWithUrl, setLabelWithUrl] = useState(''); + const [urlForLabel, setUrlForLabel] = useState(''); + const [isUsingUrl, setIsUsingUrl] = useState(false); + + const onClick = useCallback( (event: React.MouseEvent) => { if (onLabelClick) { - onLabelClick(item, event); + if(isUsingUrl) { + window.location.href = urlForLabel; + } else { onLabelClick(item, event); } } }, - [item, onLabelClick] + [item, onLabelClick, isUsingUrl, urlForLabel] ); + console.log(item); + //item.url = item.label.split(); + useEffect(() => { + const regex = /\[([^\]]+)\]\((.*?)\)/; + const match = item.label.match(regex); + setIsUsingUrl(match ? true : false); + console.log("isUsingUrl", isUsingUrl); + if (isUsingUrl) { + setLabelWithUrl(match[1]); + setUrlForLabel(match[2]); + console.log("deconstructed url", labelWithUrl, urlForLabel); + } + }, [item.label, isUsingUrl, labelWithUrl, urlForLabel]); return (
({ onClick={onClick} className={styles.label} > - {item.label} + {isUsingUrl ? labelWithUrl : item.label} {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/VizLegend/types.ts b/packages/grafana-ui/src/components/VizLegend/types.ts index d64a96f1d5fb..e4b73668c848 100644 --- a/packages/grafana-ui/src/components/VizLegend/types.ts +++ b/packages/grafana-ui/src/components/VizLegend/types.ts @@ -40,6 +40,7 @@ export interface LegendProps extends VizLegendBaseProps, VizLegendTa export interface VizLegendItem { getItemKey?: () => string; label: string; + url?: string; color?: string; gradient?: string; yAxis: number; diff --git a/public/app/plugins/datasource/prometheus/dataquery.gen.ts b/public/app/plugins/datasource/prometheus/dataquery.gen.ts index ca31666cc831..9c6f343a6465 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.gen.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.gen.ts @@ -48,7 +48,7 @@ export interface Prometheus extends common.DataQuery { */ legendFormat?: string; /** - * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname + * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname */ legendUrlFormat?: string; /** diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx index eef9b53381f2..cbb36a4f01fb 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx @@ -10,9 +10,6 @@ export interface Props { onRunQuery: () => void; } -/** - * Tests for this component are on the parent level (PromQueryBuilderOptions). - */ export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { const inputRef = useRef(null); const onLegendUrlFormatChanged = (evt: React.FormEvent) => { From c3a831cf902ee05a50d0a262cf79355fc97f59aa Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 8 Mar 2024 13:09:19 +0100 Subject: [PATCH 05/39] Revert "added field for url and necessary variables" This reverts commit 89636b0d4daff155a61087c25d8460ea7231775d. --- packages/grafana-data/src/utils/legend.ts | 2 - .../src/selectors/components.ts | 1 - packages/grafana-prometheus/src/dataquery.cue | 2 - .../grafana-prometheus/src/dataquery.gen.ts | 4 -- .../VizLegend/VizLegendListItem.tsx | 28 ++----------- .../src/components/VizLegend/types.ts | 1 - .../rule-editor/RecordingRuleEditor.tsx | 1 - .../plugins/datasource/loki/dataquery.gen.ts | 4 -- .../datasource/prometheus/dataquery.cue | 2 - .../datasource/prometheus/dataquery.gen.ts | 4 -- .../components/PromQueryBuilderOptions.tsx | 9 +--- .../components/PromQueryLegendUrlEditor.tsx | 42 ------------------- .../plugins/datasource/prometheus/tracking.ts | 1 - 13 files changed, 5 insertions(+), 96 deletions(-) delete mode 100644 public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx diff --git a/packages/grafana-data/src/utils/legend.ts b/packages/grafana-data/src/utils/legend.ts index abcadae76e79..407e4fb874aa 100644 --- a/packages/grafana-data/src/utils/legend.ts +++ b/packages/grafana-data/src/utils/legend.ts @@ -3,7 +3,5 @@ import { Labels } from '../types'; /** replace labels in a string. Used for loki+prometheus legend formats */ export function renderLegendFormat(aliasPattern: string, aliasData: Labels): string { const aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; - console.log(aliasPattern); - console.log(aliasData); return aliasPattern.replace(aliasRegex, (_, g1) => (aliasData[g1] ? aliasData[g1] : g1)); } diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index eb2b2a6d5dcf..82c905e8c4eb 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -85,7 +85,6 @@ export const Components = { editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle options: 'data-testid prometheus options', // wrapper for options group legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents - legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents format: 'data-testid prometheus format', step: 'prometheus-step', // id for autosize component type: 'data-testid prometheus type', //wrapper for radio button group diff --git a/packages/grafana-prometheus/src/dataquery.cue b/packages/grafana-prometheus/src/dataquery.cue index 6c1a4764476d..177af1929c69 100644 --- a/packages/grafana-prometheus/src/dataquery.cue +++ b/packages/grafana-prometheus/src/dataquery.cue @@ -41,8 +41,6 @@ composableKinds: DataQuery: { format?: #PromQueryFormat // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname legendFormat?: string - // Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname - legendUrlFormat?: string // @deprecated Used to specify how many times to divide max data points by. We use max data points under query options // See https://github.com/grafana/grafana/issues/48081 intervalFactor?: number diff --git a/packages/grafana-prometheus/src/dataquery.gen.ts b/packages/grafana-prometheus/src/dataquery.gen.ts index 9c6f343a6465..3ba622cecffa 100644 --- a/packages/grafana-prometheus/src/dataquery.gen.ts +++ b/packages/grafana-prometheus/src/dataquery.gen.ts @@ -47,10 +47,6 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; - /** - * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname - */ - legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 9171d35ad8df..0518ae5ac5d9 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -56,34 +56,14 @@ export const VizLegendListItem = ({ [item, onLabelMouseOut] ); - const [labelWithUrl, setLabelWithUrl] = useState(''); - const [urlForLabel, setUrlForLabel] = useState(''); - const [isUsingUrl, setIsUsingUrl] = useState(false); - - const onClick = useCallback( (event: React.MouseEvent) => { if (onLabelClick) { - if(isUsingUrl) { - window.location.href = urlForLabel; - } else { onLabelClick(item, event); } + onLabelClick(item, event); } }, - [item, onLabelClick, isUsingUrl, urlForLabel] + [item, onLabelClick] ); - console.log(item); - //item.url = item.label.split(); - useEffect(() => { - const regex = /\[([^\]]+)\]\((.*?)\)/; - const match = item.label.match(regex); - setIsUsingUrl(match ? true : false); - console.log("isUsingUrl", isUsingUrl); - if (isUsingUrl) { - setLabelWithUrl(match[1]); - setUrlForLabel(match[2]); - console.log("deconstructed url", labelWithUrl, urlForLabel); - } - }, [item.label, isUsingUrl, labelWithUrl, urlForLabel]); return (
({ onClick={onClick} className={styles.label} > - {isUsingUrl ? labelWithUrl : item.label} + {item.label} {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/VizLegend/types.ts b/packages/grafana-ui/src/components/VizLegend/types.ts index e4b73668c848..d64a96f1d5fb 100644 --- a/packages/grafana-ui/src/components/VizLegend/types.ts +++ b/packages/grafana-ui/src/components/VizLegend/types.ts @@ -40,7 +40,6 @@ export interface LegendProps extends VizLegendBaseProps, VizLegendTa export interface VizLegendItem { getItemKey?: () => string; label: string; - url?: string; color?: string; gradient?: string; yAxis: number; diff --git a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx index c52df76554b4..3fe2feb640f2 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx @@ -71,7 +71,6 @@ export const RecordingRuleEditor: FC = ({ instant: Boolean(changedQuery.instant), range: Boolean(changedQuery.range), legendFormat: changedQuery.legendFormat, - legendUrlFormat: changedQuery.legendUrlFormat, }, }; onChangeQuery([merged]); diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index 3e782ab1f883..e9f78618566b 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -47,10 +47,6 @@ export interface Loki extends common.DataQuery { * Used to override the name of the series. */ legendFormat?: string; - /** - * Used to override the name of the series. - */ - legendUrlFormat?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/prometheus/dataquery.cue b/public/app/plugins/datasource/prometheus/dataquery.cue index 6c1a4764476d..177af1929c69 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.cue +++ b/public/app/plugins/datasource/prometheus/dataquery.cue @@ -41,8 +41,6 @@ composableKinds: DataQuery: { format?: #PromQueryFormat // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname legendFormat?: string - // Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname - legendUrlFormat?: string // @deprecated Used to specify how many times to divide max data points by. We use max data points under query options // See https://github.com/grafana/grafana/issues/48081 intervalFactor?: number diff --git a/public/app/plugins/datasource/prometheus/dataquery.gen.ts b/public/app/plugins/datasource/prometheus/dataquery.gen.ts index 9c6f343a6465..3ba622cecffa 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.gen.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.gen.ts @@ -47,10 +47,6 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; - /** - * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname - */ - legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index c1f90b0b26c1..27beaafb4d84 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,7 +12,6 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; -import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; export interface UIOptions { exemplars: boolean; @@ -20,7 +19,6 @@ export interface UIOptions { format: boolean; minStep: boolean; legend: boolean; - legendUrl: boolean; resolution: boolean; } @@ -61,6 +59,7 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0]; const queryTypeValue = getQueryTypeValue(query); const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryTypeValue)!.label; + return (
@@ -73,11 +72,6 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> - onChange({ ...query, legendUrlFormat })} - onRunQuery={onRunQuery} - /> void; - onRunQuery: () => void; -} - -export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { - const inputRef = useRef(null); - const onLegendUrlFormatChanged = (evt: React.FormEvent) => { - onChange(evt.currentTarget.value); - onRunQuery(); - } - - return ( - - - - ); -}); - -PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; - -export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { - return legendUrlFormat; -} diff --git a/public/app/plugins/datasource/prometheus/tracking.ts b/public/app/plugins/datasource/prometheus/tracking.ts index 3a047efa8d2a..c604f34c6f3f 100644 --- a/public/app/plugins/datasource/prometheus/tracking.ts +++ b/public/app/plugins/datasource/prometheus/tracking.ts @@ -32,7 +32,6 @@ export function trackQuery( intervalFactor: query.intervalFactor, utcOffsetSec: query.utcOffsetSec, legend: query.legendFormat, - legendUrl: query.legendUrlFormat, valueWithRefId: query.valueWithRefId, requestId: request.requestId, showingGraph: query.showingGraph, From e9571ca7bb4643f304564189d80279faf2285559 Mon Sep 17 00:00:00 2001 From: Eduard Date: Tue, 12 Mar 2024 11:20:10 +0100 Subject: [PATCH 06/39] new url field and ability to save it --- .../src/selectors/components.ts | 1 + .../VizLegend/VizLegendListItem.tsx | 2 +- .../src/components/VizLegend/types.ts | 1 + .../datasource/prometheus/dataquery.gen.ts | 4 ++ .../components/PromQueryBuilderOptions.tsx | 10 +++++ .../components/PromQueryLegendUrlEditor.tsx | 42 +++++++++++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 82c905e8c4eb..eb2b2a6d5dcf 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -85,6 +85,7 @@ export const Components = { editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle options: 'data-testid prometheus options', // wrapper for options group legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents + legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents format: 'data-testid prometheus format', step: 'prometheus-step', // id for autosize component type: 'data-testid prometheus type', //wrapper for radio button group diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 0518ae5ac5d9..4aa2b511d8a6 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -81,7 +81,7 @@ export const VizLegendListItem = ({ onClick={onClick} className={styles.label} > - {item.label} + {item.label} " {item.url} " {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/VizLegend/types.ts b/packages/grafana-ui/src/components/VizLegend/types.ts index d64a96f1d5fb..e4b73668c848 100644 --- a/packages/grafana-ui/src/components/VizLegend/types.ts +++ b/packages/grafana-ui/src/components/VizLegend/types.ts @@ -40,6 +40,7 @@ export interface LegendProps extends VizLegendBaseProps, VizLegendTa export interface VizLegendItem { getItemKey?: () => string; label: string; + url?: string; color?: string; gradient?: string; yAxis: number; diff --git a/public/app/plugins/datasource/prometheus/dataquery.gen.ts b/public/app/plugins/datasource/prometheus/dataquery.gen.ts index 3ba622cecffa..9c6f343a6465 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.gen.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + /** + * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + */ + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 27beaafb4d84..af1521aefc0e 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,6 +12,8 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; +import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; + export interface UIOptions { exemplars: boolean; @@ -19,6 +21,7 @@ export interface UIOptions { format: boolean; minStep: boolean; legend: boolean; + legendUrl: boolean; resolution: boolean; } @@ -72,6 +75,12 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> + onChange({ ...query, legendUrlFormat })} + onRunQuery={onRunQuery} + /> + void; + onRunQuery: () => void; +} + +export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { + const inputRef = useRef(null); + const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + onChange(evt.currentTarget.value); + onRunQuery(); + } + + return ( + + + + ); +}); + +PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; + +export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { + return legendUrlFormat; +} From 5409b7e7f01164b8ede15296f2e74265d0c90b61 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 25 Mar 2024 11:39:46 +0100 Subject: [PATCH 07/39] Introduce temporary constant for URL field in backend getUrl does not seem to be functioning as expected, it is not able to read the LegendUrlFormat from the config file yet --- pkg/tsdb/prometheus/models/query.go | 23 +++++++++++----------- pkg/tsdb/prometheus/querydata/response.go | 24 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/pkg/tsdb/prometheus/models/query.go b/pkg/tsdb/prometheus/models/query.go index da5ef0f9b1a4..c26aa1f68e55 100644 --- a/pkg/tsdb/prometheus/models/query.go +++ b/pkg/tsdb/prometheus/models/query.go @@ -68,17 +68,18 @@ type TimeRange struct { } type Query struct { - Expr string - Step time.Duration - LegendFormat string - Start time.Time - End time.Time - RefId string - InstantQuery bool - RangeQuery bool - ExemplarQuery bool - UtcOffsetSec int64 - Scope Scope + Expr string + Step time.Duration + LegendFormat string + LegendUrlFormat string + Start time.Time + End time.Time + RefId string + InstantQuery bool + RangeQuery bool + ExemplarQuery bool + UtcOffsetSec int64 + Scope Scope } type Scope struct { diff --git a/pkg/tsdb/prometheus/querydata/response.go b/pkg/tsdb/prometheus/querydata/response.go index dd0e5f3f31bd..eb94b2a8c828 100644 --- a/pkg/tsdb/prometheus/querydata/response.go +++ b/pkg/tsdb/prometheus/querydata/response.go @@ -116,8 +116,9 @@ func addMetadataToMultiFrame(q *models.Query, frame *data.Frame, enableDataplane frame.Fields[0].Config = &data.FieldConfig{Interval: float64(q.Step.Milliseconds())} customName := getName(q, frame.Fields[1]) + labelUrl := getUrl(q, frame.Fields[1]) if customName != "" { - frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName} + frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName, UrlFromDS: labelUrl} } if enableDataplane { @@ -190,6 +191,27 @@ func getName(q *models.Query, field *data.Field) string { return legend } +func getUrl(q *models.Query, field *data.Field) string { + labels := field.Labels + var legendUrl string + if q.LegendUrlFormat != "" { + result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendUrlFormat), func(in []byte) []byte { + labelName := strings.Replace(string(in), "{{", "", 1) + labelName = strings.Replace(labelName, "}}", "", 1) + labelName = strings.TrimSpace(labelName) + if val, exists := labels[labelName]; exists { + return []byte(val) + } + return []byte{} + }) + legendUrl = string(result) + } + if legendUrl == "" { + legendUrl = "/?fallback_or_default_url" + } + return legendUrl +} + func isExemplarFrame(frame *data.Frame) bool { rt := models.ResultTypeFromFrame(frame) return rt == models.ResultTypeExemplar From 7537ed1a6bd5f62abb8c8bbfa91edbaba4ba361e Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 25 Mar 2024 11:41:36 +0100 Subject: [PATCH 08/39] Implement clickable labels in frontend using backend URLs front end wise it seems to be working fine now, we can't know for certain untill we are able to pull form the config in the back end --- packages/grafana-data/src/types/dataFrame.ts | 5 +++++ packages/grafana-prometheus/src/dataquery.gen.ts | 1 + .../src/components/VizLegend/VizLegendListItem.tsx | 6 ++++-- packages/grafana-ui/src/components/uPlot/PlotLegend.tsx | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index b2fbf0ac46a4..b41ecf7ab83c 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -46,6 +46,11 @@ export interface FieldConfig { */ displayNameFromDS?: string; + /** + * This is the URL that the field value links to. This supports template variables. + */ + urlFromDS?: string; + /** * Human readable field metadata */ diff --git a/packages/grafana-prometheus/src/dataquery.gen.ts b/packages/grafana-prometheus/src/dataquery.gen.ts index 3ba622cecffa..c49a00962b6f 100644 --- a/packages/grafana-prometheus/src/dataquery.gen.ts +++ b/packages/grafana-prometheus/src/dataquery.gen.ts @@ -47,6 +47,7 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 4aa2b511d8a6..73db7eef0382 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -59,7 +59,9 @@ export const VizLegendListItem = ({ const onClick = useCallback( (event: React.MouseEvent) => { if (onLabelClick) { - onLabelClick(item, event); + if( item.url != null) { + window.location.href = item.url; + } else { onLabelClick(item, event); } } }, [item, onLabelClick] @@ -81,7 +83,7 @@ export const VizLegendListItem = ({ onClick={onClick} className={styles.label} > - {item.label} " {item.url} " + {item.label} {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx b/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx index 3d5bfe5aaefb..37a8be04f0c5 100644 --- a/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx +++ b/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx @@ -71,6 +71,7 @@ export const PlotLegend = React.memo( } const label = getFieldDisplayName(field, data[fieldIndex.frameIndex]!, data); + const url = field.config?.urlFromDS; const scaleColor = getFieldSeriesColor(field, theme); const seriesColor = scaleColor.color; @@ -79,6 +80,7 @@ export const PlotLegend = React.memo( fieldIndex, color: seriesColor, label, + url, yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2, getDisplayValues: () => { if (!calcs?.length) { From b5275e221d1c5b2e5b161c49582844275c1de129 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 25 Mar 2024 14:20:38 +0100 Subject: [PATCH 09/39] enable dynamic datasource URL configuration via UI No longer is this done with a constant and works fully as intended --- .../kinds/dataquery/types_dataquery_gen.go | 3 ++ pkg/tsdb/prometheus/models/query.go | 32 ++++++++++--------- pkg/tsdb/prometheus/querydata/response.go | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go index b31c9eee626d..0d8b59c4f6f4 100644 --- a/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/prometheus/kinds/dataquery/types_dataquery_gen.go @@ -86,6 +86,9 @@ type PrometheusDataQuery struct { // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname LegendFormat *string `json:"legendFormat,omitempty"` + // Used for the drilldown label url. Ex. {{hostname}} will be replaced with label value for hostname within the url + LegendUrlFormat *string `json:"legendUrlFormat,omitempty"` + // Specify the query flavor // TODO make this required and give it a default QueryType *string `json:"queryType,omitempty"` diff --git a/pkg/tsdb/prometheus/models/query.go b/pkg/tsdb/prometheus/models/query.go index c26aa1f68e55..9402dd707e97 100644 --- a/pkg/tsdb/prometheus/models/query.go +++ b/pkg/tsdb/prometheus/models/query.go @@ -54,11 +54,12 @@ type QueryModel struct { dataquery.PrometheusDataQuery // The following properties may be part of the request payload, however they are not saved in panel JSON // Timezone offset to align start & end time on backend - UtcOffsetSec int64 `json:"utcOffsetSec,omitempty"` - LegendFormat string `json:"legendFormat,omitempty"` - Interval string `json:"interval,omitempty"` - IntervalMs int64 `json:"intervalMs,omitempty"` - IntervalFactor int64 `json:"intervalFactor,omitempty"` + UtcOffsetSec int64 `json:"utcOffsetSec,omitempty"` + LegendFormat string `json:"legendFormat,omitempty"` + LegendUrlFormat string `json:"legendUrlFormat,omitempty"` + Interval string `json:"interval,omitempty"` + IntervalMs int64 `json:"intervalMs,omitempty"` + IntervalFactor int64 `json:"intervalFactor,omitempty"` } type TimeRange struct { @@ -145,16 +146,17 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator } return &Query{ - Expr: expr, - Step: calculatedStep, - LegendFormat: model.LegendFormat, - Start: query.TimeRange.From, - End: query.TimeRange.To, - RefId: query.RefID, - InstantQuery: instantQuery, - RangeQuery: rangeQuery, - ExemplarQuery: exemplarQuery, - UtcOffsetSec: model.UtcOffsetSec, + Expr: expr, + Step: calculatedStep, + LegendFormat: model.LegendFormat, + LegendUrlFormat: model.LegendUrlFormat, + Start: query.TimeRange.From, + End: query.TimeRange.To, + RefId: query.RefID, + InstantQuery: instantQuery, + RangeQuery: rangeQuery, + ExemplarQuery: exemplarQuery, + UtcOffsetSec: model.UtcOffsetSec, }, nil } diff --git a/pkg/tsdb/prometheus/querydata/response.go b/pkg/tsdb/prometheus/querydata/response.go index eb94b2a8c828..fdea0bcb2bcd 100644 --- a/pkg/tsdb/prometheus/querydata/response.go +++ b/pkg/tsdb/prometheus/querydata/response.go @@ -117,10 +117,10 @@ func addMetadataToMultiFrame(q *models.Query, frame *data.Frame, enableDataplane customName := getName(q, frame.Fields[1]) labelUrl := getUrl(q, frame.Fields[1]) + if customName != "" { frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName, UrlFromDS: labelUrl} } - if enableDataplane { valueField := frame.Fields[1] if n, ok := valueField.Labels["__name__"]; ok { From 726333a39f1430984b184cfc9149379bdb57f74a Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 29 Mar 2024 13:08:02 +0100 Subject: [PATCH 10/39] Refactored duplicate code into a unified helper function for improved maintainability. --- pkg/tsdb/prometheus/querydata/response.go | 30 +++++++++-------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/pkg/tsdb/prometheus/querydata/response.go b/pkg/tsdb/prometheus/querydata/response.go index fdea0bcb2bcd..d0fe27f4a34a 100644 --- a/pkg/tsdb/prometheus/querydata/response.go +++ b/pkg/tsdb/prometheus/querydata/response.go @@ -170,17 +170,8 @@ func getName(q *models.Query, field *data.Field) string { if len(labels) > 0 { legend = "" } - } else if q.LegendFormat != "" { - result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendFormat), func(in []byte) []byte { - labelName := strings.Replace(string(in), "{{", "", 1) - labelName = strings.Replace(labelName, "}}", "", 1) - labelName = strings.TrimSpace(labelName) - if val, exists := labels[labelName]; exists { - return []byte(val) - } - return []byte{} - }) - legend = string(result) + } else { + legend = convertLabel(q.LegendFormat, field) } // If legend is empty brackets, use query expression @@ -192,10 +183,14 @@ func getName(q *models.Query, field *data.Field) string { } func getUrl(q *models.Query, field *data.Field) string { + return convertLabel(q.LegendUrlFormat, field) +} + +func convertLabel(label string, field *data.Field) string { + var convertedLabel string labels := field.Labels - var legendUrl string - if q.LegendUrlFormat != "" { - result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendUrlFormat), func(in []byte) []byte { + if label != "" { + result := legendFormatRegexp.ReplaceAllFunc([]byte(label), func(in []byte) []byte { labelName := strings.Replace(string(in), "{{", "", 1) labelName = strings.Replace(labelName, "}}", "", 1) labelName = strings.TrimSpace(labelName) @@ -204,12 +199,9 @@ func getUrl(q *models.Query, field *data.Field) string { } return []byte{} }) - legendUrl = string(result) - } - if legendUrl == "" { - legendUrl = "/?fallback_or_default_url" + convertedLabel = string(result) } - return legendUrl + return convertedLabel } func isExemplarFrame(frame *data.Frame) bool { From 8dbecd9430ae97e4dd798252004fd00df35f8218 Mon Sep 17 00:00:00 2001 From: Eduard Date: Thu, 4 Apr 2024 14:38:07 +0200 Subject: [PATCH 11/39] Implement backend support for Loki drilldown link This commit introduces the necessary backend enhancements to enable drilldown links within Loki. The implementation follows a similar style of converting the url from the datasource as used in Prometheus --- pkg/tsdb/loki/frame.go | 23 +++++++++++++++++++ .../kinds/dataquery/types_dataquery_gen.go | 3 +++ pkg/tsdb/loki/parse_query.go | 5 ++++ pkg/tsdb/loki/types.go | 1 + 4 files changed, 32 insertions(+) diff --git a/pkg/tsdb/loki/frame.go b/pkg/tsdb/loki/frame.go index 8655b746acac..a861a14498c9 100644 --- a/pkg/tsdb/loki/frame.go +++ b/pkg/tsdb/loki/frame.go @@ -50,6 +50,7 @@ func adjustMetricFrame(frame *data.Frame, query *lokiQuery, setFrameName bool) e isMetricRange := query.QueryType == QueryTypeRange name := formatName(labels, query) + Url := formatUrl(labels, query) if setFrameName { frame.Name = name } @@ -78,6 +79,7 @@ func adjustMetricFrame(frame *data.Frame, query *lokiQuery, setFrameName bool) e valueField.Config = &data.FieldConfig{} } valueField.Config.DisplayNameFromDS = name + valueField.Config.UrlFromDS = Url return nil } @@ -296,6 +298,27 @@ func formatName(labels map[string]string, query *lokiQuery) string { return string(result) } +func formatUrl(labels map[string]string, query *lokiQuery) string { + return convertLabel(query.LegendUrlFormat, labels) +} + +func convertLabel(label string, labels map[string]string) string { + var convertedLabel string + if label != "" { + result := legendFormat.ReplaceAllFunc([]byte(label), func(in []byte) []byte { + labelName := strings.Replace(string(in), "{{", "", 1) + labelName = strings.Replace(labelName, "}}", "", 1) + labelName = strings.TrimSpace(labelName) + if val, exists := labels[labelName]; exists { + return []byte(val) + } + return []byte{} + }) + convertedLabel = string(result) + } + return convertedLabel +} + func getFrameLabels(frame *data.Frame) map[string]string { labels := make(map[string]string) diff --git a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go index db33add28137..1777ccd95989 100644 --- a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go @@ -89,6 +89,9 @@ type LokiDataQuery struct { // Used to override the name of the series. LegendFormat *string `json:"legendFormat,omitempty"` + // Used for the drilldown label url + LegendUrlFormat *string `json:"legendUrlFormat,omitempty"` + // Used to limit the number of log rows returned. MaxLines *int64 `json:"maxLines,omitempty"` diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go index 5e1c8e44d0b5..05e5a517726b 100644 --- a/pkg/tsdb/loki/parse_query.go +++ b/pkg/tsdb/loki/parse_query.go @@ -165,6 +165,10 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { if model.LegendFormat != nil { legendFormat = *model.LegendFormat } + var legendUrlFormat string + if model.LegendUrlFormat != nil { + legendUrlFormat = *model.LegendUrlFormat + } supportingQueryType, err := parseSupportingQueryType(model.SupportingQueryType) if err != nil { @@ -178,6 +182,7 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { Step: step, MaxLines: int(maxLines), LegendFormat: legendFormat, + LegendUrlFormat: legendUrlFormat, Start: start, End: end, RefID: query.RefID, diff --git a/pkg/tsdb/loki/types.go b/pkg/tsdb/loki/types.go index 5ac5ee514c34..1fe9196dcb58 100644 --- a/pkg/tsdb/loki/types.go +++ b/pkg/tsdb/loki/types.go @@ -35,6 +35,7 @@ type lokiQuery struct { Step time.Duration MaxLines int LegendFormat string + LegendUrlFormat string Start time.Time End time.Time RefID string From a868c283d8050590b448b2a7396b98a5a84983e4 Mon Sep 17 00:00:00 2001 From: Eduard Date: Thu, 4 Apr 2024 14:42:10 +0200 Subject: [PATCH 12/39] Refactored duplicate code into a unified helper function Applied the helper function to the formatName to avoid duplicate code --- pkg/tsdb/loki/frame.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/tsdb/loki/frame.go b/pkg/tsdb/loki/frame.go index a861a14498c9..e895ef35e791 100644 --- a/pkg/tsdb/loki/frame.go +++ b/pkg/tsdb/loki/frame.go @@ -285,17 +285,7 @@ func formatName(labels map[string]string, query *lokiQuery) string { return formatNamePrometheusStyle(labels) } - result := legendFormat.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { - labelName := strings.Replace(string(in), "{{", "", 1) - labelName = strings.Replace(labelName, "}}", "", 1) - labelName = strings.TrimSpace(labelName) - if val, exists := labels[labelName]; exists { - return []byte(val) - } - return []byte{} - }) - - return string(result) + return convertLabel(query.LegendFormat, labels) } func formatUrl(labels map[string]string, query *lokiQuery) string { From 4783569bf47d456f4892ec4d42440d0b4f1ec371 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 5 Apr 2024 12:49:14 +0200 Subject: [PATCH 13/39] Implement front end support for Loki drilldown link added the required fields to be able to save the url to the config --- .../plugins/datasource/loki/dataquery.gen.ts | 4 ++++ .../components/LokiQueryBuilderOptions.tsx | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index e9f78618566b..30a575809dbc 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface Loki extends common.DataQuery { * Used to override the name of the series. */ legendFormat?: string; + /** + * Used for drilldown/clickable legend labels. + */ + legendUrlFormat?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index ad9b7ae80f12..cead76814cbf 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -52,6 +52,10 @@ export const LokiQueryBuilderOptions = React.memo( onChange({ ...query, legendFormat: evt.currentTarget.value }); onRunQuery(); }; + const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + onChange({ ...query, legendUrlFormat: evt.currentTarget.value }); + onRunQuery(); + }; function onMaxLinesChange(e: React.SyntheticEvent) { const newMaxLines = preprocessMaxLines(e.currentTarget.value); @@ -95,6 +99,18 @@ export const LokiQueryBuilderOptions = React.memo( onCommitChange={onLegendFormatChanged} /> + + + @@ -185,6 +201,10 @@ function getCollapsedInfo( items.push(`Legend: ${query.legendFormat}`); } + if (query.legendUrlFormat) { + items.push(`Legend URL: ${query.legendUrlFormat}`); + } + items.push(`Type: ${queryTypeLabel?.label}`); if (isLogQuery) { From af2f52adc6f3733dc02352ecac71891b07e6c721 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 5 Apr 2024 12:55:58 +0200 Subject: [PATCH 14/39] Collapsed info url now cuts of with ... if too long in the dashboard for both loki and prometheus --- .../loki/querybuilder/components/LokiQueryBuilderOptions.tsx | 3 ++- .../querybuilder/components/PromQueryBuilderOptions.tsx | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index cead76814cbf..26d29ea778e2 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -202,7 +202,8 @@ function getCollapsedInfo( } if (query.legendUrlFormat) { - items.push(`Legend URL: ${query.legendUrlFormat}`); + let legendUrl = query.legendUrlFormat.length > 10 ? query.legendUrlFormat.slice(0,10) + "..." : query.legendUrlFormat + items.push(`Legend URL: ${legendUrl}`); } items.push(`Type: ${queryTypeLabel?.label}`); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index af1521aefc0e..909798d05674 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -153,8 +153,11 @@ function getQueryTypeValue(query: PromQuery) { function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: string, app?: CoreApp): string[] { const items: string[] = []; + let rawLegendUrl = getLegendUrlModeLabel(query.legendUrlFormat); + let legendUrl = rawLegendUrl!.length > 10 ? rawLegendUrl!.slice(0,10) + "..." : rawLegendUrl!; + items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`); + items.push(`URL: ${legendUrl}`); items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); From e8e73d46613a728cf8c51e3f219410c78d0eec49 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 5 Apr 2024 13:01:43 +0200 Subject: [PATCH 15/39] Renamed Legend URL to URL for consistency in collapsed info --- .../loki/querybuilder/components/LokiQueryBuilderOptions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 26d29ea778e2..364e0becef56 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -203,7 +203,7 @@ function getCollapsedInfo( if (query.legendUrlFormat) { let legendUrl = query.legendUrlFormat.length > 10 ? query.legendUrlFormat.slice(0,10) + "..." : query.legendUrlFormat - items.push(`Legend URL: ${legendUrl}`); + items.push(`URL: ${legendUrl}`); } items.push(`Type: ${queryTypeLabel?.label}`); From e7734947d4809f3bcd62dc34a1de723417181b5f Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 8 Apr 2024 11:07:45 +0200 Subject: [PATCH 16/39] Refactored the "..." for backwards compatability the current tests were failing due to this implementation, i solved it by changing how its adding the "..." at the end --- .../querybuilder/components/LokiQueryBuilderOptions.tsx | 7 ++++++- .../querybuilder/components/PromQueryBuilderOptions.tsx | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 364e0becef56..f434ba97da08 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -202,7 +202,12 @@ function getCollapsedInfo( } if (query.legendUrlFormat) { - let legendUrl = query.legendUrlFormat.length > 10 ? query.legendUrlFormat.slice(0,10) + "..." : query.legendUrlFormat + let legendUrl = query.legendUrlFormat; + + if (typeof legendUrl === 'string' && legendUrl.length > 10) { + legendUrl = legendUrl.slice(0, 10) + "..."; + } + items.push(`URL: ${legendUrl}`); } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 909798d05674..3358f8f71200 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -153,8 +153,11 @@ function getQueryTypeValue(query: PromQuery) { function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: string, app?: CoreApp): string[] { const items: string[] = []; - let rawLegendUrl = getLegendUrlModeLabel(query.legendUrlFormat); - let legendUrl = rawLegendUrl!.length > 10 ? rawLegendUrl!.slice(0,10) + "..." : rawLegendUrl!; + let legendUrl = getLegendUrlModeLabel(query.legendUrlFormat);; + + if (typeof legendUrl === 'string' && legendUrl.length > 10) { + legendUrl = legendUrl.slice(0, 10) + "..."; + } items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); items.push(`URL: ${legendUrl}`); From a94430f241a2315ddca7352e2a8b28086f23d662 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 8 Apr 2024 16:19:08 +0200 Subject: [PATCH 17/39] Introduced tests for cutting off the url This is so it wouldn't take too much screen real estate --- .../LokiQueryBuilderOptions.test.tsx | 32 +++++++++++++++++-- .../PromQueryBuilderOptions.test.tsx | 22 ++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index cdc80221e9f8..8eaa4634ece8 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -42,7 +42,7 @@ describe('LokiQueryBuilderOptions', () => { await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit - const element = screen.getAllByTestId('autosize-input')[1]; + const element = screen.getAllByTestId('autosize-input')[2]; await userEvent.type(element, '10'); await userEvent.keyboard('{enter}'); @@ -59,7 +59,7 @@ describe('LokiQueryBuilderOptions', () => { await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit - const element = screen.getAllByTestId('autosize-input')[1]; + const element = screen.getAllByTestId('autosize-input')[2]; await userEvent.type(element, '-10'); await userEvent.keyboard('{enter}'); @@ -76,7 +76,7 @@ describe('LokiQueryBuilderOptions', () => { await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit - const element = screen.getAllByTestId('autosize-input')[1]; + const element = screen.getAllByTestId('autosize-input')[2]; await userEvent.type(element, 'asd'); await userEvent.keyboard('{enter}'); @@ -154,6 +154,32 @@ describe('LokiQueryBuilderOptions', () => { }); }); +describe('getCollapsedInfo', () => { + it('displays a clipped legend URL for long URLs', () => { + const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate'; + setup({ legendUrlFormat: longUrl }); + + const urlText = screen.getByText((content, node) => { + const hasText = (node: any) => node.textContent === `URL: ${longUrl.slice(0, 10)}...`; + const nodeHasText = hasText(node); + const childrenDontHaveText = Array.from(node!.children).every( + (child) => !hasText(child) + ); + return nodeHasText && childrenDontHaveText; + }); + expect(urlText).toBeInTheDocument(); + }); + + it('displays the full legend URL for short URLs', () => { + const shortUrl = 'shortUrl'; + setup({ legendUrlFormat: shortUrl }); + + const urlTextElement = screen.getByText(`URL: ${shortUrl}`); + expect(urlTextElement).toBeInTheDocument(); + }); +}); + + function setup(queryOverrides: Partial = {}) { const props = { query: { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx index 4f9e1384980a..b11ab138c1b9 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -102,6 +102,25 @@ describe('PromQueryBuilderOptions', () => { }); }); +describe('getCollapsedInfo', () => { + it('displays a clipped legend URL for long URLs', async () => { + const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate'; + setup({ legendUrlFormat: longUrl }); + + const optionsElement = screen.getByTestId('data-testid prometheus options'); + expect(optionsElement.textContent).toContain(longUrl.slice(0, 10) + "..."); + }); + + it('displays the full legend URL for short URLs', async () => { + const shortUrl = 'shortUrl'; + setup({ legendUrlFormat: shortUrl }); + + const optionsElement = screen.getByTestId('data-testid prometheus options'); + expect(optionsElement.textContent).toContain(shortUrl); + }); +}); + + function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.PanelEditor) { const props = { app, @@ -112,6 +131,7 @@ function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.P expr: '', range: true, instant: false, + legendUrlFormat: '', } as PromQuery, CoreApp.PanelEditor ), @@ -128,7 +148,7 @@ function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.P resolution: true, }, }; - + const { container } = render(); return { container, props }; } From 335556e2bc2a69c765071fc6b394ca75bad61c75 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 26 Feb 2024 12:41:00 +0100 Subject: [PATCH 18/39] added field for url and necessary variables still not able to save even though the code is a copy of the one i had before, still looking into it --- .../src/selectors/components.ts | 1 + .../datasource/prometheus/dataquery.ts | 4 ++ .../components/PromQueryBuilderOptions.tsx | 11 ++++- .../components/PromQueryLegendUrlEditor.tsx | 44 +++++++++++++++++++ .../plugins/datasource/prometheus/tracking.ts | 1 + 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index f67c6874dcf1..fdcbd3308d76 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -86,6 +86,7 @@ export const Components = { editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle options: 'data-testid prometheus options', // wrapper for options group legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents + legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents format: 'data-testid prometheus format', step: 'prometheus-step', // id for autosize component type: 'data-testid prometheus type', //wrapper for radio button group diff --git a/public/app/plugins/datasource/prometheus/dataquery.ts b/public/app/plugins/datasource/prometheus/dataquery.ts index 54f6cd7bac90..28b305867edf 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.ts @@ -38,6 +38,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + /** + * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname + */ + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index aa0f055f2a0d..1d342450352a 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,6 +12,7 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; +import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; export interface UIOptions { exemplars: boolean; @@ -19,6 +20,7 @@ export interface UIOptions { format: boolean; minStep: boolean; legend: boolean; + legendUrl: boolean; resolution: boolean; } @@ -59,7 +61,6 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0]; const queryTypeValue = getQueryTypeValue(query); const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryTypeValue)!.label; - return (
@@ -72,6 +73,11 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> + onChange({ ...query, legendUrlFormat })} + onRunQuery={onRunQuery} + /> void; + onRunQuery: () => void; +} + +/** + * Tests for this component are on the parent level (PromQueryBuilderOptions). + */ +export const PromQueryLegendUrlEditor = React.memo(({ legendFormat, onChange, onRunQuery }) => { + const inputRef = useRef(null); + const onLegendFormatChanged = (evt: React.FormEvent) => { + onRunQuery(); + } + + return ( + + + + ); +}); + +PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; + +export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { + return legendUrlFormat; +} diff --git a/public/app/plugins/datasource/prometheus/tracking.ts b/public/app/plugins/datasource/prometheus/tracking.ts index c604f34c6f3f..3a047efa8d2a 100644 --- a/public/app/plugins/datasource/prometheus/tracking.ts +++ b/public/app/plugins/datasource/prometheus/tracking.ts @@ -32,6 +32,7 @@ export function trackQuery( intervalFactor: query.intervalFactor, utcOffsetSec: query.utcOffsetSec, legend: query.legendFormat, + legendUrl: query.legendUrlFormat, valueWithRefId: query.valueWithRefId, requestId: request.requestId, showingGraph: query.showingGraph, From cf47b2f2e7e6283efd31b62d139bb98ce7350bb4 Mon Sep 17 00:00:00 2001 From: Eduard Date: Tue, 27 Feb 2024 12:41:54 +0100 Subject: [PATCH 19/39] ability to save has been restored i thought i fixed it yesterday morning but aparently i didn't, now i have for sure --- packages/grafana-prometheus/src/dataquery.ts | 4 ++++ .../rule-editor/RecordingRuleEditor.tsx | 1 + .../app/plugins/datasource/loki/dataquery.gen.ts | 4 ++++ .../components/PromQueryBuilderOptions.tsx | 6 ++---- .../components/PromQueryLegendUrlEditor.tsx | 15 ++++++++------- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/grafana-prometheus/src/dataquery.ts b/packages/grafana-prometheus/src/dataquery.ts index 545a32345b74..b9f240db0b9f 100644 --- a/packages/grafana-prometheus/src/dataquery.ts +++ b/packages/grafana-prometheus/src/dataquery.ts @@ -38,6 +38,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + /** + * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + */ + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx index 60a0d44fe619..8d0634306155 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx @@ -74,6 +74,7 @@ export const RecordingRuleEditor: FC = ({ // Query type is used by Loki queries queryType: changedQuery.queryType, legendFormat: changedQuery.legendFormat, + legendUrlFormat: changedQuery.legendUrlFormat, }, }; onChangeQuery([merged]); diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index 063deb3ee2f4..31bfc86718da 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface LokiDataQuery extends common.DataQuery { * Used to override the name of the series. */ legendFormat?: string; + /** + * Used to override the name of the series. + */ + legendUrlFormat?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 1d342450352a..296c0b6d488f 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -74,7 +74,7 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onRunQuery={onRunQuery} /> onChange({ ...query, legendUrlFormat })} onRunQuery={onRunQuery} /> @@ -151,9 +151,7 @@ function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: str const items: string[] = []; items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`); - console.log(query.legendUrlFormat); - console.log(getLegendUrlModeLabel(query.legendUrlFormat)); + items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`) items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx index 86dd1ce39af5..eef9b53381f2 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx @@ -5,17 +5,18 @@ import { EditorField } from '@grafana/experimental'; import { AutoSizeInput } from '@grafana/ui'; export interface Props { - legendFormat: string | undefined; - onChange: (legendFormat: string) => void; + legendUrlFormat: string | undefined; + onChange: (legendUrlFormat: string) => void; onRunQuery: () => void; } /** * Tests for this component are on the parent level (PromQueryBuilderOptions). */ -export const PromQueryLegendUrlEditor = React.memo(({ legendFormat, onChange, onRunQuery }) => { +export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { const inputRef = useRef(null); - const onLegendFormatChanged = (evt: React.FormEvent) => { + const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + onChange(evt.currentTarget.value); onRunQuery(); } @@ -26,11 +27,11 @@ export const PromQueryLegendUrlEditor = React.memo(({ legendFormat, onCha data-testid={selectors.components.DataSource.Prometheus.queryEditor.legendUrl} > From 5009754e7b2cf98d174fdc2193adc6b0bcc4292a Mon Sep 17 00:00:00 2001 From: Eduard Date: Tue, 27 Feb 2024 13:53:52 +0100 Subject: [PATCH 20/39] log renderlegendformat --- packages/grafana-data/src/utils/legend.ts | 2 ++ .../querybuilder/components/PromQueryBuilderOptions.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grafana-data/src/utils/legend.ts b/packages/grafana-data/src/utils/legend.ts index 407e4fb874aa..abcadae76e79 100644 --- a/packages/grafana-data/src/utils/legend.ts +++ b/packages/grafana-data/src/utils/legend.ts @@ -3,5 +3,7 @@ import { Labels } from '../types'; /** replace labels in a string. Used for loki+prometheus legend formats */ export function renderLegendFormat(aliasPattern: string, aliasData: Labels): string { const aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; + console.log(aliasPattern); + console.log(aliasData); return aliasPattern.replace(aliasRegex, (_, g1) => (aliasData[g1] ? aliasData[g1] : g1)); } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 296c0b6d488f..aa513e4c3770 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -151,7 +151,7 @@ function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: str const items: string[] = []; items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`) + items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`); items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); From f69e45debf6b6bebd7593ddd5e49830d21b58591 Mon Sep 17 00:00:00 2001 From: Eduard Date: Thu, 7 Mar 2024 10:27:05 +0100 Subject: [PATCH 21/39] added MD Url as label [Your New Legend Label with {{job}} variables](https://your.url/{{instance}}/for/example) --- .../VizLegend/VizLegendListItem.tsx | 28 ++++++++++++++++--- .../src/components/VizLegend/types.ts | 1 + .../datasource/prometheus/dataquery.ts | 2 +- .../components/PromQueryLegendUrlEditor.tsx | 3 -- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 5a43727e7286..89db88226442 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -56,14 +56,34 @@ export const VizLegendListItem = ({ [item, onLabelMouseOut] ); + const [labelWithUrl, setLabelWithUrl] = useState(''); + const [urlForLabel, setUrlForLabel] = useState(''); + const [isUsingUrl, setIsUsingUrl] = useState(false); + + const onClick = useCallback( (event: React.MouseEvent) => { if (onLabelClick) { - onLabelClick(item, event); + if(isUsingUrl) { + window.location.href = urlForLabel; + } else { onLabelClick(item, event); } } }, - [item, onLabelClick] + [item, onLabelClick, isUsingUrl, urlForLabel] ); + console.log(item); + //item.url = item.label.split(); + useEffect(() => { + const regex = /\[([^\]]+)\]\((.*?)\)/; + const match = item.label.match(regex); + setIsUsingUrl(match ? true : false); + console.log("isUsingUrl", isUsingUrl); + if (isUsingUrl) { + setLabelWithUrl(match[1]); + setUrlForLabel(match[2]); + console.log("deconstructed url", labelWithUrl, urlForLabel); + } + }, [item.label, isUsingUrl, labelWithUrl, urlForLabel]); return (
({ onClick={onClick} className={styles.label} > - {item.label} + {isUsingUrl ? labelWithUrl : item.label} {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/VizLegend/types.ts b/packages/grafana-ui/src/components/VizLegend/types.ts index 341008ba4805..d608b00af727 100644 --- a/packages/grafana-ui/src/components/VizLegend/types.ts +++ b/packages/grafana-ui/src/components/VizLegend/types.ts @@ -40,6 +40,7 @@ export interface LegendProps extends VizLegendBaseProps, VizLegendTa export interface VizLegendItem { getItemKey?: () => string; label: string; + url?: string; color?: string; gradient?: string; yAxis: number; diff --git a/public/app/plugins/datasource/prometheus/dataquery.ts b/public/app/plugins/datasource/prometheus/dataquery.ts index 28b305867edf..0646d7bb608a 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.ts @@ -39,7 +39,7 @@ export interface Prometheus extends common.DataQuery { */ legendFormat?: string; /** - * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname + * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname */ legendUrlFormat?: string; /** diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx index eef9b53381f2..cbb36a4f01fb 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx @@ -10,9 +10,6 @@ export interface Props { onRunQuery: () => void; } -/** - * Tests for this component are on the parent level (PromQueryBuilderOptions). - */ export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { const inputRef = useRef(null); const onLegendUrlFormatChanged = (evt: React.FormEvent) => { From 619679f0c2f5bb09cfef478ca270bc406613408b Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 8 Mar 2024 13:09:19 +0100 Subject: [PATCH 22/39] Revert "added field for url and necessary variables" This reverts commit 89636b0d4daff155a61087c25d8460ea7231775d. --- packages/grafana-data/src/utils/legend.ts | 2 - .../src/selectors/components.ts | 1 - packages/grafana-prometheus/src/dataquery.ts | 4 -- .../VizLegend/VizLegendListItem.tsx | 28 ++----------- .../src/components/VizLegend/types.ts | 1 - .../rule-editor/RecordingRuleEditor.tsx | 1 - .../plugins/datasource/loki/dataquery.gen.ts | 4 -- .../datasource/prometheus/dataquery.ts | 4 -- .../components/PromQueryBuilderOptions.tsx | 9 +--- .../components/PromQueryLegendUrlEditor.tsx | 42 ------------------- .../plugins/datasource/prometheus/tracking.ts | 1 - 11 files changed, 5 insertions(+), 92 deletions(-) delete mode 100644 public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx diff --git a/packages/grafana-data/src/utils/legend.ts b/packages/grafana-data/src/utils/legend.ts index abcadae76e79..407e4fb874aa 100644 --- a/packages/grafana-data/src/utils/legend.ts +++ b/packages/grafana-data/src/utils/legend.ts @@ -3,7 +3,5 @@ import { Labels } from '../types'; /** replace labels in a string. Used for loki+prometheus legend formats */ export function renderLegendFormat(aliasPattern: string, aliasData: Labels): string { const aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; - console.log(aliasPattern); - console.log(aliasData); return aliasPattern.replace(aliasRegex, (_, g1) => (aliasData[g1] ? aliasData[g1] : g1)); } diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index fdcbd3308d76..f67c6874dcf1 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -86,7 +86,6 @@ export const Components = { editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle options: 'data-testid prometheus options', // wrapper for options group legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents - legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents format: 'data-testid prometheus format', step: 'prometheus-step', // id for autosize component type: 'data-testid prometheus type', //wrapper for radio button group diff --git a/packages/grafana-prometheus/src/dataquery.ts b/packages/grafana-prometheus/src/dataquery.ts index b9f240db0b9f..545a32345b74 100644 --- a/packages/grafana-prometheus/src/dataquery.ts +++ b/packages/grafana-prometheus/src/dataquery.ts @@ -38,10 +38,6 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; - /** - * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname - */ - legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 89db88226442..5a43727e7286 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -56,34 +56,14 @@ export const VizLegendListItem = ({ [item, onLabelMouseOut] ); - const [labelWithUrl, setLabelWithUrl] = useState(''); - const [urlForLabel, setUrlForLabel] = useState(''); - const [isUsingUrl, setIsUsingUrl] = useState(false); - - const onClick = useCallback( (event: React.MouseEvent) => { if (onLabelClick) { - if(isUsingUrl) { - window.location.href = urlForLabel; - } else { onLabelClick(item, event); } + onLabelClick(item, event); } }, - [item, onLabelClick, isUsingUrl, urlForLabel] + [item, onLabelClick] ); - console.log(item); - //item.url = item.label.split(); - useEffect(() => { - const regex = /\[([^\]]+)\]\((.*?)\)/; - const match = item.label.match(regex); - setIsUsingUrl(match ? true : false); - console.log("isUsingUrl", isUsingUrl); - if (isUsingUrl) { - setLabelWithUrl(match[1]); - setUrlForLabel(match[2]); - console.log("deconstructed url", labelWithUrl, urlForLabel); - } - }, [item.label, isUsingUrl, labelWithUrl, urlForLabel]); return (
({ onClick={onClick} className={styles.label} > - {isUsingUrl ? labelWithUrl : item.label} + {item.label} {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/VizLegend/types.ts b/packages/grafana-ui/src/components/VizLegend/types.ts index d608b00af727..341008ba4805 100644 --- a/packages/grafana-ui/src/components/VizLegend/types.ts +++ b/packages/grafana-ui/src/components/VizLegend/types.ts @@ -40,7 +40,6 @@ export interface LegendProps extends VizLegendBaseProps, VizLegendTa export interface VizLegendItem { getItemKey?: () => string; label: string; - url?: string; color?: string; gradient?: string; yAxis: number; diff --git a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx index 8d0634306155..60a0d44fe619 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx @@ -74,7 +74,6 @@ export const RecordingRuleEditor: FC = ({ // Query type is used by Loki queries queryType: changedQuery.queryType, legendFormat: changedQuery.legendFormat, - legendUrlFormat: changedQuery.legendUrlFormat, }, }; onChangeQuery([merged]); diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index 31bfc86718da..063deb3ee2f4 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -47,10 +47,6 @@ export interface LokiDataQuery extends common.DataQuery { * Used to override the name of the series. */ legendFormat?: string; - /** - * Used to override the name of the series. - */ - legendUrlFormat?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/prometheus/dataquery.ts b/public/app/plugins/datasource/prometheus/dataquery.ts index 0646d7bb608a..54f6cd7bac90 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.ts @@ -38,10 +38,6 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; - /** - * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname - */ - legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index aa513e4c3770..aa0f055f2a0d 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,7 +12,6 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; -import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; export interface UIOptions { exemplars: boolean; @@ -20,7 +19,6 @@ export interface UIOptions { format: boolean; minStep: boolean; legend: boolean; - legendUrl: boolean; resolution: boolean; } @@ -61,6 +59,7 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0]; const queryTypeValue = getQueryTypeValue(query); const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryTypeValue)!.label; + return (
@@ -73,11 +72,6 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> - onChange({ ...query, legendUrlFormat })} - onRunQuery={onRunQuery} - /> void; - onRunQuery: () => void; -} - -export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { - const inputRef = useRef(null); - const onLegendUrlFormatChanged = (evt: React.FormEvent) => { - onChange(evt.currentTarget.value); - onRunQuery(); - } - - return ( - - - - ); -}); - -PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; - -export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { - return legendUrlFormat; -} diff --git a/public/app/plugins/datasource/prometheus/tracking.ts b/public/app/plugins/datasource/prometheus/tracking.ts index 3a047efa8d2a..c604f34c6f3f 100644 --- a/public/app/plugins/datasource/prometheus/tracking.ts +++ b/public/app/plugins/datasource/prometheus/tracking.ts @@ -32,7 +32,6 @@ export function trackQuery( intervalFactor: query.intervalFactor, utcOffsetSec: query.utcOffsetSec, legend: query.legendFormat, - legendUrl: query.legendUrlFormat, valueWithRefId: query.valueWithRefId, requestId: request.requestId, showingGraph: query.showingGraph, From da30d6d9bc41abb5dea5ed33965caf5b21ffc52a Mon Sep 17 00:00:00 2001 From: Eduard Date: Tue, 12 Mar 2024 11:20:10 +0100 Subject: [PATCH 23/39] new url field and ability to save it --- .../src/selectors/components.ts | 1 + .../VizLegend/VizLegendListItem.tsx | 2 +- .../src/components/VizLegend/types.ts | 1 + .../datasource/prometheus/dataquery.ts | 4 ++ .../components/PromQueryBuilderOptions.tsx | 10 +++++ .../components/PromQueryLegendUrlEditor.tsx | 42 +++++++++++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index f67c6874dcf1..fdcbd3308d76 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -86,6 +86,7 @@ export const Components = { editorToggle: 'data-testid QueryEditorModeToggle', // wrapper for toggle options: 'data-testid prometheus options', // wrapper for options group legend: 'data-testid prometheus legend wrapper', // wrapper for multiple compomnents + legendUrl: 'data-testid prometheus legendUrl wrapper', // wrapper for multiple compomnents format: 'data-testid prometheus format', step: 'prometheus-step', // id for autosize component type: 'data-testid prometheus type', //wrapper for radio button group diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 5a43727e7286..89f9c3ef36e7 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -86,7 +86,7 @@ export const VizLegendListItem = ({ onClick={onClick} className={styles.label} > - {item.label} + {item.label} " {item.url} " {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/VizLegend/types.ts b/packages/grafana-ui/src/components/VizLegend/types.ts index 341008ba4805..d608b00af727 100644 --- a/packages/grafana-ui/src/components/VizLegend/types.ts +++ b/packages/grafana-ui/src/components/VizLegend/types.ts @@ -40,6 +40,7 @@ export interface LegendProps extends VizLegendBaseProps, VizLegendTa export interface VizLegendItem { getItemKey?: () => string; label: string; + url?: string; color?: string; gradient?: string; yAxis: number; diff --git a/public/app/plugins/datasource/prometheus/dataquery.ts b/public/app/plugins/datasource/prometheus/dataquery.ts index 54f6cd7bac90..0646d7bb608a 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.ts @@ -38,6 +38,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + /** + * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + */ + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index aa0f055f2a0d..18b56fce7903 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,6 +12,8 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; +import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; + export interface UIOptions { exemplars: boolean; @@ -19,6 +21,7 @@ export interface UIOptions { format: boolean; minStep: boolean; legend: boolean; + legendUrl: boolean; resolution: boolean; } @@ -72,6 +75,12 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> + onChange({ ...query, legendUrlFormat })} + onRunQuery={onRunQuery} + /> + void; + onRunQuery: () => void; +} + +export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { + const inputRef = useRef(null); + const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + onChange(evt.currentTarget.value); + onRunQuery(); + } + + return ( + + + + ); +}); + +PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; + +export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { + return legendUrlFormat; +} From 00aaabde125fbad1f9fc6028549f2aa3559751fe Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 25 Mar 2024 11:39:46 +0100 Subject: [PATCH 24/39] Introduce temporary constant for URL field in backend getUrl does not seem to be functioning as expected, it is not able to read the LegendUrlFormat from the config file yet --- pkg/promlib/models/query.go | 23 ++++++++++++----------- pkg/promlib/querydata/response.go | 24 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/pkg/promlib/models/query.go b/pkg/promlib/models/query.go index bb6c996cbab7..a510711c831c 100644 --- a/pkg/promlib/models/query.go +++ b/pkg/promlib/models/query.go @@ -149,17 +149,18 @@ type TimeRange struct { // The internal query object type Query struct { - Expr string - Step time.Duration - LegendFormat string - Start time.Time - End time.Time - RefId string - InstantQuery bool - RangeQuery bool - ExemplarQuery bool - UtcOffsetSec int64 - Scope *ScopeSpec + Expr string + Step time.Duration + LegendFormat string + LegendUrlFormat string + Start time.Time + End time.Time + RefId string + InstantQuery bool + RangeQuery bool + ExemplarQuery bool + UtcOffsetSec int64 + Scope *ScopeSpec } type Scope struct { diff --git a/pkg/promlib/querydata/response.go b/pkg/promlib/querydata/response.go index 7181400df168..7207d1255ab4 100644 --- a/pkg/promlib/querydata/response.go +++ b/pkg/promlib/querydata/response.go @@ -116,8 +116,9 @@ func addMetadataToMultiFrame(q *models.Query, frame *data.Frame, enableDataplane frame.Fields[0].Config = &data.FieldConfig{Interval: float64(q.Step.Milliseconds())} customName := getName(q, frame.Fields[1]) + labelUrl := getUrl(q, frame.Fields[1]) if customName != "" { - frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName} + frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName, UrlFromDS: labelUrl} } if enableDataplane { @@ -190,6 +191,27 @@ func getName(q *models.Query, field *data.Field) string { return legend } +func getUrl(q *models.Query, field *data.Field) string { + labels := field.Labels + var legendUrl string + if q.LegendUrlFormat != "" { + result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendUrlFormat), func(in []byte) []byte { + labelName := strings.Replace(string(in), "{{", "", 1) + labelName = strings.Replace(labelName, "}}", "", 1) + labelName = strings.TrimSpace(labelName) + if val, exists := labels[labelName]; exists { + return []byte(val) + } + return []byte{} + }) + legendUrl = string(result) + } + if legendUrl == "" { + legendUrl = "/?fallback_or_default_url" + } + return legendUrl +} + func isExemplarFrame(frame *data.Frame) bool { rt := models.ResultTypeFromFrame(frame) return rt == models.ResultTypeExemplar From 2cc3b486c78deff712d01510b3853bee3e1dfbd1 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 25 Mar 2024 11:41:36 +0100 Subject: [PATCH 25/39] Implement clickable labels in frontend using backend URLs front end wise it seems to be working fine now, we can't know for certain untill we are able to pull form the config in the back end --- packages/grafana-data/src/types/dataFrame.ts | 5 +++++ packages/grafana-prometheus/src/dataquery.ts | 1 + .../src/components/VizLegend/VizLegendListItem.tsx | 6 ++++-- packages/grafana-ui/src/components/uPlot/PlotLegend.tsx | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index 291f88b23332..57dce2c90e1c 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -47,6 +47,11 @@ export interface FieldConfig { */ displayNameFromDS?: string; + /** + * This is the URL that the field value links to. This supports template variables. + */ + urlFromDS?: string; + /** * Human readable field metadata */ diff --git a/packages/grafana-prometheus/src/dataquery.ts b/packages/grafana-prometheus/src/dataquery.ts index 545a32345b74..4120b1862dcf 100644 --- a/packages/grafana-prometheus/src/dataquery.ts +++ b/packages/grafana-prometheus/src/dataquery.ts @@ -38,6 +38,7 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; + legendUrlFormat?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx index 89f9c3ef36e7..55893011cb0c 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendListItem.tsx @@ -59,7 +59,9 @@ export const VizLegendListItem = ({ const onClick = useCallback( (event: React.MouseEvent) => { if (onLabelClick) { - onLabelClick(item, event); + if( item.url != null) { + window.location.href = item.url; + } else { onLabelClick(item, event); } } }, [item, onLabelClick] @@ -86,7 +88,7 @@ export const VizLegendListItem = ({ onClick={onClick} className={styles.label} > - {item.label} " {item.url} " + {item.label} {item.getDisplayValues && } diff --git a/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx b/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx index 3d5bfe5aaefb..37a8be04f0c5 100644 --- a/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx +++ b/packages/grafana-ui/src/components/uPlot/PlotLegend.tsx @@ -71,6 +71,7 @@ export const PlotLegend = React.memo( } const label = getFieldDisplayName(field, data[fieldIndex.frameIndex]!, data); + const url = field.config?.urlFromDS; const scaleColor = getFieldSeriesColor(field, theme); const seriesColor = scaleColor.color; @@ -79,6 +80,7 @@ export const PlotLegend = React.memo( fieldIndex, color: seriesColor, label, + url, yAxis: axisPlacement === AxisPlacement.Left || axisPlacement === AxisPlacement.Bottom ? 1 : 2, getDisplayValues: () => { if (!calcs?.length) { From a110d2239fbba91282ef38ea108aed4a199ce617 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 25 Mar 2024 14:20:38 +0100 Subject: [PATCH 26/39] enable dynamic datasource URL configuration via UI No longer is this done with a constant and works fully as intended --- pkg/promlib/models/query.go | 21 +++++++++++---------- pkg/promlib/querydata/response.go | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pkg/promlib/models/query.go b/pkg/promlib/models/query.go index a510711c831c..065ec9448b34 100644 --- a/pkg/promlib/models/query.go +++ b/pkg/promlib/models/query.go @@ -206,16 +206,17 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator } return &Query{ - Expr: expr, - Step: calculatedStep, - LegendFormat: model.LegendFormat, - Start: query.TimeRange.From, - End: query.TimeRange.To, - RefId: query.RefID, - InstantQuery: model.Instant, - RangeQuery: model.Range, - ExemplarQuery: model.Exemplar, - UtcOffsetSec: model.UtcOffsetSec, + Expr: expr, + Step: calculatedStep, + LegendFormat: model.LegendFormat, + LegendUrlFormat: model.LegendUrlFormat, + Start: query.TimeRange.From, + End: query.TimeRange.To, + RefId: query.RefID, + InstantQuery: model.Instant, + RangeQuery: model.Range, + ExemplarQuery: model.Exemplar, + UtcOffsetSec: model.UtcOffsetSec, }, nil } diff --git a/pkg/promlib/querydata/response.go b/pkg/promlib/querydata/response.go index 7207d1255ab4..a2ed8831ee7a 100644 --- a/pkg/promlib/querydata/response.go +++ b/pkg/promlib/querydata/response.go @@ -117,10 +117,10 @@ func addMetadataToMultiFrame(q *models.Query, frame *data.Frame, enableDataplane customName := getName(q, frame.Fields[1]) labelUrl := getUrl(q, frame.Fields[1]) + if customName != "" { frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: customName, UrlFromDS: labelUrl} } - if enableDataplane { valueField := frame.Fields[1] if n, ok := valueField.Labels["__name__"]; ok { From d853f3c0d830f8d9c07fa11bbafb47cb9ddd56f7 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 29 Mar 2024 13:08:02 +0100 Subject: [PATCH 27/39] Refactored duplicate code into a unified helper function for improved maintainability. --- pkg/promlib/querydata/response.go | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/pkg/promlib/querydata/response.go b/pkg/promlib/querydata/response.go index a2ed8831ee7a..45d3f1a35828 100644 --- a/pkg/promlib/querydata/response.go +++ b/pkg/promlib/querydata/response.go @@ -170,17 +170,8 @@ func getName(q *models.Query, field *data.Field) string { if len(labels) > 0 { legend = "" } - } else if q.LegendFormat != "" { - result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendFormat), func(in []byte) []byte { - labelName := strings.Replace(string(in), "{{", "", 1) - labelName = strings.Replace(labelName, "}}", "", 1) - labelName = strings.TrimSpace(labelName) - if val, exists := labels[labelName]; exists { - return []byte(val) - } - return []byte{} - }) - legend = string(result) + } else { + legend = convertLabel(q.LegendFormat, field) } // If legend is empty brackets, use query expression @@ -192,10 +183,14 @@ func getName(q *models.Query, field *data.Field) string { } func getUrl(q *models.Query, field *data.Field) string { + return convertLabel(q.LegendUrlFormat, field) +} + +func convertLabel(label string, field *data.Field) string { + var convertedLabel string labels := field.Labels - var legendUrl string - if q.LegendUrlFormat != "" { - result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendUrlFormat), func(in []byte) []byte { + if label != "" { + result := legendFormatRegexp.ReplaceAllFunc([]byte(label), func(in []byte) []byte { labelName := strings.Replace(string(in), "{{", "", 1) labelName = strings.Replace(labelName, "}}", "", 1) labelName = strings.TrimSpace(labelName) @@ -204,12 +199,9 @@ func getUrl(q *models.Query, field *data.Field) string { } return []byte{} }) - legendUrl = string(result) - } - if legendUrl == "" { - legendUrl = "/?fallback_or_default_url" + convertedLabel = string(result) } - return legendUrl + return convertedLabel } func isExemplarFrame(frame *data.Frame) bool { From c7888439361f68faaec5390f82f1da1d960baf63 Mon Sep 17 00:00:00 2001 From: Eduard Date: Thu, 4 Apr 2024 14:38:07 +0200 Subject: [PATCH 28/39] Implement backend support for Loki drilldown link This commit introduces the necessary backend enhancements to enable drilldown links within Loki. The implementation follows a similar style of converting the url from the datasource as used in Prometheus --- pkg/tsdb/loki/frame.go | 23 +++++++++++++++++++ .../kinds/dataquery/types_dataquery_gen.go | 3 +++ pkg/tsdb/loki/parse_query.go | 5 ++++ pkg/tsdb/loki/types.go | 1 + 4 files changed, 32 insertions(+) diff --git a/pkg/tsdb/loki/frame.go b/pkg/tsdb/loki/frame.go index 8655b746acac..a861a14498c9 100644 --- a/pkg/tsdb/loki/frame.go +++ b/pkg/tsdb/loki/frame.go @@ -50,6 +50,7 @@ func adjustMetricFrame(frame *data.Frame, query *lokiQuery, setFrameName bool) e isMetricRange := query.QueryType == QueryTypeRange name := formatName(labels, query) + Url := formatUrl(labels, query) if setFrameName { frame.Name = name } @@ -78,6 +79,7 @@ func adjustMetricFrame(frame *data.Frame, query *lokiQuery, setFrameName bool) e valueField.Config = &data.FieldConfig{} } valueField.Config.DisplayNameFromDS = name + valueField.Config.UrlFromDS = Url return nil } @@ -296,6 +298,27 @@ func formatName(labels map[string]string, query *lokiQuery) string { return string(result) } +func formatUrl(labels map[string]string, query *lokiQuery) string { + return convertLabel(query.LegendUrlFormat, labels) +} + +func convertLabel(label string, labels map[string]string) string { + var convertedLabel string + if label != "" { + result := legendFormat.ReplaceAllFunc([]byte(label), func(in []byte) []byte { + labelName := strings.Replace(string(in), "{{", "", 1) + labelName = strings.Replace(labelName, "}}", "", 1) + labelName = strings.TrimSpace(labelName) + if val, exists := labels[labelName]; exists { + return []byte(val) + } + return []byte{} + }) + convertedLabel = string(result) + } + return convertedLabel +} + func getFrameLabels(frame *data.Frame) map[string]string { labels := make(map[string]string) diff --git a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go index f9dcd723805a..b950a29fe65c 100644 --- a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go @@ -80,6 +80,9 @@ type LokiDataQuery struct { // Used to override the name of the series. LegendFormat *string `json:"legendFormat,omitempty"` + // Used for the drilldown label url + LegendUrlFormat *string `json:"legendUrlFormat,omitempty"` + // Used to limit the number of log rows returned. MaxLines *int64 `json:"maxLines,omitempty"` diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go index a4f39679d6c8..1de4437c8a8f 100644 --- a/pkg/tsdb/loki/parse_query.go +++ b/pkg/tsdb/loki/parse_query.go @@ -168,6 +168,10 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { if model.LegendFormat != nil { legendFormat = *model.LegendFormat } + var legendUrlFormat string + if model.LegendUrlFormat != nil { + legendUrlFormat = *model.LegendUrlFormat + } supportingQueryType := parseSupportingQueryType(model.SupportingQueryType) @@ -178,6 +182,7 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { Step: step, MaxLines: int(maxLines), LegendFormat: legendFormat, + LegendUrlFormat: legendUrlFormat, Start: start, End: end, RefID: query.RefID, diff --git a/pkg/tsdb/loki/types.go b/pkg/tsdb/loki/types.go index 5ac5ee514c34..1fe9196dcb58 100644 --- a/pkg/tsdb/loki/types.go +++ b/pkg/tsdb/loki/types.go @@ -35,6 +35,7 @@ type lokiQuery struct { Step time.Duration MaxLines int LegendFormat string + LegendUrlFormat string Start time.Time End time.Time RefID string From f5588d4ff2107a6e7575b5d5062a67eb67b9c778 Mon Sep 17 00:00:00 2001 From: Eduard Date: Thu, 4 Apr 2024 14:42:10 +0200 Subject: [PATCH 29/39] Refactored duplicate code into a unified helper function Applied the helper function to the formatName to avoid duplicate code --- pkg/tsdb/loki/frame.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/tsdb/loki/frame.go b/pkg/tsdb/loki/frame.go index a861a14498c9..e895ef35e791 100644 --- a/pkg/tsdb/loki/frame.go +++ b/pkg/tsdb/loki/frame.go @@ -285,17 +285,7 @@ func formatName(labels map[string]string, query *lokiQuery) string { return formatNamePrometheusStyle(labels) } - result := legendFormat.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { - labelName := strings.Replace(string(in), "{{", "", 1) - labelName = strings.Replace(labelName, "}}", "", 1) - labelName = strings.TrimSpace(labelName) - if val, exists := labels[labelName]; exists { - return []byte(val) - } - return []byte{} - }) - - return string(result) + return convertLabel(query.LegendFormat, labels) } func formatUrl(labels map[string]string, query *lokiQuery) string { From 1dae7ae453e5c432ce2fb7a8af9df8d50f669b26 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 5 Apr 2024 12:49:14 +0200 Subject: [PATCH 30/39] Implement front end support for Loki drilldown link added the required fields to be able to save the url to the config --- .../plugins/datasource/loki/dataquery.gen.ts | 4 ++++ .../components/LokiQueryBuilderOptions.tsx | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index 063deb3ee2f4..f995713f1ddf 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -47,6 +47,10 @@ export interface LokiDataQuery extends common.DataQuery { * Used to override the name of the series. */ legendFormat?: string; + /** + * Used for drilldown/clickable legend labels. + */ + legendUrlFormat?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index ad9b7ae80f12..cead76814cbf 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -52,6 +52,10 @@ export const LokiQueryBuilderOptions = React.memo( onChange({ ...query, legendFormat: evt.currentTarget.value }); onRunQuery(); }; + const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + onChange({ ...query, legendUrlFormat: evt.currentTarget.value }); + onRunQuery(); + }; function onMaxLinesChange(e: React.SyntheticEvent) { const newMaxLines = preprocessMaxLines(e.currentTarget.value); @@ -95,6 +99,18 @@ export const LokiQueryBuilderOptions = React.memo( onCommitChange={onLegendFormatChanged} /> + + + @@ -185,6 +201,10 @@ function getCollapsedInfo( items.push(`Legend: ${query.legendFormat}`); } + if (query.legendUrlFormat) { + items.push(`Legend URL: ${query.legendUrlFormat}`); + } + items.push(`Type: ${queryTypeLabel?.label}`); if (isLogQuery) { From 6507d3b612b8ccd2ae86e8ca5357e8eb538da0a5 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 5 Apr 2024 12:55:58 +0200 Subject: [PATCH 31/39] Collapsed info url now cuts of with ... if too long in the dashboard for both loki and prometheus --- .../loki/querybuilder/components/LokiQueryBuilderOptions.tsx | 3 ++- .../querybuilder/components/PromQueryBuilderOptions.tsx | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index cead76814cbf..26d29ea778e2 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -202,7 +202,8 @@ function getCollapsedInfo( } if (query.legendUrlFormat) { - items.push(`Legend URL: ${query.legendUrlFormat}`); + let legendUrl = query.legendUrlFormat.length > 10 ? query.legendUrlFormat.slice(0,10) + "..." : query.legendUrlFormat + items.push(`Legend URL: ${legendUrl}`); } items.push(`Type: ${queryTypeLabel?.label}`); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index 18b56fce7903..abce4ede35b6 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -153,8 +153,11 @@ function getQueryTypeValue(query: PromQuery) { function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: string, app?: CoreApp): string[] { const items: string[] = []; + let rawLegendUrl = getLegendUrlModeLabel(query.legendUrlFormat); + let legendUrl = rawLegendUrl!.length > 10 ? rawLegendUrl!.slice(0,10) + "..." : rawLegendUrl!; + items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${getLegendUrlModeLabel(query.legendUrlFormat)}`); + items.push(`URL: ${legendUrl}`); items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); From e3674c121e2af41f9fe1278c55c92add9c9c0647 Mon Sep 17 00:00:00 2001 From: Eduard Date: Fri, 5 Apr 2024 13:01:43 +0200 Subject: [PATCH 32/39] Renamed Legend URL to URL for consistency in collapsed info --- .../loki/querybuilder/components/LokiQueryBuilderOptions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 26d29ea778e2..364e0becef56 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -203,7 +203,7 @@ function getCollapsedInfo( if (query.legendUrlFormat) { let legendUrl = query.legendUrlFormat.length > 10 ? query.legendUrlFormat.slice(0,10) + "..." : query.legendUrlFormat - items.push(`Legend URL: ${legendUrl}`); + items.push(`URL: ${legendUrl}`); } items.push(`Type: ${queryTypeLabel?.label}`); From 6415842466c22314219a0c0e65f6dfc48a1f86df Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 8 Apr 2024 11:07:45 +0200 Subject: [PATCH 33/39] Refactored the "..." for backwards compatability the current tests were failing due to this implementation, i solved it by changing how its adding the "..." at the end --- .../querybuilder/components/LokiQueryBuilderOptions.tsx | 7 ++++++- .../querybuilder/components/PromQueryBuilderOptions.tsx | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 364e0becef56..f434ba97da08 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -202,7 +202,12 @@ function getCollapsedInfo( } if (query.legendUrlFormat) { - let legendUrl = query.legendUrlFormat.length > 10 ? query.legendUrlFormat.slice(0,10) + "..." : query.legendUrlFormat + let legendUrl = query.legendUrlFormat; + + if (typeof legendUrl === 'string' && legendUrl.length > 10) { + legendUrl = legendUrl.slice(0, 10) + "..."; + } + items.push(`URL: ${legendUrl}`); } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index abce4ede35b6..b4e308e81e05 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -153,8 +153,11 @@ function getQueryTypeValue(query: PromQuery) { function getCollapsedInfo(query: PromQuery, formatOption: string, queryType: string, app?: CoreApp): string[] { const items: string[] = []; - let rawLegendUrl = getLegendUrlModeLabel(query.legendUrlFormat); - let legendUrl = rawLegendUrl!.length > 10 ? rawLegendUrl!.slice(0,10) + "..." : rawLegendUrl!; + let legendUrl = getLegendUrlModeLabel(query.legendUrlFormat);; + + if (typeof legendUrl === 'string' && legendUrl.length > 10) { + legendUrl = legendUrl.slice(0, 10) + "..."; + } items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); items.push(`URL: ${legendUrl}`); From 26fc10427f4f588f3368b62d3deaccd9e0975599 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 8 Apr 2024 16:19:08 +0200 Subject: [PATCH 34/39] Introduced tests for cutting off the url This is so it wouldn't take too much screen real estate --- .../LokiQueryBuilderOptions.test.tsx | 32 +++++++++++++++++-- .../PromQueryBuilderOptions.test.tsx | 22 ++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index cdc80221e9f8..8eaa4634ece8 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -42,7 +42,7 @@ describe('LokiQueryBuilderOptions', () => { await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit - const element = screen.getAllByTestId('autosize-input')[1]; + const element = screen.getAllByTestId('autosize-input')[2]; await userEvent.type(element, '10'); await userEvent.keyboard('{enter}'); @@ -59,7 +59,7 @@ describe('LokiQueryBuilderOptions', () => { await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit - const element = screen.getAllByTestId('autosize-input')[1]; + const element = screen.getAllByTestId('autosize-input')[2]; await userEvent.type(element, '-10'); await userEvent.keyboard('{enter}'); @@ -76,7 +76,7 @@ describe('LokiQueryBuilderOptions', () => { await userEvent.click(screen.getByRole('button', { name: /Options/ })); // Second autosize input is a Line limit - const element = screen.getAllByTestId('autosize-input')[1]; + const element = screen.getAllByTestId('autosize-input')[2]; await userEvent.type(element, 'asd'); await userEvent.keyboard('{enter}'); @@ -154,6 +154,32 @@ describe('LokiQueryBuilderOptions', () => { }); }); +describe('getCollapsedInfo', () => { + it('displays a clipped legend URL for long URLs', () => { + const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate'; + setup({ legendUrlFormat: longUrl }); + + const urlText = screen.getByText((content, node) => { + const hasText = (node: any) => node.textContent === `URL: ${longUrl.slice(0, 10)}...`; + const nodeHasText = hasText(node); + const childrenDontHaveText = Array.from(node!.children).every( + (child) => !hasText(child) + ); + return nodeHasText && childrenDontHaveText; + }); + expect(urlText).toBeInTheDocument(); + }); + + it('displays the full legend URL for short URLs', () => { + const shortUrl = 'shortUrl'; + setup({ legendUrlFormat: shortUrl }); + + const urlTextElement = screen.getByText(`URL: ${shortUrl}`); + expect(urlTextElement).toBeInTheDocument(); + }); +}); + + function setup(queryOverrides: Partial = {}) { const props = { query: { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx index 4f9e1384980a..b11ab138c1b9 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -102,6 +102,25 @@ describe('PromQueryBuilderOptions', () => { }); }); +describe('getCollapsedInfo', () => { + it('displays a clipped legend URL for long URLs', async () => { + const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate'; + setup({ legendUrlFormat: longUrl }); + + const optionsElement = screen.getByTestId('data-testid prometheus options'); + expect(optionsElement.textContent).toContain(longUrl.slice(0, 10) + "..."); + }); + + it('displays the full legend URL for short URLs', async () => { + const shortUrl = 'shortUrl'; + setup({ legendUrlFormat: shortUrl }); + + const optionsElement = screen.getByTestId('data-testid prometheus options'); + expect(optionsElement.textContent).toContain(shortUrl); + }); +}); + + function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.PanelEditor) { const props = { app, @@ -112,6 +131,7 @@ function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.P expr: '', range: true, instant: false, + legendUrlFormat: '', } as PromQuery, CoreApp.PanelEditor ), @@ -128,7 +148,7 @@ function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.P resolution: true, }, }; - + const { container } = render(); return { container, props }; } From c87f46ba447f738aaa01bcf85916039172449008 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 8 Apr 2024 17:35:04 +0200 Subject: [PATCH 35/39] Solved issue after rebase --- pkg/promlib/models/query.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/promlib/models/query.go b/pkg/promlib/models/query.go index 065ec9448b34..89967eafb31a 100644 --- a/pkg/promlib/models/query.go +++ b/pkg/promlib/models/query.go @@ -63,6 +63,9 @@ type PrometheusQueryProperties struct { // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname LegendFormat string `json:"legendFormat,omitempty"` + // UrlFromDS adds the drilldown link to the legend labels. + LegendUrlFormat string `json:"legendUrlFormat,omitempty"` + // ??? Scope *ScopeSpec `json:"scope,omitempty"` } From a21c77c28a25e211934f7c3d06eea7bf00a3b3a5 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 15 Apr 2024 14:03:02 +0200 Subject: [PATCH 36/39] Refactored test to be more robust --- .../components/LokiQueryBuilderOptions.test.tsx | 6 +++--- .../components/PromQueryBuilderOptions.test.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index 8eaa4634ece8..6395efadc657 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -157,10 +157,10 @@ describe('LokiQueryBuilderOptions', () => { describe('getCollapsedInfo', () => { it('displays a clipped legend URL for long URLs', () => { const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate'; - setup({ legendUrlFormat: longUrl }); + setup({ legendUrl: longUrl }); const urlText = screen.getByText((content, node) => { - const hasText = (node: any) => node.textContent === `URL: ${longUrl.slice(0, 10)}...`; + const hasText = (node: any) => node.textContent === `URL: extremelyL...`; const nodeHasText = hasText(node); const childrenDontHaveText = Array.from(node!.children).every( (child) => !hasText(child) @@ -172,7 +172,7 @@ describe('getCollapsedInfo', () => { it('displays the full legend URL for short URLs', () => { const shortUrl = 'shortUrl'; - setup({ legendUrlFormat: shortUrl }); + setup({ legendUrl: shortUrl }); const urlTextElement = screen.getByText(`URL: ${shortUrl}`); expect(urlTextElement).toBeInTheDocument(); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx index b11ab138c1b9..9ac5539f765a 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.test.tsx @@ -105,15 +105,15 @@ describe('PromQueryBuilderOptions', () => { describe('getCollapsedInfo', () => { it('displays a clipped legend URL for long URLs', async () => { const longUrl = 'extremelyLongUrlThatShouldBeCutOffElseItTakesUpTooMuchOnScreenRealestate'; - setup({ legendUrlFormat: longUrl }); + setup({ legendUrl: longUrl }); const optionsElement = screen.getByTestId('data-testid prometheus options'); - expect(optionsElement.textContent).toContain(longUrl.slice(0, 10) + "..."); + expect(optionsElement.textContent).toContain("extremelyL..."); }); it('displays the full legend URL for short URLs', async () => { const shortUrl = 'shortUrl'; - setup({ legendUrlFormat: shortUrl }); + setup({ legendUrl: shortUrl }); const optionsElement = screen.getByTestId('data-testid prometheus options'); expect(optionsElement.textContent).toContain(shortUrl); @@ -131,7 +131,7 @@ function setup(queryOverrides: Partial = {}, app: CoreApp = CoreApp.P expr: '', range: true, instant: false, - legendUrlFormat: '', + legendUrl: '', } as PromQuery, CoreApp.PanelEditor ), From 7cd4ded8e2690630a0b87331a89263aa5fe77256 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 15 Apr 2024 14:03:40 +0200 Subject: [PATCH 37/39] Added comment to explain legendUrl --- packages/grafana-prometheus/src/dataquery.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/grafana-prometheus/src/dataquery.ts b/packages/grafana-prometheus/src/dataquery.ts index 4120b1862dcf..e5a54b8671a4 100644 --- a/packages/grafana-prometheus/src/dataquery.ts +++ b/packages/grafana-prometheus/src/dataquery.ts @@ -38,7 +38,10 @@ export interface Prometheus extends common.DataQuery { * Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname */ legendFormat?: string; - legendUrlFormat?: string; + /** + * Drilldown URL for LegendFormat with template support. Ex. https://example.com/{{instance}} will replace the variable with its value + */ + legendUrl?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ From 8567e716f38ddbff73eb4a7c34bdfa10c03b592a Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 15 Apr 2024 14:05:56 +0200 Subject: [PATCH 38/39] Renamed legendUrlFormat to legendUrl for clarity --- pkg/promlib/models/query.go | 50 +++++++++---------- pkg/promlib/querydata/response.go | 2 +- pkg/tsdb/loki/frame.go | 2 +- .../kinds/dataquery/types_dataquery_gen.go | 2 +- pkg/tsdb/loki/parse_query.go | 8 +-- pkg/tsdb/loki/types.go | 2 +- .../plugins/datasource/loki/dataquery.gen.ts | 2 +- .../datasource/prometheus/dataquery.ts | 4 +- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pkg/promlib/models/query.go b/pkg/promlib/models/query.go index 89967eafb31a..6d3441d180c9 100644 --- a/pkg/promlib/models/query.go +++ b/pkg/promlib/models/query.go @@ -63,8 +63,8 @@ type PrometheusQueryProperties struct { // Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname LegendFormat string `json:"legendFormat,omitempty"` - // UrlFromDS adds the drilldown link to the legend labels. - LegendUrlFormat string `json:"legendUrlFormat,omitempty"` + // Drilldown URL for LegendFormat with template support. Ex. https://example.com/{{instance}} will replace the variable with its value + LegendUrl string `json:"legendUrl,omitempty"` // ??? Scope *ScopeSpec `json:"scope,omitempty"` @@ -152,18 +152,18 @@ type TimeRange struct { // The internal query object type Query struct { - Expr string - Step time.Duration - LegendFormat string - LegendUrlFormat string - Start time.Time - End time.Time - RefId string - InstantQuery bool - RangeQuery bool - ExemplarQuery bool - UtcOffsetSec int64 - Scope *ScopeSpec + Expr string + Step time.Duration + LegendFormat string + LegendUrl string + Start time.Time + End time.Time + RefId string + InstantQuery bool + RangeQuery bool + ExemplarQuery bool + UtcOffsetSec int64 + Scope *ScopeSpec } type Scope struct { @@ -209,17 +209,17 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator } return &Query{ - Expr: expr, - Step: calculatedStep, - LegendFormat: model.LegendFormat, - LegendUrlFormat: model.LegendUrlFormat, - Start: query.TimeRange.From, - End: query.TimeRange.To, - RefId: query.RefID, - InstantQuery: model.Instant, - RangeQuery: model.Range, - ExemplarQuery: model.Exemplar, - UtcOffsetSec: model.UtcOffsetSec, + Expr: expr, + Step: calculatedStep, + LegendFormat: model.LegendFormat, + LegendUrl: model.LegendUrl, + Start: query.TimeRange.From, + End: query.TimeRange.To, + RefId: query.RefID, + InstantQuery: model.Instant, + RangeQuery: model.Range, + ExemplarQuery: model.Exemplar, + UtcOffsetSec: model.UtcOffsetSec, }, nil } diff --git a/pkg/promlib/querydata/response.go b/pkg/promlib/querydata/response.go index 45d3f1a35828..a7d0d262bc9d 100644 --- a/pkg/promlib/querydata/response.go +++ b/pkg/promlib/querydata/response.go @@ -183,7 +183,7 @@ func getName(q *models.Query, field *data.Field) string { } func getUrl(q *models.Query, field *data.Field) string { - return convertLabel(q.LegendUrlFormat, field) + return convertLabel(q.LegendUrl, field) } func convertLabel(label string, field *data.Field) string { diff --git a/pkg/tsdb/loki/frame.go b/pkg/tsdb/loki/frame.go index e895ef35e791..6eb49c75c4d4 100644 --- a/pkg/tsdb/loki/frame.go +++ b/pkg/tsdb/loki/frame.go @@ -289,7 +289,7 @@ func formatName(labels map[string]string, query *lokiQuery) string { } func formatUrl(labels map[string]string, query *lokiQuery) string { - return convertLabel(query.LegendUrlFormat, labels) + return convertLabel(query.LegendUrl, labels) } func convertLabel(label string, labels map[string]string) string { diff --git a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go index b950a29fe65c..07cfb132020c 100644 --- a/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/loki/kinds/dataquery/types_dataquery_gen.go @@ -81,7 +81,7 @@ type LokiDataQuery struct { LegendFormat *string `json:"legendFormat,omitempty"` // Used for the drilldown label url - LegendUrlFormat *string `json:"legendUrlFormat,omitempty"` + LegendUrl *string `json:"legendUrl,omitempty"` // Used to limit the number of log rows returned. MaxLines *int64 `json:"maxLines,omitempty"` diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go index 1de4437c8a8f..5f8c9aaedae6 100644 --- a/pkg/tsdb/loki/parse_query.go +++ b/pkg/tsdb/loki/parse_query.go @@ -168,9 +168,9 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { if model.LegendFormat != nil { legendFormat = *model.LegendFormat } - var legendUrlFormat string - if model.LegendUrlFormat != nil { - legendUrlFormat = *model.LegendUrlFormat + var legendUrl string + if model.LegendUrl != nil { + legendUrl = *model.LegendUrl } supportingQueryType := parseSupportingQueryType(model.SupportingQueryType) @@ -182,7 +182,7 @@ func parseQuery(queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { Step: step, MaxLines: int(maxLines), LegendFormat: legendFormat, - LegendUrlFormat: legendUrlFormat, + LegendUrl: legendUrl, Start: start, End: end, RefID: query.RefID, diff --git a/pkg/tsdb/loki/types.go b/pkg/tsdb/loki/types.go index 1fe9196dcb58..aeb201fdb8a0 100644 --- a/pkg/tsdb/loki/types.go +++ b/pkg/tsdb/loki/types.go @@ -35,7 +35,7 @@ type lokiQuery struct { Step time.Duration MaxLines int LegendFormat string - LegendUrlFormat string + LegendUrl string Start time.Time End time.Time RefID string diff --git a/public/app/plugins/datasource/loki/dataquery.gen.ts b/public/app/plugins/datasource/loki/dataquery.gen.ts index f995713f1ddf..9eae73ee664e 100644 --- a/public/app/plugins/datasource/loki/dataquery.gen.ts +++ b/public/app/plugins/datasource/loki/dataquery.gen.ts @@ -50,7 +50,7 @@ export interface LokiDataQuery extends common.DataQuery { /** * Used for drilldown/clickable legend labels. */ - legendUrlFormat?: string; + legendUrl?: string; /** * Used to limit the number of log rows returned. */ diff --git a/public/app/plugins/datasource/prometheus/dataquery.ts b/public/app/plugins/datasource/prometheus/dataquery.ts index 0646d7bb608a..2ff64c2c864d 100644 --- a/public/app/plugins/datasource/prometheus/dataquery.ts +++ b/public/app/plugins/datasource/prometheus/dataquery.ts @@ -39,9 +39,9 @@ export interface Prometheus extends common.DataQuery { */ legendFormat?: string; /** - * Drilldown URL similar to Series name. Ex. {{hostname}} will be replaced with label value for hostname + * Drilldown URL for LegendFormat with template support. Ex. https://example.com/{{instance}} will replace the variable with its value */ - legendUrlFormat?: string; + legendUrl?: string; /** * Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series */ From a7b905698049c32b5fe589f55eb3601d979a97b1 Mon Sep 17 00:00:00 2001 From: Eduard Date: Mon, 15 Apr 2024 14:08:17 +0200 Subject: [PATCH 39/39] Refactored the tooltip & placeholders, continued rename to legendUrl --- .../components/LokiQueryBuilderOptions.tsx | 22 ++++++++--------- .../components/PromQueryBuilderOptions.tsx | 18 +++++++------- .../components/PromQueryLegendUrlEditor.tsx | 24 ++++++++----------- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index f434ba97da08..b5aee721d7c9 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -52,8 +52,8 @@ export const LokiQueryBuilderOptions = React.memo( onChange({ ...query, legendFormat: evt.currentTarget.value }); onRunQuery(); }; - const onLegendUrlFormatChanged = (evt: React.FormEvent) => { - onChange({ ...query, legendUrlFormat: evt.currentTarget.value }); + const onLegendUrlChanged = (evt: React.FormEvent) => { + onChange({ ...query, legendUrl: evt.currentTarget.value }); onRunQuery(); }; @@ -101,14 +101,14 @@ export const LokiQueryBuilderOptions = React.memo( + tooltip="If the url is set, the label in the legend will become clickable, you can use variables similar to Legend Label." + > @@ -201,14 +201,14 @@ function getCollapsedInfo( items.push(`Legend: ${query.legendFormat}`); } - if (query.legendUrlFormat) { - let legendUrl = query.legendUrlFormat; - + if (query.legendUrl) { + let legendUrl = query.legendUrl; + if (typeof legendUrl === 'string' && legendUrl.length > 10) { legendUrl = legendUrl.slice(0, 10) + "..."; } - items.push(`URL: ${legendUrl}`); + items.push(`URL: ${legendUrl?.length === 0 ? "None" : legendUrl}`); } items.push(`Type: ${queryTypeLabel?.label}`); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx index b4e308e81e05..08f32158fb52 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderOptions.tsx @@ -12,7 +12,7 @@ import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; -import { getLegendUrlModeLabel, PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; +import { PromQueryLegendUrlEditor } from './PromQueryLegendUrlEditor'; export interface UIOptions { @@ -75,12 +75,11 @@ export const PromQueryBuilderOptions = React.memo(({ query, app, onChange onChange={(legendFormat) => onChange({ ...query, legendFormat })} onRunQuery={onRunQuery} /> - onChange({ ...query, legendUrlFormat })} - onRunQuery={onRunQuery} - /> - + onChange({ ...query, legendUrl })} + onRunQuery={onRunQuery} + /> 10) { legendUrl = legendUrl.slice(0, 10) + "..."; } items.push(`Legend: ${getLegendModeLabel(query.legendFormat)}`); - items.push(`URL: ${legendUrl}`); + console.log('"' + legendUrl + '"') + items.push(`URL: ${legendUrl?.length === 0 ? "None" : legendUrl}`); items.push(`Format: ${formatOption}`); items.push(`Step: ${query.interval ?? 'auto'}`); items.push(`Type: ${queryType}`); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx index cbb36a4f01fb..3347dcb6ce6c 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendUrlEditor.tsx @@ -5,14 +5,14 @@ import { EditorField } from '@grafana/experimental'; import { AutoSizeInput } from '@grafana/ui'; export interface Props { - legendUrlFormat: string | undefined; - onChange: (legendUrlFormat: string) => void; + legendUrl: string | undefined; + onChange: (legendUrl: string) => void; onRunQuery: () => void; } -export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, onChange, onRunQuery }) => { +export const PromQueryLegendUrlEditor = React.memo(({ legendUrl, onChange, onRunQuery }) => { const inputRef = useRef(null); - const onLegendUrlFormatChanged = (evt: React.FormEvent) => { + const onLegendUrlChanged = (evt: React.FormEvent) => { onChange(evt.currentTarget.value); onRunQuery(); } @@ -20,23 +20,19 @@ export const PromQueryLegendUrlEditor = React.memo(({ legendUrlFormat, on return ( ); }); -PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; - -export function getLegendUrlModeLabel(legendUrlFormat: string | undefined) { - return legendUrlFormat; -} +PromQueryLegendUrlEditor.displayName = 'PromQueryLegendUrlEditor'; \ No newline at end of file