From e1ac3bd2c82a57a8fdb8c4e7f7ce69754ee07113 Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 29 Apr 2025 16:00:48 -0400 Subject: [PATCH 1/2] pull out object.keys from loop --- .../factory/helpers/build_ecs_objects.ts | 5 ++-- .../helpers/build_object_recursive.test.ts | 23 +++++++++++-------- .../factory/helpers/build_object_recursive.ts | 6 ++--- .../factory/helpers/format_timeline_data.ts | 5 ++-- .../helpers/get_nested_parent_path.test.ts | 8 ++++--- .../factory/helpers/get_nested_parent_path.ts | 8 ++----- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_ecs_objects.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_ecs_objects.ts index 3b9891e79d9ab..ce97ee6d113c2 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_ecs_objects.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_ecs_objects.ts @@ -15,15 +15,16 @@ import { getNestedParentPath } from './get_nested_parent_path'; export const buildEcsObjects = (hit: EventHit): Ecs => { const ecsFields = [...TIMELINE_EVENTS_FIELDS]; + const fieldsKeys = Object.keys(hit.fields ?? {}); return ecsFields.reduce( (acc, field) => { - const nestedParentPath = getNestedParentPath(field, hit.fields); + const nestedParentPath = getNestedParentPath(field, fieldsKeys); if ( nestedParentPath != null || has(field, hit.fields) || ECS_METADATA_FIELDS.includes(field) ) { - return merge(acc, buildObjectRecursive(field, hit.fields)); + return merge(acc, buildObjectRecursive(field, hit.fields, fieldsKeys)); } return acc; }, diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts index e6865780608a5..f16eca43c0d16 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts @@ -10,8 +10,9 @@ import { EventHit } from '../../../../../common/search_strategy'; import { buildObjectRecursive } from './build_object_recursive'; describe('buildObjectRecursive', () => { + let eventHitKeys = Object.keys(eventHit.fields ?? {}); it('builds an object from a single non-nested field', () => { - expect(buildObjectRecursive('@timestamp', eventHit.fields)).toEqual({ + expect(buildObjectRecursive('@timestamp', eventHit.fields, eventHitKeys)).toEqual({ '@timestamp': ['2020-11-17T14:48:08.922Z'], }); }); @@ -19,7 +20,7 @@ describe('buildObjectRecursive', () => { it('builds an object with no fields response', () => { const { fields, ...fieldLessHit } = eventHit; // @ts-expect-error fieldLessHit is intentionally missing fields - expect(buildObjectRecursive('@timestamp', fieldLessHit)).toEqual({ + expect(buildObjectRecursive('@timestamp', fieldLessHit, eventHitKeys)).toEqual({ '@timestamp': [], }); }); @@ -33,7 +34,7 @@ describe('buildObjectRecursive', () => { }, }; - expect(buildObjectRecursive('foo.barBaz', hit.fields)).toEqual({ + expect(buildObjectRecursive('foo.barBaz', hit.fields, eventHitKeys)).toEqual({ foo: { barBaz: ['foo'] }, }); }); @@ -45,7 +46,8 @@ describe('buildObjectRecursive', () => { foo: [{ bar: ['baz'] }], }, }; - expect(buildObjectRecursive('foo.bar', hit.fields)).toEqual({ + const hitKeys = Object.keys(hit.fields ?? {}); + expect(buildObjectRecursive('foo.bar', hit.fields, hitKeys)).toEqual({ foo: [{ bar: ['baz'] }], }); }); @@ -61,7 +63,8 @@ describe('buildObjectRecursive', () => { ], }, }; - expect(buildObjectRecursive('foo.bar.baz', nestedHit.fields)).toEqual({ + const nestedHitKeys = Object.keys(nestedHit.fields ?? {}); + expect(buildObjectRecursive('foo.bar.baz', nestedHit.fields, nestedHitKeys)).toEqual({ foo: { bar: [ { @@ -73,7 +76,7 @@ describe('buildObjectRecursive', () => { }); it('builds intermediate objects at multiple levels', () => { - expect(buildObjectRecursive('threat.enrichments.matched.atomic', eventHit.fields)).toEqual({ + expect(buildObjectRecursive('threat.enrichments.matched.atomic', eventHit.fields, eventHitKeys)).toEqual({ threat: { enrichments: [ { @@ -117,7 +120,7 @@ describe('buildObjectRecursive', () => { }); it('preserves multiple values for a single leaf', () => { - expect(buildObjectRecursive('threat.enrichments.matched.field', eventHit.fields)).toEqual({ + expect(buildObjectRecursive('threat.enrichments.matched.field', eventHit.fields, eventHitKeys)).toEqual({ threat: { enrichments: [ { @@ -162,6 +165,7 @@ describe('buildObjectRecursive', () => { describe('multiple levels of nested fields', () => { let nestedHit: EventHit; + let nestedHitKeys: string[]; beforeEach(() => { // @ts-expect-error nestedHit is minimal @@ -183,10 +187,11 @@ describe('buildObjectRecursive', () => { ], }, }; + nestedHitKeys = Object.keys(nestedHit.fields ?? {}); }); it('includes objects without the field', () => { - expect(buildObjectRecursive('nested_1.foo.nested_2.bar.leaf', nestedHit.fields)).toEqual({ + expect(buildObjectRecursive('nested_1.foo.nested_2.bar.leaf', nestedHit.fields, nestedHitKeys)).toEqual({ nested_1: { foo: [ { @@ -205,7 +210,7 @@ describe('buildObjectRecursive', () => { }); it('groups multiple leaf values', () => { - expect(buildObjectRecursive('nested_1.foo.nested_2.bar.leaf_2', nestedHit.fields)).toEqual({ + expect(buildObjectRecursive('nested_1.foo.nested_2.bar.leaf_2', nestedHit.fields, nestedHitKeys)).toEqual({ nested_1: { foo: [ { diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts index 55284812d6d5a..9683b95a12e19 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts @@ -12,8 +12,8 @@ import { Fields } from '../../../../../common/search_strategy'; import { toStringArray } from '../../../../../common/utils/to_array'; import { getNestedParentPath } from './get_nested_parent_path'; -export const buildObjectRecursive = (fieldPath: string, fields: Fields): Partial => { - const nestedParentPath = getNestedParentPath(fieldPath, fields); +export const buildObjectRecursive = (fieldPath: string, fields: Fields, fieldsKeys: string[]): Partial => { + const nestedParentPath = getNestedParentPath(fieldPath, fieldsKeys); if (!nestedParentPath) { return set({}, fieldPath, toStringArray(get(fieldPath, fields))); } @@ -23,6 +23,6 @@ export const buildObjectRecursive = (fieldPath: string, fields: Fields): Partial return set( {}, nestedParentPath, - subFields.map((subField) => buildObjectRecursive(subPath, subField)) + subFields.map((subField) => buildObjectRecursive(subPath, subField, Object.keys(subField))) ); }; diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts index 3b83bb0873981..711c7f9c8d874 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts @@ -109,9 +109,10 @@ export const formatTimelineData = async ( } result.node.data = []; + const hitFieldKeys = Object.keys(hit.fields || {}); for (const fieldName of uniqueFields) { - const nestedParentPath = getNestedParentPath(fieldName, hit.fields); + const nestedParentPath = getNestedParentPath(fieldName, hitFieldKeys); const isEcs = ECS_METADATA_FIELDS.includes(fieldName); if (!nestedParentPath && !has(fieldName, hit.fields) && !isEcs) { // eslint-disable-next-line no-continue @@ -127,7 +128,7 @@ export const formatTimelineData = async ( } if (ecsFieldSet.has(fieldName)) { - deepMerge(result.node.ecs, buildObjectRecursive(fieldName, hit.fields)); + deepMerge(result.node.ecs, buildObjectRecursive(fieldName, hit.fields, hitFieldKeys)); } } diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.test.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.test.ts index ad923ed8ce954..52667ec4e3628 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.test.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.test.ts @@ -9,6 +9,7 @@ import { getNestedParentPath } from './get_nested_parent_path'; describe('getNestedParentPath', () => { let testFields: Fields | undefined; + let testFieldsKeys: string[]; beforeAll(() => { testFields = { 'not.nested': ['I am not nested'], @@ -18,22 +19,23 @@ describe('getNestedParentPath', () => { }, ], }; + testFieldsKeys = Object.keys(testFields); }); it('should ignore fields that are not nested', () => { const notNestedPath = 'not.nested'; - const shouldBeUndefined = getNestedParentPath(notNestedPath, testFields); + const shouldBeUndefined = getNestedParentPath(notNestedPath, testFieldsKeys); expect(shouldBeUndefined).toBe(undefined); }); it('should capture fields that are nested', () => { const nestedPath = 'is.nested.field'; - const nestedParentPath = getNestedParentPath(nestedPath, testFields); + const nestedParentPath = getNestedParentPath(nestedPath, testFieldsKeys); expect(nestedParentPath).toEqual('is.nested'); }); it('should return undefined when the `fields` param is undefined', () => { const nestedPath = 'is.nested.field'; - expect(getNestedParentPath(nestedPath, undefined)).toBe(undefined); + expect(getNestedParentPath(nestedPath, [])).toBe(undefined); }); }); diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts index f2e769229fc1b..10653ffb7bca4 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts @@ -5,14 +5,10 @@ * 2.0. */ -import { Fields } from '../../../../../common/search_strategy'; - /** * If a prefix of our full field path is present as a field, we know that our field is nested */ export const getNestedParentPath = ( fieldPath: string, - fields: Fields | undefined -): string | undefined => - fields && - Object.keys(fields).find((field) => field !== fieldPath && fieldPath.startsWith(`${field}.`)); + fields: string[], +): string | undefined => fields.find((field) => field !== fieldPath && fieldPath.startsWith(`${field}.`)); From c13446f64543e2f4106bb5af43871e2f48e89dfd Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:27:53 +0000 Subject: [PATCH 2/2] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../helpers/build_object_recursive.test.ts | 18 +++++++++++++----- .../factory/helpers/build_object_recursive.ts | 6 +++++- .../factory/helpers/get_nested_parent_path.ts | 6 ++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts index f16eca43c0d16..f2084e2c48c07 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.test.ts @@ -10,7 +10,7 @@ import { EventHit } from '../../../../../common/search_strategy'; import { buildObjectRecursive } from './build_object_recursive'; describe('buildObjectRecursive', () => { - let eventHitKeys = Object.keys(eventHit.fields ?? {}); + const eventHitKeys = Object.keys(eventHit.fields ?? {}); it('builds an object from a single non-nested field', () => { expect(buildObjectRecursive('@timestamp', eventHit.fields, eventHitKeys)).toEqual({ '@timestamp': ['2020-11-17T14:48:08.922Z'], @@ -76,7 +76,9 @@ describe('buildObjectRecursive', () => { }); it('builds intermediate objects at multiple levels', () => { - expect(buildObjectRecursive('threat.enrichments.matched.atomic', eventHit.fields, eventHitKeys)).toEqual({ + expect( + buildObjectRecursive('threat.enrichments.matched.atomic', eventHit.fields, eventHitKeys) + ).toEqual({ threat: { enrichments: [ { @@ -120,7 +122,9 @@ describe('buildObjectRecursive', () => { }); it('preserves multiple values for a single leaf', () => { - expect(buildObjectRecursive('threat.enrichments.matched.field', eventHit.fields, eventHitKeys)).toEqual({ + expect( + buildObjectRecursive('threat.enrichments.matched.field', eventHit.fields, eventHitKeys) + ).toEqual({ threat: { enrichments: [ { @@ -191,7 +195,9 @@ describe('buildObjectRecursive', () => { }); it('includes objects without the field', () => { - expect(buildObjectRecursive('nested_1.foo.nested_2.bar.leaf', nestedHit.fields, nestedHitKeys)).toEqual({ + expect( + buildObjectRecursive('nested_1.foo.nested_2.bar.leaf', nestedHit.fields, nestedHitKeys) + ).toEqual({ nested_1: { foo: [ { @@ -210,7 +216,9 @@ describe('buildObjectRecursive', () => { }); it('groups multiple leaf values', () => { - expect(buildObjectRecursive('nested_1.foo.nested_2.bar.leaf_2', nestedHit.fields, nestedHitKeys)).toEqual({ + expect( + buildObjectRecursive('nested_1.foo.nested_2.bar.leaf_2', nestedHit.fields, nestedHitKeys) + ).toEqual({ nested_1: { foo: [ { diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts index 9683b95a12e19..96b9eef3a377d 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/build_object_recursive.ts @@ -12,7 +12,11 @@ import { Fields } from '../../../../../common/search_strategy'; import { toStringArray } from '../../../../../common/utils/to_array'; import { getNestedParentPath } from './get_nested_parent_path'; -export const buildObjectRecursive = (fieldPath: string, fields: Fields, fieldsKeys: string[]): Partial => { +export const buildObjectRecursive = ( + fieldPath: string, + fields: Fields, + fieldsKeys: string[] +): Partial => { const nestedParentPath = getNestedParentPath(fieldPath, fieldsKeys); if (!nestedParentPath) { return set({}, fieldPath, toStringArray(get(fieldPath, fields))); diff --git a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts index 10653ffb7bca4..6b865628821b2 100644 --- a/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts +++ b/x-pack/platform/plugins/shared/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts @@ -8,7 +8,5 @@ /** * If a prefix of our full field path is present as a field, we know that our field is nested */ -export const getNestedParentPath = ( - fieldPath: string, - fields: string[], -): string | undefined => fields.find((field) => field !== fieldPath && fieldPath.startsWith(`${field}.`)); +export const getNestedParentPath = (fieldPath: string, fields: string[]): string | undefined => + fields.find((field) => field !== fieldPath && fieldPath.startsWith(`${field}.`));