Implemented ability to clear and properly validate alert interval (#60571) (#60778)

* Implemented ability to clear and properly validate alert interval

* Fixed due to comments

* Fixed additional request for the last field

* Fixed failing test
This commit is contained in:
Yuliia Naumenko 2020-03-20 12:00:28 -07:00 committed by GitHub
parent e7a4d57f93
commit 788b889cf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 25 deletions

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { parseDuration } from './parse_duration'; import { parseDuration, getDurationNumberInItsUnit, getDurationUnitValue } from './parse_duration';
test('parses seconds', () => { test('parses seconds', () => {
const result = parseDuration('10s'); const result = parseDuration('10s');
@ -52,3 +52,33 @@ test('throws error when 0 based', () => {
`"Invalid duration \\"0d\\". Durations must be of the form {number}x. Example: 5s, 5m, 5h or 5d\\""` `"Invalid duration \\"0d\\". Durations must be of the form {number}x. Example: 5s, 5m, 5h or 5d\\""`
); );
}); });
test('getDurationNumberInItsUnit days', () => {
const result = getDurationNumberInItsUnit('10d');
expect(result).toEqual(10);
});
test('getDurationNumberInItsUnit minutes', () => {
const result = getDurationNumberInItsUnit('1m');
expect(result).toEqual(1);
});
test('getDurationNumberInItsUnit seconds', () => {
const result = getDurationNumberInItsUnit('123s');
expect(result).toEqual(123);
});
test('getDurationUnitValue minutes', () => {
const result = getDurationUnitValue('1m');
expect(result).toEqual('m');
});
test('getDurationUnitValue days', () => {
const result = getDurationUnitValue('23d');
expect(result).toEqual('d');
});
test('getDurationUnitValue hours', () => {
const result = getDurationUnitValue('100h');
expect(result).toEqual('h');
});

View file

@ -25,6 +25,15 @@ export function parseDuration(duration: string): number {
); );
} }
export function getDurationNumberInItsUnit(duration: string): number {
return parseInt(duration.replace(/[^0-9.]/g, ''), 0);
}
export function getDurationUnitValue(duration: string): string {
const durationNumber = getDurationNumberInItsUnit(duration);
return duration.replace(durationNumber.toString(), '');
}
export function validateDurationSchema(duration: string) { export function validateDurationSchema(duration: string) {
if (duration.match(SECONDS_REGEX)) { if (duration.match(SECONDS_REGEX)) {
return; return;

View file

@ -399,8 +399,8 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<IndexThr
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<ForLastExpression <ForLastExpression
popupPosition={'upLeft'} popupPosition={'upLeft'}
timeWindowSize={timeWindowSize || 1} timeWindowSize={timeWindowSize}
timeWindowUnit={timeWindowUnit || ''} timeWindowUnit={timeWindowUnit}
errors={errors} errors={errors}
onChangeWindowSize={(selectedWindowSize: any) => onChangeWindowSize={(selectedWindowSize: any) =>
setAlertParams('timeWindowSize', selectedWindowSize) setAlertParams('timeWindowSize', selectedWindowSize)

View file

@ -86,7 +86,7 @@ describe('alert_form', () => {
uiSettings: deps!.uiSettings, uiSettings: deps!.uiSettings,
}} }}
> >
<AlertForm alert={initialAlert} dispatch={() => {}} errors={{ name: [] }} /> <AlertForm alert={initialAlert} dispatch={() => {}} errors={{ name: [], interval: [] }} />
</AlertsContextProvider> </AlertsContextProvider>
); );
@ -165,7 +165,7 @@ describe('alert_form', () => {
uiSettings: deps!.uiSettings, uiSettings: deps!.uiSettings,
}} }}
> >
<AlertForm alert={initialAlert} dispatch={() => {}} errors={{ name: [] }} /> <AlertForm alert={initialAlert} dispatch={() => {}} errors={{ name: [], interval: [] }} />
</AlertsContextProvider> </AlertsContextProvider>
); );

View file

@ -24,6 +24,10 @@ import {
EuiButtonIcon, EuiButtonIcon,
EuiHorizontalRule, EuiHorizontalRule,
} from '@elastic/eui'; } from '@elastic/eui';
import {
getDurationNumberInItsUnit,
getDurationUnitValue,
} from '../../../../../alerting/common/parse_duration';
import { loadAlertTypes } from '../../lib/alert_api'; import { loadAlertTypes } from '../../lib/alert_api';
import { actionVariablesFromAlertType } from '../../lib/action_variables'; import { actionVariablesFromAlertType } from '../../lib/action_variables';
import { AlertReducerAction } from './alert_reducer'; import { AlertReducerAction } from './alert_reducer';
@ -48,7 +52,7 @@ export function validateBaseProperties(alertObject: Alert) {
}) })
); );
} }
if (!alertObject.schedule.interval) { if (alertObject.schedule.interval.length < 2) {
errors.interval.push( errors.interval.push(
i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredIntervalText', { i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredIntervalText', {
defaultMessage: 'Check interval is required.', defaultMessage: 'Check interval is required.',
@ -88,17 +92,17 @@ export const AlertForm = ({
); );
const [alertTypesIndex, setAlertTypesIndex] = useState<AlertTypeIndex | undefined>(undefined); const [alertTypesIndex, setAlertTypesIndex] = useState<AlertTypeIndex | undefined>(undefined);
const [alertInterval, setAlertInterval] = useState<number>( const [alertInterval, setAlertInterval] = useState<number | undefined>(
alert.schedule.interval ? parseInt(alert.schedule.interval.replace(/^[A-Za-z]+$/, ''), 0) : 1 alert.schedule.interval ? getDurationNumberInItsUnit(alert.schedule.interval) : undefined
); );
const [alertIntervalUnit, setAlertIntervalUnit] = useState<string>( const [alertIntervalUnit, setAlertIntervalUnit] = useState<string>(
alert.schedule.interval ? alert.schedule.interval.replace(alertInterval.toString(), '') : 'm' alert.schedule.interval ? getDurationUnitValue(alert.schedule.interval) : 'm'
); );
const [alertThrottle, setAlertThrottle] = useState<number | null>( const [alertThrottle, setAlertThrottle] = useState<number | null>(
alert.throttle ? parseInt(alert.throttle.replace(/^[A-Za-z]+$/, ''), 0) : null alert.throttle ? getDurationNumberInItsUnit(alert.throttle) : null
); );
const [alertThrottleUnit, setAlertThrottleUnit] = useState<string>( const [alertThrottleUnit, setAlertThrottleUnit] = useState<string>(
alert.throttle ? alert.throttle.replace((alertThrottle ?? '').toString(), '') : 'm' alert.throttle ? getDurationUnitValue(alert.throttle) : 'm'
); );
const [defaultActionGroupId, setDefaultActionGroupId] = useState<string | undefined>(undefined); const [defaultActionGroupId, setDefaultActionGroupId] = useState<string | undefined>(undefined);
@ -352,19 +356,27 @@ export const AlertForm = ({
<EuiSpacer size="m" /> <EuiSpacer size="m" />
<EuiFlexGrid columns={2}> <EuiFlexGrid columns={2}>
<EuiFlexItem> <EuiFlexItem>
<EuiFormRow fullWidth compressed label={labelForAlertChecked}> <EuiFormRow
fullWidth
compressed
label={labelForAlertChecked}
isInvalid={errors.interval.length > 0}
error={errors.interval}
>
<EuiFlexGroup gutterSize="s"> <EuiFlexGroup gutterSize="s">
<EuiFlexItem> <EuiFlexItem>
<EuiFieldNumber <EuiFieldNumber
fullWidth fullWidth
min={1} min={1}
isInvalid={errors.interval.length > 0}
compressed compressed
value={alertInterval} value={alertInterval || ''}
name="interval" name="interval"
data-test-subj="intervalInput" data-test-subj="intervalInput"
onChange={e => { onChange={e => {
const interval = e.target.value !== '' ? parseInt(e.target.value, 10) : null; const interval =
setAlertInterval(interval ?? 1); e.target.value !== '' ? parseInt(e.target.value, 10) : undefined;
setAlertInterval(interval);
setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`);
}} }}
/> />
@ -374,7 +386,7 @@ export const AlertForm = ({
fullWidth fullWidth
compressed compressed
value={alertIntervalUnit} value={alertIntervalUnit}
options={getTimeOptions(alertInterval)} options={getTimeOptions(alertInterval ?? 1)}
onChange={e => { onChange={e => {
setAlertIntervalUnit(e.target.value); setAlertIntervalUnit(e.target.value);
setScheduleProperty('interval', `${alertInterval}${e.target.value}`); setScheduleProperty('interval', `${alertInterval}${e.target.value}`);

View file

@ -36,7 +36,7 @@ describe('for the last expression', () => {
/> />
); );
wrapper.simulate('click'); wrapper.simulate('click');
expect(wrapper.find('[value=1]').length > 0).toBeTruthy(); expect(wrapper.find('[value=""]').length > 0).toBeTruthy();
expect(wrapper.find('[value="s"]').length > 0).toBeTruthy(); expect(wrapper.find('[value="s"]').length > 0).toBeTruthy();
expect( expect(
wrapper.contains( wrapper.contains(

View file

@ -25,7 +25,7 @@ interface ForLastExpressionProps {
timeWindowSize?: number; timeWindowSize?: number;
timeWindowUnit?: string; timeWindowUnit?: string;
errors: { [key: string]: string[] }; errors: { [key: string]: string[] };
onChangeWindowSize: (selectedWindowSize: number | '') => void; onChangeWindowSize: (selectedWindowSize: number | undefined) => void;
onChangeWindowUnit: (selectedWindowUnit: string) => void; onChangeWindowUnit: (selectedWindowUnit: string) => void;
popupPosition?: popupPosition?:
| 'upCenter' | 'upCenter'
@ -43,7 +43,7 @@ interface ForLastExpressionProps {
} }
export const ForLastExpression = ({ export const ForLastExpression = ({
timeWindowSize = 1, timeWindowSize,
timeWindowUnit = 's', timeWindowUnit = 's',
errors, errors,
onChangeWindowSize, onChangeWindowSize,
@ -64,7 +64,7 @@ export const ForLastExpression = ({
)} )}
value={`${timeWindowSize} ${getTimeUnitLabel( value={`${timeWindowSize} ${getTimeUnitLabel(
timeWindowUnit as TIME_UNITS, timeWindowUnit as TIME_UNITS,
timeWindowSize.toString() (timeWindowSize ?? '').toString()
)}`} )}`}
isActive={alertDurationPopoverOpen} isActive={alertDurationPopoverOpen}
onClick={() => { onClick={() => {
@ -97,11 +97,11 @@ export const ForLastExpression = ({
<EuiFieldNumber <EuiFieldNumber
data-test-subj="timeWindowSizeNumber" data-test-subj="timeWindowSizeNumber"
isInvalid={errors.timeWindowSize.length > 0 && timeWindowSize !== undefined} isInvalid={errors.timeWindowSize.length > 0 && timeWindowSize !== undefined}
min={1} min={0}
value={timeWindowSize} value={timeWindowSize || ''}
onChange={e => { onChange={e => {
const { value } = e.target; const { value } = e.target;
const timeWindowSizeVal = value !== '' ? parseInt(value, 10) : value; const timeWindowSizeVal = value !== '' ? parseInt(value, 10) : undefined;
onChangeWindowSize(timeWindowSizeVal); onChangeWindowSize(timeWindowSizeVal);
}} }}
/> />
@ -114,7 +114,7 @@ export const ForLastExpression = ({
onChange={e => { onChange={e => {
onChangeWindowUnit(e.target.value); onChangeWindowUnit(e.target.value);
}} }}
options={getTimeOptions(timeWindowSize)} options={getTimeOptions(timeWindowSize ?? 1)}
/> />
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>

View file

@ -63,7 +63,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await fieldOptions[1].click(); await fieldOptions[1].click();
// need this two out of popup clicks to close them // need this two out of popup clicks to close them
await nameInput.click(); await nameInput.click();
await testSubjects.click('intervalInput');
await testSubjects.click('.slack-ActionTypeSelectOption'); await testSubjects.click('.slack-ActionTypeSelectOption');
await testSubjects.click('createActionConnectorButton'); await testSubjects.click('createActionConnectorButton');