mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ML] For categorization anomalies, display the category regex/terms in the expanded row (#28376) (#28484)
* Fetch terms/regex when row expanded * Adds error handling for definition fetch * update anomalyDetails tests * Handle definition regex/terms not returned * Adds tooltip to regex header
This commit is contained in:
parent
0377081d8d
commit
683ab12ec5
5 changed files with 151 additions and 4 deletions
|
@ -75,6 +75,10 @@
|
|||
min-width: 150px;
|
||||
}
|
||||
|
||||
.mlAnomalyCategoryExamples__header {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mlAnomalyCategoryExamples__link {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,11 @@ import { AnomalyDetails } from './anomaly_details';
|
|||
|
||||
import { mlTableService } from '../../services/table_service';
|
||||
import { RuleEditorFlyout } from '../../components/rule_editor';
|
||||
import { ml } from '../../services/ml_api_service';
|
||||
import {
|
||||
INFLUENCERS_LIMIT,
|
||||
ANOMALIES_TABLE_TABS
|
||||
ANOMALIES_TABLE_TABS,
|
||||
MAX_CHARS
|
||||
} from './anomalies_table_constants';
|
||||
|
||||
class AnomaliesTable extends Component {
|
||||
|
@ -71,18 +73,36 @@ class AnomaliesTable extends Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
toggleRow = (item, tab = ANOMALIES_TABLE_TABS.DETAILS) => {
|
||||
toggleRow = async (item, tab = ANOMALIES_TABLE_TABS.DETAILS) => {
|
||||
const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap };
|
||||
if (itemIdToExpandedRowMap[item.rowId]) {
|
||||
delete itemIdToExpandedRowMap[item.rowId];
|
||||
} else {
|
||||
const examples = (item.entityName === 'mlcategory') ?
|
||||
_.get(this.props.tableData, ['examplesByJobId', item.jobId, item.entityValue]) : undefined;
|
||||
let definition = undefined;
|
||||
|
||||
if (examples !== undefined) {
|
||||
try {
|
||||
definition = await ml.results.getCategoryDefinition(item.jobId, item.source.mlcategory[0]);
|
||||
|
||||
if (definition.terms && definition.terms.length > MAX_CHARS) {
|
||||
definition.terms = `${definition.terms.substring(0, MAX_CHARS)}...`;
|
||||
}
|
||||
if (definition.regex && definition.regex.length > MAX_CHARS) {
|
||||
definition.terms = `${definition.regex.substring(0, MAX_CHARS)}...`;
|
||||
}
|
||||
} catch(error) {
|
||||
console.log('Error fetching category definition for row item.', error);
|
||||
}
|
||||
}
|
||||
|
||||
itemIdToExpandedRowMap[item.rowId] = (
|
||||
<AnomalyDetails
|
||||
tabIndex={tab}
|
||||
anomaly={item}
|
||||
examples={examples}
|
||||
definition={definition}
|
||||
isAggregatedData={this.isShowingAggregatedData()}
|
||||
filter={this.props.filter}
|
||||
influencersLimit={INFLUENCERS_LIMIT}
|
||||
|
|
|
@ -13,3 +13,5 @@ export const ANOMALIES_TABLE_TABS = {
|
|||
DETAILS: 0,
|
||||
CATEGORY_EXAMPLES: 1
|
||||
};
|
||||
|
||||
export const MAX_CHARS = 500;
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiTabbedContent,
|
||||
|
@ -35,6 +36,7 @@ import {
|
|||
} from '../../../common/util/anomaly_utils';
|
||||
import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact';
|
||||
import { formatValue } from '../../formatters/format_value';
|
||||
import { MAX_CHARS } from './anomalies_table_constants';
|
||||
|
||||
const TIME_FIELD_NAME = 'timestamp';
|
||||
|
||||
|
@ -218,6 +220,8 @@ export class AnomalyDetails extends Component {
|
|||
}
|
||||
|
||||
renderCategoryExamples() {
|
||||
const { examples, definition } = this.props;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
|
@ -225,9 +229,54 @@ export class AnomalyDetails extends Component {
|
|||
gutterSize="m"
|
||||
className="mlAnomalyCategoryExamples"
|
||||
>
|
||||
{this.props.examples.map((example, i) => {
|
||||
{(definition !== undefined && definition.terms) &&
|
||||
<Fragment>
|
||||
<EuiFlexItem key={`example-terms`}>
|
||||
<EuiText size="xs">
|
||||
<h4 className="mlAnomalyCategoryExamples__header">Terms</h4>
|
||||
<EuiIconTip
|
||||
aria-label="Description"
|
||||
type="questionInCircle"
|
||||
color="subdued"
|
||||
size="s"
|
||||
content={`A space separated list of the common tokens that are matched in values of the category
|
||||
(may have been truncated to a max character limit of ${MAX_CHARS})`}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiText size="xs">
|
||||
{definition.terms}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="m" />
|
||||
</Fragment> }
|
||||
{(definition !== undefined && definition.regex) &&
|
||||
<Fragment>
|
||||
<EuiFlexItem key={`example-regex`}>
|
||||
<EuiText size="xs">
|
||||
<h4 className="mlAnomalyCategoryExamples__header">Regex</h4>
|
||||
<EuiIconTip
|
||||
aria-label="Description"
|
||||
type="questionInCircle"
|
||||
color="subdued"
|
||||
size="s"
|
||||
content={`The regular expression that is used to search for values that match the category
|
||||
(may have been truncated to a max character limit of ${MAX_CHARS})`}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiText size="xs">
|
||||
{definition.regex}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="l" />
|
||||
</Fragment>}
|
||||
|
||||
{examples.map((example, i) => {
|
||||
return (
|
||||
<EuiFlexItem key={`example${i}`}>
|
||||
{(i === 0 && definition !== undefined) &&
|
||||
<EuiText size="s">
|
||||
<h4>Examples</h4>
|
||||
</EuiText>}
|
||||
<span className="mlAnomalyCategoryExamples__item">{example}</span>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
@ -384,6 +433,7 @@ export class AnomalyDetails extends Component {
|
|||
AnomalyDetails.propTypes = {
|
||||
anomaly: PropTypes.object.isRequired,
|
||||
examples: PropTypes.array,
|
||||
definition: PropTypes.object,
|
||||
isAggregatedData: PropTypes.bool,
|
||||
filter: PropTypes.func,
|
||||
influencersLimit: PropTypes.number,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import { AnomalyDetails } from './anomaly_details';
|
||||
|
||||
const props = {
|
||||
|
@ -86,4 +86,75 @@ describe('AnomalyDetails', () => {
|
|||
);
|
||||
expect(wrapper.prop('initialSelectedTab').id).toBe('Category examples');
|
||||
});
|
||||
|
||||
test('Renders with terms and regex when definition prop is not undefined', () => {
|
||||
const categoryTabProps = {
|
||||
...props,
|
||||
tabIndex: 1,
|
||||
definition: {
|
||||
terms: 'example terms for test',
|
||||
regex: '.*?DBMS.+?ERROR.+?svc_prod.+?Err.+?Microsoft.+?ODBC.+?SQL.+?Server.+?Driver'
|
||||
}
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<AnomalyDetails {...categoryTabProps} />
|
||||
);
|
||||
|
||||
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(true);
|
||||
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(true);
|
||||
expect(wrapper.contains(<h4>Examples</h4>)).toBe(true);
|
||||
});
|
||||
|
||||
test('Renders only with examples when definition prop is undefined', () => {
|
||||
const categoryTabProps = {
|
||||
...props,
|
||||
tabIndex: 1,
|
||||
definition: undefined
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<AnomalyDetails {...categoryTabProps} />
|
||||
);
|
||||
|
||||
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(false);
|
||||
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(false);
|
||||
expect(wrapper.contains(<h4>Examples</h4>)).toBe(false);
|
||||
});
|
||||
|
||||
test('Renders only with terms when definition.regex is undefined', () => {
|
||||
const categoryTabProps = {
|
||||
...props,
|
||||
tabIndex: 1,
|
||||
definition: {
|
||||
terms: 'example terms for test',
|
||||
}
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<AnomalyDetails {...categoryTabProps} />
|
||||
);
|
||||
|
||||
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(false);
|
||||
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(true);
|
||||
expect(wrapper.contains(<h4>Examples</h4>)).toBe(true);
|
||||
});
|
||||
|
||||
test('Renders only with regex when definition.terms is undefined', () => {
|
||||
const categoryTabProps = {
|
||||
...props,
|
||||
tabIndex: 1,
|
||||
definition: {
|
||||
regex: '.*?DBMS.+?ERROR.+?svc_prod.+?Err.+?Microsoft.+?ODBC.+?SQL.+?Server.+?Driver'
|
||||
}
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<AnomalyDetails {...categoryTabProps} />
|
||||
);
|
||||
|
||||
expect(wrapper.containsMatchingElement(<h4>Regex</h4>)).toBe(true);
|
||||
expect(wrapper.containsMatchingElement(<h4>Terms</h4>)).toBe(false);
|
||||
expect(wrapper.contains(<h4>Examples</h4>)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue