mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
feat: split impact metrics label filter into sections (#10623)
This commit is contained in:
parent
39ee5b97cb
commit
29dc6c746d
@ -48,29 +48,33 @@ export const LabelFilterItem: FC<LabelFilterItemProps> = ({
|
|||||||
newValues.filter((v) => v !== METRIC_LABELS_SELECT_ALL),
|
newValues.filter((v) => v !== METRIC_LABELS_SELECT_ALL),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderOption={(props, option, { selected }) => (
|
renderOption={(props, option, { selected }) => {
|
||||||
<li {...props}>
|
const { key, ...listItemProps } = props as any;
|
||||||
<Checkbox
|
|
||||||
size='small'
|
return (
|
||||||
checked={
|
<li key={key || option} {...listItemProps}>
|
||||||
option === METRIC_LABELS_SELECT_ALL
|
<Checkbox
|
||||||
? isAllSelected
|
size='small'
|
||||||
: selected
|
checked={
|
||||||
}
|
option === METRIC_LABELS_SELECT_ALL
|
||||||
style={{ marginRight: 8 }}
|
? isAllSelected
|
||||||
/>
|
: selected
|
||||||
{option === METRIC_LABELS_SELECT_ALL ? (
|
}
|
||||||
<Typography
|
style={{ marginRight: 8 }}
|
||||||
component='span'
|
/>
|
||||||
sx={{ color: 'text.secondary' }}
|
{option === METRIC_LABELS_SELECT_ALL ? (
|
||||||
>
|
<Typography
|
||||||
Select all
|
component='span'
|
||||||
</Typography>
|
sx={{ color: 'text.secondary' }}
|
||||||
) : (
|
>
|
||||||
option
|
Select all
|
||||||
)}
|
</Typography>
|
||||||
</li>
|
) : (
|
||||||
)}
|
option
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}}
|
||||||
renderTags={(value, getTagProps) => {
|
renderTags={(value, getTagProps) => {
|
||||||
const overflowCount = 5;
|
const overflowCount = 5;
|
||||||
const displayedValues = value.slice(-overflowCount);
|
const displayedValues = value.slice(-overflowCount);
|
@ -0,0 +1,105 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import { Box, Typography, Chip, styled } from '@mui/material';
|
||||||
|
import { LabelFilterItem } from './LabelFilterItem/LabelFilterItem.tsx';
|
||||||
|
|
||||||
|
const StyledContainer = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
width: '100%',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledHeader = styled(Box)(({ theme }) => ({
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledGrid = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
flexGrow: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledGridItem = styled(Box)({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flexGrow: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledTitle = styled(Typography)({
|
||||||
|
lineHeight: 1.5,
|
||||||
|
height: '24px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledClearAll = styled(Chip)(({ theme }) => ({
|
||||||
|
position: 'relative',
|
||||||
|
height: '20px',
|
||||||
|
margin: theme.spacing(-1, 0),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const LabelFilterSection: FC<{
|
||||||
|
title: string;
|
||||||
|
labels: [string, string[]][];
|
||||||
|
labelSelectors: Record<string, string[]>;
|
||||||
|
onLabelChange: (labelKey: string, values: string[]) => void;
|
||||||
|
onAllToggle: (labelKey: string, checked: boolean) => void;
|
||||||
|
onChange: (labels: Record<string, string[]>) => void;
|
||||||
|
}> = ({
|
||||||
|
title,
|
||||||
|
labels,
|
||||||
|
labelSelectors,
|
||||||
|
onLabelChange,
|
||||||
|
onAllToggle,
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const labelKeys = labels.map(([key]) => key);
|
||||||
|
|
||||||
|
const hasSelections = labelKeys.some((k) => labelSelectors[k]);
|
||||||
|
|
||||||
|
const clearSection = () => {
|
||||||
|
const newLabels: Record<string, string[]> = {};
|
||||||
|
Object.entries(labelSelectors).forEach(([key, val]) => {
|
||||||
|
if (!labelKeys.includes(key)) {
|
||||||
|
newLabels[key] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onChange(newLabels);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledHeader>
|
||||||
|
<StyledTitle variant='subtitle2'>{title}</StyledTitle>
|
||||||
|
{hasSelections && (
|
||||||
|
<StyledClearAll
|
||||||
|
label='Clear all'
|
||||||
|
size='small'
|
||||||
|
variant='outlined'
|
||||||
|
onClick={clearSection}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledHeader>
|
||||||
|
<StyledGrid>
|
||||||
|
{labels.map(([labelKey, values]) => {
|
||||||
|
const currentSelection = labelSelectors[labelKey] || [];
|
||||||
|
return (
|
||||||
|
<StyledGridItem key={labelKey}>
|
||||||
|
<LabelFilterItem
|
||||||
|
labelKey={labelKey}
|
||||||
|
options={values}
|
||||||
|
value={currentSelection}
|
||||||
|
onChange={(newValues) =>
|
||||||
|
onLabelChange(labelKey, newValues)
|
||||||
|
}
|
||||||
|
handleAllToggle={onAllToggle}
|
||||||
|
/>
|
||||||
|
</StyledGridItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</StyledGrid>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { Box, Typography, Chip } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData';
|
import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData';
|
||||||
import { LabelFilterItem } from './LabelFilterItem/LabelFilterItem.tsx';
|
import { LabelFilterSection } from './LabelFilterSection/LabelFilterSection.tsx';
|
||||||
|
|
||||||
export type LabelsFilterProps = {
|
export type LabelsFilterProps = {
|
||||||
labelSelectors: Record<string, string[]>;
|
labelSelectors: Record<string, string[]>;
|
||||||
@ -9,6 +9,8 @@ export type LabelsFilterProps = {
|
|||||||
availableLabels: ImpactMetricsLabels;
|
availableLabels: ImpactMetricsLabels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STATIC_LABELS = ['environment', 'appName', 'origin'];
|
||||||
|
|
||||||
export const LabelsFilter: FC<LabelsFilterProps> = ({
|
export const LabelsFilter: FC<LabelsFilterProps> = ({
|
||||||
labelSelectors,
|
labelSelectors,
|
||||||
onChange,
|
onChange,
|
||||||
@ -34,71 +36,40 @@ export const LabelsFilter: FC<LabelsFilterProps> = ({
|
|||||||
onChange(newLabels);
|
onChange(newLabels);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAllLabels = () => {
|
|
||||||
onChange({});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!availableLabels || Object.keys(availableLabels).length === 0) {
|
if (!availableLabels || Object.keys(availableLabels).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const staticLabels = Object.entries(availableLabels)
|
||||||
|
.filter(([key]) => STATIC_LABELS.includes(key))
|
||||||
|
.sort();
|
||||||
|
const dynamicLabels = Object.entries(availableLabels)
|
||||||
|
.filter(([key]) => !STATIC_LABELS.includes(key))
|
||||||
|
.sort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||||
<Box
|
{staticLabels.length > 0 && (
|
||||||
sx={{
|
<LabelFilterSection
|
||||||
display: 'flex',
|
title='Filter by labels'
|
||||||
alignItems: 'center',
|
labels={staticLabels}
|
||||||
gap: 1,
|
labelSelectors={labelSelectors}
|
||||||
width: '100%',
|
onLabelChange={handleLabelChange}
|
||||||
}}
|
onAllToggle={handleAllToggle}
|
||||||
>
|
onChange={onChange}
|
||||||
<Typography variant='subtitle2'>Filter by labels</Typography>
|
/>
|
||||||
{Object.keys(labelSelectors).length > 0 && (
|
)}
|
||||||
<Chip
|
|
||||||
label='Clear all'
|
|
||||||
size='small'
|
|
||||||
variant='outlined'
|
|
||||||
onClick={clearAllLabels}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box
|
{dynamicLabels.length > 0 && (
|
||||||
sx={{
|
<LabelFilterSection
|
||||||
display: 'grid',
|
title='Flag specific filters'
|
||||||
gridTemplateColumns:
|
labels={dynamicLabels}
|
||||||
'repeat(auto-fill, minmax(300px, 1fr))',
|
labelSelectors={labelSelectors}
|
||||||
gap: 2,
|
onLabelChange={handleLabelChange}
|
||||||
flexGrow: 1,
|
onAllToggle={handleAllToggle}
|
||||||
}}
|
onChange={onChange}
|
||||||
>
|
/>
|
||||||
{Object.entries(availableLabels)
|
)}
|
||||||
.sort()
|
|
||||||
.map(([labelKey, values]) => {
|
|
||||||
const currentSelection = labelSelectors[labelKey] || [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={labelKey}
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LabelFilterItem
|
|
||||||
labelKey={labelKey}
|
|
||||||
options={values}
|
|
||||||
value={currentSelection}
|
|
||||||
onChange={(newValues) =>
|
|
||||||
handleLabelChange(labelKey, newValues)
|
|
||||||
}
|
|
||||||
handleAllToggle={handleAllToggle}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user