Tooltips: Generate data links in TooltipPlugin2 (#97818)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
pull/98136/head
Adela Almasan 6 months ago committed by GitHub
parent 8d4e5a4e09
commit 03d176fae4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx
  2. 30
      packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx
  3. 6
      public/app/plugins/panel/barchart/BarChartPanel.tsx
  4. 6
      public/app/plugins/panel/candlestick/CandlestickPanel.tsx
  5. 6
      public/app/plugins/panel/state-timeline/StateTimelinePanel.tsx
  6. 6
      public/app/plugins/panel/state-timeline/StateTimelineTooltip2.tsx
  7. 6
      public/app/plugins/panel/status-history/StatusHistoryPanel.tsx
  8. 6
      public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx
  9. 9
      public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx
  10. 6
      public/app/plugins/panel/trend/TrendPanel.tsx

@ -40,7 +40,7 @@ export const VizTooltipFooter = ({ dataLinks, actions, annotate }: VizTooltipFoo
return (
<div className={styles.wrapper}>
{dataLinks.length > 0 && <div className={styles.dataLinks}>{renderDataLinks(dataLinks, styles)}</div>}
{dataLinks?.length > 0 && <div className={styles.dataLinks}>{renderDataLinks(dataLinks, styles)}</div>}
{actions && actions.length > 0 && <div className={styles.dataLinks}>{renderActions(actions)}</div>}
{annotate != null && (
<div className={styles.addAnnotations}>

@ -4,7 +4,7 @@ import * as React from 'react';
import { createPortal } from 'react-dom';
import uPlot from 'uplot';
import { GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2, LinkModel } from '@grafana/data';
import { DashboardCursorSync } from '@grafana/schema';
import { useStyles2 } from '../../../themes';
@ -27,6 +27,8 @@ export const enum TooltipHoverMode {
xyOne,
}
type GeDataLinksCallback = (seriesIdx: number, dataIdx: number) => LinkModel[];
interface TooltipPlugin2Props {
config: UPlotConfigBuilder;
hoverMode: TooltipHoverMode;
@ -40,6 +42,7 @@ interface TooltipPlugin2Props {
clientZoom?: boolean;
onSelectRange?: OnSelectRangeCallback;
getDataLinks?: GeDataLinksCallback;
render: (
u: uPlot,
@ -49,7 +52,8 @@ interface TooltipPlugin2Props {
dismiss: () => void,
// selected time range (for annotation triggering)
timeRange: TimeRange2 | null,
viaSync: boolean
viaSync: boolean,
dataLinks: LinkModel[]
) => React.ReactNode;
maxWidth?: number;
@ -102,6 +106,8 @@ const MIN_ZOOM_DIST = 5;
const maybeZoomAction = (e?: MouseEvent | null) => e != null && !e.ctrlKey && !e.metaKey;
const getDataLinksFallback: GeDataLinksCallback = () => [];
/**
* @alpha
*/
@ -115,6 +121,7 @@ export const TooltipPlugin2 = ({
maxWidth,
syncMode = DashboardCursorSync.Off,
syncScope = 'global', // eventsScope
getDataLinks = getDataLinksFallback,
}: TooltipPlugin2Props) => {
const domRef = useRef<HTMLDivElement>(null);
const portalRoot = useRef<HTMLElement | null>(null);
@ -131,6 +138,9 @@ export const TooltipPlugin2 = ({
const renderRef = useRef(render);
renderRef.current = render;
const getLinksRef = useRef(getDataLinks);
getLinksRef.current = getDataLinks;
useLayoutEffect(() => {
sizeRef.current = {
width: 0,
@ -187,6 +197,7 @@ export const TooltipPlugin2 = ({
let seriesIdxs: Array<number | null> = plot?.cursor.idxs!.slice()!;
let closestSeriesIdx: number | null = null;
let viaSync = false;
let dataLinks: LinkModel[] = [];
let pendingRender = false;
let pendingPinned = false;
@ -242,7 +253,16 @@ export const TooltipPlugin2 = ({
isHovering: _isHovering,
contents:
_isHovering || selectedRange != null
? renderRef.current(_plot!, seriesIdxs, closestSeriesIdx, _isPinned, dismiss, selectedRange, viaSync)
? renderRef.current(
_plot!,
seriesIdxs,
closestSeriesIdx,
_isPinned,
dismiss,
selectedRange,
viaSync,
dataLinks
)
: null,
dismiss,
};
@ -257,6 +277,8 @@ export const TooltipPlugin2 = ({
_isPinned = false;
_isHovering = false;
_plot!.setCursor({ left: -10, top: -10 });
dataLinks = [];
scheduleRender(prevIsPinned);
};
@ -313,6 +335,8 @@ export const TooltipPlugin2 = ({
}
// only pinnable tooltip is visible *and* is within proximity to series/point
else if (_isHovering && closestSeriesIdx != null && !_isPinned) {
dataLinks = getLinksRef.current(closestSeriesIdx!, seriesIdxs[closestSeriesIdx!]!);
setTimeout(() => {
_isPinned = true;
scheduleRender(true);

@ -157,7 +157,10 @@ export const BarChartPanel = (props: PanelProps<Options>) => {
hoverMode={
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2) => {
getDataLinks={(seriesIdx: number, dataIdx: number) =>
vizSeries[0].fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => {
return (
<TimeSeriesTooltip
series={vizSeries[0]}
@ -169,6 +172,7 @@ export const BarChartPanel = (props: PanelProps<Options>) => {
isPinned={isPinned}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}

@ -282,7 +282,10 @@ export const CandlestickPanel = ({
clientZoom={true}
syncMode={cursorSync}
syncScope={eventsScope}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync) => {
getDataLinks={(seriesIdx: number, dataIdx: number) =>
alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync, dataLinks) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
@ -307,6 +310,7 @@ export const CandlestickPanel = ({
annotate={enableAnnotationCreation ? annotate : undefined}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}

@ -172,7 +172,10 @@ export const StateTimelinePanel = ({
queryZoom={onChangeTimeRange}
syncMode={cursorSync}
syncScope={eventsScope}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync) => {
getDataLinks={(seriesIdx: number, dataIdx: number) =>
alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
@ -199,6 +202,7 @@ export const StateTimelinePanel = ({
withDuration={true}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}

@ -11,7 +11,7 @@ import { VizTooltipItem } from '@grafana/ui/src/components/VizTooltip/types';
import { getContentItems } from '@grafana/ui/src/components/VizTooltip/utils';
import { findNextStateIndex, fmtDuration } from 'app/core/components/TimelineChart/utils';
import { getDataLinks, getFieldActions } from '../status-history/utils';
import { getFieldActions } from '../status-history/utils';
import { TimeSeriesTooltipProps } from '../timeseries/TimeSeriesTooltip';
import { isTooltipScrollable } from '../timeseries/utils';
@ -32,6 +32,7 @@ export const StateTimelineTooltip2 = ({
withDuration,
maxHeight,
replaceVariables,
dataLinks,
}: StateTimelineTooltip2Props) => {
const xField = series.fields[0];
@ -70,10 +71,9 @@ export const StateTimelineTooltip2 = ({
if (isPinned && seriesIdx != null) {
const field = series.fields[seriesIdx];
const dataIdx = dataIdxs[seriesIdx]!;
const links = getDataLinks(field, dataIdx);
const actions = getFieldActions(series, field, replaceVariables!, dataIdx);
footer = <VizTooltipFooter dataLinks={links} annotate={annotate} actions={actions} />;
footer = <VizTooltipFooter dataLinks={dataLinks} annotate={annotate} actions={actions} />;
}
const headerItem: VizTooltipItem = {

@ -104,7 +104,10 @@ export const StatusHistoryPanel = ({
queryZoom={onChangeTimeRange}
syncMode={cursorSync}
syncScope={eventsScope}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync) => {
getDataLinks={(seriesIdx: number, dataIdx: number) =>
alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned, dismiss, timeRange2, viaSync, dataLinks) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
@ -131,6 +134,7 @@ export const StatusHistoryPanel = ({
withDuration={false}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}

@ -106,7 +106,10 @@ export const TimeSeriesPanel = ({
clientZoom={true}
syncMode={cursorSync}
syncScope={eventsScope}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync) => {
getDataLinks={(seriesIdx: number, dataIdx: number) =>
alignedFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange2, viaSync, dataLinks) => {
if (enableAnnotationCreation && timeRange2 != null) {
setNewAnnotationRange(timeRange2);
dismiss();
@ -132,6 +135,7 @@ export const TimeSeriesPanel = ({
annotate={enableAnnotationCreation ? annotate : undefined}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}

@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import { DataFrame, Field, FieldType, formattedValueToString, InterpolateFunction } from '@grafana/data';
import { DataFrame, Field, FieldType, formattedValueToString, InterpolateFunction, LinkModel } from '@grafana/data';
import { SortOrder, TooltipDisplayMode } from '@grafana/schema/dist/esm/common/common.gen';
import { VizTooltipContent } from '@grafana/ui/src/components/VizTooltip/VizTooltipContent';
import { VizTooltipFooter } from '@grafana/ui/src/components/VizTooltip/VizTooltipFooter';
@ -9,7 +9,7 @@ import { VizTooltipWrapper } from '@grafana/ui/src/components/VizTooltip/VizTool
import { VizTooltipItem } from '@grafana/ui/src/components/VizTooltip/types';
import { getContentItems } from '@grafana/ui/src/components/VizTooltip/utils';
import { getDataLinks, getFieldActions } from '../status-history/utils';
import { getFieldActions } from '../status-history/utils';
import { fmt } from '../xychart/utils';
import { isTooltipScrollable } from './utils';
@ -37,6 +37,7 @@ export interface TimeSeriesTooltipProps {
maxHeight?: number;
replaceVariables?: InterpolateFunction;
dataLinks: LinkModel[];
}
export const TimeSeriesTooltip = ({
@ -50,6 +51,7 @@ export const TimeSeriesTooltip = ({
annotate,
maxHeight,
replaceVariables,
dataLinks,
}: TimeSeriesTooltipProps) => {
const xField = series.fields[0];
const xVal = formattedValueToString(xField.display!(xField.values[dataIdxs[0]!]));
@ -78,10 +80,9 @@ export const TimeSeriesTooltip = ({
if (isPinned && seriesIdx != null) {
const field = series.fields[seriesIdx];
const dataIdx = dataIdxs[seriesIdx]!;
const links = getDataLinks(field, dataIdx);
const actions = getFieldActions(series, field, replaceVariables!, dataIdx);
footer = <VizTooltipFooter dataLinks={links} actions={actions} annotate={annotate} />;
footer = <VizTooltipFooter dataLinks={dataLinks} actions={actions} annotate={annotate} />;
}
const headerItem: VizTooltipItem | null = xField.config.custom?.hideFrom?.tooltip

@ -119,7 +119,10 @@ export const TrendPanel = ({
hoverMode={
options.tooltip.mode === TooltipDisplayMode.Single ? TooltipHoverMode.xOne : TooltipHoverMode.xAll
}
render={(u, dataIdxs, seriesIdx, isPinned = false) => {
getDataLinks={(seriesIdx: number, dataIdx: number) =>
alignedDataFrame.fields[seriesIdx]!.getLinks?.({ valueRowIndex: dataIdx }) ?? []
}
render={(u, dataIdxs, seriesIdx, isPinned = false, dismiss, timeRange, viaSync, dataLinks) => {
return (
<TimeSeriesTooltip
series={alignedDataFrame}
@ -130,6 +133,7 @@ export const TrendPanel = ({
isPinned={isPinned}
maxHeight={options.tooltip.maxHeight}
replaceVariables={replaceVariables}
dataLinks={dataLinks}
/>
);
}}

Loading…
Cancel
Save