mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Logs UI] Provide index name pattern choice during ML job setup (#48231)
This fixes #48219 by adding the option for the user to select a subset of the configured log indices during the setup process. It also surfaces the errors returned by Kibana when the setup fails.
This commit is contained in:
parent
ed382bca33
commit
603e27edb1
22 changed files with 527 additions and 294 deletions
|
@ -54,3 +54,8 @@ export const isSetupStatusWithResults = (setupStatus: SetupStatus) =>
|
|||
['skipped', 'hiddenAfterSuccess', 'skippedButReconfigurable', 'skippedButUpdatable'].includes(
|
||||
setupStatus
|
||||
);
|
||||
|
||||
const KIBANA_SAMPLE_DATA_INDICES = ['kibana_sample_data_logs*'];
|
||||
|
||||
export const isExampleDataIndex = (indexName: string) =>
|
||||
KIBANA_SAMPLE_DATA_INDICES.includes(indexName);
|
||||
|
|
|
@ -11,6 +11,11 @@ export type Pick3<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyo
|
|||
[P1 in K1]: { [P2 in K2]: { [P3 in K3]: ((T[K1])[K2])[P3] } };
|
||||
};
|
||||
|
||||
export type MandatoryProperty<T, Prop extends keyof T> = T &
|
||||
{
|
||||
[prop in Prop]-?: NonNullable<T[Prop]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Portions of below code are derived from https://github.com/tycho01/typical
|
||||
* under the MIT License
|
||||
|
|
|
@ -92,22 +92,34 @@ const setupMlModuleRequestPayloadRT = rt.intersection([
|
|||
setupMlModuleRequestParamsRT,
|
||||
]);
|
||||
|
||||
const setupErrorResponseRT = rt.type({
|
||||
msg: rt.string,
|
||||
});
|
||||
|
||||
const datafeedSetupResponseRT = rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
started: rt.boolean,
|
||||
success: rt.boolean,
|
||||
}),
|
||||
rt.partial({
|
||||
error: setupErrorResponseRT,
|
||||
}),
|
||||
]);
|
||||
|
||||
const jobSetupResponseRT = rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
success: rt.boolean,
|
||||
}),
|
||||
rt.partial({
|
||||
error: setupErrorResponseRT,
|
||||
}),
|
||||
]);
|
||||
|
||||
const setupMlModuleResponsePayloadRT = rt.type({
|
||||
datafeeds: rt.array(
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
started: rt.boolean,
|
||||
success: rt.boolean,
|
||||
error: rt.any,
|
||||
})
|
||||
),
|
||||
jobs: rt.array(
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
success: rt.boolean,
|
||||
error: rt.any,
|
||||
})
|
||||
),
|
||||
datafeeds: rt.array(datafeedSetupResponseRT),
|
||||
jobs: rt.array(jobSetupResponseRT),
|
||||
});
|
||||
|
||||
export type SetupMlModuleResponsePayload = rt.TypeOf<typeof setupMlModuleResponsePayloadRT>;
|
||||
|
|
|
@ -16,7 +16,6 @@ import { useLogAnalysisCleanup } from './log_analysis_cleanup';
|
|||
import { useStatusState } from './log_analysis_status_state';
|
||||
|
||||
const MODULE_ID = 'logs_ui_analysis';
|
||||
const SAMPLE_DATA_INDEX = 'kibana_sample_data_logs*';
|
||||
|
||||
export const useLogAnalysisJobs = ({
|
||||
indexPattern,
|
||||
|
@ -29,11 +28,10 @@ export const useLogAnalysisJobs = ({
|
|||
spaceId: string;
|
||||
timeField: string;
|
||||
}) => {
|
||||
const filteredIndexPattern = useMemo(() => removeSampleDataIndex(indexPattern), [indexPattern]);
|
||||
const { cleanupMLResources } = useLogAnalysisCleanup({ sourceId, spaceId });
|
||||
const [statusState, dispatch] = useStatusState({
|
||||
bucketSpan,
|
||||
indexPattern: filteredIndexPattern,
|
||||
indexPattern,
|
||||
timestampField: timeField,
|
||||
});
|
||||
|
||||
|
@ -62,7 +60,11 @@ export const useLogAnalysisJobs = ({
|
|||
const [setupMlModuleRequest, setupMlModule] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async (start, end) => {
|
||||
createPromise: async (
|
||||
indices: string[],
|
||||
start: number | undefined,
|
||||
end: number | undefined
|
||||
) => {
|
||||
dispatch({ type: 'startedSetup' });
|
||||
return await callSetupMlModuleAPI(
|
||||
MODULE_ID,
|
||||
|
@ -70,7 +72,7 @@ export const useLogAnalysisJobs = ({
|
|||
end,
|
||||
spaceId,
|
||||
sourceId,
|
||||
filteredIndexPattern,
|
||||
indices.join(','),
|
||||
timeField,
|
||||
bucketSpan
|
||||
);
|
||||
|
@ -82,7 +84,7 @@ export const useLogAnalysisJobs = ({
|
|||
dispatch({ type: 'failedSetup' });
|
||||
},
|
||||
},
|
||||
[filteredIndexPattern, spaceId, sourceId, timeField, bucketSpan]
|
||||
[spaceId, sourceId, timeField, bucketSpan]
|
||||
);
|
||||
|
||||
const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise(
|
||||
|
@ -99,7 +101,7 @@ export const useLogAnalysisJobs = ({
|
|||
dispatch({ type: 'failedFetchingJobStatuses' });
|
||||
},
|
||||
},
|
||||
[filteredIndexPattern, spaceId, sourceId]
|
||||
[spaceId, sourceId]
|
||||
);
|
||||
|
||||
const isLoadingSetupStatus = useMemo(
|
||||
|
@ -108,16 +110,18 @@ export const useLogAnalysisJobs = ({
|
|||
[fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state]
|
||||
);
|
||||
|
||||
const availableIndices = useMemo(() => indexPattern.split(','), [indexPattern]);
|
||||
|
||||
const viewResults = useCallback(() => {
|
||||
dispatch({ type: 'viewedResults' });
|
||||
}, []);
|
||||
|
||||
const cleanupAndSetup = useCallback(
|
||||
(start, end) => {
|
||||
(indices: string[], start: number | undefined, end: number | undefined) => {
|
||||
dispatch({ type: 'startedSetup' });
|
||||
cleanupMLResources()
|
||||
.then(() => {
|
||||
setupMlModule(start, end);
|
||||
setupMlModule(indices, start, end);
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({ type: 'failedSetup' });
|
||||
|
@ -145,9 +149,11 @@ export const useLogAnalysisJobs = ({
|
|||
}, [sourceId, spaceId]);
|
||||
|
||||
return {
|
||||
availableIndices,
|
||||
fetchJobStatus,
|
||||
isLoadingSetupStatus,
|
||||
jobStatus: statusState.jobStatus,
|
||||
lastSetupErrorMessages: statusState.lastSetupErrorMessages,
|
||||
cleanupAndSetup,
|
||||
setup: setupMlModule,
|
||||
setupMlModuleRequest,
|
||||
|
@ -160,11 +166,3 @@ export const useLogAnalysisJobs = ({
|
|||
};
|
||||
|
||||
export const LogAnalysisJobs = createContainer(useLogAnalysisJobs);
|
||||
//
|
||||
// This is needed due to: https://github.com/elastic/kibana/issues/43671
|
||||
const removeSampleDataIndex = (indexPattern: string) => {
|
||||
return indexPattern
|
||||
.split(',')
|
||||
.filter(index => index !== SAMPLE_DATA_INDEX)
|
||||
.join(',');
|
||||
};
|
||||
|
|
|
@ -3,35 +3,81 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void;
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
|
||||
interface Props {
|
||||
import { isExampleDataIndex } from '../../../../common/log_analysis';
|
||||
|
||||
type SetupHandler = (
|
||||
indices: string[],
|
||||
startTime: number | undefined,
|
||||
endTime: number | undefined
|
||||
) => void;
|
||||
|
||||
interface AnalysisSetupStateArguments {
|
||||
availableIndices: string[];
|
||||
cleanupAndSetupModule: SetupHandler;
|
||||
setupModule: SetupHandler;
|
||||
}
|
||||
|
||||
type IndicesSelection = Record<string, boolean>;
|
||||
|
||||
type ValidationErrors = 'TOO_FEW_SELECTED_INDICES';
|
||||
|
||||
const fourWeeksInMs = 86400000 * 7 * 4;
|
||||
|
||||
export const useAnalysisSetupState = ({ setupModule, cleanupAndSetupModule }: Props) => {
|
||||
export const useAnalysisSetupState = ({
|
||||
availableIndices,
|
||||
cleanupAndSetupModule,
|
||||
setupModule,
|
||||
}: AnalysisSetupStateArguments) => {
|
||||
const [startTime, setStartTime] = useState<number | undefined>(Date.now() - fourWeeksInMs);
|
||||
const [endTime, setEndTime] = useState<number | undefined>(undefined);
|
||||
|
||||
const [selectedIndices, setSelectedIndices] = useState<IndicesSelection>(
|
||||
availableIndices.reduce(
|
||||
(indexMap, indexName) => ({
|
||||
...indexMap,
|
||||
[indexName]: !(availableIndices.length > 1 && isExampleDataIndex(indexName)),
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
const selectedIndexNames = useMemo(
|
||||
() =>
|
||||
Object.entries(selectedIndices)
|
||||
.filter(([_indexName, isSelected]) => isSelected)
|
||||
.map(([indexName]) => indexName),
|
||||
[selectedIndices]
|
||||
);
|
||||
|
||||
const setup = useCallback(() => {
|
||||
return setupModule(startTime, endTime);
|
||||
}, [setupModule, startTime, endTime]);
|
||||
return setupModule(selectedIndexNames, startTime, endTime);
|
||||
}, [setupModule, selectedIndexNames, startTime, endTime]);
|
||||
|
||||
const cleanupAndSetup = useCallback(() => {
|
||||
return cleanupAndSetupModule(startTime, endTime);
|
||||
}, [cleanupAndSetupModule, startTime, endTime]);
|
||||
return cleanupAndSetupModule(selectedIndexNames, startTime, endTime);
|
||||
}, [cleanupAndSetupModule, selectedIndexNames, startTime, endTime]);
|
||||
|
||||
const validationErrors: ValidationErrors[] = useMemo(
|
||||
() =>
|
||||
Object.values(selectedIndices).some(isSelected => isSelected)
|
||||
? []
|
||||
: ['TOO_FEW_SELECTED_INDICES' as const],
|
||||
[selectedIndices]
|
||||
);
|
||||
|
||||
return {
|
||||
cleanupAndSetup,
|
||||
endTime,
|
||||
selectedIndexNames,
|
||||
selectedIndices,
|
||||
setEndTime,
|
||||
setSelectedIndices,
|
||||
setStartTime,
|
||||
setup,
|
||||
startTime,
|
||||
validationErrors,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -18,11 +18,13 @@ import {
|
|||
import { FetchJobStatusResponsePayload, JobSummary } from './api/ml_get_jobs_summary_api';
|
||||
import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module';
|
||||
import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api';
|
||||
import { MandatoryProperty } from '../../../../common/utility_types';
|
||||
|
||||
interface StatusReducerState {
|
||||
jobDefinitions: JobDefinition[];
|
||||
jobStatus: Record<JobType, JobStatus>;
|
||||
jobSummaries: JobSummary[];
|
||||
lastSetupErrorMessages: string[];
|
||||
setupStatus: SetupStatus;
|
||||
sourceConfiguration: JobSourceConfiguration;
|
||||
}
|
||||
|
@ -69,6 +71,7 @@ const createInitialState = (sourceConfiguration: JobSourceConfiguration): Status
|
|||
'log-entry-rate': 'unknown',
|
||||
},
|
||||
jobSummaries: [],
|
||||
lastSetupErrorMessages: [],
|
||||
setupStatus: 'initializing',
|
||||
sourceConfiguration,
|
||||
});
|
||||
|
@ -101,9 +104,18 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction):
|
|||
)
|
||||
? 'succeeded'
|
||||
: 'failed';
|
||||
const nextErrorMessages = [
|
||||
...Object.values(datafeeds)
|
||||
.filter(hasError)
|
||||
.map(datafeed => datafeed.error.msg),
|
||||
...Object.values(jobs)
|
||||
.filter(hasError)
|
||||
.map(job => job.error.msg),
|
||||
];
|
||||
return {
|
||||
...state,
|
||||
jobStatus: nextJobStatus,
|
||||
lastSetupErrorMessages: nextErrorMessages,
|
||||
setupStatus: nextSetupStatus,
|
||||
};
|
||||
}
|
||||
|
@ -348,11 +360,22 @@ const isJobConfigurationConsistent = (
|
|||
return (
|
||||
jobConfiguration &&
|
||||
jobConfiguration.bucketSpan === sourceConfiguration.bucketSpan &&
|
||||
jobConfiguration.indexPattern === sourceConfiguration.indexPattern &&
|
||||
jobConfiguration.indexPattern &&
|
||||
isIndexPatternSubset(jobConfiguration.indexPattern, sourceConfiguration.indexPattern) &&
|
||||
jobConfiguration.timestampField === sourceConfiguration.timestampField
|
||||
);
|
||||
});
|
||||
|
||||
const isIndexPatternSubset = (indexPatternSubset: string, indexPatternSuperset: string) => {
|
||||
const subsetSubPatterns = indexPatternSubset.split(',');
|
||||
const supersetSubPatterns = new Set(indexPatternSuperset.split(','));
|
||||
|
||||
return subsetSubPatterns.every(subPattern => supersetSubPatterns.has(subPattern));
|
||||
};
|
||||
|
||||
const hasError = <Value extends any>(value: Value): value is MandatoryProperty<Value, 'error'> =>
|
||||
value.error != null;
|
||||
|
||||
export const useStatusState = (sourceConfiguration: JobSourceConfiguration) => {
|
||||
return useReducer(statusReducer, sourceConfiguration, createInitialState);
|
||||
};
|
||||
|
|
|
@ -17,12 +17,18 @@ import { AnalysisUnavailableContent } from './page_unavailable_content';
|
|||
import { AnalysisSetupStatusUnknownContent } from './page_setup_status_unknown';
|
||||
|
||||
export const AnalysisPageContent = () => {
|
||||
const { sourceId, source } = useContext(Source.Context);
|
||||
const { sourceId } = useContext(Source.Context);
|
||||
const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context);
|
||||
|
||||
const { setup, cleanupAndSetup, setupStatus, viewResults, fetchJobStatus } = useContext(
|
||||
LogAnalysisJobs.Context
|
||||
);
|
||||
const {
|
||||
availableIndices,
|
||||
cleanupAndSetup,
|
||||
fetchJobStatus,
|
||||
lastSetupErrorMessages,
|
||||
setup,
|
||||
setupStatus,
|
||||
viewResults,
|
||||
} = useContext(LogAnalysisJobs.Context);
|
||||
|
||||
useEffect(() => {
|
||||
fetchJobStatus();
|
||||
|
@ -50,10 +56,11 @@ export const AnalysisPageContent = () => {
|
|||
} else {
|
||||
return (
|
||||
<AnalysisSetupContent
|
||||
setup={setup}
|
||||
availableIndices={availableIndices}
|
||||
cleanupAndSetup={cleanupAndSetup}
|
||||
errorMessages={lastSetupErrorMessages}
|
||||
setup={setup}
|
||||
setupStatus={setupStatus}
|
||||
indexPattern={source ? source.configuration.logAlias : ''}
|
||||
viewResults={viewResults}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -20,21 +20,27 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import euiStyled from '../../../../../../common/eui_styled_components';
|
||||
import { SetupStatus } from '../../../../common/log_analysis';
|
||||
import { useTrackPageview } from '../../../hooks/use_track_metric';
|
||||
import { AnalysisSetupSteps } from './setup/steps';
|
||||
import { AnalysisSetupSteps } from './setup';
|
||||
|
||||
type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void;
|
||||
type SetupHandler = (
|
||||
indices: string[],
|
||||
startTime: number | undefined,
|
||||
endTime: number | undefined
|
||||
) => void;
|
||||
|
||||
interface AnalysisSetupContentProps {
|
||||
availableIndices: string[];
|
||||
cleanupAndSetup: SetupHandler;
|
||||
indexPattern: string;
|
||||
errorMessages: string[];
|
||||
setup: SetupHandler;
|
||||
setupStatus: SetupStatus;
|
||||
viewResults: () => void;
|
||||
}
|
||||
|
||||
export const AnalysisSetupContent: React.FunctionComponent<AnalysisSetupContentProps> = ({
|
||||
availableIndices,
|
||||
cleanupAndSetup,
|
||||
indexPattern,
|
||||
errorMessages,
|
||||
setup,
|
||||
setupStatus,
|
||||
viewResults,
|
||||
|
@ -71,11 +77,12 @@ export const AnalysisSetupContent: React.FunctionComponent<AnalysisSetupContentP
|
|||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<AnalysisSetupSteps
|
||||
setup={setup}
|
||||
availableIndices={availableIndices}
|
||||
cleanupAndSetup={cleanupAndSetup}
|
||||
viewResults={viewResults}
|
||||
indexPattern={indexPattern}
|
||||
errorMessages={errorMessages}
|
||||
setup={setup}
|
||||
setupStatus={setupStatus}
|
||||
viewResults={viewResults}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
</AnalysisPageContent>
|
||||
|
@ -86,7 +93,7 @@ export const AnalysisSetupContent: React.FunctionComponent<AnalysisSetupContentP
|
|||
|
||||
// !important due to https://github.com/elastic/eui/issues/2232
|
||||
const AnalysisPageContent = euiStyled(EuiPageContent)`
|
||||
max-width: 518px !important;
|
||||
max-width: 768px !important;
|
||||
`;
|
||||
|
||||
const AnalysisSetupPage = euiStyled(EuiPage)`
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import moment, { Moment } from 'moment';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiForm,
|
||||
EuiDescribedFormGroup,
|
||||
EuiFormRow,
|
||||
EuiDatePicker,
|
||||
EuiFlexGroup,
|
||||
EuiFormControlLayout,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
const startTimeLabel = i18n.translate('xpack.infra.analysisSetup.startTimeLabel', {
|
||||
defaultMessage: 'Start time',
|
||||
});
|
||||
const endTimeLabel = i18n.translate('xpack.infra.analysisSetup.endTimeLabel', {
|
||||
defaultMessage: 'End time',
|
||||
});
|
||||
const startTimeDefaultDescription = i18n.translate(
|
||||
'xpack.infra.analysisSetup.startTimeDefaultDescription',
|
||||
{
|
||||
defaultMessage: 'Start of log indices',
|
||||
}
|
||||
);
|
||||
const endTimeDefaultDescription = i18n.translate(
|
||||
'xpack.infra.analysisSetup.endTimeDefaultDescription',
|
||||
{
|
||||
defaultMessage: 'Indefinitely',
|
||||
}
|
||||
);
|
||||
|
||||
function selectedDateToParam(selectedDate: Moment | null) {
|
||||
if (selectedDate) {
|
||||
return selectedDate.valueOf(); // To ms unix timestamp
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
|
||||
setStartTime: (startTime: number | undefined) => void;
|
||||
setEndTime: (endTime: number | undefined) => void;
|
||||
startTime: number | undefined;
|
||||
endTime: number | undefined;
|
||||
}> = ({ setStartTime, setEndTime, startTime, endTime }) => {
|
||||
const now = useMemo(() => 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]);
|
||||
return (
|
||||
<EuiForm>
|
||||
<EuiDescribedFormGroup
|
||||
idAria="timeRange"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.timeRangeTitle"
|
||||
defaultMessage="Choose a time range"
|
||||
/>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.timeRangeDescription"
|
||||
defaultMessage="By default, Machine Learning analyzes log messages in your log indices no older than four weeks, and continues indefinitely. You can specify a different date to begin, to end, or both."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={['timeRange']}
|
||||
error={false}
|
||||
fullWidth
|
||||
isInvalid={false}
|
||||
label={startTimeLabel}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFormControlLayout
|
||||
clear={startTime ? { onClick: () => setStartTime(undefined) } : undefined}
|
||||
>
|
||||
<EuiDatePicker
|
||||
showTimeSelect
|
||||
selected={startTimeValue}
|
||||
onChange={date => setStartTime(selectedDateToParam(date))}
|
||||
placeholder={startTimeDefaultDescription}
|
||||
maxDate={now}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={['timeRange']}
|
||||
error={false}
|
||||
fullWidth
|
||||
isInvalid={false}
|
||||
label={endTimeLabel}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFormControlLayout
|
||||
clear={endTime ? { onClick: () => setEndTime(undefined) } : undefined}
|
||||
>
|
||||
<EuiDatePicker
|
||||
showTimeSelect
|
||||
selected={endTimeValue}
|
||||
onChange={date => setEndTime(selectedDateToParam(date))}
|
||||
placeholder={endTimeDefaultDescription}
|
||||
openToDate={now}
|
||||
minDate={startTimeValue}
|
||||
minTime={
|
||||
selectedEndTimeIsToday
|
||||
? now
|
||||
: moment()
|
||||
.hour(0)
|
||||
.minutes(0)
|
||||
}
|
||||
maxTime={moment()
|
||||
.hour(23)
|
||||
.minutes(59)}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './setup_steps';
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiCheckboxGroup, EuiCode, EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
export type IndicesSelection = Record<string, boolean>;
|
||||
|
||||
export type IndicesValidationError = 'TOO_FEW_SELECTED_INDICES';
|
||||
|
||||
export const AnalysisSetupIndicesForm: React.FunctionComponent<{
|
||||
indices: IndicesSelection;
|
||||
onChangeSelectedIndices: (selectedIndices: IndicesSelection) => void;
|
||||
validationErrors?: IndicesValidationError[];
|
||||
}> = ({ indices, onChangeSelectedIndices, validationErrors = [] }) => {
|
||||
const choices = useMemo(
|
||||
() =>
|
||||
Object.keys(indices).map(indexName => ({
|
||||
id: indexName,
|
||||
label: <EuiCode>{indexName}</EuiCode>,
|
||||
})),
|
||||
[indices]
|
||||
);
|
||||
|
||||
const handleCheckboxGroupChange = useCallback(
|
||||
indexName => {
|
||||
onChangeSelectedIndices({
|
||||
...indices,
|
||||
[indexName]: !indices[indexName],
|
||||
});
|
||||
},
|
||||
[indices, onChangeSelectedIndices]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
idAria="indices"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.indicesSelectionTitle"
|
||||
defaultMessage="Choose indices"
|
||||
/>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.indicesSelectionDescription"
|
||||
defaultMessage="By default, Machine Learning analyzes log messages in all log indices configured for the source. You can choose to only analyze a subset of the index names. Every selected index name must match at least one index with log entries."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={['indices']}
|
||||
error={validationErrors.map(formatValidationError)}
|
||||
fullWidth
|
||||
isInvalid={validationErrors.length > 0}
|
||||
label={indicesSelectionLabel}
|
||||
labelType="legend"
|
||||
>
|
||||
<EuiCheckboxGroup
|
||||
options={choices}
|
||||
idToSelectedMap={indices}
|
||||
onChange={handleCheckboxGroupChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const indicesSelectionLabel = i18n.translate('xpack.infra.analysisSetup.indicesSelectionLabel', {
|
||||
defaultMessage: 'Indices',
|
||||
});
|
||||
|
||||
const formatValidationError = (validationError: IndicesValidationError) => {
|
||||
switch (validationError) {
|
||||
case 'TOO_FEW_SELECTED_INDICES':
|
||||
return i18n.translate(
|
||||
'xpack.infra.analysisSetup.indicesSelectionTooFewSelectedIndicesDescription',
|
||||
{
|
||||
defaultMessage: 'Select at least one index name.',
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import moment, { Moment } from 'moment';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiDescribedFormGroup,
|
||||
EuiFormRow,
|
||||
EuiDatePicker,
|
||||
EuiFlexGroup,
|
||||
EuiFormControlLayout,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
const startTimeLabel = i18n.translate('xpack.infra.analysisSetup.startTimeLabel', {
|
||||
defaultMessage: 'Start time',
|
||||
});
|
||||
const endTimeLabel = i18n.translate('xpack.infra.analysisSetup.endTimeLabel', {
|
||||
defaultMessage: 'End time',
|
||||
});
|
||||
const startTimeDefaultDescription = i18n.translate(
|
||||
'xpack.infra.analysisSetup.startTimeDefaultDescription',
|
||||
{
|
||||
defaultMessage: 'Start of log indices',
|
||||
}
|
||||
);
|
||||
const endTimeDefaultDescription = i18n.translate(
|
||||
'xpack.infra.analysisSetup.endTimeDefaultDescription',
|
||||
{
|
||||
defaultMessage: 'Indefinitely',
|
||||
}
|
||||
);
|
||||
|
||||
function selectedDateToParam(selectedDate: Moment | null) {
|
||||
if (selectedDate) {
|
||||
return selectedDate.valueOf(); // To ms unix timestamp
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
|
||||
setStartTime: (startTime: number | undefined) => void;
|
||||
setEndTime: (endTime: number | undefined) => void;
|
||||
startTime: number | undefined;
|
||||
endTime: number | undefined;
|
||||
}> = ({ setStartTime, setEndTime, startTime, endTime }) => {
|
||||
const now = useMemo(() => 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]);
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
idAria="timeRange"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.timeRangeTitle"
|
||||
defaultMessage="Choose a time range"
|
||||
/>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.timeRangeDescription"
|
||||
defaultMessage="By default, Machine Learning analyzes log messages in your log indices no older than four weeks, and continues indefinitely. You can specify a different date to begin, to end, or both."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={['timeRange']}
|
||||
error={false}
|
||||
fullWidth
|
||||
isInvalid={false}
|
||||
label={startTimeLabel}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFormControlLayout
|
||||
clear={startTime ? { onClick: () => setStartTime(undefined) } : undefined}
|
||||
>
|
||||
<EuiDatePicker
|
||||
showTimeSelect
|
||||
selected={startTimeValue}
|
||||
onChange={date => setStartTime(selectedDateToParam(date))}
|
||||
placeholder={startTimeDefaultDescription}
|
||||
maxDate={now}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
describedByIds={['timeRange']}
|
||||
error={false}
|
||||
fullWidth
|
||||
isInvalid={false}
|
||||
label={endTimeLabel}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFormControlLayout
|
||||
clear={endTime ? { onClick: () => setEndTime(undefined) } : undefined}
|
||||
>
|
||||
<EuiDatePicker
|
||||
showTimeSelect
|
||||
selected={endTimeValue}
|
||||
onChange={date => setEndTime(selectedDateToParam(date))}
|
||||
placeholder={endTimeDefaultDescription}
|
||||
openToDate={now}
|
||||
minDate={startTimeValue}
|
||||
minTime={
|
||||
selectedEndTimeIsToday
|
||||
? now
|
||||
: moment()
|
||||
.hour(0)
|
||||
.minutes(0)
|
||||
}
|
||||
maxTime={moment()
|
||||
.hour(23)
|
||||
.minutes(59)}
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './initial_configuration_step';
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiSpacer, EuiForm } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
AnalysisSetupIndicesForm,
|
||||
IndicesSelection,
|
||||
IndicesValidationError,
|
||||
} from './analysis_setup_indices_form';
|
||||
import { AnalysisSetupTimerangeForm } from './analysis_setup_timerange_form';
|
||||
|
||||
interface InitialConfigurationStepProps {
|
||||
setStartTime: (startTime: number | undefined) => void;
|
||||
setEndTime: (endTime: number | undefined) => void;
|
||||
startTime: number | undefined;
|
||||
endTime: number | undefined;
|
||||
selectedIndices: IndicesSelection;
|
||||
setSelectedIndices: (selectedIndices: IndicesSelection) => void;
|
||||
validationErrors?: IndicesValidationError[];
|
||||
}
|
||||
|
||||
export const InitialConfigurationStep: React.FunctionComponent<InitialConfigurationStepProps> = ({
|
||||
setStartTime,
|
||||
setEndTime,
|
||||
startTime,
|
||||
endTime,
|
||||
selectedIndices,
|
||||
setSelectedIndices,
|
||||
validationErrors = [],
|
||||
}: InitialConfigurationStepProps) => {
|
||||
const indicesFormValidationErrors = useMemo(
|
||||
() =>
|
||||
validationErrors.filter(validationError => validationError === 'TOO_FEW_SELECTED_INDICES'),
|
||||
[validationErrors]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiForm>
|
||||
<AnalysisSetupTimerangeForm
|
||||
setStartTime={setStartTime}
|
||||
setEndTime={setEndTime}
|
||||
startTime={startTime}
|
||||
endTime={endTime}
|
||||
/>
|
||||
<AnalysisSetupIndicesForm
|
||||
indices={selectedIndices}
|
||||
onChangeSelectedIndices={setSelectedIndices}
|
||||
validationErrors={indicesFormValidationErrors}
|
||||
/>
|
||||
</EuiForm>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -4,15 +4,16 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
export const CreateMLJobsButton: React.FunctionComponent<{
|
||||
isDisabled?: boolean;
|
||||
onClick: () => void;
|
||||
}> = ({ onClick }) => {
|
||||
}> = ({ isDisabled, onClick }) => {
|
||||
return (
|
||||
<EuiButton fill onClick={onClick}>
|
||||
<EuiButton isDisabled={isDisabled} fill onClick={onClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.createMlJobButton"
|
||||
defaultMessage="Create ML job"
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './process_step';
|
|
@ -11,29 +11,34 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { SetupStatus } from '../../../../../../common/log_analysis';
|
||||
import { CreateMLJobsButton } from '../create_ml_jobs_button';
|
||||
import { RecreateMLJobsButton } from '../recreate_ml_jobs_button';
|
||||
import { CreateMLJobsButton } from './create_ml_jobs_button';
|
||||
import { RecreateMLJobsButton } from './recreate_ml_jobs_button';
|
||||
|
||||
interface Props {
|
||||
viewResults: () => void;
|
||||
setup: () => void;
|
||||
interface ProcessStepProps {
|
||||
cleanupAndSetup: () => void;
|
||||
indexPattern: string;
|
||||
errorMessages: string[];
|
||||
isConfigurationValid: boolean;
|
||||
setup: () => void;
|
||||
setupStatus: SetupStatus;
|
||||
viewResults: () => void;
|
||||
}
|
||||
|
||||
export const SetupProcess: React.FunctionComponent<Props> = ({
|
||||
viewResults,
|
||||
setup,
|
||||
export const ProcessStep: React.FunctionComponent<ProcessStepProps> = ({
|
||||
cleanupAndSetup,
|
||||
indexPattern,
|
||||
errorMessages,
|
||||
isConfigurationValid,
|
||||
setup,
|
||||
setupStatus,
|
||||
}: Props) => {
|
||||
viewResults,
|
||||
}) => {
|
||||
return (
|
||||
<EuiText size="s">
|
||||
{setupStatus === 'pending' ? (
|
||||
|
@ -52,13 +57,15 @@ export const SetupProcess: React.FunctionComponent<Props> = ({
|
|||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.steps.setupProcess.failureText"
|
||||
defaultMessage="Something went wrong creating the necessary ML jobs.
|
||||
Please ensure your configured logs indices ({indexPattern}) exist."
|
||||
values={{
|
||||
indexPattern,
|
||||
}}
|
||||
defaultMessage="Something went wrong creating the necessary ML jobs. Please ensure all selected log indices exist."
|
||||
/>
|
||||
<EuiSpacer />
|
||||
{errorMessages.map(errorMessage => (
|
||||
<EuiCallOut color="danger" iconType="alert" title={errorCalloutTitle}>
|
||||
<EuiCode transparentBackground>{errorMessage}</EuiCode>
|
||||
</EuiCallOut>
|
||||
))}
|
||||
<EuiSpacer />
|
||||
<EuiButton fill onClick={cleanupAndSetup}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.steps.setupProcess.tryAgainButton"
|
||||
|
@ -81,10 +88,17 @@ export const SetupProcess: React.FunctionComponent<Props> = ({
|
|||
</EuiButton>
|
||||
</>
|
||||
) : setupStatus === 'requiredForUpdate' || setupStatus === 'requiredForReconfiguration' ? (
|
||||
<RecreateMLJobsButton onClick={cleanupAndSetup} />
|
||||
<RecreateMLJobsButton isDisabled={!isConfigurationValid} onClick={cleanupAndSetup} />
|
||||
) : (
|
||||
<CreateMLJobsButton onClick={setup} />
|
||||
<CreateMLJobsButton isDisabled={!isConfigurationValid} onClick={setup} />
|
||||
)}
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
|
||||
const errorCalloutTitle = i18n.translate(
|
||||
'xpack.infra.analysisSetup.steps.setupProcess.errorCalloutTitle',
|
||||
{
|
||||
defaultMessage: 'An error occurred',
|
||||
}
|
||||
);
|
|
@ -4,13 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
export const RecreateMLJobsButton: React.FunctionComponent<{
|
||||
isDisabled?: boolean;
|
||||
onClick: () => void;
|
||||
}> = ({ onClick }) => {
|
||||
}> = ({ isDisabled, onClick }) => {
|
||||
return (
|
||||
<>
|
||||
<FormattedMessage
|
||||
|
@ -18,7 +19,7 @@ export const RecreateMLJobsButton: React.FunctionComponent<{
|
|||
defaultMessage="This removes previously detected anomalies."
|
||||
tagName="p"
|
||||
/>
|
||||
<EuiButton fill onClick={onClick}>
|
||||
<EuiButton isDisabled={isDisabled} fill onClick={onClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.recreateMlJobButton"
|
||||
defaultMessage="Recreate ML jobs"
|
|
@ -8,24 +8,30 @@ import { EuiSteps, EuiStepStatus } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
import { SetupStatus } from '../../../../../../common/log_analysis';
|
||||
import { useAnalysisSetupState } from '../../../../../containers/logs/log_analysis/log_analysis_setup_state';
|
||||
import { InitialConfiguration } from './initial_configuration';
|
||||
import { SetupProcess } from './setup_process';
|
||||
import { SetupStatus } from '../../../../../common/log_analysis';
|
||||
import { useAnalysisSetupState } from '../../../../containers/logs/log_analysis/log_analysis_setup_state';
|
||||
import { InitialConfigurationStep } from './initial_configuration_step';
|
||||
import { ProcessStep } from './process_step';
|
||||
|
||||
type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void;
|
||||
type SetupHandler = (
|
||||
indices: string[],
|
||||
startTime: number | undefined,
|
||||
endTime: number | undefined
|
||||
) => void;
|
||||
|
||||
interface AnalysisSetupStepsProps {
|
||||
availableIndices: string[];
|
||||
cleanupAndSetup: SetupHandler;
|
||||
indexPattern: string;
|
||||
errorMessages: string[];
|
||||
setup: SetupHandler;
|
||||
setupStatus: SetupStatus;
|
||||
viewResults: () => void;
|
||||
}
|
||||
|
||||
export const AnalysisSetupSteps: React.FunctionComponent<AnalysisSetupStepsProps> = ({
|
||||
availableIndices,
|
||||
cleanupAndSetup: cleanupAndSetupModule,
|
||||
indexPattern,
|
||||
errorMessages,
|
||||
setup: setupModule,
|
||||
setupStatus,
|
||||
viewResults,
|
||||
|
@ -37,36 +43,44 @@ export const AnalysisSetupSteps: React.FunctionComponent<AnalysisSetupStepsProps
|
|||
setEndTime,
|
||||
startTime,
|
||||
endTime,
|
||||
selectedIndices,
|
||||
setSelectedIndices,
|
||||
validationErrors,
|
||||
} = useAnalysisSetupState({
|
||||
availableIndices,
|
||||
setupModule,
|
||||
cleanupAndSetupModule,
|
||||
});
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: i18n.translate('xpack.infra.analysisSetup.stepOneTitle', {
|
||||
defaultMessage: 'Configuration (optional)',
|
||||
title: i18n.translate('xpack.infra.analysisSetup.configurationStepTitle', {
|
||||
defaultMessage: 'Configuration',
|
||||
}),
|
||||
children: (
|
||||
<InitialConfiguration
|
||||
<InitialConfigurationStep
|
||||
setStartTime={setStartTime}
|
||||
setEndTime={setEndTime}
|
||||
startTime={startTime}
|
||||
endTime={endTime}
|
||||
selectedIndices={selectedIndices}
|
||||
setSelectedIndices={setSelectedIndices}
|
||||
validationErrors={validationErrors}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.infra.analysisSetup.stepTwoTitle', {
|
||||
title: i18n.translate('xpack.infra.analysisSetup.actionStepTitle', {
|
||||
defaultMessage: 'Create ML job',
|
||||
}),
|
||||
children: (
|
||||
<SetupProcess
|
||||
<ProcessStep
|
||||
cleanupAndSetup={cleanupAndSetup}
|
||||
errorMessages={errorMessages}
|
||||
isConfigurationValid={validationErrors.length <= 0}
|
||||
setup={setup}
|
||||
setupStatus={setupStatus}
|
||||
viewResults={viewResults}
|
||||
setup={setup}
|
||||
cleanupAndSetup={cleanupAndSetup}
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
),
|
||||
status:
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { AnalysisSetupTimerangeForm } from '../analysis_setup_timerange_form';
|
||||
|
||||
interface InitialConfigurationProps {
|
||||
setStartTime: (startTime: number | undefined) => void;
|
||||
setEndTime: (endTime: number | undefined) => void;
|
||||
startTime: number | undefined;
|
||||
endTime: number | undefined;
|
||||
}
|
||||
|
||||
export const InitialConfiguration: React.FunctionComponent<InitialConfigurationProps> = ({
|
||||
setStartTime,
|
||||
setEndTime,
|
||||
startTime,
|
||||
endTime,
|
||||
}: InitialConfigurationProps) => {
|
||||
const [showTimeRangeForm, setShowTimeRangeForm] = useState(false);
|
||||
return (
|
||||
<>
|
||||
{showTimeRangeForm ? (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<AnalysisSetupTimerangeForm
|
||||
setStartTime={setStartTime}
|
||||
setEndTime={setEndTime}
|
||||
startTime={startTime}
|
||||
endTime={endTime}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.timeRangeByDefault"
|
||||
defaultMessage="By default, Machine Learning analyzes log messages in your log indices no older than four weeks, and continues indefinitely."
|
||||
/>{' '}
|
||||
<EuiLink
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
setShowTimeRangeForm(true);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.analysisSetup.configureTimeRange"
|
||||
defaultMessage="Configure time range?"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -5188,13 +5188,11 @@
|
|||
"xpack.infra.waffleTime.stopRefreshingButtonLabel": "更新中止",
|
||||
"xpack.infra.analysisSetup.analysisSetupDescription": "機械学習を使用して自動的に異常ログレートカウントを検出します。",
|
||||
"xpack.infra.analysisSetup.analysisSetupTitle": "機械学習分析を有効にする",
|
||||
"xpack.infra.analysisSetup.configureTimeRange": "時間範囲を構成しますか?",
|
||||
"xpack.infra.analysisSetup.createMlJobButton": "ML ジョブを作成",
|
||||
"xpack.infra.analysisSetup.endTimeDefaultDescription": "永久",
|
||||
"xpack.infra.analysisSetup.endTimeLabel": "終了時刻",
|
||||
"xpack.infra.analysisSetup.startTimeDefaultDescription": "ログインデックスの開始地点です。",
|
||||
"xpack.infra.analysisSetup.startTimeLabel": "開始時刻",
|
||||
"xpack.infra.analysisSetup.timeRangeByDefault": "デフォルトで、ログインデックス内のすべての過去と未来のログメッセージを分析します。",
|
||||
"xpack.infra.analysisSetup.timeRangeDescription": "デフォルトで、機械学習はログインデックスの始めからログメッセージを分析し、永久に継続します。別の開始日、終了日、または両方を指定できます。",
|
||||
"xpack.infra.analysisSetup.timeRangeTitle": "時間範囲の選択",
|
||||
"xpack.infra.chartSection.missingMetricDataBody": "このチャートはデータが欠けています。",
|
||||
|
|
|
@ -5191,13 +5191,11 @@
|
|||
"xpack.infra.waffleTime.stopRefreshingButtonLabel": "停止刷新",
|
||||
"xpack.infra.analysisSetup.analysisSetupDescription": "使用 Machine Learning 自动检测异常日志速率计数。",
|
||||
"xpack.infra.analysisSetup.analysisSetupTitle": "启用 Machine Learning 分析",
|
||||
"xpack.infra.analysisSetup.configureTimeRange": "配置时间范围?",
|
||||
"xpack.infra.analysisSetup.createMlJobButton": "创建 ML 作业",
|
||||
"xpack.infra.analysisSetup.endTimeDefaultDescription": "无限期",
|
||||
"xpack.infra.analysisSetup.endTimeLabel": "结束时间",
|
||||
"xpack.infra.analysisSetup.startTimeDefaultDescription": "日志索引的开始时间",
|
||||
"xpack.infra.analysisSetup.startTimeLabel": "开始时间",
|
||||
"xpack.infra.analysisSetup.timeRangeByDefault": "默认情况下,我们将分析日志索引中的所有过去和未来日志消息。",
|
||||
"xpack.infra.analysisSetup.timeRangeDescription": "默认情况下,Machine Learning 分析自日志索引开始时间起的日志消息并无限期继续下去。您可以指定不同的开始日期或/和结束日期。",
|
||||
"xpack.infra.analysisSetup.timeRangeTitle": "选择时间范围",
|
||||
"xpack.infra.chartSection.missingMetricDataBody": "此图表的数据缺失。",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue