From 65feb917c18eff3150f5d28ec175ea0edd509418 Mon Sep 17 00:00:00 2001 From: Jethro Nederhof Date: Tue, 15 Oct 2024 10:56:17 +1100 Subject: [PATCH] Fix SelfDescribingJson type regression from #1330 (#1347) Include some Snowtype-generated code as tests. --- .../browser-tracker/browser-tracker.api.md | 16 ++-- .../docs/node-tracker/node-tracker.api.md | 12 ++- .../fix-sdj-regression_2024-10-04-03-07.json | 10 +++ .../fix-sdj-regression_2024-10-04-03-07.json | 10 +++ libraries/tracker-core/src/contexts.ts | 12 ++- libraries/tracker-core/src/core.ts | 18 ++-- .../src/types.ts | 7 +- trackers/browser-tracker/src/api.ts | 5 +- .../browser-tracker/test/snowtype.test.ts | 82 ++++++++++++++++++ trackers/node-tracker/test/snowtype.ts | 86 +++++++++++++++++++ 10 files changed, 223 insertions(+), 35 deletions(-) create mode 100644 common/changes/@snowplow/browser-plugin-snowplow-ecommerce/fix-sdj-regression_2024-10-04-03-07.json create mode 100644 common/changes/@snowplow/tracker-core/fix-sdj-regression_2024-10-04-03-07.json create mode 100644 trackers/browser-tracker/test/snowtype.test.ts create mode 100644 trackers/node-tracker/test/snowtype.ts diff --git a/api-docs/docs/browser-tracker/browser-tracker.api.md b/api-docs/docs/browser-tracker/browser-tracker.api.md index 2a40e49a3..4eedddea0 100644 --- a/api-docs/docs/browser-tracker/browser-tracker.api.md +++ b/api-docs/docs/browser-tracker/browser-tracker.api.md @@ -137,9 +137,7 @@ export interface ClientSession extends Record { } // @public -export interface CommonEventProperties> { +export interface CommonEventProperties> { context?: Array> | null; timestamp?: Timestamp | null; } @@ -433,16 +431,14 @@ Array | ContextPrimitive ]; // @public -export interface SelfDescribingEvent { - event: SelfDescribingJson; +export interface SelfDescribingEvent> { + event: SelfDescribingJson; } // @public -export type SelfDescribingJson> = { +export type SelfDescribingJson> = { schema: string; - data: T; + data: T extends any[] ? never : T extends {} ? T : never; }; // @public @@ -574,7 +570,7 @@ export interface TrackerCore { export function trackPageView(event?: PageViewEvent & CommonEventProperties, trackers?: Array): void; // @public -export function trackSelfDescribingEvent(event: SelfDescribingEvent & CommonEventProperties, trackers?: Array): void; +export function trackSelfDescribingEvent>(event: SelfDescribingEvent & CommonEventProperties, trackers?: Array): void; // @public export function trackStructEvent(event: StructuredEvent & CommonEventProperties, trackers?: Array): void; diff --git a/api-docs/docs/node-tracker/node-tracker.api.md b/api-docs/docs/node-tracker/node-tracker.api.md index 2f19d0301..d8a26a209 100644 --- a/api-docs/docs/node-tracker/node-tracker.api.md +++ b/api-docs/docs/node-tracker/node-tracker.api.md @@ -98,7 +98,7 @@ export function buildRemoveFromCart(event: RemoveFromCartEvent): PayloadBuilder; export function buildScreenView(event: ScreenViewEvent): PayloadBuilder; // @public -export function buildSelfDescribingEvent(event: SelfDescribingEvent): PayloadBuilder; +export function buildSelfDescribingEvent>(event: SelfDescribingEvent): PayloadBuilder; // @public export function buildSiteSearch(event: SiteSearchEvent): PayloadBuilder; @@ -459,16 +459,14 @@ export interface ScreenViewEvent { } // @public -export interface SelfDescribingEvent { - event: SelfDescribingJson; +export interface SelfDescribingEvent> { + event: SelfDescribingJson; } // @public -export type SelfDescribingJson> = { +export type SelfDescribingJson> = { schema: string; - data: T; + data: T extends any[] ? never : T extends {} ? T : never; }; // @public diff --git a/common/changes/@snowplow/browser-plugin-snowplow-ecommerce/fix-sdj-regression_2024-10-04-03-07.json b/common/changes/@snowplow/browser-plugin-snowplow-ecommerce/fix-sdj-regression_2024-10-04-03-07.json new file mode 100644 index 000000000..cb4138a3b --- /dev/null +++ b/common/changes/@snowplow/browser-plugin-snowplow-ecommerce/fix-sdj-regression_2024-10-04-03-07.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-plugin-snowplow-ecommerce", + "comment": "", + "type": "none" + } + ], + "packageName": "@snowplow/browser-plugin-snowplow-ecommerce" +} \ No newline at end of file diff --git a/common/changes/@snowplow/tracker-core/fix-sdj-regression_2024-10-04-03-07.json b/common/changes/@snowplow/tracker-core/fix-sdj-regression_2024-10-04-03-07.json new file mode 100644 index 000000000..6d6fd6aef --- /dev/null +++ b/common/changes/@snowplow/tracker-core/fix-sdj-regression_2024-10-04-03-07.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/tracker-core", + "comment": "Fix regression in SelfDescribingJson type from #1330", + "type": "none" + } + ], + "packageName": "@snowplow/tracker-core" +} \ No newline at end of file diff --git a/libraries/tracker-core/src/contexts.ts b/libraries/tracker-core/src/contexts.ts index 9379fa9d7..b304e5cc6 100644 --- a/libraries/tracker-core/src/contexts.ts +++ b/libraries/tracker-core/src/contexts.ts @@ -246,7 +246,9 @@ export interface PluginContexts { /** * Returns list of contexts from all active plugins */ - addPluginContexts: (additionalContexts?: SelfDescribingJson[] | null) => SelfDescribingJson[]; + addPluginContexts: >( + additionalContexts?: SelfDescribingJson[] | null + ) => SelfDescribingJson[]; } export function pluginContexts(plugins: Array): PluginContexts { @@ -257,8 +259,10 @@ export function pluginContexts(plugins: Array): PluginContexts { * @returns userContexts combined with commonContexts */ return { - addPluginContexts: (additionalContexts?: SelfDescribingJson[] | null): SelfDescribingJson[] => { - const combinedContexts: SelfDescribingJson[] = additionalContexts ? [...additionalContexts] : []; + addPluginContexts: >(additionalContexts?: SelfDescribingJson[] | null) => { + const combinedContexts: SelfDescribingJson>[] = additionalContexts + ? [...additionalContexts] + : []; plugins.forEach((plugin) => { try { @@ -270,7 +274,7 @@ export function pluginContexts(plugins: Array): PluginContexts { } }); - return combinedContexts; + return combinedContexts as SelfDescribingJson[]; }, }; } diff --git a/libraries/tracker-core/src/core.ts b/libraries/tracker-core/src/core.ts index f9000fef7..0dfff0d59 100644 --- a/libraries/tracker-core/src/core.ts +++ b/libraries/tracker-core/src/core.ts @@ -45,7 +45,7 @@ import { LOG } from './logger'; * Export interface for any Self-Describing JSON such as context or Self Describing events * @typeParam T - The type of the data object within a SelfDescribingJson */ -export type SelfDescribingJson> = { +export type SelfDescribingJson> = { /** * The schema string * @example 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0' @@ -54,14 +54,14 @@ export type SelfDescribingJson> = { +export type SelfDescribingJsonArray> = { /** * The schema string * @example 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-1' @@ -119,7 +119,7 @@ function getTimestamp(timestamp?: Timestamp | null): TimestampPayload { } /** Additional data points to set when tracking an event */ -export interface CommonEventProperties> { +export interface CommonEventProperties> { /** Add context to an event by setting an Array of Self Describing JSON */ context?: Array> | null; /** Set the true timestamp or overwrite the device sent timestamp on an event */ @@ -382,9 +382,9 @@ export function trackerCore(configuration: CoreConfiguration = {}): TrackerCore * @param timestamp - Timestamp of the event * @returns Payload after the callback is applied or undefined if the event is skipped */ - function track( + function track>( pb: PayloadBuilder, - context?: Array | null, + context?: Array> | null, timestamp?: Timestamp | null ): Payload | undefined { pb.withJsonProcessor(payloadJsonProcessor(encodeBase64)); @@ -565,9 +565,9 @@ export function trackerCore(configuration: CoreConfiguration = {}): TrackerCore * A custom event type, allowing for an event to be tracked using your own custom schema * and a data object which conforms to the supplied schema */ -export interface SelfDescribingEvent { +export interface SelfDescribingEvent> { /** The Self Describing JSON which describes the event */ - event: SelfDescribingJson; + event: SelfDescribingJson; } /** @@ -578,7 +578,7 @@ export interface SelfDescribingEvent { * @param event - Contains the properties and schema location for the event * @returns PayloadBuilder to be sent to {@link @snowplow/tracker-core#TrackerCore.track} */ -export function buildSelfDescribingEvent(event: SelfDescribingEvent): PayloadBuilder { +export function buildSelfDescribingEvent>(event: SelfDescribingEvent): PayloadBuilder { const { event: { schema, data }, } = event, diff --git a/plugins/browser-plugin-snowplow-ecommerce/src/types.ts b/plugins/browser-plugin-snowplow-ecommerce/src/types.ts index f46908fac..79f69a13b 100644 --- a/plugins/browser-plugin-snowplow-ecommerce/src/types.ts +++ b/plugins/browser-plugin-snowplow-ecommerce/src/types.ts @@ -1,4 +1,4 @@ -import { CommonEventProperties, SelfDescribingJson } from '@snowplow/tracker-core'; +import { CommonEventProperties } from '@snowplow/tracker-core'; /** * Type/Schema for an ecommerce Action @@ -317,10 +317,9 @@ export interface User { email?: string; } -export interface CommonEcommerceEventProperties> - extends CommonEventProperties { +export interface CommonEcommerceEventProperties> extends CommonEventProperties { /** Add context to an event by setting an Array of Self Describing JSON */ - context?: Array>; + context?: Exclude['context'], null>; } export type ListViewEvent = { name: string; products: Product[] }; diff --git a/trackers/browser-tracker/src/api.ts b/trackers/browser-tracker/src/api.ts index a605599e7..4a69c3687 100644 --- a/trackers/browser-tracker/src/api.ts +++ b/trackers/browser-tracker/src/api.ts @@ -400,7 +400,10 @@ export function trackStructEvent(event: StructuredEvent & CommonEventProperties, * @param event - The event information * @param trackers - The tracker identifiers which the event will be sent to */ -export function trackSelfDescribingEvent(event: SelfDescribingEvent & CommonEventProperties, trackers?: Array) { +export function trackSelfDescribingEvent>( + event: SelfDescribingEvent & CommonEventProperties, + trackers?: Array +) { dispatchToTrackers(trackers, (t) => { t.core.track(buildSelfDescribingEvent({ event: event.event }), event.context, event.timestamp); }); diff --git a/trackers/browser-tracker/test/snowtype.test.ts b/trackers/browser-tracker/test/snowtype.test.ts new file mode 100644 index 000000000..50ab39a7a --- /dev/null +++ b/trackers/browser-tracker/test/snowtype.test.ts @@ -0,0 +1,82 @@ +describe('Snowtype compat', () => { + it('pass if this snowtype-generated file passes typechecks', () => {}); +}); +// generated with Snowtype v0.8.4, package import on below line needs to be updated to '../src' + +import { trackSelfDescribingEvent, CommonEventProperties, SelfDescribingJson } from '../src'; +// Automatically generated by Snowtype + +/** + * Schema for an example event + */ +export type SubscriptionFunnel = { + /** + * the action of the funnel step + */ + action?: null | string; + /** + * the number of the funnel step + */ + step: number; + /** + * the type of subscription the user is signing up to + */ + subscription_type?: null | string; +}; + +/** + * Creates a Snowplow Event Specification entity. + */ +export function createEventSpecification(eventSpecification: EventSpecification) { + return { + schema: 'iglu:com.snowplowanalytics.snowplow/event_specification/jsonschema/1-0-2', + data: eventSpecification, + }; +} + +/** + * Automatically attached context for event specifications + */ +interface EventSpecification { + id: string; + name: string; + data_product_id: string; + data_product_name: string; +} + +type ContextsOrTimestamp = Omit, 'context'> & { + context?: SelfDescribingJson[] | null | undefined; +}; +/** + * Track a Snowplow event for SubscriptionFunnel. + * Schema for an example event + */ +export function trackSubscriptionFunnel( + subscriptionFunnel: SubscriptionFunnel & ContextsOrTimestamp, + trackers?: string[] +) { + const { context, timestamp, ...data } = subscriptionFunnel; + const event: SelfDescribingJson = { + schema: 'iglu:com.example/subscription_funnel/jsonschema/1-0-0', + data, + }; + + trackSelfDescribingEvent( + { + event, + context, + timestamp, + }, + trackers + ); +} + +/** + * Creates a Snowplow SubscriptionFunnel entity. + */ +export function createSubscriptionFunnel(subscriptionFunnel: SubscriptionFunnel) { + return { + schema: 'iglu:com.example/subscription_funnel/jsonschema/1-0-0', + data: subscriptionFunnel, + }; +} diff --git a/trackers/node-tracker/test/snowtype.ts b/trackers/node-tracker/test/snowtype.ts new file mode 100644 index 000000000..df4965f46 --- /dev/null +++ b/trackers/node-tracker/test/snowtype.ts @@ -0,0 +1,86 @@ +import test from 'ava'; +test('pass if this snowtype-generated file passes typechecks', () => {}); +// generated with Snowtype v0.8.4, package import on below line needs to be updated to '../src' + +import { buildSelfDescribingEvent, SelfDescribingJson, Timestamp, Tracker } from '../src'; +// Automatically generated by Snowtype + +/** + * Schema for an example event + */ +export type SubscriptionFunnel = { + /** + * the action of the funnel step + */ + action?: null | string; + /** + * the number of the funnel step + */ + step: number; + /** + * the type of subscription the user is signing up to + */ + subscription_type?: null | string; +}; + +interface CommonEventProperties> { + /** Add context to an event by setting an Array of Self Describing JSON */ + context?: Array> | null; + /** Set the true timestamp or overwrite the device sent timestamp on an event */ + timestamp?: Timestamp | null; +} + +/** + * Creates a Snowplow Event Specification entity. + */ +export function createEventSpecification(eventSpecification: EventSpecification) { + return { + schema: 'iglu:com.snowplowanalytics.snowplow/event_specification/jsonschema/1-0-2', + data: eventSpecification, + }; +} + +/** + * Automatically attached context for event specifications + */ +interface EventSpecification { + id: string; + name: string; + data_product_id: string; + data_product_name: string; +} + +type ContextsOrTimestamp = Omit, 'context'> & { + context?: SelfDescribingJson[] | null | undefined; +}; + +/** + * Track a Snowplow event for SubscriptionFunnel. + * Schema for an example event + */ +export function trackSubscriptionFunnel( + tracker: Tracker, + subscriptionFunnel: SubscriptionFunnel & ContextsOrTimestamp +) { + const { context, timestamp, ...data } = subscriptionFunnel; + tracker.track( + buildSelfDescribingEvent({ + event: { + schema: 'iglu:com.example/subscription_funnel/jsonschema/1-0-0', + data, + }, + }), + context, + timestamp + ); +} + +/** + * Creates a Snowplow SubscriptionFunnel entity. + */ +export function createSubscriptionFunnel(subscriptionFunnel: SubscriptionFunnel) { + return { + schema: 'iglu:com.example/subscription_funnel/jsonschema/1-0-0', + data: subscriptionFunnel, + }; +}