mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
Impact metrics errors with rate per second option (#10337)
- checkbox to select 'rate' vs 'increase' - always available for now, but does nothing for gauge. I can improve it later on - better preview - it will show resolved query underneath - cleaner error handling that doesn't overflow widgets
This commit is contained in:
parent
ada4431957
commit
69905185c5
@ -120,6 +120,7 @@ export const ChartConfigModal: FC<ChartConfigModalProps> = ({
|
|||||||
selectedRange={formData.selectedRange}
|
selectedRange={formData.selectedRange}
|
||||||
selectedLabels={formData.selectedLabels}
|
selectedLabels={formData.selectedLabels}
|
||||||
beginAtZero={formData.beginAtZero}
|
beginAtZero={formData.beginAtZero}
|
||||||
|
showRate={formData.showRate}
|
||||||
/>
|
/>
|
||||||
</StyledPreviewPanel>
|
</StyledPreviewPanel>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -21,6 +21,10 @@ const getConfigDescription = (config: ChartConfig): string => {
|
|||||||
|
|
||||||
parts.push(`last ${config.selectedRange}`);
|
parts.push(`last ${config.selectedRange}`);
|
||||||
|
|
||||||
|
if (config.showRate) {
|
||||||
|
parts.push('rate per second');
|
||||||
|
}
|
||||||
|
|
||||||
const labelCount = Object.keys(config.selectedLabels).length;
|
const labelCount = Object.keys(config.selectedLabels).length;
|
||||||
if (labelCount > 0) {
|
if (labelCount > 0) {
|
||||||
parts.push(`${labelCount} filter${labelCount > 1 ? 's' : ''}`);
|
parts.push(`${labelCount} filter${labelCount > 1 ? 's' : ''}`);
|
||||||
@ -29,15 +33,6 @@ const getConfigDescription = (config: ChartConfig): string => {
|
|||||||
return parts.join(' • ');
|
return parts.join(' • ');
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledChartWrapper = styled(Box)({
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
'& > div': {
|
|
||||||
height: '100% !important',
|
|
||||||
width: '100% !important',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const StyledWidget = styled(Paper)(({ theme }) => ({
|
const StyledWidget = styled(Paper)(({ theme }) => ({
|
||||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
@ -127,17 +122,16 @@ export const ChartItem: FC<ChartItemProps> = ({ config, onEdit, onDelete }) => (
|
|||||||
|
|
||||||
<StyledChartContent>
|
<StyledChartContent>
|
||||||
<StyledImpactChartContainer>
|
<StyledImpactChartContainer>
|
||||||
<StyledChartWrapper>
|
|
||||||
<ImpactMetricsChart
|
<ImpactMetricsChart
|
||||||
selectedSeries={config.selectedSeries}
|
selectedSeries={config.selectedSeries}
|
||||||
selectedRange={config.selectedRange}
|
selectedRange={config.selectedRange}
|
||||||
selectedLabels={config.selectedLabels}
|
selectedLabels={config.selectedLabels}
|
||||||
beginAtZero={config.beginAtZero}
|
beginAtZero={config.beginAtZero}
|
||||||
|
showRate={config.showRate}
|
||||||
aspectRatio={1.5}
|
aspectRatio={1.5}
|
||||||
overrideOptions={{ maintainAspectRatio: false }}
|
overrideOptions={{ maintainAspectRatio: false }}
|
||||||
emptyDataDescription='Send impact metrics using Unleash SDK for this series to view the chart.'
|
emptyDataDescription='Send impact metrics using Unleash SDK for this series to view the chart.'
|
||||||
/>
|
/>
|
||||||
</StyledChartWrapper>
|
|
||||||
</StyledImpactChartContainer>
|
</StyledImpactChartContainer>
|
||||||
</StyledChartContent>
|
</StyledChartContent>
|
||||||
</StyledWidget>
|
</StyledWidget>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
import type { FC, ReactNode } from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Alert } from '@mui/material';
|
import { Alert, Box, Typography } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
LineChart,
|
LineChart,
|
||||||
NotEnoughData,
|
NotEnoughData,
|
||||||
@ -16,11 +16,13 @@ type ImpactMetricsChartProps = {
|
|||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
selectedLabels: Record<string, string[]>;
|
selectedLabels: Record<string, string[]>;
|
||||||
beginAtZero: boolean;
|
beginAtZero: boolean;
|
||||||
|
showRate?: boolean;
|
||||||
aspectRatio?: number;
|
aspectRatio?: number;
|
||||||
overrideOptions?: Record<string, unknown>;
|
overrideOptions?: Record<string, unknown>;
|
||||||
errorTitle?: string;
|
errorTitle?: string;
|
||||||
emptyDataDescription?: string;
|
emptyDataDescription?: string;
|
||||||
noSeriesPlaceholder?: ReactNode;
|
noSeriesPlaceholder?: ReactNode;
|
||||||
|
isPreview?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||||
@ -28,14 +30,16 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
selectedRange,
|
selectedRange,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
beginAtZero,
|
beginAtZero,
|
||||||
|
showRate,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
overrideOptions = {},
|
overrideOptions = {},
|
||||||
errorTitle = 'Failed to load impact metrics. Please check if Prometheus is configured and the feature flag is enabled.',
|
errorTitle = 'Failed to load impact metrics.',
|
||||||
emptyDataDescription = 'Send impact metrics using Unleash SDK and select data series to view the chart.',
|
emptyDataDescription = 'Send impact metrics using Unleash SDK and select data series to view the chart.',
|
||||||
noSeriesPlaceholder,
|
noSeriesPlaceholder,
|
||||||
|
isPreview,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
data: { start, end, series: timeSeriesData },
|
data: { start, end, series: timeSeriesData, debug },
|
||||||
loading: dataLoading,
|
loading: dataLoading,
|
||||||
error: dataError,
|
error: dataError,
|
||||||
} = useImpactMetricsData(
|
} = useImpactMetricsData(
|
||||||
@ -43,6 +47,7 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
? {
|
? {
|
||||||
series: selectedSeries,
|
series: selectedSeries,
|
||||||
range: selectedRange,
|
range: selectedRange,
|
||||||
|
showRate,
|
||||||
labels:
|
labels:
|
||||||
Object.keys(selectedLabels).length > 0
|
Object.keys(selectedLabels).length > 0
|
||||||
? selectedLabels
|
? selectedLabels
|
||||||
@ -113,13 +118,14 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
y: {
|
y: {
|
||||||
beginAtZero,
|
beginAtZero,
|
||||||
title: {
|
title: {
|
||||||
display: false,
|
display: !!showRate,
|
||||||
|
text: showRate ? 'Rate per second' : '',
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
precision: 0,
|
precision: 0,
|
||||||
callback: (value: unknown): string | number =>
|
callback: (value: unknown): string | number =>
|
||||||
typeof value === 'number'
|
typeof value === 'number'
|
||||||
? formatLargeNumbers(value)
|
? `${formatLargeNumbers(value)}${showRate ? '/s' : ''}`
|
||||||
: (value as number),
|
: (value as number),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -143,13 +149,46 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasError ? <Alert severity='error'>{errorTitle}</Alert> : null}
|
<Box
|
||||||
|
sx={
|
||||||
|
!isPreview
|
||||||
|
? {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
'& > div': {
|
||||||
|
height: '100% !important',
|
||||||
|
width: '100% !important',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
>
|
||||||
<LineChart
|
<LineChart
|
||||||
data={notEnoughData || isLoading ? placeholderData : data}
|
data={notEnoughData || isLoading ? placeholderData : data}
|
||||||
aspectRatio={aspectRatio}
|
aspectRatio={aspectRatio}
|
||||||
overrideOptions={chartOptions}
|
overrideOptions={chartOptions}
|
||||||
cover={cover}
|
cover={
|
||||||
|
hasError ? (
|
||||||
|
<Alert severity='error'>{errorTitle}</Alert>
|
||||||
|
) : (
|
||||||
|
cover
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
{isPreview && debug?.query ? (
|
||||||
|
<Box
|
||||||
|
sx={(theme) => ({
|
||||||
|
margin: theme.spacing(2),
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
background: theme.palette.background.elevation1,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Typography variant='caption' color='text.secondary'>
|
||||||
|
<code>{debug.query}</code>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ type ImpactMetricsChartPreviewProps = {
|
|||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
selectedLabels: Record<string, string[]>;
|
selectedLabels: Record<string, string[]>;
|
||||||
beginAtZero: boolean;
|
beginAtZero: boolean;
|
||||||
|
showRate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
||||||
@ -15,6 +16,7 @@ export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
|||||||
selectedRange,
|
selectedRange,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
beginAtZero,
|
beginAtZero,
|
||||||
|
showRate,
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
<Typography variant='h6' color='text.secondary'>
|
<Typography variant='h6' color='text.secondary'>
|
||||||
@ -33,6 +35,8 @@ export const ImpactMetricsChartPreview: FC<ImpactMetricsChartPreviewProps> = ({
|
|||||||
selectedRange={selectedRange}
|
selectedRange={selectedRange}
|
||||||
selectedLabels={selectedLabels}
|
selectedLabels={selectedLabels}
|
||||||
beginAtZero={beginAtZero}
|
beginAtZero={beginAtZero}
|
||||||
|
showRate={showRate}
|
||||||
|
isPreview
|
||||||
/>
|
/>
|
||||||
</StyledChartContainer>
|
</StyledChartContainer>
|
||||||
</>
|
</>
|
||||||
|
@ -15,6 +15,7 @@ export type ImpactMetricsControlsProps = {
|
|||||||
| 'setSelectedRange'
|
| 'setSelectedRange'
|
||||||
| 'setBeginAtZero'
|
| 'setBeginAtZero'
|
||||||
| 'setSelectedLabels'
|
| 'setSelectedLabels'
|
||||||
|
| 'setShowRate'
|
||||||
>;
|
>;
|
||||||
metricSeries: (ImpactMetricsSeries & { name: string })[];
|
metricSeries: (ImpactMetricsSeries & { name: string })[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
@ -54,16 +55,29 @@ export const ImpactMetricsControls: FC<ImpactMetricsControlsProps> = ({
|
|||||||
onChange={actions.setSelectedRange}
|
onChange={actions.setSelectedRange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={formData.beginAtZero}
|
checked={formData.beginAtZero}
|
||||||
onChange={(e) => actions.setBeginAtZero(e.target.checked)}
|
onChange={(e) =>
|
||||||
|
actions.setBeginAtZero(e.target.checked)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label='Begin at zero'
|
label='Begin at zero'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={formData.showRate}
|
||||||
|
onChange={(e) => actions.setShowRate(e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label='Show rate per second'
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
{availableLabels && (
|
{availableLabels && (
|
||||||
<LabelsFilter
|
<LabelsFilter
|
||||||
selectedLabels={formData.selectedLabels}
|
selectedLabels={formData.selectedLabels}
|
||||||
|
@ -14,6 +14,7 @@ export type ChartFormState = {
|
|||||||
selectedSeries: string;
|
selectedSeries: string;
|
||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
beginAtZero: boolean;
|
beginAtZero: boolean;
|
||||||
|
showRate: boolean;
|
||||||
selectedLabels: Record<string, string[]>;
|
selectedLabels: Record<string, string[]>;
|
||||||
};
|
};
|
||||||
actions: {
|
actions: {
|
||||||
@ -21,6 +22,7 @@ export type ChartFormState = {
|
|||||||
setSelectedSeries: (series: string) => void;
|
setSelectedSeries: (series: string) => void;
|
||||||
setSelectedRange: (range: 'hour' | 'day' | 'week' | 'month') => void;
|
setSelectedRange: (range: 'hour' | 'day' | 'week' | 'month') => void;
|
||||||
setBeginAtZero: (beginAtZero: boolean) => void;
|
setBeginAtZero: (beginAtZero: boolean) => void;
|
||||||
|
setShowRate: (showRate: boolean) => void;
|
||||||
setSelectedLabels: (labels: Record<string, string[]>) => void;
|
setSelectedLabels: (labels: Record<string, string[]>) => void;
|
||||||
handleSeriesChange: (series: string) => void;
|
handleSeriesChange: (series: string) => void;
|
||||||
getConfigToSave: () => Omit<ChartConfig, 'id'>;
|
getConfigToSave: () => Omit<ChartConfig, 'id'>;
|
||||||
@ -46,6 +48,7 @@ export const useChartFormState = ({
|
|||||||
const [selectedLabels, setSelectedLabels] = useState<
|
const [selectedLabels, setSelectedLabels] = useState<
|
||||||
Record<string, string[]>
|
Record<string, string[]>
|
||||||
>(initialConfig?.selectedLabels || {});
|
>(initialConfig?.selectedLabels || {});
|
||||||
|
const [showRate, setShowRate] = useState(initialConfig?.showRate || false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { labels: currentAvailableLabels },
|
data: { labels: currentAvailableLabels },
|
||||||
@ -54,6 +57,7 @@ export const useChartFormState = ({
|
|||||||
? {
|
? {
|
||||||
series: selectedSeries,
|
series: selectedSeries,
|
||||||
range: selectedRange,
|
range: selectedRange,
|
||||||
|
showRate,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
@ -65,12 +69,14 @@ export const useChartFormState = ({
|
|||||||
setSelectedRange(initialConfig.selectedRange);
|
setSelectedRange(initialConfig.selectedRange);
|
||||||
setBeginAtZero(initialConfig.beginAtZero);
|
setBeginAtZero(initialConfig.beginAtZero);
|
||||||
setSelectedLabels(initialConfig.selectedLabels);
|
setSelectedLabels(initialConfig.selectedLabels);
|
||||||
|
setShowRate(initialConfig.showRate || false);
|
||||||
} else if (open && !initialConfig) {
|
} else if (open && !initialConfig) {
|
||||||
setTitle('');
|
setTitle('');
|
||||||
setSelectedSeries('');
|
setSelectedSeries('');
|
||||||
setSelectedRange('day');
|
setSelectedRange('day');
|
||||||
setBeginAtZero(false);
|
setBeginAtZero(false);
|
||||||
setSelectedLabels({});
|
setSelectedLabels({});
|
||||||
|
setShowRate(false);
|
||||||
}
|
}
|
||||||
}, [open, initialConfig]);
|
}, [open, initialConfig]);
|
||||||
|
|
||||||
@ -85,6 +91,7 @@ export const useChartFormState = ({
|
|||||||
selectedRange,
|
selectedRange,
|
||||||
beginAtZero,
|
beginAtZero,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
|
showRate,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isValid = selectedSeries.length > 0;
|
const isValid = selectedSeries.length > 0;
|
||||||
@ -95,6 +102,7 @@ export const useChartFormState = ({
|
|||||||
selectedSeries,
|
selectedSeries,
|
||||||
selectedRange,
|
selectedRange,
|
||||||
beginAtZero,
|
beginAtZero,
|
||||||
|
showRate,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -102,6 +110,7 @@ export const useChartFormState = ({
|
|||||||
setSelectedSeries,
|
setSelectedSeries,
|
||||||
setSelectedRange,
|
setSelectedRange,
|
||||||
setBeginAtZero,
|
setBeginAtZero,
|
||||||
|
setShowRate,
|
||||||
setSelectedLabels,
|
setSelectedLabels,
|
||||||
handleSeriesChange,
|
handleSeriesChange,
|
||||||
getConfigToSave,
|
getConfigToSave,
|
||||||
|
@ -43,6 +43,7 @@ describe('useImpactMetricsState', () => {
|
|||||||
selectedSeries: 'test-series',
|
selectedSeries: 'test-series',
|
||||||
selectedRange: 'day' as const,
|
selectedRange: 'day' as const,
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
|
showRate: false,
|
||||||
selectedLabels: {},
|
selectedLabels: {},
|
||||||
title: 'Test Chart',
|
title: 'Test Chart',
|
||||||
},
|
},
|
||||||
@ -84,6 +85,7 @@ describe('useImpactMetricsState', () => {
|
|||||||
selectedSeries: 'old-series',
|
selectedSeries: 'old-series',
|
||||||
selectedRange: 'day' as const,
|
selectedRange: 'day' as const,
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
|
showRate: false,
|
||||||
selectedLabels: {},
|
selectedLabels: {},
|
||||||
title: 'Old Chart',
|
title: 'Old Chart',
|
||||||
},
|
},
|
||||||
@ -98,6 +100,7 @@ describe('useImpactMetricsState', () => {
|
|||||||
selectedSeries: 'url-series',
|
selectedSeries: 'url-series',
|
||||||
selectedRange: 'day',
|
selectedRange: 'day',
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
|
showRate: false,
|
||||||
selectedLabels: {},
|
selectedLabels: {},
|
||||||
title: 'URL Chart',
|
title: 'URL Chart',
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ export type ChartConfig = {
|
|||||||
selectedSeries: string;
|
selectedSeries: string;
|
||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
beginAtZero: boolean;
|
beginAtZero: boolean;
|
||||||
|
showRate: boolean;
|
||||||
selectedLabels: Record<string, string[]>;
|
selectedLabels: Record<string, string[]>;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
@ -16,12 +16,16 @@ export type ImpactMetricsResponse = {
|
|||||||
step?: string;
|
step?: string;
|
||||||
series: ImpactMetricsSeries[];
|
series: ImpactMetricsSeries[];
|
||||||
labels?: ImpactMetricsLabels;
|
labels?: ImpactMetricsLabels;
|
||||||
|
debug?: {
|
||||||
|
query?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImpactMetricsQuery = {
|
export type ImpactMetricsQuery = {
|
||||||
series: string;
|
series: string;
|
||||||
range: 'hour' | 'day' | 'week' | 'month';
|
range: 'hour' | 'day' | 'week' | 'month';
|
||||||
labels?: Record<string, string[]>;
|
labels?: Record<string, string[]>;
|
||||||
|
showRate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useImpactMetricsData = (query?: ImpactMetricsQuery) => {
|
export const useImpactMetricsData = (query?: ImpactMetricsQuery) => {
|
||||||
@ -34,6 +38,10 @@ export const useImpactMetricsData = (query?: ImpactMetricsQuery) => {
|
|||||||
range: query.range,
|
range: query.range,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (query.showRate !== undefined) {
|
||||||
|
params.append('showRate', query.showRate.toString());
|
||||||
|
}
|
||||||
|
|
||||||
if (query.labels && Object.keys(query.labels).length > 0) {
|
if (query.labels && Object.keys(query.labels).length > 0) {
|
||||||
// Send labels as they are - the backend will handle the formatting
|
// Send labels as they are - the backend will handle the formatting
|
||||||
const labelsParam = Object.entries(query.labels).reduce(
|
const labelsParam = Object.entries(query.labels).reduce(
|
||||||
|
Loading…
Reference in New Issue
Block a user