1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-14 00:19:16 +01:00

feat: select multiple apps (#5860)

This commit is contained in:
Mateusz Kwasniewski 2024-01-12 08:33:52 +01:00 committed by GitHub
parent e3fc4b51fa
commit 6ba4591c7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 71 deletions

View File

@ -8,13 +8,18 @@ import {
import { IFeatureMetricsRaw } from 'interfaces/featureToggle'; import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
import { Grid } from '@mui/material'; import { Grid } from '@mui/material';
import { FeatureMetricsContent } from './FeatureMetricsContent/FeatureMetricsContent'; import { FeatureMetricsContent } from './FeatureMetricsContent/FeatureMetricsContent';
import { useQueryStringNumberState } from 'hooks/useQueryStringNumberState';
import { useQueryStringState } from 'hooks/useQueryStringState';
import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips'; import { FeatureMetricsChips } from './FeatureMetricsChips/FeatureMetricsChips';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { usePageTitle } from 'hooks/usePageTitle'; import { usePageTitle } from 'hooks/usePageTitle';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import {
ArrayParam,
NumberParam,
StringParam,
useQueryParams,
withDefault,
} from 'use-query-params';
export const FeatureMetrics = () => { export const FeatureMetrics = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
@ -23,8 +28,18 @@ export const FeatureMetrics = () => {
const applications = useFeatureMetricsApplications(featureId); const applications = useFeatureMetricsApplications(featureId);
usePageTitle('Metrics'); usePageTitle('Metrics');
const [hoursBack = FEATURE_METRIC_HOURS_BACK_DEFAULT, setHoursBack] = const defaultEnvironment = Array.from(environments)[0];
useQueryStringNumberState('hoursBack'); 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); const { featureMetrics } = useFeatureMetricsRaw(featureId, hoursBack);
// Keep a cache of the fetched metrics so that we can // Keep a cache of the fetched metrics so that we can
@ -37,18 +52,15 @@ export const FeatureMetrics = () => {
featureMetrics && setCachedMetrics(featureMetrics); featureMetrics && setCachedMetrics(featureMetrics);
}, [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(() => { const filteredMetrics = useMemo(() => {
return cachedMetrics return cachedMetrics
?.filter((metric) => metric.environment === environment) ?.filter((metric) => selectedEnvironment === metric.environment)
.filter((metric) => metric.appName === application); .filter((metric) => selectedApplications.includes(metric.appName));
}, [cachedMetrics, environment, application]); }, [
cachedMetrics,
selectedEnvironment,
JSON.stringify(selectedApplications),
]);
if (!filteredMetrics) { if (!filteredMetrics) {
return null; return null;
@ -64,8 +76,10 @@ export const FeatureMetrics = () => {
<FeatureMetricsChips <FeatureMetricsChips
title='Environments' title='Environments'
values={environments} values={environments}
value={environment} selectedValues={[selectedEnvironment]}
setValue={setEnvironment} toggleValue={(value) => {
setQuery({ environment: value });
}}
/> />
} }
/> />
@ -77,8 +91,24 @@ export const FeatureMetrics = () => {
<FeatureMetricsChips <FeatureMetricsChips
title='Applications' title='Applications'
values={applications} values={applications}
value={application} selectedValues={selectedApplications}
setValue={setApplication} toggleValue={(value) => {
if (selectedApplications.includes(value)) {
setQuery({
applications:
selectedApplications.filter(
(app) => app !== value,
),
});
} else {
setQuery({
applications: [
...selectedApplications,
value,
],
});
}
}}
/> />
} }
/> />
@ -86,7 +116,7 @@ export const FeatureMetrics = () => {
<Grid item xs={12} md={2}> <Grid item xs={12} md={2}>
<FeatureMetricsHours <FeatureMetricsHours
hoursBack={hoursBack} hoursBack={hoursBack}
setHoursBack={setHoursBack} setHoursBack={(value) => setQuery({ hoursBack: value })}
/> />
</Grid> </Grid>
</Grid> </Grid>

View File

@ -5,8 +5,8 @@ import { focusable } from 'themes/themeStyles';
interface IFeatureMetricsChipsProps { interface IFeatureMetricsChipsProps {
title: string; title: string;
values: Set<string>; values: Set<string>;
value?: string; selectedValues: string[];
setValue: (value: string) => void; toggleValue: (value: string) => void;
} }
const StyledTitle = styled('h2')(({ theme }) => ({ const StyledTitle = styled('h2')(({ theme }) => ({
@ -39,13 +39,11 @@ const StyledItem = styled('li')(({ theme }) => ({
export const FeatureMetricsChips = ({ export const FeatureMetricsChips = ({
title, title,
values, values,
value, selectedValues,
setValue, toggleValue,
}: IFeatureMetricsChipsProps) => { }: IFeatureMetricsChipsProps) => {
const onClick = (value: string) => () => { const onClick = (value: string) => () => {
if (values.has(value)) { toggleValue(value);
setValue(value);
}
}; };
const sortedValues = useMemo(() => { const sortedValues = useMemo(() => {
@ -63,7 +61,7 @@ export const FeatureMetricsChips = ({
<Chip <Chip
label={val} label={val}
onClick={onClick(val)} onClick={onClick(val)}
aria-pressed={val === value} aria-pressed={selectedValues?.includes(val)}
sx={focusable} sx={focusable}
/> />
</StyledItem> </StyledItem>

View File

@ -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,
];
};

View File

@ -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];
};