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