From 412a683e689362d5745b99b15f6d395a93af8e64 Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Thu, 22 Aug 2024 11:43:02 +0800 Subject: [PATCH 1/9] fix: fix Docusaurus Starter link --- website/sidebars.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/sidebars.ts b/website/sidebars.ts index 90fa1d0..d0aeb79 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -103,8 +103,8 @@ const sidebars: SidebarsConfig = { }, { type: 'link', - label: 'Hyperse gh-pages starter', - href: 'https://hyperse-io.github.io/gh-pages-starter/', + label: 'Hyperse Docusaurus Starter', + href: 'https://hyperse-io.github.io/docusaurus-mono-starter/', }, ], }, From ae77ae1dc2cce5082e8589d153e545402ec461d1 Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Fri, 23 Aug 2024 20:53:06 +0800 Subject: [PATCH 2/9] feat: supports mapping evenType based on RealEventData --- src/adapter/adapter-base.ts | 137 +++++-- src/adapter/adapter-builder.ts | 65 +++- src/adapter/create-adapter-builder.ts | 14 +- src/helpers/helper-adapter-track.ts | 14 +- src/types/types-adapter.ts | 86 ++++- src/types/types-create.ts | 5 +- tests/test-adapter.spec.ts | 36 +- tests/test-real-transform.spec.ts | 345 ++++++++++++++++++ tests/test-track-error.spec.ts | 4 +- tests/test-track-pipeline.spec.ts | 21 +- tests/test-utils/adapter/analyzer-adapter.ts | 47 +++ tests/test-utils/adapter/report-adapter.ts | 19 +- .../test-utils/types/type-adapter-options.ts | 18 +- tests/test-utils/types/type-event.ts | 28 ++ website/docs/api/adapter-builder.md | 65 +++- website/docs/api/base-adapter.md | 84 +++-- website/docs/intro/sample-example.md | 25 +- 17 files changed, 844 insertions(+), 169 deletions(-) create mode 100644 tests/test-real-transform.spec.ts create mode 100644 tests/test-utils/adapter/analyzer-adapter.ts diff --git a/src/adapter/adapter-base.ts b/src/adapter/adapter-base.ts index 9d28dd1..37f87f3 100644 --- a/src/adapter/adapter-base.ts +++ b/src/adapter/adapter-base.ts @@ -5,6 +5,7 @@ import { AdapterBeforeFunction, AdapterReportData, AdapterTransformFunction, + CheckUndefined, TrackAdapter, } from '../types/types-adapter.js'; import { TrackAdapterOptions, TrackContext } from '../types/types-create.js'; @@ -13,26 +14,37 @@ import { TrackEventDataBase } from '../types/types-track.js'; export abstract class BaseAdapter< Context extends TrackContext, EventData extends TrackEventDataBase, - AdapterOptions extends TrackAdapterOptions, -> implements TrackAdapter + AdapterOptions extends TrackAdapterOptions, + RealEventData extends TrackEventDataBase = EventData, +> implements TrackAdapter { private setupHook?: AdapterOptions['setup']; private beforeHook?: AdapterBeforeFunction; transformHookMap: { - [K in keyof EventData]?: AdapterTransformFunction; + [K in keyof EventData]?: { + realEventType: keyof RealEventData | keyof EventData; + execute: AdapterTransformFunction; + }; } = {}; - private afterHook?: AdapterAfterFunction; + private afterHook?: AdapterAfterFunction; - abstract isTrackable( + abstract isTrackable< + EventType extends CheckUndefined, + >( ctx: Context, - eventType: EventType, - eventData: EventData[EventType] + eventType: CheckUndefined, + reportData?: + | AdapterReportData + | Awaited> ): boolean | Promise; - protected report( + protected report>( ctx: Context, - reportData: AdapterReportData, + eventType: CheckUndefined, + reportData?: + | AdapterReportData + | Awaited>, setupData?: Required['setup'] extends (...args: any) => any ? Awaited['setup']>> : undefined @@ -48,15 +60,34 @@ export abstract class BaseAdapter< this.beforeHook = fun; } - public _mountAfterHook(fun: AdapterAfterFunction): void { + public _mountAfterHook( + fun: AdapterAfterFunction + ): void { this.afterHook = fun; } - public _mountTransformHook( + public _mountTransformHook< + EventType extends keyof EventData | [keyof EventData, keyof RealEventData], + >( eventType: EventType, - fun: AdapterTransformFunction + fun: AdapterTransformFunction< + Context, + keyof EventData, + EventData, + RealEventData + > ) { - this.transformHookMap[eventType] = fun; + if (typeof eventType === 'string') { + this.transformHookMap[eventType] = { + realEventType: eventType, + execute: fun, + }; + } else if (Array.isArray(eventType)) { + this.transformHookMap[eventType[0]] = { + realEventType: eventType[1], + execute: fun, + }; + } } private executeTransform = async ( @@ -66,45 +97,83 @@ export abstract class BaseAdapter< ) => { if (Object.keys(this.transformHookMap).length < 1) { ctx.logger?.warn('Adapter transform hook is not defined'); - return eventData; + return { + reportData: eventData, + realEventType: eventType as unknown as CheckUndefined< + RealEventData, + EventData + >, + }; } const transformHook = this.transformHookMap[eventType]; if (!transformHook) { - return eventData; + return { + reportData: eventData, + realEventType: eventType as unknown as CheckUndefined< + RealEventData, + EventData + >, + }; } - const result = await executeFunction( - transformHook as AdapterTransformFunction, + + const reportData = await executeFunction( + transformHook.execute, ctx, eventType, eventData ); - return result; + + return { + reportData, + realEventType: transformHook.realEventType as unknown as CheckUndefined< + RealEventData, + EventData + >, + }; }; - private executeReport = async ( + private executeReport = async < + EventType extends CheckUndefined, + >( + adapterName: string, ctx: Context, - eventType: EventType, - eventData: EventData[EventType], - reportData: ReportData - ): Promise => { + realEventType: CheckUndefined, + eventData: EventData[keyof EventData], + reportData?: + | AdapterReportData + | Awaited> + ) => { + const isTrackable = await executeFunction( + this.isTrackable, + ctx, + realEventType, + reportData + ); + + if (!isTrackable) { + ctx.logger?.warn(`Adapter is not trackable: ${adapterName}`); + return; + } + let setupResult; if (this.setupHook) { - setupResult = await this.setupHook(ctx, eventType, eventData); + setupResult = await this.setupHook(ctx, realEventType, eventData); } - await this.report(ctx, reportData, setupResult); - return reportData; + await this.report(ctx, realEventType, reportData, setupResult); }; /** * Tracks an event. * + * @param adapterName - The adapter name. * @param ctx - The context object. * @param eventType - The type of the event. * @param eventData - The data associated with the event. * @returns A promise that resolves when the tracking is complete. */ public async track( + adapterName: string, ctx: Context, eventType: EventType, eventData: EventData[EventType] @@ -113,10 +182,18 @@ export abstract class BaseAdapter< async () => await executeFunction(this.beforeHook, ctx, eventType, eventData), async () => await this.executeTransform(ctx, eventType, eventData), - async (reportData) => - await this.executeReport(ctx, eventType, eventData, reportData), - async (reportData) => - await executeFunction(this.afterHook, ctx, eventType, reportData) + async ({ reportData, realEventType }) => { + await this.executeReport( + adapterName, + ctx, + realEventType, + eventData, + reportData + ); + return { reportData, realEventType }; + }, + async ({ reportData, realEventType }) => + await executeFunction(this.afterHook, ctx, realEventType, reportData) )(); } } diff --git a/src/adapter/adapter-builder.ts b/src/adapter/adapter-builder.ts index 7c239f1..f8b983c 100644 --- a/src/adapter/adapter-builder.ts +++ b/src/adapter/adapter-builder.ts @@ -2,8 +2,10 @@ import { UnionToTuple } from '../types/type-union-tuple.js'; import { AdapterAfterFunction, AdapterBeforeFunction, - AdapterReportData, TrackAdapter, + TransformEventData, + TransformEventType, + TransformReturns, } from '../types/types-adapter.js'; import { TrackAdapterOptions, TrackContext } from '../types/types-create.js'; import { TrackEventDataBase } from '../types/types-track.js'; @@ -18,11 +20,19 @@ import { TrackEventDataBase } from '../types/types-track.js'; export class AdapterBuilder< Context extends TrackContext, EventData extends TrackEventDataBase, - AdapterOptions extends TrackAdapterOptions, + AdapterOptions extends TrackAdapterOptions, + RealEventData extends TrackEventDataBase = EventData, > { - private adapter: TrackAdapter; + private adapter: TrackAdapter< + Context, + EventData, + AdapterOptions, + RealEventData + >; - constructor(_adapter: TrackAdapter) { + constructor( + _adapter: TrackAdapter + ) { this.adapter = _adapter; } @@ -84,52 +94,69 @@ export class AdapterBuilder< } private mountTransformHook = < - Key extends keyof LeftEventData, + Key extends + | keyof LeftEventData + | [keyof LeftEventData, keyof RealEventData], LeftEventData = EventData, >( eventType: Key, fun: ( ctx: Context, - eventType: Key, - eventData: LeftEventData[Key] - ) => AdapterReportData | Promise + eventType: TransformEventType, + eventData: TransformEventData + ) => TransformReturns ) => { this.adapter._mountTransformHook( - eventType as keyof EventData, - fun as (...args: any[]) => AdapterReportData | Promise + eventType as keyof EventData | [keyof EventData, keyof RealEventData], + fun as ( + ...args: any[] + ) => TransformReturns ); + const transform = < - RightKey extends keyof RightEventData, - RightEventData = Omit, + RightKey extends + | keyof RightEventData + | [keyof RightEventData, keyof RealEventData], + RightEventData = Key extends keyof LeftEventData + ? Omit + : Key extends [keyof LeftEventData, keyof RealEventData] + ? Omit + : never, >( eventType: RightKey, fun: ( ctx: Context, - eventType: RightKey, - eventData: RightEventData[RightKey] - ) => AdapterReportData | Promise + eventType: TransformEventType, + eventData: TransformEventData + ) => TransformReturns ) => { this.adapter._mountTransformHook( - eventType as keyof EventData, + eventType as keyof EventData | [keyof EventData, keyof RealEventData], fun as ( ...args: any[] - ) => AdapterReportData | Promise + ) => TransformReturns ); return this.mountTransformHook(eventType, fun); }; + const result = { transform: transform, ...this.buildTransformChainer(), }; return result as UnionToTuple< - Exclude + Exclude< + keyof LeftEventData, + TransformEventType + > >['length'] extends 0 ? ReturnType : typeof result; }; - private mountAfterHook = (fun: AdapterAfterFunction) => { + private mountAfterHook = ( + fun: AdapterAfterFunction + ) => { this.adapter._mountAfterHook(fun); return this.buildAfterChainer(); }; diff --git a/src/adapter/create-adapter-builder.ts b/src/adapter/create-adapter-builder.ts index 89abb8e..e3aa39f 100644 --- a/src/adapter/create-adapter-builder.ts +++ b/src/adapter/create-adapter-builder.ts @@ -13,15 +13,19 @@ import { AdapterBuilder } from './adapter-builder.js'; export function createAdapterBuilder< Context extends TrackContext, EventData extends TrackEventDataBase, - AdapterOptions extends TrackAdapterOptions, ->(adapter: TrackAdapter) { + AdapterOptions extends TrackAdapterOptions, + RealEventData extends TrackEventDataBase = EventData, +>(adapter: TrackAdapter) { if (!adapter) { throw new Error('Adapter is required'); } - const adapterBuilder = new AdapterBuilder( - adapter - ); + const adapterBuilder = new AdapterBuilder< + Context, + EventData, + AdapterOptions, + RealEventData + >(adapter); return adapterBuilder.init(); } diff --git a/src/helpers/helper-adapter-track.ts b/src/helpers/helper-adapter-track.ts index dd14f22..c1c2280 100644 --- a/src/helpers/helper-adapter-track.ts +++ b/src/helpers/helper-adapter-track.ts @@ -1,6 +1,5 @@ import { TrackContext } from '../types/types-create.js'; import { TrackAdapterMap, TrackEventDataBase } from '../types/types-track.js'; -import { executeFunction } from './helper-execute.js'; /** * Executes the track function of each adapter in the adapterMap for the given eventType and result. @@ -24,18 +23,7 @@ export const executeAdapterTrack = async < result: EventData[EventType] ): Promise => { for (const [adapterName, adapter] of Object.entries(adapterMap)) { - const isTrackable = await executeFunction( - adapter.isTrackable, - ctx, - eventType, - result - ); - - if (!isTrackable) { - ctx.logger?.warn(`Adapter is not trackable: ${adapterName}`); - continue; - } - await adapter.track(ctx, eventType, result); + await adapter.track(adapterName, ctx, eventType, result); } return result; }; diff --git a/src/types/types-adapter.ts b/src/types/types-adapter.ts index 4a3e8e5..58db6f2 100644 --- a/src/types/types-adapter.ts +++ b/src/types/types-adapter.ts @@ -1,10 +1,42 @@ import { TrackAdapterOptions, TrackContext } from './types-create.js'; import { TrackEventDataBase } from './types-track.js'; +export type TransformReturns = + Key extends keyof EventData + ? EventData[Key] | Promise + : Key extends [keyof EventData, keyof RealEventData] + ? RealEventData[Key[1]] | Promise + : never; + +export type TransformEventType = + Key extends keyof EventData + ? Key + : Key extends [keyof EventData, keyof RealEventData] + ? Key[0] + : never; + +export type TransformEventData = + Key extends keyof EventData + ? EventData[Key] + : Key extends [keyof EventData, keyof RealEventData] + ? EventData[Key[0]] + : never; + +export type CheckUndefined = + RealEventData extends undefined ? keyof EventData : keyof RealEventData; + /** * Represents the data reported by the adapter. */ -export type AdapterReportData = any; +export type AdapterReportData< + RealEventData, + EventData, + EventType = CheckUndefined, +> = EventType extends keyof EventData + ? EventData[EventType] + : EventType extends keyof RealEventData + ? RealEventData[EventType] + : never; /** * Represents a function that takes a context and returns void or a promise that resolves to void. @@ -25,10 +57,12 @@ export type AdapterBeforeFunction = ( /** * The adapter hook function is triggered after the report is executed */ -export type AdapterAfterFunction = ( +export type AdapterAfterFunction = ( ctx: Context, - eventType: keyof EventData, - reportData: AdapterReportData + eventType: CheckUndefined, + reportData?: + | AdapterReportData + | Awaited> ) => void | Promise; /** @@ -38,11 +72,14 @@ export type AdapterTransformFunction< Context, EventType extends keyof EventData, EventData, + RealEventData extends TrackEventDataBase, > = ( ctx: Context, eventType: EventType, eventData: EventData[EventType] -) => AdapterReportData | Promise; +) => + | AdapterReportData + | Promise>; /** * Track adapter interface. @@ -50,7 +87,8 @@ export type AdapterTransformFunction< export interface TrackAdapter< Context extends TrackContext, EventData extends TrackEventDataBase, - AdapterOptions extends TrackAdapterOptions, + AdapterOptions extends TrackAdapterOptions, + RealEventData extends TrackEventDataBase = EventData, > { /** * The adapter hook Performs data consolidation against the rules defined by AdapterOptions @@ -67,38 +105,54 @@ export interface TrackAdapter< /** * The adapter hook function is triggered after the report is executed */ - _mountAfterHook(fun: AdapterAfterFunction): void; + _mountAfterHook( + fun: AdapterAfterFunction + ): void; /** * The adapter hook function that converts EventData corresponding to different EventType * @param eventType The type of the event. * @param fun The transform function. */ - _mountTransformHook( + _mountTransformHook< + EventType extends keyof EventData | [keyof EventData, keyof RealEventData], + >( eventType: EventType, - fun: AdapterTransformFunction + fun: AdapterTransformFunction< + Context, + keyof EventData, + EventData, + RealEventData + > ): void; /** - * Checks if the adapter is available. - * @param ctx The track context. - * @param eventType The type of the event. - * @param eventData The data associated with the event. - * @returns A boolean indicating if the adapter is available. + * Determines if an event is trackable based on the provided context, event type, and event data. + * + * @param ctx - The context in which the event is being processed. + * @param eventType - The type of the event to check, which can be either from `RealEventData` or `EventData`. + * If `RealEventData` is `undefined`, it uses `EventData`. + * @param reportData - The data associated with the event. If `RealEventData` is `undefined`, + * it uses data from `EventData`; otherwise, it uses data from `RealEventData`. + * @returns A boolean or a promise that resolves to a boolean indicating whether the event is trackable. */ - isTrackable( + isTrackable>( ctx: Context, eventType: EventType, - eventData: EventData[EventType] + reportData?: + | AdapterReportData + | Awaited> ): boolean | Promise; /** * Tracks an event. + * @param adapterName The adapter Name. * @param ctx The track context. * @param eventType The type of the event. * @param eventData The data associated with the event. */ track( + adapterName: string, ctx: Context, eventType: EventType, eventData: EventData[EventType] diff --git a/src/types/types-create.ts b/src/types/types-create.ts index cea9360..a71cddb 100644 --- a/src/types/types-create.ts +++ b/src/types/types-create.ts @@ -1,3 +1,4 @@ +import { CheckUndefined } from './types-adapter.js'; import { TrackLogger } from './types-logger.js'; import { TrackEventDataBase } from './types-track.js'; @@ -44,10 +45,12 @@ export type TrackContext = { * * @template Context - The type of the track context. * @template EventData - The type of the track event data. + * @template RealEventData - The type of the real track event data. */ export type TrackAdapterOptions< Context extends TrackContext, EventData extends TrackEventDataBase, + RealEventData extends TrackEventDataBase = EventData, > = { /** * The adapter hook Performs data consolidation against the rules defined by AdapterOptions @@ -61,7 +64,7 @@ export type TrackAdapterOptions< */ setup?: ( ctx: Context, - eventType: EventType, + eventType: CheckUndefined, eventData: EventData[EventType] ) => any | Promise; }; diff --git a/tests/test-adapter.spec.ts b/tests/test-adapter.spec.ts index 8ef2d67..7884b1e 100644 --- a/tests/test-adapter.spec.ts +++ b/tests/test-adapter.spec.ts @@ -67,7 +67,7 @@ describe('test-adapter.spec', () => { eventType, }; }); - const reportFun = vi.fn((ctx, eventData, setupData) => {}); + const reportFun = vi.fn((ctx, eventType, reportData, setupData) => {}); vi.spyOn(reportAdapter, 'report').mockImplementation(reportFun); const adapter = adapterBuilder @@ -78,6 +78,7 @@ describe('test-adapter.spec', () => { .build(); await adapter.track( + 'reportAdapter', { data: trackData, }, @@ -128,12 +129,13 @@ describe('test-adapter.spec', () => { expect(reportFun.mock.lastCall?.[0]).toMatchObject({ data: trackData, }); - expect(reportFun.mock.lastCall?.[1]).toMatchObject({ + expect(reportFun.mock.lastCall?.[1]).toBe('addCart'); + expect(reportFun.mock.lastCall?.[2]).toMatchObject({ ...eventData['addCart'], eventType: 'addCart', }); - expect(Object.keys(reportFun.mock.lastCall?.[2])).toMatchObject([ + expect(Object.keys(reportFun.mock.lastCall?.[3])).toMatchObject([ 'name', 'timeStamp', 'newField', @@ -154,7 +156,7 @@ describe('test-adapter.spec', () => { }); const reportFun = vi.fn( - (ctx: TrackContext, reportData: EventDataOption) => {} + (ctx: TrackContext, eventType, reportData) => {} ); vi.spyOn(reportAdapter, 'report').mockImplementation(reportFun); @@ -162,7 +164,12 @@ describe('test-adapter.spec', () => { const adapter = trackBuilder.transform('addCart', transformFun).build(); //eventType is addCart - await adapter.track({ data: trackData }, 'addCart', eventData.addCart); + await adapter.track( + 'reportAdapter', + { data: trackData }, + 'addCart', + eventData.addCart + ); const transformOptions = transformFun.mock.lastCall; @@ -175,20 +182,28 @@ describe('test-adapter.spec', () => { expect(reportOptions).toBeDefined(); expect(reportOptions?.[0]).toMatchObject({ data: trackData }); expect(reportOptions?.[1]).toBeDefined(); - expect(reportOptions?.[1]).toMatchObject(eventData.addCart!); + expect(reportOptions?.[1]).toBe('addCart'); + expect(reportOptions?.[2]).toMatchObject(eventData.addCart!); //eventType is registry - await adapter.track({ data: trackData }, 'registry', eventData.registry); + await adapter.track( + 'reportAdapter', + { data: trackData }, + 'registry', + eventData.registry + ); const reportOptions1 = reportFun.mock.lastCall; expect(reportOptions1).toBeDefined(); expect(reportOptions1?.[1]).toBeDefined(); - expect(reportOptions1?.[1]).toMatchObject({ + expect(reportOptions1?.[1]).toBe('registry'); + expect(reportOptions1?.[2]).toMatchObject({ ...eventData.registry, }); //eventType is previewGoods await adapter.track( + 'reportAdapter', { data: trackData }, 'previewGoods', eventData.previewGoods @@ -196,7 +211,8 @@ describe('test-adapter.spec', () => { const reportOptions3 = reportFun.mock.lastCall; expect(reportOptions3).toBeDefined(); - expect(reportOptions?.[1]).toBeDefined(); - expect(reportOptions?.[1]).toMatchObject(eventData.previewGoods!); + expect(reportOptions3?.[1]).toBeDefined(); + expect(reportOptions3?.[1]).toBe('previewGoods'); + expect(reportOptions3?.[2]).toMatchObject(eventData.previewGoods!); }); }); diff --git a/tests/test-real-transform.spec.ts b/tests/test-real-transform.spec.ts new file mode 100644 index 0000000..4ae0c83 --- /dev/null +++ b/tests/test-real-transform.spec.ts @@ -0,0 +1,345 @@ +import { createAdapterBuilder } from '../src/adapter/create-adapter-builder.js'; +import { TrackContext } from '../src/types/types-create.js'; +import { AnalyzerAdapter } from './test-utils/adapter/analyzer-adapter.js'; +import { ConsoleLogger } from './test-utils/console-logger.js'; +import { AdapterRealOptions } from './test-utils/types/type-adapter-options.js'; +import { + EventDataOption, + RealEventDataOption, +} from './test-utils/types/type-event.js'; +import { TrackData } from './test-utils/types/type-track-data.js'; + +describe('test-real-transform.spec', () => { + const trackData: TrackData = { + bizMode: 'test', + env: 'prod', + platform: 'android', + ip: '0.0.0.0', + userId: 'uuid_10001', + }; + + const eventData: EventDataOption = { + registry: { + userName: 'testUser', + mobile: '1234567890', + pwd: 'password123', + email: 'testuser@example.com', + }, + previewGoods: { + goodsId: 'g123', + goodsName: 'Sample Goods', + }, + addCart: { + price: 99.99, + goodsId: 'g123', + goodsName: 'Sample Goods', + count: 2, + }, + addCartList: [ + { + price: 99.99, + goodsId: 'g123', + goodsName: 'Sample Goods', + count: 2, + }, + { + price: 125.5, + goodsId: 'T10001', + goodsName: 'T shirt', + count: 10, + }, + ], + }; + + const setupData = { + name: 'setup' as const, + timeStamp: new Date().getTime(), + user: 'admin', + }; + + const analyzerAdapter = new AnalyzerAdapter(); + + const adapterBuilder = createAdapterBuilder< + TrackContext, + EventDataOption, + AdapterRealOptions< + TrackContext, + EventDataOption, + RealEventDataOption + >, + RealEventDataOption + >(analyzerAdapter); + + const isTrackableFun = vi.fn((ctx, eventType, reportData) => { + return eventType !== '_timeStamp'; + }); + vi.spyOn(analyzerAdapter, 'isTrackable').mockImplementation(isTrackableFun); + + const reportFun = vi.fn((ctx, eventType, reportData, setupData) => {}); + vi.spyOn(analyzerAdapter, 'report').mockImplementation(reportFun); + + const adapter = adapterBuilder + .setup((ctx, eventType, eventData) => setupData) + .before((ctx, eventType, eventData) => {}) + .transform(['addCart', '_addCart'], async (ctx, eventType, eventData) => { + if (eventData) { + return [ + { + _price: eventData.price, + _goodsId: eventData.goodsId, + _goodsName: eventData.goodsName, + _count: eventData.count, + }, + ]; + } + return []; + }) + .transform( + ['addCartList', '_addCart'], + async (ctx, eventType, eventData) => { + if (eventData) { + return eventData.map((item) => { + return { + _price: item.price, + _goodsId: item.goodsId, + _goodsName: item.goodsName, + _count: item.count, + }; + }); + } + return []; + } + ) + .transform(['registry', '_registry'], (ctx, eventType, eventData) => { + if (!eventData) { + return; + } + return { + _userName: eventData?.userName, + _mobile: eventData?.mobile, + _pwd: eventData?.pwd, + _email: eventData?.email, + }; + }) + .transform( + ['previewGoods', '_previewGoods'], + (ctx, eventType, eventData) => { + if (!eventData) { + return; + } + return { + _goodsId: eventData.goodsId, + _goodsName: eventData.goodsName, + _url: 'https://example.com', + timeStamp: '2021-09-01T00:00:00Z', + }; + } + ) + .transform( + ['timeStamp', '_timeStamp'], + (ctx, eventType, eventData) => eventData + ) + .after((ctx, eventType, reportData) => {}) + .build(); + + it(`['addCart', '_addCart']`, async () => { + reportFun.mockClear(); + isTrackableFun.mockClear(); + await adapter.track( + 'reportAdapter', + { + data: trackData, + logger: new ConsoleLogger(), + }, + 'addCart', + eventData.addCart + ); + + expect(isTrackableFun.mock.lastCall).toBeDefined(); + expect(isTrackableFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(isTrackableFun.mock.lastCall?.[1]).toBe('_addCart'); + expect(isTrackableFun.mock.lastCall?.[2]).toMatchObject([ + { + _price: eventData.addCart?.price, + _goodsId: eventData.addCart?.goodsId, + _goodsName: eventData.addCart?.goodsName, + _count: eventData.addCart?.count, + }, + ]); + + expect(reportFun.mock.lastCall).toBeDefined(); + expect(reportFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(reportFun.mock.lastCall?.[1]).toBe('_addCart'); + expect(reportFun.mock.lastCall?.[2]).toMatchObject([ + { + _price: eventData.addCart?.price, + _goodsId: eventData.addCart?.goodsId, + _goodsName: eventData.addCart?.goodsName, + _count: eventData.addCart?.count, + }, + ]); + expect(reportFun.mock.lastCall?.[3]).toMatchObject(setupData); + }); + + it(`['addCartList', '_addCart']`, async () => { + reportFun.mockClear(); + isTrackableFun.mockClear(); + await adapter.track( + 'reportAdapter', + { + data: trackData, + logger: new ConsoleLogger(), + }, + 'addCartList', + eventData.addCartList + ); + + expect(isTrackableFun.mock.lastCall).toBeDefined(); + expect(isTrackableFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(isTrackableFun.mock.lastCall?.[1]).toBe('_addCart'); + expect(isTrackableFun.mock.lastCall?.[2]).toMatchObject([ + { + _price: eventData.addCartList?.[0]?.price, + _goodsId: eventData.addCartList?.[0]?.goodsId, + _goodsName: eventData.addCartList?.[0]?.goodsName, + _count: eventData.addCartList?.[0]?.count, + }, + { + _price: eventData.addCartList?.[1]?.price, + _goodsId: eventData.addCartList?.[1]?.goodsId, + _goodsName: eventData.addCartList?.[1]?.goodsName, + _count: eventData.addCartList?.[1]?.count, + }, + ]); + + expect(reportFun.mock.lastCall).toBeDefined(); + expect(reportFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(reportFun.mock.lastCall?.[1]).toBe('_addCart'); + expect(reportFun.mock.lastCall?.[2]).toMatchObject([ + { + _price: eventData.addCartList?.[0]?.price, + _goodsId: eventData.addCartList?.[0]?.goodsId, + _goodsName: eventData.addCartList?.[0]?.goodsName, + _count: eventData.addCartList?.[0]?.count, + }, + { + _price: eventData.addCartList?.[1]?.price, + _goodsId: eventData.addCartList?.[1]?.goodsId, + _goodsName: eventData.addCartList?.[1]?.goodsName, + _count: eventData.addCartList?.[1]?.count, + }, + ]); + expect(reportFun.mock.lastCall?.[3]).toMatchObject(setupData); + }); + + it(`['registry', '_registry']`, async () => { + reportFun.mockClear(); + isTrackableFun.mockClear(); + await adapter.track( + 'reportAdapter', + { + data: trackData, + logger: new ConsoleLogger(), + }, + 'registry', + eventData.registry + ); + + expect(isTrackableFun.mock.lastCall).toBeDefined(); + expect(isTrackableFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(isTrackableFun.mock.lastCall?.[1]).toBe('_registry'); + expect(isTrackableFun.mock.lastCall?.[2]).toMatchObject({ + _userName: eventData.registry?.userName, + _mobile: eventData.registry?.mobile, + _pwd: eventData.registry?.pwd, + _email: eventData.registry?.email, + }); + + expect(reportFun.mock.lastCall).toBeDefined(); + expect(reportFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(reportFun.mock.lastCall?.[1]).toBe('_registry'); + expect(reportFun.mock.lastCall?.[2]).toMatchObject({ + _userName: eventData.registry?.userName, + _mobile: eventData.registry?.mobile, + _pwd: eventData.registry?.pwd, + _email: eventData.registry?.email, + }); + expect(reportFun.mock.lastCall?.[3]).toMatchObject(setupData); + }); + + it(`['previewGoods', '_previewGoods']`, async () => { + reportFun.mockClear(); + isTrackableFun.mockClear(); + await adapter.track( + 'reportAdapter', + { + data: trackData, + logger: new ConsoleLogger(), + }, + 'previewGoods', + eventData.previewGoods + ); + + expect(isTrackableFun.mock.lastCall).toBeDefined(); + expect(isTrackableFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(isTrackableFun.mock.lastCall?.[1]).toBe('_previewGoods'); + expect(isTrackableFun.mock.lastCall?.[2]).toMatchObject({ + _goodsId: eventData.previewGoods?.goodsId, + _goodsName: eventData.previewGoods?.goodsName, + _url: 'https://example.com', + timeStamp: '2021-09-01T00:00:00Z', + }); + + expect(reportFun.mock.lastCall).toBeDefined(); + expect(reportFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(reportFun.mock.lastCall?.[1]).toBe('_previewGoods'); + expect(reportFun.mock.lastCall?.[2]).toMatchObject({ + _goodsId: eventData.previewGoods?.goodsId, + _goodsName: eventData.previewGoods?.goodsName, + _url: 'https://example.com', + timeStamp: '2021-09-01T00:00:00Z', + }); + expect(reportFun.mock.lastCall?.[3]).toMatchObject(setupData); + }); + + it(`['timeStamp', '_timeStamp']`, async () => { + reportFun.mockClear(); + isTrackableFun.mockClear(); + await adapter.track( + 'reportAdapter', + { + data: trackData, + logger: new ConsoleLogger(), + }, + 'timeStamp', + eventData.timeStamp + ); + + expect(isTrackableFun.mock.lastCall).toBeDefined(); + expect(isTrackableFun.mock.lastCall?.[0]).toMatchObject({ + data: trackData, + }); + expect(isTrackableFun.mock.lastCall?.[1]).toBe('_timeStamp'); + expect(isTrackableFun.mock.lastCall?.[2]).toBe(eventData.timeStamp); + expect(isTrackableFun.mock.results?.[0]?.value).toBe(false); + + // The report function should not be called because the event type is not trackable + expect(reportFun.mock.lastCall).toBeUndefined(); + }); +}); diff --git a/tests/test-track-error.spec.ts b/tests/test-track-error.spec.ts index be89b96..7b8b3bf 100644 --- a/tests/test-track-error.spec.ts +++ b/tests/test-track-error.spec.ts @@ -80,7 +80,9 @@ describe('test-track-error.spec', () => { adapterBuilder = defaultAdapterBuilder(); adapter = adapterBuilder - .transform('previewGoods', () => {}) + .transform('previewGoods', (ctx, eventType, eventData) => { + return eventData; + }) .after(() => { throw new Error('after Error'); }) diff --git a/tests/test-track-pipeline.spec.ts b/tests/test-track-pipeline.spec.ts index 01fb715..d79d1e4 100644 --- a/tests/test-track-pipeline.spec.ts +++ b/tests/test-track-pipeline.spec.ts @@ -49,23 +49,16 @@ describe('test-track-pipeline.spec', () => { console.log('before'); }) .transform('addCart', (ctx, eventType, eventData) => { - return { - ...eventData, - pay: { - payId: 'p123', - payName: 'Sample Pay', - payType: 'credit', - }, - timeStamp: '2024-09-01T00:00:00Z', - }; + return eventData; }) - .transform('previewGoods', (ctx, eventType, eventData) => {}) - .transform('registry', (ctx, eventType, eventData) => {}) - .transform('timeStamp', (ctx, eventType, eventData) => {}) + .transform('previewGoods', (ctx, eventType, eventData) => { + return eventData; + }) + .transform('registry', (ctx, eventType, eventData) => eventData) + .transform('timeStamp', (ctx, eventType, eventData) => eventData) .after(async (ctx, eventType, eventData) => { console.log('after', eventData); - }) - .build(); + }); const trackBuilder = createTrackBuilder< TrackContext, diff --git a/tests/test-utils/adapter/analyzer-adapter.ts b/tests/test-utils/adapter/analyzer-adapter.ts new file mode 100644 index 0000000..91e0f95 --- /dev/null +++ b/tests/test-utils/adapter/analyzer-adapter.ts @@ -0,0 +1,47 @@ +import { AdapterReportData, BaseAdapter } from '../../../src/index.js'; +import { TrackContext } from '../../../src/types/types-create.js'; +import { AdapterRealOptions } from '../types/type-adapter-options.js'; +import { EventDataOption, RealEventDataOption } from '../types/type-event.js'; +import { TrackData } from '../types/type-track-data.js'; + +export class AnalyzerAdapter extends BaseAdapter< + TrackContext, + EventDataOption, + AdapterRealOptions< + TrackContext, + EventDataOption, + RealEventDataOption + >, + RealEventDataOption +> { + isTrackable( + ctx: TrackContext, + eventType: keyof RealEventDataOption, + reportData?: + | AdapterReportData + | Awaited< + AdapterReportData + > + | undefined + ): boolean | Promise { + return eventType !== '_timeStamp'; + } + + report( + ctx: TrackContext, + eventType: keyof RealEventDataOption, + reportData?: + | AdapterReportData + | Awaited< + AdapterReportData + > + | undefined, + setupData?: + | { + name: 'setup' | 'setup1' | 'setup2'; + timeStamp: number; + user?: string; + } + | undefined + ): void | Promise {} +} diff --git a/tests/test-utils/adapter/report-adapter.ts b/tests/test-utils/adapter/report-adapter.ts index 25a4887..2e214d7 100644 --- a/tests/test-utils/adapter/report-adapter.ts +++ b/tests/test-utils/adapter/report-adapter.ts @@ -1,5 +1,4 @@ -import { BaseAdapter } from '../../../src/index.js'; -import { AdapterReportData } from '../../../src/types/types-adapter.js'; +import { AdapterReportData, BaseAdapter } from '../../../src/index.js'; import { TrackContext } from '../../../src/types/types-create.js'; import { AdapterOptions } from '../types/type-adapter-options.js'; import { EventDataOption } from '../types/type-event.js'; @@ -12,14 +11,22 @@ export class ReportAdapter extends BaseAdapter< > { isTrackable( ctx: TrackContext, - eventType: EventType, - eventData: EventDataOption[EventType] + eventType: keyof EventDataOption, + reportData?: + | AdapterReportData + | Awaited> + | undefined ): boolean | Promise { return true; } - report( + + report( ctx: TrackContext, - reportData: AdapterReportData, + eventType: keyof EventDataOption, + reportData?: + | AdapterReportData + | Awaited> + | undefined, setupData?: | { name: 'setup' | 'setup1' | 'setup2'; diff --git a/tests/test-utils/types/type-adapter-options.ts b/tests/test-utils/types/type-adapter-options.ts index d03cc73..9506e45 100644 --- a/tests/test-utils/types/type-adapter-options.ts +++ b/tests/test-utils/types/type-adapter-options.ts @@ -1,11 +1,25 @@ -export type AdapterOptions = { +import { CheckUndefined } from '../../../src/index.js'; + +export interface AdapterOptions { setup?: ( ctx: Context, - eventTYpe: EventType, + eventType: EventType, eventData: EventData[EventType] ) => Promise<{ name: 'setup' | 'setup1' | 'setup2'; timeStamp: number; user?: string; }>; +} + +export type AdapterRealOptions = { + setup?: ( + ctx: Context, + eventType: CheckUndefined, + eventData: EventData[EventType] + ) => { + name: 'setup' | 'setup1' | 'setup2'; + timeStamp: number; + user?: string; + }; }; diff --git a/tests/test-utils/types/type-event.ts b/tests/test-utils/types/type-event.ts index 2ec4b16..8339ecf 100644 --- a/tests/test-utils/types/type-event.ts +++ b/tests/test-utils/types/type-event.ts @@ -15,5 +15,33 @@ export type EventDataOption = { goodsName: string; count: number; }; + addCartList?: { + price: number; + goodsId: string; + goodsName: string; + count: number; + }[]; timeStamp?: string; }; + +export type RealEventDataOption = { + _registry?: { + _userName: string; + _mobile: string; + _pwd: string; + _email: string; + }; + _previewGoods?: { + _goodsId: string; + _goodsName: string; + _url: string; + timeStamp: string; + }; + _addCart?: { + _price: number; + _goodsId: string; + _goodsName: string; + _count: number; + }[]; + _timeStamp?: string; +}; diff --git a/website/docs/api/adapter-builder.md b/website/docs/api/adapter-builder.md index 4821fff..49fe3d7 100644 --- a/website/docs/api/adapter-builder.md +++ b/website/docs/api/adapter-builder.md @@ -85,56 +85,83 @@ adapterBuilder.before( ### `transform` -The `transform` hook allows you to modify the event data before it is sent to the tracking system. You can use this hook to change, enrich, or sanitize the event data. +The `transform` hook allows you to modify the event data before it is sent to the tracking system. This hook is highly flexible, enabling you to change, enrich, or sanitize the event data according to your specific needs. It also supports scenarios where `RealEventData` is a mapped object of `EventData`. #### Parameters -- **eventType** : `keyof EventData` +- **eventType** : `keyof EventData | [keyof EventData, keyof RealEventData]` - The type of event being tracked. This is usually a key from the EventData that corresponds to specific events like click, purchase, etc. + The type of event being tracked. Typically, this is a key from the `EventData` that corresponds to specific events like click, purchase, etc. When `RealEventData` is used, it allows you to map the `EventData` to a corresponding event type in `RealEventData`. - **fun** : `( ctx: Context, - eventType: Key, - eventData: LeftEventData[Key] + eventType: EventType, + eventData: EventData[keyof EventType] ) => AdapterReportData | Promise` - The function to transform the event data. + A function that transforms the event data. The function receives the event type and data, and should return the modified data, either directly or as a promise. #### Example +The following examples illustrate how to use the `transform` hook to modify event data for different scenarios: + ```typescript title="AdapterBuilder.ts" +// Example 1: Simple event transformation for a single event type +adapterBuilder.transform( + 'addCart', + ( + ctx: TrackContext, + eventType: 'addCart', + eventData: EventData['addCart'] + ) => { + return { + ...eventData, + goodsName: 'ac_' + eventData?.goodsName, + timeStamp: Date.now(), + }; + } +); + +// Example 2: Using RealEventData to map and transform data between different event types adapterBuilder .transform( - 'addCart', + ['previewGoods', '_previewGoods'], ( ctx: TrackContext, - eventType: 'addCart', - eventData: EventData['addCart'] + eventType: 'previewGoods', + eventData: EventData['previewGoods'] ) => { return { ...eventData, - goodName: 'ac_' + eventData?.goodsName, + goodsName: 'pg_' + eventData?.goodsName, timeStamp: Date.now(), }; } ) .transform( - 'previewGoods', + ['checkout', '_checkout'], ( ctx: TrackContext, - eventType: 'previewGoods', - eventData: EventData['previewGoods'] + eventType: 'checkout', + eventData: RealEventData['_checkout'] ) => { return { ...eventData, - goodName: 'pg_' + eventData?.goodsName, + totalAmount: eventData.amount * 1.2, // Applying tax or additional charges timeStamp: Date.now(), }; } ); ``` +:::note +• eventType: When using a tuple like [keyof EventData, keyof RealEventData], the first element should map to the key in EventData and the second to the corresponding key in RealEventData. + +• fun: Ensure the transformation function handles asynchronous operations properly when returning a `Promise`. + +• This approach provides a flexible and powerful way to map and modify events, making it easier to adapt the event data for various tracking systems and scenarios. +::: + ### `after` The `after` hook is executed after the event has been reported. This is where you can perform any post-processing, such as logging or triggering additional actions based on the reported data. @@ -149,7 +176,7 @@ The `after` hook is executed after the event has been reported. This is where yo The type of event being tracked. This is usually a key from the EventData that corresponds to specific events like click, purchase, etc. -- **reportData** : `AdapterReportData` +- **reportData** : `AdapterReportData | Awaited> | undefined` The data that needs to be reported. This can include the event type, associated data, and any additional metadata that should be sent to the third-party service. @@ -158,9 +185,11 @@ The `after` hook is executed after the event has been reported. This is where yo ```typescript title="AdapterBuilder.ts" adapterBuilder.after( ( - ctx: TrackContext, - eventType: keyof EventData, - reportData: AdapterReportData + ctx: Context, + eventType: CheckUndefined, + reportData?: + | AdapterReportData + | Awaited> ) => { //do something } diff --git a/website/docs/api/base-adapter.md b/website/docs/api/base-adapter.md index 3e53be8..1d5b1cf 100644 --- a/website/docs/api/base-adapter.md +++ b/website/docs/api/base-adapter.md @@ -12,20 +12,29 @@ The `BaseAdapter` class defines two key methods: Both methods are meant to be extended in a concrete implementation of the `BaseAdapter` class. ```typescript title="Signature" -export interface TrackAdapter< +export abstract class BaseAdapter< Context extends TrackContext, EventData extends TrackEventDataBase, - AdapterOptions extends TrackAdapterOptions, -> { - abstract isTrackable( + AdapterOptions extends TrackAdapterOptions, + RealEventData extends TrackEventDataBase = EventData, +> implements TrackAdapter +{ + abstract isTrackable< + EventType extends CheckUndefined, + >( ctx: Context, - eventType: EventType, - eventData: EventData[EventType] + eventType: CheckUndefined, + reportData?: + | AdapterReportData + | Awaited> ): boolean | Promise; - protected report( + protected report>( ctx: Context, - reportData: AdapterReportData, + eventType: CheckUndefined, + reportData?: + | AdapterReportData + | Awaited>, setupData?: Required['setup'] extends (...args: any) => any ? Awaited['setup']>> : undefined @@ -43,13 +52,13 @@ export interface TrackAdapter< The context in which the tracking is occurring. This typically includes details such as user information, environment, or other contextual data relevant to the tracking event. -- **eventType** : `keyof EventData` +- **eventType** : `keyof RealEventDataOption` The type of event being tracked. This is usually a key from the EventData that corresponds to specific events like click, purchase, etc. -- **eventData** : `EventData[keyof EventData]` +- **reportData** : `AdapterReportData | Awaited> | undefined` - The data associated with the event. This contains all relevant information for the specific event type. + The data that needs to be reported. This can include the event type, associated data, and any additional metadata that should be sent to the third-party service. #### Returns @@ -62,13 +71,19 @@ Here’s an example implementation of `isTrackable` that only tracks `addCart` e ```typescript title="ReportAdapter.ts" export class ReportAdapter extends BaseAdapter< TrackContext, - EventData, - AdapterOptions, EventData> + EventDataOption, + AdapterOptions, EventDataOption, RealEventDataOption>, + RealEventDataOption > { - isTrackable( + isTrackable( ctx: TrackContext, - eventType: EventType, - eventData: EventData[EventType] + eventType: keyof RealEventDataOption, + reportData?: + | AdapterReportData + | Awaited< + AdapterReportData + > + | undefined ): boolean | Promise { return eventType === 'addCart'; } @@ -85,7 +100,11 @@ export class ReportAdapter extends BaseAdapter< The context in which the tracking is occurring. This typically includes details such as user information, environment, or other contextual data relevant to the tracking event. -- **reportData** : `AdapterReportData` +- **eventType** : `keyof RealEventDataOption` + + The type of event being tracked. This is usually a key from the EventData that corresponds to specific events like click, purchase, etc. + +- **reportData** : `AdapterReportData | Awaited> | undefined` The data that needs to be reported. This can include the event type, associated data, and any additional metadata that should be sent to the third-party service. @@ -106,15 +125,28 @@ Here’s an example implementation of the report method: ```typescript title="ReportAdapter.ts" export class ReportAdapter extends BaseAdapter< TrackContext, - EventData, - AdapterOptions, EventData> + EventDataOption, + AdapterOptions, EventDataOption, RealEventDataOption>, + RealEventDataOption > { - report( - ctx: TrackContext, - reportData: AdapterReportData, - setupData?: Awaited, EventData>['setup']> - ): void | Promise { - // do something - } + protected report( + ctx: TrackContext, + eventType: keyof RealEventDataOption, + reportData?: + | AdapterReportData + | Awaited< + AdapterReportData + > + | undefined, + setupData?: + | { + name: 'setup' | 'setup1' | 'setup2'; + timeStamp: number; + user?: string; + } + | undefined + ): void | Promise { + //do something with the report data + } } ``` diff --git a/website/docs/intro/sample-example.md b/website/docs/intro/sample-example.md index 8798246..0637f80 100644 --- a/website/docs/intro/sample-example.md +++ b/website/docs/intro/sample-example.md @@ -84,21 +84,30 @@ export class ReportAdapter extends BaseAdapter< > { isTrackable( ctx: TrackContext, - eventType: EventType, - eventData: ReportEventData[EventType] + eventType: keyof ReportEventData, + reportData?: + | AdapterReportData + | Awaited> + | undefined ): boolean | Promise { return true; } - protected report( + protected report( ctx: TrackContext, - reportData: AdapterReportData, + eventType: keyof ReportEventData, + reportData?: + | AdapterReportData + | Awaited> + | undefined, setupData?: - | { name: 'setup' | 'setup2' | 'setup3'; timeStamp: number } + | { + name: 'setup' | 'setup1' | 'setup2'; + timeStamp: number; + user?: string; + } | undefined - ): void | Promise { - console.log('report', ctx, reportData, setupData); - } + ): void | Promise {} } ``` From 391c000317034eae8f3d7e8fc5bd8219cddae700 Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Fri, 23 Aug 2024 20:54:17 +0800 Subject: [PATCH 3/9] chore: update changlog --- .changeset/eleven-cycles-do.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eleven-cycles-do.md diff --git a/.changeset/eleven-cycles-do.md b/.changeset/eleven-cycles-do.md new file mode 100644 index 0000000..a744a81 --- /dev/null +++ b/.changeset/eleven-cycles-do.md @@ -0,0 +1,5 @@ +--- +"@hyperse/track": patch +--- + +1、Supports mapping evenType based on RealEventData From b8e712b7bca0b62ac22ecc35b6f646cb880930c0 Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Fri, 23 Aug 2024 22:25:48 +0800 Subject: [PATCH 4/9] chore: update example --- examples/next-example/app/page.tsx | 18 +++++++++- examples/next-example/package.json | 2 +- examples/next-example/track/report-adapter.ts | 34 +++++++++++++----- examples/next-example/track/track.ts | 36 +++++++++---------- examples/next-example/track/types.ts | 21 ++++++++--- src/adapter/adapter-base.ts | 10 ++++++ tests/test-track-pipeline.spec.ts | 7 ++++ tests/test-utils/adapter/analyzer-adapter.ts | 2 +- 8 files changed, 96 insertions(+), 34 deletions(-) diff --git a/examples/next-example/app/page.tsx b/examples/next-example/app/page.tsx index ab01bc5..20613d1 100644 --- a/examples/next-example/app/page.tsx +++ b/examples/next-example/app/page.tsx @@ -54,6 +54,16 @@ export default function Home() { }); }; + const onAddToMultiCart = (item: GoodsRecord) => { + reportTrack() + .select('reportAdapter') + .track('addCartList', [ + { + ...item, + }, + ]); + }; + return (
{mounted && @@ -76,7 +86,13 @@ export default function Home() { diff --git a/examples/next-example/package.json b/examples/next-example/package.json index d254b78..764cfa1 100644 --- a/examples/next-example/package.json +++ b/examples/next-example/package.json @@ -21,7 +21,7 @@ } }, "dependencies": { - "@hyperse/track": "1.0.2", + "@hyperse/track": "^1.0.2", "@types/node": "20.6.2", "@types/react": "18.2.22", "@types/react-dom": "18.2.7", diff --git a/examples/next-example/track/report-adapter.ts b/examples/next-example/track/report-adapter.ts index f6e017b..f7c707c 100644 --- a/examples/next-example/track/report-adapter.ts +++ b/examples/next-example/track/report-adapter.ts @@ -2,33 +2,49 @@ import { AdapterReportData, BaseAdapter, TrackContext } from '@hyperse/track'; import { ReportAdapterOptions, ReportEventData, + ReportRealEventData, ReportTrackData, } from './types'; export class ReportAdapter extends BaseAdapter< TrackContext, ReportEventData, - ReportAdapterOptions, ReportEventData> + ReportAdapterOptions< + TrackContext, + ReportEventData, + ReportRealEventData + >, + ReportRealEventData > { - isTrackable( + isTrackable( ctx: TrackContext, - eventType: EventType, - eventData: ReportEventData[EventType] + eventType: keyof ReportRealEventData, + reportData?: + | AdapterReportData + | Awaited< + AdapterReportData + > + | undefined ): boolean | Promise { return true; } - protected report( + protected report( ctx: TrackContext, - reportData: AdapterReportData, - setupData?: - | { name: 'setup' | 'setup2' | 'setup3'; timeStamp: number } - | undefined + eventType: keyof ReportRealEventData, + reportData?: + | AdapterReportData + | Awaited< + AdapterReportData + > + | undefined, + setupData?: { timeStamp: number } | undefined ): void | Promise { window.postMessage({ type: 'report', data: { ctx, + eventType, reportData, setupData, }, diff --git a/examples/next-example/track/track.ts b/examples/next-example/track/track.ts index 14f1c48..d2f0cde 100644 --- a/examples/next-example/track/track.ts +++ b/examples/next-example/track/track.ts @@ -7,6 +7,7 @@ import { ReportAdapter } from './report-adapter'; import { ReportAdapterOptions, ReportEventData, + ReportRealEventData, ReportTrackData, } from './types'; @@ -16,35 +17,34 @@ export const reportTrack = () => { const adapterBuilder = createAdapterBuilder< TrackContext, ReportEventData, - ReportAdapterOptions, ReportEventData> + ReportAdapterOptions< + TrackContext, + ReportEventData, + ReportRealEventData + >, + ReportRealEventData >(reportAdapter); const adapter = adapterBuilder .setup(() => { - return Promise.resolve({ - name: 'setup', + return { timeStamp: Date.now(), - }); + }; }) .before((ctx, eventType, eventData) => { console.log('before', ctx, eventType, eventData); }) - .transform('addCart', (ctx, eventType, eventData) => { - return { - eventType, - goodName: 'ac_' + eventData?.goodsName, - goodsId: 'ac_' + eventData?.goodsId, - price: eventData?.price, - }; + .transform(['addCart', 'addCart'], (ctx, eventType, eventData) => { + if (eventData) { + return [eventData]; + } + return []; + }) + .transform(['addCartList', 'addCart'], (ctx, eventType, eventData) => { + return eventData || []; }) .transform('pv', (ctx, eventType, eventData) => { - return { - eventType, - url: eventData?.url, - timeStamp: 'pv_' + eventData?.timeStamp, - userName: 'pv_' + eventData?.userName, - userId: 'pv_' + eventData?.userId, - }; + return eventData; }) .after((ctx, eventType, reportData) => { console.log('after', ctx, eventType, reportData); diff --git a/examples/next-example/track/types.ts b/examples/next-example/track/types.ts index 631da45..ff81140 100644 --- a/examples/next-example/track/types.ts +++ b/examples/next-example/track/types.ts @@ -1,11 +1,13 @@ -export type ReportAdapterOptions = { +import { CheckUndefined } from '@hyperse/track'; + +export type ReportAdapterOptions = { setup?: ( ctx: Context, - eventTYpe: EventType, + eventType: CheckUndefined, eventData: EventData[EventType] - ) => Promise<{ + ) => { timeStamp: number; - }>; + }; }; export type ReportTrackData = { @@ -22,6 +24,17 @@ export type ReportEventData = { userId: string; }; addCart?: GoodsRecord; + addCartList: GoodsRecord[]; +}; + +export type ReportRealEventData = { + pageView?: { + url: string; + timeStamp: number; + userName: string; + userId: string; + }; + addCart?: GoodsRecord[]; }; export interface GoodsRecord { diff --git a/src/adapter/adapter-base.ts b/src/adapter/adapter-base.ts index 37f87f3..af3893f 100644 --- a/src/adapter/adapter-base.ts +++ b/src/adapter/adapter-base.ts @@ -39,6 +39,16 @@ export abstract class BaseAdapter< | Awaited> ): boolean | Promise; + protected isEventOfReportDataEqual< + EventType extends CheckUndefined, + >( + eventType: CheckUndefined, + reportData: RealEventData[keyof RealEventData] | EventData[keyof EventData], + K: EventType + ): reportData is AdapterReportData { + return eventType === K; + } + protected report>( ctx: Context, eventType: CheckUndefined, diff --git a/tests/test-track-pipeline.spec.ts b/tests/test-track-pipeline.spec.ts index d79d1e4..bbd9c68 100644 --- a/tests/test-track-pipeline.spec.ts +++ b/tests/test-track-pipeline.spec.ts @@ -45,6 +45,13 @@ describe('test-track-pipeline.spec', () => { >(adapter); adapterBuilder + .setup((ctx, eventType, eventData) => { + return Promise.resolve({ + name: 'setup', + user: 'admin', + timeStamp: Date.now(), + }); + }) .before(async (ctx, eventType, eventData) => { console.log('before'); }) diff --git a/tests/test-utils/adapter/analyzer-adapter.ts b/tests/test-utils/adapter/analyzer-adapter.ts index 91e0f95..915ea2a 100644 --- a/tests/test-utils/adapter/analyzer-adapter.ts +++ b/tests/test-utils/adapter/analyzer-adapter.ts @@ -24,7 +24,7 @@ export class AnalyzerAdapter extends BaseAdapter< > | undefined ): boolean | Promise { - return eventType !== '_timeStamp'; + return !this.isEventOfReportDataEqual(eventType, reportData, '_timeStamp'); } report( From 2ce6d6eef11871fdc780dfc7a5512718580417d7 Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Sun, 25 Aug 2024 10:46:11 +0800 Subject: [PATCH 5/9] feat: update realEventData --- examples/next-example/track/report-adapter.ts | 5 ++++- src/adapter/adapter-base.ts | 15 +++++++++++---- src/types/types-adapter.ts | 2 +- src/types/types-track.ts | 2 +- tests/test-track-error.spec.ts | 3 ++- tests/test-track-parallel-pipeline.spec.ts | 2 +- tests/test-utils/adapter/analyzer-adapter.ts | 4 +++- website/docs/api/adapter-builder.md | 5 ++++- 8 files changed, 27 insertions(+), 11 deletions(-) diff --git a/examples/next-example/track/report-adapter.ts b/examples/next-example/track/report-adapter.ts index f7c707c..2ff1a78 100644 --- a/examples/next-example/track/report-adapter.ts +++ b/examples/next-example/track/report-adapter.ts @@ -26,7 +26,10 @@ export class ReportAdapter extends BaseAdapter< > | undefined ): boolean | Promise { - return true; + return this.isEventOfReportDataEqual(eventType, reportData, [ + 'addCart', + 'pageView', + ]); } protected report( diff --git a/src/adapter/adapter-base.ts b/src/adapter/adapter-base.ts index af3893f..de13df9 100644 --- a/src/adapter/adapter-base.ts +++ b/src/adapter/adapter-base.ts @@ -40,13 +40,20 @@ export abstract class BaseAdapter< ): boolean | Promise; protected isEventOfReportDataEqual< - EventType extends CheckUndefined, + EventType extends + | CheckUndefined + | CheckUndefined[], >( eventType: CheckUndefined, reportData: RealEventData[keyof RealEventData] | EventData[keyof EventData], - K: EventType - ): reportData is AdapterReportData { - return eventType === K; + realEventType: EventType + ): reportData is EventType extends CheckUndefined[] + ? AdapterReportData + : AdapterReportData { + if (Array.isArray(realEventType)) { + return realEventType.map(String).includes(`${eventType}`); + } + return eventType === realEventType; } protected report>( diff --git a/src/types/types-adapter.ts b/src/types/types-adapter.ts index 58db6f2..c73d384 100644 --- a/src/types/types-adapter.ts +++ b/src/types/types-adapter.ts @@ -88,7 +88,7 @@ export interface TrackAdapter< Context extends TrackContext, EventData extends TrackEventDataBase, AdapterOptions extends TrackAdapterOptions, - RealEventData extends TrackEventDataBase = EventData, + RealEventData extends TrackEventDataBase, > { /** * The adapter hook Performs data consolidation against the rules defined by AdapterOptions diff --git a/src/types/types-track.ts b/src/types/types-track.ts index dd1df8d..db5237f 100644 --- a/src/types/types-track.ts +++ b/src/types/types-track.ts @@ -58,5 +58,5 @@ export type TrackAdapterMap< Context extends TrackContext, EventData extends TrackEventDataBase, > = { - [name: string]: TrackAdapter; + [name: string]: TrackAdapter; }; diff --git a/tests/test-track-error.spec.ts b/tests/test-track-error.spec.ts index 7b8b3bf..98557a5 100644 --- a/tests/test-track-error.spec.ts +++ b/tests/test-track-error.spec.ts @@ -100,7 +100,8 @@ describe('test-track-error.spec', () => { adapter: TrackAdapter< TrackContext, EventDataOption, - AdapterOptions, EventDataOption> + AdapterOptions, EventDataOption>, + EventDataOption > ) => { const logger = new ConsoleLogger(); diff --git a/tests/test-track-parallel-pipeline.spec.ts b/tests/test-track-parallel-pipeline.spec.ts index 0f83ff4..c9f5d5a 100644 --- a/tests/test-track-parallel-pipeline.spec.ts +++ b/tests/test-track-parallel-pipeline.spec.ts @@ -129,7 +129,7 @@ const mockAdapter = ( ): [ Mock<(ctx: any, reportData: any, setupData: any) => any>, Mock<(adapterName: string) => string>, - TrackAdapter, EventDataOption, any>, + TrackAdapter, EventDataOption, any, EventDataOption>, ] => { const reportAdapterBuilder = createAdapterBuilder< TrackContext, diff --git a/tests/test-utils/adapter/analyzer-adapter.ts b/tests/test-utils/adapter/analyzer-adapter.ts index 915ea2a..383fcaf 100644 --- a/tests/test-utils/adapter/analyzer-adapter.ts +++ b/tests/test-utils/adapter/analyzer-adapter.ts @@ -24,7 +24,9 @@ export class AnalyzerAdapter extends BaseAdapter< > | undefined ): boolean | Promise { - return !this.isEventOfReportDataEqual(eventType, reportData, '_timeStamp'); + return !this.isEventOfReportDataEqual(eventType, reportData, [ + '_timeStamp', + ]); } report( diff --git a/website/docs/api/adapter-builder.md b/website/docs/api/adapter-builder.md index 49fe3d7..10a7560 100644 --- a/website/docs/api/adapter-builder.md +++ b/website/docs/api/adapter-builder.md @@ -7,8 +7,11 @@ class AdapterBuilder< Context extends TrackContext, EventData extends TrackEventDataBase, AdapterOptions extends TrackAdapterOptions, + RealEventData extends TrackEventDataBase = EventData, > { - constructor(adapter: TrackAdapter); + constructor( + adapter: TrackAdapter + ); } ``` From 52db97f631c8dc770d1322d6fb36234486b0e4ed Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Sun, 25 Aug 2024 10:50:28 +0800 Subject: [PATCH 6/9] fix: fix yarrn.lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 62d77a5..79bf794 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3273,7 +3273,7 @@ __metadata: resolution: "@hyperse/next-example@workspace:examples/next-example" dependencies: "@hyperse/eslint-config-hyperse": "npm:1.1.3" - "@hyperse/track": "npm:1.0.2" + "@hyperse/track": "npm:^1.0.2" "@types/mockjs": "npm:^1" "@types/node": "npm:20.6.2" "@types/react": "npm:18.2.22" @@ -3338,7 +3338,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperse/track@npm:1.0.2, @hyperse/track@workspace:.": +"@hyperse/track@npm:^1.0.2, @hyperse/track@workspace:.": version: 0.0.0-use.local resolution: "@hyperse/track@workspace:." dependencies: From cd076b2329b94bc030169440dd53413165ad99df Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Sun, 25 Aug 2024 11:11:45 +0800 Subject: [PATCH 7/9] test: update test --- README.md | 16 +++++------ src/adapter/adapter-base.ts | 2 +- tests/test-real-transform.spec.ts | 28 ++++++++++++++++++++ tests/test-utils/adapter/analyzer-adapter.ts | 25 ++++++++++++++++- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7c2c483..198a9e1 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ ## Coverage Report -
Status Category Percentage Covered / Total
🔵 Lines 100% 185 / 185
🔵 Statements 100% 185 / 185
🔵 Functions 98.18% 54 / 55
🔵 Branches 93.75% 75 / 80
+
Status Category Percentage Covered / Total
🔵 Lines 100% 191 / 191
🔵 Statements 100% 191 / 191
🔵 Functions 98.21% 55 / 56
🔵 Branches 93.02% 80 / 86
## Prerequisites Before you begin, make sure you have the following installed: -- Node.js (recommended version 16.x or higher) -- npm (comes with Node.js) +* Node.js (recommended version 16.x or higher) +* npm (comes with Node.js) ## Installation @@ -51,8 +51,8 @@ yarn add @hyperse/track ## Development -> [!IMPORTANT] -> The following instructions are for those who want to develop the hyperse related framework or plugins (e.g. if you intend to make a pull request). For instructions on how to build a project _using_ Hyperse, please see the [Getting Started guide](https://hyperse-io.github.io/track/docs/community/contributing). +> \[!IMPORTANT] +> The following instructions are for those who want to develop the hyperse related framework or plugins (e.g. if you intend to make a pull request). For instructions on how to build a project *using* Hyperse, please see the [Getting Started guide](https://hyperse-io.github.io/track/docs/community/contributing). ### 1. Clone project to the local directory @@ -74,9 +74,9 @@ npm install The root directory has a `package.json` which contains build-related dependencies for tasks including: -- Building & deploying the docs -- Project for online presentation -- Linting, formatting & testing tasks to run on git commit & push +* Building & deploying the docs +* Project for online presentation +* Linting, formatting & testing tasks to run on git commit & push ### 3. Testing diff --git a/src/adapter/adapter-base.ts b/src/adapter/adapter-base.ts index de13df9..66fc746 100644 --- a/src/adapter/adapter-base.ts +++ b/src/adapter/adapter-base.ts @@ -20,7 +20,7 @@ export abstract class BaseAdapter< { private setupHook?: AdapterOptions['setup']; private beforeHook?: AdapterBeforeFunction; - transformHookMap: { + private transformHookMap: { [K in keyof EventData]?: { realEventType: keyof RealEventData | keyof EventData; execute: AdapterTransformFunction; diff --git a/tests/test-real-transform.spec.ts b/tests/test-real-transform.spec.ts index 4ae0c83..208a50d 100644 --- a/tests/test-real-transform.spec.ts +++ b/tests/test-real-transform.spec.ts @@ -342,4 +342,32 @@ describe('test-real-transform.spec', () => { // The report function should not be called because the event type is not trackable expect(reportFun.mock.lastCall).toBeUndefined(); }); + + it(`test IsEventOfReportDataEqual`, async () => { + reportFun.mockClear(); + isTrackableFun.mockClear(); + expect( + analyzerAdapter.testIsEventOfReportDataEqual( + '_registry', + eventData?.registry, + '_registry' + ) + ).toBeTruthy(); + + expect( + analyzerAdapter.testIsEventOfReportDataEqual( + '_registry', + eventData?.registry, + ['_registry'] + ) + ).toBeTruthy(); + + expect( + analyzerAdapter.testIsEventOfReportDataEqual( + '_registry', + eventData?.registry, + ['_addCart'] + ) + ).toBe(false); + }); }); diff --git a/tests/test-utils/adapter/analyzer-adapter.ts b/tests/test-utils/adapter/analyzer-adapter.ts index 383fcaf..b03d084 100644 --- a/tests/test-utils/adapter/analyzer-adapter.ts +++ b/tests/test-utils/adapter/analyzer-adapter.ts @@ -1,4 +1,8 @@ -import { AdapterReportData, BaseAdapter } from '../../../src/index.js'; +import { + AdapterReportData, + BaseAdapter, + CheckUndefined, +} from '../../../src/index.js'; import { TrackContext } from '../../../src/types/types-create.js'; import { AdapterRealOptions } from '../types/type-adapter-options.js'; import { EventDataOption, RealEventDataOption } from '../types/type-event.js'; @@ -14,6 +18,25 @@ export class AnalyzerAdapter extends BaseAdapter< >, RealEventDataOption > { + testIsEventOfReportDataEqual< + EventType extends + | CheckUndefined + | CheckUndefined[], + >( + eventType: CheckUndefined, + reportData: + | RealEventDataOption[keyof RealEventDataOption] + | EventDataOption[keyof EventDataOption], + realEventType: EventType + ): reportData is EventType extends CheckUndefined< + RealEventDataOption, + EventDataOption + >[] + ? AdapterReportData + : AdapterReportData { + return this.isEventOfReportDataEqual(eventType, reportData, realEventType); + } + isTrackable( ctx: TrackContext, eventType: keyof RealEventDataOption, From 5fd49ce78802dbb35f8179bc14c0f2d6526e84bb Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Sun, 25 Aug 2024 11:19:49 +0800 Subject: [PATCH 8/9] docs: update readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 198a9e1..2338f30 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ Before you begin, make sure you have the following installed: -* Node.js (recommended version 16.x or higher) -* npm (comes with Node.js) +- Node.js (recommended version 16.x or higher) +- npm (comes with Node.js) ## Installation @@ -52,7 +52,7 @@ yarn add @hyperse/track ## Development > \[!IMPORTANT] -> The following instructions are for those who want to develop the hyperse related framework or plugins (e.g. if you intend to make a pull request). For instructions on how to build a project *using* Hyperse, please see the [Getting Started guide](https://hyperse-io.github.io/track/docs/community/contributing). +> The following instructions are for those who want to develop the hyperse related framework or plugins (e.g. if you intend to make a pull request). For instructions on how to build a project _using_ Hyperse, please see the [Getting Started guide](https://hyperse-io.github.io/track/docs/community/contributing). ### 1. Clone project to the local directory @@ -74,9 +74,9 @@ npm install The root directory has a `package.json` which contains build-related dependencies for tasks including: -* Building & deploying the docs -* Project for online presentation -* Linting, formatting & testing tasks to run on git commit & push +- Building & deploying the docs +- Project for online presentation +- Linting, formatting & testing tasks to run on git commit & push ### 3. Testing From fa472d23c5ce5de34737dceadab3feabad489a79 Mon Sep 17 00:00:00 2001 From: "shunquan.wang" Date: Sun, 25 Aug 2024 12:47:13 +0800 Subject: [PATCH 9/9] fix: fix --- examples/next-example/track/types.ts | 4 +- src/adapter/adapter-base.ts | 59 ++++++++----------- src/types/types-adapter.ts | 10 ++-- src/types/types-create.ts | 4 +- src/types/types-track.ts | 2 +- tests/test-real-transform.spec.ts | 16 +++-- tests/test-track-execute-select.spec.ts | 8 +-- tests/test-track-parallel-pipeline.spec.ts | 2 +- tests/test-utils/adapter/analyzer-adapter.ts | 24 ++++---- .../test-utils/types/type-adapter-options.ts | 4 +- website/docs/api/adapter-builder.md | 2 +- website/docs/api/base-adapter.md | 10 ++-- 12 files changed, 68 insertions(+), 77 deletions(-) diff --git a/examples/next-example/track/types.ts b/examples/next-example/track/types.ts index ff81140..cf6d1f9 100644 --- a/examples/next-example/track/types.ts +++ b/examples/next-example/track/types.ts @@ -1,9 +1,9 @@ -import { CheckUndefined } from '@hyperse/track'; +import { GetSafeRealEventTypes } from '@hyperse/track'; export type ReportAdapterOptions = { setup?: ( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, eventData: EventData[EventType] ) => { timeStamp: number; diff --git a/src/adapter/adapter-base.ts b/src/adapter/adapter-base.ts index 66fc746..1fbdcb4 100644 --- a/src/adapter/adapter-base.ts +++ b/src/adapter/adapter-base.ts @@ -5,7 +5,7 @@ import { AdapterBeforeFunction, AdapterReportData, AdapterTransformFunction, - CheckUndefined, + GetSafeRealEventTypes, TrackAdapter, } from '../types/types-adapter.js'; import { TrackAdapterOptions, TrackContext } from '../types/types-create.js'; @@ -30,35 +30,30 @@ export abstract class BaseAdapter< private afterHook?: AdapterAfterFunction; abstract isTrackable< - EventType extends CheckUndefined, + EventType extends GetSafeRealEventTypes, >( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData?: | AdapterReportData | Awaited> ): boolean | Promise; protected isEventOfReportDataEqual< - EventType extends - | CheckUndefined - | CheckUndefined[], + EventType extends GetSafeRealEventTypes, >( - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData: RealEventData[keyof RealEventData] | EventData[keyof EventData], realEventType: EventType - ): reportData is EventType extends CheckUndefined[] - ? AdapterReportData - : AdapterReportData { - if (Array.isArray(realEventType)) { - return realEventType.map(String).includes(`${eventType}`); - } + ): reportData is AdapterReportData { return eventType === realEventType; } - protected report>( + protected report< + EventType extends GetSafeRealEventTypes, + >( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData?: | AdapterReportData | Awaited>, @@ -108,26 +103,20 @@ export abstract class BaseAdapter< } private executeTransform = async ( + adapterName: string, ctx: Context, eventType: EventType, eventData: EventData[EventType] ) => { - if (Object.keys(this.transformHookMap).length < 1) { - ctx.logger?.warn('Adapter transform hook is not defined'); - return { - reportData: eventData, - realEventType: eventType as unknown as CheckUndefined< - RealEventData, - EventData - >, - }; - } const transformHook = this.transformHookMap[eventType]; if (!transformHook) { + ctx.logger?.debug( + `Adapter ${adapterName}: transform hook is not defined` + ); return { reportData: eventData, - realEventType: eventType as unknown as CheckUndefined< + realEventType: eventType as unknown as GetSafeRealEventTypes< RealEventData, EventData >, @@ -143,19 +132,20 @@ export abstract class BaseAdapter< return { reportData, - realEventType: transformHook.realEventType as unknown as CheckUndefined< - RealEventData, - EventData - >, + realEventType: + transformHook.realEventType as unknown as GetSafeRealEventTypes< + RealEventData, + EventData + >, }; }; private executeReport = async < - EventType extends CheckUndefined, + EventType extends GetSafeRealEventTypes, >( adapterName: string, ctx: Context, - realEventType: CheckUndefined, + realEventType: GetSafeRealEventTypes, eventData: EventData[keyof EventData], reportData?: | AdapterReportData @@ -169,7 +159,7 @@ export abstract class BaseAdapter< ); if (!isTrackable) { - ctx.logger?.warn(`Adapter is not trackable: ${adapterName}`); + ctx.logger?.warn(`Adapter ${adapterName}: is not trackable`); return; } @@ -198,7 +188,8 @@ export abstract class BaseAdapter< await pipe( async () => await executeFunction(this.beforeHook, ctx, eventType, eventData), - async () => await this.executeTransform(ctx, eventType, eventData), + async () => + await this.executeTransform(adapterName, ctx, eventType, eventData), async ({ reportData, realEventType }) => { await this.executeReport( adapterName, diff --git a/src/types/types-adapter.ts b/src/types/types-adapter.ts index c73d384..86576e1 100644 --- a/src/types/types-adapter.ts +++ b/src/types/types-adapter.ts @@ -22,7 +22,7 @@ export type TransformEventData = ? EventData[Key[0]] : never; -export type CheckUndefined = +export type GetSafeRealEventTypes = RealEventData extends undefined ? keyof EventData : keyof RealEventData; /** @@ -31,7 +31,7 @@ export type CheckUndefined = export type AdapterReportData< RealEventData, EventData, - EventType = CheckUndefined, + EventType = GetSafeRealEventTypes, > = EventType extends keyof EventData ? EventData[EventType] : EventType extends keyof RealEventData @@ -59,7 +59,7 @@ export type AdapterBeforeFunction = ( */ export type AdapterAfterFunction = ( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData?: | AdapterReportData | Awaited> @@ -136,7 +136,9 @@ export interface TrackAdapter< * it uses data from `EventData`; otherwise, it uses data from `RealEventData`. * @returns A boolean or a promise that resolves to a boolean indicating whether the event is trackable. */ - isTrackable>( + isTrackable< + EventType extends GetSafeRealEventTypes, + >( ctx: Context, eventType: EventType, reportData?: diff --git a/src/types/types-create.ts b/src/types/types-create.ts index a71cddb..6abb0f0 100644 --- a/src/types/types-create.ts +++ b/src/types/types-create.ts @@ -1,4 +1,4 @@ -import { CheckUndefined } from './types-adapter.js'; +import { GetSafeRealEventTypes } from './types-adapter.js'; import { TrackLogger } from './types-logger.js'; import { TrackEventDataBase } from './types-track.js'; @@ -64,7 +64,7 @@ export type TrackAdapterOptions< */ setup?: ( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, eventData: EventData[EventType] ) => any | Promise; }; diff --git a/src/types/types-track.ts b/src/types/types-track.ts index db5237f..03a1c23 100644 --- a/src/types/types-track.ts +++ b/src/types/types-track.ts @@ -58,5 +58,5 @@ export type TrackAdapterMap< Context extends TrackContext, EventData extends TrackEventDataBase, > = { - [name: string]: TrackAdapter; + [name: string]: TrackAdapter; }; diff --git a/tests/test-real-transform.spec.ts b/tests/test-real-transform.spec.ts index 208a50d..a67586d 100644 --- a/tests/test-real-transform.spec.ts +++ b/tests/test-real-transform.spec.ts @@ -1,4 +1,5 @@ import { createAdapterBuilder } from '../src/adapter/create-adapter-builder.js'; +import { createTrackBuilder } from '../src/index.js'; import { TrackContext } from '../src/types/types-create.js'; import { AnalyzerAdapter } from './test-utils/adapter/analyzer-adapter.js'; import { ConsoleLogger } from './test-utils/console-logger.js'; @@ -331,6 +332,11 @@ describe('test-real-transform.spec', () => { eventData.timeStamp ); + const trackBuilder = createTrackBuilder< + TrackContext, + EventDataOption + >(); + expect(isTrackableFun.mock.lastCall).toBeDefined(); expect(isTrackableFun.mock.lastCall?.[0]).toMatchObject({ data: trackData, @@ -358,15 +364,7 @@ describe('test-real-transform.spec', () => { analyzerAdapter.testIsEventOfReportDataEqual( '_registry', eventData?.registry, - ['_registry'] - ) - ).toBeTruthy(); - - expect( - analyzerAdapter.testIsEventOfReportDataEqual( - '_registry', - eventData?.registry, - ['_addCart'] + '_addCart' ) ).toBe(false); }); diff --git a/tests/test-track-execute-select.spec.ts b/tests/test-track-execute-select.spec.ts index beefe55..4eb2697 100644 --- a/tests/test-track-execute-select.spec.ts +++ b/tests/test-track-execute-select.spec.ts @@ -247,16 +247,16 @@ describe('test-track-execute-select.spec', () => { expect(print.mock.calls).toHaveLength(4); expect(print.mock.results).toHaveLength(4); expect(print.mock.results?.[0].value).toBe( - 'Adapter is not trackable: analyzerAdapter' + 'Adapter analyzerAdapter: is not trackable' ); expect(print.mock.results?.[1].value).toBe( - 'Adapter is not trackable: reportAdapter' + 'Adapter reportAdapter: is not trackable' ); expect(print.mock.results?.[2].value).toBe( - 'Adapter is not trackable: logAdapter' + 'Adapter logAdapter: is not trackable' ); expect(print.mock.results?.[3].value).toBe( - 'Adapter is not trackable: businessAdapter' + 'Adapter businessAdapter: is not trackable' ); }); }); diff --git a/tests/test-track-parallel-pipeline.spec.ts b/tests/test-track-parallel-pipeline.spec.ts index c9f5d5a..16fe773 100644 --- a/tests/test-track-parallel-pipeline.spec.ts +++ b/tests/test-track-parallel-pipeline.spec.ts @@ -129,7 +129,7 @@ const mockAdapter = ( ): [ Mock<(ctx: any, reportData: any, setupData: any) => any>, Mock<(adapterName: string) => string>, - TrackAdapter, EventDataOption, any, EventDataOption>, + TrackAdapter, EventDataOption, any, any>, ] => { const reportAdapterBuilder = createAdapterBuilder< TrackContext, diff --git a/tests/test-utils/adapter/analyzer-adapter.ts b/tests/test-utils/adapter/analyzer-adapter.ts index b03d084..ebfaa6f 100644 --- a/tests/test-utils/adapter/analyzer-adapter.ts +++ b/tests/test-utils/adapter/analyzer-adapter.ts @@ -1,7 +1,7 @@ import { AdapterReportData, BaseAdapter, - CheckUndefined, + GetSafeRealEventTypes, } from '../../../src/index.js'; import { TrackContext } from '../../../src/types/types-create.js'; import { AdapterRealOptions } from '../types/type-adapter-options.js'; @@ -19,21 +19,21 @@ export class AnalyzerAdapter extends BaseAdapter< RealEventDataOption > { testIsEventOfReportDataEqual< - EventType extends - | CheckUndefined - | CheckUndefined[], + EventType extends GetSafeRealEventTypes< + RealEventDataOption, + EventDataOption + >, >( - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData: | RealEventDataOption[keyof RealEventDataOption] | EventDataOption[keyof EventDataOption], realEventType: EventType - ): reportData is EventType extends CheckUndefined< + ): reportData is AdapterReportData< RealEventDataOption, - EventDataOption - >[] - ? AdapterReportData - : AdapterReportData { + EventDataOption, + EventType + > { return this.isEventOfReportDataEqual(eventType, reportData, realEventType); } @@ -47,9 +47,7 @@ export class AnalyzerAdapter extends BaseAdapter< > | undefined ): boolean | Promise { - return !this.isEventOfReportDataEqual(eventType, reportData, [ - '_timeStamp', - ]); + return !this.isEventOfReportDataEqual(eventType, reportData, '_timeStamp'); } report( diff --git a/tests/test-utils/types/type-adapter-options.ts b/tests/test-utils/types/type-adapter-options.ts index 9506e45..6e0df1b 100644 --- a/tests/test-utils/types/type-adapter-options.ts +++ b/tests/test-utils/types/type-adapter-options.ts @@ -1,4 +1,4 @@ -import { CheckUndefined } from '../../../src/index.js'; +import { GetSafeRealEventTypes } from '../../../src/index.js'; export interface AdapterOptions { setup?: ( @@ -15,7 +15,7 @@ export interface AdapterOptions { export type AdapterRealOptions = { setup?: ( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, eventData: EventData[EventType] ) => { name: 'setup' | 'setup1' | 'setup2'; diff --git a/website/docs/api/adapter-builder.md b/website/docs/api/adapter-builder.md index 10a7560..7ffb458 100644 --- a/website/docs/api/adapter-builder.md +++ b/website/docs/api/adapter-builder.md @@ -189,7 +189,7 @@ The `after` hook is executed after the event has been reported. This is where yo adapterBuilder.after( ( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData?: | AdapterReportData | Awaited> diff --git a/website/docs/api/base-adapter.md b/website/docs/api/base-adapter.md index 1d5b1cf..f43b6c1 100644 --- a/website/docs/api/base-adapter.md +++ b/website/docs/api/base-adapter.md @@ -20,18 +20,20 @@ export abstract class BaseAdapter< > implements TrackAdapter { abstract isTrackable< - EventType extends CheckUndefined, + EventType extends GetSafeRealEventTypes, >( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData?: | AdapterReportData | Awaited> ): boolean | Promise; - protected report>( + protected report< + EventType extends GetSafeRealEventTypes, + >( ctx: Context, - eventType: CheckUndefined, + eventType: GetSafeRealEventTypes, reportData?: | AdapterReportData | Awaited>,