mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
## Summary Fixes a crashing bug when Machine Learning influencers is an undefined value: https://github.com/elastic/ingest-dev/issues/611 ```ts TypeError: Cannot read property 'find' of undefined at getNetworkFromInfluencers (https://siem-dev-kibana.app.elstc.co/bundles/siem.bundle.js:41744:33) at https://siem-dev-kibana.app.elstc.co/bundles/siem.bundle.js:43012:83 at Array.reduce (<anonymous>) at convertAnomaliesToNetwork (https://siem-dev-kibana.app.elstc.co/bundles/siem.bundle.js:43004:32) at https://siem-dev-kibana.app.elstc.co/bundles/siem.bundle.js:42830:78 at renderWithHooks (webpack://%5Bname%5D/./node_modules/react-dom/cjs/react-dom.development.js?:12839:18) at updateFunctionComponent (webpack://%5Bname%5D/./node_modules/react-dom/cjs/react-dom.development.js?:14421:20) at beginWork (webpack://%5Bname%5D/./node_modules/react-dom/cjs/react-dom.development.js?:15431:16) at performUnitOfWork (webpack://%5Bname%5D/./node_modules/react-dom/cjs/react-dom.development.js?:19106:12) at workLoop (webpack://%5Bname%5D/./node_modules/react-dom/cjs/react-dom.development.js?:19146:24) ``` ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)
This commit is contained in:
parent
e555582a4c
commit
7dcf815049
15 changed files with 249 additions and 90 deletions
|
@ -19,10 +19,15 @@ describe('create_influencers', () => {
|
|||
});
|
||||
|
||||
test('renders correctly against snapshot', () => {
|
||||
const wrapper = shallow(<span>{createInfluencers(anomalies.anomalies[0])}</span>);
|
||||
const wrapper = shallow(<span>{createInfluencers(anomalies.anomalies[0].influencers)}</span>);
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('it returns an empty string when influencers is undefined', () => {
|
||||
const wrapper = mount(<span>{createInfluencers()}</span>);
|
||||
expect(wrapper.text()).toEqual('');
|
||||
});
|
||||
|
||||
test('it returns expected createKeyAndValue record with special left and right quotes', () => {
|
||||
const entities = createKeyAndValue({ 'name-1': 'value-1' });
|
||||
expect(entities).toEqual('name-1: "value-1"');
|
||||
|
@ -34,13 +39,13 @@ describe('create_influencers', () => {
|
|||
});
|
||||
|
||||
test('it creates the anomalies without filtering anything out since they are all well formed', () => {
|
||||
const wrapper = mount(<span>{createInfluencers(anomalies.anomalies[0])}</span>);
|
||||
const wrapper = mount(<span>{createInfluencers(anomalies.anomalies[0].influencers)}</span>);
|
||||
expect(wrapper.text()).toEqual('host.name: "zeek-iowa"process.name: "du"user.name: "root"');
|
||||
});
|
||||
|
||||
test('it returns empty text when passed in empty objects of influencers', () => {
|
||||
anomalies.anomalies[0].influencers = [{}, {}, {}];
|
||||
const wrapper = mount(<span>{createInfluencers(anomalies.anomalies[0])}</span>);
|
||||
const wrapper = mount(<span>{createInfluencers(anomalies.anomalies[0].influencers)}</span>);
|
||||
expect(wrapper.text()).toEqual('');
|
||||
});
|
||||
|
||||
|
@ -50,7 +55,7 @@ describe('create_influencers', () => {
|
|||
{},
|
||||
{ 'influencer-name-two': 'influencer-value-two' },
|
||||
];
|
||||
const wrapper = mount(<span>{createInfluencers(anomalies.anomalies[0])}</span>);
|
||||
const wrapper = mount(<span>{createInfluencers(anomalies.anomalies[0].influencers)}</span>);
|
||||
expect(wrapper.text()).toEqual(
|
||||
'influencer-name-one: "influencer-value-one"influencer-name-two: "influencer-value-two"'
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { Anomaly } from '../types';
|
||||
import { getEntries } from '../get_entries';
|
||||
|
||||
export const createKeyAndValue = (influencer: Record<string, string>): string => {
|
||||
|
@ -19,19 +18,14 @@ export const createKeyAndValue = (influencer: Record<string, string>): string =>
|
|||
}
|
||||
};
|
||||
|
||||
export const createInfluencers = (score: Anomaly): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
{score.influencers
|
||||
.filter(influencer => !isEmpty(influencer))
|
||||
.map(influencer => {
|
||||
const keyAndValue = createKeyAndValue(influencer);
|
||||
return (
|
||||
<EuiFlexItem key={keyAndValue} grow={false}>
|
||||
{keyAndValue}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const createInfluencers = (influencers: Array<Record<string, string>> = []): JSX.Element[] =>
|
||||
influencers
|
||||
.filter(influencer => !isEmpty(influencer))
|
||||
.map(influencer => {
|
||||
const keyAndValue = createKeyAndValue(influencer);
|
||||
return (
|
||||
<EuiFlexItem key={keyAndValue} grow={false}>
|
||||
{keyAndValue}
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -26,6 +26,11 @@ describe('get_host_name_from_influencers', () => {
|
|||
expect(hostName).toEqual(null);
|
||||
});
|
||||
|
||||
test('returns null if it is given undefined influencers', () => {
|
||||
const hostName = getHostNameFromInfluencers();
|
||||
expect(hostName).toEqual(null);
|
||||
});
|
||||
|
||||
test('returns null if there influencers is an empty object', () => {
|
||||
anomalies.anomalies[0].influencers = [{}];
|
||||
const hostName = getHostNameFromInfluencers(anomalies.anomalies[0].influencers);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { getEntries } from '../get_entries';
|
||||
|
||||
export const getHostNameFromInfluencers = (
|
||||
influencers: Array<Record<string, string>>,
|
||||
influencers: Array<Record<string, string>> = [],
|
||||
hostName?: string
|
||||
): string | null => {
|
||||
const recordFound = influencers.find(influencer => {
|
||||
|
|
|
@ -28,6 +28,11 @@ describe('get_network_from_influencers', () => {
|
|||
expect(network).toEqual(null);
|
||||
});
|
||||
|
||||
test('returns null if the influencers are undefined', () => {
|
||||
const network = getNetworkFromInfluencers();
|
||||
expect(network).toEqual(null);
|
||||
});
|
||||
|
||||
test('returns network name of source mixed with other data', () => {
|
||||
anomalies.anomalies[0].influencers = [{ 'host.name': 'name-1' }, { 'source.ip': '127.0.0.1' }];
|
||||
const network = getNetworkFromInfluencers(anomalies.anomalies[0].influencers);
|
||||
|
|
|
@ -8,7 +8,7 @@ import { DestinationOrSource, isDestinationOrSource } from '../types';
|
|||
import { getEntries } from '../get_entries';
|
||||
|
||||
export const getNetworkFromInfluencers = (
|
||||
influencers: Array<Record<string, string>>,
|
||||
influencers: Array<Record<string, string>> = [],
|
||||
ip?: string
|
||||
): { ip: string; type: DestinationOrSource } | null => {
|
||||
const recordFound = influencers.find(influencer => {
|
||||
|
|
|
@ -190,23 +190,21 @@ exports[`anomaly_scores renders correctly against snapshot 1`] = `
|
|||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
<React.Fragment>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
host.name: "zeek-iowa"
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
process.name: "du"
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
user.name: "root"
|
||||
</EuiFlexItem>
|
||||
</React.Fragment>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
host.name: "zeek-iowa"
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
process.name: "du"
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
user.name: "root"
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>,
|
||||
"title": "Influenced By",
|
||||
},
|
||||
|
|
|
@ -88,7 +88,7 @@ export const createDescriptionList = (
|
|||
title: i18n.INFLUENCED_BY,
|
||||
description: (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
{createInfluencers(score)}
|
||||
{createInfluencers(score.influencers)}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
createEntitiesFromScore,
|
||||
createEntity,
|
||||
createEntityFromRecord,
|
||||
createInfluencersFromScore,
|
||||
} from './create_entities_from_score';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
|
@ -68,4 +69,41 @@ describe('create_entities_from_score', () => {
|
|||
const entity = createEntityFromRecord({ 'name-1': 'value-1' });
|
||||
expect(entity).toEqual("name-1:'value-1'");
|
||||
});
|
||||
|
||||
test('it returns expected entities from a typical score for influencers', () => {
|
||||
const influencers = createInfluencersFromScore(anomalies.anomalies[0].influencers);
|
||||
expect(influencers).toEqual("host.name:'zeek-iowa',process.name:'du',user.name:'root'");
|
||||
});
|
||||
|
||||
test('it returns empty string for empty influencers', () => {
|
||||
const influencers = createInfluencersFromScore([]);
|
||||
expect(influencers).toEqual('');
|
||||
});
|
||||
|
||||
test('it returns empty string for undefined influencers', () => {
|
||||
const influencers = createInfluencersFromScore();
|
||||
expect(influencers).toEqual('');
|
||||
});
|
||||
|
||||
test('it returns single influencer', () => {
|
||||
const influencers = createInfluencersFromScore([{ 'influencer-1': 'value-1' }]);
|
||||
expect(influencers).toEqual("influencer-1:'value-1'");
|
||||
});
|
||||
|
||||
test('it returns two influencers', () => {
|
||||
const influencers = createInfluencersFromScore([
|
||||
{ 'influencer-1': 'value-1' },
|
||||
{ 'influencer-2': 'value-2' },
|
||||
]);
|
||||
expect(influencers).toEqual("influencer-1:'value-1',influencer-2:'value-2'");
|
||||
});
|
||||
|
||||
test('it creates a simple string entity with undefined influencers', () => {
|
||||
const anomaly = anomalies.anomalies[0];
|
||||
anomaly.entityName = 'name-1';
|
||||
anomaly.entityValue = 'value-1';
|
||||
delete anomaly.influencers;
|
||||
const entities = createEntitiesFromScore(anomaly);
|
||||
expect(entities).toEqual("name-1:'value-1'");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,8 +12,10 @@ export const createEntityFromRecord = (entity: Record<string, string>): string =
|
|||
export const createEntity = (entityName: string, entityValue: string): string =>
|
||||
`${entityName}:'${entityValue}'`;
|
||||
|
||||
export const createEntitiesFromScore = (score: Anomaly): string => {
|
||||
const influencers = score.influencers.reduce((accum, item, index) => {
|
||||
export const createInfluencersFromScore = (
|
||||
influencers: Array<Record<string, string>> = []
|
||||
): string =>
|
||||
influencers.reduce((accum, item, index) => {
|
||||
if (index === 0) {
|
||||
return createEntityFromRecord(item);
|
||||
} else {
|
||||
|
@ -21,6 +23,9 @@ export const createEntitiesFromScore = (score: Anomaly): string => {
|
|||
}
|
||||
}, '');
|
||||
|
||||
export const createEntitiesFromScore = (score: Anomaly): string => {
|
||||
const influencers = createInfluencersFromScore(score.influencers);
|
||||
|
||||
if (influencers.length === 0) {
|
||||
return createEntity(score.entityName, score.entityValue);
|
||||
} else if (!influencers.includes(score.entityName)) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { getAnomaliesHostTableColumnsCurated } from './get_anomalies_host_table_columns';
|
||||
import { HostsType } from '../../../store/hosts/model';
|
||||
import * as i18n from './translations';
|
||||
import { AnomaliesByHost } from '../types';
|
||||
import { AnomaliesByHost, Anomaly } from '../types';
|
||||
import { Columns } from '../../load_more_table';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import { mount } from 'enzyme';
|
||||
|
@ -96,7 +96,7 @@ describe('get_anomalies_host_table_columns', () => {
|
|||
is_interim: true,
|
||||
timestamp: new Date('01/01/2000').valueOf(),
|
||||
by_field_name: 'some field name',
|
||||
by_field_value: 'some field valuke',
|
||||
by_field_value: 'some field value',
|
||||
partition_field_name: 'partition field name',
|
||||
partition_field_value: 'partition field value',
|
||||
function: 'function-1',
|
||||
|
@ -121,4 +121,57 @@ describe('get_anomalies_host_table_columns', () => {
|
|||
expect(column).not.toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
test('on host page, undefined influencers should turn into an empty column string', () => {
|
||||
const columns = getAnomaliesHostTableColumnsCurated(
|
||||
HostsType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const column = columns.find(col => col.name === i18n.INFLUENCED_BY) as Columns<
|
||||
Anomaly['influencers'],
|
||||
AnomaliesByHost
|
||||
>;
|
||||
const anomaly: AnomaliesByHost = {
|
||||
hostName: 'host.name',
|
||||
anomaly: {
|
||||
detectorIndex: 0,
|
||||
entityName: 'entity-name-1',
|
||||
entityValue: 'entity-value-1',
|
||||
jobId: 'job-1',
|
||||
rowId: 'row-1',
|
||||
severity: 100,
|
||||
time: new Date('01/01/2000').valueOf(),
|
||||
source: {
|
||||
job_id: 'job-1',
|
||||
result_type: 'result-1',
|
||||
probability: 50,
|
||||
multi_bucket_impact: 0,
|
||||
record_score: 0,
|
||||
initial_record_score: 0,
|
||||
bucket_span: 0,
|
||||
detector_index: 0,
|
||||
is_interim: true,
|
||||
timestamp: new Date('01/01/2000').valueOf(),
|
||||
by_field_name: 'some field name',
|
||||
by_field_value: 'some field value',
|
||||
partition_field_name: 'partition field name',
|
||||
partition_field_value: 'partition field value',
|
||||
function: 'function-1',
|
||||
function_description: 'description-1',
|
||||
typical: [5, 3],
|
||||
actual: [7, 4],
|
||||
influencers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
if (column != null && column.render != null) {
|
||||
const wrapper = mount(<TestProviders>{column.render(undefined, anomaly)}</TestProviders>);
|
||||
expect(wrapper.text()).toEqual('');
|
||||
} else {
|
||||
expect(column).not.toBe(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -95,29 +95,30 @@ export const getAnomaliesHostTableColumns = (
|
|||
field: 'anomaly.influencers',
|
||||
render: (influencers, anomaliesByHost) => (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
{influencers.map(influencer => {
|
||||
const [key, value] = getEntries(influencer);
|
||||
const entityName = key != null ? key : '';
|
||||
const entityValue = value != null ? value : '';
|
||||
return (
|
||||
<EuiFlexItem
|
||||
key={`${entityName}-${entityValue}-${createCompoundHostKey(anomaliesByHost)}`}
|
||||
grow={false}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EntityDraggable
|
||||
idPrefix={`anomalies-host-table-influencers-${entityName}-${entityValue}-${createCompoundHostKey(
|
||||
anomaliesByHost
|
||||
)}`}
|
||||
entityName={entityName}
|
||||
entityValue={entityValue}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
{influencers &&
|
||||
influencers.map(influencer => {
|
||||
const [key, value] = getEntries(influencer);
|
||||
const entityName = key != null ? key : '';
|
||||
const entityValue = value != null ? value : '';
|
||||
return (
|
||||
<EuiFlexItem
|
||||
key={`${entityName}-${entityValue}-${createCompoundHostKey(anomaliesByHost)}`}
|
||||
grow={false}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="none" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EntityDraggable
|
||||
idPrefix={`anomalies-host-table-influencers-${entityName}-${entityValue}-${createCompoundHostKey(
|
||||
anomaliesByHost
|
||||
)}`}
|
||||
entityName={entityName}
|
||||
entityValue={entityValue}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { getAnomaliesNetworkTableColumnsCurated } from './get_anomalies_network_table_columns';
|
||||
import { NetworkType } from '../../../store/network/model';
|
||||
import * as i18n from './translations';
|
||||
import { AnomaliesByNetwork } from '../types';
|
||||
import { AnomaliesByNetwork, Anomaly } from '../types';
|
||||
import { Columns } from '../../load_more_table';
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
@ -100,7 +100,7 @@ describe('get_anomalies_network_table_columns', () => {
|
|||
is_interim: true,
|
||||
timestamp: new Date('01/01/2000').valueOf(),
|
||||
by_field_name: 'some field name',
|
||||
by_field_value: 'some field valuke',
|
||||
by_field_value: 'some field value',
|
||||
partition_field_name: 'partition field name',
|
||||
partition_field_value: 'partition field value',
|
||||
function: 'function-1',
|
||||
|
@ -125,4 +125,58 @@ describe('get_anomalies_network_table_columns', () => {
|
|||
expect(column).not.toBe(null);
|
||||
}
|
||||
});
|
||||
|
||||
test('on network page, undefined influencers should turn into an empty column string', () => {
|
||||
const columns = getAnomaliesNetworkTableColumnsCurated(
|
||||
NetworkType.page,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
narrowDateRange
|
||||
);
|
||||
const column = columns.find(col => col.name === i18n.INFLUENCED_BY) as Columns<
|
||||
Anomaly['influencers'],
|
||||
AnomaliesByNetwork
|
||||
>;
|
||||
const anomaly: AnomaliesByNetwork = {
|
||||
type: 'source.ip',
|
||||
ip: '127.0.0.1',
|
||||
anomaly: {
|
||||
detectorIndex: 0,
|
||||
entityName: 'entity-name-1',
|
||||
entityValue: 'entity-value-1',
|
||||
jobId: 'job-1',
|
||||
rowId: 'row-1',
|
||||
severity: 100,
|
||||
time: new Date('01/01/2000').valueOf(),
|
||||
source: {
|
||||
job_id: 'job-1',
|
||||
result_type: 'result-1',
|
||||
probability: 50,
|
||||
multi_bucket_impact: 0,
|
||||
record_score: 0,
|
||||
initial_record_score: 0,
|
||||
bucket_span: 0,
|
||||
detector_index: 0,
|
||||
is_interim: true,
|
||||
timestamp: new Date('01/01/2000').valueOf(),
|
||||
by_field_name: 'some field name',
|
||||
by_field_value: 'some field value',
|
||||
partition_field_name: 'partition field name',
|
||||
partition_field_value: 'partition field value',
|
||||
function: 'function-1',
|
||||
function_description: 'description-1',
|
||||
typical: [5, 3],
|
||||
actual: [7, 4],
|
||||
influencers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
if (column != null && column.render != null) {
|
||||
const wrapper = mount(<TestProviders>{column.render(undefined, anomaly)}</TestProviders>);
|
||||
expect(wrapper.text()).toEqual('');
|
||||
} else {
|
||||
expect(column).not.toBe(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -93,25 +93,26 @@ export const getAnomaliesNetworkTableColumns = (
|
|||
field: 'anomaly.influencers',
|
||||
render: (influencers, anomaliesByNetwork) => (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" responsive={false}>
|
||||
{influencers.map(influencer => {
|
||||
const [key, value] = getEntries(influencer);
|
||||
const entityName = key != null ? key : '';
|
||||
const entityValue = value != null ? value : '';
|
||||
return (
|
||||
<EuiFlexItem
|
||||
key={`${entityName}-${entityValue}-${createCompoundNetworkKey(anomaliesByNetwork)}`}
|
||||
grow={false}
|
||||
>
|
||||
<EntityDraggable
|
||||
idPrefix={`anomalies-network-table-influencers-${entityName}-${entityValue}-${createCompoundNetworkKey(
|
||||
anomaliesByNetwork
|
||||
)}`}
|
||||
entityName={entityName}
|
||||
entityValue={entityValue}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
{influencers &&
|
||||
influencers.map(influencer => {
|
||||
const [key, value] = getEntries(influencer);
|
||||
const entityName = key != null ? key : '';
|
||||
const entityValue = value != null ? value : '';
|
||||
return (
|
||||
<EuiFlexItem
|
||||
key={`${entityName}-${entityValue}-${createCompoundNetworkKey(anomaliesByNetwork)}`}
|
||||
grow={false}
|
||||
>
|
||||
<EntityDraggable
|
||||
idPrefix={`anomalies-network-table-influencers-${entityName}-${entityValue}-${createCompoundNetworkKey(
|
||||
anomaliesByNetwork
|
||||
)}`}
|
||||
entityName={entityName}
|
||||
entityValue={entityValue}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@ export interface Anomaly {
|
|||
detectorIndex: number;
|
||||
entityName: string;
|
||||
entityValue: string;
|
||||
influencers: Array<Record<string, string>>;
|
||||
influencers?: Array<Record<string, string>>;
|
||||
jobId: string;
|
||||
rowId: string;
|
||||
severity: number;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue