Skip to content

Commit

Permalink
[eem] _count api (#203605)
Browse files Browse the repository at this point in the history
implements `countEntities` API

the query to count a single-source definition is straightforward but
gets tricky when sources > 1 because we have to resolve entity ids to
avoid counting duplicates. I've reused the entity.id/source eval logic
implemented here
elastic/elastic-entity-model#202 (comment)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
klacabane and kibanamachine authored Dec 12, 2024
1 parent 16d45f5 commit 0350618
Show file tree
Hide file tree
Showing 18 changed files with 1,293 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ import {
storeSourceDefinition,
} from './definitions/source_definition';
import { readTypeDefinitions, storeTypeDefinition } from './definitions/type_definition';
import { getEntityInstancesQuery } from './queries';
import { mergeEntitiesList, sortEntitiesList } from './queries/utils';
import { getEntityInstancesQuery, getEntityCountQuery } from './queries';
import {
isFulfilledResult,
isRejectedResult,
mergeEntitiesList,
sortEntitiesList,
} from './queries/utils';
import {
EntitySourceDefinition,
EntityTypeDefinition,
SearchByType,
SearchBySources,
CountByTypes,
} from './types';
import { UnknownEntityType } from './errors/unknown_entity_type';
import { runESQLQuery } from './run_esql_query';
Expand Down Expand Up @@ -79,7 +85,7 @@ export class EntityClient {
limit,
});
this.options.logger.debug(
() => `Entity query: ${query}\nfilter: ${JSON.stringify(filter, null, 2)}`
() => `Entity instances query: ${query}\nfilter: ${JSON.stringify(filter, null, 2)}`
);

const rawEntities = await runESQLQuery<EntityV2>('resolve entities', {
Expand All @@ -92,15 +98,10 @@ export class EntityClient {
return rawEntities;
});

const results = await Promise.allSettled(searches);
const entities = (
results.filter((result) => result.status === 'fulfilled') as Array<
PromiseFulfilledResult<EntityV2[]>
>
).flatMap((result) => result.value);
const errors = (
results.filter((result) => result.status === 'rejected') as PromiseRejectedResult[]
).map((result) => result.reason.message);
const { entities, errors } = await Promise.allSettled(searches).then((results) => ({
entities: results.filter(isFulfilledResult).flatMap((result) => result.value),
errors: results.filter(isRejectedResult).map((result) => result.reason.message as string),
}));

if (sources.length === 1) {
return { entities, errors };
Expand All @@ -118,6 +119,63 @@ export class EntityClient {
};
}

async countEntities({ start, end, types = [], filters = [] }: CountByTypes) {
if (types.length === 0) {
types = (await this.readTypeDefinitions()).map((definition) => definition.id);
}

const counts = await Promise.all(
types.map(async (type) => {
const sources = await this.readSourceDefinitions({ type });
if (sources.length === 0) {
return { type, value: 0, errors: [] };
}

const { sources: validSources, errors } = await Promise.allSettled(
sources.map((source) =>
validateFields({
source,
esClient: this.options.clusterClient.asCurrentUser,
logger: this.options.logger,
}).then(() => source)
)
).then((results) => ({
sources: results.filter(isFulfilledResult).flatMap((result) => result.value),
errors: results.filter(isRejectedResult).map((result) => result.reason.message as string),
}));

const { query, filter } = getEntityCountQuery({
sources: validSources,
filters,
start,
end,
});
this.options.logger.info(
`Entity count query: ${query}\nfilter: ${JSON.stringify(filter, null, 2)}`
);

const [{ count }] = await runESQLQuery<{ count: number }>('count entities', {
query,
filter,
esClient: this.options.clusterClient.asCurrentUser,
logger: this.options.logger,
});

return { type, value: count, errors };
})
);

return counts.reduce(
(result, count) => {
result.types[count.type] = count.value;
result.total += count.value;
result.errors.push(...count.errors);
return result;
},
{ total: 0, types: {} as Record<string, number>, errors: [] as string[] }
);
}

async storeTypeDefinition(type: EntityTypeDefinition) {
return storeTypeDefinition({
type,
Expand Down
Loading

0 comments on commit 0350618

Please sign in to comment.