mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
feat: rename impact metrics fields to match prometheus and grafana (#10616)
This commit is contained in:
parent
6a8a6e2373
commit
4a00792f1e
@ -123,10 +123,10 @@ export const ChartConfigModal: FC<ChartConfigModalProps> = ({
|
|||||||
<Box sx={(theme) => ({ padding: theme.spacing(1) })}>
|
<Box sx={(theme) => ({ padding: theme.spacing(1) })}>
|
||||||
<ImpactMetricsChart
|
<ImpactMetricsChart
|
||||||
key={screenBreakpoint ? 'small' : 'large'}
|
key={screenBreakpoint ? 'small' : 'large'}
|
||||||
selectedSeries={formData.selectedSeries}
|
metricName={formData.metricName}
|
||||||
selectedRange={formData.selectedRange}
|
timeRange={formData.timeRange}
|
||||||
selectedLabels={formData.selectedLabels}
|
labelSelectors={formData.labelSelectors}
|
||||||
beginAtZero={formData.beginAtZero}
|
yAxisMin={formData.yAxisMin}
|
||||||
aggregationMode={formData.aggregationMode}
|
aggregationMode={formData.aggregationMode}
|
||||||
isPreview
|
isPreview
|
||||||
/>
|
/>
|
||||||
@ -136,8 +136,8 @@ export const ChartConfigModal: FC<ChartConfigModalProps> = ({
|
|||||||
|
|
||||||
{currentAvailableLabels ? (
|
{currentAvailableLabels ? (
|
||||||
<LabelsFilter
|
<LabelsFilter
|
||||||
selectedLabels={formData.selectedLabels}
|
labelSelectors={formData.labelSelectors}
|
||||||
onChange={actions.setSelectedLabels}
|
onChange={actions.setLabelSelectors}
|
||||||
availableLabels={currentAvailableLabels}
|
availableLabels={currentAvailableLabels}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -12,9 +12,9 @@ export type ImpactMetricsControlsProps = {
|
|||||||
actions: Pick<
|
actions: Pick<
|
||||||
ChartFormState['actions'],
|
ChartFormState['actions'],
|
||||||
| 'handleSeriesChange'
|
| 'handleSeriesChange'
|
||||||
| 'setSelectedRange'
|
| 'setTimeRange'
|
||||||
| 'setBeginAtZero'
|
| 'setYAxisMin'
|
||||||
| 'setSelectedLabels'
|
| 'setLabelSelectors'
|
||||||
| 'setAggregationMode'
|
| 'setAggregationMode'
|
||||||
>;
|
>;
|
||||||
metricSeries: (ImpactMetricsSeries & { name: string })[];
|
metricSeries: (ImpactMetricsSeries & { name: string })[];
|
||||||
@ -43,34 +43,36 @@ export const ImpactMetricsControls: FC<ImpactMetricsControlsProps> = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<SeriesSelector
|
<SeriesSelector
|
||||||
value={formData.selectedSeries}
|
value={formData.metricName}
|
||||||
onChange={actions.handleSeriesChange}
|
onChange={actions.handleSeriesChange}
|
||||||
options={metricSeries}
|
options={metricSeries}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{formData.selectedSeries ? (
|
{formData.metricName ? (
|
||||||
<>
|
<>
|
||||||
<RangeSelector
|
<RangeSelector
|
||||||
value={formData.selectedRange}
|
value={formData.timeRange}
|
||||||
onChange={actions.setSelectedRange}
|
onChange={actions.setTimeRange}
|
||||||
/>
|
/>
|
||||||
<ModeSelector
|
<ModeSelector
|
||||||
value={formData.aggregationMode}
|
value={formData.aggregationMode}
|
||||||
onChange={actions.setAggregationMode}
|
onChange={actions.setAggregationMode}
|
||||||
seriesType={getMetricType(formData.selectedSeries)!}
|
seriesType={getMetricType(formData.metricName)!}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
{formData.selectedSeries ? (
|
{formData.metricName ? (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
|
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={formData.beginAtZero}
|
checked={formData.yAxisMin === 'zero'}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
actions.setBeginAtZero(e.target.checked)
|
actions.setYAxisMin(
|
||||||
|
e.target.checked ? 'zero' : 'auto',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,18 @@ import type { ImpactMetricsLabels } from 'hooks/api/getters/useImpactMetricsData
|
|||||||
import { LabelFilterItem } from './LabelFilterItem/LabelFilterItem.tsx';
|
import { LabelFilterItem } from './LabelFilterItem/LabelFilterItem.tsx';
|
||||||
|
|
||||||
export type LabelsFilterProps = {
|
export type LabelsFilterProps = {
|
||||||
selectedLabels: Record<string, string[]>;
|
labelSelectors: Record<string, string[]>;
|
||||||
onChange: (labels: Record<string, string[]>) => void;
|
onChange: (labels: Record<string, string[]>) => void;
|
||||||
availableLabels: ImpactMetricsLabels;
|
availableLabels: ImpactMetricsLabels;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LabelsFilter: FC<LabelsFilterProps> = ({
|
export const LabelsFilter: FC<LabelsFilterProps> = ({
|
||||||
selectedLabels,
|
labelSelectors,
|
||||||
onChange,
|
onChange,
|
||||||
availableLabels,
|
availableLabels,
|
||||||
}) => {
|
}) => {
|
||||||
const handleLabelChange = (labelKey: string, values: string[]) => {
|
const handleLabelChange = (labelKey: string, values: string[]) => {
|
||||||
const newLabels = { ...selectedLabels };
|
const newLabels = { ...labelSelectors };
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
delete newLabels[labelKey];
|
delete newLabels[labelKey];
|
||||||
} else {
|
} else {
|
||||||
@ -25,7 +25,7 @@ export const LabelsFilter: FC<LabelsFilterProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAllToggle = (labelKey: string, checked: boolean) => {
|
const handleAllToggle = (labelKey: string, checked: boolean) => {
|
||||||
const newLabels = { ...selectedLabels };
|
const newLabels = { ...labelSelectors };
|
||||||
if (checked) {
|
if (checked) {
|
||||||
newLabels[labelKey] = ['*'];
|
newLabels[labelKey] = ['*'];
|
||||||
} else {
|
} else {
|
||||||
@ -53,7 +53,7 @@ export const LabelsFilter: FC<LabelsFilterProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant='subtitle2'>Filter by labels</Typography>
|
<Typography variant='subtitle2'>Filter by labels</Typography>
|
||||||
{Object.keys(selectedLabels).length > 0 && (
|
{Object.keys(labelSelectors).length > 0 && (
|
||||||
<Chip
|
<Chip
|
||||||
label='Clear all'
|
label='Clear all'
|
||||||
size='small'
|
size='small'
|
||||||
@ -75,7 +75,7 @@ export const LabelsFilter: FC<LabelsFilterProps> = ({
|
|||||||
{Object.entries(availableLabels)
|
{Object.entries(availableLabels)
|
||||||
.sort()
|
.sort()
|
||||||
.map(([labelKey, values]) => {
|
.map(([labelKey, values]) => {
|
||||||
const currentSelection = selectedLabels[labelKey] || [];
|
const currentSelection = labelSelectors[labelKey] || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
@ -23,7 +23,7 @@ const getConfigDescription = (config: DisplayChartConfig): string => {
|
|||||||
parts.push(`${config.displayName}`);
|
parts.push(`${config.displayName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
parts.push(`last ${config.selectedRange}`);
|
parts.push(`last ${config.timeRange}`);
|
||||||
|
|
||||||
if (config.aggregationMode === 'rps') {
|
if (config.aggregationMode === 'rps') {
|
||||||
parts.push('rate per second');
|
parts.push('rate per second');
|
||||||
@ -35,7 +35,7 @@ const getConfigDescription = (config: DisplayChartConfig): string => {
|
|||||||
parts.push('sum');
|
parts.push('sum');
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelCount = Object.keys(config.selectedLabels).length;
|
const labelCount = Object.keys(config.labelSelectors).length;
|
||||||
if (labelCount > 0) {
|
if (labelCount > 0) {
|
||||||
parts.push(`${labelCount} filter${labelCount > 1 ? 's' : ''}`);
|
parts.push(`${labelCount} filter${labelCount > 1 ? 's' : ''}`);
|
||||||
}
|
}
|
||||||
@ -147,10 +147,10 @@ export const ChartItem: FC<ChartItemProps> = ({
|
|||||||
<StyledChartContent>
|
<StyledChartContent>
|
||||||
<StyledImpactChartContainer>
|
<StyledImpactChartContainer>
|
||||||
<ImpactMetricsChart
|
<ImpactMetricsChart
|
||||||
selectedSeries={config.selectedSeries}
|
metricName={config.metricName}
|
||||||
selectedRange={config.selectedRange}
|
timeRange={config.timeRange}
|
||||||
selectedLabels={config.selectedLabels}
|
labelSelectors={config.labelSelectors}
|
||||||
beginAtZero={config.beginAtZero}
|
yAxisMin={config.yAxisMin}
|
||||||
aggregationMode={config.aggregationMode}
|
aggregationMode={config.aggregationMode}
|
||||||
aspectRatio={1.5}
|
aspectRatio={1.5}
|
||||||
overrideOptions={{ maintainAspectRatio: false }}
|
overrideOptions={{ maintainAspectRatio: false }}
|
||||||
|
@ -13,10 +13,10 @@ import { useChartData } from './hooks/useChartData.ts';
|
|||||||
import type { AggregationMode } from './types.ts';
|
import type { AggregationMode } from './types.ts';
|
||||||
|
|
||||||
type ImpactMetricsChartProps = {
|
type ImpactMetricsChartProps = {
|
||||||
selectedSeries: string;
|
metricName: string;
|
||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
timeRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
selectedLabels: Record<string, string[]>;
|
labelSelectors: Record<string, string[]>;
|
||||||
beginAtZero: boolean;
|
yAxisMin: 'auto' | 'zero';
|
||||||
aggregationMode?: AggregationMode;
|
aggregationMode?: AggregationMode;
|
||||||
aspectRatio?: number;
|
aspectRatio?: number;
|
||||||
overrideOptions?: Record<string, unknown>;
|
overrideOptions?: Record<string, unknown>;
|
||||||
@ -27,10 +27,10 @@ type ImpactMetricsChartProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
||||||
selectedSeries,
|
metricName,
|
||||||
selectedRange,
|
timeRange,
|
||||||
selectedLabels,
|
labelSelectors,
|
||||||
beginAtZero,
|
yAxisMin,
|
||||||
aggregationMode,
|
aggregationMode,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
overrideOptions = {},
|
overrideOptions = {},
|
||||||
@ -44,14 +44,14 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
loading: dataLoading,
|
loading: dataLoading,
|
||||||
error: dataError,
|
error: dataError,
|
||||||
} = useImpactMetricsData(
|
} = useImpactMetricsData(
|
||||||
selectedSeries
|
metricName
|
||||||
? {
|
? {
|
||||||
series: selectedSeries,
|
series: metricName,
|
||||||
range: selectedRange,
|
range: timeRange,
|
||||||
aggregationMode,
|
aggregationMode,
|
||||||
labels:
|
labels:
|
||||||
Object.keys(selectedLabels).length > 0
|
Object.keys(labelSelectors).length > 0
|
||||||
? selectedLabels
|
? labelSelectors
|
||||||
: undefined,
|
: undefined,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
@ -66,7 +66,7 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
|
|
||||||
const hasError = !!dataError;
|
const hasError = !!dataError;
|
||||||
const isLoading = dataLoading;
|
const isLoading = dataLoading;
|
||||||
const shouldShowPlaceholder = !selectedSeries || isLoading || hasError;
|
const shouldShowPlaceholder = !metricName || isLoading || hasError;
|
||||||
const notEnoughData = useMemo(
|
const notEnoughData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
@ -81,7 +81,7 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
: undefined;
|
: undefined;
|
||||||
const maxTime = end ? fromUnixTime(Number.parseInt(end, 10)) : undefined;
|
const maxTime = end ? fromUnixTime(Number.parseInt(end, 10)) : undefined;
|
||||||
|
|
||||||
const placeholder = selectedSeries ? (
|
const placeholder = metricName ? (
|
||||||
<NotEnoughData description={emptyDataDescription} />
|
<NotEnoughData description={emptyDataDescription} />
|
||||||
) : noSeriesPlaceholder ? (
|
) : noSeriesPlaceholder ? (
|
||||||
noSeriesPlaceholder
|
noSeriesPlaceholder
|
||||||
@ -92,7 +92,7 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasManyLabels = Object.keys(selectedLabels).length > 0;
|
const hasManyLabels = Object.keys(labelSelectors).length > 0;
|
||||||
|
|
||||||
const cover = notEnoughData ? placeholder : isLoading;
|
const cover = notEnoughData ? placeholder : isLoading;
|
||||||
|
|
||||||
@ -106,10 +106,10 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
min: minTime?.getTime(),
|
min: minTime?.getTime(),
|
||||||
max: maxTime?.getTime(),
|
max: maxTime?.getTime(),
|
||||||
time: {
|
time: {
|
||||||
unit: getTimeUnit(selectedRange),
|
unit: getTimeUnit(timeRange),
|
||||||
displayFormats: {
|
displayFormats: {
|
||||||
[getTimeUnit(selectedRange)]:
|
[getTimeUnit(timeRange)]:
|
||||||
getDisplayFormat(selectedRange),
|
getDisplayFormat(timeRange),
|
||||||
},
|
},
|
||||||
tooltipFormat: 'PPpp',
|
tooltipFormat: 'PPpp',
|
||||||
},
|
},
|
||||||
@ -120,7 +120,7 @@ export const ImpactMetricsChart: FC<ImpactMetricsChartProps> = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
beginAtZero,
|
beginAtZero: yAxisMin === 'zero',
|
||||||
title: {
|
title: {
|
||||||
display: aggregationMode === 'rps',
|
display: aggregationMode === 'rps',
|
||||||
text:
|
text:
|
||||||
|
@ -12,19 +12,19 @@ type UseChartConfigParams = {
|
|||||||
export type ChartFormState = {
|
export type ChartFormState = {
|
||||||
formData: {
|
formData: {
|
||||||
title: string;
|
title: string;
|
||||||
selectedSeries: string;
|
metricName: string;
|
||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
timeRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
beginAtZero: boolean;
|
yAxisMin: 'auto' | 'zero';
|
||||||
aggregationMode: AggregationMode;
|
aggregationMode: AggregationMode;
|
||||||
selectedLabels: Record<string, string[]>;
|
labelSelectors: Record<string, string[]>;
|
||||||
};
|
};
|
||||||
actions: {
|
actions: {
|
||||||
setTitle: (title: string) => void;
|
setTitle: (title: string) => void;
|
||||||
setSelectedSeries: (series: string) => void;
|
setMetricName: (series: string) => void;
|
||||||
setSelectedRange: (range: 'hour' | 'day' | 'week' | 'month') => void;
|
setTimeRange: (range: 'hour' | 'day' | 'week' | 'month') => void;
|
||||||
setBeginAtZero: (beginAtZero: boolean) => void;
|
setYAxisMin: (yAxisMin: 'auto' | 'zero') => void;
|
||||||
setAggregationMode: (mode: AggregationMode) => void;
|
setAggregationMode: (mode: AggregationMode) => void;
|
||||||
setSelectedLabels: (labels: Record<string, string[]>) => void;
|
setLabelSelectors: (labels: Record<string, string[]>) => void;
|
||||||
handleSeriesChange: (series: string) => void;
|
handleSeriesChange: (series: string) => void;
|
||||||
getConfigToSave: () => Omit<ChartConfig, 'id'>;
|
getConfigToSave: () => Omit<ChartConfig, 'id'>;
|
||||||
};
|
};
|
||||||
@ -37,20 +37,18 @@ export const useChartFormState = ({
|
|||||||
initialConfig,
|
initialConfig,
|
||||||
}: UseChartConfigParams): ChartFormState => {
|
}: UseChartConfigParams): ChartFormState => {
|
||||||
const [title, setTitle] = useState(initialConfig?.title || '');
|
const [title, setTitle] = useState(initialConfig?.title || '');
|
||||||
const [selectedSeries, setSelectedSeries] = useState(
|
const [metricName, setMetricName] = useState(
|
||||||
initialConfig?.selectedSeries || '',
|
initialConfig?.metricName || '',
|
||||||
);
|
);
|
||||||
const [selectedRange, setSelectedRange] = useState<
|
const [timeRange, setTimeRange] = useState<
|
||||||
'hour' | 'day' | 'week' | 'month'
|
'hour' | 'day' | 'week' | 'month'
|
||||||
>(initialConfig?.selectedRange || 'day');
|
>(initialConfig?.timeRange || 'day');
|
||||||
const [beginAtZero, setBeginAtZero] = useState(
|
const [yAxisMin, setYAxisMin] = useState(initialConfig?.yAxisMin || 'auto');
|
||||||
initialConfig?.beginAtZero || false,
|
const [labelSelectors, setLabelSelectors] = useState<
|
||||||
);
|
|
||||||
const [selectedLabels, setSelectedLabels] = useState<
|
|
||||||
Record<string, string[]>
|
Record<string, string[]>
|
||||||
>(initialConfig?.selectedLabels || {});
|
>(initialConfig?.labelSelectors || {});
|
||||||
const [aggregationMode, setAggregationMode] = useState<AggregationMode>(
|
const [aggregationMode, setAggregationMode] = useState<AggregationMode>(
|
||||||
(initialConfig?.aggregationMode || getMetricType(selectedSeries)) ===
|
(initialConfig?.aggregationMode || getMetricType(metricName)) ===
|
||||||
'counter'
|
'counter'
|
||||||
? 'count'
|
? 'count'
|
||||||
: 'avg',
|
: 'avg',
|
||||||
@ -59,10 +57,10 @@ export const useChartFormState = ({
|
|||||||
const {
|
const {
|
||||||
data: { labels: currentAvailableLabels },
|
data: { labels: currentAvailableLabels },
|
||||||
} = useImpactMetricsData(
|
} = useImpactMetricsData(
|
||||||
selectedSeries
|
metricName
|
||||||
? {
|
? {
|
||||||
series: selectedSeries,
|
series: metricName,
|
||||||
range: selectedRange,
|
range: timeRange,
|
||||||
aggregationMode,
|
aggregationMode,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
@ -71,29 +69,29 @@ export const useChartFormState = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && initialConfig) {
|
if (open && initialConfig) {
|
||||||
setTitle(initialConfig.title || '');
|
setTitle(initialConfig.title || '');
|
||||||
setSelectedSeries(initialConfig.selectedSeries);
|
setMetricName(initialConfig.metricName);
|
||||||
setSelectedRange(initialConfig.selectedRange);
|
setTimeRange(initialConfig.timeRange);
|
||||||
setBeginAtZero(initialConfig.beginAtZero);
|
setYAxisMin(initialConfig.yAxisMin);
|
||||||
setSelectedLabels(initialConfig.selectedLabels);
|
setLabelSelectors(initialConfig.labelSelectors);
|
||||||
setAggregationMode(
|
setAggregationMode(
|
||||||
initialConfig.aggregationMode ||
|
initialConfig.aggregationMode ||
|
||||||
(getMetricType(initialConfig.selectedSeries) === 'counter'
|
(getMetricType(initialConfig.metricName) === 'counter'
|
||||||
? 'count'
|
? 'count'
|
||||||
: 'avg'),
|
: 'avg'),
|
||||||
);
|
);
|
||||||
} else if (open && !initialConfig) {
|
} else if (open && !initialConfig) {
|
||||||
setTitle('');
|
setTitle('');
|
||||||
setSelectedSeries('');
|
setMetricName('');
|
||||||
setSelectedRange('day');
|
setTimeRange('day');
|
||||||
setBeginAtZero(false);
|
setYAxisMin('auto');
|
||||||
setSelectedLabels({});
|
setLabelSelectors({});
|
||||||
setAggregationMode('count');
|
setAggregationMode('count');
|
||||||
}
|
}
|
||||||
}, [open, initialConfig]);
|
}, [open, initialConfig]);
|
||||||
|
|
||||||
const handleSeriesChange = (series: string) => {
|
const handleSeriesChange = (series: string) => {
|
||||||
setSelectedSeries(series);
|
setMetricName(series);
|
||||||
setSelectedLabels({});
|
setLabelSelectors({});
|
||||||
const metric = getMetricType(series);
|
const metric = getMetricType(series);
|
||||||
if (metric === 'counter') {
|
if (metric === 'counter') {
|
||||||
setAggregationMode('count');
|
setAggregationMode('count');
|
||||||
@ -104,31 +102,31 @@ export const useChartFormState = ({
|
|||||||
|
|
||||||
const getConfigToSave = (): Omit<ChartConfig, 'id'> => ({
|
const getConfigToSave = (): Omit<ChartConfig, 'id'> => ({
|
||||||
title: title || undefined,
|
title: title || undefined,
|
||||||
selectedSeries,
|
metricName,
|
||||||
selectedRange,
|
timeRange,
|
||||||
beginAtZero,
|
yAxisMin,
|
||||||
selectedLabels,
|
labelSelectors,
|
||||||
aggregationMode,
|
aggregationMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isValid = selectedSeries.length > 0;
|
const isValid = metricName.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formData: {
|
formData: {
|
||||||
title,
|
title,
|
||||||
selectedSeries,
|
metricName,
|
||||||
selectedRange,
|
timeRange,
|
||||||
beginAtZero,
|
yAxisMin,
|
||||||
aggregationMode,
|
aggregationMode,
|
||||||
selectedLabels,
|
labelSelectors,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setTitle,
|
setTitle,
|
||||||
setSelectedSeries,
|
setMetricName,
|
||||||
setSelectedRange,
|
setTimeRange,
|
||||||
setBeginAtZero,
|
setYAxisMin,
|
||||||
setAggregationMode,
|
setAggregationMode,
|
||||||
setSelectedLabels,
|
setLabelSelectors,
|
||||||
handleSeriesChange,
|
handleSeriesChange,
|
||||||
getConfigToSave,
|
getConfigToSave,
|
||||||
},
|
},
|
||||||
|
@ -34,11 +34,11 @@ const TestComponent: FC<{
|
|||||||
data-testid='add-chart'
|
data-testid='add-chart'
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addChart({
|
addChart({
|
||||||
selectedSeries: 'test-series',
|
metricName: 'test-series',
|
||||||
selectedRange: 'day',
|
timeRange: 'day',
|
||||||
beginAtZero: true,
|
yAxisMin: 'zero',
|
||||||
aggregationMode: 'count',
|
aggregationMode: 'count',
|
||||||
selectedLabels: {},
|
labelSelectors: {},
|
||||||
title: 'Test Chart',
|
title: 'Test Chart',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -76,11 +76,11 @@ const mockSettings: ImpactMetricsState = {
|
|||||||
charts: [
|
charts: [
|
||||||
{
|
{
|
||||||
id: 'test-chart',
|
id: 'test-chart',
|
||||||
selectedSeries: 'test-series',
|
metricName: 'test-series',
|
||||||
selectedRange: 'day' as const,
|
timeRange: 'day' as const,
|
||||||
beginAtZero: true,
|
yAxisMin: 'zero',
|
||||||
aggregationMode: 'count',
|
aggregationMode: 'count',
|
||||||
selectedLabels: {},
|
labelSelectors: {},
|
||||||
title: 'Test Chart',
|
title: 'Test Chart',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -179,11 +179,11 @@ describe('useImpactMetricsState', () => {
|
|||||||
charts: [
|
charts: [
|
||||||
{
|
{
|
||||||
id: 'new-chart-id',
|
id: 'new-chart-id',
|
||||||
selectedSeries: 'test-series',
|
metricName: 'test-series',
|
||||||
selectedRange: 'day',
|
timeRange: 'day',
|
||||||
beginAtZero: true,
|
yAxisMin: 'zero',
|
||||||
mode: 'count',
|
mode: 'count',
|
||||||
selectedLabels: {},
|
labelSelectors: {},
|
||||||
title: 'Test Chart',
|
title: 'Test Chart',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -212,11 +212,11 @@ describe('useImpactMetricsState', () => {
|
|||||||
charts: [
|
charts: [
|
||||||
{
|
{
|
||||||
id: 'new-chart-id',
|
id: 'new-chart-id',
|
||||||
selectedSeries: 'test-series',
|
metricName: 'test-series',
|
||||||
selectedRange: 'day',
|
timeRange: 'day',
|
||||||
beginAtZero: true,
|
yAxisMin: 'zero',
|
||||||
mode: 'count',
|
mode: 'count',
|
||||||
selectedLabels: {},
|
labelSelectors: {},
|
||||||
title: 'Test Chart',
|
title: 'Test Chart',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
export type ChartConfig = {
|
export type ChartConfig = {
|
||||||
id: string;
|
id: string;
|
||||||
selectedSeries: string; // e.g. unleash_counter_my_metric
|
metricName: string; // e.g. unleash_counter_my_metric
|
||||||
selectedRange: 'hour' | 'day' | 'week' | 'month';
|
timeRange: 'hour' | 'day' | 'week' | 'month';
|
||||||
beginAtZero: boolean;
|
yAxisMin: 'auto' | 'zero';
|
||||||
aggregationMode: AggregationMode;
|
aggregationMode: AggregationMode;
|
||||||
selectedLabels: Record<string, string[]>;
|
labelSelectors: Record<string, string[]>;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export const getTimeUnit = (selectedRange: string) => {
|
export const getTimeUnit = (timeRange: string) => {
|
||||||
switch (selectedRange) {
|
switch (timeRange) {
|
||||||
case 'hour':
|
case 'hour':
|
||||||
return 'minute';
|
return 'minute';
|
||||||
case 'day':
|
case 'day':
|
||||||
@ -13,8 +13,8 @@ export const getTimeUnit = (selectedRange: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDisplayFormat = (selectedRange: string) => {
|
export const getDisplayFormat = (timeRange: string) => {
|
||||||
switch (selectedRange) {
|
switch (timeRange) {
|
||||||
case 'hour':
|
case 'hour':
|
||||||
return 'HH:mm';
|
return 'HH:mm';
|
||||||
case 'day':
|
case 'day':
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
* See `gen:api` script in package.json
|
* See `gen:api` script in package.json
|
||||||
*/
|
*/
|
||||||
import type { CreateImpactMetricsConfigSchemaAggregationMode } from './createImpactMetricsConfigSchemaAggregationMode.js';
|
import type { CreateImpactMetricsConfigSchemaAggregationMode } from './createImpactMetricsConfigSchemaAggregationMode.js';
|
||||||
import type { CreateImpactMetricsConfigSchemaSelectedLabels } from './createImpactMetricsConfigSchemaSelectedLabels.js';
|
import type { CreateImpactMetricsConfigSchemaLabelSelectors } from './createImpactMetricsConfigSchemaLabelSelectors.js';
|
||||||
import type { CreateImpactMetricsConfigSchemaSelectedRange } from './createImpactMetricsConfigSchemaSelectedRange.js';
|
import type { CreateImpactMetricsConfigSchemaTimeRange } from './createImpactMetricsConfigSchemaTimeRange.js';
|
||||||
|
import type { CreateImpactMetricsConfigSchemaYAxisMin } from './createImpactMetricsConfigSchemaYAxisMin.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the configuration for a single impact metric chart.
|
* Describes the configuration for a single impact metric chart.
|
||||||
@ -13,22 +14,24 @@ import type { CreateImpactMetricsConfigSchemaSelectedRange } from './createImpac
|
|||||||
export interface CreateImpactMetricsConfigSchema {
|
export interface CreateImpactMetricsConfigSchema {
|
||||||
/** The aggregation mode for the metric data. */
|
/** The aggregation mode for the metric data. */
|
||||||
aggregationMode: CreateImpactMetricsConfigSchemaAggregationMode;
|
aggregationMode: CreateImpactMetricsConfigSchemaAggregationMode;
|
||||||
/** Whether the chart should begin at zero on the y-axis. */
|
|
||||||
beginAtZero: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Optional feature name that this impact metric is associated with.
|
* Optional feature name that this impact metric is associated with.
|
||||||
* @nullable
|
* @nullable
|
||||||
*/
|
*/
|
||||||
feature?: string | null;
|
feature?: string | null;
|
||||||
|
/** The unique ULID identifier for this impact metric configuration. Generated automatically if not provided. */
|
||||||
|
id?: string;
|
||||||
/** The selected labels and their values for filtering the metric data. */
|
/** The selected labels and their values for filtering the metric data. */
|
||||||
selectedLabels: CreateImpactMetricsConfigSchemaSelectedLabels;
|
labelSelectors: CreateImpactMetricsConfigSchemaLabelSelectors;
|
||||||
/** The time range for the metric data. */
|
|
||||||
selectedRange: CreateImpactMetricsConfigSchemaSelectedRange;
|
|
||||||
/** The Prometheus metric series to display. It includes both unleash prefix and metric type and display name */
|
/** The Prometheus metric series to display. It includes both unleash prefix and metric type and display name */
|
||||||
selectedSeries: string;
|
metricName: string;
|
||||||
|
/** The time range for the metric data. */
|
||||||
|
timeRange: CreateImpactMetricsConfigSchemaTimeRange;
|
||||||
/**
|
/**
|
||||||
* Optional title for the impact metric chart.
|
* Optional title for the impact metric chart.
|
||||||
*/
|
*/
|
||||||
title?: string;
|
title?: string;
|
||||||
|
/** Whether the chart should begin at zero on the y-axis. */
|
||||||
|
yAxisMin: CreateImpactMetricsConfigSchemaYAxisMin;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
/**
|
/**
|
||||||
* The selected labels and their values for filtering the metric data.
|
* The selected labels and their values for filtering the metric data.
|
||||||
*/
|
*/
|
||||||
export type CreateImpactMetricsConfigSchemaSelectedLabels = {
|
export type CreateImpactMetricsConfigSchemaLabelSelectors = {
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
};
|
};
|
@ -7,11 +7,11 @@
|
|||||||
/**
|
/**
|
||||||
* The time range for the metric data.
|
* The time range for the metric data.
|
||||||
*/
|
*/
|
||||||
export type CreateImpactMetricsConfigSchemaSelectedRange =
|
export type CreateImpactMetricsConfigSchemaTimeRange =
|
||||||
(typeof CreateImpactMetricsConfigSchemaSelectedRange)[keyof typeof CreateImpactMetricsConfigSchemaSelectedRange];
|
(typeof CreateImpactMetricsConfigSchemaTimeRange)[keyof typeof CreateImpactMetricsConfigSchemaTimeRange];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
export const CreateImpactMetricsConfigSchemaSelectedRange = {
|
export const CreateImpactMetricsConfigSchemaTimeRange = {
|
||||||
hour: 'hour',
|
hour: 'hour',
|
||||||
day: 'day',
|
day: 'day',
|
||||||
week: 'week',
|
week: 'week',
|
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by Orval
|
||||||
|
* Do not edit manually.
|
||||||
|
* See `gen:api` script in package.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the chart should begin at zero on the y-axis.
|
||||||
|
*/
|
||||||
|
export type CreateImpactMetricsConfigSchemaYAxisMin =
|
||||||
|
(typeof CreateImpactMetricsConfigSchemaYAxisMin)[keyof typeof CreateImpactMetricsConfigSchemaYAxisMin];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const CreateImpactMetricsConfigSchemaYAxisMin = {
|
||||||
|
auto: 'auto',
|
||||||
|
zero: 'zero',
|
||||||
|
} as const;
|
@ -4,9 +4,10 @@
|
|||||||
* See `gen:api` script in package.json
|
* See `gen:api` script in package.json
|
||||||
*/
|
*/
|
||||||
import type { ImpactMetricsConfigSchemaAggregationMode } from './impactMetricsConfigSchemaAggregationMode.js';
|
import type { ImpactMetricsConfigSchemaAggregationMode } from './impactMetricsConfigSchemaAggregationMode.js';
|
||||||
import type { ImpactMetricsConfigSchemaSelectedLabels } from './impactMetricsConfigSchemaSelectedLabels.js';
|
import type { ImpactMetricsConfigSchemaLabelSelectors } from './impactMetricsConfigSchemaLabelSelectors.js';
|
||||||
import type { ImpactMetricsConfigSchemaSelectedRange } from './impactMetricsConfigSchemaSelectedRange.js';
|
import type { ImpactMetricsConfigSchemaTimeRange } from './impactMetricsConfigSchemaTimeRange.js';
|
||||||
import type { ImpactMetricsConfigSchemaType } from './impactMetricsConfigSchemaType.js';
|
import type { ImpactMetricsConfigSchemaType } from './impactMetricsConfigSchemaType.js';
|
||||||
|
import type { ImpactMetricsConfigSchemaYAxisMin } from './impactMetricsConfigSchemaYAxisMin.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the configuration for a single impact metric chart.
|
* Describes the configuration for a single impact metric chart.
|
||||||
@ -14,8 +15,6 @@ import type { ImpactMetricsConfigSchemaType } from './impactMetricsConfigSchemaT
|
|||||||
export interface ImpactMetricsConfigSchema {
|
export interface ImpactMetricsConfigSchema {
|
||||||
/** The aggregation mode for the metric data. */
|
/** The aggregation mode for the metric data. */
|
||||||
aggregationMode: ImpactMetricsConfigSchemaAggregationMode;
|
aggregationMode: ImpactMetricsConfigSchemaAggregationMode;
|
||||||
/** Whether the chart should begin at zero on the y-axis. */
|
|
||||||
beginAtZero: boolean;
|
|
||||||
/** The human readable display name of the impact metric */
|
/** The human readable display name of the impact metric */
|
||||||
displayName: string;
|
displayName: string;
|
||||||
/**
|
/**
|
||||||
@ -26,15 +25,17 @@ export interface ImpactMetricsConfigSchema {
|
|||||||
/** The unique ULID identifier for this impact metric configuration. Generated automatically if not provided. */
|
/** The unique ULID identifier for this impact metric configuration. Generated automatically if not provided. */
|
||||||
id: string;
|
id: string;
|
||||||
/** The selected labels and their values for filtering the metric data. */
|
/** The selected labels and their values for filtering the metric data. */
|
||||||
selectedLabels: ImpactMetricsConfigSchemaSelectedLabels;
|
labelSelectors: ImpactMetricsConfigSchemaLabelSelectors;
|
||||||
/** The time range for the metric data. */
|
|
||||||
selectedRange: ImpactMetricsConfigSchemaSelectedRange;
|
|
||||||
/** The Prometheus metric series to display. It includes both unleash prefix and metric type and display name */
|
/** The Prometheus metric series to display. It includes both unleash prefix and metric type and display name */
|
||||||
selectedSeries: string;
|
metricName: string;
|
||||||
|
/** The time range for the metric data. */
|
||||||
|
timeRange: ImpactMetricsConfigSchemaTimeRange;
|
||||||
/**
|
/**
|
||||||
* Optional title for the impact metric chart.
|
* Optional title for the impact metric chart.
|
||||||
*/
|
*/
|
||||||
title?: string;
|
title?: string;
|
||||||
/** The type of metric */
|
/** The type of metric */
|
||||||
type: ImpactMetricsConfigSchemaType;
|
type: ImpactMetricsConfigSchemaType;
|
||||||
|
/** Whether the chart should begin at zero on the y-axis. */
|
||||||
|
yAxisMin: ImpactMetricsConfigSchemaYAxisMin;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
/**
|
/**
|
||||||
* The selected labels and their values for filtering the metric data.
|
* The selected labels and their values for filtering the metric data.
|
||||||
*/
|
*/
|
||||||
export type ImpactMetricsConfigSchemaSelectedLabels = {
|
export type ImpactMetricsConfigSchemaLabelSelectors = {
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
};
|
};
|
@ -7,11 +7,11 @@
|
|||||||
/**
|
/**
|
||||||
* The time range for the metric data.
|
* The time range for the metric data.
|
||||||
*/
|
*/
|
||||||
export type ImpactMetricsConfigSchemaSelectedRange =
|
export type ImpactMetricsConfigSchemaTimeRange =
|
||||||
(typeof ImpactMetricsConfigSchemaSelectedRange)[keyof typeof ImpactMetricsConfigSchemaSelectedRange];
|
(typeof ImpactMetricsConfigSchemaTimeRange)[keyof typeof ImpactMetricsConfigSchemaTimeRange];
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
export const ImpactMetricsConfigSchemaSelectedRange = {
|
export const ImpactMetricsConfigSchemaTimeRange = {
|
||||||
hour: 'hour',
|
hour: 'hour',
|
||||||
day: 'day',
|
day: 'day',
|
||||||
week: 'week',
|
week: 'week',
|
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Generated by Orval
|
||||||
|
* Do not edit manually.
|
||||||
|
* See `gen:api` script in package.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the chart should begin at zero on the y-axis.
|
||||||
|
*/
|
||||||
|
export type ImpactMetricsConfigSchemaYAxisMin =
|
||||||
|
(typeof ImpactMetricsConfigSchemaYAxisMin)[keyof typeof ImpactMetricsConfigSchemaYAxisMin];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const ImpactMetricsConfigSchemaYAxisMin = {
|
||||||
|
auto: 'auto',
|
||||||
|
zero: 'zero',
|
||||||
|
} as const;
|
@ -428,8 +428,9 @@ export * from './createGroupSchemaUsersItem.js';
|
|||||||
export * from './createGroupSchemaUsersItemUser.js';
|
export * from './createGroupSchemaUsersItemUser.js';
|
||||||
export * from './createImpactMetricsConfigSchema.js';
|
export * from './createImpactMetricsConfigSchema.js';
|
||||||
export * from './createImpactMetricsConfigSchemaAggregationMode.js';
|
export * from './createImpactMetricsConfigSchemaAggregationMode.js';
|
||||||
export * from './createImpactMetricsConfigSchemaSelectedLabels.js';
|
export * from './createImpactMetricsConfigSchemaLabelSelectors.js';
|
||||||
export * from './createImpactMetricsConfigSchemaSelectedRange.js';
|
export * from './createImpactMetricsConfigSchemaTimeRange.js';
|
||||||
|
export * from './createImpactMetricsConfigSchemaYAxisMin.js';
|
||||||
export * from './createInvitedUserSchema.js';
|
export * from './createInvitedUserSchema.js';
|
||||||
export * from './createPat401.js';
|
export * from './createPat401.js';
|
||||||
export * from './createPat403.js';
|
export * from './createPat403.js';
|
||||||
@ -894,9 +895,10 @@ export * from './idsSchema.js';
|
|||||||
export * from './impactMetricsConfigListSchema.js';
|
export * from './impactMetricsConfigListSchema.js';
|
||||||
export * from './impactMetricsConfigSchema.js';
|
export * from './impactMetricsConfigSchema.js';
|
||||||
export * from './impactMetricsConfigSchemaAggregationMode.js';
|
export * from './impactMetricsConfigSchemaAggregationMode.js';
|
||||||
export * from './impactMetricsConfigSchemaSelectedLabels.js';
|
export * from './impactMetricsConfigSchemaLabelSelectors.js';
|
||||||
export * from './impactMetricsConfigSchemaSelectedRange.js';
|
export * from './impactMetricsConfigSchemaTimeRange.js';
|
||||||
export * from './impactMetricsConfigSchemaType.js';
|
export * from './impactMetricsConfigSchemaType.js';
|
||||||
|
export * from './impactMetricsConfigSchemaYAxisMin.js';
|
||||||
export * from './impactMetricsSchema.js';
|
export * from './impactMetricsSchema.js';
|
||||||
export * from './impactMetricsSchemaSamplesItem.js';
|
export * from './impactMetricsSchemaSamplesItem.js';
|
||||||
export * from './impactMetricsSchemaSamplesItemLabels.js';
|
export * from './impactMetricsSchemaSamplesItemLabels.js';
|
||||||
@ -1374,6 +1376,8 @@ export * from './uncomplete401.js';
|
|||||||
export * from './uncomplete403.js';
|
export * from './uncomplete403.js';
|
||||||
export * from './uncomplete404.js';
|
export * from './uncomplete404.js';
|
||||||
export * from './unknownFlagSchema.js';
|
export * from './unknownFlagSchema.js';
|
||||||
|
export * from './unknownFlagSchemaReportsItem.js';
|
||||||
|
export * from './unknownFlagSchemaReportsItemEnvironmentsItem.js';
|
||||||
export * from './unknownFlagsResponseSchema.js';
|
export * from './unknownFlagsResponseSchema.js';
|
||||||
export * from './unsubscribeEmailSubscription401.js';
|
export * from './unsubscribeEmailSubscription401.js';
|
||||||
export * from './unsubscribeEmailSubscription404.js';
|
export * from './unsubscribeEmailSubscription404.js';
|
||||||
|
@ -3,22 +3,21 @@
|
|||||||
* Do not edit manually.
|
* Do not edit manually.
|
||||||
* See `gen:api` script in package.json
|
* See `gen:api` script in package.json
|
||||||
*/
|
*/
|
||||||
|
import type { UnknownFlagSchemaReportsItem } from './unknownFlagSchemaReportsItem.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An unknown flag report
|
* An unknown flag report
|
||||||
*/
|
*/
|
||||||
export interface UnknownFlagSchema {
|
export interface UnknownFlagSchema {
|
||||||
/** The name of the application that reported the unknown flag. */
|
|
||||||
appName: string;
|
|
||||||
/** The environment in which the unknown flag was reported. */
|
|
||||||
environment: string;
|
|
||||||
/**
|
/**
|
||||||
* The date and time when the last event for the unknown flag name occurred, if any.
|
* The date and time when the last event for the unknown flag name occurred, if any.
|
||||||
* @nullable
|
* @nullable
|
||||||
*/
|
*/
|
||||||
lastEventAt?: string | null;
|
lastEventAt?: string | null;
|
||||||
|
/** The date and time when the unknown flag was last reported. */
|
||||||
|
lastSeenAt: string;
|
||||||
/** The name of the unknown flag. */
|
/** The name of the unknown flag. */
|
||||||
name: string;
|
name: string;
|
||||||
/** The date and time when the unknown flag was reported. */
|
/** The list of reports for this unknown flag. */
|
||||||
seenAt: string;
|
reports?: UnknownFlagSchemaReportsItem[];
|
||||||
}
|
}
|
||||||
|
13
frontend/src/openapi/models/unknownFlagSchemaReportsItem.ts
Normal file
13
frontend/src/openapi/models/unknownFlagSchemaReportsItem.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Generated by Orval
|
||||||
|
* Do not edit manually.
|
||||||
|
* See `gen:api` script in package.json
|
||||||
|
*/
|
||||||
|
import type { UnknownFlagSchemaReportsItemEnvironmentsItem } from './unknownFlagSchemaReportsItemEnvironmentsItem.js';
|
||||||
|
|
||||||
|
export type UnknownFlagSchemaReportsItem = {
|
||||||
|
/** The name of the application that reported the unknown flag. */
|
||||||
|
appName: string;
|
||||||
|
/** The list of environments where this application reported the unknown flag. */
|
||||||
|
environments: UnknownFlagSchemaReportsItemEnvironmentsItem[];
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Generated by Orval
|
||||||
|
* Do not edit manually.
|
||||||
|
* See `gen:api` script in package.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type UnknownFlagSchemaReportsItemEnvironmentsItem = {
|
||||||
|
/** The environment in which the unknown flag was reported. */
|
||||||
|
environment: string;
|
||||||
|
/** The date and time when the unknown flag was last seen in this environment. */
|
||||||
|
seenAt: string;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user