Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
615f247
docs: add design spec for entity maintainers relationship schema update
seanrathier Apr 13, 2026
c563696
docs: add implementation plan for entity maintainers relationship sch…
seanrathier Apr 13, 2026
dc49cba
feat(entity-analytics): update communicates_with relationship to new …
seanrathier Apr 13, 2026
803ccb2
feat(entity-analytics): update ProcessedEntityRecord to carry relatio…
seanrathier Apr 13, 2026
a577916
feat(entity-analytics): update postprocessEsqlResults to emit ids obj…
seanrathier Apr 13, 2026
8c4d0c1
feat(entity-analytics): update accesses run_maintainer logging for ne…
seanrathier Apr 13, 2026
c6da530
feat(entity-analytics): update accesses update_entities to use new id…
seanrathier Apr 13, 2026
ba9ad77
chore: apply lint fixes to entity maintainer schema update
seanrathier Apr 13, 2026
0deeb0c
feat(entity-analytics): align communicates_with ProcessedEntityRecord…
seanrathier Apr 13, 2026
61d48bc
lint fix
seanrathier Apr 13, 2026
c752e35
revert: remove docs/superpowers from repo
seanrathier Apr 13, 2026
618f27f
revert: remove docs/superpowers from repo
seanrathier Apr 13, 2026
68374cb
test(entity-analytics): add failing tests for accesses merge-before-w…
seanrathier Apr 13, 2026
d9362ac
test(entity-analytics): add deduplication and entityType tests for ac…
seanrathier Apr 13, 2026
6414775
fix(entity-analytics): merge accesses records by entityId before bulk…
seanrathier Apr 13, 2026
d0d0eae
refactor(entity-analytics): inline entity doc and add empty-ids guard…
seanrathier Apr 13, 2026
ee31fa8
Merge branch 'main' of github.com:elastic/kibana
seanrathier Apr 14, 2026
552051c
Merge branch 'main' into feat/entity-maintainers-relationship-schema-…
seanrathier Apr 14, 2026
2ae50e1
Merge branch 'main' of github.com:elastic/kibana into feat/entity-mai…
seanrathier Apr 15, 2026
c3e2829
[Entity Analytics] Address review feedback on accesses update_entities
seanrathier Apr 15, 2026
3b9883b
[Entity Analytics] Tighten entityType to EntityType in communicates_with
seanrathier Apr 16, 2026
f6d1517
Merge branch 'main' into feat/entity-maintainers-relationship-schema-…
seanrathier Apr 16, 2026
2a0392c
[Entity Analytics] Refactor maintainer merge logic into named pipelin…
seanrathier Apr 16, 2026
f5619b5
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 16, 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
@@ -0,0 +1,78 @@
/*
* 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 { postprocessEsqlResults } from './postprocess_records';

const COLUMNS = [
{ name: 'actorUserId', type: 'keyword' },
{ name: 'accesses_frequently', type: 'keyword' },
{ name: 'accesses_infrequently', type: 'keyword' },
];

describe('postprocessEsqlResults', () => {
it('returns an empty array when values is empty', () => {
expect(postprocessEsqlResults(COLUMNS, [])).toEqual([]);
});

it('sets entityId to null when actorUserId is null', () => {
const [record] = postprocessEsqlResults(COLUMNS, [[null, null, null]]);
expect(record.entityId).toBeNull();
});

it('prefixes actorUserId with "user:" to form entityId', () => {
const [record] = postprocessEsqlResults(COLUMNS, [['alice@corp', null, null]]);
expect(record.entityId).toBe('user:alice@corp');
});

it('wraps null accesses_frequently in { ids: [] }', () => {
const [record] = postprocessEsqlResults(COLUMNS, [['alice@corp', null, null]]);
expect(record.accesses_frequently).toEqual({ ids: [] });
});

it('wraps null accesses_infrequently in { ids: [] }', () => {
const [record] = postprocessEsqlResults(COLUMNS, [['alice@corp', null, null]]);
expect(record.accesses_infrequently).toEqual({ ids: [] });
});

it('wraps a single string accesses_frequently value in { ids: [value] }', () => {
const [record] = postprocessEsqlResults(COLUMNS, [
['alice@corp', 'host:prod-db-01@corp', null],
]);
expect(record.accesses_frequently).toEqual({ ids: ['host:prod-db-01@corp'] });
});

it('wraps an array accesses_frequently value in { ids: [...] }', () => {
const [record] = postprocessEsqlResults(COLUMNS, [
['alice@corp', ['host:prod-db-01@corp', 'host:prod-db-02@corp'], null],
]);
expect(record.accesses_frequently).toEqual({
ids: ['host:prod-db-01@corp', 'host:prod-db-02@corp'],
});
});

it('wraps accesses_infrequently independently from accesses_frequently', () => {
const [record] = postprocessEsqlResults(COLUMNS, [
['alice@corp', ['host:prod-db-01@corp'], ['host:legacy-01@corp']],
]);
expect(record.accesses_frequently).toEqual({ ids: ['host:prod-db-01@corp'] });
expect(record.accesses_infrequently).toEqual({ ids: ['host:legacy-01@corp'] });
});

it('processes multiple rows independently', () => {
const results = postprocessEsqlResults(COLUMNS, [
['alice@corp', ['host:a@corp'], null],
['bob@corp', null, ['host:b@corp']],
]);
expect(results).toHaveLength(2);
expect(results[0].entityId).toBe('user:alice@corp');
expect(results[0].accesses_frequently).toEqual({ ids: ['host:a@corp'] });
expect(results[0].accesses_infrequently).toEqual({ ids: [] });
expect(results[1].entityId).toBe('user:bob@corp');
expect(results[1].accesses_frequently).toEqual({ ids: [] });
expect(results[1].accesses_infrequently).toEqual({ ids: ['host:b@corp'] });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export function postprocessEsqlResults(
return {
// update entity method is using `user:<user_euid>` format
entityId: record.actorUserId != null ? `user:${record.actorUserId}` : null,
accesses_frequently: toStringArray(record.accesses_frequently),
accesses_infrequently: toStringArray(record.accesses_infrequently),
accesses_frequently: { ids: toStringArray(record.accesses_frequently) },
accesses_infrequently: { ids: toStringArray(record.accesses_infrequently) },
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,15 @@ describe('runMaintainer', () => {
const page1Records: ProcessedEntityRecord[] = [
{
entityId: 'user-0',
accesses_frequently: ['host-a'],
accesses_infrequently: [],
accesses_frequently: { ids: ['host-a'] },
accesses_infrequently: { ids: [] },
},
];
const page2Records: ProcessedEntityRecord[] = [
{
entityId: 'user-last',
accesses_frequently: [],
accesses_infrequently: ['host-b'],
accesses_frequently: { ids: [] },
accesses_infrequently: { ids: ['host-b'] },
},
];

Expand Down Expand Up @@ -303,15 +303,15 @@ describe('runMaintainer', () => {
const records1: ProcessedEntityRecord[] = [
{
entityId: 'user-a',
accesses_frequently: ['host-1'],
accesses_infrequently: [],
accesses_frequently: { ids: ['host-1'] },
accesses_infrequently: { ids: [] },
},
];
const records2: ProcessedEntityRecord[] = [
{
entityId: 'user-b',
accesses_frequently: [],
accesses_infrequently: ['host-2'],
accesses_frequently: { ids: [] },
accesses_infrequently: { ids: ['host-2'] },
},
];

Expand Down Expand Up @@ -429,8 +429,8 @@ describe('runMaintainer', () => {
const records: ProcessedEntityRecord[] = [
{
entityId: 'user-1',
accesses_frequently: [],
accesses_infrequently: ['host-a'],
accesses_frequently: { ids: [] },
accesses_infrequently: { ids: ['host-a'] },
},
];
esClient.esql.query.mockResolvedValueOnce(createEsqlResponse() as never);
Expand Down Expand Up @@ -564,7 +564,11 @@ describe('runMaintainer', () => {
it('skips bulk update when aborted after collecting records', async () => {
const abortCtrl = new AbortController();
const records: ProcessedEntityRecord[] = [
{ entityId: 'user-1', accesses_frequently: ['host-a'], accesses_infrequently: [] },
{
entityId: 'user-1',
accesses_frequently: { ids: ['host-a'] },
accesses_infrequently: { ids: [] },
},
];

esClient.search.mockResolvedValueOnce(createAggResponse([createBucket('user-1')]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ export async function runMaintainer({

for (const record of records) {
const freq =
record.accesses_frequently.length > 0 ? record.accesses_frequently.join(', ') : 'none';
record.accesses_frequently.ids.length > 0
? record.accesses_frequently.ids.join(', ')
: 'none';
const infreq =
record.accesses_infrequently.length > 0
? record.accesses_infrequently.join(', ')
record.accesses_infrequently.ids.length > 0
? record.accesses_infrequently.ids.join(', ')
: 'none';
logger.info(
`[${integration.id}] Entity ${record.entityId}: frequently=[${freq}], infrequently=[${infreq}]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export interface CompositeBucket {

export interface ProcessedEntityRecord {
entityId: string | null;
accesses_frequently: string[];
accesses_infrequently: string[];
accesses_frequently: { ids: string[] };
accesses_infrequently: { ids: string[] };
}
Loading
Loading