Skip to content

Commit

Permalink
Fix SelfDescribingJson type regression from #1330 (#1347)
Browse files Browse the repository at this point in the history
Include some Snowtype-generated code as tests.
  • Loading branch information
jethron authored and matus-tomlein committed Oct 28, 2024
1 parent 03664aa commit 7e8cf49
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 35 deletions.
16 changes: 6 additions & 10 deletions api-docs/docs/browser-tracker/browser-tracker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,7 @@ export interface ClientSession extends Record<string, unknown> {
}

// @public
export interface CommonEventProperties<T extends {
[_: string]: unknown;
} = Record<string, unknown>> {
export interface CommonEventProperties<T = Record<string, unknown>> {
context?: Array<SelfDescribingJson<T>> | null;
timestamp?: Timestamp | null;
}
Expand Down Expand Up @@ -433,16 +431,14 @@ Array<ContextPrimitive> | ContextPrimitive
];

// @public
export interface SelfDescribingEvent {
event: SelfDescribingJson;
export interface SelfDescribingEvent<T = Record<string, unknown>> {
event: SelfDescribingJson<T>;
}

// @public
export type SelfDescribingJson<T extends {
[_: string]: unknown;
} = Record<string, unknown>> = {
export type SelfDescribingJson<T = Record<string, unknown>> = {
schema: string;
data: T;
data: T extends any[] ? never : T extends {} ? T : never;
};

// @public
Expand Down Expand Up @@ -574,7 +570,7 @@ export interface TrackerCore {
export function trackPageView(event?: PageViewEvent & CommonEventProperties, trackers?: Array<string>): void;

// @public
export function trackSelfDescribingEvent(event: SelfDescribingEvent & CommonEventProperties, trackers?: Array<string>): void;
export function trackSelfDescribingEvent<T = Record<string, unknown>>(event: SelfDescribingEvent<T> & CommonEventProperties, trackers?: Array<string>): void;

// @public
export function trackStructEvent(event: StructuredEvent & CommonEventProperties, trackers?: Array<string>): void;
Expand Down
12 changes: 5 additions & 7 deletions api-docs/docs/node-tracker/node-tracker.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = Record<string, unknown>>(event: SelfDescribingEvent<T>): PayloadBuilder;

// @public
export function buildSiteSearch(event: SiteSearchEvent): PayloadBuilder;
Expand Down Expand Up @@ -459,16 +459,14 @@ export interface ScreenViewEvent {
}

// @public
export interface SelfDescribingEvent {
event: SelfDescribingJson;
export interface SelfDescribingEvent<T = Record<string, unknown>> {
event: SelfDescribingJson<T>;
}

// @public
export type SelfDescribingJson<T extends {
[_: string]: unknown;
} = Record<string, unknown>> = {
export type SelfDescribingJson<T = Record<string, unknown>> = {
schema: string;
data: T;
data: T extends any[] ? never : T extends {} ? T : never;
};

// @public
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/browser-plugin-snowplow-ecommerce",
"comment": "",
"type": "none"
}
],
"packageName": "@snowplow/browser-plugin-snowplow-ecommerce"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/tracker-core",
"comment": "Fix regression in SelfDescribingJson type from #1330",
"type": "none"
}
],
"packageName": "@snowplow/tracker-core"
}
12 changes: 8 additions & 4 deletions libraries/tracker-core/src/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,9 @@ export interface PluginContexts {
/**
* Returns list of contexts from all active plugins
*/
addPluginContexts: (additionalContexts?: SelfDescribingJson[] | null) => SelfDescribingJson[];
addPluginContexts: <T = Record<string, unknown>>(
additionalContexts?: SelfDescribingJson<T>[] | null
) => SelfDescribingJson[];
}

export function pluginContexts(plugins: Array<CorePlugin>): PluginContexts {
Expand All @@ -255,8 +257,10 @@ export function pluginContexts(plugins: Array<CorePlugin>): PluginContexts {
* @returns userContexts combined with commonContexts
*/
return {
addPluginContexts: (additionalContexts?: SelfDescribingJson[] | null): SelfDescribingJson[] => {
const combinedContexts: SelfDescribingJson[] = additionalContexts ? [...additionalContexts] : [];
addPluginContexts: <T = Record<string, unknown>>(additionalContexts?: SelfDescribingJson<T>[] | null) => {
const combinedContexts: SelfDescribingJson<T | Record<string, unknown>>[] = additionalContexts
? [...additionalContexts]
: [];

plugins.forEach((plugin) => {
try {
Expand All @@ -268,7 +272,7 @@ export function pluginContexts(plugins: Array<CorePlugin>): PluginContexts {
}
});

return combinedContexts;
return combinedContexts as SelfDescribingJson[];
},
};
}
Expand Down
18 changes: 9 additions & 9 deletions libraries/tracker-core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends { [_: string]: unknown } = Record<string, unknown>> = {
export type SelfDescribingJson<T = Record<string, unknown>> = {
/**
* The schema string
* @example 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0'
Expand All @@ -54,14 +54,14 @@ export type SelfDescribingJson<T extends { [_: string]: unknown } = Record<strin
/**
* The data object which should conform to the supplied schema
*/
data: T;
data: T extends any[] ? never : T extends {} ? T : never;
};

/**
* Export interface for any Self-Describing JSON which has the data attribute as an array
* @typeParam T - The type of the data object within the SelfDescribingJson data array
*/
export type SelfDescribingJsonArray<T extends { [_: string]: unknown } = Record<string, unknown>> = {
export type SelfDescribingJsonArray<T = Record<string, unknown>> = {
/**
* The schema string
* @example 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-1'
Expand Down Expand Up @@ -119,7 +119,7 @@ function getTimestamp(timestamp?: Timestamp | null): TimestampPayload {
}

/** Additional data points to set when tracking an event */
export interface CommonEventProperties<T extends { [_: string]: unknown } = Record<string, unknown>> {
export interface CommonEventProperties<T = Record<string, unknown>> {
/** Add context to an event by setting an Array of Self Describing JSON */
context?: Array<SelfDescribingJson<T>> | null;
/** Set the true timestamp or overwrite the device sent timestamp on an event */
Expand Down Expand Up @@ -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<C = Record<string, unknown>>(
pb: PayloadBuilder,
context?: Array<SelfDescribingJson> | null,
context?: Array<SelfDescribingJson<C>> | null,
timestamp?: Timestamp | null
): Payload | undefined {
pb.withJsonProcessor(payloadJsonProcessor(encodeBase64));
Expand Down Expand Up @@ -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<T = Record<string, unknown>> {
/** The Self Describing JSON which describes the event */
event: SelfDescribingJson;
event: SelfDescribingJson<T>;
}

/**
Expand All @@ -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<T = Record<string, unknown>>(event: SelfDescribingEvent<T>): PayloadBuilder {
const {
event: { schema, data },
} = event,
Expand Down
7 changes: 3 additions & 4 deletions plugins/browser-plugin-snowplow-ecommerce/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CommonEventProperties, SelfDescribingJson } from '@snowplow/tracker-core';
import { CommonEventProperties } from '@snowplow/tracker-core';

/**
* Type/Schema for an ecommerce Action
Expand Down Expand Up @@ -317,10 +317,9 @@ export interface User {
email?: string;
}

export interface CommonEcommerceEventProperties<T extends { [_: string]: unknown } = Record<string, unknown>>
extends CommonEventProperties<T> {
export interface CommonEcommerceEventProperties<T = Record<string, unknown>> extends CommonEventProperties<T> {
/** Add context to an event by setting an Array of Self Describing JSON */
context?: Array<SelfDescribingJson<T>>;
context?: Exclude<CommonEventProperties<T>['context'], null>;
}

export type ListViewEvent = { name: string; products: Product[] };
Expand Down
5 changes: 4 additions & 1 deletion trackers/browser-tracker/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>) {
export function trackSelfDescribingEvent<T = Record<string, unknown>>(
event: SelfDescribingEvent<T> & CommonEventProperties,
trackers?: Array<string>
) {
dispatchToTrackers(trackers, (t) => {
t.core.track(buildSelfDescribingEvent({ event: event.event }), event.context, event.timestamp);
});
Expand Down
82 changes: 82 additions & 0 deletions trackers/browser-tracker/test/snowtype.test.ts
Original file line number Diff line number Diff line change
@@ -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<T = any> = Omit<CommonEventProperties<T>, 'context'> & {
context?: SelfDescribingJson<T>[] | null | undefined;
};
/**
* Track a Snowplow event for SubscriptionFunnel.
* Schema for an example event
*/
export function trackSubscriptionFunnel<T extends {} = any>(
subscriptionFunnel: SubscriptionFunnel & ContextsOrTimestamp<T>,
trackers?: string[]
) {
const { context, timestamp, ...data } = subscriptionFunnel;
const event: SelfDescribingJson<typeof data> = {
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,
};
}
86 changes: 86 additions & 0 deletions trackers/node-tracker/test/snowtype.ts
Original file line number Diff line number Diff line change
@@ -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<T = Record<string, unknown>> {
/** Add context to an event by setting an Array of Self Describing JSON */
context?: Array<SelfDescribingJson<T>> | 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<T = any> = Omit<CommonEventProperties<T>, 'context'> & {
context?: SelfDescribingJson<T>[] | null | undefined;
};

/**
* Track a Snowplow event for SubscriptionFunnel.
* Schema for an example event
*/
export function trackSubscriptionFunnel<T extends {} = any>(
tracker: Tracker,
subscriptionFunnel: SubscriptionFunnel & ContextsOrTimestamp<T>
) {
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,
};
}

0 comments on commit 7e8cf49

Please sign in to comment.