Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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