fix: Use nuqs for ChartPage url query params (#311)

This commit is contained in:
Shorpo 2024-02-16 18:46:20 -07:00 committed by GitHub
parent b05f8467f1
commit 396468c4a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 90 additions and 74 deletions

View file

@ -0,0 +1,5 @@
---
'@hyperdx/app': minor
---
fix: Use nuqs for ChartPage url query params

View file

@ -50,6 +50,7 @@
"nextra-theme-docs": "^2.0.2",
"normalize-url": "4",
"numbro": "^2.4.0",
"nuqs": "^1.17.0",
"pluralize": "^8.0.0",
"react": "^18.2.0",
"react-bootstrap": "^2.4.0",
@ -93,9 +94,6 @@
"use-query-params": "^2.1.2"
},
"devDependencies": {
"@types/react": "18.2.23",
"@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.2.18",
"@deploysentinel/jest-rtl-debugger": "^0.2.3",
"@jedmao/location": "^3.0.0",
"@testing-library/jest-dom": "^5.16.5",
@ -106,8 +104,11 @@
"@types/jest": "^28.1.6",
"@types/lodash": "^4.14.186",
"@types/pluralize": "^0.0.29",
"@types/react": "18.2.23",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-csv": "^1.1.3",
"@types/react-datepicker": "^4.10.0",
"@types/react-dom": "^18.2.18",
"@types/react-grid-layout": "^1.3.2",
"@types/react-slider": "^1.3.1",
"@types/react-syntax-highlighter": "^13.5.2",

View file

@ -1,31 +1,18 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import type { QueryParamConfig } from 'serialize-query-params';
import { decodeArray, encodeArray } from 'serialize-query-params';
import {
parseAsJson,
parseAsStringEnum,
parseAsStringLiteral,
useQueryState,
} from 'nuqs';
import { Granularity, isGranularity } from './ChartUtils';
import { Granularity } from './ChartUtils';
import EditTileForm from './EditTileForm';
import { withAppNav } from './layout';
import { parseTimeQuery, useNewTimeQuery } from './timeQuery';
import type { Chart, ChartSeries, Dashboard } from './types';
import { useQueryParam as useHDXQueryParam } from './useQueryParam';
const ChartSeriesParam: QueryParamConfig<ChartSeries[] | undefined> = {
encode: (
chartSeries: ChartSeries[] | undefined,
): (string | null)[] | null | undefined => {
return encodeArray(chartSeries?.map(chart => JSON.stringify(chart)));
},
decode: (
input: string | (string | null)[] | null | undefined,
): ChartSeries[] | undefined => {
// TODO: Validation
return decodeArray(input)?.flatMap(series =>
series != null ? [JSON.parse(series)] : [],
);
},
};
import type { Chart, ChartSeries } from './types';
function getDashboard(chart: Chart) {
return {
@ -63,45 +50,69 @@ function getDashboardHref({
return `/dashboards?${params.toString()}`;
}
const DEFAULT_SERIES: ChartSeries[] = [
{
table: 'logs',
type: 'time',
aggFn: 'count',
field: undefined,
where: '',
groupBy: [],
},
];
const getLegacySeriesQueryParam = () => {
try {
const params = new URLSearchParams(window.location.search);
const series = params.getAll('series');
return series?.flatMap(series =>
series != null ? [JSON.parse(series)] : [],
);
} catch (e) {
console.warn('Failed to parse legacy query param', e);
}
};
// TODO: This is a hack to set the default time range
const defaultTimeRange = parseTimeQuery('Past 1h', false) as [Date, Date];
function GraphPage() {
const [chartSeries, setChartSeries] = useHDXQueryParam<ChartSeries[]>(
'series',
[
{
table: 'logs',
type: 'time',
aggFn: 'count',
field: undefined,
where: '',
groupBy: [],
},
],
{
queryParamConfig: ChartSeriesParam,
},
const [_granularity, _setGranularity] = useQueryState(
'granularity',
parseAsStringEnum<Granularity>(Object.values(Granularity)),
);
const [granularity, setGranularity] = useHDXQueryParam<
Granularity | undefined
>('granularity', undefined, {
queryParamConfig: {
encode: (value: Granularity | undefined) => value ?? undefined,
decode: (input: string | (string | null)[] | null | undefined) =>
typeof input === 'string' && isGranularity(input) ? input : undefined,
const granularity = _granularity ?? undefined;
const setGranularity = useCallback(
(value: Granularity | undefined) => {
_setGranularity(value || null);
},
[_setGranularity],
);
const [seriesReturnType, setSeriesReturnType] = useQueryState(
'seriesReturnType',
parseAsStringLiteral(['ratio', 'column'] as const),
);
const [chartSeries, setChartSeries] = useQueryState(
'chartSeries',
parseAsJson<ChartSeries[]>().withDefault(DEFAULT_SERIES),
);
// Support for legacy query param
const [_, setLegacySeries] = useQueryState('series', {
shallow: true,
});
const [seriesReturnType, setSeriesReturnType] = useHDXQueryParam<
'ratio' | 'column' | undefined
>('seriesReturnType', undefined, {
queryParamConfig: {
encode: (value: 'ratio' | 'column' | undefined) => value ?? undefined,
decode: (input: string | (string | null)[] | null | undefined) =>
input === 'ratio' ? 'ratio' : 'column',
},
});
useEffect(() => {
const legacySeries = getLegacySeriesQueryParam();
if (legacySeries?.length) {
// Clear the legacy query param
setChartSeries(legacySeries);
setLegacySeries(null);
}
}, []);
const editedChart = useMemo<Chart>(() => {
return {
@ -111,7 +122,7 @@ function GraphPage() {
y: 0,
w: 6,
h: 3,
series: chartSeries,
series: chartSeries.length ? chartSeries : DEFAULT_SERIES,
seriesReturnType: seriesReturnType ?? 'column',
};
}, [chartSeries, seriesReturnType]);

View file

@ -82,7 +82,7 @@ export const isGranularity = (value: string): value is Granularity => {
};
const seriesDisplayName = (
s: ChartSeries,
s: ChartSeries | undefined,
{
showAggFn,
showField,
@ -93,6 +93,9 @@ const seriesDisplayName = (
showWhere?: boolean;
} = {},
) => {
if (!s) {
return '';
}
if (s.type === 'time' || s.type === 'table') {
if (s.displayName != null) {
return s.displayName;

View file

@ -5004,14 +5004,7 @@
date-fns "^2.0.1"
react-popper "^2.2.5"
"@types/react-dom@<18.0.0":
version "17.0.21"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.21.tgz#85d56965483ce4850f5f03f9234e54a1f47786e5"
integrity sha512-3rQEFUNUUz2MYiRwJJj6UekcW7rFLOtmK7ajQP7qJpjNdggInl3I/xM4I3Hq1yYPdCGVMgax1gZsB7BBTtayXg==
dependencies:
"@types/react" "^17"
"@types/react-dom@^18.2.18":
"@types/react-dom@<18.0.0", "@types/react-dom@^18.2.18":
version "18.2.18"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
@ -5069,15 +5062,6 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^17":
version "17.0.75"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.75.tgz#cffbc76840a12fcadaf5a3cf14878bb06efcf73d"
integrity sha512-MSA+NzEzXnQKrqpO63CYqNstFjsESgvJAdAyyJ1n6ZQq/GLgf6nOfIKwk+Twuz0L1N6xPe+qz5xRCJrbhMaLsw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
@ -12610,6 +12594,11 @@ mitt@^3.0.0:
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
mitt@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@ -13026,6 +13015,13 @@ numbro@^2.4.0:
dependencies:
bignumber.js "^8 || ^9"
nuqs@^1.17.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/nuqs/-/nuqs-1.17.0.tgz#b011a4b9ce445344db1cb9cd0d509a6c14659f9a"
integrity sha512-Lp2qLETMb7AAYhDtRtx20oAZNkYYTPApZcaX7Q5nQz7bfy893TCIe92IYVIfl9ZUqQLTsl9855KGEsA57dylPQ==
dependencies:
mitt "^3.0.1"
nwsapi@^2.0.7, nwsapi@^2.2.0, nwsapi@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0"