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),
|
||||
);
|
||||
}}
|
||||
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);
|
@ -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 { 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>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user