Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a90ce28
WIP - initial PR, made it work for user host and service
kfirpeled Mar 29, 2026
16d3f58
type checks fixes
kfirpeled Mar 30, 2026
f7083dc
Merge branch 'main' into siem/ea-entity-flyout
kfirpeled Mar 30, 2026
624978c
Another type fix
kfirpeled Mar 30, 2026
c5523cf
Fixed tests and storybooks
kfirpeled Mar 30, 2026
886869c
another typecheck fix
kfirpeled Mar 30, 2026
6b66550
Merge branch 'main' into siem/ea-entity-flyout
kfirpeled Mar 31, 2026
22fdc65
Fixed entity relationships
kfirpeled Mar 31, 2026
e81fe49
Open preview flyout based on engine type
kfirpeled Mar 31, 2026
6d5c6e0
code cleanup
kfirpeled Mar 31, 2026
dac3933
type fixes
kfirpeled Apr 1, 2026
d3f9186
Merge branch 'main' into siem/ea-entity-flyout
kfirpeled Apr 1, 2026
a044cc5
(via kfirpeled): Fix CASE conditions in targetDocData to check target…
macroscopeapp[bot] Apr 1, 2026
26d6593
fixed fetch events graph
kfirpeled Apr 1, 2026
6da30e8
tests fixes
kfirpeled Apr 1, 2026
a5c82e4
storybook fix
kfirpeled Apr 1, 2026
89d390b
refactoring
kfirpeled Apr 1, 2026
d30d5af
bug fix
kfirpeled Apr 1, 2026
eb52875
tests fixes
kfirpeled Apr 1, 2026
0b4d22c
(via kfirpeled): Stabilize empty array reference passed to useGraphFi…
macroscopeapp[bot] Apr 1, 2026
4b6c975
fixed missed code due to merge
kfirpeled Apr 2, 2026
697e422
tests fixes
kfirpeled Apr 2, 2026
afaeae4
Merge branch 'main' into siem/ea-entity-flyout
kfirpeled Apr 2, 2026
cc84295
refactoring
kfirpeled Apr 2, 2026
2a08cc5
fixes
kfirpeled Apr 2, 2026
ce5f889
jest test fixes
kfirpeled Apr 2, 2026
416a217
FTR fix
kfirpeled Apr 2, 2026
0136d2e
ftr fix
kfirpeled Apr 3, 2026
c5cc04a
added support to open all the entities from grouped preview flyout
kfirpeled Apr 3, 2026
a32c76e
typecheck fix
kfirpeled Apr 4, 2026
be6447e
tests fixes
kfirpeled Apr 4, 2026
ca091d2
Merge branch 'main' into siem/ea-entity-flyout
kfirpeled Apr 4, 2026
fea3faa
(via kfirpeled): Add error handling to clickOnEntity method when enti…
macroscopeapp[bot] Apr 4, 2026
2cc83a9
fix commas replacement logic
kfirpeled Apr 6, 2026
4a4da38
revert changes
kfirpeled Apr 6, 2026
74b6963
refactoring
kfirpeled Apr 6, 2026
3f0b459
bug fix
kfirpeled Apr 6, 2026
2d219bc
Merge branch 'main' into siem/ea-entity-flyout
kfirpeled Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const entitySchema = schema.object({
name: schema.maybe(schema.string()),
type: schema.maybe(schema.string()),
sub_type: schema.maybe(schema.string()),
engine_type: schema.maybe(schema.string()),
host: schema.maybe(
schema.object({
ip: schema.maybe(schema.string()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import type {
} from '../../schema/graph/v1';
import { REACHED_NODES_LIMIT } from '../../schema/graph/v1';

export { DOCUMENT_TYPE_ALERT, DOCUMENT_TYPE_EVENT } from '../../schema/graph/v1';
export {
DOCUMENT_TYPE_ALERT,
DOCUMENT_TYPE_ENTITY,
DOCUMENT_TYPE_EVENT,
} from '../../schema/graph/v1';

export type GraphRequest = Omit<TypeOf<typeof graphRequestSchema>, 'query.esQuery'> & {
query: { esQuery?: { bool: Partial<BoolQuery> } };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ export const isEntityRelationshipExpandedForScope = (
return store?.isEntityRelationshipExpanded(entityId) ?? false;
};

export const isInitialEntityForScope = (scopeId: string, entityId: string): boolean => {
const store = stores.get(scopeId);
return store?.isInitialEntity(entityId) ?? false;
};

/**
* Check if a filter is active for the given scope, field, and value.
* Returns false gracefully if no store exists (no warning logged).
Expand Down Expand Up @@ -142,6 +147,7 @@ const stores = new Map<string, FilterStore>();
export class FilterStore {
readonly scopeId: string;
private dataViewId?: string;
private initialEntityIds: Array<{ id: string; isOrigin: boolean }> = [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would extract the inline type and define it as an interface so we can reuse it twice in this file and in use_graph_filters.ts

private readonly filters$ = new BehaviorSubject<Filter[]>([]);
private readonly expandedEntityIds$ = new BehaviorSubject<Set<string>>(new Set());
private readonly filterEventSubscription: Subscription;
Expand Down Expand Up @@ -174,6 +180,10 @@ export class FilterStore {
}
}

setInitialEntityIds(initialEntityIds: Array<{ id: string; isOrigin: boolean }>): void {
this.initialEntityIds = initialEntityIds;
}

/**
* Get the current filters from the store.
*/
Expand Down Expand Up @@ -244,7 +254,14 @@ export class FilterStore {
* Check if an entity's relationships are currently expanded.
*/
isEntityRelationshipExpanded(entityId: string): boolean {
return this.expandedEntityIds$.value.has(entityId);
return this.expandedEntityIds$.value.has(entityId) || this.isInitialEntity(entityId);
}

/**
* Check if an entity ID is part of the initial set of entities (e.g. from the original graph request).
*/
isInitialEntity(entityId: string): boolean {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium filters/filter_store.ts:263

isInitialEntity returns false for entities that exist in initialEntityIds with isOrigin: false, despite the JSDoc stating it checks "if an entity ID is part of the initial set of entities." This causes isEntityRelationshipExpanded to skip auto-expanding non-origin initial entities, which appears to be a semantic mismatch between the documented and actual behavior. If the intent is to check membership in the initial set, consider using .some() to check existence regardless of isOrigin. If the intent is specifically to check for origin entities, consider renaming the method to isOriginEntity and updating the JSDoc.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/filters/filter_store.ts around line 263:

`isInitialEntity` returns `false` for entities that exist in `initialEntityIds` with `isOrigin: false`, despite the JSDoc stating it checks "if an entity ID is part of the initial set of entities." This causes `isEntityRelationshipExpanded` to skip auto-expanding non-origin initial entities, which appears to be a semantic mismatch between the documented and actual behavior. If the intent is to check membership in the initial set, consider using `.some()` to check existence regardless of `isOrigin`. If the intent is specifically to check for origin entities, consider renaming the method to `isOriginEntity` and updating the JSDoc.

Evidence trail:
x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/src/components/filters/filter_store.ts lines 255-264 (REVIEWED_COMMIT): JSDoc at line 261-262 says 'Check if an entity ID is part of the initial set of entities' but implementation at line 264 returns `this.initialEntityIds.find((entity) => entity.id === entityId)?.isOrigin ?? false` which returns `false` for entities that exist in the array but have `isOrigin: false`. Line 150 shows the type: `Array<{ id: string; isOrigin: boolean }>`.

return this.initialEntityIds.find((entity) => entity.id === entityId)?.isOrigin ?? false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ import { getOrCreateFilterStore, destroyFilterStore } from './filter_store';
*/
export const useGraphFilters = (
scopeId: string,
initialEntityIds: Array<{
/**
* The ID of the entity.
*/
id: string;

/**
* Whether this entity is the origin of the graph (for centering).
*/
isOrigin: boolean;
}>,
dataViewId: string
): {
searchFilters: Filter[];
Expand All @@ -39,7 +50,8 @@ export const useGraphFilters = (
// Update dataViewId when it changes
useEffect(() => {
store.setDataViewId(dataViewId);
}, [store, dataViewId]);
store.setInitialEntityIds(initialEntityIds);
}, [store, dataViewId, initialEntityIds]);

// Clean up store on unmount or when scopeId changes
useEffect(() => {
Expand Down Expand Up @@ -91,13 +103,17 @@ export const useGraphFilters = (

// Convert expandedEntityIds Set to API format
const entityIdsForApi = useMemo(() => {
if (expandedEntityIds.size === 0) return undefined;
if (expandedEntityIds.size === 0) return initialEntityIds;

return Array.from(expandedEntityIds).map((id) => ({
id,
isOrigin: false, // User-expanded entities are not the graph origin
}));
}, [expandedEntityIds]);
return initialEntityIds.concat(
Array.from(expandedEntityIds)
.filter((id) => !initialEntityIds.some((entity) => entity.id === id))
.map((id) => ({
id,
isOrigin: false, // User-expanded entities are not the graph origin
}))
);
}, [expandedEntityIds, initialEntityIds]);

return {
searchFilters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const EntityItem: StoryFn<EntityStoryProps> = ({
}: EntityStoryProps) => {
const item: EntityItemType = {
itemType: DOCUMENT_TYPE_ENTITY,
entity: {},
...itemArgs,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('<GroupedItem />', () => {
risk: 55,
ips: ['5.5.5.5'],
countryCodes: ['US'],
entity: {},
}}
/>
);
Expand Down Expand Up @@ -138,7 +139,10 @@ describe('<GroupedItem />', () => {
it('falls back to entity id when entity label is missing', () => {
const entityId = 'entity-id';
const { getByTestId } = render(
<GroupedItem scopeId={TEST_SCOPE_ID} item={{ itemType: 'entity', id: entityId }} />
<GroupedItem
scopeId={TEST_SCOPE_ID}
item={{ itemType: 'entity', id: entityId, entity: {} }}
/>
);
expect(getByTestId(GROUPED_ITEM_TITLE_TEST_ID_TEXT).textContent).toBe(entityId);
});
Expand Down Expand Up @@ -175,6 +179,7 @@ describe('<GroupedItem />', () => {
itemType: 'entity',
id: 'e1',
label: 'entity-1',
entity: {},
actor: { id: 'a1', label: 'actor' },
target: { id: 't1', label: 'target' },
} as any // eslint-disable-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -413,6 +418,7 @@ describe('<GroupedItem />', () => {
itemType: 'entity',
id: 'entity-1',
label: 'test_entity',
entity: {},
}}
/>
);
Expand All @@ -432,6 +438,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: ['il'],
entity: {},
}}
/>
);
Expand All @@ -448,6 +455,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: undefined,
entity: {},
}}
/>
);
Expand All @@ -464,6 +472,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: [''],
entity: {},
}}
/>
);
Expand All @@ -480,6 +489,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: ['INVALID'],
entity: {},
}}
/>
);
Expand All @@ -493,6 +503,7 @@ describe('<GroupedItem />', () => {
itemType: 'entity',
id: 'e1',
label: 'entity-1',
entity: {},
};

const { getByTestId, rerender } = render(
Expand Down Expand Up @@ -531,6 +542,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
ips: undefined,
entity: {},
}}
/>
);
Expand All @@ -547,6 +559,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
ips: [''],
entity: {},
}}
/>
);
Expand All @@ -564,6 +577,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
ips: [ipv4],
entity: {},
}}
/>
);
Expand All @@ -581,6 +595,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
ips: ipArray,
entity: {},
}}
/>
);
Expand All @@ -597,6 +612,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
ips: [],
entity: {},
}}
/>
);
Expand All @@ -613,6 +629,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
ips: ['', ''],
entity: {},
}}
/>
);
Expand All @@ -632,6 +649,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: countryArray,
entity: {},
}}
/>
);
Expand All @@ -650,6 +668,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: [],
entity: {},
}}
/>
);
Expand All @@ -666,6 +685,7 @@ describe('<GroupedItem />', () => {
id: 'e1',
label: 'entity-1',
countryCodes: ['', ''],
entity: {},
}}
/>
);
Expand All @@ -692,6 +712,7 @@ describe('<GroupedItem />', () => {
itemType: 'entity',
id: 'e1',
label: 'entity-1',
entity: {},
}}
/>
);
Expand All @@ -710,6 +731,7 @@ describe('<GroupedItem />', () => {
itemType: 'entity',
id: 'e1',
label: 'entity-1',
entity: {},
}}
/>
);
Expand All @@ -727,6 +749,7 @@ describe('<GroupedItem />', () => {
itemType: 'entity',
id: 'e1',
label: 'entity-1',
entity: {},
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('EntityActionsButton', () => {
itemType: 'entity',
icon: 'user',
label: 'Test Entity',
availableInEntityStore: true,
entity: { availableInEntityStore: true },
};

const scopeId = 'test-scope-id';
Expand Down Expand Up @@ -81,7 +81,7 @@ describe('EntityActionsButton', () => {
describe('when entity is not available in entity store', () => {
const notInStoreItem: EntityItem = {
...mockEntityItem,
availableInEntityStore: false,
entity: { availableInEntityStore: false },
};

it('should render entity details item as disabled', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import React, { useCallback, useState } from 'react';
import { EuiButtonIcon, EuiPopover, EuiListGroup, EuiHorizontalRule } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { PopoverListItem } from '../../../../popovers/primitives/popover_list_item';
import {
GROUPED_ITEM_ACTIONS_BUTTON_TEST_ID,
Expand All @@ -26,8 +25,8 @@ import {
emitEntityRelationshipToggle,
isEntityRelationshipExpandedForScope,
} from '../../../../filters/filter_store';
import { GenericEntityPanelKey, GENERIC_ENTITY_PREVIEW_BANNER } from '../../../constants';
import { RELATED_ENTITY } from '../../../../../common/constants';
import { useOpenEntityPreviewPanel } from '../../../hooks/use_open_entity_preview_panel';

const actionsButtonAriaLabel = i18n.translate(
'securitySolutionPackages.csp.graph.groupedItem.actionsButton.ariaLabel',
Expand All @@ -52,25 +51,11 @@ export interface EntityActionsButtonProps {
*/
export const EntityActionsButton = ({ item, scopeId }: EntityActionsButtonProps) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const { openPreviewPanel } = useExpandableFlyoutApi();

const closePopover = useCallback(() => setIsPopoverOpen(false), []);
const togglePopover = useCallback(() => setIsPopoverOpen((prev) => !prev), []);

const handleShowEntityDetails = useCallback(() => {
openPreviewPanel({
id: GenericEntityPanelKey,
params: {
entityId: item.id,
scopeId,
isPreviewMode: true,
banner: GENERIC_ENTITY_PREVIEW_BANNER,
isEngineMetadataExist: !!item.availableInEntityStore,
},
});
}, [item.id, item.availableInEntityStore, openPreviewPanel, scopeId]);

const sourceFields = item.sourceFields ?? {};
const openEntityPreviewPanel = useOpenEntityPreviewPanel();
const sourceFields = item.entity.sourceFields ?? {};

const entityFilterActions: EntityFilterActions = {
toggleEntityFilter: (role, action) => {
Expand All @@ -95,7 +80,7 @@ export const EntityActionsButton = ({ item, scopeId }: EntityActionsButtonProps)
const items = getEntityExpandItems({
nodeId: item.id,
entityFilterActions,
onShowEntityDetails: handleShowEntityDetails,
onShowEntityDetails: () => openEntityPreviewPanel(item.id, scopeId, item.entity),
onClose: closePopover,
shouldRender: {
showEntityRelationships: true,
Expand All @@ -104,10 +89,10 @@ export const EntityActionsButton = ({ item, scopeId }: EntityActionsButtonProps)
showRelatedEvents: true,
showEntityDetails: true,
},
showEntityDetailsDisabled: !item.availableInEntityStore,
showEntityDetailsDisabled: !item.entity.availableInEntityStore,
isEntityRelationshipsExpanded: isEntityRelationshipExpandedForScope(scopeId, item.id),
toggleEntityRelationships: (action) => emitEntityRelationshipToggle(scopeId, item.id, action),
showEntityRelationshipsDisabled: !item.availableInEntityStore,
showEntityRelationshipsDisabled: !item.entity.availableInEntityStore,
});

return (
Expand Down
Loading
Loading