1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-01 13:47:27 +02:00

feat: series query warning (#10413)

Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
Tymoteusz Czech 2025-07-29 10:03:43 +02:00 committed by GitHub
parent 15449e83d3
commit 8554eee37a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 110 additions and 64 deletions

View File

@ -1,13 +1,19 @@
import { import {
Alert,
Autocomplete, Autocomplete,
Box,
Checkbox, Checkbox,
Chip, Chip,
FormControlLabel, FormControlLabel,
styled,
TextField, TextField,
Typography,
} from '@mui/material'; } from '@mui/material';
import type { FC } from 'react'; import type { FC } from 'react';
const StyledSelectAllLabel = styled('span')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));
type LabelFilterItemProps = { type LabelFilterItemProps = {
labelKey: string; labelKey: string;
options: string[]; options: string[];
@ -25,6 +31,7 @@ export const LabelFilterItem: FC<LabelFilterItemProps> = ({
const isAllSelected = value.includes('*'); const isAllSelected = value.includes('*');
const autocompleteId = `autocomplete-${labelKey}`; const autocompleteId = `autocomplete-${labelKey}`;
const selectAllId = `select-all-${labelKey}`; const selectAllId = `select-all-${labelKey}`;
const isTruncated = options.length >= 1_000;
return ( return (
<> <>
@ -46,16 +53,7 @@ export const LabelFilterItem: FC<LabelFilterItemProps> = ({
}} }}
/> />
} }
label={ label={<StyledSelectAllLabel>Select all</StyledSelectAllLabel>}
<Box
component='span'
sx={(theme) => ({
fontSize: theme.fontSizes.smallBody,
})}
>
Select all
</Box>
}
/> />
<Autocomplete <Autocomplete
multiple multiple
@ -66,37 +64,68 @@ export const LabelFilterItem: FC<LabelFilterItemProps> = ({
onChange(newValues); onChange(newValues);
}} }}
disabled={isAllSelected} disabled={isAllSelected}
renderTags={(value, getTagProps) => renderTags={(value, getTagProps) => {
value.map((option, index) => { const overflowCount = 5;
const { key, ...chipProps } = getTagProps({ const displayedValues = value.slice(-overflowCount);
index, const remainingCount = value.length - overflowCount;
});
return ( return (
<Chip <>
{...chipProps} {displayedValues.map((option, index) => {
key={key} const { key, ...chipProps } = getTagProps({
label={option} index,
size='small' });
/> return (
); <Chip
}) {...chipProps}
} key={key}
label={option}
size='small'
/>
);
})}
{remainingCount > 0 ? (
<Typography
component='span'
sx={{ color: 'text.secondary' }}
>
{' '}
(+{remainingCount})
</Typography>
) : null}
</>
);
}}
renderInput={(params) => ( renderInput={(params) => (
<TextField <>
{...params} <TextField
label={labelKey} {...params}
placeholder={ label={labelKey}
isAllSelected ? undefined : 'Select values…' placeholder={
} isAllSelected ? undefined : 'Select values…'
variant='outlined' }
size='small' variant='outlined'
inputProps={{ size='small'
...params.inputProps, inputProps={{
'aria-describedby': isAllSelected ...params.inputProps,
? `${selectAllId}-description` 'aria-describedby': isAllSelected
: undefined, ? `${selectAllId}-description`
}} : undefined,
/> }}
/>
{isTruncated && (
<Alert
severity='warning'
sx={(theme) => ({
padding: theme.spacing(1, 2),
marginTop: theme.spacing(1),
})}
>
Maximum of 1000 values loaded due to
performance.
</Alert>
)}
</>
)} )}
/> />
</> </>

View File

@ -72,30 +72,32 @@ export const LabelsFilter: FC<LabelsFilterProps> = ({
flexGrow: 1, flexGrow: 1,
}} }}
> >
{Object.entries(availableLabels).map(([labelKey, values]) => { {Object.entries(availableLabels)
const currentSelection = selectedLabels[labelKey] || []; .sort()
.map(([labelKey, values]) => {
const currentSelection = selectedLabels[labelKey] || [];
return ( return (
<Box <Box
key={labelKey} key={labelKey}
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flexGrow: 1, flexGrow: 1,
}} }}
> >
<LabelFilterItem <LabelFilterItem
labelKey={labelKey} labelKey={labelKey}
options={values} options={values}
value={currentSelection} value={currentSelection}
onChange={(newValues) => onChange={(newValues) =>
handleLabelChange(labelKey, newValues) handleLabelChange(labelKey, newValues)
} }
handleAllToggle={handleAllToggle} handleAllToggle={handleAllToggle}
/> />
</Box> </Box>
); );
})} })}
</Box> </Box>
</Box> </Box>
); );

View File

@ -202,6 +202,19 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
</Typography> </Typography>
</Box> </Box>
) : null} ) : null}
{isPreview && debug?.isTruncated ? (
<Box
sx={(theme) => ({
padding: theme.spacing(0, 2),
})}
>
<Alert severity='warning'>
Showing only {timeSeriesData.length} series due to
performance. Please change filters for more accurate
results.
</Alert>
</Box>
) : null}
</> </>
); );
}; };

View File

@ -10,6 +10,7 @@ export type ImpactMetricsSeries = {
data: TimeSeriesData; data: TimeSeriesData;
}; };
// TODO(impactMetrics): use OpenAPI types
export type ImpactMetricsResponse = { export type ImpactMetricsResponse = {
start?: string; start?: string;
end?: string; end?: string;
@ -18,6 +19,7 @@ export type ImpactMetricsResponse = {
labels?: ImpactMetricsLabels; labels?: ImpactMetricsLabels;
debug?: { debug?: {
query?: string; query?: string;
isTruncated?: string;
}; };
}; };