Data Links: Allow passing data links post processor via context (#110590)

* Data Links: Allow passing data links post processor via context

* Deprecate data links post processor in PanelContext

* Mark new context as internal
This commit is contained in:
Piotr Jamróz
2025-09-10 22:12:24 +02:00
committed by GitHub
parent 73a241da86
commit 8dd72408b2
11 changed files with 64 additions and 66 deletions
@@ -1,5 +1,5 @@
import { isNumber, set, unset, get, cloneDeep } from 'lodash';
import { useMemo, useRef } from 'react';
import { createContext, useContext, useMemo, useRef } from 'react';
import { usePrevious } from 'react-use';
import { ThresholdsMode, VariableFormatID } from '@grafana/schema';
@@ -583,13 +583,14 @@ export function useFieldOverrides(
data: PanelData | undefined,
timeZone: string,
theme: GrafanaTheme2,
replace: InterpolateFunction,
dataLinkPostProcessor?: DataLinkPostProcessor
replace: InterpolateFunction
): PanelData | undefined {
const fieldConfigRegistry = plugin?.fieldConfigRegistry;
const structureRev = useRef(0);
const prevSeries = usePrevious(data?.series);
const { dataLinkPostProcessor } = useDataLinksContext();
return useMemo(() => {
if (!fieldConfigRegistry || !fieldConfig || !data) {
return;
@@ -650,3 +651,15 @@ export function getFieldDataContextClone(frame: DataFrame, field: Field, fieldSc
return { value: { frame, field, data: [frame] } };
}
/**
* @internal
*/
export const DataLinksContext = createContext<{
dataLinkPostProcessor: DataLinkPostProcessor;
}>({ dataLinkPostProcessor: defaultInternalLinkPostProcessor });
/**
* @internal
*/
export const useDataLinksContext = () => useContext(DataLinksContext);
+2
View File
@@ -150,6 +150,8 @@ export {
applyRawFieldOverrides,
useFieldOverrides,
getFieldDataContextClone,
DataLinksContext,
useDataLinksContext,
} from './field/fieldOverrides';
export { getFieldDisplayValuesProxy } from './field/getFieldDisplayValuesProxy';
export {
@@ -96,6 +96,7 @@ export interface PanelContext {
/**
* Optional supplier for internal data links. If not provided a link pointing to Explore will be generated.
* @internal
* @deprecated Please use DataLinksContext instead. This property will be removed in next major.
*/
dataLinkPostProcessor?: DataLinkPostProcessor;
}
@@ -1,6 +1,6 @@
import { DataFrame, EventBus, LoadingState, SplitOpen, TimeRange } from '@grafana/data';
import { DataFrame, DataLinksContext, EventBus, LoadingState, SplitOpen, TimeRange } from '@grafana/data';
import { PanelRenderer } from '@grafana/runtime';
import { PanelChrome, PanelContext, PanelContextProvider } from '@grafana/ui';
import { PanelChrome } from '@grafana/ui';
import { getPanelPluginMeta } from '../plugins/importPanelPlugin';
@@ -18,29 +18,13 @@ export interface Props {
eventBus: EventBus;
}
export function CustomContainer({
width,
height,
timeZone,
state,
pluginId,
frames,
timeRange,
splitOpenFn,
eventBus,
}: Props) {
export function CustomContainer({ width, height, timeZone, state, pluginId, frames, timeRange, splitOpenFn }: Props) {
const plugin = getPanelPluginMeta(pluginId);
const dataLinkPostProcessor = useExploreDataLinkPostProcessor(splitOpenFn, timeRange);
const panelContext: PanelContext = {
dataLinkPostProcessor,
eventBus,
eventsScope: 'explore',
};
return (
<PanelContextProvider value={panelContext}>
<DataLinksContext.Provider value={{ dataLinkPostProcessor }}>
<PanelChrome title={plugin.name} width={width} height={height} loadingState={state}>
{(innerWidth, innerHeight) => (
<PanelRenderer
@@ -53,6 +37,6 @@ export function CustomContainer({
/>
)}
</PanelChrome>
</PanelContextProvider>
</DataLinksContext.Provider>
);
}
@@ -8,6 +8,7 @@ import {
createFieldConfigRegistry,
DashboardCursorSync,
DataFrame,
DataLinksContext,
EventBus,
FieldColorModeId,
FieldConfigSource,
@@ -121,9 +122,8 @@ export function ExploreGraph({
replaceVariables: (value) => value, // We don't need proper replace here as it is only used in getLinks and we use getFieldLinks
theme,
fieldConfigRegistry,
dataLinkPostProcessor,
});
}, [fieldConfigRegistry, data, timeZone, theme, styledFieldConfig, dataLinkPostProcessor]);
}, [fieldConfigRegistry, data, timeZone, theme, styledFieldConfig]);
const annotationsWithConfig = useMemo(() => {
return applyFieldOverrides({
@@ -171,7 +171,6 @@ export function ExploreGraph({
onToggleSeriesVisibility(label: string, mode: SeriesVisibilityChangeMode) {
setFieldConfig(seriesVisibilityConfigFactory(label, mode, fieldConfig, data));
},
dataLinkPostProcessor,
};
function toggleLegend(name: string | undefined, mode: SeriesVisibilityChangeMode) {
@@ -204,23 +203,25 @@ export function ExploreGraph({
);
return (
<PanelContextProvider value={panelContext}>
<PanelRenderer
data={{
series: dataWithConfig,
timeRange,
state: loadingState,
annotations: annotationsWithConfig,
structureRev,
}}
pluginId="timeseries"
title=""
width={width}
height={height}
onChangeTimeRange={onChangeTime}
timeZone={timeZone}
options={panelOptions}
/>
</PanelContextProvider>
<DataLinksContext.Provider value={{ dataLinkPostProcessor }}>
<PanelContextProvider value={panelContext}>
<PanelRenderer
data={{
series: dataWithConfig,
timeRange,
state: loadingState,
annotations: annotationsWithConfig,
structureRev,
}}
pluginId="timeseries"
title=""
width={width}
height={height}
onChangeTimeRange={onChangeTime}
timeZone={timeZone}
options={panelOptions}
/>
</PanelContextProvider>
</DataLinksContext.Provider>
);
}
@@ -11,7 +11,7 @@ import {
} from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { getTemplateSrv, PanelRendererProps } from '@grafana/runtime';
import { ErrorBoundaryAlert, usePanelContext, useTheme2 } from '@grafana/ui';
import { ErrorBoundaryAlert, useTheme2 } from '@grafana/ui';
import { appEvents } from 'app/core/core';
import { importPanelPlugin, syncGetPanelPlugin } from '../../plugins/importPanelPlugin';
@@ -39,16 +39,7 @@ export function PanelRenderer<P extends object = {}, F extends object = {}>(prop
const [plugin, setPlugin] = useState(syncGetPanelPlugin(pluginId));
const [error, setError] = useState<string | undefined>();
const optionsWithDefaults = useOptionDefaults(plugin, options, fieldConfig);
const { dataLinkPostProcessor } = usePanelContext();
const dataWithOverrides = useFieldOverrides(
plugin,
optionsWithDefaults?.fieldConfig,
data,
timeZone,
theme,
replace,
dataLinkPostProcessor
);
const dataWithOverrides = useFieldOverrides(plugin, optionsWithDefaults?.fieldConfig, data, timeZone, theme, replace);
useEffect(() => {
// If we already have a plugin and it's correct one do nothing
@@ -4,7 +4,7 @@
import { useMemo, useState } from 'react';
import uPlot from 'uplot';
import { Field, getDisplayProcessor, PanelProps } from '@grafana/data';
import { Field, getDisplayProcessor, PanelProps, useDataLinksContext } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import { DashboardCursorSync, TooltipDisplayMode } from '@grafana/schema';
import {
@@ -50,11 +50,12 @@ export const CandlestickPanel = ({
onThresholdsChange,
canEditThresholds,
showThresholds,
dataLinkPostProcessor,
eventBus,
canExecuteActions,
} = usePanelContext();
const { dataLinkPostProcessor } = useDataLinksContext();
const userCanExecuteActions = useMemo(() => canExecuteActions?.() ?? false, [canExecuteActions]);
const theme = useTheme2();
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react';
import { DashboardCursorSync, PanelProps } from '@grafana/data';
import { DashboardCursorSync, PanelProps, useDataLinksContext } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import {
AxisPlacement,
@@ -47,8 +47,9 @@ export const StateTimelinePanel = ({
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const { sync, eventsScope, canAddAnnotations, dataLinkPostProcessor, eventBus, canExecuteActions } =
usePanelContext();
const { sync, eventsScope, canAddAnnotations, eventBus, canExecuteActions } = usePanelContext();
const { dataLinkPostProcessor } = useDataLinksContext();
const userCanExecuteActions = useMemo(() => canExecuteActions?.() ?? false, [canExecuteActions]);
const cursorSync = sync?.() ?? DashboardCursorSync.Off;
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react';
import { DashboardCursorSync, PanelProps } from '@grafana/data';
import { DashboardCursorSync, PanelProps, useDataLinksContext } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { PanelDataErrorView } from '@grafana/runtime';
import {
@@ -48,7 +48,8 @@ export const StatusHistoryPanel = ({
// temp range set for adding new annotation set by TooltipPlugin2, consumed by AnnotationPlugin2
const [newAnnotationRange, setNewAnnotationRange] = useState<TimeRange2 | null>(null);
const { sync, eventsScope, canAddAnnotations, dataLinkPostProcessor, eventBus } = usePanelContext();
const { sync, eventsScope, canAddAnnotations, eventBus } = usePanelContext();
const { dataLinkPostProcessor } = useDataLinksContext();
const cursorSync = sync?.() ?? DashboardCursorSync.Off;
const enableAnnotationCreation = Boolean(canAddAnnotations && canAddAnnotations());
@@ -7,6 +7,7 @@ import {
DataFrame,
alignTimeRangeCompareData,
shouldAlignTimeCompare,
useDataLinksContext,
FieldType,
} from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
@@ -46,11 +47,12 @@ export const TimeSeriesPanel = ({
onThresholdsChange,
canEditThresholds,
showThresholds,
dataLinkPostProcessor,
eventBus,
canExecuteActions,
} = usePanelContext();
const { dataLinkPostProcessor } = useDataLinksContext();
const userCanExecuteActions = useMemo(() => canExecuteActions?.() ?? false, [canExecuteActions]);
// Vertical orientation is not available for users through config.
// It is simplified version of horizontal time series panel and it does not support all plugins.
@@ -8,9 +8,10 @@ import {
FieldType,
PanelProps,
TimeRange,
useDataLinksContext,
} from '@grafana/data';
import { config, PanelDataErrorView } from '@grafana/runtime';
import { KeyboardPlugin, TooltipDisplayMode, usePanelContext, TooltipPlugin2 } from '@grafana/ui';
import { KeyboardPlugin, TooltipDisplayMode, TooltipPlugin2 } from '@grafana/ui';
import { TooltipHoverMode } from '@grafana/ui/internal';
import { XYFieldMatchers } from 'app/core/components/GraphNG/types';
import { preparePlotFrame } from 'app/core/components/GraphNG/utils';
@@ -33,7 +34,7 @@ export const TrendPanel = ({
replaceVariables,
id,
}: PanelProps<Options>) => {
const { dataLinkPostProcessor } = usePanelContext();
const { dataLinkPostProcessor } = useDataLinksContext();
// Need to fallback to first number field if no xField is set in options otherwise panel crashes 😬
const trendXFieldName =
options.xField ?? data.series[0]?.fields.find((field) => field.type === FieldType.number)?.name;