mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-01 19:42:55 +00:00
Compare commits
1 Commits
feat/pinne
...
enh/extern
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7974f4fb08 |
@@ -3,16 +3,14 @@ import { UseQueryResult } from 'react-query';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Card, Skeleton, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
|
||||
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||
import Uplot from 'components/Uplot';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
getCustomFiltersForBarChart,
|
||||
getFormattedEndPointStatusCodeChartData,
|
||||
getStatusCodeBarChartWidgetData,
|
||||
statusCodeWidgetInfo,
|
||||
} from 'container/ApiMonitoring/utils';
|
||||
import BarChart from 'container/DashboardContainer/visualization/charts/BarChart/BarChart';
|
||||
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
|
||||
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
|
||||
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
|
||||
@@ -20,15 +18,16 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { Options } from 'uplot';
|
||||
|
||||
import ErrorState from './ErrorState';
|
||||
import { prepareStatusCodeBarChartsConfig } from './utils';
|
||||
|
||||
function StatusCodeBarCharts({
|
||||
endPointStatusCodeBarChartsDataQuery,
|
||||
@@ -67,13 +66,6 @@ function StatusCodeBarCharts({
|
||||
} = endPointStatusCodeLatencyBarChartsDataQuery;
|
||||
|
||||
const { startTime: minTime, endTime: maxTime } = timeRange;
|
||||
const legendScrollPositionRef = useRef<{
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}>({
|
||||
scrollTop: 0,
|
||||
scrollLeft: 0,
|
||||
});
|
||||
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
const dimensions = useResizeObserver(graphRef);
|
||||
@@ -119,6 +111,7 @@ function StatusCodeBarCharts({
|
||||
|
||||
const navigateToExplorer = useNavigateToExplorer();
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const navigateToExplorerPages = useNavigateToExplorerPages();
|
||||
const { notifications } = useNotifications();
|
||||
@@ -134,12 +127,6 @@ function StatusCodeBarCharts({
|
||||
[],
|
||||
);
|
||||
|
||||
const { getCustomSeries } = useGetGraphCustomSeries({
|
||||
isDarkMode,
|
||||
drawStyle: 'bars',
|
||||
colorMapping,
|
||||
});
|
||||
|
||||
const widget = useMemo<Widgets>(
|
||||
() =>
|
||||
getStatusCodeBarChartWidgetData(domainName, {
|
||||
@@ -193,49 +180,36 @@ function StatusCodeBarCharts({
|
||||
],
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
apiResponse:
|
||||
currentWidgetInfoIndex === 0
|
||||
? formattedEndPointStatusCodeBarChartsDataPayload
|
||||
: formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
||||
isDarkMode,
|
||||
dimensions,
|
||||
yAxisUnit: statusCodeWidgetInfo[currentWidgetInfoIndex].yAxisUnit,
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
minTimeScale: minTime,
|
||||
maxTimeScale: maxTime,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
onClickHandler: graphClickHandler,
|
||||
customSeries: getCustomSeries,
|
||||
onDragSelect,
|
||||
colorMapping,
|
||||
query: currentQuery,
|
||||
legendScrollPosition: legendScrollPositionRef.current,
|
||||
setLegendScrollPosition: (position: {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}) => {
|
||||
legendScrollPositionRef.current = position;
|
||||
},
|
||||
}),
|
||||
[
|
||||
minTime,
|
||||
maxTime,
|
||||
currentWidgetInfoIndex,
|
||||
dimensions,
|
||||
formattedEndPointStatusCodeBarChartsDataPayload,
|
||||
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
||||
const config = useMemo(() => {
|
||||
const apiResponse =
|
||||
currentWidgetInfoIndex === 0
|
||||
? formattedEndPointStatusCodeBarChartsDataPayload
|
||||
: formattedEndPointStatusCodeLatencyBarChartsDataPayload;
|
||||
return prepareStatusCodeBarChartsConfig({
|
||||
timezone,
|
||||
isDarkMode,
|
||||
graphClickHandler,
|
||||
getCustomSeries,
|
||||
query: currentQuery,
|
||||
onDragSelect,
|
||||
onClick: graphClickHandler,
|
||||
apiResponse,
|
||||
minTimeScale: minTime,
|
||||
maxTimeScale: maxTime,
|
||||
yAxisUnit: statusCodeWidgetInfo[currentWidgetInfoIndex].yAxisUnit,
|
||||
colorMapping,
|
||||
currentQuery,
|
||||
],
|
||||
);
|
||||
});
|
||||
}, [
|
||||
currentQuery,
|
||||
isDarkMode,
|
||||
minTime,
|
||||
maxTime,
|
||||
graphClickHandler,
|
||||
onDragSelect,
|
||||
formattedEndPointStatusCodeBarChartsDataPayload,
|
||||
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
||||
timezone,
|
||||
currentWidgetInfoIndex,
|
||||
colorMapping,
|
||||
]);
|
||||
|
||||
const renderCardContent = useCallback(
|
||||
(query: UseQueryResult<SuccessResponse<any>, unknown>): JSX.Element => {
|
||||
@@ -253,11 +227,20 @@ function StatusCodeBarCharts({
|
||||
!query.isLoading && !query?.data?.payload?.data?.result?.length,
|
||||
})}
|
||||
>
|
||||
<Uplot options={options as Options} data={chartData} />
|
||||
<BarChart
|
||||
config={config}
|
||||
data={chartData}
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
timezone={timezone}
|
||||
legendConfig={{
|
||||
position: LegendPosition.BOTTOM,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[options, chartData],
|
||||
[config, chartData, dimensions, timezone],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { ExecStats } from 'api/v5/v5';
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { buildBaseConfig } from 'container/DashboardContainer/visualization/panels/utils/baseConfigBuilder';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { DrawStyle } from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { get } from 'lodash-es';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
|
||||
export const prepareStatusCodeBarChartsConfig = ({
|
||||
timezone,
|
||||
isDarkMode,
|
||||
query,
|
||||
onDragSelect,
|
||||
onClick,
|
||||
apiResponse,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
yAxisUnit,
|
||||
colorMapping,
|
||||
}: {
|
||||
timezone: Timezone;
|
||||
isDarkMode: boolean;
|
||||
query: Query;
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
onClick?: OnClickPluginOpts['onClick'];
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
yAxisUnit?: string;
|
||||
colorMapping?: Record<string, string>;
|
||||
}): UPlotConfigBuilder => {
|
||||
const stepIntervals: ExecStats['stepIntervals'] = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
);
|
||||
const minStepInterval = Math.min(...Object.values(stepIntervals));
|
||||
|
||||
const config = buildBaseConfig({
|
||||
yAxisUnit: yAxisUnit,
|
||||
apiResponse,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
timezone,
|
||||
onClick,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
stepInterval: minStepInterval,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
});
|
||||
|
||||
const seriesList: QueryData[] = apiResponse?.data?.result || [];
|
||||
seriesList.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '', // query
|
||||
series.legend || '',
|
||||
);
|
||||
|
||||
const label = query ? getLegend(series, query, baseLabelName) : baseLabelName;
|
||||
|
||||
const currentStepInterval = get(stepIntervals, series.queryName, undefined);
|
||||
|
||||
config.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: DrawStyle.Bar,
|
||||
label: label,
|
||||
colorMapping: colorMapping ?? {},
|
||||
isDarkMode,
|
||||
stepInterval: currentStepInterval,
|
||||
});
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
||||
@@ -10,15 +10,7 @@ import { useBarChartStacking } from '../../hooks/useBarChartStacking';
|
||||
import { BarChartProps } from '../types';
|
||||
|
||||
export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
const {
|
||||
children,
|
||||
isStackedBarChart,
|
||||
renderTooltip: customRenderTooltip,
|
||||
config,
|
||||
data,
|
||||
pinnedTooltipElement,
|
||||
...rest
|
||||
} = props;
|
||||
const { children, isStackedBarChart, config, data, ...rest } = props;
|
||||
|
||||
const chartData = useBarChartStacking({
|
||||
data,
|
||||
@@ -28,27 +20,16 @@ export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
if (customRenderTooltip) {
|
||||
return customRenderTooltip(props);
|
||||
}
|
||||
const tooltipProps: BarTooltipProps = {
|
||||
...props,
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
isStackedBarChart: isStackedBarChart,
|
||||
pinnedTooltipElement,
|
||||
};
|
||||
return <BarChartTooltip {...tooltipProps} />;
|
||||
},
|
||||
[
|
||||
customRenderTooltip,
|
||||
rest.timezone,
|
||||
rest.yAxisUnit,
|
||||
rest.decimalPrecision,
|
||||
isStackedBarChart,
|
||||
pinnedTooltipElement,
|
||||
],
|
||||
[rest.timezone, rest.yAxisUnit, rest.decimalPrecision, isStackedBarChart],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,7 +13,6 @@ export default function Histogram(props: HistogramChartProps): JSX.Element {
|
||||
children,
|
||||
renderTooltip: customRenderTooltip,
|
||||
isQueriesMerged,
|
||||
pinnedTooltipElement,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -27,17 +26,10 @@ export default function Histogram(props: HistogramChartProps): JSX.Element {
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
pinnedTooltipElement,
|
||||
};
|
||||
return <HistogramTooltip {...tooltipProps} />;
|
||||
},
|
||||
[
|
||||
customRenderTooltip,
|
||||
pinnedTooltipElement,
|
||||
rest.timezone,
|
||||
rest.yAxisUnit,
|
||||
rest.decimalPrecision,
|
||||
],
|
||||
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,12 +9,7 @@ import {
|
||||
import { TimeSeriesChartProps } from '../types';
|
||||
|
||||
export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
const {
|
||||
children,
|
||||
renderTooltip: customRenderTooltip,
|
||||
pinnedTooltipElement,
|
||||
...rest
|
||||
} = props;
|
||||
const { children, renderTooltip: customRenderTooltip, ...rest } = props;
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
@@ -26,17 +21,10 @@ export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
pinnedTooltipElement,
|
||||
};
|
||||
return <TimeSeriesTooltip {...tooltipProps} />;
|
||||
},
|
||||
[
|
||||
customRenderTooltip,
|
||||
pinnedTooltipElement,
|
||||
rest.timezone,
|
||||
rest.yAxisUnit,
|
||||
rest.decimalPrecision,
|
||||
],
|
||||
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import { LegendConfig, TooltipRenderArgs } from 'lib/uPlotV2/components/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
TooltipClickData,
|
||||
} from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
|
||||
|
||||
interface BaseChartProps {
|
||||
width: number;
|
||||
@@ -16,7 +13,6 @@ interface BaseChartProps {
|
||||
canPinTooltip?: boolean;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
pinnedTooltipElement?: (clickData: TooltipClickData) => React.ReactNode;
|
||||
renderTooltip?: (props: TooltipRenderArgs) => React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,7 @@ export default function Tooltip({
|
||||
uPlotInstance,
|
||||
timezone,
|
||||
content,
|
||||
isPinned,
|
||||
showTooltipHeader = true,
|
||||
pinnedTooltipElement,
|
||||
clickData,
|
||||
}: TooltipProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [listHeight, setListHeight] = useState(0);
|
||||
@@ -75,45 +72,39 @@ export default function Tooltip({
|
||||
)}
|
||||
data-testid="uplot-tooltip-container"
|
||||
>
|
||||
{isPinned && clickData ? (
|
||||
pinnedTooltipElement?.(clickData)
|
||||
) : (
|
||||
<>
|
||||
{showTooltipHeader && (
|
||||
<div className="uplot-tooltip-header" data-testid="uplot-tooltip-header">
|
||||
<span>{headerTitle}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="uplot-tooltip-list-container">
|
||||
{tooltipContent.length > 0 ? (
|
||||
<Virtuoso
|
||||
className="uplot-tooltip-list"
|
||||
data-testid="uplot-tooltip-list"
|
||||
data={tooltipContent}
|
||||
style={virtuosoStyle}
|
||||
totalListHeightChanged={setListHeight}
|
||||
itemContent={(_, item): JSX.Element => (
|
||||
<div className="uplot-tooltip-item" data-testid="uplot-tooltip-item">
|
||||
<div
|
||||
className="uplot-tooltip-item-marker"
|
||||
style={{ borderColor: item.color }}
|
||||
data-is-legend-marker={true}
|
||||
data-testid="uplot-tooltip-item-marker"
|
||||
/>
|
||||
<div
|
||||
className="uplot-tooltip-item-content"
|
||||
style={{ color: item.color, fontWeight: item.isActive ? 700 : 400 }}
|
||||
data-testid="uplot-tooltip-item-content"
|
||||
>
|
||||
{item.label}: {item.tooltipValue}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
{showTooltipHeader && (
|
||||
<div className="uplot-tooltip-header" data-testid="uplot-tooltip-header">
|
||||
<span>{headerTitle}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="uplot-tooltip-list-container">
|
||||
{tooltipContent.length > 0 ? (
|
||||
<Virtuoso
|
||||
className="uplot-tooltip-list"
|
||||
data-testid="uplot-tooltip-list"
|
||||
data={tooltipContent}
|
||||
style={virtuosoStyle}
|
||||
totalListHeightChanged={setListHeight}
|
||||
itemContent={(_, item): JSX.Element => (
|
||||
<div className="uplot-tooltip-item" data-testid="uplot-tooltip-item">
|
||||
<div
|
||||
className="uplot-tooltip-item-marker"
|
||||
style={{ borderColor: item.color }}
|
||||
data-is-legend-marker={true}
|
||||
data-testid="uplot-tooltip-item-marker"
|
||||
/>
|
||||
<div
|
||||
className="uplot-tooltip-item-content"
|
||||
style={{ color: item.color, fontWeight: item.isActive ? 700 : 400 }}
|
||||
data-testid="uplot-tooltip-item-content"
|
||||
>
|
||||
{item.label}: {item.tooltipValue}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { render, RenderResult, screen } from 'tests/test-utils';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import type { TooltipClickData } from '../../../plugins/TooltipPlugin/types';
|
||||
import { TooltipContentItem } from '../../types';
|
||||
import Tooltip from '../Tooltip';
|
||||
|
||||
@@ -93,7 +92,6 @@ function renderTooltip(props: Partial<TooltipTestProps> = {}): RenderResult {
|
||||
isPinned: false,
|
||||
dismiss: jest.fn(),
|
||||
viaSync: false,
|
||||
clickData: null,
|
||||
} as TooltipTestProps;
|
||||
|
||||
return render(
|
||||
@@ -207,43 +205,4 @@ describe('Tooltip', () => {
|
||||
// Falls back to content length: 2 items * 38px = 76px
|
||||
expect(list).toHaveStyle({ height: '76px' });
|
||||
});
|
||||
|
||||
it('renders pinned tooltip content when clickData is provided', () => {
|
||||
const uPlotInstance = createUPlotInstance(0);
|
||||
|
||||
const clickData: TooltipClickData = {
|
||||
xValue: 100,
|
||||
yValue: 200,
|
||||
focusedSeries: {
|
||||
seriesIndex: 1,
|
||||
seriesName: 'Series A',
|
||||
value: 10,
|
||||
color: '#ff0000',
|
||||
},
|
||||
clickedDataTimestamp: 100,
|
||||
mouseX: 50,
|
||||
mouseY: 60,
|
||||
absoluteMouseX: 150,
|
||||
absoluteMouseY: 160,
|
||||
};
|
||||
|
||||
const pinnedTooltipElement = jest.fn(
|
||||
(data: TooltipClickData): JSX.Element => (
|
||||
<div data-testid="pinned-tooltip">{`Pinned at ${data.xValue}`}</div>
|
||||
),
|
||||
);
|
||||
|
||||
renderTooltip({
|
||||
uPlotInstance,
|
||||
isPinned: true,
|
||||
clickData,
|
||||
pinnedTooltipElement,
|
||||
content: [createTooltipContent()],
|
||||
});
|
||||
|
||||
expect(pinnedTooltipElement).toHaveBeenCalledWith(clickData);
|
||||
expect(screen.getByTestId('pinned-tooltip')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('uplot-tooltip-header')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('uplot-tooltip-list')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { PrecisionOption } from 'components/Graph/types';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder';
|
||||
import { TooltipClickData } from '../plugins/TooltipPlugin/types';
|
||||
|
||||
/**
|
||||
* Props for the Plot component
|
||||
@@ -59,7 +58,6 @@ export interface TooltipRenderArgs {
|
||||
isPinned: boolean;
|
||||
dismiss: () => void;
|
||||
viaSync: boolean;
|
||||
clickData: TooltipClickData | null;
|
||||
}
|
||||
|
||||
export interface BaseTooltipProps {
|
||||
@@ -68,7 +66,6 @@ export interface BaseTooltipProps {
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
content?: TooltipContentItem[];
|
||||
pinnedTooltipElement?: (clickData: TooltipClickData) => ReactNode;
|
||||
}
|
||||
|
||||
export interface TimeSeriesTooltipProps
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import cx from 'classnames';
|
||||
import { getFocusedSeriesAtPosition } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import {
|
||||
@@ -143,7 +142,6 @@ export default function TooltipPlugin({
|
||||
const isPinnedBeforeDismiss = controller.pinned;
|
||||
controller.pinned = false;
|
||||
controller.hoverActive = false;
|
||||
controller.clickData = null;
|
||||
if (controller.plot) {
|
||||
controller.plot.setCursor({ left: -10, top: -10 });
|
||||
}
|
||||
@@ -163,7 +161,6 @@ export default function TooltipPlugin({
|
||||
isPinned: controller.pinned,
|
||||
dismiss: dismissTooltip,
|
||||
viaSync: controller.cursorDrivenBySync,
|
||||
clickData: controller.clickData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -250,26 +247,6 @@ export default function TooltipPlugin({
|
||||
!controller.pinned &&
|
||||
controller.focusedSeriesIndex != null
|
||||
) {
|
||||
const xValue = u.posToVal(event.offsetX, 'x');
|
||||
const yValue = u.posToVal(event.offsetY, 'y');
|
||||
const focusedSeries = getFocusedSeriesAtPosition(event, u);
|
||||
|
||||
let clickedDataTimestamp = xValue;
|
||||
if (focusedSeries) {
|
||||
clickedDataTimestamp = u.data[0][u.posToIdx(event.offsetX)];
|
||||
}
|
||||
|
||||
controller.clickData = {
|
||||
xValue,
|
||||
yValue,
|
||||
focusedSeries,
|
||||
clickedDataTimestamp,
|
||||
mouseX: event.offsetX,
|
||||
mouseY: event.offsetY,
|
||||
absoluteMouseX: event.clientX,
|
||||
absoluteMouseY: event.clientY,
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
controller.pinned = true;
|
||||
scheduleRender(true);
|
||||
|
||||
@@ -26,7 +26,6 @@ export function createInitialControllerState(): TooltipControllerState {
|
||||
windowWidth: window.innerWidth - WINDOW_OFFSET,
|
||||
windowHeight: window.innerHeight - WINDOW_OFFSET,
|
||||
pendingPinnedUpdate: false,
|
||||
clickData: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -42,22 +42,6 @@ export interface TooltipPluginProps {
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export interface TooltipClickData {
|
||||
xValue: number;
|
||||
yValue: number;
|
||||
focusedSeries: {
|
||||
seriesIndex: number;
|
||||
seriesName: string;
|
||||
value: number;
|
||||
color: string;
|
||||
} | null;
|
||||
clickedDataTimestamp: number;
|
||||
mouseX: number;
|
||||
mouseY: number;
|
||||
absoluteMouseX: number;
|
||||
absoluteMouseY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable, non-React state that drives tooltip behaviour:
|
||||
* - whether the tooltip is active / pinned
|
||||
@@ -84,9 +68,6 @@ export interface TooltipControllerState {
|
||||
windowWidth: number;
|
||||
windowHeight: number;
|
||||
pendingPinnedUpdate: boolean;
|
||||
|
||||
/** Data about the click that triggered the tooltip */
|
||||
clickData: TooltipClickData | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,12 +9,6 @@ import { UPlotConfigBuilder } from '../../config/UPlotConfigBuilder';
|
||||
import TooltipPlugin from '../TooltipPlugin/TooltipPlugin';
|
||||
import { DashboardCursorSync } from '../TooltipPlugin/types';
|
||||
|
||||
// Avoid depending on the full uPlot + onClickPlugin behaviour in these tests.
|
||||
// We only care that pinning logic runs without throwing, not which series is focused.
|
||||
jest.mock('lib/uPlotLib/plugins/onClickPlugin', () => ({
|
||||
getFocusedSeriesAtPosition: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -61,21 +55,11 @@ function createFakePlot(): {
|
||||
over: HTMLDivElement;
|
||||
setCursor: jest.Mock<void, [uPlot.Cursor]>;
|
||||
cursor: { event: Record<string, unknown> };
|
||||
posToVal: jest.Mock<number, [value: number]>;
|
||||
posToIdx: jest.Mock<number, []>;
|
||||
data: [number[], number[]];
|
||||
} {
|
||||
const over = document.createElement('div');
|
||||
|
||||
// Provide the minimal uPlot surface used by TooltipPlugin's pin logic.
|
||||
return {
|
||||
over,
|
||||
over: document.createElement('div'),
|
||||
setCursor: jest.fn(),
|
||||
cursor: { event: {} },
|
||||
// In real uPlot these map overlay coordinates to data-space values.
|
||||
posToVal: jest.fn((value: number) => value),
|
||||
posToIdx: jest.fn(() => 0),
|
||||
data: [[0], [0]],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user