From f17b0c5aa05ad6246bc1b6f2834b4018243767a6 Mon Sep 17 00:00:00 2001 From: Max Toball Date: Wed, 23 Sep 2026 15:01:21 +0200 Subject: [PATCH] fix(api-client): allow unattributed ExO node events --- .../experience/event/ExoEventProperties.ts | 8 ++--- .../schemas/insights/event/ExoEvent.test.ts | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/universal/api-client/src/schemas/experience/event/ExoEventProperties.ts b/packages/universal/api-client/src/schemas/experience/event/ExoEventProperties.ts index 57e5254eb..487f36827 100644 --- a/packages/universal/api-client/src/schemas/experience/event/ExoEventProperties.ts +++ b/packages/universal/api-client/src/schemas/experience/event/ExoEventProperties.ts @@ -33,8 +33,8 @@ export const ExoEventProperties = z.extend(UniversalEventProperties, { /** Contentful entry identifiers rendered by the entity. */ entryIds: z.optional(z.array(z.string())), - /** Identifier of the optimization that selected the entity variant. */ - optimizationId: z.string(), + /** Identifier of the optimization that selected the entity variant, when available. */ + optimizationId: z.optional(z.string()), /** Parameter values used to render the entity. */ parameters: z.optional(z.record(z.string(), z.json())), @@ -42,8 +42,8 @@ export const ExoEventProperties = z.extend(UniversalEventProperties, { /** Identifier of the containing experience, when the entity is nested. */ parentExperienceId: z.optional(z.string()), - /** Identifier of the selected entity variant. */ - variantId: z.string(), + /** Identifier of the selected entity variant, when available. */ + variantId: z.optional(z.string()), /** Index of the selected variant when available. */ variantIndex: z.optional(z.number()), diff --git a/packages/universal/api-client/src/schemas/insights/event/ExoEvent.test.ts b/packages/universal/api-client/src/schemas/insights/event/ExoEvent.test.ts index 051222ad9..e7fafc091 100644 --- a/packages/universal/api-client/src/schemas/insights/event/ExoEvent.test.ts +++ b/packages/universal/api-client/src/schemas/insights/event/ExoEvent.test.ts @@ -40,6 +40,8 @@ const exoProperties = { variantIndex: 1, } as const +const { optimizationId, variantId, ...unattributedExoProperties } = exoProperties + describe('ExO events', () => { it.each(['Experience', 'Fragment', 'InlineFragment', 'InlineComponent'])( 'accepts the %s entity kind', @@ -87,6 +89,40 @@ describe('ExO events', () => { expect(InsightsEvent.safeParse(event).success).toBe(true) }) + it.each([ + { + type: 'exo_node_view', + viewDurationMs: 3000, + viewId: 'view-123', + }, + { type: 'exo_node_click' }, + { + type: 'exo_node_hover', + hoverDurationMs: 1500, + hoverId: 'hover-123', + }, + ])('parses an unattributed $type event through the Insights event union', (event) => { + expect( + InsightsEvent.safeParse({ + ...unattributedExoProperties, + ...event, + }).success, + ).toBe(true) + }) + + it('preserves attribution for a baseline selection', () => { + const event = { + ...exoProperties, + type: 'exo_node_view', + variantIndex: 0, + viewDurationMs: 3000, + viewId: 'view-123', + } + + expect(ExoViewEvent.safeParse(event).success).toBe(true) + expect(InsightsEvent.safeParse(event).success).toBe(true) + }) + it.each([ { viewDurationMs: -1, viewId: 'view-123' }, { viewDurationMs: 1.5, viewId: 'view-123' },