Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 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
bede6a9
read related fields of euid - WIP
kfirpeled Apr 6, 2026
a7f3a50
WIP - fetch euid fields to document data
kfirpeled Apr 6, 2026
c609395
Merge branch 'main' into siem/ea-entity-flyout-euid-filters
kfirpeled Apr 6, 2026
8e25562
filter only by supported source fields
kfirpeled Apr 6, 2026
37f5973
Merge branch 'main' into siem/ea-entity-flyout-euid-filters
kfirpeled Apr 6, 2026
479ed88
Changes from node scripts/lint_ts_projects --fix
kibanamachine Apr 7, 2026
76fdf2a
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Apr 7, 2026
28cd968
Fix: Pin entities using only EUIDs
albertoblaz Apr 9, 2026
e194c46
Fix: Prevent relationships query from breaking with empty entityIds
albertoblaz Apr 9, 2026
6e932d7
Fix: build docData JSON correctly in relationships
albertoblaz Apr 9, 2026
7733827
Test: Assert sourceFields without target in key
albertoblaz Apr 9, 2026
812e499
Test: Assert engine_type in API tests
albertoblaz Apr 9, 2026
fa273e2
Test: Assert filters in correct order in UI tests
albertoblaz Apr 9, 2026
1c83dec
Remove Entity Store from package references
albertoblaz Apr 9, 2026
276700d
Test: Assert correct fields in query tests
albertoblaz Apr 11, 2026
76ca5ed
Fix: Update preview hook
albertoblaz Apr 11, 2026
1886448
Test: Update preview hook tests
albertoblaz Apr 11, 2026
05f65e5
Refactor: Clean-up unused function in parse_records
albertoblaz Apr 11, 2026
9ed30f8
Fix: Match generic entities in relationships query
albertoblaz Apr 11, 2026
cda60fc
Merge branch 'main' into siem/ea-entity-flyout-euid-filters
albertoblaz Apr 11, 2026
a36803e
refactoring
kfirpeled Apr 13, 2026
6cd3458
Merge branch 'main' into siem/ea-entity-flyout-euid-filters
kfirpeled Apr 13, 2026
0eb1d5e
Merge branch 'main' into siem/ea-entity-flyout-euid-filters
kfirpeled Apr 13, 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 @@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { EntityStoreEuid } from '@kbn/entity-store/common/euid_helpers';
import type { VulnSeverity } from './types/vulnerabilities';
import type { MisconfigurationEvaluationStatus } from './types/misconfigurations';

Expand Down Expand Up @@ -166,36 +167,32 @@ export const GRAPH_TARGET_ENTITY_FIELDS = [
* These mirror the identity fields from Entity Store definitions.
* Server-side code derives these dynamically via euid.getEuidSourceFields().
*/
export const GRAPH_ACTOR_EUID_SOURCE_FIELDS = [
// user EUID source fields
'user.email',
'user.id',
'user.name',
// host EUID source fields
'host.id',
'host.name',
'host.hostname',
// service EUID source field
'service.name',
// generic entity id
'entity.id',
] as const;
export const getGraphActorEuidSourceFields = (euid: EntityStoreEuid) => {
return {
user: [...euid.getEuidSourceFields('user').identitySourceFields],
host: [...euid.getEuidSourceFields('host').identitySourceFields],
service: [...euid.getEuidSourceFields('service').identitySourceFields],
generic: [...euid.getEuidSourceFields('generic').identitySourceFields],
all: ['event.dataset', 'event.module', 'data_stream.dataset'],
};
};

function toTargetField(field: string): string {
return field.replace('.', '.target.');
}

/**
* Raw source fields used to compute target EUIDs in entity store v2.
* Target-namespace equivalents of GRAPH_ACTOR_EUID_SOURCE_FIELDS.
*/
export const GRAPH_TARGET_EUID_SOURCE_FIELDS = [
// user target EUID source fields
'user.target.email',
'user.target.id',
'user.target.name',
// host target EUID source fields
'host.target.id',
'host.target.name',
'host.target.hostname',
// service target EUID source field
'service.target.name',
// generic target entity id
'entity.target.id',
] as const;
export const getGraphTargetEuidSourceFields = (euid: EntityStoreEuid) => {
return {
user: [...euid.getEuidSourceFields('user').identitySourceFields.map(toTargetField)],
host: [...euid.getEuidSourceFields('host').identitySourceFields.map(toTargetField)],
service: [...euid.getEuidSourceFields('service').identitySourceFields.map(toTargetField)],
generic: [...euid.getEuidSourceFields('generic').identitySourceFields.map(toTargetField)],
all: ['event.dataset', 'event.module', 'data_stream.dataset'],
};
};

export type EuidSourceFields = ReturnType<typeof getGraphActorEuidSourceFields>;
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,21 @@ 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()),
engine_type: schema.maybe(
schema.oneOf([
schema.literal('host'),
schema.literal('user'),
schema.literal('service'),
schema.literal('generic'),
])
),
host: schema.maybe(
schema.object({
ip: schema.maybe(schema.string()),
})
),
availableInEntityStore: schema.maybe(schema.boolean()),
sourceFields: schema.maybe(
schema.recordOf(
schema.string(),
schema.oneOf([schema.string(), schema.arrayOf(schema.string())])
)
),
sourceFields: schema.maybe(schema.object({}, { unknowns: 'allow' })),
});

export const nodeDocumentDataSchema = schema.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
destroyFilterStore,
emitFilterToggle,
emitEntityRelationshipToggle,
emitPinnedEuidToggle,
isFilterActiveForScope,
isEntityRelationshipExpandedForScope,
isPinnedForScope,
} from './filter_store';

// Simple phrase filter builder for tests
Expand Down Expand Up @@ -187,17 +189,67 @@ describe('FilterStore', () => {
});
});

describe('togglePinnedEuid', () => {
it('should pin an entity with action "show"', () => {
const store = new FilterStore(uniqueScopeId());
store.togglePinnedEuid('user:alice@okta', 'show');
expect(store.isPinned('user:alice@okta')).toBe(true);
store.destroy();
});

it('should unpin an entity with action "hide"', () => {
const store = new FilterStore(uniqueScopeId());
store.togglePinnedEuid('user:alice@okta', 'show');
store.togglePinnedEuid('user:alice@okta', 'hide');
expect(store.isPinned('user:alice@okta')).toBe(false);
store.destroy();
});

it('should handle multiple pinned entities independently', () => {
const store = new FilterStore(uniqueScopeId());
store.togglePinnedEuid('user:alice@okta', 'show');
store.togglePinnedEuid('host:server-1', 'show');

expect(store.isPinned('user:alice@okta')).toBe(true);
expect(store.isPinned('host:server-1')).toBe(true);

store.togglePinnedEuid('user:alice@okta', 'hide');
expect(store.isPinned('user:alice@okta')).toBe(false);
expect(store.isPinned('host:server-1')).toBe(true);
store.destroy();
});
});

describe('subscribeToPinnedEuids', () => {
it('should notify subscribers when pinned EUIDs change', () => {
const store = new FilterStore(uniqueScopeId());
const callback = jest.fn();
const subscription = store.subscribeToPinnedEuids(callback);

// BehaviorSubject emits current value on subscribe
expect(callback).toHaveBeenCalledWith(new Set());

store.togglePinnedEuid('user:alice@okta', 'show');
expect(callback).toHaveBeenCalledWith(new Set(['user:alice@okta']));

subscription.unsubscribe();
store.destroy();
});
});

describe('reset', () => {
it('should clear filters and expanded entity IDs', () => {
it('should clear filters, expanded entity IDs, and pinned EUIDs', () => {
const store = new FilterStore(uniqueScopeId());
store.setDataViewId('data-view-1');
store.toggleFilter('user.name', 'alice', 'show');
store.toggleEntityRelationship('entity-1', 'show');
store.togglePinnedEuid('user:alice@okta', 'show');

store.reset();

expect(store.getFilters()).toEqual([]);
expect(store.getExpandedEntityIds().size).toBe(0);
expect(store.getPinnedEuids().size).toBe(0);
store.destroy();
});
});
Expand Down Expand Up @@ -275,6 +327,38 @@ describe('event bus', () => {
destroyFilterStore(idB);
});
});

describe('emitPinnedEuidToggle', () => {
it('should deliver pinned EUID events to the matching store', () => {
const id = uniqueScopeId();
const store = getOrCreateFilterStore(id);

emitPinnedEuidToggle(id, 'user:alice@okta', 'show');

expect(store.isPinned('user:alice@okta')).toBe(true);
destroyFilterStore(id);
});

it('should not deliver pinned EUID events to a different scope', () => {
const idA = uniqueScopeId();
const idB = uniqueScopeId();
getOrCreateFilterStore(idA);
getOrCreateFilterStore(idB);

emitPinnedEuidToggle(idA, 'user:alice@okta', 'show');

expect(isPinnedForScope(idA, 'user:alice@okta')).toBe(true);
expect(isPinnedForScope(idB, 'user:alice@okta')).toBe(false);
destroyFilterStore(idA);
destroyFilterStore(idB);
});

it('should not throw when no store exists for the scopeId', () => {
expect(() => {
emitPinnedEuidToggle('non-existent', 'user:alice@okta', 'show');
}).not.toThrow();
});
});
});

describe('registry functions', () => {
Expand Down Expand Up @@ -376,4 +460,26 @@ describe('scope helper functions', () => {
expect(isEntityRelationshipExpandedForScope('non-existent', 'entity-1')).toBe(false);
});
});

describe('isPinnedForScope', () => {
it('should return true when entity is pinned', () => {
const id = uniqueScopeId();
const store = getOrCreateFilterStore(id);
store.togglePinnedEuid('user:alice@okta', 'show');

expect(isPinnedForScope(id, 'user:alice@okta')).toBe(true);
destroyFilterStore(id);
});

it('should return false when entity is not pinned', () => {
const id = uniqueScopeId();
getOrCreateFilterStore(id);
expect(isPinnedForScope(id, 'user:alice@okta')).toBe(false);
destroyFilterStore(id);
});

it('should return false when store does not exist', () => {
expect(isPinnedForScope('non-existent', 'user:alice@okta')).toBe(false);
});
});
});
Loading
Loading