[8.x] [SecuritySolution] Update entity store source field (#197186) (#197376)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[SecuritySolution] Update entity store source field
(#197186)](https://github.com/elastic/kibana/pull/197186)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Pablo
Machado","email":"pablo.nevesmachado@elastic.co"},"sourceCommit":{"committedDate":"2024-10-23T08:05:05Z","message":"[SecuritySolution]
Update entity store source field (#197186)\n\n## Summary\r\n\r\nIn this
PR the source field will only store the first identified index\r\nfor an
entity.\r\nThe PR also updates the entities list panel to display a
textual\r\ndescription of the source index and adds a new source field
filter.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/c7aad254-f871-4035-9dac-89decce31a55\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Mark Hopkin
<mark.hopkin@elastic.co>\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"0bafb9632c2e1b09dd56586f15dca83d8ad5b358","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","Team:
SecuritySolution","Theme: entity_analytics","Feature:Entity
Analytics","v8.9.0","Team:Entity Analytics","8.16
candidate","v8.16.0","backport:version","v8.17.0"],"title":"[SecuritySolution]
Update entity store source
field","number":197186,"url":"https://github.com/elastic/kibana/pull/197186","mergeCommit":{"message":"[SecuritySolution]
Update entity store source field (#197186)\n\n## Summary\r\n\r\nIn this
PR the source field will only store the first identified index\r\nfor an
entity.\r\nThe PR also updates the entities list panel to display a
textual\r\ndescription of the source index and adds a new source field
filter.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/c7aad254-f871-4035-9dac-89decce31a55\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Mark Hopkin
<mark.hopkin@elastic.co>\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"0bafb9632c2e1b09dd56586f15dca83d8ad5b358"}},"sourceBranch":"main","suggestedTargetBranches":["8.9","8.16","8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197186","number":197186,"mergeCommit":{"message":"[SecuritySolution]
Update entity store source field (#197186)\n\n## Summary\r\n\r\nIn this
PR the source field will only store the first identified index\r\nfor an
entity.\r\nThe PR also updates the entities list panel to display a
textual\r\ndescription of the source index and adds a new source field
filter.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/c7aad254-f871-4035-9dac-89decce31a55\r\n\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Mark Hopkin
<mark.hopkin@elastic.co>\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"0bafb9632c2e1b09dd56586f15dca83d8ad5b358"}},{"branch":"8.9","label":"v8.9.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co>
This commit is contained in:
Kibana Machine 2024-10-23 20:48:39 +11:00 committed by GitHub
parent b663248f8a
commit dc1eb11071
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 246 additions and 126 deletions

View file

@ -30472,9 +30472,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source
@ -30626,9 +30624,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source

View file

@ -30472,9 +30472,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source
@ -30626,9 +30624,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source

View file

@ -39302,9 +39302,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source
@ -39456,9 +39454,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source

View file

@ -39302,9 +39302,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source
@ -39456,9 +39454,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source

View file

@ -24,7 +24,7 @@ export const UserEntity = z.object({
'@timestamp': z.string().datetime(), '@timestamp': z.string().datetime(),
entity: z.object({ entity: z.object({
name: z.string(), name: z.string(),
source: z.array(z.string()), source: z.string(),
}), }),
user: z.object({ user: z.object({
full_name: z.array(z.string()).optional(), full_name: z.array(z.string()).optional(),
@ -48,7 +48,7 @@ export const HostEntity = z.object({
'@timestamp': z.string().datetime(), '@timestamp': z.string().datetime(),
entity: z.object({ entity: z.object({
name: z.string(), name: z.string(),
source: z.array(z.string()), source: z.string(),
}), }),
host: z.object({ host: z.object({
hostname: z.array(z.string()).optional(), hostname: z.array(z.string()).optional(),

View file

@ -22,12 +22,10 @@ components:
- name - name
- source - source
properties: properties:
name: name:
type: string type: string
source: source:
type: array type: string
items:
type: string
user: user:
type: object type: object
properties: properties:
@ -84,12 +82,10 @@ components:
- name - name
- source - source
properties: properties:
name: name:
type: string type: string
source: source:
type: array type: string
items:
type: string
host: host:
type: object type: object
properties: properties:

View file

@ -910,9 +910,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source
@ -1062,9 +1060,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source

View file

@ -910,9 +910,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source
@ -1062,9 +1060,7 @@ components:
name: name:
type: string type: string
source: source:
items: type: string
type: string
type: array
required: required:
- name - name
- source - source

View file

@ -8,30 +8,26 @@
import React from 'react'; import React from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { MultiselectFilter } from '../../../../common/components/multiselect_filter'; import { MultiselectFilter } from '../../../../common/components/multiselect_filter';
import { EntitySourceTag } from '../types';
interface SourceFilterProps { interface SourceFilterProps {
selectedItems: EntitySource[]; selectedItems: EntitySourceTag[];
onChange: (selectedItems: EntitySource[]) => void; onChange: (selectedItems: EntitySourceTag[]) => void;
} }
export enum EntitySource {
CSV_UPLOAD = 'CSV upload',
EVENTS = 'Events',
}
// TODO Fix the Entity Source field before using it
export const EntitySourceFilter: React.FC<SourceFilterProps> = ({ selectedItems, onChange }) => { export const EntitySourceFilter: React.FC<SourceFilterProps> = ({ selectedItems, onChange }) => {
return ( return (
<MultiselectFilter<EntitySource> <MultiselectFilter<EntitySourceTag>
title={i18n.translate( title={i18n.translate(
'xpack.securitySolution.entityAnalytics.entityStore.entitySource.filterTitle', 'xpack.securitySolution.entityAnalytics.entityStore.entitySource.filterTitle',
{ {
defaultMessage: 'Source', defaultMessage: 'Source',
} }
)} )}
items={Object.values(EntitySource)} items={Object.values(EntitySourceTag)}
selectedItems={selectedItems} selectedItems={selectedItems}
onSelectionChange={onChange} onSelectionChange={onChange}
width={140} width={190}
/> />
); );
}; };

View file

@ -33,7 +33,7 @@ const responseData: ListEntitiesResponse = {
user: { name: entityName }, user: { name: entityName },
entity: { entity: {
name: entityName, name: entityName,
source: ['source'], source: 'test-index',
}, },
}, },
], ],

View file

@ -21,12 +21,13 @@ import { EntityType } from '../../../../common/api/entity_analytics/entity_store
import type { Criteria } from '../../../explore/components/paginated_table'; import type { Criteria } from '../../../explore/components/paginated_table';
import { PaginatedTable } from '../../../explore/components/paginated_table'; import { PaginatedTable } from '../../../explore/components/paginated_table';
import { SeverityFilter } from '../severity/severity_filter'; import { SeverityFilter } from '../severity/severity_filter';
import type { EntitySource } from './components/entity_source_filter'; import { EntitySourceFilter } from './components/entity_source_filter';
import { useEntitiesListFilters } from './hooks/use_entities_list_filters'; import { useEntitiesListFilters } from './hooks/use_entities_list_filters';
import { AssetCriticalityFilter } from '../asset_criticality/asset_criticality_filter'; import { AssetCriticalityFilter } from '../asset_criticality/asset_criticality_filter';
import { useEntitiesListQuery } from './hooks/use_entities_list_query'; import { useEntitiesListQuery } from './hooks/use_entities_list_query';
import { ENTITIES_LIST_TABLE_ID, rowItems } from './constants'; import { ENTITIES_LIST_TABLE_ID, rowItems } from './constants';
import { useEntitiesListColumns } from './hooks/use_entities_list_columns'; import { useEntitiesListColumns } from './hooks/use_entities_list_columns';
import type { EntitySourceTag } from './types';
export const EntitiesList: React.FC = () => { export const EntitiesList: React.FC = () => {
const { deleteQuery, setQuery, isInitializing, from, to } = useGlobalTime(); const { deleteQuery, setQuery, isInitializing, from, to } = useGlobalTime();
@ -40,7 +41,7 @@ export const EntitiesList: React.FC = () => {
const [selectedSeverities, setSelectedSeverities] = useState<RiskSeverity[]>([]); const [selectedSeverities, setSelectedSeverities] = useState<RiskSeverity[]>([]);
const [selectedCriticalities, setSelectedCriticalities] = useState<CriticalityLevels[]>([]); const [selectedCriticalities, setSelectedCriticalities] = useState<CriticalityLevels[]>([]);
const [selectedSources, _] = useState<EntitySource[]>([]); const [selectedSources, setSelectedSources] = useState<EntitySourceTag[]>([]);
const filter = useEntitiesListFilters({ const filter = useEntitiesListFilters({
selectedSeverities, selectedSeverities,
@ -147,6 +148,7 @@ export const EntitiesList: React.FC = () => {
selectedItems={selectedCriticalities} selectedItems={selectedCriticalities}
onChange={setSelectedCriticalities} onChange={setSelectedCriticalities}
/> />
<EntitySourceFilter selectedItems={selectedSources} onChange={setSelectedSources} />
</EuiFilterGroup> </EuiFilterGroup>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>

View file

@ -5,40 +5,70 @@
* 2.0. * 2.0.
*/ */
import { isUserEntity } from './helpers'; import { isUserEntity, sourceFieldToText } from './helpers';
import type { import type {
Entity, Entity,
UserEntity, UserEntity,
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen'; } from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
import { render } from '@testing-library/react';
import { TestProviders } from '@kbn/timelines-plugin/public/mock';
describe('isUserEntity', () => { describe('helpers', () => {
it('should return true if the record is a UserEntity', () => { describe('isUserEntity', () => {
const userEntity: UserEntity = { it('should return true if the record is a UserEntity', () => {
'@timestamp': '2021-08-02T14:00:00.000Z', const userEntity: UserEntity = {
user: { '@timestamp': '2021-08-02T14:00:00.000Z',
name: 'test_user', user: {
}, name: 'test_user',
entity: { },
name: 'test_user', entity: {
source: ['logs-test'], name: 'test_user',
}, source: 'logs-test',
}; },
};
expect(isUserEntity(userEntity)).toBe(true); expect(isUserEntity(userEntity)).toBe(true);
});
it('should return false if the record is not a UserEntity', () => {
const nonUserEntity: Entity = {
'@timestamp': '2021-08-02T14:00:00.000Z',
host: {
name: 'test_host',
},
entity: {
name: 'test_host',
source: 'logs-test',
},
};
expect(isUserEntity(nonUserEntity)).toBe(false);
});
}); });
it('should return false if the record is not a UserEntity', () => { describe('sourceFieldToText', () => {
const nonUserEntity: Entity = { it("should return 'Events' if the value isn't risk or asset", () => {
'@timestamp': '2021-08-02T14:00:00.000Z', const { container } = render(sourceFieldToText('anything'), {
host: { wrapper: TestProviders,
name: 'test_host', });
},
entity: {
name: 'test_host',
source: ['logs-test'],
},
};
expect(isUserEntity(nonUserEntity)).toBe(false); expect(container).toHaveTextContent('Events');
});
it("should return 'Risk' if the value is a risk index", () => {
const { container } = render(sourceFieldToText('risk-score.risk-score-default'), {
wrapper: TestProviders,
});
expect(container).toHaveTextContent('Risk');
});
it("should return 'Asset Criticality' if the value is a asset criticality index", () => {
const { container } = render(sourceFieldToText('.asset-criticality.asset-criticality-*'), {
wrapper: TestProviders,
});
expect(container).toHaveTextContent('Asset Criticality');
});
}); });
}); });

View file

@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import {
ASSET_CRITICALITY_INDEX_PATTERN,
RISK_SCORE_INDEX_PATTERN,
} from '../../../../common/constants';
import type {
Entity,
UserEntity,
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen';
export const isUserEntity = (record: Entity): record is UserEntity =>
!!(record as UserEntity)?.user;
export const sourceFieldToText = (source: string) => {
if (source.match(`^${RISK_SCORE_INDEX_PATTERN}`)) {
return (
<FormattedMessage
id="xpack.securitySolution.entityAnalytics.entityStore.helpers.sourceField.riskDescription"
defaultMessage="Risk"
/>
);
}
if (source.match(`^${ASSET_CRITICALITY_INDEX_PATTERN}`)) {
return (
<FormattedMessage
id="xpack.securitySolution.entityAnalytics.entityStore.helpers.sourceField.criticalityDescription"
defaultMessage="Asset Criticality"
/>
);
}
return (
<FormattedMessage
id="xpack.securitySolution.entityAnalytics.entityStore.helpers.sourceField.eventDescription"
defaultMessage="Events"
/>
);
};

View file

@ -17,9 +17,9 @@ import { RiskScoreLevel } from '../../severity/common';
import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { getEmptyTagValue } from '../../../../common/components/empty_value';
import type { Columns } from '../../../../explore/components/paginated_table'; import type { Columns } from '../../../../explore/components/paginated_table';
import type { Entity } from '../../../../../common/api/entity_analytics/entity_store/entities/common.gen'; import type { Entity } from '../../../../../common/api/entity_analytics/entity_store/entities/common.gen';
import type { CriticalityLevels } from '../../../../../common/constants'; import { type CriticalityLevels } from '../../../../../common/constants';
import { ENTITIES_LIST_TABLE_ID } from '../constants'; import { ENTITIES_LIST_TABLE_ID } from '../constants';
import { isUserEntity } from '../helpers'; import { isUserEntity, sourceFieldToText } from '../helpers';
import { CRITICALITY_LEVEL_TITLE } from '../../asset_criticality/translations'; import { CRITICALITY_LEVEL_TITLE } from '../../asset_criticality/translations';
export type EntitiesListColumns = [ export type EntitiesListColumns = [
@ -110,7 +110,7 @@ export const useEntitiesListColumns = (): EntitiesListColumns => {
truncateText: { lines: 2 }, truncateText: { lines: 2 },
render: (source: string | undefined) => { render: (source: string | undefined) => {
if (source != null) { if (source != null) {
return <span>{source}</span>; return sourceFieldToText(source);
} }
return getEmptyTagValue(); return getEmptyTagValue();

View file

@ -11,7 +11,7 @@ import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { CriticalityLevels } from '../../../../../common/constants'; import { CriticalityLevels } from '../../../../../common/constants';
import { RiskSeverity } from '../../../../../common/search_strategy'; import { RiskSeverity } from '../../../../../common/search_strategy';
import { EntitySource } from '../components/entity_source_filter'; import { EntitySourceTag } from '../types';
jest.mock('../../../../common/hooks/use_global_filter_query'); jest.mock('../../../../common/hooks/use_global_filter_query');
@ -52,7 +52,6 @@ describe('useEntitiesListFilters', () => {
{ term: { 'host.risk.calculated_level': RiskSeverity.High } }, { term: { 'host.risk.calculated_level': RiskSeverity.High } },
{ term: { 'user.risk.calculated_level': RiskSeverity.High } }, { term: { 'user.risk.calculated_level': RiskSeverity.High } },
], ],
minimum_should_match: 1,
}, },
}, },
]; ];
@ -72,7 +71,6 @@ describe('useEntitiesListFilters', () => {
const expectedFilters: QueryDslQueryContainer[] = [ const expectedFilters: QueryDslQueryContainer[] = [
{ {
bool: { bool: {
minimum_should_match: 1,
should: [ should: [
{ {
term: { term: {
@ -97,13 +95,48 @@ describe('useEntitiesListFilters', () => {
useEntitiesListFilters({ useEntitiesListFilters({
selectedSeverities: [], selectedSeverities: [],
selectedCriticalities: [], selectedCriticalities: [],
selectedSources: [EntitySource.CSV_UPLOAD, EntitySource.EVENTS], selectedSources: [EntitySourceTag.criticality, EntitySourceTag.risk],
}) })
); );
const expectedFilters: QueryDslQueryContainer[] = [ const expectedFilters: QueryDslQueryContainer[] = [
{ term: { 'entity.source': EntitySource.CSV_UPLOAD } }, {
{ term: { 'entity.source': EntitySource.EVENTS } }, bool: {
should: [
{ wildcard: { 'entity.source': '.asset-criticality.asset-criticality-*' } },
{ wildcard: { 'entity.source': 'risk-score.risk-score-*' } },
],
},
},
];
expect(result.current).toEqual(expectedFilters);
});
it('should return source events filters when events is selected', () => {
const { result } = renderHook(() =>
useEntitiesListFilters({
selectedSeverities: [],
selectedCriticalities: [],
selectedSources: [EntitySourceTag.events],
})
);
const expectedFilters: QueryDslQueryContainer[] = [
{
bool: {
should: [
{
bool: {
must_not: [
{ wildcard: { 'entity.source': '.asset-criticality.asset-criticality-*' } },
{ wildcard: { 'entity.source': 'risk-score.risk-score-*' } },
],
},
},
],
},
},
]; ];
expect(result.current).toEqual(expectedFilters); expect(result.current).toEqual(expectedFilters);
@ -132,7 +165,7 @@ describe('useEntitiesListFilters', () => {
useEntitiesListFilters({ useEntitiesListFilters({
selectedSeverities: [RiskSeverity.Low], selectedSeverities: [RiskSeverity.Low],
selectedCriticalities: [CriticalityLevels.HIGH_IMPACT], selectedCriticalities: [CriticalityLevels.HIGH_IMPACT],
selectedSources: [EntitySource.CSV_UPLOAD], selectedSources: [EntitySourceTag.risk],
}) })
); );
@ -143,16 +176,18 @@ describe('useEntitiesListFilters', () => {
{ term: { 'host.risk.calculated_level': RiskSeverity.Low } }, { term: { 'host.risk.calculated_level': RiskSeverity.Low } },
{ term: { 'user.risk.calculated_level': RiskSeverity.Low } }, { term: { 'user.risk.calculated_level': RiskSeverity.Low } },
], ],
minimum_should_match: 1,
}, },
}, },
{ {
bool: { bool: {
should: [{ term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }], should: [{ term: { 'asset.criticality': CriticalityLevels.HIGH_IMPACT } }],
minimum_should_match: 1,
}, },
}, },
{ term: { 'entity.source': EntitySource.CSV_UPLOAD } }, {
bool: {
should: [{ wildcard: { 'entity.source': 'risk-score.risk-score-*' } }],
},
},
globalQuery, globalQuery,
]; ];

View file

@ -7,15 +7,19 @@
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { CriticalityLevels } from '../../../../../common/constants'; import {
ASSET_CRITICALITY_INDEX_PATTERN,
RISK_SCORE_INDEX_PATTERN,
type CriticalityLevels,
} from '../../../../../common/constants';
import type { RiskSeverity } from '../../../../../common/search_strategy'; import type { RiskSeverity } from '../../../../../common/search_strategy';
import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query';
import type { EntitySource } from '../components/entity_source_filter'; import { EntitySourceTag } from '../types';
interface UseEntitiesListFiltersParams { interface UseEntitiesListFiltersParams {
selectedSeverities: RiskSeverity[]; selectedSeverities: RiskSeverity[];
selectedCriticalities: CriticalityLevels[]; selectedCriticalities: CriticalityLevels[];
selectedSources: EntitySource[]; selectedSources: EntitySourceTag[];
} }
export const useEntitiesListFilters = ({ export const useEntitiesListFilters = ({
@ -35,17 +39,20 @@ export const useEntitiesListFilters = ({
'asset.criticality': value, 'asset.criticality': value,
}, },
})), })),
minimum_should_match: 1,
}, },
}, },
] ]
: []; : [];
const sourceFilter: QueryDslQueryContainer[] = selectedSources.map((value) => ({ const sourceFilter: QueryDslQueryContainer[] = selectedSources.length
term: { ? [
'entity.source': value, {
}, bool: {
})); should: selectedSources.map((tag) => getSourceTagFilterQuery(tag)),
},
},
]
: [];
const severityFilter: QueryDslQueryContainer[] = selectedSeverities.length const severityFilter: QueryDslQueryContainer[] = selectedSeverities.length
? [ ? [
@ -63,7 +70,6 @@ export const useEntitiesListFilters = ({
}, },
}, },
]), ]),
minimum_should_match: 1,
}, },
}, },
] ]
@ -80,3 +86,37 @@ export const useEntitiesListFilters = ({
return filterList; return filterList;
}, [globalQuery, selectedCriticalities, selectedSeverities, selectedSources]); }, [globalQuery, selectedCriticalities, selectedSeverities, selectedSources]);
}; };
const getSourceTagFilterQuery = (tag: EntitySourceTag): QueryDslQueryContainer => {
if (tag === EntitySourceTag.risk) {
return {
wildcard: {
'entity.source': RISK_SCORE_INDEX_PATTERN,
},
};
}
if (tag === EntitySourceTag.criticality) {
return {
wildcard: {
'entity.source': ASSET_CRITICALITY_INDEX_PATTERN,
},
};
}
return {
bool: {
must_not: [
{
wildcard: {
'entity.source': ASSET_CRITICALITY_INDEX_PATTERN,
},
},
{
wildcard: {
'entity.source': RISK_SCORE_INDEX_PATTERN,
},
},
],
},
};
};

View file

@ -5,10 +5,8 @@
* 2.0. * 2.0.
*/ */
import type { export enum EntitySourceTag {
Entity, 'risk' = 'Risk',
UserEntity, 'criticality' = 'Asset Criticality',
} from '../../../../common/api/entity_analytics/entity_store/entities/common.gen'; 'events' = 'Events',
}
export const isUserEntity = (record: Entity): record is UserEntity =>
!!(record as UserEntity)?.user;

View file

@ -7,7 +7,7 @@
import type { EntityType } from '../../../../../../common/api/entity_analytics/entity_store'; import type { EntityType } from '../../../../../../common/api/entity_analytics/entity_store';
import { getIdentityFieldForEntityType } from '../../utils'; import { getIdentityFieldForEntityType } from '../../utils';
import { collectValues, newestValue } from '../definition_utils'; import { oldestValue, newestValue } from '../definition_utils';
import type { UnitedDefinitionField } from '../types'; import type { UnitedDefinitionField } from '../types';
export const getCommonUnitedFieldDefinitions = ({ export const getCommonUnitedFieldDefinitions = ({
@ -19,10 +19,9 @@ export const getCommonUnitedFieldDefinitions = ({
}): UnitedDefinitionField[] => { }): UnitedDefinitionField[] => {
const identityField = getIdentityFieldForEntityType(entityType); const identityField = getIdentityFieldForEntityType(entityType);
return [ return [
collectValues({ oldestValue({
sourceField: '_index', sourceField: '_index',
field: 'entity.source', field: 'entity.source',
fieldHistoryLength,
}), }),
newestValue({ field: 'asset.criticality' }), newestValue({ field: 'asset.criticality' }),
newestValue({ newestValue({

View file

@ -117,8 +117,7 @@ describe('getUnitedEntityDefinition', () => {
}, },
Object { Object {
"field": "entity.source", "field": "entity.source",
"maxLength": 10, "operation": "prefer_oldest_value",
"operation": "collect_values",
}, },
Object { Object {
"field": "asset.criticality", "field": "asset.criticality",
@ -219,8 +218,10 @@ describe('getUnitedEntityDefinition', () => {
}, },
Object { Object {
"aggregation": Object { "aggregation": Object {
"limit": 10, "sort": Object {
"type": "terms", "@timestamp": "asc",
},
"type": "top_value",
}, },
"destination": "entity.source", "destination": "entity.source",
"source": "_index", "source": "_index",
@ -373,8 +374,7 @@ describe('getUnitedEntityDefinition', () => {
}, },
Object { Object {
"field": "entity.source", "field": "entity.source",
"maxLength": 10, "operation": "prefer_oldest_value",
"operation": "collect_values",
}, },
Object { Object {
"field": "asset.criticality", "field": "asset.criticality",
@ -467,8 +467,10 @@ describe('getUnitedEntityDefinition', () => {
}, },
Object { Object {
"aggregation": Object { "aggregation": Object {
"limit": 10, "sort": Object {
"type": "terms", "@timestamp": "asc",
},
"type": "top_value",
}, },
"destination": "entity.source", "destination": "entity.source",
"source": "_index", "source": "_index",