mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
feat(slo): Support for calendar aligned time window (#159949)
Resolves https://github.com/elastic/kibana/issues/159948 ## 📝 Summary This PR updates the SLO form to support the calendar aligned time windows for both create and edit flow. I've also moved the budgeting method selector down, so when selecting "timeslices", the timeslices related inputs are shown next to it on the same line. | Screenshot | Screenshot | |--------|--------| |  |  |
This commit is contained in:
parent
27df64c2bc
commit
3a34e3593d
9 changed files with 159 additions and 69 deletions
|
@ -22,6 +22,7 @@ import {
|
|||
summarySchema,
|
||||
tagsSchema,
|
||||
timeWindowSchema,
|
||||
timeWindowTypeSchema,
|
||||
} from '../schema';
|
||||
|
||||
const createSLOParamsSchema = t.type({
|
||||
|
@ -166,6 +167,7 @@ type GetPreviewDataParams = t.TypeOf<typeof getPreviewDataParamsSchema.props.bod
|
|||
type GetPreviewDataResponse = t.TypeOf<typeof getPreviewDataResponseSchema>;
|
||||
|
||||
type BudgetingMethod = t.TypeOf<typeof budgetingMethodSchema>;
|
||||
type TimeWindow = t.TypeOf<typeof timeWindowTypeSchema>;
|
||||
|
||||
type Indicator = t.OutputOf<typeof indicatorSchema>;
|
||||
type MetricCustomIndicator = t.OutputOf<typeof metricCustomIndicatorSchema>;
|
||||
|
@ -211,4 +213,5 @@ export type {
|
|||
Indicator,
|
||||
MetricCustomIndicator,
|
||||
KQLCustomIndicator,
|
||||
TimeWindow,
|
||||
};
|
||||
|
|
|
@ -20,6 +20,10 @@ const calendarAlignedTimeWindowSchema = t.type({
|
|||
type: calendarAlignedTimeWindowTypeSchema,
|
||||
});
|
||||
|
||||
const timeWindowTypeSchema = t.union([
|
||||
rollingTimeWindowTypeSchema,
|
||||
calendarAlignedTimeWindowTypeSchema,
|
||||
]);
|
||||
const timeWindowSchema = t.union([rollingTimeWindowSchema, calendarAlignedTimeWindowSchema]);
|
||||
|
||||
export {
|
||||
|
@ -28,4 +32,5 @@ export {
|
|||
calendarAlignedTimeWindowSchema,
|
||||
calendarAlignedTimeWindowTypeSchema,
|
||||
timeWindowSchema,
|
||||
timeWindowTypeSchema,
|
||||
};
|
||||
|
|
|
@ -272,17 +272,16 @@ export function SloEditForm({ slo }: Props) {
|
|||
})}
|
||||
</EuiButton>
|
||||
|
||||
<EuiButton
|
||||
color="ghost"
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
data-test-subj="sloFormCancelButton"
|
||||
fill
|
||||
disabled={isCreateSloLoading || isUpdateSloLoading}
|
||||
onClick={() => navigateToUrl(basePath.prepend(paths.observability.slos))}
|
||||
>
|
||||
{i18n.translate('xpack.observability.slo.sloEdit.cancelButton', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
EuiFieldNumber,
|
||||
EuiFlexGrid,
|
||||
|
@ -22,13 +22,29 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|||
import type { CreateSLOInput } from '@kbn/slo-schema';
|
||||
|
||||
import { SloEditFormObjectiveSectionTimeslices } from './slo_edit_form_objective_section_timeslices';
|
||||
import { BUDGETING_METHOD_OPTIONS, TIMEWINDOW_OPTIONS } from '../constants';
|
||||
import {
|
||||
BUDGETING_METHOD_OPTIONS,
|
||||
CALENDARALIGNED_TIMEWINDOW_OPTIONS,
|
||||
ROLLING_TIMEWINDOW_OPTIONS,
|
||||
TIMEWINDOW_TYPE_OPTIONS,
|
||||
} from '../constants';
|
||||
import { maxWidth } from './slo_edit_form';
|
||||
|
||||
export function SloEditFormObjectiveSection() {
|
||||
const { control, watch, getFieldState } = useFormContext<CreateSLOInput>();
|
||||
const { control, watch, getFieldState, resetField } = useFormContext<CreateSLOInput>();
|
||||
const budgetingSelect = useGeneratedHtmlId({ prefix: 'budgetingSelect' });
|
||||
const timeWindowTypeSelect = useGeneratedHtmlId({ prefix: 'timeWindowTypeSelect' });
|
||||
const timeWindowSelect = useGeneratedHtmlId({ prefix: 'timeWindowSelect' });
|
||||
const timeWindowType = watch('timeWindow.type');
|
||||
|
||||
useEffect(() => {
|
||||
resetField('timeWindow.duration', {
|
||||
defaultValue:
|
||||
timeWindowType === 'calendarAligned'
|
||||
? CALENDARALIGNED_TIMEWINDOW_OPTIONS[1].value
|
||||
: ROLLING_TIMEWINDOW_OPTIONS[1].value,
|
||||
});
|
||||
}, [timeWindowType, resetField]);
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
|
@ -38,6 +54,88 @@ export function SloEditFormObjectiveSection() {
|
|||
style={{ maxWidth }}
|
||||
data-test-subj="sloEditFormObjectiveSection"
|
||||
>
|
||||
<EuiFlexGrid columns={3}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<span>
|
||||
{i18n.translate('xpack.observability.slo.sloEdit.timeWindowType.label', {
|
||||
defaultMessage: 'Time window',
|
||||
})}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.timeWindowType.tooltip',
|
||||
{
|
||||
defaultMessage: 'Choose between a rolling or a calendar aligned window.',
|
||||
}
|
||||
)}
|
||||
position="top"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="timeWindow.type"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<EuiSelect
|
||||
{...field}
|
||||
required
|
||||
id={timeWindowTypeSelect}
|
||||
data-test-subj="sloFormTimeWindowTypeSelect"
|
||||
options={TIMEWINDOW_TYPE_OPTIONS}
|
||||
value={String(field.value)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<span>
|
||||
{i18n.translate('xpack.observability.slo.sloEdit.timeWindowDuration.label', {
|
||||
defaultMessage: 'Duration',
|
||||
})}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate(
|
||||
'xpack.observability.slo.sloEdit.timeWindowDuration.tooltip',
|
||||
{
|
||||
defaultMessage: 'The time window duration used to compute the SLO over.',
|
||||
}
|
||||
)}
|
||||
position="top"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="timeWindow.duration"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
rules={{ required: true }}
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<EuiSelect
|
||||
{...field}
|
||||
required
|
||||
id={timeWindowSelect}
|
||||
data-test-subj="sloFormTimeWindowDurationSelect"
|
||||
options={
|
||||
timeWindowType === 'calendarAligned'
|
||||
? CALENDARALIGNED_TIMEWINDOW_OPTIONS
|
||||
: ROLLING_TIMEWINDOW_OPTIONS
|
||||
}
|
||||
value={String(field.value)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGrid columns={3}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
|
@ -76,41 +174,14 @@ export function SloEditFormObjectiveSection() {
|
|||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<span>
|
||||
{i18n.translate('xpack.observability.slo.sloEdit.timeWindow.label', {
|
||||
defaultMessage: 'Time window',
|
||||
})}{' '}
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.observability.slo.sloEdit.timeWindow.tooltip', {
|
||||
defaultMessage:
|
||||
'The rolling time window duration used to compute the SLO over.',
|
||||
})}
|
||||
position="top"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="timeWindow.duration"
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<EuiSelect
|
||||
{...field}
|
||||
required
|
||||
id={timeWindowSelect}
|
||||
data-test-subj="sloFormTimeWindowDurationSelect"
|
||||
options={TIMEWINDOW_OPTIONS}
|
||||
value={String(field.value)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{watch('budgetingMethod') === 'timeslices' ? (
|
||||
<SloEditFormObjectiveSectionTimeslices />
|
||||
) : null}
|
||||
</EuiFlexGrid>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EuiFlexGrid columns={3}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={getFieldState('objective.target').invalid}
|
||||
|
@ -153,13 +224,6 @@ export function SloEditFormObjectiveSection() {
|
|||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
|
||||
{watch('budgetingMethod') === 'timeslices' ? (
|
||||
<>
|
||||
<EuiSpacer size="xl" />
|
||||
<SloEditFormObjectiveSectionTimeslices />
|
||||
</>
|
||||
) : null}
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,17 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFieldNumber, EuiFlexGrid, EuiFlexItem, EuiFormRow, EuiIconTip } from '@elastic/eui';
|
||||
import { EuiFieldNumber, EuiFlexItem, EuiFormRow, EuiIconTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import type { CreateSLOInput } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
export function SloEditFormObjectiveSectionTimeslices() {
|
||||
const { control, getFieldState } = useFormContext<CreateSLOInput>();
|
||||
|
||||
return (
|
||||
<EuiFlexGrid columns={3}>
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={getFieldState('objective.timesliceTarget').invalid}
|
||||
|
@ -98,6 +98,6 @@ export function SloEditFormObjectiveSectionTimeslices() {
|
|||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BudgetingMethod, CreateSLOInput } from '@kbn/slo-schema';
|
||||
import { BudgetingMethod, CreateSLOInput, TimeWindow } from '@kbn/slo-schema';
|
||||
import {
|
||||
BUDGETING_METHOD_OCCURRENCES,
|
||||
BUDGETING_METHOD_TIMESLICES,
|
||||
|
@ -49,9 +49,39 @@ export const BUDGETING_METHOD_OPTIONS: Array<{ value: BudgetingMethod; text: str
|
|||
},
|
||||
];
|
||||
|
||||
export const TIMEWINDOW_OPTIONS = [90, 30, 7].map((number) => ({
|
||||
export const TIMEWINDOW_TYPE_OPTIONS: Array<{ value: TimeWindow; text: string }> = [
|
||||
{
|
||||
value: 'rolling',
|
||||
text: i18n.translate('xpack.observability.slo.sloEdit.timeWindow.rolling', {
|
||||
defaultMessage: 'Rolling',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'calendarAligned',
|
||||
text: i18n.translate('xpack.observability.slo.sloEdit.timeWindow.calendarAligned', {
|
||||
defaultMessage: 'Calendar aligned',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const CALENDARALIGNED_TIMEWINDOW_OPTIONS = [
|
||||
{
|
||||
value: '1w',
|
||||
text: i18n.translate('xpack.observability.slo.sloEdit.calendarTimeWindow.weekly', {
|
||||
defaultMessage: 'Weekly',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: '1M',
|
||||
text: i18n.translate('xpack.observability.slo.sloEdit.calendarTimeWindow.monthly', {
|
||||
defaultMessage: 'Monthly',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const ROLLING_TIMEWINDOW_OPTIONS = [90, 30, 7].map((number) => ({
|
||||
value: `${number}d`,
|
||||
text: i18n.translate('xpack.observability.slo.sloEdit.timeWindow.days', {
|
||||
text: i18n.translate('xpack.observability.slo.sloEdit.rollingTimeWindow.days', {
|
||||
defaultMessage: '{number} days',
|
||||
values: { number },
|
||||
}),
|
||||
|
@ -71,8 +101,7 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES: CreateSLOInput = {
|
|||
},
|
||||
},
|
||||
timeWindow: {
|
||||
duration:
|
||||
TIMEWINDOW_OPTIONS[TIMEWINDOW_OPTIONS.findIndex((option) => option.value === '30d')].value,
|
||||
duration: ROLLING_TIMEWINDOW_OPTIONS[1].value,
|
||||
type: 'rolling',
|
||||
},
|
||||
tags: [],
|
||||
|
@ -96,8 +125,7 @@ export const SLO_EDIT_FORM_DEFAULT_VALUES_CUSTOM_METRIC: CreateSLOInput = {
|
|||
},
|
||||
},
|
||||
timeWindow: {
|
||||
duration:
|
||||
TIMEWINDOW_OPTIONS[TIMEWINDOW_OPTIONS.findIndex((option) => option.value === '30d')].value,
|
||||
duration: ROLLING_TIMEWINDOW_OPTIONS[1].value,
|
||||
type: 'rolling',
|
||||
},
|
||||
tags: [],
|
||||
|
|
|
@ -26635,7 +26635,6 @@
|
|||
"xpack.observability.slo.sloDetails.overview.observedValueSubtitle": "{value} (l'objectif est {objective})",
|
||||
"xpack.observability.slo.sloDetails.overview.rollingTimeWindow": "{duration} en cours",
|
||||
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.duration": "Dernière(s) {duration}",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.days": "{number} jours",
|
||||
"xpack.observability.transactionRateLabel": "{value} tpm",
|
||||
"xpack.observability.ux.coreVitals.averageMessage": " et inférieur à {bad}",
|
||||
"xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd} %)",
|
||||
|
@ -26998,8 +26997,6 @@
|
|||
"xpack.observability.slo.sloEdit.timeSliceTarget.tooltip": "La cible d'intervalle de temps individuel utilisée pour déterminer si l'intervalle est bon ou mauvais.",
|
||||
"xpack.observability.slo.sloEdit.timesliceWindow.label": "Fenêtre d'intervalle de temps (en minutes)",
|
||||
"xpack.observability.slo.sloEdit.timesliceWindow.tooltip": "La taille de la fenêtre d'intervalle de temps utilisée pour évaluer les données.",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.label": "Fenêtre temporelle",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.tooltip": "La durée de la fenêtre temporelle glissante utilisée pour calculer le SLO.",
|
||||
"xpack.observability.slo.sloList.pageHeader.createNewButtonLabel": "Créer un nouveau SLO",
|
||||
"xpack.observability.slo.sloList.welcomePrompt.buttonLabel": "Créer un SLO",
|
||||
"xpack.observability.slo.sloList.welcomePrompt.getStartedMessage": "Pour commencer, créez votre premier SLO.",
|
||||
|
|
|
@ -26617,7 +26617,6 @@
|
|||
"xpack.observability.slo.sloDetails.overview.observedValueSubtitle": "{objective}(目的は{value})",
|
||||
"xpack.observability.slo.sloDetails.overview.rollingTimeWindow": "{duration}ローリング",
|
||||
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.duration": "過去{duration}",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.days": "{number}日",
|
||||
"xpack.observability.transactionRateLabel": "{value} tpm",
|
||||
"xpack.observability.ux.coreVitals.averageMessage": " {bad}未満",
|
||||
"xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd}%)",
|
||||
|
@ -26980,8 +26979,6 @@
|
|||
"xpack.observability.slo.sloEdit.timeSliceTarget.tooltip": "スライスが良好か問題があるかどうかを判断するために使用される、個別のタイムスライス目標。",
|
||||
"xpack.observability.slo.sloEdit.timesliceWindow.label": "タイムスライス期間(分)",
|
||||
"xpack.observability.slo.sloEdit.timesliceWindow.tooltip": "データを評価するために使用されるタイムスライス期間サイズ。",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.label": "時間枠",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.tooltip": "SLOを計算するために使用されるローリング時間枠期間。",
|
||||
"xpack.observability.slo.sloList.pageHeader.createNewButtonLabel": "新規SLOを作成",
|
||||
"xpack.observability.slo.sloList.welcomePrompt.buttonLabel": "SLOの作成",
|
||||
"xpack.observability.slo.sloList.welcomePrompt.getStartedMessage": "開始するには、まずSLOを作成します。",
|
||||
|
|
|
@ -26615,7 +26615,6 @@
|
|||
"xpack.observability.slo.sloDetails.overview.observedValueSubtitle": "{value}(目标为 {objective})",
|
||||
"xpack.observability.slo.sloDetails.overview.rollingTimeWindow": "{duration} 滚动",
|
||||
"xpack.observability.slo.sloDetails.sliHistoryChartPanel.duration": "过去 {duration}",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.days": "{number} 天",
|
||||
"xpack.observability.transactionRateLabel": "{value} tpm",
|
||||
"xpack.observability.ux.coreVitals.averageMessage": " 且小于 {bad}",
|
||||
"xpack.observability.ux.coreVitals.paletteLegend.rankPercentage": "{labelsInd} ({ranksInd}%)",
|
||||
|
@ -26978,8 +26977,6 @@
|
|||
"xpack.observability.slo.sloEdit.timeSliceTarget.tooltip": "用于确定切片是良好还是不良的单个时间片目标。",
|
||||
"xpack.observability.slo.sloEdit.timesliceWindow.label": "时间片窗口(分钟)",
|
||||
"xpack.observability.slo.sloEdit.timesliceWindow.tooltip": "用于评估接收的数据的时间片窗口大小。",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.label": "时间窗口",
|
||||
"xpack.observability.slo.sloEdit.timeWindow.tooltip": "用于在其间计算 SLO 的滚动时间窗口持续时间。",
|
||||
"xpack.observability.slo.sloList.pageHeader.createNewButtonLabel": "创建新 SLO",
|
||||
"xpack.observability.slo.sloList.welcomePrompt.buttonLabel": "创建 SLO",
|
||||
"xpack.observability.slo.sloList.welcomePrompt.getStartedMessage": "要开始使用,请创建您的首个 SLO。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue