diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx index d2506a7748..0e624426fa 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.tsx @@ -8,13 +8,18 @@ import { import { IFeatureMetricsRaw } from 'interfaces/featureToggle'; import { Grid } from '@mui/material'; import { FeatureMetricsContent } from './FeatureMetricsContent/FeatureMetricsContent'; -import { useQueryStringNumberState } from 'hooks/useQueryStringNumberState'; -import { useQueryStringState } from 'hooks/useQueryStringState'; import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { usePageTitle } from 'hooks/usePageTitle'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import { + ArrayParam, + NumberParam, + StringParam, + useQueryParams, + withDefault, +} from 'use-query-params'; export const FeatureMetrics = () => { const projectId = useRequiredPathParam('projectId'); @@ -23,8 +28,18 @@ export const FeatureMetrics = () => { const applications = useFeatureMetricsApplications(featureId); usePageTitle('Metrics'); - const [hoursBack = FEATURE_METRIC_HOURS_BACK_DEFAULT, setHoursBack] = - useQueryStringNumberState('hoursBack'); + const defaultEnvironment = Array.from(environments)[0]; + const defaultApplication = Array.from(applications)[0]; + const [query, setQuery] = useQueryParams({ + environment: withDefault(StringParam, defaultEnvironment), + applications: withDefault(ArrayParam, [defaultApplication]), + hoursBack: withDefault(NumberParam, FEATURE_METRIC_HOURS_BACK_DEFAULT), + }); + const { environment: selectedEnvironment, hoursBack } = query; + const selectedApplications = query.applications.filter( + (item) => item !== null, + ) as string[]; + const { featureMetrics } = useFeatureMetricsRaw(featureId, hoursBack); // Keep a cache of the fetched metrics so that we can @@ -37,18 +52,15 @@ export const FeatureMetrics = () => { featureMetrics && setCachedMetrics(featureMetrics); }, [featureMetrics]); - const defaultEnvironment = Array.from(environments)[0]; - const defaultApplication = Array.from(applications)[0]; - const [environment = defaultEnvironment, setEnvironment] = - useQueryStringState('environment'); - const [application = defaultApplication, setApplication] = - useQueryStringState('application'); - const filteredMetrics = useMemo(() => { return cachedMetrics - ?.filter((metric) => metric.environment === environment) - .filter((metric) => metric.appName === application); - }, [cachedMetrics, environment, application]); + ?.filter((metric) => selectedEnvironment === metric.environment) + .filter((metric) => selectedApplications.includes(metric.appName)); + }, [ + cachedMetrics, + selectedEnvironment, + JSON.stringify(selectedApplications), + ]); if (!filteredMetrics) { return null; @@ -64,8 +76,10 @@ export const FeatureMetrics = () => { { + setQuery({ environment: value }); + }} /> } /> @@ -77,8 +91,24 @@ export const FeatureMetrics = () => { { + if (selectedApplications.includes(value)) { + setQuery({ + applications: + selectedApplications.filter( + (app) => app !== value, + ), + }); + } else { + setQuery({ + applications: [ + ...selectedApplications, + value, + ], + }); + } + }} /> } /> @@ -86,7 +116,7 @@ export const FeatureMetrics = () => { setQuery({ hoursBack: value })} /> diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChips/FeatureMetricsChips.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChips/FeatureMetricsChips.tsx index 4a53c1885a..5ddc312af8 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChips/FeatureMetricsChips.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsChips/FeatureMetricsChips.tsx @@ -5,8 +5,8 @@ import { focusable } from 'themes/themeStyles'; interface IFeatureMetricsChipsProps { title: string; values: Set; - value?: string; - setValue: (value: string) => void; + selectedValues: string[]; + toggleValue: (value: string) => void; } const StyledTitle = styled('h2')(({ theme }) => ({ @@ -39,13 +39,11 @@ const StyledItem = styled('li')(({ theme }) => ({ export const FeatureMetricsChips = ({ title, values, - value, - setValue, + selectedValues, + toggleValue, }: IFeatureMetricsChipsProps) => { const onClick = (value: string) => () => { - if (values.has(value)) { - setValue(value); - } + toggleValue(value); }; const sortedValues = useMemo(() => { @@ -63,7 +61,7 @@ export const FeatureMetricsChips = ({ diff --git a/frontend/src/hooks/useQueryStringNumberState.ts b/frontend/src/hooks/useQueryStringNumberState.ts deleted file mode 100644 index 2a6eae4f6e..0000000000 --- a/frontend/src/hooks/useQueryStringNumberState.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useCallback } from 'react'; -import { useQueryStringState } from './useQueryStringState'; - -// Store a number in the query string. Call setState to update the query string. -export const useQueryStringNumberState = ( - key: string, -): [number | undefined, (value: number) => void] => { - const [value, setValue] = useQueryStringState(key); - - const setState = useCallback( - (value: number) => setValue(String(value)), - [setValue], - ); - - return [ - Number.isFinite(Number(value)) ? Number(value) : undefined, - setState, - ]; -}; diff --git a/frontend/src/hooks/useQueryStringState.ts b/frontend/src/hooks/useQueryStringState.ts deleted file mode 100644 index eac0b9bf76..0000000000 --- a/frontend/src/hooks/useQueryStringState.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useCallback, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; - -// Store a value in the query string. Call setState to update the query string. -export const useQueryStringState = ( - key: string, -): [string | undefined, (value: string) => void] => { - const { search } = window.location; - const navigate = useNavigate(); - - const params = useMemo(() => { - return new URLSearchParams(search); - }, [search]); - - const setState = useCallback( - (value: string) => { - const next = new URLSearchParams(search); - next.set(key, value); - navigate({ search: next.toString() }, { replace: true }); - }, - [key, search, navigate], - ); - - return [params.get(key) || undefined, setState]; -};