Skip to content

Commit

Permalink
[Logs UI] Validate ML job setup time ranges (elastic#66426)
Browse files Browse the repository at this point in the history
This adds validation for the time range selection in the log rate and categories setup screens.
  • Loading branch information
weltenwort committed May 14, 2020
1 parent ef11719 commit 4f4fe39
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { useCallback } from 'react';
import { LoadingOverlayWrapper } from '../../../loading_overlay_wrapper';
import { IndexSetupRow } from './index_setup_row';
import { AvailableIndex } from './validation';
import { AvailableIndex, ValidationIndicesError } from './validation';

export const AnalysisSetupIndicesForm: React.FunctionComponent<{
disabled?: boolean;
indices: AvailableIndex[];
isValidating: boolean;
onChangeSelectedIndices: (selectedIndices: AvailableIndex[]) => void;
valid: boolean;
}> = ({ disabled = false, indices, isValidating, onChangeSelectedIndices, valid }) => {
validationErrors?: ValidationIndicesError[];
}> = ({
disabled = false,
indices,
isValidating,
onChangeSelectedIndices,
validationErrors = [],
}) => {
const changeIsIndexSelected = useCallback(
(indexName: string, isSelected: boolean) => {
onChangeSelectedIndices(
Expand All @@ -41,6 +47,8 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{
[indices, onChangeSelectedIndices]
);

const isInvalid = validationErrors.length > 0;

return (
<EuiDescribedFormGroup
title={
Expand All @@ -59,7 +67,12 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{
}
>
<LoadingOverlayWrapper isLoading={isValidating}>
<EuiFormRow fullWidth isInvalid={!valid} label={indicesSelectionLabel} labelType="legend">
<EuiFormRow
fullWidth
isInvalid={isInvalid}
label={indicesSelectionLabel}
labelType="legend"
>
<>
{indices.map(index => (
<IndexSetupRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import moment, { Moment } from 'moment';
import React, { useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import { FixedDatePicker } from '../../../fixed_datepicker';
import { TimeRangeValidationError } from './validation';

const startTimeLabel = i18n.translate('xpack.infra.analysisSetup.startTimeLabel', {
defaultMessage: 'Start time',
Expand Down Expand Up @@ -48,15 +49,35 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
setEndTime: (endTime: number | undefined) => void;
startTime: number | undefined;
endTime: number | undefined;
}> = ({ disabled = false, setStartTime, setEndTime, startTime, endTime }) => {
const now = useMemo(() => moment(), []);
validationErrors?: TimeRangeValidationError[];
}> = ({
disabled = false,
setStartTime,
setEndTime,
startTime,
endTime,
validationErrors = [],
}) => {
const [now] = useState(() => moment());
const selectedEndTimeIsToday = !endTime || moment(endTime).isSame(now, 'day');

const startTimeValue = useMemo(() => {
return startTime ? moment(startTime) : undefined;
}, [startTime]);
const endTimeValue = useMemo(() => {
return endTime ? moment(endTime) : undefined;
}, [endTime]);

const startTimeValidationErrorMessages = useMemo(
() => getStartTimeValidationErrorMessages(validationErrors),
[validationErrors]
);

const endTimeValidationErrorMessages = useMemo(
() => getEndTimeValidationErrorMessages(validationErrors),
[validationErrors]
);

return (
<EuiDescribedFormGroup
title={
Expand All @@ -74,7 +95,12 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
/>
}
>
<EuiFormRow error={false} fullWidth isInvalid={false} label={startTimeLabel}>
<EuiFormRow
error={startTimeValidationErrorMessages}
fullWidth
isInvalid={startTimeValidationErrorMessages.length > 0}
label={startTimeLabel}
>
<EuiFlexGroup gutterSize="s">
<EuiFormControlLayout
clear={startTime && !disabled ? { onClick: () => setStartTime(undefined) } : undefined}
Expand All @@ -91,7 +117,12 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
</EuiFormControlLayout>
</EuiFlexGroup>
</EuiFormRow>
<EuiFormRow error={false} fullWidth isInvalid={false} label={endTimeLabel}>
<EuiFormRow
error={endTimeValidationErrorMessages}
fullWidth
isInvalid={endTimeValidationErrorMessages.length > 0}
label={endTimeLabel}
>
<EuiFlexGroup gutterSize="s">
<EuiFormControlLayout
clear={endTime && !disabled ? { onClick: () => setEndTime(undefined) } : undefined}
Expand Down Expand Up @@ -122,3 +153,31 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
</EuiDescribedFormGroup>
);
};

const getStartTimeValidationErrorMessages = (validationErrors: TimeRangeValidationError[]) =>
validationErrors.flatMap(validationError => {
switch (validationError.error) {
case 'INVALID_TIME_RANGE':
return [
i18n.translate('xpack.infra.analysisSetup.startTimeBeforeEndTimeErrorMessage', {
defaultMessage: 'The start time must be before the end time.',
}),
];
default:
return [];
}
});

const getEndTimeValidationErrorMessages = (validationErrors: TimeRangeValidationError[]) =>
validationErrors.flatMap(validationError => {
switch (validationError.error) {
case 'INVALID_TIME_RANGE':
return [
i18n.translate('xpack.infra.analysisSetup.endTimeAfterStartTimeErrorMessage', {
defaultMessage: 'The end time must be after the start time.',
}),
];
default:
return [];
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { useCallback } from 'react';
import { DatasetFilter } from '../../../../../common/log_analysis';
import { IndexSetupDatasetFilter } from './index_setup_dataset_filter';
import { AvailableIndex, ValidationIndicesUIError } from './validation';
import { AvailableIndex, ValidationUIError } from './validation';

export const IndexSetupRow: React.FC<{
index: AvailableIndex;
Expand Down Expand Up @@ -61,7 +61,7 @@ export const IndexSetupRow: React.FC<{
);
};

const formatValidationError = (errors: ValidationIndicesUIError[]): React.ReactNode => {
const formatValidationError = (errors: ValidationUIError[]): React.ReactNode => {
return errors.map(error => {
switch (error.error) {
case 'INDEX_NOT_FOUND':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiSpacer, EuiForm, EuiCallOut } from '@elastic/eui';
import { EuiCallOut, EuiForm, EuiSpacer } from '@elastic/eui';
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';

import { SetupStatus } from '../../../../../common/log_analysis';
import { AnalysisSetupIndicesForm } from './analysis_setup_indices_form';
import { AnalysisSetupTimerangeForm } from './analysis_setup_timerange_form';
import { AvailableIndex, ValidationIndicesUIError } from './validation';
import {
AvailableIndex,
TimeRangeValidationError,
timeRangeValidationErrorRT,
ValidationIndicesError,
validationIndicesErrorRT,
ValidationUIError,
} from './validation';

interface InitialConfigurationStepProps {
setStartTime: (startTime: number | undefined) => void;
Expand All @@ -24,7 +30,7 @@ interface InitialConfigurationStepProps {
validatedIndices: AvailableIndex[];
setupStatus: SetupStatus;
setValidatedIndices: (selectedIndices: AvailableIndex[]) => void;
validationErrors?: ValidationIndicesUIError[];
validationErrors?: ValidationUIError[];
}

export const createInitialConfigurationStep = (
Expand All @@ -47,6 +53,11 @@ export const InitialConfigurationStep: React.FunctionComponent<InitialConfigurat
}: InitialConfigurationStepProps) => {
const disabled = useMemo(() => !editableFormStatus.includes(setupStatus.type), [setupStatus]);

const [indexValidationErrors, timeRangeValidationErrors, globalValidationErrors] = useMemo(
() => partitionValidationErrors(validationErrors),
[validationErrors]
);

return (
<>
<EuiSpacer size="m" />
Expand All @@ -57,16 +68,17 @@ export const InitialConfigurationStep: React.FunctionComponent<InitialConfigurat
setEndTime={setEndTime}
startTime={startTime}
endTime={endTime}
validationErrors={timeRangeValidationErrors}
/>
<AnalysisSetupIndicesForm
disabled={disabled}
indices={validatedIndices}
isValidating={isValidating}
onChangeSelectedIndices={setValidatedIndices}
valid={validationErrors.length === 0}
validationErrors={indexValidationErrors}
/>

<ValidationErrors errors={validationErrors} />
<ValidationErrors errors={globalValidationErrors} />
</EuiForm>
</>
);
Expand All @@ -88,7 +100,7 @@ const initialConfigurationStepTitle = i18n.translate(
}
);

const ValidationErrors: React.FC<{ errors: ValidationIndicesUIError[] }> = ({ errors }) => {
const ValidationErrors: React.FC<{ errors: ValidationUIError[] }> = ({ errors }) => {
if (errors.length === 0) {
return null;
}
Expand All @@ -107,7 +119,7 @@ const ValidationErrors: React.FC<{ errors: ValidationIndicesUIError[] }> = ({ er
);
};

const formatValidationError = (error: ValidationIndicesUIError): React.ReactNode => {
const formatValidationError = (error: ValidationUIError): React.ReactNode => {
switch (error.error) {
case 'NETWORK_ERROR':
return (
Expand All @@ -129,3 +141,19 @@ const formatValidationError = (error: ValidationIndicesUIError): React.ReactNode
return '';
}
};

const partitionValidationErrors = (validationErrors: ValidationUIError[]) =>
validationErrors.reduce<
[ValidationIndicesError[], TimeRangeValidationError[], ValidationUIError[]]
>(
([indicesErrors, timeRangeErrors, otherErrors], error) => {
if (validationIndicesErrorRT.is(error)) {
return [[...indicesErrors, error], timeRangeErrors, otherErrors];
} else if (timeRangeValidationErrorRT.is(error)) {
return [indicesErrors, [...timeRangeErrors, error], otherErrors];
} else {
return [indicesErrors, timeRangeErrors, [...otherErrors, error]];
}
},
[[], [], []]
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ValidationIndicesError } from '../../../../../common/http_api';
import * as rt from 'io-ts';
import { ValidationIndicesError, validationIndicesErrorRT } from '../../../../../common/http_api';
import { DatasetFilter } from '../../../../../common/log_analysis';

export { ValidationIndicesError };
export { ValidationIndicesError, validationIndicesErrorRT };

export type ValidationIndicesUIError =
export const timeRangeValidationErrorRT = rt.strict({
error: rt.literal('INVALID_TIME_RANGE'),
});

export type TimeRangeValidationError = rt.TypeOf<typeof timeRangeValidationErrorRT>;

export type ValidationUIError =
| ValidationIndicesError
| { error: 'NETWORK_ERROR' }
| { error: 'TOO_FEW_SELECTED_INDICES' };
| { error: 'TOO_FEW_SELECTED_INDICES' }
| TimeRangeValidationError;

interface ValidAvailableIndex {
validity: 'valid';
Expand Down
Loading

0 comments on commit 4f4fe39

Please sign in to comment.