Compare commits

...

7 Commits

Author SHA1 Message Date
Abhi kumar
f11153cdec Merge branch 'main' into chore/uplot-builder-restructure 2026-02-28 18:55:41 +05:30
Abhi Kumar
a380c4d6be chore: fixed tsc + test 2026-02-28 18:08:39 +05:30
Abhi Kumar
a0f576e5fb chore: fixed tsc + test 2026-02-28 17:53:44 +05:30
Abhi Kumar
98013ee3b9 chore: fixed tsc + test 2026-02-28 15:51:58 +05:30
Abhi Kumar
97b94fc4f5 chore: updated timezone types 2026-02-28 15:20:24 +05:30
Abhi Kumar
0f3f49b96a chore: updated baseconfigbuilder test 2026-02-28 15:15:21 +05:30
Abhi Kumar
0928a30863 chore: made baseconfigbuilder generic to be used across different charts 2026-02-28 15:10:16 +05:30
16 changed files with 108 additions and 82 deletions

View File

@@ -1,3 +1,4 @@
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';
@@ -8,7 +9,7 @@ interface BaseChartProps {
height: number;
showTooltip?: boolean;
showLegend?: boolean;
timezone: string;
timezone?: Timezone;
canPinTooltip?: boolean;
yAxisUnit?: string;
decimalPrecision?: PrecisionOption;

View File

@@ -129,12 +129,12 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
onDestroy={onPlotDestroy}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
timezone={timezone.value}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}
layoutChildren={layoutChildren}
isStackedBarChart={widget.stackedBarChart ?? false}
timezone={timezone}
>
<ContextMenu
coordinates={coordinates}

View File

@@ -5,12 +5,7 @@ import { getInitialStackedBands } from 'container/DashboardContainer/visualizati
import { getLegend } from 'lib/dashboard/getQueryResults';
import getLabelName from 'lib/getLabelName';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import {
DrawStyle,
LineInterpolation,
LineStyle,
VisibilityMode,
} from 'lib/uPlotV2/config/types';
import { DrawStyle } from 'lib/uPlotV2/config/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { get } from 'lodash-es';
import { Widgets } from 'types/api/dashboard/getAll';
@@ -63,7 +58,12 @@ export function prepareBarPanelConfig({
const minStepInterval = Math.min(...Object.values(stepIntervals));
const builder = buildBaseConfig({
widget,
widgetId: widget.id,
thresholds: widget.thresholds,
yAxisUnit: widget.yAxisUnit,
softMin: widget.softMin ?? undefined,
softMax: widget.softMax ?? undefined,
isLogScale: widget.isLogScale,
isDarkMode,
onClick,
onDragSelect,
@@ -98,14 +98,8 @@ export function prepareBarPanelConfig({
builder.addSeries({
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
panelType: PANEL_TYPES.BAR,
label: label,
colorMapping: widget.customLegendColors ?? {},
spanGaps: false,
lineStyle: LineStyle.Solid,
lineInterpolation: LineInterpolation.Spline,
showPoints: VisibilityMode.Never,
pointSize: 5,
isDarkMode,
stepInterval: currentStepInterval,
});

View File

@@ -100,7 +100,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
syncMode={DashboardCursorSync.Crosshair}
timezone={timezone.value}
timezone={timezone}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}

View File

@@ -154,7 +154,12 @@ export function prepareHistogramPanelConfig({
isDarkMode: boolean;
}): UPlotConfigBuilder {
const builder = buildBaseConfig({
widget,
widgetId: widget.id,
thresholds: widget.thresholds,
yAxisUnit: widget.yAxisUnit,
softMin: widget.softMin ?? undefined,
softMax: widget.softMax ?? undefined,
isLogScale: widget.isLogScale,
isDarkMode,
apiResponse,
panelMode,
@@ -191,10 +196,8 @@ export function prepareHistogramPanelConfig({
builder.addSeries({
label: '',
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
panelType: PANEL_TYPES.HISTOGRAM,
drawStyle: DrawStyle.Histogram,
colorMapping: widget.customLegendColors ?? {},
spanGaps: false,
barWidthFactor: 1,
pointSize: 5,
lineColor: '#3f5ecc',
@@ -216,10 +219,8 @@ export function prepareHistogramPanelConfig({
builder.addSeries({
label: label,
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
panelType: PANEL_TYPES.HISTOGRAM,
drawStyle: DrawStyle.Histogram,
colorMapping: widget.customLegendColors ?? {},
spanGaps: false,
barWidthFactor: 1,
pointSize: 5,
isDarkMode,

View File

@@ -118,7 +118,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
}}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
timezone={timezone.value}
timezone={timezone}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}

View File

@@ -82,7 +82,12 @@ export const prepareUPlotConfig = ({
const minStepInterval = Math.min(...Object.values(stepIntervals));
const builder = buildBaseConfig({
widget,
widgetId: widget.id,
thresholds: widget.thresholds,
yAxisUnit: widget.yAxisUnit,
softMin: widget.softMin ?? undefined,
softMax: widget.softMax ?? undefined,
isLogScale: widget.isLogScale,
isDarkMode,
onClick,
onDragSelect,
@@ -120,7 +125,6 @@ export const prepareUPlotConfig = ({
: VisibilityMode.Never,
pointSize: 5,
isDarkMode,
panelType: PANEL_TYPES.TIME_SERIES,
});
});

View File

@@ -1,11 +1,11 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { STEP_INTERVAL_MULTIPLIER } from 'lib/uPlotV2/constants';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { PanelMode } from '../../types';
import { buildBaseConfig } from '../baseConfigBuilder';
import { BaseConfigBuilderProps, buildBaseConfig } from '../baseConfigBuilder';
jest.mock(
'container/DashboardContainer/visualization/panels/utils/legendVisibilityUtils',
@@ -27,16 +27,17 @@ jest.mock('lib/uPlotLib/plugins/onClickPlugin', () => ({
default: jest.fn().mockReturnValue({ name: 'onClickPlugin' }),
}));
const createWidget = (overrides: Partial<Widgets> = {}): Widgets =>
({
id: 'widget-1',
yAxisUnit: 'ms',
isLogScale: false,
softMin: undefined,
softMax: undefined,
thresholds: [],
...overrides,
} as Widgets);
const createBaseConfigBuilderProps = (
overrides: Partial<BaseConfigBuilderProps> = {},
): Partial<BaseConfigBuilderProps> => ({
widgetId: 'widget-1',
yAxisUnit: 'ms',
isLogScale: false,
softMin: undefined,
softMax: undefined,
thresholds: [],
...overrides,
});
const createApiResponse = (
overrides: Partial<MetricRangePayloadProps> = {},
@@ -47,7 +48,7 @@ const createApiResponse = (
} as MetricRangePayloadProps);
const baseProps = {
widget: createWidget(),
...createBaseConfigBuilderProps(),
apiResponse: createApiResponse(),
isDarkMode: true,
panelMode: PanelMode.DASHBOARD_VIEW,
@@ -67,7 +68,7 @@ describe('buildBaseConfig', () => {
const builder = buildBaseConfig({
...baseProps,
panelMode: PanelMode.DASHBOARD_VIEW,
widget: createWidget({ id: 'my-widget' }),
...createBaseConfigBuilderProps({ widgetId: 'my-widget' }),
});
expect(builder.getWidgetId()).toBe('my-widget');
@@ -127,7 +128,7 @@ describe('buildBaseConfig', () => {
it('configures log scale on y axis when widget.isLogScale is true', () => {
const builder = buildBaseConfig({
...baseProps,
widget: createWidget({ isLogScale: true }),
...createBaseConfigBuilderProps({ isLogScale: true }),
});
const config = builder.getConfig();
@@ -171,7 +172,7 @@ describe('buildBaseConfig', () => {
it('adds thresholds from widget', () => {
const builder = buildBaseConfig({
...baseProps,
widget: createWidget({
...createBaseConfigBuilderProps({
thresholds: [
{
thresholdValue: 80,
@@ -179,7 +180,7 @@ describe('buildBaseConfig', () => {
thresholdUnit: 'ms',
thresholdLabel: 'High',
},
] as Widgets['thresholds'],
] as ThresholdProps[],
}),
});

View File

@@ -1,5 +1,6 @@
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import onClickPlugin, {
OnClickPluginOpts,
} from 'lib/uPlotLib/plugins/onClickPlugin';
@@ -9,28 +10,32 @@ import {
} from 'lib/uPlotV2/config/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { ThresholdsDrawHookOptions } from 'lib/uPlotV2/hooks/types';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { PanelMode } from '../types';
export interface BaseConfigBuilderProps {
widget: Widgets;
widgetId?: string;
thresholds?: ThresholdProps[];
apiResponse: MetricRangePayloadProps;
isDarkMode: boolean;
onClick?: OnClickPluginOpts['onClick'];
onDragSelect?: (startTime: number, endTime: number) => void;
timezone?: Timezone;
panelMode: PanelMode;
panelMode?: PanelMode;
panelType: PANEL_TYPES;
minTimeScale?: number;
maxTimeScale?: number;
stepInterval?: number;
isLogScale?: boolean;
yAxisUnit?: string;
softMin?: number;
softMax?: number;
}
export function buildBaseConfig({
widget,
widgetId,
isDarkMode,
onClick,
onDragSelect,
@@ -38,9 +43,14 @@ export function buildBaseConfig({
timezone,
panelMode,
panelType,
thresholds,
minTimeScale,
maxTimeScale,
stepInterval,
isLogScale,
yAxisUnit,
softMin,
softMax,
}: BaseConfigBuilderProps): UPlotConfigBuilder {
const tzDate = timezone
? (timestamp: number): Date =>
@@ -49,27 +59,26 @@ export function buildBaseConfig({
const builder = new UPlotConfigBuilder({
onDragSelect,
widgetId: widget.id,
widgetId: widgetId,
tzDate,
shouldSaveSelectionPreference: panelMode === PanelMode.DASHBOARD_VIEW,
selectionPreferencesSource: [
PanelMode.DASHBOARD_VIEW,
PanelMode.STANDALONE_VIEW,
].includes(panelMode)
? SelectionPreferencesSource.LOCAL_STORAGE
selectionPreferencesSource: panelMode
? [PanelMode.DASHBOARD_VIEW, PanelMode.STANDALONE_VIEW].includes(panelMode)
? SelectionPreferencesSource.LOCAL_STORAGE
: SelectionPreferencesSource.IN_MEMORY
: SelectionPreferencesSource.IN_MEMORY,
stepInterval,
});
const thresholdOptions: ThresholdsDrawHookOptions = {
scaleKey: 'y',
thresholds: (widget.thresholds || []).map((threshold) => ({
thresholds: (thresholds || []).map((threshold) => ({
thresholdValue: threshold.thresholdValue ?? 0,
thresholdColor: threshold.thresholdColor,
thresholdUnit: threshold.thresholdUnit,
thresholdLabel: threshold.thresholdLabel,
})),
yAxisUnit: widget.yAxisUnit,
yAxisUnit: yAxisUnit,
};
builder.addThresholds(thresholdOptions);
@@ -79,8 +88,8 @@ export function buildBaseConfig({
time: true,
min: minTimeScale,
max: maxTimeScale,
logBase: widget.isLogScale ? 10 : undefined,
distribution: widget.isLogScale
logBase: isLogScale ? 10 : undefined,
distribution: isLogScale
? DistributionType.Logarithmic
: DistributionType.Linear,
});
@@ -91,11 +100,11 @@ export function buildBaseConfig({
time: false,
min: undefined,
max: undefined,
softMin: widget.softMin ?? undefined,
softMax: widget.softMax ?? undefined,
softMin: softMin,
softMax: softMax,
thresholds: thresholdOptions,
logBase: widget.isLogScale ? 10 : undefined,
distribution: widget.isLogScale
logBase: isLogScale ? 10 : undefined,
distribution: isLogScale
? DistributionType.Logarithmic
: DistributionType.Linear,
});
@@ -114,7 +123,7 @@ export function buildBaseConfig({
show: true,
side: 2,
isDarkMode,
isLogScale: widget.isLogScale,
isLogScale: isLogScale,
panelType,
});
@@ -123,8 +132,8 @@ export function buildBaseConfig({
show: true,
side: 3,
isDarkMode,
isLogScale: widget.isLogScale,
yAxisUnit: widget.yAxisUnit,
isLogScale: isLogScale,
yAxisUnit: yAxisUnit,
panelType,
});

View File

@@ -4,6 +4,7 @@ import cx from 'classnames';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useTimezone } from 'providers/Timezone';
import { TooltipProps } from '../types';
@@ -22,6 +23,14 @@ export default function Tooltip({
const isDarkMode = useIsDarkMode();
const [listHeight, setListHeight] = useState(0);
const tooltipContent = content ?? [];
const { timezone: userTimezone } = useTimezone();
const resolvedTimezone = useMemo(() => {
if (!timezone) {
return userTimezone.value;
}
return timezone.value;
}, [timezone, userTimezone]);
const headerTitle = useMemo(() => {
if (!showTooltipHeader) {
@@ -33,10 +42,10 @@ export default function Tooltip({
return null;
}
return dayjs(data[0][cursorIdx] * 1000)
.tz(timezone)
.tz(resolvedTimezone)
.format(DATE_TIME_FORMATS.MONTH_DATETIME_SECONDS);
}, [
timezone,
resolvedTimezone,
uPlotInstance.data,
uPlotInstance.cursor.idx,
showTooltipHeader,

View File

@@ -83,7 +83,7 @@ function createUPlotInstance(cursorIdx: number | null): uPlot {
function renderTooltip(props: Partial<TooltipTestProps> = {}): RenderResult {
const defaultProps: TooltipTestProps = {
uPlotInstance: createUPlotInstance(null),
timezone: 'UTC',
timezone: { value: 'UTC', name: 'UTC', offset: '0', searchIndex: '0' },
content: [],
showTooltipHeader: true,
// TooltipRenderArgs (not used directly in component but required by type)

View File

@@ -1,4 +1,5 @@
import { ReactNode } from 'react';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import { PrecisionOption } from 'components/Graph/types';
import uPlot from 'uplot';
@@ -61,7 +62,7 @@ export interface TooltipRenderArgs {
export interface BaseTooltipProps {
showTooltipHeader?: boolean;
timezone: string;
timezone?: Timezone;
yAxisUnit?: string;
decimalPrecision?: PrecisionOption;
content?: TooltipContentItem[];

View File

@@ -1,4 +1,3 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
import { calculateWidthBasedOnStepInterval } from 'lib/uPlotV2/utils';
@@ -23,6 +22,9 @@ import {
* Path builders are static and shared across all instances of UPlotSeriesBuilder
*/
let builders: PathBuilders | null = null;
const DEFAULT_LINE_WIDTH = 2;
export const POINT_SIZE_FACTOR = 2.5;
export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
constructor(props: SeriesProps) {
super(props);
@@ -53,7 +55,7 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
const { lineWidth, lineStyle, lineCap, fillColor } = this.props;
const lineConfig: Partial<Series> = {
stroke: resolvedLineColor,
width: lineWidth ?? 2,
width: lineWidth ?? DEFAULT_LINE_WIDTH,
};
if (lineStyle === LineStyle.Dashed) {
@@ -66,9 +68,9 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
if (fillColor) {
lineConfig.fill = fillColor;
} else if (this.props.panelType === PANEL_TYPES.BAR) {
} else if (this.props.drawStyle === DrawStyle.Bar) {
lineConfig.fill = resolvedLineColor;
} else if (this.props.panelType === PANEL_TYPES.HISTOGRAM) {
} else if (this.props.drawStyle === DrawStyle.Histogram) {
lineConfig.fill = `${resolvedLineColor}40`;
}
@@ -137,10 +139,19 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
drawStyle,
showPoints,
} = this.props;
/**
* If pointSize is not provided, use the lineWidth * POINT_SIZE_FACTOR
* to determine the point size.
* POINT_SIZE_FACTOR is 2, so the point size will be 2x the line width.
*/
const resolvedPointSize =
pointSize ?? (lineWidth ?? DEFAULT_LINE_WIDTH) * POINT_SIZE_FACTOR;
const pointsConfig: Partial<Series.Points> = {
stroke: resolvedLineColor,
fill: resolvedLineColor,
size: !pointSize || pointSize < (lineWidth ?? 2) ? undefined : pointSize,
size: resolvedPointSize,
filter: pointsFilter || undefined,
};
@@ -231,7 +242,7 @@ function getPathBuilder({
throw new Error('Required uPlot path builders are not available');
}
if (drawStyle === DrawStyle.Bar) {
if (drawStyle === DrawStyle.Bar || drawStyle === DrawStyle.Histogram) {
const pathBuilders = uPlot.paths;
return getBarPathBuilder({
pathBuilders,

View File

@@ -1,4 +1,3 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import uPlot from 'uplot';
import {
@@ -43,7 +42,6 @@ describe('UPlotConfigBuilder', () => {
label: 'Requests',
colorMapping: {},
drawStyle: DrawStyle.Line,
panelType: PANEL_TYPES.TIME_SERIES,
...overrides,
});

View File

@@ -1,4 +1,3 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
import uPlot from 'uplot';
@@ -9,7 +8,7 @@ import {
LineStyle,
VisibilityMode,
} from '../types';
import { UPlotSeriesBuilder } from '../UPlotSeriesBuilder';
import { POINT_SIZE_FACTOR, UPlotSeriesBuilder } from '../UPlotSeriesBuilder';
const createBaseProps = (
overrides: Partial<SeriesProps> = {},
@@ -19,7 +18,6 @@ const createBaseProps = (
colorMapping: {},
drawStyle: DrawStyle.Line,
isDarkMode: false,
panelType: PANEL_TYPES.TIME_SERIES,
...overrides,
});
@@ -137,7 +135,6 @@ describe('UPlotSeriesBuilder', () => {
const smallPointsBuilder = new UPlotSeriesBuilder(
createBaseProps({
lineWidth: 4,
pointSize: 2,
}),
);
const largePointsBuilder = new UPlotSeriesBuilder(
@@ -150,7 +147,7 @@ describe('UPlotSeriesBuilder', () => {
const smallConfig = smallPointsBuilder.getConfig();
const largeConfig = largePointsBuilder.getConfig();
expect(smallConfig.points?.size).toBeUndefined();
expect(smallConfig.points?.size).toBe(4 * POINT_SIZE_FACTOR); // should be lineWidth * POINT_SIZE_FACTOR, when pointSize is not provided
expect(largeConfig.points?.size).toBe(4);
});

View File

@@ -112,6 +112,7 @@ export enum DrawStyle {
Line = 'line',
Points = 'points',
Bar = 'bar',
Histogram = 'histogram',
}
export enum LineInterpolation {
@@ -168,7 +169,6 @@ export interface PointsConfig {
export interface SeriesProps extends LineConfig, PointsConfig, BarConfig {
scaleKey: string;
label?: string;
panelType: PANEL_TYPES;
colorMapping: Record<string, string>;
drawStyle: DrawStyle;
pathBuilder?: Series.PathBuilder;