diff --git a/.changeset/nasty-fans-run.md b/.changeset/nasty-fans-run.md new file mode 100644 index 00000000..27d982df --- /dev/null +++ b/.changeset/nasty-fans-run.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +fix: Fix crash when navigating away from chart explorer search page diff --git a/packages/app/src/components/DBEditTimeChartForm.tsx b/packages/app/src/components/DBEditTimeChartForm.tsx index 9a6b717a..0ddf0042 100644 --- a/packages/app/src/components/DBEditTimeChartForm.tsx +++ b/packages/app/src/components/DBEditTimeChartForm.tsx @@ -7,6 +7,7 @@ import { useRef, useState, } from 'react'; +import { omit } from 'lodash'; import { Control, Controller, @@ -385,6 +386,10 @@ export type SavedChartConfigWithSelectArray = Omit< select: Exclude; }; +type SavedChartConfigWithSeries = SavedChartConfig & { + series: SavedChartConfigWithSelectArray['select']; +}; + export default function EditTimeChartForm({ dashboardId, chartConfig, @@ -414,10 +419,20 @@ export default function EditTimeChartForm({ 'data-testid'?: string; submitRef?: React.MutableRefObject<(() => void) | undefined>; }) { + // useFieldArray only supports array type fields, and select can be either a string or array. + // To solve for this, we maintain an extra form field called 'series' which is always an array. + const configWithSeries: SavedChartConfigWithSeries = useMemo( + () => ({ + ...chartConfig, + series: Array.isArray(chartConfig.select) ? chartConfig.select : [], + }), + [chartConfig], + ); + const { control, watch, setValue, handleSubmit, register } = - useForm({ - defaultValues: chartConfig, - values: chartConfig, + useForm({ + defaultValues: configWithSeries, + values: configWithSeries, resolver: zodResolver(zSavedChartConfig), }); @@ -427,8 +442,8 @@ export default function EditTimeChartForm({ remove: removeSeries, swap: swapSeries, } = useFieldArray({ - control: control as Control, - name: 'select', + control: control as Control, + name: 'series', }); const select = watch('select'); @@ -492,11 +507,18 @@ export default function EditTimeChartForm({ const onSubmit = useCallback(() => { handleSubmit(form => { - setChartConfig(form); + // Merge the series and select fields back together, and prevent the series field from being submitted + const config = { + ...omit(form, ['series']), + select: + form.displayType === DisplayType.Search ? form.select : form.series, + }; + + setChartConfig(config); if (tableSource != null) { - const isSelectEmpty = !form.select || form.select.length === 0; // select is string or array + const isSelectEmpty = !config.select || config.select.length === 0; // select is string or array const newConfig = { - ...form, + ...config, from: tableSource.from, timestampValueExpression: tableSource.timestampValueExpression, dateRange, @@ -505,7 +527,7 @@ export default function EditTimeChartForm({ metricTables: tableSource.metricTables, select: isSelectEmpty ? tableSource.defaultTableSelectExpression || '' - : form.select, + : config.select, }; setQueriedConfig( // WARNING: DON'T JUST ASSIGN OBJECTS OR DO SPREAD OPERATOR STUFF WHEN @@ -525,12 +547,15 @@ export default function EditTimeChartForm({ }, [onSubmit, submitRef]); const handleSave = useCallback( - (v: SavedChartConfig) => { + (v: SavedChartConfigWithSeries) => { // If the chart type is search, we need to ensure the select is a string if (displayType === DisplayType.Search && typeof v.select !== 'string') { v.select = ''; + } else if (displayType !== DisplayType.Search) { + v.select = v.series; } - onSave?.(v); + // Avoid saving the series field. Series should be persisted in the select field. + onSave?.(omit(v, ['series'])); }, [onSave, displayType], ); @@ -543,17 +568,20 @@ export default function EditTimeChartForm({ if (name === 'displayType' && type === 'change') { if (_.displayType === DisplayType.Search && typeof select !== 'string') { setValue('select', ''); + setValue('series', []); } if (_.displayType !== DisplayType.Search && typeof select === 'string') { - setValue('where', ''); - setValue('select', [ + const defaultSeries: SavedChartConfigWithSelectArray['select'] = [ { aggFn: 'count', aggCondition: '', aggConditionLanguage: 'lucene', valueExpression: '', }, - ]); + ]; + setValue('where', ''); + setValue('select', defaultSeries); + setValue('series', defaultSeries); } onSubmit(); } @@ -711,7 +739,7 @@ export default function EditTimeChartForm({ index={index} key={field.id} parentRef={parentRef} - namePrefix={`select.${index}.`} + namePrefix={`series.${index}.`} onRemoveSeries={removeSeries} length={fields.length} onSwapSeries={swapSeries} @@ -773,7 +801,7 @@ export default function EditTimeChartForm({ Add Series )} - {select.length == 2 && displayType !== DisplayType.Number && ( + {fields.length == 2 && displayType !== DisplayType.Number && (