Skip to content

Commit

Permalink
[7.x] [Logs UI] Add dataset-specific categorization warnings (#75351) (
Browse files Browse the repository at this point in the history
…#78437)

Backports the following commits to 7.x:
 - [Logs UI] Add dataset-specific categorization warnings (#75351)
  • Loading branch information
weltenwort authored Sep 24, 2020
1 parent 25936f7 commit 43fd2d7
Show file tree
Hide file tree
Showing 38 changed files with 1,027 additions and 273 deletions.
2 changes: 1 addition & 1 deletion src/dev/storybook/aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const storybookAliases = {
codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts',
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/scripts/storybook.js',
embeddable: 'src/plugins/embeddable/scripts/storybook.js',
infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js',
infra: 'x-pack/plugins/infra/scripts/storybook.js',
security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js',
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js',
observability: 'x-pack/plugins/observability/scripts/storybook.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

export * from './log_entry_categories';
export * from './log_entry_category_datasets';
export * from './log_entry_category_datasets_stats';
export * from './log_entry_category_examples';
export * from './log_entry_rate';
export * from './log_entry_examples';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 * as rt from 'io-ts';

import { timeRangeRT, routeTimingMetadataRT } from '../../shared';

export const LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH =
'/api/infra/log_analysis/results/latest_log_entry_category_datasets_stats';

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

export type CategorizerStatus = rt.TypeOf<typeof categorizerStatusRT>;

/**
* request
*/

export const getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT = rt.type({
data: rt.type({
// the ids of the categorization jobs
jobIds: rt.array(rt.string),
// the time range to fetch the category datasets stats for
timeRange: timeRangeRT,
// the categorizer statuses to include stats for, empty means all
includeCategorizerStatuses: rt.array(categorizerStatusRT),
}),
});

export type GetLatestLogEntryCategoryDatasetsStatsRequestPayload = rt.TypeOf<
typeof getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT
>;

/**
* response
*/

const logEntryCategoriesDatasetStatsRT = rt.type({
categorization_status: categorizerStatusRT,
categorized_doc_count: rt.number,
dataset: rt.string,
dead_category_count: rt.number,
failed_category_count: rt.number,
frequent_category_count: rt.number,
job_id: rt.string,
log_time: rt.number,
rare_category_count: rt.number,
total_category_count: rt.number,
});

export type LogEntryCategoriesDatasetStats = rt.TypeOf<typeof logEntryCategoriesDatasetStatsRT>;

export const getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT = rt.intersection([
rt.type({
data: rt.type({
datasetStats: rt.array(logEntryCategoriesDatasetStatsRT),
}),
}),
rt.partial({
timing: routeTimingMetadataRT,
}),
]);

export type GetLatestLogEntryCategoryDatasetsStatsSuccessResponsePayload = rt.TypeOf<
typeof getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT
>;
1 change: 1 addition & 0 deletions x-pack/plugins/infra/common/log_analysis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

export * from './log_analysis';
export * from './log_analysis_quality';
export * from './log_analysis_results';
export * from './log_entry_rate_analysis';
export * from './log_entry_categories_analysis';
Expand Down
42 changes: 42 additions & 0 deletions x-pack/plugins/infra/common/log_analysis/log_analysis_quality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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;
dataset: string;
reasons: CategoryQualityWarningReason[];
}

export type QualityWarning = CategoryQualityWarning;
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const JobConfigurationOutdatedCallout: React.FC<{
values={{
moduleName,
}}
tagName="p"
/>
</RecreateJobCallout>
);
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const JobDefinitionOutdatedCallout: React.FC<{
values={{
moduleName,
}}
tagName="p"
/>
</RecreateJobCallout>
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import React from 'react';
import { QualityWarning } from '../../../containers/logs/log_analysis/log_analysis_module_types';
import { QualityWarning } from '../../../../common/log_analysis';
import { LogAnalysisJobProblemIndicator } from './log_analysis_job_problem_indicator';
import { CategoryQualityWarnings } from './quality_warning_notices';

Expand Down Expand Up @@ -41,6 +41,10 @@ export const CategoryJobNoticesSection: React.FC<{
onRecreateMlJobForReconfiguration={onRecreateMlJobForReconfiguration}
onRecreateMlJobForUpdate={onRecreateMlJobForUpdate}
/>
<CategoryQualityWarnings qualityWarnings={qualityWarnings} />
<CategoryQualityWarnings
hasSetupCapabilities={hasSetupCapabilities}
qualityWarnings={qualityWarnings}
onRecreateMlJob={onRecreateMlJobForReconfiguration}
/>
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { EuiThemeProvider } from '../../../../../observability/public';
import { QualityWarning } from '../../../../common/log_analysis';
import { CategoryQualityWarnings } from './quality_warning_notices';

storiesOf('infra/logAnalysis/CategoryQualityWarnings', module)
.addDecorator((renderStory) => <EuiThemeProvider>{renderStory()}</EuiThemeProvider>)
.add('Partitioned warnings', () => {
return (
<CategoryQualityWarnings
hasSetupCapabilities={true}
onRecreateMlJob={action('on-recreate-ml-job')}
qualityWarnings={partitionedQualityWarnings}
/>
);
})
.add('Unpartitioned warnings', () => {
return (
<CategoryQualityWarnings
hasSetupCapabilities={true}
onRecreateMlJob={action('on-recreate-ml-job')}
qualityWarnings={unpartitionedQualityWarnings}
/>
);
});

const partitionedQualityWarnings: QualityWarning[] = [
{
type: 'categoryQualityWarning',
jobId: 'theMlJobId',
dataset: 'first.dataset',
reasons: [
{ type: 'singleCategory' },
{ type: 'manyRareCategories', rareCategoriesRatio: 0.95 },
{ type: 'manyCategories', categoriesDocumentRatio: 0.7 },
],
},
{
type: 'categoryQualityWarning',
jobId: 'theMlJobId',
dataset: 'second.dataset',
reasons: [
{ type: 'noFrequentCategories' },
{ type: 'manyDeadCategories', deadCategoriesRatio: 0.7 },
],
},
];

const unpartitionedQualityWarnings: QualityWarning[] = [
{
type: 'categoryQualityWarning',
jobId: 'theMlJobId',
dataset: '',
reasons: [
{ type: 'singleCategory' },
{ type: 'manyRareCategories', rareCategoriesRatio: 0.95 },
{ type: 'manyCategories', categoriesDocumentRatio: 0.7 },
],
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,89 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiCallOut } from '@elastic/eui';
import {
EuiAccordion,
EuiDescriptionList,
EuiDescriptionListDescription,
EuiDescriptionListTitle,
EuiSpacer,
htmlIdGenerator,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import type {
import groupBy from 'lodash/groupBy';
import React, { Fragment, useState } from 'react';
import { euiStyled } from '../../../../../observability/public';
import {
CategoryQualityWarning,
CategoryQualityWarningReason,
QualityWarning,
} from '../../../containers/logs/log_analysis/log_analysis_module_types';
getFriendlyNameForPartitionId,
} from '../../../../common/log_analysis';
import { RecreateJobCallout } from './recreate_job_callout';

export const CategoryQualityWarnings: React.FC<{ qualityWarnings: QualityWarning[] }> = ({
qualityWarnings,
}) => (
<>
{qualityWarnings.map((qualityWarning, qualityWarningIndex) => (
<EuiCallOut
key={`${qualityWarningIndex}`}
title={categoryQualityWarningCalloutTitle}
color="warning"
iconType="alert"
>
<p>
export const CategoryQualityWarnings: React.FC<{
hasSetupCapabilities: boolean;
onRecreateMlJob: () => void;
qualityWarnings: CategoryQualityWarning[];
}> = ({ hasSetupCapabilities, onRecreateMlJob, qualityWarnings }) => {
const [detailAccordionId] = useState(htmlIdGenerator()());

const categoryQualityWarningsByJob = groupBy(qualityWarnings, 'jobId');

return (
<>
{Object.entries(categoryQualityWarningsByJob).map(([jobId, qualityWarningsForJob]) => (
<RecreateJobCallout
hasSetupCapabilities={hasSetupCapabilities}
key={`quality-warnings-callout-${jobId}`}
onRecreateMlJob={onRecreateMlJob}
title={categoryQualityWarningCalloutTitle}
>
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.categoryQualityWarningCalloutMessage"
defaultMessage="While analyzing the log messages we've detected some problems which might indicate a reduced quality of the categorization results."
defaultMessage="While analyzing the log messages we've detected some problems which might indicate a reduced quality of the categorization results. Consider excluding the respective datasets from the analysis."
tagName="p"
/>
</p>
<ul>
{qualityWarning.reasons.map((reason, reasonIndex) => (
<li key={`${reasonIndex}`}>
<CategoryQualityWarningReasonDescription reason={reason} />
</li>
))}
</ul>
</EuiCallOut>
))}
</>
);
<EuiAccordion
id={detailAccordionId}
buttonContent={
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.categoryQualityWarningDetailsAccordionButtonLabel"
defaultMessage="Details"
/>
}
paddingSize="m"
>
<EuiDescriptionList>
{qualityWarningsForJob.flatMap((qualityWarning) => (
<Fragment key={`item-${getFriendlyNameForPartitionId(qualityWarning.dataset)}`}>
<EuiDescriptionListTitle data-test-subj={`title-${qualityWarning.dataset}`}>
{getFriendlyNameForPartitionId(qualityWarning.dataset)}
</EuiDescriptionListTitle>
{qualityWarning.reasons.map((reason) => (
<QualityWarningReasonDescription
key={`description-${reason.type}-${qualityWarning.dataset}`}
data-test-subj={`description-${reason.type}-${qualityWarning.dataset}`}
>
<CategoryQualityWarningReasonDescription reason={reason} />
</QualityWarningReasonDescription>
))}
</Fragment>
))}
</EuiDescriptionList>
</EuiAccordion>
<EuiSpacer size="l" />
</RecreateJobCallout>
))}
</>
);
};

const QualityWarningReasonDescription = euiStyled(EuiDescriptionListDescription)`
display: list-item;
list-style-type: disc;
margin-left: ${(props) => props.theme.eui.paddingSizes.m};
`;

const categoryQualityWarningCalloutTitle = i18n.translate(
'xpack.infra.logs.logEntryCategories.categoryQUalityWarningCalloutTitle',
Expand All @@ -49,15 +95,15 @@ const categoryQualityWarningCalloutTitle = i18n.translate(
}
);

const CategoryQualityWarningReasonDescription: React.FC<{
export const CategoryQualityWarningReasonDescription: React.FC<{
reason: CategoryQualityWarningReason;
}> = ({ reason }) => {
switch (reason.type) {
case 'singleCategory':
return (
<FormattedMessage
id="xpack.infra.logs.logEntryCategories.singleCategoryWarningReasonDescription"
defaultMessage="The analysis couldn't extract more than a single category from the log message."
defaultMessage="The analysis couldn't extract more than a single category from the log messages."
/>
);
case 'manyRareCategories':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const RecreateJobCallout: React.FC<{
title?: React.ReactNode;
}> = ({ children, hasSetupCapabilities, onRecreateMlJob, title }) => (
<EuiCallOut color="warning" iconType="alert" title={title}>
<p>{children}</p>
{children}
<RecreateJobButton
color="warning"
hasSetupCapabilities={hasSetupCapabilities}
Expand Down
Loading

0 comments on commit 43fd2d7

Please sign in to comment.