TimeSeries: Fix showing datalinks when clicking on a point (#76492)

This commit is contained in:
Leon Sorokin 2023-10-13 05:48:29 -05:00 committed by GitHub
parent 1573f253bf
commit 2857870bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 107 deletions

View File

@ -1,4 +1,4 @@
import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useClickAway } from 'react-use'; import { useClickAway } from 'react-use';
import { CartesianCoords2D, DataFrame, getFieldDisplayName, InterpolateFunction, TimeZone } from '@grafana/data'; import { CartesianCoords2D, DataFrame, getFieldDisplayName, InterpolateFunction, TimeZone } from '@grafana/data';
@ -11,7 +11,6 @@ import {
MenuItem, MenuItem,
UPlotConfigBuilder, UPlotConfigBuilder,
} from '@grafana/ui'; } from '@grafana/ui';
import { pluginLog } from '@grafana/ui/src/components/uPlot/utils';
type ContextMenuSelectionCoords = { viewport: CartesianCoords2D; plotCanvas: CartesianCoords2D }; type ContextMenuSelectionCoords = { viewport: CartesianCoords2D; plotCanvas: CartesianCoords2D };
type ContextMenuSelectionPoint = { seriesIdx: number | null; dataIdx: number | null }; type ContextMenuSelectionPoint = { seriesIdx: number | null; dataIdx: number | null };
@ -39,108 +38,37 @@ export const ContextMenuPlugin = ({
replaceVariables, replaceVariables,
...otherProps ...otherProps
}: ContextMenuPluginProps) => { }: ContextMenuPluginProps) => {
const plotCanvas = useRef<HTMLDivElement>();
const [coords, setCoords] = useState<ContextMenuSelectionCoords | null>(null); const [coords, setCoords] = useState<ContextMenuSelectionCoords | null>(null);
const [point, setPoint] = useState<ContextMenuSelectionPoint | null>(null); const [point, setPoint] = useState<ContextMenuSelectionPoint | null>(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const openMenu = useCallback(() => {
setIsOpen(true);
}, [setIsOpen]);
const closeMenu = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);
const clearSelection = useCallback(() => {
pluginLog('ContextMenuPlugin', false, 'clearing click selection');
setPoint(null);
}, [setPoint]);
// Add uPlot hooks to the config, or re-add when the config changed
useLayoutEffect(() => { useLayoutEffect(() => {
let bbox: DOMRect | undefined = undefined; let seriesIdx: number | null = null;
const onMouseCapture = (e: MouseEvent) => {
let update = {
viewport: {
x: e.clientX,
y: e.clientY,
},
plotCanvas: {
x: 0,
y: 0,
},
};
if (bbox) {
update = {
...update,
plotCanvas: {
x: e.clientX - bbox.left,
y: e.clientY - bbox.top,
},
};
}
setCoords(update);
};
// cache uPlot plotting area bounding box
config.addHook('syncRect', (u, rect) => {
bbox = rect;
});
config.addHook('init', (u) => { config.addHook('init', (u) => {
const canvas = u.over; u.over.addEventListener('click', (e) => {
plotCanvas.current = canvas || undefined; // only open when have a focused point, and not for explicit annotations, zooms, etc.
plotCanvas.current?.addEventListener('mousedown', onMouseCapture); if (seriesIdx != null && !e.metaKey && !e.ctrlKey && !e.shiftKey) {
setCoords({
pluginLog('ContextMenuPlugin', false, 'init'); viewport: {
// for naive click&drag check x: e.clientX,
let isClick = false; y: e.clientY,
},
// REF: https://github.com/leeoniya/uPlot/issues/239 plotCanvas: {
let pts = Array.from(u.root.querySelectorAll<HTMLDivElement>('.u-cursor-pt')); x: e.clientX - u.rect.left,
y: e.clientY - u.rect.top,
plotCanvas.current?.addEventListener('mousedown', () => { },
isClick = true;
});
plotCanvas.current?.addEventListener('mousemove', () => {
isClick = false;
});
// TODO: remove listeners on unmount
plotCanvas.current?.addEventListener('mouseup', (e: MouseEvent) => {
// ignore cmd+click, this is handled by annotation editor
if (!isClick || e.metaKey || e.ctrlKey) {
setPoint(null);
return;
}
isClick = true;
if (e.target instanceof HTMLElement) {
if (!e.target.classList.contains('u-cursor-pt')) {
pluginLog('ContextMenuPlugin', false, 'canvas click');
setPoint({ seriesIdx: null, dataIdx: null });
}
}
openMenu();
});
if (pts.length > 0) {
pts.forEach((pt, i) => {
// TODO: remove listeners on unmount
pt.addEventListener('click', () => {
const seriesIdx = i + 1;
const dataIdx = u.cursor.idx;
pluginLog('ContextMenuPlugin', false, seriesIdx, dataIdx);
setPoint({ seriesIdx, dataIdx: dataIdx ?? null });
}); });
}); setPoint({ seriesIdx, dataIdx: u.cursor.idxs![seriesIdx] });
} setIsOpen(true);
}
});
}); });
}, [config, openMenu, setCoords, setPoint]);
config.addHook('setSeries', (u, _seriesIdx) => {
seriesIdx = _seriesIdx;
});
}, [config]);
const defaultItems = useMemo(() => { const defaultItems = useMemo(() => {
return otherProps.defaultItems return otherProps.defaultItems
@ -175,8 +103,8 @@ export const ContextMenuPlugin = ({
selection={{ point, coords }} selection={{ point, coords }}
replaceVariables={replaceVariables} replaceVariables={replaceVariables}
onClose={() => { onClose={() => {
clearSelection(); setPoint(null);
closeMenu(); setIsOpen(false);
if (onClose) { if (onClose) {
onClose(); onClose();
} }

View File

@ -14,7 +14,6 @@ import {
import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types'; import { XYFieldMatchers } from '@grafana/ui/src/components/GraphNG/types';
import { findFieldIndex } from 'app/features/dimensions'; import { findFieldIndex } from 'app/features/dimensions';
import { ContextMenuPlugin } from '../timeseries/plugins/ContextMenuPlugin';
import { prepareGraphableFields, regenerateLinksSupplier } from '../timeseries/utils'; import { prepareGraphableFields, regenerateLinksSupplier } from '../timeseries/utils';
import { Options } from './panelcfg.gen'; import { Options } from './panelcfg.gen';
@ -137,15 +136,6 @@ export const TrendPanel = ({
timeZone={timeZone} timeZone={timeZone}
/> />
)} )}
<ContextMenuPlugin
data={alignedDataFrame}
frames={info.frames!}
config={config}
timeZone={timeZone}
replaceVariables={replaceVariables}
defaultItems={[]}
/>
</> </>
); );
}} }}