Skip to content

Commit c9c4b97

Browse files
committed
fix: Ensure displayed configs match queried configs in chart explorer
1 parent 2142734 commit c9c4b97

File tree

8 files changed

+272
-81
lines changed

8 files changed

+272
-81
lines changed

.changeset/pretty-donkeys-turn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/app": patch
3+
---
4+
5+
fix: Ensure displayed configs match queried configs in chart explorer

packages/app/src/ChartUtils.tsx

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useMemo } from 'react';
22
import { add } from 'date-fns';
3+
import { omit } from 'lodash';
34
import SqlString from 'sqlstring';
45
import { z } from 'zod';
56
import {
@@ -13,6 +14,7 @@ import { isMetricChartConfig } from '@hyperdx/common-utils/dist/core/renderChart
1314
import {
1415
AggregateFunction as AggFnV2,
1516
ChartConfigWithDateRange,
17+
ChartConfigWithOptTimestamp,
1618
DisplayType,
1719
Filter,
1820
MetricsDataType as MetricsDataTypeV2,
@@ -139,15 +141,20 @@ export const isGranularity = (value: string): value is Granularity => {
139141
return Object.values(Granularity).includes(value as Granularity);
140142
};
141143

142-
export function useTimeChartSettings(chartConfig: ChartConfigWithDateRange) {
143-
const autoGranularity = useMemo(() => {
144+
export function convertTimeChartAutoGranularity(
145+
chartConfig: ChartConfigWithDateRange,
146+
) {
147+
if (chartConfig.granularity === 'auto' || chartConfig.granularity == null) {
144148
return convertDateRangeToGranularityString(chartConfig.dateRange, 80);
145-
}, [chartConfig.dateRange]);
149+
}
146150

147-
const granularity =
148-
chartConfig.granularity === 'auto' || chartConfig.granularity == null
149-
? autoGranularity
150-
: chartConfig.granularity;
151+
return chartConfig.granularity;
152+
}
153+
154+
export function useTimeChartSettings(chartConfig: ChartConfigWithDateRange) {
155+
const granularity = useMemo(() => {
156+
return convertTimeChartAutoGranularity(chartConfig);
157+
}, [chartConfig]);
151158

152159
return {
153160
displayType: chartConfig.displayType,
@@ -1149,3 +1156,42 @@ export function buildTableRowSearchUrl({
11491156
valueRangeFilter,
11501157
});
11511158
}
1159+
1160+
export function convertToTimeChartConfig(config: ChartConfigWithDateRange) {
1161+
const granularity = convertTimeChartAutoGranularity(config);
1162+
1163+
return {
1164+
...config,
1165+
granularity,
1166+
limit: { limit: 100000 },
1167+
};
1168+
}
1169+
1170+
export function convertToNumberChartConfig(
1171+
config: ChartConfigWithDateRange,
1172+
): ChartConfigWithOptTimestamp {
1173+
return omit(config, ['granularity', 'groupBy']);
1174+
}
1175+
1176+
export function convertToTableChartConfig(
1177+
config: ChartConfigWithOptTimestamp,
1178+
): ChartConfigWithOptTimestamp {
1179+
const convertedConfig = omit(config, ['granularity']);
1180+
1181+
// Set a default limit if not already set
1182+
if (!convertedConfig.limit) {
1183+
convertedConfig.limit = { limit: 200 };
1184+
}
1185+
1186+
// Set a default orderBy if groupBy is set but orderBy is not,
1187+
// so that the set of rows within the limit is stable.
1188+
if (
1189+
convertedConfig.groupBy &&
1190+
typeof convertedConfig.groupBy === 'string' &&
1191+
!convertedConfig.orderBy
1192+
) {
1193+
convertedConfig.orderBy = convertedConfig.groupBy;
1194+
}
1195+
1196+
return convertedConfig;
1197+
}

packages/app/src/__tests__/ChartUtils.test.ts

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
1+
import {
2+
ChartConfigWithDateRange,
3+
SourceKind,
4+
TSource,
5+
} from '@hyperdx/common-utils/dist/types';
26

3-
import { formatResponseForTimeChart } from '@/ChartUtils';
7+
import {
8+
convertToNumberChartConfig,
9+
convertToTableChartConfig,
10+
convertToTimeChartConfig,
11+
formatResponseForTimeChart,
12+
} from '@/ChartUtils';
413

514
describe('ChartUtils', () => {
615
describe('formatResponseForTimeChart', () => {
@@ -529,4 +538,112 @@ describe('ChartUtils', () => {
529538
]);
530539
});
531540
});
541+
542+
describe('convertToTimeChartConfig', () => {
543+
it('should set granularity when granularity is auto', () => {
544+
const config = {
545+
granularity: 'auto',
546+
dateRange: [
547+
new Date('2025-11-26T00:00:00Z'),
548+
new Date('2025-11-27T00:00:00Z'),
549+
],
550+
} as ChartConfigWithDateRange;
551+
552+
const granularityFromFunction =
553+
convertToTimeChartConfig(config).granularity;
554+
555+
expect(granularityFromFunction).toBe('30 minute');
556+
});
557+
558+
it('should set granularity when granularity is undefined', () => {
559+
const config = {
560+
dateRange: [
561+
new Date('2025-11-26T00:00:00Z'),
562+
new Date('2025-11-27T00:00:00Z'),
563+
],
564+
} as ChartConfigWithDateRange;
565+
566+
const granularityFromFunction =
567+
convertToTimeChartConfig(config).granularity;
568+
569+
expect(granularityFromFunction).toBe('30 minute');
570+
});
571+
572+
it('should retain the specified granularity when not auto', () => {
573+
const config = {
574+
granularity: '5 minute',
575+
dateRange: [
576+
new Date('2025-11-26T00:00:00Z'),
577+
new Date('2025-11-27T00:00:00Z'),
578+
],
579+
} as ChartConfigWithDateRange;
580+
581+
const granularityFromFunction =
582+
convertToTimeChartConfig(config).granularity;
583+
584+
expect(granularityFromFunction).toBe('5 minute');
585+
});
586+
});
587+
588+
describe('convertToNumberChartConfig', () => {
589+
it('should remove granularity and groupBy from the config', () => {
590+
const config = {
591+
granularity: '5 minute',
592+
groupBy: 'ServiceName',
593+
dateRange: [
594+
new Date('2025-11-26T00:00:00Z'),
595+
new Date('2025-11-27T00:00:00Z'),
596+
],
597+
} as ChartConfigWithDateRange;
598+
599+
const convertedConfig = convertToNumberChartConfig(config);
600+
601+
expect(convertedConfig.granularity).toBeUndefined();
602+
expect(convertedConfig.groupBy).toBeUndefined();
603+
});
604+
});
605+
606+
describe('convertToTableChartConfig', () => {
607+
it('should remove granularity from the config', () => {
608+
const config = {
609+
granularity: '5 minute',
610+
dateRange: [
611+
new Date('2025-11-26T00:00:00Z'),
612+
new Date('2025-11-27T00:00:00Z'),
613+
],
614+
} as ChartConfigWithDateRange;
615+
616+
const convertedConfig = convertToTableChartConfig(config);
617+
618+
expect(convertedConfig.granularity).toBeUndefined();
619+
});
620+
621+
it('should apply a default sort if none is provided', () => {
622+
const config = {
623+
groupBy: 'ServiceName',
624+
dateRange: [
625+
new Date('2025-11-26T00:00:00Z'),
626+
new Date('2025-11-27T00:00:00Z'),
627+
],
628+
} as ChartConfigWithDateRange;
629+
630+
const convertedConfig = convertToTableChartConfig(config);
631+
632+
expect(convertedConfig.orderBy).toEqual('ServiceName');
633+
});
634+
635+
it('should apply a default limit if none is provided', () => {
636+
const config = {
637+
groupBy: 'ServiceName',
638+
dateRange: [
639+
new Date('2025-11-26T00:00:00Z'),
640+
new Date('2025-11-27T00:00:00Z'),
641+
],
642+
} as ChartConfigWithDateRange;
643+
644+
const convertedConfig = convertToTableChartConfig(config);
645+
646+
expect(convertedConfig.limit).toEqual({ limit: 200 });
647+
});
648+
});
532649
});

packages/app/src/components/ChartSQLPreview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState } from 'react';
22
import CopyToClipboard from 'react-copy-to-clipboard';
33
import { sql } from '@codemirror/lang-sql';
44
import { format } from '@hyperdx/common-utils/dist/sqlFormatter';
5-
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
5+
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
66
import { Button, Paper } from '@mantine/core';
77
import { IconCheck, IconCopy } from '@tabler/icons-react';
88
import CodeMirror from '@uiw/react-codemirror';
@@ -87,7 +87,7 @@ export function SQLPreview({
8787
export default function ChartSQLPreview({
8888
config,
8989
}: {
90-
config: ChartConfigWithDateRange;
90+
config: ChartConfigWithOptDateRange;
9191
}) {
9292
const { data } = useRenderedSqlChartConfig(config);
9393

0 commit comments

Comments
 (0)