Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Inventory][ECO] Replace Entity with InventoryEntityLatest type #198760

Merged
merged 34 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ad95291
Improve typing
crespocarlos Oct 23, 2024
99cd0d4
Improve types
crespocarlos Oct 25, 2024
b23fae2
update type considering the latest changes
crespocarlos Nov 4, 2024
8ba7131
Adjust group by feature
crespocarlos Nov 4, 2024
9321024
Fix imports
crespocarlos Nov 4, 2024
5155dcf
Create an inteface between API and UI independent from index mapping
crespocarlos Nov 5, 2024
cb96542
Clean up
crespocarlos Nov 5, 2024
b0d66b1
Merge branch 'main' into 196142-adjust-types
elasticmachine Nov 5, 2024
bd28149
Missing change
crespocarlos Nov 5, 2024
0334bfc
Fix tests and zod schema
crespocarlos Nov 5, 2024
8a49c1c
Fix infra get_latest_entity
crespocarlos Nov 6, 2024
be5cc41
Revert get_entity_types change
crespocarlos Nov 6, 2024
8ef786e
Merge branch 'main' of github.com:elastic/kibana into 196142-adjust-t…
crespocarlos Nov 6, 2024
de12d05
Merge branch 'main' of github.com:elastic/kibana into 196142-adjust-t…
crespocarlos Nov 6, 2024
8d8df1c
Fix cypress tests
crespocarlos Nov 6, 2024
29551e2
CR fixes
crespocarlos Nov 8, 2024
595ac73
CR adjustments
crespocarlos Nov 8, 2024
185696f
More CR adjustments
crespocarlos Nov 8, 2024
b463ccc
More CR adjustments
crespocarlos Nov 8, 2024
33832dc
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Nov 8, 2024
1620988
Typo
crespocarlos Nov 8, 2024
a186a60
Fix join_by_key
crespocarlos Nov 8, 2024
b046148
Clean up
crespocarlos Nov 8, 2024
3f06d21
Merge branch 'main' of github.com:elastic/kibana into 196142-adjust-t…
crespocarlos Nov 8, 2024
3ccdac2
Fix after merge
crespocarlos Nov 8, 2024
38ffdc2
Type fix
crespocarlos Nov 8, 2024
ebf9c58
Merge branch 'main' into 196142-adjust-types
elasticmachine Nov 11, 2024
423b2a8
Replace API return type with InventoryEntity
crespocarlos Nov 11, 2024
f087c7d
Rename type guard function
crespocarlos Nov 11, 2024
38c7370
Fix after isEntityOfType was renamed
crespocarlos Nov 11, 2024
a370ab3
Merge branch 'main' of github.com:elastic/kibana into 196142-adjust-t…
crespocarlos Nov 12, 2024
535568b
Improve esql function type
crespocarlos Nov 12, 2024
21dd1a9
Fix after merge
crespocarlos Nov 12, 2024
3b5540d
Merge branch 'main' into 196142-adjust-types
elasticmachine Nov 12, 2024
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 @@ -58,6 +58,8 @@ export class K8sEntity extends Serializable<EntityFields> {
'entity.definition_id': `builtin_${entityTypeWithSchema}`,
'entity.identity_fields': identityFields,
'entity.display_name': getDisplayName({ identityFields, fields }),
'entity.definition_version': '1.0.0',
'entity.schema_version': '1.0',
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function replaceTemplateStrings(text, params = {}) {
filebeat: docLinks.links.filebeat.base,
metricbeat: docLinks.links.metricbeat.base,
heartbeat: docLinks.links.heartbeat.base,
functionbeat: docLinks.links.functionbeat.base,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed #198902

winlogbeat: docLinks.links.winlogbeat.base,
auditbeat: docLinks.links.auditbeat.base,
},
Expand Down
14 changes: 10 additions & 4 deletions x-pack/packages/kbn-entities-schema/src/schema/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { arrayOfStringsSchema } from './common';
export const entityBaseSchema = z.object({
id: z.string(),
type: z.string(),
identity_fields: arrayOfStringsSchema,
identity_fields: z.union([arrayOfStringsSchema, z.string()]),
Copy link
Contributor Author

@crespocarlos crespocarlos Nov 4, 2024

Choose a reason for hiding this comment

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

esql returns a string when fields hold a single value.

display_name: z.string(),
metrics: z.record(z.string(), z.number()),
metrics: z.optional(z.record(z.string(), z.number())),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

perhaps we should remove this?

Copy link
Contributor

Choose a reason for hiding this comment

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

The framework still supports metrics even if the Inventory doesn't use it, let's leave this for now

definition_version: z.string(),
schema_version: z.string(),
definition_id: z.string(),
Expand All @@ -24,10 +24,13 @@ export interface MetadataRecord {
}

const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);

type Literal = z.infer<typeof literalSchema>;
type Metadata = Literal | { [key: string]: Metadata } | Metadata[];
interface Metadata {
[key: string]: Metadata | Literal | Literal[];
}
export const entityMetadataSchema: z.ZodType<Metadata> = z.lazy(() =>
z.union([literalSchema, z.array(entityMetadataSchema), z.record(entityMetadataSchema)])
z.record(z.string(), z.union([literalSchema, z.array(literalSchema), entityMetadataSchema]))
);

export const entityLatestSchema = z
Expand All @@ -39,3 +42,6 @@ export const entityLatestSchema = z
),
})
.and(entityMetadataSchema);

export type EntityInstance = z.infer<typeof entityLatestSchema>;
export type EntityMetadata = z.infer<typeof entityMetadataSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,50 @@ describe('joinByKey', () => {
},
});
});

it('deeply merges by unflatten keys', () => {
const joined = joinByKey(
[
{
service: {
name: 'opbeans-node',
metrics: {
cpu: 0.1,
},
},
properties: {
foo: 'bar',
},
},
{
service: {
environment: 'prod',
metrics: {
memory: 0.5,
},
},
properties: {
foo: 'bar',
},
},
],
'properties.foo'
);

expect(joined).toEqual([
{
service: {
name: 'opbeans-node',
environment: 'prod',
metrics: {
cpu: 0.1,
memory: 0.5,
},
},
properties: {
foo: 'bar',
},
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,29 @@ export type JoinedReturnType<
}
>;

type ArrayOrSingle<T> = T | T[];
function getValueByPath(obj: any, path: string): any {
return path.split('.').reduce((acc, keyPart) => {
// Check if acc is a valid object and has the key
return acc && Object.prototype.hasOwnProperty.call(acc, keyPart) ? acc[keyPart] : undefined;
}, obj);
}

type NestedKeys<T> = T extends object
? { [K in keyof T]: K extends string ? `${K}` | `${K}.${NestedKeys<T[K]>}` : never }[keyof T]
: never;

type ArrayOrSingle<T> = T | T[];
type CombinedNestedKeys<T, U> = (NestedKeys<T> & NestedKeys<U>) | (keyof T & keyof U);
export function joinByKey<
T extends Record<string, any>,
U extends UnionToIntersection<T>,
V extends ArrayOrSingle<keyof T & keyof U>
V extends ArrayOrSingle<CombinedNestedKeys<T, U>>
>(items: T[], key: V): JoinedReturnType<T, U>;

export function joinByKey<
T extends Record<string, any>,
U extends UnionToIntersection<T>,
V extends ArrayOrSingle<keyof T & keyof U>,
V extends ArrayOrSingle<CombinedNestedKeys<T, U>>,
W extends JoinedReturnType<T, U>,
X extends (a: T, b: T) => ValuesType<W>
>(items: T[], key: V, mergeFn: X): W;
Expand All @@ -45,7 +56,7 @@ export function joinByKey(
items.forEach((current) => {
// The key of the map is a stable JSON string of the values from given keys.
// We need stable JSON string to support plain object values.
const stableKey = stableStringify(keys.map((k) => current[k]));
const stableKey = stableStringify(keys.map((k) => current[k] ?? getValueByPath(current, k)));

if (map.has(stableKey)) {
const item = map.get(stableKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,28 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import type { ESQLSearchResponse, ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types';
import { withSpan } from '@kbn/apm-utils';
import type { EsqlQueryRequest } from '@elastic/elasticsearch/lib/api/types';
import { esqlResultToPlainObjects } from '../utils/esql_result_to_plain_objects';

type SearchRequest = ESSearchRequest & {
index: string | string[];
track_total_hits: number | boolean;
size: number | boolean;
};

type EsqlQueryParameters = EsqlQueryRequest & { parseOutput?: boolean };
type EsqlOutputParameters = Omit<EsqlQueryRequest, 'format' | 'columnar'> & {
parseOutput?: true;
format?: 'json';
columnar?: false;
};

type EsqlParameters = EsqlOutputParameters | EsqlQueryParameters;

export type InferEsqlResponseOf<
TOutput = unknown,
TParameters extends EsqlParameters = EsqlParameters
> = TParameters extends EsqlOutputParameters ? TOutput[] : ESQLSearchResponse;

/**
* An Elasticsearch Client with a fully typed `search` method and built-in
* APM instrumentation.
Expand All @@ -25,7 +40,14 @@ export interface ObservabilityElasticsearchClient {
operationName: string,
parameters: TSearchRequest
): Promise<InferSearchResponseOf<TDocument, TSearchRequest>>;
esql(operationName: string, parameters: EsqlQueryRequest): Promise<ESQLSearchResponse>;
esql<TOutput = unknown, TQueryParams extends EsqlOutputParameters = EsqlOutputParameters>(
operationName: string,
parameters: TQueryParams
): Promise<InferEsqlResponseOf<TOutput, TQueryParams>>;
esql<TOutput = unknown, TQueryParams extends EsqlQueryParameters = EsqlQueryParameters>(
operationName: string,
parameters: TQueryParams
): Promise<InferEsqlResponseOf<TOutput, TQueryParams>>;
client: ElasticsearchClient;
}

Expand All @@ -40,11 +62,14 @@ export function createObservabilityEsClient({
}): ObservabilityElasticsearchClient {
return {
client,
esql(operationName: string, parameters: EsqlQueryRequest) {
esql<TOutput = unknown, TSearchRequest extends EsqlParameters = EsqlParameters>(
operationName: string,
{ parseOutput = true, format = 'json', columnar = false, ...parameters }: TSearchRequest
) {
logger.trace(() => `Request (${operationName}):\n${JSON.stringify(parameters, null, 2)}`);
return withSpan({ name: operationName, labels: { plugin } }, () => {
return client.esql.query(
{ ...parameters },
{ ...parameters, format, columnar },
{
querystring: {
drop_null_columns: true,
Expand All @@ -54,7 +79,11 @@ export function createObservabilityEsClient({
})
.then((response) => {
logger.trace(() => `Response (${operationName}):\n${JSON.stringify(response, null, 2)}`);
return response as unknown as ESQLSearchResponse;

const esqlResponse = response as unknown as ESQLSearchResponse;

const shouldParseOutput = parseOutput && !columnar && format === 'json';
return shouldParseOutput ? esqlResultToPlainObjects<TOutput>(esqlResponse) : esqlResponse;
})
.catch((error) => {
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@
*/

import type { ESQLSearchResponse } from '@kbn/es-types';
import { unflattenObject } from '../../object/unflatten_object';

export function esqlResultToPlainObjects<T extends Record<string, any>>(
export function esqlResultToPlainObjects<TDocument = unknown>(
result: ESQLSearchResponse
): T[] {
): TDocument[] {
return result.values.map((row) => {
return row.reduce<Record<string, unknown>>((acc, value, index) => {
const column = result.columns[index];
return unflattenObject(
row.reduce<Record<string, any>>((acc, value, index) => {
const column = result.columns[index];

if (!column) {
return acc;
}
if (!column) {
return acc;
}

// Removes the type suffix from the column name
const name = column.name.replace(/\.(text|keyword)$/, '');
if (!acc[name]) {
acc[name] = value;
}
// Removes the type suffix from the column name
const name = column.name.replace(/\.(text|keyword)$/, '');
if (!acc[name]) {
acc[name] = value;
}

return acc;
}, {});
}) as T[];
return acc;
}, {})
) as TDocument;
});
}
21 changes: 11 additions & 10 deletions x-pack/plugins/entity_manager/public/lib/entity_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
* 2.0.
*/

import { EntityClient, EnitityInstance } from './entity_client';
import { EntityClient } from './entity_client';
import { coreMock } from '@kbn/core/public/mocks';
import type { EntityInstance } from '@kbn/entities-schema';

const commonEntityFields: EnitityInstance = {
const commonEntityFields: EntityInstance = {
entity: {
last_seen_timestamp: '2023-10-09T00:00:00Z',
id: '1',
display_name: 'entity_name',
definition_id: 'entity_definition_id',
} as EnitityInstance['entity'],
} as EntityInstance['entity'],
};

describe('EntityClient', () => {
Expand All @@ -26,7 +27,7 @@ describe('EntityClient', () => {

describe('asKqlFilter', () => {
it('should return the kql filter', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
entity: {
...commonEntityFields.entity,
identity_fields: ['service.name', 'service.environment'],
Expand All @@ -42,7 +43,7 @@ describe('EntityClient', () => {
});

it('should return the kql filter when indentity_fields is composed by multiple fields', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
entity: {
...commonEntityFields.entity,
identity_fields: ['service.name', 'service.environment'],
Expand All @@ -59,7 +60,7 @@ describe('EntityClient', () => {
});

it('should ignore fields that are not present in the entity', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
entity: {
...commonEntityFields.entity,
identity_fields: ['host.name', 'foo.bar'],
Expand All @@ -76,7 +77,7 @@ describe('EntityClient', () => {

describe('getIdentityFieldsValue', () => {
it('should return identity fields values', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
entity: {
...commonEntityFields.entity,
identity_fields: ['service.name', 'service.environment'],
Expand All @@ -93,7 +94,7 @@ describe('EntityClient', () => {
});

it('should return identity fields values when indentity_fields is composed by multiple fields', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
entity: {
...commonEntityFields.entity,
identity_fields: ['service.name', 'service.environment'],
Expand All @@ -112,7 +113,7 @@ describe('EntityClient', () => {
});

it('should return identity fields when field is in the root', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
entity: {
...commonEntityFields.entity,
identity_fields: ['name'],
Expand All @@ -127,7 +128,7 @@ describe('EntityClient', () => {
});

it('should throw an error when identity fields are missing', () => {
const entityLatest: EnitityInstance = {
const entityLatest: EntityInstance = {
...commonEntityFields,
};

Expand Down
Loading