1
0
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:
Tymoteusz Czech 2025-09-05 09:26:23 +02:00 committed by GitHub
parent 39ee5b97cb
commit 29dc6c746d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 163 additions and 83 deletions

View File

@ -48,29 +48,33 @@ export const LabelFilterItem: FC<LabelFilterItemProps> = ({
newValues.filter((v) => v !== METRIC_LABELS_SELECT_ALL),
);
}}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
size='small'
checked={
option === METRIC_LABELS_SELECT_ALL
? isAllSelected
: selected
}
style={{ marginRight: 8 }}
/>
{option === METRIC_LABELS_SELECT_ALL ? (
<Typography
component='span'
sx={{ color: 'text.secondary' }}
>
Select all
</Typography>
) : (
option
)}
</li>
)}
renderOption={(props, option, { selected }) => {
const { key, ...listItemProps } = props as any;
return (
<li key={key || option} {...listItemProps}>
<Checkbox
size='small'
checked={
option === METRIC_LABELS_SELECT_ALL
? isAllSelected
: selected
}
style={{ marginRight: 8 }}
/>
{option === METRIC_LABELS_SELECT_ALL ? (
<Typography
component='span'
sx={{ color: 'text.secondary' }}
>
Select all
</Typography>
) : (
option
)}
</li>
);
}}
renderTags={(value, getTagProps) => {
const overflowCount = 5;
const displayedValues = value.slice(-overflowCount);

View File

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

View File

@ -1,7 +1,7 @@
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 { LabelFilterItem } from './LabelFilterItem/LabelFilterItem.tsx';
import { LabelFilterSection } from './LabelFilterSection/LabelFilterSection.tsx';
export type LabelsFilterProps = {
labelSelectors: Record<string, string[]>;
@ -9,6 +9,8 @@ export type LabelsFilterProps = {
availableLabels: ImpactMetricsLabels;
};
const STATIC_LABELS = ['environment', 'appName', 'origin'];
export const LabelsFilter: FC<LabelsFilterProps> = ({
labelSelectors,
onChange,
@ -34,71 +36,40 @@ export const LabelsFilter: FC<LabelsFilterProps> = ({
onChange(newLabels);
};
const clearAllLabels = () => {
onChange({});
};
if (!availableLabels || Object.keys(availableLabels).length === 0) {
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 (
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
width: '100%',
}}
>
<Typography variant='subtitle2'>Filter by labels</Typography>
{Object.keys(labelSelectors).length > 0 && (
<Chip
label='Clear all'
size='small'
variant='outlined'
onClick={clearAllLabels}
/>
)}
</Box>
{staticLabels.length > 0 && (
<LabelFilterSection
title='Filter by labels'
labels={staticLabels}
labelSelectors={labelSelectors}
onLabelChange={handleLabelChange}
onAllToggle={handleAllToggle}
onChange={onChange}
/>
)}
<Box
sx={{
display: 'grid',
gridTemplateColumns:
'repeat(auto-fill, minmax(300px, 1fr))',
gap: 2,
flexGrow: 1,
}}
>
{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>
{dynamicLabels.length > 0 && (
<LabelFilterSection
title='Flag specific filters'
labels={dynamicLabels}
labelSelectors={labelSelectors}
onLabelChange={handleLabelChange}
onAllToggle={handleAllToggle}
onChange={onChange}
/>
)}
</Box>
);
};