Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Logs UI] Add category data quality warning based on ML job stats (#60551) #61687

Merged
merged 4 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions x-pack/plugins/infra/common/log_analysis/log_analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,28 @@ export type JobStatus =
| 'finished'
| '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 =
| 'initializing' // acquiring job statuses to determine setup status
| 'unknown' // job status could not be acquired (failed request etc)
| 'required' // jobs are missing
| 'requiredForReconfiguration' // the configurations don't match the source configurations
| 'requiredForUpdate' // the definitions don't match the module definitions
| 'pending' // In the process of setting up the module for the first time or retrying, waiting for response
| 'succeeded' // setup succeeded, notifying user
| 'failed' // setup failed, notifying user
| 'hiddenAfterSuccess' // hide the setup screen and we show the results for the first time
| 'skipped' // setup hidden because the module is in a correct state already
| 'skippedButReconfigurable' // setup hidden even though the job configurations are outdated
| 'skippedButUpdatable'; // setup hidden even though the job definitions are outdated
| { type: 'initializing' } // acquiring job statuses to determine setup status
| { type: 'unknown' } // job status could not be acquired (failed request etc)
| {
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: 'succeeded' } // setup succeeded, notifying user
| {
type: 'failed';
reasons: string[];
} // setup failed, notifying user
| {
type: 'skipped';
newlyCreated?: boolean;
}; // setup is hidden

/**
* Maps a job status to the possibility that results have already been produced
Expand All @@ -43,9 +52,7 @@ export const isHealthyJobStatus = (jobStatus: JobStatus) =>
* produced before this state was reached.
*/
export const isSetupStatusWithResults = (setupStatus: SetupStatus) =>
['skipped', 'hiddenAfterSuccess', 'skippedButReconfigurable', 'skippedButUpdatable'].includes(
setupStatus
);
setupStatus.type === 'skipped';

const KIBANA_SAMPLE_DATA_INDICES = ['kibana_sample_data_logs*'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,36 @@

import React from 'react';

import { JobStatus, SetupStatus } from '../../../../common/log_analysis';
import { JobConfigurationOutdatedCallout } from './job_configuration_outdated_callout';
import { JobDefinitionOutdatedCallout } from './job_definition_outdated_callout';
import { JobStoppedCallout } from './job_stopped_callout';
import { FirstUseCallout } from '../log_analysis_results';

export const LogAnalysisJobProblemIndicator: React.FC<{
jobStatus: JobStatus;
setupStatus: SetupStatus;
hasOutdatedJobConfigurations: boolean;
hasOutdatedJobDefinitions: boolean;
hasStoppedJobs: boolean;
isFirstUse: boolean;
onRecreateMlJobForReconfiguration: () => void;
onRecreateMlJobForUpdate: () => void;
}> = ({ jobStatus, setupStatus, onRecreateMlJobForReconfiguration, onRecreateMlJobForUpdate }) => {
if (isStopped(jobStatus)) {
return <JobStoppedCallout />;
} else if (isUpdatable(setupStatus)) {
return <JobDefinitionOutdatedCallout onRecreateMlJob={onRecreateMlJobForUpdate} />;
} else if (isReconfigurable(setupStatus)) {
return <JobConfigurationOutdatedCallout onRecreateMlJob={onRecreateMlJobForReconfiguration} />;
}

return null; // no problem to indicate
}> = ({
hasOutdatedJobConfigurations,
hasOutdatedJobDefinitions,
hasStoppedJobs,
isFirstUse,
onRecreateMlJobForReconfiguration,
onRecreateMlJobForUpdate,
}) => {
return (
<>
{hasOutdatedJobDefinitions ? (
<JobDefinitionOutdatedCallout onRecreateMlJob={onRecreateMlJobForUpdate} />
) : null}
{hasOutdatedJobConfigurations ? (
<JobConfigurationOutdatedCallout onRecreateMlJob={onRecreateMlJobForReconfiguration} />
) : null}
{hasStoppedJobs ? <JobStoppedCallout /> : null}
{isFirstUse ? <FirstUseCallout /> : null}
</>
);
};

const isStopped = (jobStatus: JobStatus) => jobStatus === 'stopped';

const isUpdatable = (setupStatus: SetupStatus) => setupStatus === 'skippedButUpdatable';

const isReconfigurable = (setupStatus: SetupStatus) => setupStatus === 'skippedButReconfigurable';

export const jobHasProblem = (jobStatus: JobStatus, setupStatus: SetupStatus) =>
isStopped(jobStatus) || isUpdatable(setupStatus) || isReconfigurable(setupStatus);
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const InitialConfigurationStep: React.FunctionComponent<InitialConfigurat
setValidatedIndices,
validationErrors = [],
}: InitialConfigurationStepProps) => {
const disabled = useMemo(() => !editableFormStatus.includes(setupStatus), [setupStatus]);
const disabled = useMemo(() => !editableFormStatus.includes(setupStatus.type), [setupStatus]);

return (
<>
Expand All @@ -72,12 +72,7 @@ export const InitialConfigurationStep: React.FunctionComponent<InitialConfigurat
);
};

const editableFormStatus = [
'required',
'requiredForReconfiguration',
'requiredForUpdate',
'failed',
];
const editableFormStatus = ['required', 'failed'];

const errorCalloutTitle = i18n.translate(
'xpack.infra.analysisSetup.steps.initialConfigurationStep.errorCalloutTitle',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export const createProcessStep = (props: ProcessStepProps): EuiContainedStepProp
title: processStepTitle,
children: <ProcessStep {...props} />,
status:
props.setupStatus === 'pending'
props.setupStatus.type === 'pending'
? 'incomplete'
: props.setupStatus === 'failed'
: props.setupStatus.type === 'failed'
? 'danger'
: props.setupStatus === 'succeeded'
: props.setupStatus.type === 'succeeded'
? 'complete'
: undefined,
});
Expand All @@ -55,7 +55,7 @@ export const ProcessStep: React.FunctionComponent<ProcessStepProps> = ({
}) => {
return (
<EuiText size="s">
{setupStatus === 'pending' ? (
{setupStatus.type === 'pending' ? (
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
Expand All @@ -67,7 +67,7 @@ export const ProcessStep: React.FunctionComponent<ProcessStepProps> = ({
/>
</EuiFlexItem>
</EuiFlexGroup>
) : setupStatus === 'failed' ? (
) : setupStatus.type === 'failed' ? (
<>
<FormattedMessage
id="xpack.infra.analysisSetup.steps.setupProcess.failureText"
Expand All @@ -87,7 +87,7 @@ export const ProcessStep: React.FunctionComponent<ProcessStepProps> = ({
/>
</EuiButton>
</>
) : setupStatus === 'succeeded' ? (
) : setupStatus.type === 'succeeded' ? (
<>
<FormattedMessage
id="xpack.infra.analysisSetup.steps.setupProcess.successText"
Expand All @@ -101,7 +101,8 @@ export const ProcessStep: React.FunctionComponent<ProcessStepProps> = ({
/>
</EuiButton>
</>
) : setupStatus === 'requiredForUpdate' || setupStatus === 'requiredForReconfiguration' ? (
) : setupStatus.type === 'required' &&
(setupStatus.reason === 'update' || setupStatus.reason === 'reconfiguration') ? (
<RecreateMLJobsButton isDisabled={!isConfigurationValid} onClick={cleanUpAndSetUp} />
) : (
<CreateMLJobsButton isDisabled={!isConfigurationValid} onClick={setUp} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ const jobStateRT = rt.keyof({
opening: null,
});

const jobCategorizationStatusRT = rt.keyof({
ok: null,
warn: null,
});

const jobModelSizeStatsRT = rt.type({
categorization_status: jobCategorizationStatusRT,
categorized_doc_count: rt.number,
dead_category_count: rt.number,
frequent_category_count: rt.number,
rare_category_count: rt.number,
total_category_count: rt.number,
});

export type JobModelSizeStats = rt.TypeOf<typeof jobModelSizeStatsRT>;

export const jobSummaryRT = rt.intersection([
rt.type({
id: rt.string,
Expand All @@ -65,6 +81,7 @@ export const jobSummaryRT = rt.intersection([
fullJob: rt.partial({
custom_settings: jobCustomSettingsRT,
finished_time: rt.number,
model_size_stats: jobModelSizeStatsRT,
}),
}),
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
export * from './log_analysis_capabilities';
export * from './log_analysis_cleanup';
export * from './log_analysis_module';
export * from './log_analysis_module_configuration';
export * from './log_analysis_module_definition';
export * from './log_analysis_module_status';
export * from './log_analysis_module_types';
export * from './log_analysis_setup_state';

export { JobModelSizeStats, JobSummary } from './api/ml_get_jobs_summary_api';
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useMemo } from 'react';

import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import { useModuleStatus } from './log_analysis_module_status';
Expand All @@ -17,36 +17,10 @@ export const useLogAnalysisModule = <JobType extends string>({
sourceConfiguration: ModuleSourceConfiguration;
moduleDescriptor: ModuleDescriptor<JobType>;
}) => {
const { spaceId, sourceId, timestampField, indices } = sourceConfiguration;
const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes, {
bucketSpan: moduleDescriptor.bucketSpan,
indexPattern: indices.join(','),
timestampField,
});
const { spaceId, sourceId, timestampField } = sourceConfiguration;
const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes);

const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
dispatchModuleStatus({ type: 'fetchingModuleDefinition' });
return await moduleDescriptor.getModuleDefinition();
},
onResolve: response => {
dispatchModuleStatus({
type: 'fetchedModuleDefinition',
spaceId,
sourceId,
moduleDefinition: response,
});
},
onReject: () => {
dispatchModuleStatus({ type: 'failedFetchingModuleDefinition' });
},
},
[moduleDescriptor.getModuleDefinition, spaceId, sourceId]
);

const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise(
const [, fetchJobStatus] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
Expand All @@ -68,12 +42,6 @@ export const useLogAnalysisModule = <JobType extends string>({
[spaceId, sourceId]
);

const isLoadingModuleStatus = useMemo(
() =>
fetchJobStatusRequest.state === 'pending' || fetchModuleDefinitionRequest.state === 'pending',
[fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state]
);

const [, setUpModule] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
Expand All @@ -83,15 +51,24 @@ export const useLogAnalysisModule = <JobType extends string>({
end: number | undefined
) => {
dispatchModuleStatus({ type: 'startedSetup' });
return await moduleDescriptor.setUpModule(start, end, {
const setupResult = await moduleDescriptor.setUpModule(start, end, {
indices: selectedIndices,
sourceId,
spaceId,
timestampField,
});
const jobSummaries = await moduleDescriptor.getJobSummary(spaceId, sourceId);
return { setupResult, jobSummaries };
},
onResolve: ({ datafeeds, jobs }) => {
dispatchModuleStatus({ type: 'finishedSetup', datafeeds, jobs, spaceId, sourceId });
onResolve: ({ setupResult: { datafeeds, jobs }, jobSummaries }) => {
dispatchModuleStatus({
type: 'finishedSetup',
datafeedSetupResults: datafeeds,
jobSetupResults: jobs,
jobSummaries,
spaceId,
sourceId,
});
},
onReject: () => {
dispatchModuleStatus({ type: 'failedSetup' });
Expand Down Expand Up @@ -146,36 +123,14 @@ export const useLogAnalysisModule = <JobType extends string>({
sourceId,
]);

useEffect(() => {
dispatchModuleStatus({
type: 'updatedSourceConfiguration',
spaceId,
sourceId,
sourceConfiguration: {
timestampField,
indexPattern: indices.join(','),
bucketSpan: moduleDescriptor.bucketSpan,
},
});
}, [
dispatchModuleStatus,
indices,
moduleDescriptor.bucketSpan,
sourceConfiguration,
sourceId,
spaceId,
timestampField,
]);

return {
cleanUpAndSetUpModule,
cleanUpModule,
fetchJobStatus,
fetchModuleDefinition,
isCleaningUp,
isLoadingModuleStatus,
jobIds,
jobStatus: moduleStatus.jobStatus,
jobSummaries: moduleStatus.jobSummaries,
lastSetupErrorMessages: moduleStatus.lastSetupErrorMessages,
moduleDescriptor,
setUpModule,
Expand Down
Loading