-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(slo): burn rate rule editor (#147211)
- Loading branch information
Showing
22 changed files
with
466 additions
and
115 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { screen } from '@testing-library/dom'; | ||
import userEvent from '@testing-library/user-event'; | ||
import React from 'react'; | ||
|
||
import { render } from '../../../utils/test_helper'; | ||
import { BurnRate } from './burn_rate'; | ||
|
||
describe('BurnRate', () => { | ||
it('shows error when entered burn rate exceed max burn rate', () => { | ||
render(<BurnRate onChange={() => {}} maxBurnRate={20} />); | ||
|
||
userEvent.type(screen.getByTestId('burnRate'), '1441', { delay: 0 }); | ||
|
||
expect(screen.getByText(/cannot exceed/i)).toBeTruthy(); | ||
}); | ||
}); |
58 changes: 58 additions & 0 deletions
58
x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/burn_rate.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
import React, { ChangeEvent, useEffect, useState } from 'react'; | ||
|
||
interface Props { | ||
maxBurnRate: number; | ||
onChange: (burnRate: number) => void; | ||
} | ||
|
||
export function BurnRate({ onChange, maxBurnRate }: Props) { | ||
const [burnRate, setBurnRate] = useState<number>(1); | ||
const [burnRateError, setBurnRateError] = useState<string | undefined>(undefined); | ||
|
||
useEffect(() => { | ||
if (burnRate > maxBurnRate) { | ||
setBurnRateError(getErrorText(maxBurnRate)); | ||
} else { | ||
setBurnRateError(undefined); | ||
} | ||
}, [burnRate, maxBurnRate]); | ||
|
||
const onBurnRateChange = (event: ChangeEvent<HTMLInputElement>) => { | ||
const value = Number(event.target.value); | ||
setBurnRate(value); | ||
onChange(value); | ||
}; | ||
|
||
return ( | ||
<EuiFormRow label={rowLabel} fullWidth isInvalid={!!burnRateError} error={burnRateError}> | ||
<EuiFieldNumber | ||
fullWidth | ||
step={0.1} | ||
min={1} | ||
max={maxBurnRate} | ||
value={String(burnRate)} | ||
onChange={(event) => onBurnRateChange(event)} | ||
data-test-subj="burnRate" | ||
/> | ||
</EuiFormRow> | ||
); | ||
} | ||
|
||
const rowLabel = i18n.translate('xpack.observability.slo.rules.burnRate.rowLabel', { | ||
defaultMessage: 'Burn rate threshold', | ||
}); | ||
|
||
const getErrorText = (maxBurnRate: number) => | ||
i18n.translate('xpack.observability.slo.rules.burnRate.errorText', { | ||
defaultMessage: 'Burn rate cannot exceed {maxBurnRate}', | ||
values: { maxBurnRate }, | ||
}); |
24 changes: 24 additions & 0 deletions
24
...servability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import React from 'react'; | ||
import { ComponentStory } from '@storybook/react'; | ||
|
||
import { BurnRateRuleEditor as Component } from './burn_rate_rule_editor'; | ||
|
||
export default { | ||
component: Component, | ||
title: 'app/SLO/BurnRateRule', | ||
argTypes: {}, | ||
}; | ||
|
||
const Template: ComponentStory<typeof Component> = () => <Component />; | ||
|
||
const defaultProps = {}; | ||
|
||
export const BurnRateRuleEditor = Template.bind({}); | ||
BurnRateRuleEditor.args = defaultProps; |
93 changes: 93 additions & 0 deletions
93
...ugins/observability/public/components/app/burn_rate_rule_editor/burn_rate_rule_editor.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer } from '@elastic/eui'; | ||
import { assertNever } from '@kbn/std'; | ||
import React, { useEffect, useState } from 'react'; | ||
|
||
import { Duration, SLO } from '../../../typings'; | ||
import { SloSelector } from '../../shared/slo/slo_selector/slo_selector'; | ||
import { BurnRate } from './burn_rate'; | ||
import { LongWindowDuration } from './long_window_duration'; | ||
|
||
export function BurnRateRuleEditor() { | ||
const [selectedSlo, setSelectedSlo] = useState<SLO | undefined>(undefined); | ||
const [longWindowDuration, setLongWindowDuration] = useState<Duration>({ value: 1, unit: 'h' }); | ||
const [, setShortWindowDuration] = useState<Duration>({ value: 5, unit: 'm' }); | ||
const [, setBurnRate] = useState<number>(1); | ||
const [maxBurnRate, setMaxBurnRate] = useState<number>(1); | ||
|
||
const onLongWindowDurationChange = (duration: Duration) => { | ||
setLongWindowDuration(duration); | ||
const longWindowdurationInMinutes = toMinutes(duration); | ||
const shortWindowDurationValue = Math.floor(longWindowdurationInMinutes / 12); | ||
setShortWindowDuration({ value: shortWindowDurationValue, unit: 'm' }); | ||
}; | ||
|
||
const onBurnRateChange = (value: number) => { | ||
setBurnRate(value); | ||
}; | ||
|
||
const onSelectedSlo = (slo: SLO | undefined) => { | ||
setSelectedSlo(slo); | ||
}; | ||
|
||
useEffect(() => { | ||
if (selectedSlo) { | ||
const sloDurationInMinutes = toMinutes(selectedSlo.timeWindow.duration); | ||
const longWindowDurationInMinutes = toMinutes(longWindowDuration); | ||
setMaxBurnRate(Math.floor(sloDurationInMinutes / longWindowDurationInMinutes)); | ||
} else { | ||
setMaxBurnRate(1); | ||
} | ||
}, [longWindowDuration, selectedSlo]); | ||
|
||
return ( | ||
<EuiFlexGroup direction="column"> | ||
<EuiFlexGroup direction="row"> | ||
<EuiFlexItem> | ||
<EuiFormRow label="Select SLO" fullWidth> | ||
<SloSelector onSelected={onSelectedSlo} /> | ||
</EuiFormRow> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
|
||
<EuiFlexGroup direction="row"> | ||
<EuiFlexItem> | ||
<LongWindowDuration | ||
initialDuration={longWindowDuration} | ||
onChange={onLongWindowDurationChange} | ||
/> | ||
</EuiFlexItem> | ||
<EuiFlexItem> | ||
<BurnRate maxBurnRate={maxBurnRate} onChange={onBurnRateChange} /> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
|
||
<EuiSpacer size="m" /> | ||
</EuiFlexGroup> | ||
); | ||
} | ||
|
||
function toMinutes(duration: Duration) { | ||
switch (duration.unit) { | ||
case 'm': | ||
return duration.value; | ||
case 'h': | ||
return duration.value * 60; | ||
case 'd': | ||
return duration.value * 24 * 60; | ||
case 'w': | ||
return duration.value * 7 * 24 * 60; | ||
case 'M': | ||
return duration.value * 30 * 24 * 60; | ||
case 'Y': | ||
return duration.value * 365 * 24 * 60; | ||
} | ||
|
||
assertNever(duration.unit); | ||
} |
9 changes: 9 additions & 0 deletions
9
x-pack/plugins/observability/public/components/app/burn_rate_rule_editor/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
// eslint-disable-next-line import/no-default-export | ||
export { BurnRateRuleEditor as default } from './burn_rate_rule_editor'; |
45 changes: 45 additions & 0 deletions
45
...s/observability/public/components/app/burn_rate_rule_editor/long_window_duration.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { screen } from '@testing-library/dom'; | ||
import userEvent from '@testing-library/user-event'; | ||
import React from 'react'; | ||
|
||
import { render } from '../../../utils/test_helper'; | ||
import { LongWindowDuration } from './long_window_duration'; | ||
|
||
describe('LongWindowDuration', () => { | ||
it('shows error when duration is greater than 1440minutes', () => { | ||
render(<LongWindowDuration onChange={() => {}} />); | ||
|
||
userEvent.selectOptions(screen.getByTestId('durationUnitSelect'), 'm'); | ||
userEvent.clear(screen.getByTestId('durationValueInput')); | ||
userEvent.type(screen.getByTestId('durationValueInput'), '1441', { delay: 0 }); | ||
|
||
expect(screen.getByText(/cannot exceed/i)).toBeTruthy(); | ||
}); | ||
|
||
it('shows error when duration is greater than 24 hours', () => { | ||
render(<LongWindowDuration onChange={() => {}} />); | ||
|
||
userEvent.selectOptions(screen.getByTestId('durationUnitSelect'), 'h'); | ||
userEvent.clear(screen.getByTestId('durationValueInput')); | ||
userEvent.type(screen.getByTestId('durationValueInput'), '25', { delay: 0 }); | ||
|
||
expect(screen.getByText(/cannot exceed/i)).toBeTruthy(); | ||
}); | ||
|
||
it('shows error when duration is lower than 30 minutes', async () => { | ||
render(<LongWindowDuration onChange={() => {}} />); | ||
|
||
userEvent.selectOptions(screen.getByTestId('durationUnitSelect'), ['m']); | ||
userEvent.clear(screen.getByTestId('durationValueInput')); | ||
userEvent.type(screen.getByTestId('durationValueInput'), '29'); | ||
|
||
expect(screen.getByText(/cannot exceed/i)).toBeTruthy(); | ||
}); | ||
}); |
Oops, something went wrong.