[Logs UI] Show log analysis ML jobs in a list (#71132)

This modifies the ML job setup flyout of the anomalies tab to offer a list of the two available modules. Via the list each of the modules' jobs can be created or re-created.
This commit is contained in:
Felix Stürmer 2020-07-13 23:29:55 +02:00 committed by GitHub
parent 29580bee4e
commit ff7b736cc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 715 additions and 359 deletions

View file

@ -14,18 +14,10 @@ export type JobStatus =
| 'finished' | 'finished'
| 'failed'; | 'failed';
export type SetupStatusRequiredReason =
| 'missing' // jobs are missing
| 'reconfiguration' // the configurations don't match the source configurations
| 'update'; // the definitions don't match the module definitions
export type SetupStatus = export type SetupStatus =
| { type: 'initializing' } // acquiring job statuses to determine setup status | { type: 'initializing' } // acquiring job statuses to determine setup status
| { type: 'unknown' } // job status could not be acquired (failed request etc) | { type: 'unknown' } // job status could not be acquired (failed request etc)
| { | { type: 'required' } // setup required
type: 'required';
reason: SetupStatusRequiredReason;
} // setup required
| { type: 'pending' } // In the process of setting up the module for the first time or retrying, waiting for response | { type: 'pending' } // In the process of setting up the module for the first time or retrying, waiting for response
| { type: 'succeeded' } // setup succeeded, notifying user | { type: 'succeeded' } // setup succeeded, notifying user
| { | {

View file

@ -5,4 +5,5 @@
*/ */
export * from './log_analysis_job_problem_indicator'; export * from './log_analysis_job_problem_indicator';
export * from './notices_section';
export * from './recreate_job_button'; export * from './recreate_job_button';

View file

@ -11,19 +11,24 @@ import React from 'react';
import { RecreateJobCallout } from './recreate_job_callout'; import { RecreateJobCallout } from './recreate_job_callout';
export const JobConfigurationOutdatedCallout: React.FC<{ export const JobConfigurationOutdatedCallout: React.FC<{
moduleName: string;
onRecreateMlJob: () => void; onRecreateMlJob: () => void;
}> = ({ onRecreateMlJob }) => ( }> = ({ moduleName, onRecreateMlJob }) => (
<RecreateJobCallout title={jobConfigurationOutdatedTitle} onRecreateMlJob={onRecreateMlJob}> <RecreateJobCallout
title={i18n.translate('xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutTitle', {
defaultMessage: 'The {moduleName} ML job configuration is outdated',
values: {
moduleName,
},
})}
onRecreateMlJob={onRecreateMlJob}
>
<FormattedMessage <FormattedMessage
id="xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutMessage" id="xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutMessage"
defaultMessage="The ML job was created using a different source configuration. Recreate the job to apply the current configuration. This removes previously detected anomalies." defaultMessage="The {moduleName} ML job was created using a different source configuration. Recreate the job to apply the current configuration. This removes previously detected anomalies."
values={{
moduleName,
}}
/> />
</RecreateJobCallout> </RecreateJobCallout>
); );
const jobConfigurationOutdatedTitle = i18n.translate(
'xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutTitle',
{
defaultMessage: 'ML job configuration outdated',
}
);

View file

@ -11,19 +11,24 @@ import React from 'react';
import { RecreateJobCallout } from './recreate_job_callout'; import { RecreateJobCallout } from './recreate_job_callout';
export const JobDefinitionOutdatedCallout: React.FC<{ export const JobDefinitionOutdatedCallout: React.FC<{
moduleName: string;
onRecreateMlJob: () => void; onRecreateMlJob: () => void;
}> = ({ onRecreateMlJob }) => ( }> = ({ moduleName, onRecreateMlJob }) => (
<RecreateJobCallout title={jobDefinitionOutdatedTitle} onRecreateMlJob={onRecreateMlJob}> <RecreateJobCallout
title={i18n.translate('xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutTitle', {
defaultMessage: 'The {moduleName} ML job definition is outdated',
values: {
moduleName,
},
})}
onRecreateMlJob={onRecreateMlJob}
>
<FormattedMessage <FormattedMessage
id="xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutMessage" id="xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutMessage"
defaultMessage="A newer version of the ML job is available. Recreate the job to deploy the newer version. This removes previously detected anomalies." defaultMessage="A newer version of the {moduleName} ML job is available. Recreate the job to deploy the newer version. This removes previously detected anomalies."
values={{
moduleName,
}}
/> />
</RecreateJobCallout> </RecreateJobCallout>
); );
const jobDefinitionOutdatedTitle = i18n.translate(
'xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutTitle',
{
defaultMessage: 'ML job definition outdated',
}
);

View file

@ -16,6 +16,7 @@ export const LogAnalysisJobProblemIndicator: React.FC<{
hasOutdatedJobDefinitions: boolean; hasOutdatedJobDefinitions: boolean;
hasStoppedJobs: boolean; hasStoppedJobs: boolean;
isFirstUse: boolean; isFirstUse: boolean;
moduleName: string;
onRecreateMlJobForReconfiguration: () => void; onRecreateMlJobForReconfiguration: () => void;
onRecreateMlJobForUpdate: () => void; onRecreateMlJobForUpdate: () => void;
}> = ({ }> = ({
@ -23,16 +24,23 @@ export const LogAnalysisJobProblemIndicator: React.FC<{
hasOutdatedJobDefinitions, hasOutdatedJobDefinitions,
hasStoppedJobs, hasStoppedJobs,
isFirstUse, isFirstUse,
moduleName,
onRecreateMlJobForReconfiguration, onRecreateMlJobForReconfiguration,
onRecreateMlJobForUpdate, onRecreateMlJobForUpdate,
}) => { }) => {
return ( return (
<> <>
{hasOutdatedJobDefinitions ? ( {hasOutdatedJobDefinitions ? (
<JobDefinitionOutdatedCallout onRecreateMlJob={onRecreateMlJobForUpdate} /> <JobDefinitionOutdatedCallout
moduleName={moduleName}
onRecreateMlJob={onRecreateMlJobForUpdate}
/>
) : null} ) : null}
{hasOutdatedJobConfigurations ? ( {hasOutdatedJobConfigurations ? (
<JobConfigurationOutdatedCallout onRecreateMlJob={onRecreateMlJobForReconfiguration} /> <JobConfigurationOutdatedCallout
moduleName={moduleName}
onRecreateMlJob={onRecreateMlJobForReconfiguration}
/>
) : null} ) : null}
{hasStoppedJobs ? <JobStoppedCallout /> : null} {hasStoppedJobs ? <JobStoppedCallout /> : null}
{isFirstUse ? <FirstUseCallout /> : null} {isFirstUse ? <FirstUseCallout /> : null}

View file

@ -5,8 +5,8 @@
*/ */
import React from 'react'; import React from 'react';
import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status'; import { QualityWarning } from '../../../containers/logs/log_analysis/log_analysis_module_types';
import { QualityWarning } from './quality_warnings'; import { LogAnalysisJobProblemIndicator } from './log_analysis_job_problem_indicator';
import { CategoryQualityWarnings } from './quality_warning_notices'; import { CategoryQualityWarnings } from './quality_warning_notices';
export const CategoryJobNoticesSection: React.FC<{ export const CategoryJobNoticesSection: React.FC<{
@ -14,6 +14,7 @@ export const CategoryJobNoticesSection: React.FC<{
hasOutdatedJobDefinitions: boolean; hasOutdatedJobDefinitions: boolean;
hasStoppedJobs: boolean; hasStoppedJobs: boolean;
isFirstUse: boolean; isFirstUse: boolean;
moduleName: string;
onRecreateMlJobForReconfiguration: () => void; onRecreateMlJobForReconfiguration: () => void;
onRecreateMlJobForUpdate: () => void; onRecreateMlJobForUpdate: () => void;
qualityWarnings: QualityWarning[]; qualityWarnings: QualityWarning[];
@ -22,6 +23,7 @@ export const CategoryJobNoticesSection: React.FC<{
hasOutdatedJobDefinitions, hasOutdatedJobDefinitions,
hasStoppedJobs, hasStoppedJobs,
isFirstUse, isFirstUse,
moduleName,
onRecreateMlJobForReconfiguration, onRecreateMlJobForReconfiguration,
onRecreateMlJobForUpdate, onRecreateMlJobForUpdate,
qualityWarnings, qualityWarnings,
@ -32,6 +34,7 @@ export const CategoryJobNoticesSection: React.FC<{
hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} hasOutdatedJobDefinitions={hasOutdatedJobDefinitions}
hasStoppedJobs={hasStoppedJobs} hasStoppedJobs={hasStoppedJobs}
isFirstUse={isFirstUse} isFirstUse={isFirstUse}
moduleName={moduleName}
onRecreateMlJobForReconfiguration={onRecreateMlJobForReconfiguration} onRecreateMlJobForReconfiguration={onRecreateMlJobForReconfiguration}
onRecreateMlJobForUpdate={onRecreateMlJobForUpdate} onRecreateMlJobForUpdate={onRecreateMlJobForUpdate}
/> />

View file

@ -8,7 +8,10 @@ import { EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { CategoryQualityWarningReason, QualityWarning } from './quality_warnings'; import type {
CategoryQualityWarningReason,
QualityWarning,
} from '../../../containers/logs/log_analysis/log_analysis_module_types';
export const CategoryQualityWarnings: React.FC<{ qualityWarnings: QualityWarning[] }> = ({ export const CategoryQualityWarnings: React.FC<{ qualityWarnings: QualityWarning[] }> = ({
qualityWarnings, qualityWarnings,

View file

@ -84,7 +84,7 @@ export const InitialConfigurationStep: React.FunctionComponent<InitialConfigurat
); );
}; };
const editableFormStatus = ['required', 'failed']; const editableFormStatus = ['required', 'failed', 'skipped'];
const errorCalloutTitle = i18n.translate( const errorCalloutTitle = i18n.translate(
'xpack.infra.analysisSetup.steps.initialConfigurationStep.errorCalloutTitle', 'xpack.infra.analysisSetup.steps.initialConfigurationStep.errorCalloutTitle',

View file

@ -0,0 +1,18 @@
/*
* 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 { EuiButton, PropsOf } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
export const ManageJobsButton: React.FunctionComponent<PropsOf<typeof EuiButton>> = (props) => (
<EuiButton {...props}>
<FormattedMessage
id="xpack.infra.logs.analysis.manageMlJobsButtonLabel"
defaultMessage="Manage ML jobs"
/>
</EuiButton>
);

View file

@ -101,11 +101,10 @@ export const ProcessStep: React.FunctionComponent<ProcessStepProps> = ({
/> />
</EuiButton> </EuiButton>
</> </>
) : setupStatus.type === 'required' && ) : setupStatus.type === 'required' ? (
(setupStatus.reason === 'update' || setupStatus.reason === 'reconfiguration') ? (
<RecreateMLJobsButton isDisabled={!isConfigurationValid} onClick={cleanUpAndSetUp} />
) : (
<CreateMLJobsButton isDisabled={!isConfigurationValid} onClick={setUp} /> <CreateMLJobsButton isDisabled={!isConfigurationValid} onClick={setUp} />
) : (
<RecreateMLJobsButton isDisabled={!isConfigurationValid} onClick={cleanUpAndSetUp} />
)} )}
</EuiText> </EuiText>
); );

View file

@ -3,3 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License; * or more contributor license agreements. Licensed under the Elastic License;
* 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.
*/ */
export * from './setup_flyout';
export * from './setup_flyout_state';

View file

@ -0,0 +1,87 @@
/*
* 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, EuiSteps, EuiText, EuiTitle } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { useLogEntryCategoriesSetup } from '../../../../containers/logs/log_analysis/modules/log_entry_categories';
import { createInitialConfigurationStep } from '../initial_configuration_step';
import { createProcessStep } from '../process_step';
export const LogEntryCategoriesSetupView: React.FC<{
onClose: () => void;
}> = ({ onClose }) => {
const {
cleanUpAndSetUp,
endTime,
isValidating,
lastSetupErrorMessages,
moduleDescriptor,
setEndTime,
setStartTime,
setValidatedIndices,
setUp,
setupStatus,
startTime,
validatedIndices,
validationErrors,
viewResults,
} = useLogEntryCategoriesSetup();
const viewResultsAndClose = useCallback(() => {
viewResults();
onClose();
}, [viewResults, onClose]);
const steps = useMemo(
() => [
createInitialConfigurationStep({
setStartTime,
setEndTime,
startTime,
endTime,
isValidating,
validatedIndices,
setupStatus,
setValidatedIndices,
validationErrors,
}),
createProcessStep({
cleanUpAndSetUp,
errorMessages: lastSetupErrorMessages,
isConfigurationValid: validationErrors.length <= 0 && !isValidating,
setUp,
setupStatus,
viewResults: viewResultsAndClose,
}),
],
[
cleanUpAndSetUp,
endTime,
isValidating,
lastSetupErrorMessages,
setEndTime,
setStartTime,
setUp,
setValidatedIndices,
setupStatus,
startTime,
validatedIndices,
validationErrors,
viewResultsAndClose,
]
);
return (
<>
<EuiTitle size="s">
<h3>{moduleDescriptor.moduleName} </h3>
</EuiTitle>
<EuiText size="s">{moduleDescriptor.moduleDescription}</EuiText>
<EuiSpacer />
<EuiSteps steps={steps} />
</>
);
};

View file

@ -5,37 +5,20 @@
*/ */
import React, { useMemo, useCallback } from 'react'; import React, { useMemo, useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer, EuiSteps } from '@elastic/eui';
import { import { createInitialConfigurationStep } from '../initial_configuration_step';
EuiFlyout, import { createProcessStep } from '../process_step';
EuiFlyoutHeader, import { useLogEntryRateSetup } from '../../../../containers/logs/log_analysis/modules/log_entry_rate';
EuiFlyoutBody,
EuiTitle,
EuiText,
EuiSpacer,
EuiSteps,
} from '@elastic/eui';
import { export const LogEntryRateSetupView: React.FC<{
createInitialConfigurationStep,
createProcessStep,
} from '../../../components/logging/log_analysis_setup';
import { useLogEntryRateSetup } from './use_log_entry_rate_setup';
interface LogEntryRateSetupFlyoutProps {
isOpen: boolean;
onClose: () => void; onClose: () => void;
} }> = ({ onClose }) => {
export const LogEntryRateSetupFlyout: React.FC<LogEntryRateSetupFlyoutProps> = ({
isOpen,
onClose,
}) => {
const { const {
cleanUpAndSetUp, cleanUpAndSetUp,
endTime, endTime,
isValidating, isValidating,
lastSetupErrorMessages, lastSetupErrorMessages,
moduleDescriptor,
setEndTime, setEndTime,
setStartTime, setStartTime,
setValidatedIndices, setValidatedIndices,
@ -91,39 +74,14 @@ export const LogEntryRateSetupFlyout: React.FC<LogEntryRateSetupFlyoutProps> = (
] ]
); );
if (!isOpen) {
return null;
}
return ( return (
<EuiFlyout onClose={onClose}> <>
<EuiFlyoutHeader hasBorder> <EuiTitle size="s">
<EuiTitle size="s"> <h3>{moduleDescriptor.moduleName} </h3>
<h3> </EuiTitle>
<FormattedMessage <EuiText size="s">{moduleDescriptor.moduleDescription}</EuiText>
id="xpack.infra.logs.setupFlyout.setupFlyoutTitle" <EuiSpacer />
defaultMessage="Anomaly detection with Machine Learning" <EuiSteps steps={steps} />
/> </>
</h3>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.infra.logs.setupFlyout.logRateTitle"
defaultMessage="Log rate"
/>
</h3>
</EuiTitle>
<EuiText size="s">
<FormattedMessage
id="xpack.infra.logs.setupFlyout.logRateDescription"
defaultMessage="Use Machine Learning to automatically detect anomalous log rate counts."
/>
</EuiText>
<EuiSpacer />
<EuiSteps steps={steps} />
</EuiFlyoutBody>
</EuiFlyout>
); );
}; };

View file

@ -0,0 +1,55 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useCallback } from 'react';
import {
logEntryCategoriesModule,
useLogEntryCategoriesModuleContext,
} from '../../../../containers/logs/log_analysis/modules/log_entry_categories';
import {
logEntryRateModule,
useLogEntryRateModuleContext,
} from '../../../../containers/logs/log_analysis/modules/log_entry_rate';
import { LogAnalysisModuleListCard } from './module_list_card';
import type { ModuleId } from './setup_flyout_state';
export const LogAnalysisModuleList: React.FC<{
onViewModuleSetup: (module: ModuleId) => void;
}> = ({ onViewModuleSetup }) => {
const { setupStatus: logEntryRateSetupStatus } = useLogEntryRateModuleContext();
const { setupStatus: logEntryCategoriesSetupStatus } = useLogEntryCategoriesModuleContext();
const viewLogEntryRateSetupFlyout = useCallback(() => {
onViewModuleSetup('logs_ui_analysis');
}, [onViewModuleSetup]);
const viewLogEntryCategoriesSetupFlyout = useCallback(() => {
onViewModuleSetup('logs_ui_categories');
}, [onViewModuleSetup]);
return (
<>
<EuiFlexGroup>
<EuiFlexItem>
<LogAnalysisModuleListCard
moduleDescription={logEntryRateModule.moduleDescription}
moduleName={logEntryRateModule.moduleName}
moduleStatus={logEntryRateSetupStatus}
onViewSetup={viewLogEntryRateSetupFlyout}
/>
</EuiFlexItem>
<EuiFlexItem>
<LogAnalysisModuleListCard
moduleDescription={logEntryCategoriesModule.moduleDescription}
moduleName={logEntryCategoriesModule.moduleName}
moduleStatus={logEntryCategoriesSetupStatus}
onViewSetup={viewLogEntryCategoriesSetupFlyout}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

View file

@ -0,0 +1,46 @@
/*
* 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 { EuiCard, EuiIcon } from '@elastic/eui';
import React from 'react';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { RecreateJobButton } from '../../log_analysis_job_status';
import { SetupStatus } from '../../../../../common/log_analysis';
export const LogAnalysisModuleListCard: React.FC<{
moduleDescription: string;
moduleName: string;
moduleStatus: SetupStatus;
onViewSetup: () => void;
}> = ({ moduleDescription, moduleName, moduleStatus, onViewSetup }) => {
const icon =
moduleStatus.type === 'required' ? (
<EuiIcon size="xxl" type="machineLearningApp" />
) : (
<EuiIcon color="secondary" size="xxl" type="check" />
);
const footerContent =
moduleStatus.type === 'required' ? (
<EuiButton onClick={onViewSetup}>
<FormattedMessage
id="xpack.infra.logs.analysis.enableAnomalyDetectionButtonLabel"
defaultMessage="Enable anomaly detection"
/>
</EuiButton>
) : (
<RecreateJobButton onClick={onViewSetup} />
);
return (
<EuiCard
description={moduleDescription}
footer={<div>{footerContent}</div>}
icon={icon}
title={moduleName}
/>
);
};

View file

@ -0,0 +1,80 @@
/*
* 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 {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { LogEntryRateSetupView } from './log_entry_rate_setup_view';
import { LogEntryCategoriesSetupView } from './log_entry_categories_setup_view';
import { LogAnalysisModuleList } from './module_list';
import { useLogAnalysisSetupFlyoutStateContext } from './setup_flyout_state';
const FLYOUT_HEADING_ID = 'logAnalysisSetupFlyoutHeading';
export const LogAnalysisSetupFlyout: React.FC = () => {
const {
closeFlyout,
flyoutView,
showModuleList,
showModuleSetup,
} = useLogAnalysisSetupFlyoutStateContext();
if (flyoutView.view === 'hidden') {
return null;
}
return (
<EuiFlyout aria-labelledby={FLYOUT_HEADING_ID} maxWidth={800} onClose={closeFlyout}>
<EuiFlyoutHeader hasBorder>
<EuiTitle>
<h2 id={FLYOUT_HEADING_ID}>
<FormattedMessage
id="xpack.infra.logs.analysis.setupFlyoutTitle"
defaultMessage="Anomaly detection with Machine Learning"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{flyoutView.view === 'moduleList' ? (
<LogAnalysisModuleList onViewModuleSetup={showModuleSetup} />
) : flyoutView.view === 'moduleSetup' && flyoutView.module === 'logs_ui_analysis' ? (
<LogAnalysisSetupFlyoutSubPage onViewModuleList={showModuleList}>
<LogEntryRateSetupView onClose={closeFlyout} />
</LogAnalysisSetupFlyoutSubPage>
) : flyoutView.view === 'moduleSetup' && flyoutView.module === 'logs_ui_categories' ? (
<LogAnalysisSetupFlyoutSubPage onViewModuleList={showModuleList}>
<LogEntryCategoriesSetupView onClose={closeFlyout} />
</LogAnalysisSetupFlyoutSubPage>
) : null}
</EuiFlyoutBody>
</EuiFlyout>
);
};
const LogAnalysisSetupFlyoutSubPage: React.FC<{
onViewModuleList: () => void;
}> = ({ children, onViewModuleList }) => (
<EuiFlexGroup alignItems="flexStart" direction="column" gutterSize="none">
<EuiFlexItem grow={false}>
<EuiButtonEmpty flush="left" iconSide="left" iconType="arrowLeft" onClick={onViewModuleList}>
<FormattedMessage
id="xpack.infra.logs.analysis.setupFlyoutGotoListButtonLabel"
defaultMessage="All Machine Learning jobs"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem>{children}</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -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;
* you may not use this file except in compliance with the Elastic License.
*/
import createContainer from 'constate';
import { useState, useCallback } from 'react';
export type ModuleId = 'logs_ui_analysis' | 'logs_ui_categories';
type FlyoutView =
| { view: 'hidden' }
| { view: 'moduleList' }
| { view: 'moduleSetup'; module: ModuleId };
export const useLogAnalysisSetupFlyoutState = ({
initialFlyoutView = { view: 'hidden' },
}: {
initialFlyoutView?: FlyoutView;
}) => {
const [flyoutView, setFlyoutView] = useState<FlyoutView>(initialFlyoutView);
const closeFlyout = useCallback(() => setFlyoutView({ view: 'hidden' }), []);
const showModuleList = useCallback(() => setFlyoutView({ view: 'moduleList' }), []);
const showModuleSetup = useCallback(
(module: ModuleId) => {
setFlyoutView({ view: 'moduleSetup', module });
},
[setFlyoutView]
);
return {
closeFlyout,
flyoutView,
setFlyoutView,
showModuleList,
showModuleSetup,
};
};
export const [
LogAnalysisSetupFlyoutStateProvider,
useLogAnalysisSetupFlyoutStateContext,
] = createContainer(useLogAnalysisSetupFlyoutState);

View file

@ -111,14 +111,6 @@ export const useLogAnalysisModule = <JobType extends string>({
[cleanUpModule, dispatchModuleStatus, setUpModule] [cleanUpModule, dispatchModuleStatus, setUpModule]
); );
const viewSetupForReconfiguration = useCallback(() => {
dispatchModuleStatus({ type: 'requestedJobConfigurationUpdate' });
}, [dispatchModuleStatus]);
const viewSetupForUpdate = useCallback(() => {
dispatchModuleStatus({ type: 'requestedJobDefinitionUpdate' });
}, [dispatchModuleStatus]);
const viewResults = useCallback(() => { const viewResults = useCallback(() => {
dispatchModuleStatus({ type: 'viewedResults' }); dispatchModuleStatus({ type: 'viewedResults' });
}, [dispatchModuleStatus]); }, [dispatchModuleStatus]);
@ -143,7 +135,5 @@ export const useLogAnalysisModule = <JobType extends string>({
setupStatus: moduleStatus.setupStatus, setupStatus: moduleStatus.setupStatus,
sourceConfiguration, sourceConfiguration,
viewResults, viewResults,
viewSetupForReconfiguration,
viewSetupForUpdate,
}; };
}; };

View file

@ -43,8 +43,6 @@ type StatusReducerAction =
payload: FetchJobStatusResponsePayload; payload: FetchJobStatusResponsePayload;
} }
| { type: 'failedFetchingJobStatuses' } | { type: 'failedFetchingJobStatuses' }
| { type: 'requestedJobConfigurationUpdate' }
| { type: 'requestedJobDefinitionUpdate' }
| { type: 'viewedResults' }; | { type: 'viewedResults' };
const createInitialState = <JobType extends string>({ const createInitialState = <JobType extends string>({
@ -173,18 +171,6 @@ const createStatusReducer = <JobType extends string>(jobTypes: JobType[]) => (
), ),
}; };
} }
case 'requestedJobConfigurationUpdate': {
return {
...state,
setupStatus: { type: 'required', reason: 'reconfiguration' },
};
}
case 'requestedJobDefinitionUpdate': {
return {
...state,
setupStatus: { type: 'required', reason: 'update' },
};
}
case 'viewedResults': { case 'viewedResults': {
return { return {
...state, ...state,
@ -251,7 +237,7 @@ const getSetupStatus = <JobType extends string>(everyJobStatus: Record<JobType,
): SetupStatus => ): SetupStatus =>
Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>((setupStatus, [, jobStatus]) => { Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>((setupStatus, [, jobStatus]) => {
if (jobStatus === 'missing') { if (jobStatus === 'missing') {
return { type: 'required', reason: 'missing' }; return { type: 'required' };
} else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') {
return setupStatus; return setupStatus;
} else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) {

View file

@ -4,18 +4,22 @@
* 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 {
ValidateLogEntryDatasetsResponsePayload,
ValidationIndicesResponsePayload,
} from '../../../../common/http_api/log_analysis';
import { DatasetFilter } from '../../../../common/log_analysis';
import { DeleteJobsResponsePayload } from './api/ml_cleanup'; import { DeleteJobsResponsePayload } from './api/ml_cleanup';
import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api';
import { GetMlModuleResponsePayload } from './api/ml_get_module'; import { GetMlModuleResponsePayload } from './api/ml_get_module';
import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api';
import {
ValidationIndicesResponsePayload, export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api';
ValidateLogEntryDatasetsResponsePayload,
} from '../../../../common/http_api/log_analysis';
import { DatasetFilter } from '../../../../common/log_analysis';
export interface ModuleDescriptor<JobType extends string> { export interface ModuleDescriptor<JobType extends string> {
moduleId: string; moduleId: string;
moduleName: string;
moduleDescription: string;
jobTypes: JobType[]; jobTypes: JobType[];
bucketSpan: number; bucketSpan: number;
getJobIds: (spaceId: string, sourceId: string) => Record<JobType, string>; getJobIds: (spaceId: string, sourceId: string) => Record<JobType, string>;
@ -46,3 +50,43 @@ export interface ModuleSourceConfiguration {
spaceId: string; spaceId: string;
timestampField: string; timestampField: string;
} }
interface ManyCategoriesWarningReason {
type: 'manyCategories';
categoriesDocumentRatio: number;
}
interface ManyDeadCategoriesWarningReason {
type: 'manyDeadCategories';
deadCategoriesRatio: number;
}
interface ManyRareCategoriesWarningReason {
type: 'manyRareCategories';
rareCategoriesRatio: number;
}
interface NoFrequentCategoriesWarningReason {
type: 'noFrequentCategories';
}
interface SingleCategoryWarningReason {
type: 'singleCategory';
}
export type CategoryQualityWarningReason =
| ManyCategoriesWarningReason
| ManyDeadCategoriesWarningReason
| ManyRareCategoriesWarningReason
| NoFrequentCategoriesWarningReason
| SingleCategoryWarningReason;
export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type'];
export interface CategoryQualityWarning {
type: 'categoryQualityWarning';
jobId: string;
reasons: CategoryQualityWarningReason[];
}
export type QualityWarning = CategoryQualityWarning;

View file

@ -0,0 +1,10 @@
/*
* 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 './module_descriptor';
export * from './use_log_entry_categories_module';
export * from './use_log_entry_categories_quality';
export * from './use_log_entry_categories_setup';

View file

@ -4,6 +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 { i18n } from '@kbn/i18n';
import { import {
bucketSpan, bucketSpan,
categoriesMessageField, categoriesMessageField,
@ -12,19 +13,25 @@ import {
LogEntryCategoriesJobType, LogEntryCategoriesJobType,
logEntryCategoriesJobTypes, logEntryCategoriesJobTypes,
partitionField, partitionField,
} from '../../../../common/log_analysis'; } from '../../../../../../common/log_analysis';
import { import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
cleanUpJobsAndDatafeeds, import { callGetMlModuleAPI } from '../../api/ml_get_module';
ModuleDescriptor, import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api';
ModuleSourceConfiguration, import { callValidateDatasetsAPI } from '../../api/validate_datasets';
} from '../../../containers/logs/log_analysis'; import { callValidateIndicesAPI } from '../../api/validate_indices';
import { callJobsSummaryAPI } from '../../../containers/logs/log_analysis/api/ml_get_jobs_summary_api'; import { cleanUpJobsAndDatafeeds } from '../../log_analysis_cleanup';
import { callGetMlModuleAPI } from '../../../containers/logs/log_analysis/api/ml_get_module'; import { ModuleDescriptor, ModuleSourceConfiguration } from '../../log_analysis_module_types';
import { callSetupMlModuleAPI } from '../../../containers/logs/log_analysis/api/ml_setup_module_api';
import { callValidateDatasetsAPI } from '../../../containers/logs/log_analysis/api/validate_datasets';
import { callValidateIndicesAPI } from '../../../containers/logs/log_analysis/api/validate_indices';
const moduleId = 'logs_ui_categories'; const moduleId = 'logs_ui_categories';
const moduleName = i18n.translate('xpack.infra.logs.analysis.logEntryCategoriesModuleName', {
defaultMessage: 'Categorization',
});
const moduleDescription = i18n.translate(
'xpack.infra.logs.analysis.logEntryCategoriesModuleDescription',
{
defaultMessage: 'Use Machine Learning to automatically categorize log messages.',
}
);
const getJobIds = (spaceId: string, sourceId: string) => const getJobIds = (spaceId: string, sourceId: string) =>
logEntryCategoriesJobTypes.reduce( logEntryCategoriesJobTypes.reduce(
@ -138,6 +145,8 @@ const validateSetupDatasets = async (
export const logEntryCategoriesModule: ModuleDescriptor<LogEntryCategoriesJobType> = { export const logEntryCategoriesModule: ModuleDescriptor<LogEntryCategoriesJobType> = {
moduleId, moduleId,
moduleName,
moduleDescription,
jobTypes: logEntryCategoriesJobTypes, jobTypes: logEntryCategoriesJobTypes,
bucketSpan, bucketSpan,
getJobIds, getJobIds,

View file

@ -6,12 +6,10 @@
import createContainer from 'constate'; import createContainer from 'constate';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { import { useLogAnalysisModule } from '../../log_analysis_module';
ModuleSourceConfiguration, import { useLogAnalysisModuleConfiguration } from '../../log_analysis_module_configuration';
useLogAnalysisModule, import { useLogAnalysisModuleDefinition } from '../../log_analysis_module_definition';
useLogAnalysisModuleConfiguration, import { ModuleSourceConfiguration } from '../../log_analysis_module_types';
useLogAnalysisModuleDefinition,
} from '../../../containers/logs/log_analysis';
import { logEntryCategoriesModule } from './module_descriptor'; import { logEntryCategoriesModule } from './module_descriptor';
import { useLogEntryCategoriesQuality } from './use_log_entry_categories_quality'; import { useLogEntryCategoriesQuality } from './use_log_entry_categories_quality';

View file

@ -5,9 +5,12 @@
*/ */
import { useMemo } from 'react'; import { useMemo } from 'react';
import {
import { JobModelSizeStats, JobSummary } from '../../../containers/logs/log_analysis'; JobModelSizeStats,
import { QualityWarning, CategoryQualityWarningReason } from './sections/notices/quality_warnings'; JobSummary,
QualityWarning,
CategoryQualityWarningReason,
} from '../../log_analysis_module_types';
export const useLogEntryCategoriesQuality = ({ jobSummaries }: { jobSummaries: JobSummary[] }) => { export const useLogEntryCategoriesQuality = ({ jobSummaries }: { jobSummaries: JobSummary[] }) => {
const categoryQualityWarnings: QualityWarning[] = useMemo( const categoryQualityWarnings: QualityWarning[] = useMemo(

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 { useAnalysisSetupState } from '../../../containers/logs/log_analysis'; import { useAnalysisSetupState } from '../../log_analysis_setup_state';
import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module'; import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module';
export const useLogEntryCategoriesSetup = () => { export const useLogEntryCategoriesSetup = () => {
@ -41,6 +41,7 @@ export const useLogEntryCategoriesSetup = () => {
endTime, endTime,
isValidating, isValidating,
lastSetupErrorMessages, lastSetupErrorMessages,
moduleDescriptor,
setEndTime, setEndTime,
setStartTime, setStartTime,
setValidatedIndices, setValidatedIndices,

View file

@ -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;
* you may not use this file except in compliance with the Elastic License.
*/
export * from './module_descriptor';
export * from './use_log_entry_rate_module';
export * from './use_log_entry_rate_setup';

View file

@ -4,6 +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 { i18n } from '@kbn/i18n';
import { import {
bucketSpan, bucketSpan,
DatasetFilter, DatasetFilter,
@ -11,19 +12,25 @@ import {
LogEntryRateJobType, LogEntryRateJobType,
logEntryRateJobTypes, logEntryRateJobTypes,
partitionField, partitionField,
} from '../../../../common/log_analysis'; } from '../../../../../../common/log_analysis';
import { import { ModuleDescriptor, ModuleSourceConfiguration } from '../../log_analysis_module_types';
cleanUpJobsAndDatafeeds, import { cleanUpJobsAndDatafeeds } from '../../log_analysis_cleanup';
ModuleDescriptor, import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
ModuleSourceConfiguration, import { callGetMlModuleAPI } from '../../api/ml_get_module';
} from '../../../containers/logs/log_analysis'; import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api';
import { callJobsSummaryAPI } from '../../../containers/logs/log_analysis/api/ml_get_jobs_summary_api'; import { callValidateDatasetsAPI } from '../../api/validate_datasets';
import { callGetMlModuleAPI } from '../../../containers/logs/log_analysis/api/ml_get_module'; import { callValidateIndicesAPI } from '../../api/validate_indices';
import { callSetupMlModuleAPI } from '../../../containers/logs/log_analysis/api/ml_setup_module_api';
import { callValidateDatasetsAPI } from '../../../containers/logs/log_analysis/api/validate_datasets';
import { callValidateIndicesAPI } from '../../../containers/logs/log_analysis/api/validate_indices';
const moduleId = 'logs_ui_analysis'; const moduleId = 'logs_ui_analysis';
const moduleName = i18n.translate('xpack.infra.logs.analysis.logEntryRateModuleName', {
defaultMessage: 'Log rate',
});
const moduleDescription = i18n.translate(
'xpack.infra.logs.analysis.logEntryRateModuleDescription',
{
defaultMessage: 'Use Machine Learning to automatically detect anomalous log entry rates.',
}
);
const getJobIds = (spaceId: string, sourceId: string) => const getJobIds = (spaceId: string, sourceId: string) =>
logEntryRateJobTypes.reduce( logEntryRateJobTypes.reduce(
@ -126,6 +133,8 @@ const validateSetupDatasets = async (
export const logEntryRateModule: ModuleDescriptor<LogEntryRateJobType> = { export const logEntryRateModule: ModuleDescriptor<LogEntryRateJobType> = {
moduleId, moduleId,
moduleName,
moduleDescription,
jobTypes: logEntryRateJobTypes, jobTypes: logEntryRateJobTypes,
bucketSpan, bucketSpan,
getJobIds, getJobIds,

View file

@ -6,12 +6,10 @@
import createContainer from 'constate'; import createContainer from 'constate';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { import { ModuleSourceConfiguration } from '../../log_analysis_module_types';
ModuleSourceConfiguration, import { useLogAnalysisModule } from '../../log_analysis_module';
useLogAnalysisModule, import { useLogAnalysisModuleConfiguration } from '../../log_analysis_module_configuration';
useLogAnalysisModuleConfiguration, import { useLogAnalysisModuleDefinition } from '../../log_analysis_module_definition';
useLogAnalysisModuleDefinition,
} from '../../../containers/logs/log_analysis';
import { logEntryRateModule } from './module_descriptor'; import { logEntryRateModule } from './module_descriptor';
export const useLogEntryRateModule = ({ export const useLogEntryRateModule = ({

View file

@ -4,7 +4,8 @@
* 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 { useAnalysisSetupState } from '../../../containers/logs/log_analysis'; import createContainer from 'constate';
import { useAnalysisSetupState } from '../../log_analysis_setup_state';
import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; import { useLogEntryRateModuleContext } from './use_log_entry_rate_module';
export const useLogEntryRateSetup = () => { export const useLogEntryRateSetup = () => {
@ -41,6 +42,7 @@ export const useLogEntryRateSetup = () => {
endTime, endTime,
isValidating, isValidating,
lastSetupErrorMessages, lastSetupErrorMessages,
moduleDescriptor,
setEndTime, setEndTime,
setStartTime, setStartTime,
setValidatedIndices, setValidatedIndices,
@ -52,3 +54,7 @@ export const useLogEntryRateSetup = () => {
viewResults, viewResults,
}; };
}; };
export const [LogEntryRateSetupProvider, useLogEntryRateSetupContext] = createContainer(
useLogEntryRateSetup
);

View file

@ -5,7 +5,7 @@
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import React, { useEffect, useState, useCallback } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { isJobStatusWithResults } from '../../../../common/log_analysis';
import { LoadingPage } from '../../../components/loading_page'; import { LoadingPage } from '../../../components/loading_page';
import { import {
@ -17,10 +17,10 @@ import {
import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceErrorPage } from '../../../components/source_error_page';
import { SourceLoadingPage } from '../../../components/source_loading_page'; import { SourceLoadingPage } from '../../../components/source_loading_page';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { useLogSourceContext } from '../../../containers/logs/log_source'; import { useLogSourceContext } from '../../../containers/logs/log_source';
import { LogEntryCategoriesResultsContent } from './page_results_content'; import { LogEntryCategoriesResultsContent } from './page_results_content';
import { LogEntryCategoriesSetupContent } from './page_setup_content'; import { LogEntryCategoriesSetupContent } from './page_setup_content';
import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module';
import { LogEntryCategoriesSetupFlyout } from './setup_flyout'; import { LogEntryCategoriesSetupFlyout } from './setup_flyout';
export const LogEntryCategoriesPageContent = () => { export const LogEntryCategoriesPageContent = () => {
@ -50,13 +50,6 @@ export const LogEntryCategoriesPageContent = () => {
} }
}, [fetchJobStatus, hasLogAnalysisReadCapabilities]); }, [fetchJobStatus, hasLogAnalysisReadCapabilities]);
// Open flyout if there are no ML jobs
useEffect(() => {
if (setupStatus.type === 'required' && setupStatus.reason === 'missing') {
openFlyout();
}
}, [setupStatus, openFlyout]);
if (isLoading || isUninitialized) { if (isLoading || isUninitialized) {
return <SourceLoadingPage />; return <SourceLoadingPage />;
} else if (hasFailedLoadingSource) { } else if (hasFailedLoadingSource) {

View file

@ -5,10 +5,9 @@
*/ */
import React from 'react'; import React from 'react';
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { useLogSourceContext } from '../../../containers/logs/log_source'; import { useLogSourceContext } from '../../../containers/logs/log_source';
import { useKibanaSpaceId } from '../../../utils/use_kibana_space_id'; import { useKibanaSpaceId } from '../../../utils/use_kibana_space_id';
import { LogEntryCategoriesModuleProvider } from './use_log_entry_categories_module';
export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ children }) => { export const LogEntryCategoriesPageProviders: React.FunctionComponent = ({ children }) => {
const { sourceId, sourceConfiguration } = useLogSourceContext(); const { sourceId, sourceConfiguration } = useLogSourceContext();

View file

@ -12,17 +12,17 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { euiStyled, useTrackPageview } from '../../../../../observability/public'; import { euiStyled, useTrackPageview } from '../../../../../observability/public';
import { TimeRange } from '../../../../common/http_api/shared/time_range'; import { TimeRange } from '../../../../common/http_api/shared/time_range';
import { CategoryJobNoticesSection } from '../../../components/logging/log_analysis_job_status';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
import { useInterval } from '../../../hooks/use_interval'; import { useInterval } from '../../../hooks/use_interval';
import { CategoryJobNoticesSection } from './sections/notices/notices_section'; import { PageViewLogInContext } from '../stream/page_view_log_in_context';
import { TopCategoriesSection } from './sections/top_categories'; import { TopCategoriesSection } from './sections/top_categories';
import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module';
import { useLogEntryCategoriesResults } from './use_log_entry_categories_results'; import { useLogEntryCategoriesResults } from './use_log_entry_categories_results';
import { import {
StringTimeRange, StringTimeRange,
useLogEntryCategoriesResultsUrlState, useLogEntryCategoriesResultsUrlState,
} from './use_log_entry_categories_results_url_state'; } from './use_log_entry_categories_results_url_state';
import { PageViewLogInContext } from '../stream/page_view_log_in_context';
import { ViewLogInContext } from '../../../containers/logs/view_log_in_context';
const JOB_STATUS_POLLING_INTERVAL = 30000; const JOB_STATUS_POLLING_INTERVAL = 30000;
@ -39,9 +39,8 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
const { const {
fetchJobStatus, fetchJobStatus,
fetchModuleDefinition, fetchModuleDefinition,
moduleDescriptor,
setupStatus, setupStatus,
viewSetupForReconfiguration,
viewSetupForUpdate,
hasOutdatedJobConfigurations, hasOutdatedJobConfigurations,
hasOutdatedJobDefinitions, hasOutdatedJobDefinitions,
hasStoppedJobs, hasStoppedJobs,
@ -131,16 +130,6 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
[setAutoRefresh] [setAutoRefresh]
); );
const viewSetupFlyoutForReconfiguration = useCallback(() => {
viewSetupForReconfiguration();
onOpenSetup();
}, [onOpenSetup, viewSetupForReconfiguration]);
const viewSetupFlyoutForUpdate = useCallback(() => {
viewSetupForUpdate();
onOpenSetup();
}, [onOpenSetup, viewSetupForUpdate]);
const hasResults = useMemo(() => topLogEntryCategories.length > 0, [ const hasResults = useMemo(() => topLogEntryCategories.length > 0, [
topLogEntryCategories.length, topLogEntryCategories.length,
]); ]);
@ -210,8 +199,9 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} hasOutdatedJobDefinitions={hasOutdatedJobDefinitions}
hasStoppedJobs={hasStoppedJobs} hasStoppedJobs={hasStoppedJobs}
isFirstUse={isFirstUse} isFirstUse={isFirstUse}
onRecreateMlJobForReconfiguration={viewSetupFlyoutForReconfiguration} moduleName={moduleDescriptor.moduleName}
onRecreateMlJobForUpdate={viewSetupFlyoutForUpdate} onRecreateMlJobForReconfiguration={onOpenSetup}
onRecreateMlJobForUpdate={onOpenSetup}
qualityWarnings={categoryQualityWarnings} qualityWarnings={categoryQualityWarnings}
/> />
</EuiFlexItem> </EuiFlexItem>
@ -223,7 +213,7 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryC
isLoadingTopCategories={isLoadingTopLogEntryCategories} isLoadingTopCategories={isLoadingTopLogEntryCategories}
jobId={jobIds['log-entry-categories-count']} jobId={jobIds['log-entry-categories-count']}
onChangeDatasetSelection={setCategoryQueryDatasets} onChangeDatasetSelection={setCategoryQueryDatasets}
onRequestRecreateMlJob={viewSetupFlyoutForReconfiguration} onRequestRecreateMlJob={onOpenSetup}
selectedDatasets={categoryQueryDatasets} selectedDatasets={categoryQueryDatasets}
sourceId={sourceId} sourceId={sourceId}
timeRange={categoryQueryTimeRange.timeRange} timeRange={categoryQueryTimeRange.timeRange}

View file

@ -1,45 +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.
*/
interface ManyCategoriesWarningReason {
type: 'manyCategories';
categoriesDocumentRatio: number;
}
interface ManyDeadCategoriesWarningReason {
type: 'manyDeadCategories';
deadCategoriesRatio: number;
}
interface ManyRareCategoriesWarningReason {
type: 'manyRareCategories';
rareCategoriesRatio: number;
}
interface NoFrequentCategoriesWarningReason {
type: 'noFrequentCategories';
}
interface SingleCategoryWarningReason {
type: 'singleCategory';
}
export type CategoryQualityWarningReason =
| ManyCategoriesWarningReason
| ManyDeadCategoriesWarningReason
| ManyRareCategoriesWarningReason
| NoFrequentCategoriesWarningReason
| SingleCategoryWarningReason;
export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type'];
export interface CategoryQualityWarning {
type: 'categoryQualityWarning';
jobId: string;
reasons: CategoryQualityWarningReason[];
}
export type QualityWarning = CategoryQualityWarning;

View file

@ -4,23 +4,22 @@
* 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 React, { useMemo, useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { import {
EuiFlyout, EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody, EuiFlyoutBody,
EuiTitle, EuiFlyoutHeader,
EuiText,
EuiSpacer, EuiSpacer,
EuiSteps, EuiSteps,
EuiText,
EuiTitle,
} from '@elastic/eui'; } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useCallback, useMemo } from 'react';
import { import {
createInitialConfigurationStep, createInitialConfigurationStep,
createProcessStep, createProcessStep,
} from '../../../components/logging/log_analysis_setup'; } from '../../../components/logging/log_analysis_setup';
import { useLogEntryCategoriesSetup } from './use_log_entry_categories_setup'; import { useLogEntryCategoriesSetup } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
interface LogEntryCategoriesSetupFlyoutProps { interface LogEntryCategoriesSetupFlyoutProps {
isOpen: boolean; isOpen: boolean;

View file

@ -5,7 +5,7 @@
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import React, { useEffect, useState, useCallback } from 'react'; import React, { memo, useEffect, useCallback } from 'react';
import { isJobStatusWithResults } from '../../../../common/log_analysis'; import { isJobStatusWithResults } from '../../../../common/log_analysis';
import { LoadingPage } from '../../../components/loading_page'; import { LoadingPage } from '../../../components/loading_page';
import { import {
@ -14,16 +14,23 @@ import {
MissingSetupPrivilegesPrompt, MissingSetupPrivilegesPrompt,
SubscriptionSplashContent, SubscriptionSplashContent,
} from '../../../components/logging/log_analysis_setup'; } from '../../../components/logging/log_analysis_setup';
import {
LogAnalysisSetupFlyout,
useLogAnalysisSetupFlyoutStateContext,
} from '../../../components/logging/log_analysis_setup/setup_flyout';
import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceErrorPage } from '../../../components/source_error_page';
import { SourceLoadingPage } from '../../../components/source_loading_page'; import { SourceLoadingPage } from '../../../components/source_loading_page';
import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
import { useLogSourceContext } from '../../../containers/logs/log_source'; import { useLogSourceContext } from '../../../containers/logs/log_source';
import { LogEntryRateResultsContent } from './page_results_content'; import { LogEntryRateResultsContent } from './page_results_content';
import { LogEntryRateSetupContent } from './page_setup_content'; import { LogEntryRateSetupContent } from './page_setup_content';
import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; import { useInterval } from '../../../hooks/use_interval';
import { LogEntryRateSetupFlyout } from './setup_flyout';
export const LogEntryRatePageContent = () => { const JOB_STATUS_POLLING_INTERVAL = 30000;
export const LogEntryRatePageContent = memo(() => {
const { const {
hasFailedLoadingSource, hasFailedLoadingSource,
isLoading, isLoading,
@ -38,24 +45,52 @@ export const LogEntryRatePageContent = () => {
hasLogAnalysisSetupCapabilities, hasLogAnalysisSetupCapabilities,
} = useLogAnalysisCapabilitiesContext(); } = useLogAnalysisCapabilitiesContext();
const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryRateModuleContext(); const {
fetchJobStatus: fetchLogEntryCategoriesJobStatus,
fetchModuleDefinition: fetchLogEntryCategoriesModuleDefinition,
jobStatus: logEntryCategoriesJobStatus,
setupStatus: logEntryCategoriesSetupStatus,
} = useLogEntryCategoriesModuleContext();
const {
fetchJobStatus: fetchLogEntryRateJobStatus,
fetchModuleDefinition: fetchLogEntryRateModuleDefinition,
jobStatus: logEntryRateJobStatus,
setupStatus: logEntryRateSetupStatus,
} = useLogEntryRateModuleContext();
const [isFlyoutOpen, setIsFlyoutOpen] = useState<boolean>(false); const { showModuleList } = useLogAnalysisSetupFlyoutStateContext();
const openFlyout = useCallback(() => setIsFlyoutOpen(true), []);
const closeFlyout = useCallback(() => setIsFlyoutOpen(false), []); const fetchAllJobStatuses = useCallback(
() => Promise.all([fetchLogEntryCategoriesJobStatus(), fetchLogEntryRateJobStatus()]),
[fetchLogEntryCategoriesJobStatus, fetchLogEntryRateJobStatus]
);
useEffect(() => { useEffect(() => {
if (hasLogAnalysisReadCapabilities) { if (hasLogAnalysisReadCapabilities) {
fetchJobStatus(); fetchAllJobStatuses();
} }
}, [fetchJobStatus, hasLogAnalysisReadCapabilities]); }, [fetchAllJobStatuses, hasLogAnalysisReadCapabilities]);
// Open flyout if there are no ML jobs
useEffect(() => { useEffect(() => {
if (setupStatus.type === 'required' && setupStatus.reason === 'missing') { if (hasLogAnalysisReadCapabilities) {
openFlyout(); fetchLogEntryCategoriesModuleDefinition();
} }
}, [setupStatus, openFlyout]); }, [fetchLogEntryCategoriesModuleDefinition, hasLogAnalysisReadCapabilities]);
useEffect(() => {
if (hasLogAnalysisReadCapabilities) {
fetchLogEntryRateModuleDefinition();
}
}, [fetchLogEntryRateModuleDefinition, hasLogAnalysisReadCapabilities]);
useInterval(() => {
if (logEntryCategoriesSetupStatus.type !== 'pending' && hasLogAnalysisReadCapabilities) {
fetchLogEntryCategoriesJobStatus();
}
if (logEntryRateSetupStatus.type !== 'pending' && hasLogAnalysisReadCapabilities) {
fetchLogEntryRateJobStatus();
}
}, JOB_STATUS_POLLING_INTERVAL);
if (isLoading || isUninitialized) { if (isLoading || isUninitialized) {
return <SourceLoadingPage />; return <SourceLoadingPage />;
@ -65,7 +100,10 @@ export const LogEntryRatePageContent = () => {
return <SubscriptionSplashContent />; return <SubscriptionSplashContent />;
} else if (!hasLogAnalysisReadCapabilities) { } else if (!hasLogAnalysisReadCapabilities) {
return <MissingResultsPrivilegesPrompt />; return <MissingResultsPrivilegesPrompt />;
} else if (setupStatus.type === 'initializing') { } else if (
logEntryCategoriesSetupStatus.type === 'initializing' ||
logEntryRateSetupStatus.type === 'initializing'
) {
return ( return (
<LoadingPage <LoadingPage
message={i18n.translate('xpack.infra.logs.analysisPage.loadingMessage', { message={i18n.translate('xpack.infra.logs.analysisPage.loadingMessage', {
@ -73,13 +111,19 @@ export const LogEntryRatePageContent = () => {
})} })}
/> />
); );
} else if (setupStatus.type === 'unknown') { } else if (
return <LogAnalysisSetupStatusUnknownPrompt retry={fetchJobStatus} />; logEntryCategoriesSetupStatus.type === 'unknown' ||
} else if (isJobStatusWithResults(jobStatus['log-entry-rate'])) { logEntryRateSetupStatus.type === 'unknown'
) {
return <LogAnalysisSetupStatusUnknownPrompt retry={fetchAllJobStatuses} />;
} else if (
isJobStatusWithResults(logEntryCategoriesJobStatus['log-entry-categories-count']) ||
isJobStatusWithResults(logEntryRateJobStatus['log-entry-rate'])
) {
return ( return (
<> <>
<LogEntryRateResultsContent onOpenSetup={openFlyout} /> <LogEntryRateResultsContent />
<LogEntryRateSetupFlyout isOpen={isFlyoutOpen} onClose={closeFlyout} /> <LogAnalysisSetupFlyout />
</> </>
); );
} else if (!hasLogAnalysisSetupCapabilities) { } else if (!hasLogAnalysisSetupCapabilities) {
@ -87,9 +131,9 @@ export const LogEntryRatePageContent = () => {
} else { } else {
return ( return (
<> <>
<LogEntryRateSetupContent onOpenSetup={openFlyout} /> <LogEntryRateSetupContent onOpenSetup={showModuleList} />
<LogEntryRateSetupFlyout isOpen={isFlyoutOpen} onClose={closeFlyout} /> <LogAnalysisSetupFlyout />
</> </>
); );
} }
}; });

View file

@ -5,10 +5,11 @@
*/ */
import React from 'react'; import React from 'react';
import { LogAnalysisSetupFlyoutStateProvider } from '../../../components/logging/log_analysis_setup/setup_flyout';
import { LogEntryCategoriesModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { LogEntryRateModuleProvider } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
import { useLogSourceContext } from '../../../containers/logs/log_source'; import { useLogSourceContext } from '../../../containers/logs/log_source';
import { useKibanaSpaceId } from '../../../utils/use_kibana_space_id'; import { useKibanaSpaceId } from '../../../utils/use_kibana_space_id';
import { LogEntryRateModuleProvider } from './use_log_entry_rate_module';
export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => { export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => {
const { sourceId, sourceConfiguration } = useLogSourceContext(); const { sourceId, sourceConfiguration } = useLogSourceContext();
@ -21,7 +22,14 @@ export const LogEntryRatePageProviders: React.FunctionComponent = ({ children })
spaceId={spaceId} spaceId={spaceId}
timestampField={sourceConfiguration?.configuration.fields.timestamp ?? ''} timestampField={sourceConfiguration?.configuration.fields.timestamp ?? ''}
> >
{children} <LogEntryCategoriesModuleProvider
indexPattern={sourceConfiguration?.configuration.logAlias ?? ''}
sourceId={sourceId}
spaceId={spaceId}
timestampField={sourceConfiguration?.configuration.fields.timestamp ?? ''}
>
<LogAnalysisSetupFlyoutStateProvider>{children}</LogAnalysisSetupFlyoutStateProvider>
</LogEntryCategoriesModuleProvider>
</LogEntryRateModuleProvider> </LogEntryRateModuleProvider>
); );
}; };

View file

@ -11,19 +11,23 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { euiStyled, useTrackPageview } from '../../../../../observability/public'; import { euiStyled, useTrackPageview } from '../../../../../observability/public';
import { TimeRange } from '../../../../common/http_api/shared/time_range'; import { TimeRange } from '../../../../common/http_api/shared/time_range';
import { bucketSpan } from '../../../../common/log_analysis'; import { bucketSpan } from '../../../../common/log_analysis';
import { LogAnalysisJobProblemIndicator } from '../../../components/logging/log_analysis_job_status'; import {
CategoryJobNoticesSection,
LogAnalysisJobProblemIndicator,
} from '../../../components/logging/log_analysis_job_status';
import { useLogAnalysisSetupFlyoutStateContext } from '../../../components/logging/log_analysis_setup/setup_flyout';
import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories';
import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate';
import { useLogSourceContext } from '../../../containers/logs/log_source';
import { useInterval } from '../../../hooks/use_interval'; import { useInterval } from '../../../hooks/use_interval';
import { AnomaliesResults } from './sections/anomalies'; import { AnomaliesResults } from './sections/anomalies';
import { useLogEntryRateModuleContext } from './use_log_entry_rate_module';
import { useLogEntryRateResults } from './use_log_entry_rate_results';
import { useLogEntryAnomaliesResults } from './use_log_entry_anomalies_results'; import { useLogEntryAnomaliesResults } from './use_log_entry_anomalies_results';
import { useLogEntryRateResults } from './use_log_entry_rate_results';
import { import {
StringTimeRange, StringTimeRange,
useLogAnalysisResultsUrlState, useLogAnalysisResultsUrlState,
} from './use_log_entry_rate_results_url_state'; } from './use_log_entry_rate_results_url_state';
const JOB_STATUS_POLLING_INTERVAL = 30000;
export const SORT_DEFAULTS = { export const SORT_DEFAULTS = {
direction: 'desc' as const, direction: 'desc' as const,
field: 'anomalyScore' as const, field: 'anomalyScore' as const,
@ -33,28 +37,29 @@ export const PAGINATION_DEFAULTS = {
pageSize: 25, pageSize: 25,
}; };
interface LogEntryRateResultsContentProps { export const LogEntryRateResultsContent: React.FunctionComponent = () => {
onOpenSetup: () => void;
}
export const LogEntryRateResultsContent: React.FunctionComponent<LogEntryRateResultsContentProps> = ({
onOpenSetup,
}) => {
useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' }); useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' });
useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 }); useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 });
const { sourceId } = useLogSourceContext();
const { const {
fetchJobStatus, hasOutdatedJobConfigurations: hasOutdatedLogEntryRateJobConfigurations,
fetchModuleDefinition, hasOutdatedJobDefinitions: hasOutdatedLogEntryRateJobDefinitions,
setupStatus, hasStoppedJobs: hasStoppedLogEntryRateJobs,
viewSetupForReconfiguration, moduleDescriptor: logEntryRateModuleDescriptor,
viewSetupForUpdate, setupStatus: logEntryRateSetupStatus,
hasOutdatedJobConfigurations,
hasOutdatedJobDefinitions,
hasStoppedJobs,
sourceConfiguration: { sourceId },
} = useLogEntryRateModuleContext(); } = useLogEntryRateModuleContext();
const {
categoryQualityWarnings,
hasOutdatedJobConfigurations: hasOutdatedLogEntryCategoriesJobConfigurations,
hasOutdatedJobDefinitions: hasOutdatedLogEntryCategoriesJobDefinitions,
hasStoppedJobs: hasStoppedLogEntryCategoriesJobs,
moduleDescriptor: logEntryCategoriesModuleDescriptor,
setupStatus: logEntryCategoriesSetupStatus,
} = useLogEntryCategoriesModuleContext();
const { const {
timeRange: selectedTimeRange, timeRange: selectedTimeRange,
setTimeRange: setSelectedTimeRange, setTimeRange: setSelectedTimeRange,
@ -145,41 +150,33 @@ export const LogEntryRateResultsContent: React.FunctionComponent<LogEntryRateRes
[setAutoRefresh] [setAutoRefresh]
); );
const viewSetupFlyoutForReconfiguration = useCallback(() => { const { showModuleList, showModuleSetup } = useLogAnalysisSetupFlyoutStateContext();
viewSetupForReconfiguration();
onOpenSetup();
}, [viewSetupForReconfiguration, onOpenSetup]);
const viewSetupFlyoutForUpdate = useCallback(() => { const showLogEntryRateSetup = useCallback(() => showModuleSetup('logs_ui_analysis'), [
viewSetupForUpdate(); showModuleSetup,
onOpenSetup();
}, [viewSetupForUpdate, onOpenSetup]);
/* eslint-disable-next-line react-hooks/exhaustive-deps */
const hasResults = useMemo(() => (logEntryRate?.histogramBuckets?.length ?? 0) > 0, [
logEntryRate,
]); ]);
const showLogEntryCategoriesSetup = useCallback(() => showModuleSetup('logs_ui_categories'), [
showModuleSetup,
]);
const hasLogRateResults = (logEntryRate?.histogramBuckets?.length ?? 0) > 0;
const hasAnomalyResults = logEntryAnomalies.length > 0;
const isFirstUse = useMemo( const isFirstUse = useMemo(
() => () =>
((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) || ((logEntryCategoriesSetupStatus.type === 'skipped' &&
setupStatus.type === 'succeeded') && !!logEntryCategoriesSetupStatus.newlyCreated) ||
!hasResults, logEntryCategoriesSetupStatus.type === 'succeeded' ||
[hasResults, setupStatus] (logEntryRateSetupStatus.type === 'skipped' && !!logEntryRateSetupStatus.newlyCreated) ||
logEntryRateSetupStatus.type === 'succeeded') &&
!(hasLogRateResults || hasAnomalyResults),
[hasAnomalyResults, hasLogRateResults, logEntryCategoriesSetupStatus, logEntryRateSetupStatus]
); );
useEffect(() => { useEffect(() => {
getLogEntryRate(); getLogEntryRate();
}, [getLogEntryRate, queryTimeRange.lastChangedTime]); }, [getLogEntryRate, queryTimeRange.lastChangedTime]);
useEffect(() => {
fetchModuleDefinition();
}, [fetchModuleDefinition]);
useInterval(() => {
fetchJobStatus();
}, JOB_STATUS_POLLING_INTERVAL);
useInterval( useInterval(
() => { () => {
handleQueryTimeRangeChange({ handleQueryTimeRangeChange({
@ -209,12 +206,23 @@ export const LogEntryRateResultsContent: React.FunctionComponent<LogEntryRateRes
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<LogAnalysisJobProblemIndicator <LogAnalysisJobProblemIndicator
hasOutdatedJobConfigurations={hasOutdatedJobConfigurations} hasOutdatedJobConfigurations={hasOutdatedLogEntryRateJobConfigurations}
hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} hasOutdatedJobDefinitions={hasOutdatedLogEntryRateJobDefinitions}
hasStoppedJobs={hasStoppedJobs} hasStoppedJobs={hasStoppedLogEntryRateJobs}
isFirstUse={false /* the first use message is already shown by the section below */}
moduleName={logEntryRateModuleDescriptor.moduleName}
onRecreateMlJobForReconfiguration={showLogEntryRateSetup}
onRecreateMlJobForUpdate={showLogEntryRateSetup}
/>
<CategoryJobNoticesSection
hasOutdatedJobConfigurations={hasOutdatedLogEntryCategoriesJobConfigurations}
hasOutdatedJobDefinitions={hasOutdatedLogEntryCategoriesJobDefinitions}
hasStoppedJobs={hasStoppedLogEntryCategoriesJobs}
isFirstUse={isFirstUse} isFirstUse={isFirstUse}
onRecreateMlJobForReconfiguration={viewSetupFlyoutForReconfiguration} moduleName={logEntryCategoriesModuleDescriptor.moduleName}
onRecreateMlJobForUpdate={viewSetupFlyoutForUpdate} onRecreateMlJobForReconfiguration={showLogEntryCategoriesSetup}
onRecreateMlJobForUpdate={showLogEntryCategoriesSetup}
qualityWarnings={categoryQualityWarnings}
/> />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
@ -222,7 +230,7 @@ export const LogEntryRateResultsContent: React.FunctionComponent<LogEntryRateRes
<AnomaliesResults <AnomaliesResults
isLoadingLogRateResults={isLoading} isLoadingLogRateResults={isLoading}
isLoadingAnomaliesResults={isLoadingLogEntryAnomalies} isLoadingAnomaliesResults={isLoadingLogEntryAnomalies}
viewSetupForReconfiguration={viewSetupFlyoutForReconfiguration} onViewModuleList={showModuleList}
logEntryRateResults={logEntryRate} logEntryRateResults={logEntryRate}
anomalies={logEntryAnomalies} anomalies={logEntryAnomalies}
setTimeRange={handleChartTimeRangeChange} setTimeRange={handleChartTimeRangeChange}

View file

@ -4,18 +4,18 @@
* 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 { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiStat } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiTitle } from '@elastic/eui';
import numeral from '@elastic/numeral'; import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import React from 'react'; import React from 'react';
import { useMount } from 'react-use'; import { useMount } from 'react-use';
import { TimeRange } from '../../../../../../common/http_api/shared/time_range';
import { LogEntryAnomaly } from '../../../../../../common/http_api';
import { useLogEntryExamples } from '../../use_log_entry_examples';
import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples';
import { LogEntryExampleMessage, LogEntryExampleMessageHeaders } from './log_entry_example';
import { euiStyled } from '../../../../../../../observability/public'; import { euiStyled } from '../../../../../../../observability/public';
import { LogEntryAnomaly } from '../../../../../../common/http_api';
import { TimeRange } from '../../../../../../common/http_api/shared/time_range';
import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples';
import { useLogSourceContext } from '../../../../../containers/logs/log_source'; import { useLogSourceContext } from '../../../../../containers/logs/log_source';
import { useLogEntryExamples } from '../../use_log_entry_examples';
import { LogEntryExampleMessage, LogEntryExampleMessageHeaders } from './log_entry_example';
const EXAMPLE_COUNT = 5; const EXAMPLE_COUNT = 5;

View file

@ -20,16 +20,16 @@ import { TimeRange } from '../../../../../../common/http_api/shared/time_range';
import { getAnnotationsForAll, getLogEntryRateCombinedSeries } from '../helpers/data_formatters'; import { getAnnotationsForAll, getLogEntryRateCombinedSeries } from '../helpers/data_formatters';
import { AnomaliesChart } from './chart'; import { AnomaliesChart } from './chart';
import { AnomaliesTable } from './table'; import { AnomaliesTable } from './table';
import { RecreateJobButton } from '../../../../../components/logging/log_analysis_job_status'; import { ManageJobsButton } from '../../../../../components/logging/log_analysis_setup/manage_jobs_button';
import { import {
Page, ChangePaginationOptions,
ChangeSortOptions,
FetchNextPage, FetchNextPage,
FetchPreviousPage, FetchPreviousPage,
ChangeSortOptions,
ChangePaginationOptions,
SortOptions,
PaginationOptions,
LogEntryAnomalies, LogEntryAnomalies,
Page,
PaginationOptions,
SortOptions,
} from '../../use_log_entry_anomalies_results'; } from '../../use_log_entry_anomalies_results';
import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
@ -40,7 +40,7 @@ export const AnomaliesResults: React.FunctionComponent<{
anomalies: LogEntryAnomalies; anomalies: LogEntryAnomalies;
setTimeRange: (timeRange: TimeRange) => void; setTimeRange: (timeRange: TimeRange) => void;
timeRange: TimeRange; timeRange: TimeRange;
viewSetupForReconfiguration: () => void; onViewModuleList: () => void;
page: Page; page: Page;
fetchNextPage?: FetchNextPage; fetchNextPage?: FetchNextPage;
fetchPreviousPage?: FetchPreviousPage; fetchPreviousPage?: FetchPreviousPage;
@ -54,7 +54,7 @@ export const AnomaliesResults: React.FunctionComponent<{
logEntryRateResults, logEntryRateResults,
setTimeRange, setTimeRange,
timeRange, timeRange,
viewSetupForReconfiguration, onViewModuleList,
anomalies, anomalies,
changeSortOptions, changeSortOptions,
sortOptions, sortOptions,
@ -93,7 +93,7 @@ export const AnomaliesResults: React.FunctionComponent<{
</EuiTitle> </EuiTitle>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<RecreateJobButton onClick={viewSetupForReconfiguration} size="s" /> <ManageJobsButton onClick={onViewModuleList} size="s" />
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
<EuiSpacer size="m" /> <EuiSpacer size="m" />

View file

@ -42,10 +42,10 @@ export const LogsPageContent: React.FunctionComponent = () => {
pathname: '/stream', pathname: '/stream',
}; };
const logRateTab = { const anomaliesTab = {
app: 'logs', app: 'logs',
title: logRateTabTitle, title: anomaliesTabTitle,
pathname: '/log-rate', pathname: '/anomalies',
}; };
const logCategoriesTab = { const logCategoriesTab = {
@ -77,7 +77,7 @@ export const LogsPageContent: React.FunctionComponent = () => {
<AppNavigation aria-label={pageTitle}> <AppNavigation aria-label={pageTitle}>
<EuiFlexGroup gutterSize={'none'} alignItems={'center'}> <EuiFlexGroup gutterSize={'none'} alignItems={'center'}>
<EuiFlexItem> <EuiFlexItem>
<RoutedTabs tabs={[streamTab, logRateTab, logCategoriesTab, settingsTab]} /> <RoutedTabs tabs={[streamTab, anomaliesTab, logCategoriesTab, settingsTab]} />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<AlertDropdown /> <AlertDropdown />
@ -96,10 +96,11 @@ export const LogsPageContent: React.FunctionComponent = () => {
</AppNavigation> </AppNavigation>
<Switch> <Switch>
<Route path={streamTab.pathname} component={StreamPage} /> <Route path={streamTab.pathname} component={StreamPage} />
<Route path={logRateTab.pathname} component={LogEntryRatePage} /> <Route path={anomaliesTab.pathname} component={LogEntryRatePage} />
<Route path={logCategoriesTab.pathname} component={LogEntryCategoriesPage} /> <Route path={logCategoriesTab.pathname} component={LogEntryCategoriesPage} />
<Route path={settingsTab.pathname} component={LogsSettingsPage} /> <Route path={settingsTab.pathname} component={LogsSettingsPage} />
<RedirectWithQueryParams from={'/analysis'} to={logRateTab.pathname} exact /> <RedirectWithQueryParams from={'/analysis'} to={anomaliesTab.pathname} exact />
<RedirectWithQueryParams from={'/log-rate'} to={anomaliesTab.pathname} exact />
<RedirectWithQueryParams from={'/'} to={streamTab.pathname} exact /> <RedirectWithQueryParams from={'/'} to={streamTab.pathname} exact />
</Switch> </Switch>
</ColumnarPage> </ColumnarPage>
@ -114,8 +115,8 @@ const streamTabTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', {
defaultMessage: 'Stream', defaultMessage: 'Stream',
}); });
const logRateTabTitle = i18n.translate('xpack.infra.logs.index.logRateBetaBadgeTitle', { const anomaliesTabTitle = i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', {
defaultMessage: 'Log Rate', defaultMessage: 'Anomalies',
}); });
const logCategoriesTabTitle = i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { const logCategoriesTabTitle = i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', {

View file

@ -7469,14 +7469,9 @@
"xpack.infra.logs.alerting.threshold.fired": "実行", "xpack.infra.logs.alerting.threshold.fired": "実行",
"xpack.infra.logs.analysis.analyzeInMlButtonLabel": "ML で分析", "xpack.infra.logs.analysis.analyzeInMlButtonLabel": "ML で分析",
"xpack.infra.logs.analysis.anomaliesSectionLineSeriesName": "15 分ごとのログエントリー (平均)", "xpack.infra.logs.analysis.anomaliesSectionLineSeriesName": "15 分ごとのログエントリー (平均)",
"xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel": "異常を読み込み中",
"xpack.infra.logs.analysis.anomaliesSectionTitle": "異常", "xpack.infra.logs.analysis.anomaliesSectionTitle": "異常",
"xpack.infra.logs.analysis.anomalySectionNoDataBody": "時間範囲を調整する必要があるかもしれません。", "xpack.infra.logs.analysis.anomalySectionNoDataBody": "時間範囲を調整する必要があるかもしれません。",
"xpack.infra.logs.analysis.anomalySectionNoDataTitle": "表示するデータがありません。", "xpack.infra.logs.analysis.anomalySectionNoDataTitle": "表示するデータがありません。",
"xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutMessage": "異なるソース構成を使用して ML ジョブが作成されました。現在の構成を適用するにはジョブを再作成してください。これにより以前検出された異常が削除されます。",
"xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutTitle": "古い ML ジョブ構成",
"xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutMessage": "ML ジョブの新しいバージョンが利用可能です。新しいバージョンをデプロイするにはジョブを再作成してください。これにより以前検出された異常が削除されます。",
"xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutTitle": "古い ML ジョブ定義",
"xpack.infra.logs.analysis.jobStoppedCalloutMessage": "ML ジョブが手動またはリソース不足により停止しました。新しいログエントリーはジョブが再起動するまで処理されません。", "xpack.infra.logs.analysis.jobStoppedCalloutMessage": "ML ジョブが手動またはリソース不足により停止しました。新しいログエントリーはジョブが再起動するまで処理されません。",
"xpack.infra.logs.analysis.jobStoppedCalloutTitle": "ML ジョブが停止しました", "xpack.infra.logs.analysis.jobStoppedCalloutTitle": "ML ジョブが停止しました",
"xpack.infra.logs.analysis.missingMlResultsPrivilegesBody": "本機能は機械学習ジョブを利用し、そのステータスと結果にアクセスするためには、少なくとも{machineLearningUserRole}ロールが必要です。", "xpack.infra.logs.analysis.missingMlResultsPrivilegesBody": "本機能は機械学習ジョブを利用し、そのステータスと結果にアクセスするためには、少なくとも{machineLearningUserRole}ロールが必要です。",
@ -7517,7 +7512,6 @@
"xpack.infra.logs.highlights.highlightsPopoverButtonLabel": "ハイライト", "xpack.infra.logs.highlights.highlightsPopoverButtonLabel": "ハイライト",
"xpack.infra.logs.highlights.highlightTermsFieldLabel": "ハイライトする用語", "xpack.infra.logs.highlights.highlightTermsFieldLabel": "ハイライトする用語",
"xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "カテゴリー", "xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "カテゴリー",
"xpack.infra.logs.index.logRateBetaBadgeTitle": "ログレート",
"xpack.infra.logs.index.settingsTabTitle": "設定", "xpack.infra.logs.index.settingsTabTitle": "設定",
"xpack.infra.logs.index.streamTabTitle": "ストリーム", "xpack.infra.logs.index.streamTabTitle": "ストリーム",
"xpack.infra.logs.jumpToTailText": "最も新しいエントリーに移動", "xpack.infra.logs.jumpToTailText": "最も新しいエントリーに移動",

View file

@ -7474,14 +7474,9 @@
"xpack.infra.logs.alerting.threshold.fired": "已触发", "xpack.infra.logs.alerting.threshold.fired": "已触发",
"xpack.infra.logs.analysis.analyzeInMlButtonLabel": "在 ML 中分析", "xpack.infra.logs.analysis.analyzeInMlButtonLabel": "在 ML 中分析",
"xpack.infra.logs.analysis.anomaliesSectionLineSeriesName": "每 15 分钟日志条目数(平均值)", "xpack.infra.logs.analysis.anomaliesSectionLineSeriesName": "每 15 分钟日志条目数(平均值)",
"xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel": "正在加载异常",
"xpack.infra.logs.analysis.anomaliesSectionTitle": "异常", "xpack.infra.logs.analysis.anomaliesSectionTitle": "异常",
"xpack.infra.logs.analysis.anomalySectionNoDataBody": "您可能想调整时间范围。", "xpack.infra.logs.analysis.anomalySectionNoDataBody": "您可能想调整时间范围。",
"xpack.infra.logs.analysis.anomalySectionNoDataTitle": "没有可显示的数据。", "xpack.infra.logs.analysis.anomalySectionNoDataTitle": "没有可显示的数据。",
"xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutMessage": "创建 ML 作业时所使用的源配置不同。重新创建作业以应用当前配置。这将移除以前检测到的异常。",
"xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutTitle": "ML 作业配置已过期",
"xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutMessage": "ML 作业有更新的版本可用。重新创建作业以部署更新的版本。这将移除以前检测到的异常。",
"xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutTitle": "ML 作业定义已过期",
"xpack.infra.logs.analysis.jobStoppedCalloutMessage": "ML 作业已手动停止或由于缺乏资源而停止。作业重新启动后,才会处理新的日志条目。", "xpack.infra.logs.analysis.jobStoppedCalloutMessage": "ML 作业已手动停止或由于缺乏资源而停止。作业重新启动后,才会处理新的日志条目。",
"xpack.infra.logs.analysis.jobStoppedCalloutTitle": "ML 作业已停止", "xpack.infra.logs.analysis.jobStoppedCalloutTitle": "ML 作业已停止",
"xpack.infra.logs.analysis.missingMlResultsPrivilegesBody": "此功能使用 Machine Learning 作业,要访问这些作业的状态和结果,至少需要 {machineLearningUserRole} 角色。", "xpack.infra.logs.analysis.missingMlResultsPrivilegesBody": "此功能使用 Machine Learning 作业,要访问这些作业的状态和结果,至少需要 {machineLearningUserRole} 角色。",
@ -7522,7 +7517,6 @@
"xpack.infra.logs.highlights.highlightsPopoverButtonLabel": "突出显示", "xpack.infra.logs.highlights.highlightsPopoverButtonLabel": "突出显示",
"xpack.infra.logs.highlights.highlightTermsFieldLabel": "要突出显示的词", "xpack.infra.logs.highlights.highlightTermsFieldLabel": "要突出显示的词",
"xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "类别", "xpack.infra.logs.index.logCategoriesBetaBadgeTitle": "类别",
"xpack.infra.logs.index.logRateBetaBadgeTitle": "日志速率",
"xpack.infra.logs.index.settingsTabTitle": "设置", "xpack.infra.logs.index.settingsTabTitle": "设置",
"xpack.infra.logs.index.streamTabTitle": "流式传输", "xpack.infra.logs.index.streamTabTitle": "流式传输",
"xpack.infra.logs.jumpToTailText": "跳到最近的条目", "xpack.infra.logs.jumpToTailText": "跳到最近的条目",