Skip to content

Commit 3f2e9f7

Browse files
weltenwortafgomez
andauthored
[Logs UI] Add dataset-specific categorization warnings (#75351)
This adds dataset-specific categorization warnings for the categorization module. The warnings are displayed in call-outs on the relevant tabs as well as the job setup screens if a prior job with warnings exists. To that end this also changes the categorization job configuration to enable the partitioned categorization mode. Co-authored-by: Alejandro Fernández Gómez <antarticonorte@gmail.com>
1 parent 5ff0c00 commit 3f2e9f7

38 files changed

+1027
-273
lines changed

src/dev/storybook/aliases.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const storybookAliases = {
2323
codeeditor: 'src/plugins/kibana_react/public/code_editor/scripts/storybook.ts',
2424
dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/scripts/storybook.js',
2525
embeddable: 'src/plugins/embeddable/scripts/storybook.js',
26-
infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js',
26+
infra: 'x-pack/plugins/infra/scripts/storybook.js',
2727
security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js',
2828
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js',
2929
observability: 'x-pack/plugins/observability/scripts/storybook.js',

x-pack/plugins/infra/common/http_api/log_analysis/results/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
export * from './log_entry_categories';
88
export * from './log_entry_category_datasets';
9+
export * from './log_entry_category_datasets_stats';
910
export * from './log_entry_category_examples';
1011
export * from './log_entry_rate';
1112
export * from './log_entry_examples';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import * as rt from 'io-ts';
8+
9+
import { timeRangeRT, routeTimingMetadataRT } from '../../shared';
10+
11+
export const LOG_ANALYSIS_GET_LATEST_LOG_ENTRY_CATEGORY_DATASETS_STATS_PATH =
12+
'/api/infra/log_analysis/results/latest_log_entry_category_datasets_stats';
13+
14+
const categorizerStatusRT = rt.keyof({
15+
ok: null,
16+
warn: null,
17+
});
18+
19+
export type CategorizerStatus = rt.TypeOf<typeof categorizerStatusRT>;
20+
21+
/**
22+
* request
23+
*/
24+
25+
export const getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT = rt.type({
26+
data: rt.type({
27+
// the ids of the categorization jobs
28+
jobIds: rt.array(rt.string),
29+
// the time range to fetch the category datasets stats for
30+
timeRange: timeRangeRT,
31+
// the categorizer statuses to include stats for, empty means all
32+
includeCategorizerStatuses: rt.array(categorizerStatusRT),
33+
}),
34+
});
35+
36+
export type GetLatestLogEntryCategoryDatasetsStatsRequestPayload = rt.TypeOf<
37+
typeof getLatestLogEntryCategoryDatasetsStatsRequestPayloadRT
38+
>;
39+
40+
/**
41+
* response
42+
*/
43+
44+
const logEntryCategoriesDatasetStatsRT = rt.type({
45+
categorization_status: categorizerStatusRT,
46+
categorized_doc_count: rt.number,
47+
dataset: rt.string,
48+
dead_category_count: rt.number,
49+
failed_category_count: rt.number,
50+
frequent_category_count: rt.number,
51+
job_id: rt.string,
52+
log_time: rt.number,
53+
rare_category_count: rt.number,
54+
total_category_count: rt.number,
55+
});
56+
57+
export type LogEntryCategoriesDatasetStats = rt.TypeOf<typeof logEntryCategoriesDatasetStatsRT>;
58+
59+
export const getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT = rt.intersection([
60+
rt.type({
61+
data: rt.type({
62+
datasetStats: rt.array(logEntryCategoriesDatasetStatsRT),
63+
}),
64+
}),
65+
rt.partial({
66+
timing: routeTimingMetadataRT,
67+
}),
68+
]);
69+
70+
export type GetLatestLogEntryCategoryDatasetsStatsSuccessResponsePayload = rt.TypeOf<
71+
typeof getLatestLogEntryCategoryDatasetsStatsSuccessResponsePayloadRT
72+
>;

x-pack/plugins/infra/common/log_analysis/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
export * from './log_analysis';
8+
export * from './log_analysis_quality';
89
export * from './log_analysis_results';
910
export * from './log_entry_rate_analysis';
1011
export * from './log_entry_categories_analysis';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
interface ManyCategoriesWarningReason {
8+
type: 'manyCategories';
9+
categoriesDocumentRatio: number;
10+
}
11+
interface ManyDeadCategoriesWarningReason {
12+
type: 'manyDeadCategories';
13+
deadCategoriesRatio: number;
14+
}
15+
interface ManyRareCategoriesWarningReason {
16+
type: 'manyRareCategories';
17+
rareCategoriesRatio: number;
18+
}
19+
interface NoFrequentCategoriesWarningReason {
20+
type: 'noFrequentCategories';
21+
}
22+
interface SingleCategoryWarningReason {
23+
type: 'singleCategory';
24+
}
25+
26+
export type CategoryQualityWarningReason =
27+
| ManyCategoriesWarningReason
28+
| ManyDeadCategoriesWarningReason
29+
| ManyRareCategoriesWarningReason
30+
| NoFrequentCategoriesWarningReason
31+
| SingleCategoryWarningReason;
32+
33+
export type CategoryQualityWarningReasonType = CategoryQualityWarningReason['type'];
34+
35+
export interface CategoryQualityWarning {
36+
type: 'categoryQualityWarning';
37+
jobId: string;
38+
dataset: string;
39+
reasons: CategoryQualityWarningReason[];
40+
}
41+
42+
export type QualityWarning = CategoryQualityWarning;

x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const JobConfigurationOutdatedCallout: React.FC<{
3131
values={{
3232
moduleName,
3333
}}
34+
tagName="p"
3435
/>
3536
</RecreateJobCallout>
3637
);

x-pack/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const JobDefinitionOutdatedCallout: React.FC<{
3131
values={{
3232
moduleName,
3333
}}
34+
tagName="p"
3435
/>
3536
</RecreateJobCallout>
3637
);

x-pack/plugins/infra/public/components/logging/log_analysis_job_status/notices_section.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import React from 'react';
8-
import { QualityWarning } from '../../../containers/logs/log_analysis/log_analysis_module_types';
8+
import { QualityWarning } from '../../../../common/log_analysis';
99
import { LogAnalysisJobProblemIndicator } from './log_analysis_job_problem_indicator';
1010
import { CategoryQualityWarnings } from './quality_warning_notices';
1111

@@ -41,6 +41,10 @@ export const CategoryJobNoticesSection: React.FC<{
4141
onRecreateMlJobForReconfiguration={onRecreateMlJobForReconfiguration}
4242
onRecreateMlJobForUpdate={onRecreateMlJobForUpdate}
4343
/>
44-
<CategoryQualityWarnings qualityWarnings={qualityWarnings} />
44+
<CategoryQualityWarnings
45+
hasSetupCapabilities={hasSetupCapabilities}
46+
qualityWarnings={qualityWarnings}
47+
onRecreateMlJob={onRecreateMlJobForReconfiguration}
48+
/>
4549
</>
4650
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { action } from '@storybook/addon-actions';
8+
import { storiesOf } from '@storybook/react';
9+
import React from 'react';
10+
import { EuiThemeProvider } from '../../../../../observability/public';
11+
import { QualityWarning } from '../../../../common/log_analysis';
12+
import { CategoryQualityWarnings } from './quality_warning_notices';
13+
14+
storiesOf('infra/logAnalysis/CategoryQualityWarnings', module)
15+
.addDecorator((renderStory) => <EuiThemeProvider>{renderStory()}</EuiThemeProvider>)
16+
.add('Partitioned warnings', () => {
17+
return (
18+
<CategoryQualityWarnings
19+
hasSetupCapabilities={true}
20+
onRecreateMlJob={action('on-recreate-ml-job')}
21+
qualityWarnings={partitionedQualityWarnings}
22+
/>
23+
);
24+
})
25+
.add('Unpartitioned warnings', () => {
26+
return (
27+
<CategoryQualityWarnings
28+
hasSetupCapabilities={true}
29+
onRecreateMlJob={action('on-recreate-ml-job')}
30+
qualityWarnings={unpartitionedQualityWarnings}
31+
/>
32+
);
33+
});
34+
35+
const partitionedQualityWarnings: QualityWarning[] = [
36+
{
37+
type: 'categoryQualityWarning',
38+
jobId: 'theMlJobId',
39+
dataset: 'first.dataset',
40+
reasons: [
41+
{ type: 'singleCategory' },
42+
{ type: 'manyRareCategories', rareCategoriesRatio: 0.95 },
43+
{ type: 'manyCategories', categoriesDocumentRatio: 0.7 },
44+
],
45+
},
46+
{
47+
type: 'categoryQualityWarning',
48+
jobId: 'theMlJobId',
49+
dataset: 'second.dataset',
50+
reasons: [
51+
{ type: 'noFrequentCategories' },
52+
{ type: 'manyDeadCategories', deadCategoriesRatio: 0.7 },
53+
],
54+
},
55+
];
56+
57+
const unpartitionedQualityWarnings: QualityWarning[] = [
58+
{
59+
type: 'categoryQualityWarning',
60+
jobId: 'theMlJobId',
61+
dataset: '',
62+
reasons: [
63+
{ type: 'singleCategory' },
64+
{ type: 'manyRareCategories', rareCategoriesRatio: 0.95 },
65+
{ type: 'manyCategories', categoriesDocumentRatio: 0.7 },
66+
],
67+
},
68+
];

x-pack/plugins/infra/public/components/logging/log_analysis_job_status/quality_warning_notices.tsx

+78-32
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,89 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { EuiCallOut } from '@elastic/eui';
7+
import {
8+
EuiAccordion,
9+
EuiDescriptionList,
10+
EuiDescriptionListDescription,
11+
EuiDescriptionListTitle,
12+
EuiSpacer,
13+
htmlIdGenerator,
14+
} from '@elastic/eui';
815
import { i18n } from '@kbn/i18n';
916
import { FormattedMessage } from '@kbn/i18n/react';
10-
import React from 'react';
11-
import type {
17+
import groupBy from 'lodash/groupBy';
18+
import React, { Fragment, useState } from 'react';
19+
import { euiStyled } from '../../../../../observability/public';
20+
import {
21+
CategoryQualityWarning,
1222
CategoryQualityWarningReason,
13-
QualityWarning,
14-
} from '../../../containers/logs/log_analysis/log_analysis_module_types';
23+
getFriendlyNameForPartitionId,
24+
} from '../../../../common/log_analysis';
25+
import { RecreateJobCallout } from './recreate_job_callout';
1526

16-
export const CategoryQualityWarnings: React.FC<{ qualityWarnings: QualityWarning[] }> = ({
17-
qualityWarnings,
18-
}) => (
19-
<>
20-
{qualityWarnings.map((qualityWarning, qualityWarningIndex) => (
21-
<EuiCallOut
22-
key={`${qualityWarningIndex}`}
23-
title={categoryQualityWarningCalloutTitle}
24-
color="warning"
25-
iconType="alert"
26-
>
27-
<p>
27+
export const CategoryQualityWarnings: React.FC<{
28+
hasSetupCapabilities: boolean;
29+
onRecreateMlJob: () => void;
30+
qualityWarnings: CategoryQualityWarning[];
31+
}> = ({ hasSetupCapabilities, onRecreateMlJob, qualityWarnings }) => {
32+
const [detailAccordionId] = useState(htmlIdGenerator()());
33+
34+
const categoryQualityWarningsByJob = groupBy(qualityWarnings, 'jobId');
35+
36+
return (
37+
<>
38+
{Object.entries(categoryQualityWarningsByJob).map(([jobId, qualityWarningsForJob]) => (
39+
<RecreateJobCallout
40+
hasSetupCapabilities={hasSetupCapabilities}
41+
key={`quality-warnings-callout-${jobId}`}
42+
onRecreateMlJob={onRecreateMlJob}
43+
title={categoryQualityWarningCalloutTitle}
44+
>
2845
<FormattedMessage
2946
id="xpack.infra.logs.logEntryCategories.categoryQualityWarningCalloutMessage"
30-
defaultMessage="While analyzing the log messages we've detected some problems which might indicate a reduced quality of the categorization results."
47+
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."
48+
tagName="p"
3149
/>
32-
</p>
33-
<ul>
34-
{qualityWarning.reasons.map((reason, reasonIndex) => (
35-
<li key={`${reasonIndex}`}>
36-
<CategoryQualityWarningReasonDescription reason={reason} />
37-
</li>
38-
))}
39-
</ul>
40-
</EuiCallOut>
41-
))}
42-
</>
43-
);
50+
<EuiAccordion
51+
id={detailAccordionId}
52+
buttonContent={
53+
<FormattedMessage
54+
id="xpack.infra.logs.logEntryCategories.categoryQualityWarningDetailsAccordionButtonLabel"
55+
defaultMessage="Details"
56+
/>
57+
}
58+
paddingSize="m"
59+
>
60+
<EuiDescriptionList>
61+
{qualityWarningsForJob.flatMap((qualityWarning) => (
62+
<Fragment key={`item-${getFriendlyNameForPartitionId(qualityWarning.dataset)}`}>
63+
<EuiDescriptionListTitle data-test-subj={`title-${qualityWarning.dataset}`}>
64+
{getFriendlyNameForPartitionId(qualityWarning.dataset)}
65+
</EuiDescriptionListTitle>
66+
{qualityWarning.reasons.map((reason) => (
67+
<QualityWarningReasonDescription
68+
key={`description-${reason.type}-${qualityWarning.dataset}`}
69+
data-test-subj={`description-${reason.type}-${qualityWarning.dataset}`}
70+
>
71+
<CategoryQualityWarningReasonDescription reason={reason} />
72+
</QualityWarningReasonDescription>
73+
))}
74+
</Fragment>
75+
))}
76+
</EuiDescriptionList>
77+
</EuiAccordion>
78+
<EuiSpacer size="l" />
79+
</RecreateJobCallout>
80+
))}
81+
</>
82+
);
83+
};
84+
85+
const QualityWarningReasonDescription = euiStyled(EuiDescriptionListDescription)`
86+
display: list-item;
87+
list-style-type: disc;
88+
margin-left: ${(props) => props.theme.eui.paddingSizes.m};
89+
`;
4490

4591
const categoryQualityWarningCalloutTitle = i18n.translate(
4692
'xpack.infra.logs.logEntryCategories.categoryQUalityWarningCalloutTitle',
@@ -49,15 +95,15 @@ const categoryQualityWarningCalloutTitle = i18n.translate(
4995
}
5096
);
5197

52-
const CategoryQualityWarningReasonDescription: React.FC<{
98+
export const CategoryQualityWarningReasonDescription: React.FC<{
5399
reason: CategoryQualityWarningReason;
54100
}> = ({ reason }) => {
55101
switch (reason.type) {
56102
case 'singleCategory':
57103
return (
58104
<FormattedMessage
59105
id="xpack.infra.logs.logEntryCategories.singleCategoryWarningReasonDescription"
60-
defaultMessage="The analysis couldn't extract more than a single category from the log message."
106+
defaultMessage="The analysis couldn't extract more than a single category from the log messages."
61107
/>
62108
);
63109
case 'manyRareCategories':

x-pack/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const RecreateJobCallout: React.FC<{
1414
title?: React.ReactNode;
1515
}> = ({ children, hasSetupCapabilities, onRecreateMlJob, title }) => (
1616
<EuiCallOut color="warning" iconType="alert" title={title}>
17-
<p>{children}</p>
17+
{children}
1818
<RecreateJobButton
1919
color="warning"
2020
hasSetupCapabilities={hasSetupCapabilities}

0 commit comments

Comments
 (0)