mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
fix: Use nuqs for ChartPage url query params (#311)
This commit is contained in:
parent
b05f8467f1
commit
396468c4a6
5 changed files with 90 additions and 74 deletions
5
.changeset/khaki-dancers-grab.md
Normal file
5
.changeset/khaki-dancers-grab.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hyperdx/app': minor
|
||||
---
|
||||
|
||||
fix: Use nuqs for ChartPage url query params
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
30
yarn.lock
30
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue