From dc2c5ebf5d3fc6b3ae048d96f849ab439c0914e1 Mon Sep 17 00:00:00 2001 From: Tom Mayel Date: Wed, 25 Oct 2023 15:23:55 +0100 Subject: [PATCH 1/2] feat(extension): posthog lace version lw-8778 --- .../analyticsTracker/AnalyticsTracker.test.ts | 46 +++--- .../analyticsTracker/types.ts | 1 + .../client/PostHogClient.test.ts | 138 ++++++++++++------ .../client/PostHogClient.ts | 60 ++++---- .../PostHogClientProvider/context.tsx | 13 +- 5 files changed, 157 insertions(+), 101 deletions(-) diff --git a/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/AnalyticsTracker.test.ts b/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/AnalyticsTracker.test.ts index 7be9fbdc7..e97b23c08 100644 --- a/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/AnalyticsTracker.test.ts +++ b/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/AnalyticsTracker.test.ts @@ -29,13 +29,13 @@ const mockBackgroundServiceUtils = { }; const getPostHogClient = (view = ExtensionViews.Extended) => - new PostHogClient( - Wallet.Cardano.ChainIds.Preprod, + new PostHogClient({ + chain: Wallet.Cardano.ChainIds.Preprod, // eslint-disable-next-line @typescript-eslint/no-explicit-any - userIdServiceMock as any, - mockBackgroundServiceUtils, + userIdService: userIdServiceMock as any, + backgroundServiceUtils: mockBackgroundServiceUtils, view - ); + }); describe('AnalyticsTracker', () => { const preprodChain = Wallet.Cardano.ChainIds.Preprod; @@ -50,12 +50,12 @@ describe('AnalyticsTracker', () => { new AnalyticsTracker({ chain: preprodChain, postHogClient: getPostHogClient() }); expect(getUserIdService).toHaveBeenCalledTimes(1); expect(MatomoClient).toHaveBeenCalledWith(preprodChain, userIdServiceMock); - expect(PostHogClient).toHaveBeenCalledWith( - preprodChain, - userIdServiceMock, - mockBackgroundServiceUtils, - ExtensionViews.Extended - ); + expect(PostHogClient).toHaveBeenCalledWith({ + chain: preprodChain, + userIdService: userIdServiceMock, + backgroundServiceUtils: mockBackgroundServiceUtils, + view: ExtensionViews.Extended + }); }); it('should only setup matomo client if posthog is disabled', () => { // eslint-disable-next-line no-new @@ -78,12 +78,12 @@ describe('AnalyticsTracker', () => { view: ExtensionViews.Popup, postHogClient: getPostHogClient(ExtensionViews.Popup) }); - expect(PostHogClient).toHaveBeenCalledWith( - preprodChain, - userIdServiceMock, - mockBackgroundServiceUtils, - ExtensionViews.Popup - ); + expect(PostHogClient).toHaveBeenCalledWith({ + chain: preprodChain, + userIdService: userIdServiceMock, + backgroundServiceUtils: mockBackgroundServiceUtils, + view: ExtensionViews.Popup + }); }); it('should setup Post Hog client with view = extended', () => { // eslint-disable-next-line no-new @@ -92,12 +92,12 @@ describe('AnalyticsTracker', () => { view: ExtensionViews.Extended, postHogClient: getPostHogClient() }); - expect(PostHogClient).toHaveBeenCalledWith( - preprodChain, - userIdServiceMock, - mockBackgroundServiceUtils, - ExtensionViews.Extended - ); + expect(PostHogClient).toHaveBeenCalledWith({ + chain: preprodChain, + userIdService: userIdServiceMock, + backgroundServiceUtils: mockBackgroundServiceUtils, + view: ExtensionViews.Extended + }); }); }); diff --git a/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/types.ts b/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/types.ts index cdabf1e92..bd7bb0ee4 100644 --- a/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/types.ts +++ b/apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/types.ts @@ -263,6 +263,7 @@ export type PostHogMetadata = { view: ExtensionViews; sent_at_local: string; posthog_project_id: number; + lace_version?: string; } & PostHogPersonProperties; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.test.ts b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.test.ts index f5ed0746b..8bb73dd87 100644 --- a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.test.ts +++ b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.test.ts @@ -16,7 +16,7 @@ const mockUserTrackingType$ = new BehaviorSubject(UserTracking jest.mock('posthog-js'); describe('PostHogClient', () => { - const publicPosthogHost = 'test'; + const publicPostHogHost = 'test'; const chain = Wallet.Cardano.ChainIds.Preprod; const userId = 'userId'; const mockUserIdService: UserIdService = { @@ -31,20 +31,30 @@ describe('PostHogClient', () => { it('should initialize posthog on construction', async () => { // eslint-disable-next-line no-new - const client = new PostHogClient(chain, mockUserIdService, mockBackgroundStorageUtil, undefined, publicPosthogHost); + const client = new PostHogClient({ + chain, + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + publicPostHogHost + }); await waitFor(() => expect(client).toBeDefined()); expect(posthog.init).toHaveBeenCalledWith( expect.stringContaining(DEV_NETWORK_ID_TO_POSTHOG_TOKEN_MAP[chain.networkMagic]), expect.objectContaining({ // eslint-disable-next-line camelcase - api_host: publicPosthogHost + api_host: publicPostHogHost }) ); }); it('should send page navigation events with distinct id and view = extended as default', async () => { - const client = new PostHogClient(chain, mockUserIdService, mockBackgroundStorageUtil, undefined, publicPosthogHost); + const client = new PostHogClient({ + chain, + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + publicPostHogHost + }); await client.sendPageNavigationEvent(); expect(posthog.capture).toHaveBeenCalledWith( '$pageview', @@ -57,7 +67,12 @@ describe('PostHogClient', () => { }); it('should send events with distinct id', async () => { - const client = new PostHogClient(chain, mockUserIdService, mockBackgroundStorageUtil, undefined, publicPosthogHost); + const client = new PostHogClient({ + chain, + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + publicPostHogHost + }); const event = PostHogAction.OnboardingCreateClick; const extraProps = { some: 'prop', another: 'test' }; @@ -75,7 +90,12 @@ describe('PostHogClient', () => { it('should be possible to change the chain', () => { const previewChain = Wallet.Cardano.ChainIds.Preview; - const client = new PostHogClient(chain, mockUserIdService, mockBackgroundStorageUtil, undefined, publicPosthogHost); + const client = new PostHogClient({ + chain, + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + publicPostHogHost + }); expect(posthog.set_config).not.toHaveBeenCalled(); client.setChain(previewChain); expect(posthog.set_config).toHaveBeenCalledWith( @@ -86,13 +106,13 @@ describe('PostHogClient', () => { }); it('should send events with property view = popup', async () => { - const client = new PostHogClient( + const client = new PostHogClient({ chain, - mockUserIdService, - mockBackgroundStorageUtil, - ExtensionViews.Popup, - publicPosthogHost - ); + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Popup, + publicPostHogHost + }); const event = PostHogAction.OnboardingCreateClick; await client.sendEvent(event); @@ -106,13 +126,13 @@ describe('PostHogClient', () => { }); it('should send events with property view = extended', async () => { - const client = new PostHogClient( + const client = new PostHogClient({ chain, - mockUserIdService, - mockBackgroundStorageUtil, - ExtensionViews.Extended, - publicPosthogHost - ); + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost + }); const event = PostHogAction.OnboardingCreateClick; await client.sendEvent(event); @@ -125,15 +145,37 @@ describe('PostHogClient', () => { ); }); + it('should send events with property lace_version = 1.2.3', async () => { + const client = new PostHogClient({ + chain, + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost, + laceVersion: '1.2.3' + }); + const event = PostHogAction.OnboardingCreateClick; + + await client.sendEvent(event); + + expect(posthog.capture).toHaveBeenCalledWith( + event, + expect.objectContaining({ + // eslint-disable-next-line camelcase + lace_version: '1.2.3' + }) + ); + }); + it('should send events with property sent at local', async () => { jest.useFakeTimers().setSystemTime(mockSentDate); - const client = new PostHogClient( + const client = new PostHogClient({ chain, - mockUserIdService, - mockBackgroundStorageUtil, - ExtensionViews.Extended, - publicPosthogHost - ); + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost + }); const event = PostHogAction.OnboardingCreateClick; await client.sendEvent(event); @@ -150,39 +192,39 @@ describe('PostHogClient', () => { it('should send alias event if alias and id properties are defined', async () => { const mockAliasProperties = { id: 'walletBasedId', alias: 'aliasId' }; const mockGetAliasProperties = jest.fn().mockReturnValue(mockAliasProperties); - const client = new PostHogClient( + const client = new PostHogClient({ chain, - { ...mockUserIdService, getAliasProperties: mockGetAliasProperties }, - mockBackgroundStorageUtil, - ExtensionViews.Extended, - publicPosthogHost - ); + userIdService: { ...mockUserIdService, getAliasProperties: mockGetAliasProperties }, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost + }); await client.sendAliasEvent(); expect(posthog.alias).toHaveBeenCalledWith(mockAliasProperties.alias, mockAliasProperties.id); }); it('should not send alias event if alias or id properties are not defined', async () => { const mockGetAliasProperties = jest.fn().mockReturnValue({}); - const client = new PostHogClient( + const client = new PostHogClient({ chain, - { ...mockUserIdService, getAliasProperties: mockGetAliasProperties }, - mockBackgroundStorageUtil, - ExtensionViews.Extended, - publicPosthogHost - ); + userIdService: { ...mockUserIdService, getAliasProperties: mockGetAliasProperties }, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost + }); await client.sendAliasEvent(); expect(posthog.alias).not.toHaveBeenCalled(); }); it('should return user_tracking_type enhanced', async () => { const event = PostHogAction.OnboardingCreateClick; - const client = new PostHogClient( + const client = new PostHogClient({ chain, - mockUserIdService, - mockBackgroundStorageUtil, - ExtensionViews.Extended, - publicPosthogHost - ); + userIdService: mockUserIdService, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost + }); mockUserIdService.userTrackingType$.next(UserTrackingType.Enhanced); await client.sendEvent(event); expect(posthog.capture).toHaveBeenCalledWith( @@ -200,13 +242,13 @@ describe('PostHogClient', () => { it('should return user_tracking_type basic after calling twice', async () => { const event = PostHogAction.OnboardingCreateClick; const tracking = new BehaviorSubject(UserTrackingType.Enhanced); - const client = new PostHogClient( + const client = new PostHogClient({ chain, - { ...mockUserIdService, userTrackingType$: tracking }, - mockBackgroundStorageUtil, - ExtensionViews.Extended, - publicPosthogHost - ); + userIdService: { ...mockUserIdService, userTrackingType$: tracking }, + backgroundServiceUtils: mockBackgroundStorageUtil, + view: ExtensionViews.Extended, + publicPostHogHost + }); await client.sendEvent(event); expect(posthog.capture).toHaveBeenCalledWith( diff --git a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts index 804e21420..caac14b1e 100644 --- a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts +++ b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts @@ -28,19 +28,44 @@ import { Subscription, BehaviorSubject } from 'rxjs'; * PostHog API reference: * https://posthog.com/docs/libraries/js */ + +export interface PostHogInstance { + chain: Wallet.Cardano.ChainId; + userIdService: UserIdService; + backgroundServiceUtils: Pick; + laceVersion?: string; + view?: ExtensionViews; + publicPostHogHost?: string; +} + export class PostHogClient { protected static postHogClientInstance: PostHogClient; private userTrackingType: UserTrackingType; private currentUserTrackingType?: UserTrackingType; private hasPostHogInitialized$: BehaviorSubject; - constructor( - private chain: Wallet.Cardano.ChainId, - private userIdService: UserIdService, - private backgroundServiceUtils: Pick, - private view: ExtensionViews = ExtensionViews.Extended, - private publicPostHogHost: string = PUBLIC_POSTHOG_HOST - ) { + private chain: Wallet.Cardano.ChainId; + private userIdService: UserIdService; + private backgroundServiceUtils: Pick; + private view: ExtensionViews = ExtensionViews.Extended; + private publicPostHogHost: string = PUBLIC_POSTHOG_HOST; + private laceVersion: string; + + constructor({ + chain, + userIdService, + backgroundServiceUtils, + laceVersion = '', + view = ExtensionViews.Extended, + publicPostHogHost = PUBLIC_POSTHOG_HOST + }: PostHogInstance) { + this.chain = chain; + this.userIdService = userIdService; + this.backgroundServiceUtils = backgroundServiceUtils; + this.view = view; + this.publicPostHogHost = publicPostHogHost; + this.laceVersion = laceVersion; + if (!this.publicPostHogHost) throw new Error('PUBLIC_POSTHOG_HOST url has not been provided'); const token = this.getApiToken(this.chain); if (!token) throw new Error('posthog token has not been provided'); @@ -81,25 +106,9 @@ export class PostHogClient { this.subscribeToDistinctIdUpdate(); } - static getInstance( - chain: Wallet.Cardano.ChainId, - userIdService: UserIdService, - { - getBackgroundStorage, - setBackgroundStorage - }: Pick, - view?: ExtensionViews - ): PostHogClient { + static getInstance(params: PostHogInstance): PostHogClient { if (this.postHogClientInstance || !POSTHOG_ENABLED) return this.postHogClientInstance; - this.postHogClientInstance = new PostHogClient( - chain, - userIdService, - { - getBackgroundStorage, - setBackgroundStorage - }, - view - ); + this.postHogClientInstance = new PostHogClient(params); return this.postHogClientInstance; } @@ -223,6 +232,7 @@ export class PostHogClient { sent_at_local: dayjs().format(), distinct_id: await this.userIdService.getUserId(this.chain.networkMagic), posthog_project_id: this.getProjectId(), + lace_version: this.laceVersion, ...(await this.getPersonProperties()) }; } diff --git a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/context.tsx b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/context.tsx index 59a2b2cb2..354192a81 100644 --- a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/context.tsx +++ b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/context.tsx @@ -5,6 +5,7 @@ import { PostHogClient } from './client'; import { getUserIdService } from '@providers/AnalyticsProvider/getUserIdService'; import { ExtensionViews } from '@providers/AnalyticsProvider/analyticsTracker'; import { useBackgroundServiceAPIContext } from '@providers/BackgroundServiceAPI'; +import { runtime } from 'webextension-polyfill'; const userIdService = getUserIdService(); @@ -28,16 +29,18 @@ export const PostHogClientProvider = ({ children, postHogCustomClient }: PostHog (state) => ({ currentChain: state?.currentChain, view: state.walletUI.appMode }), shallow ); + const laceVersion = runtime?.getManifest?.().version; const postHogClientInstance = useMemo( () => postHogCustomClient || - PostHogClient.getInstance( - currentChain, + PostHogClient.getInstance({ + chain: currentChain, userIdService, - { getBackgroundStorage, setBackgroundStorage }, - view === 'popup' ? ExtensionViews.Popup : ExtensionViews.Extended - ), + backgroundServiceUtils: { getBackgroundStorage, setBackgroundStorage }, + view: view === 'popup' ? ExtensionViews.Popup : ExtensionViews.Extended, + laceVersion + }), [currentChain, getBackgroundStorage, postHogCustomClient, setBackgroundStorage, view] ); From 43090d55dfa488c938384a21437929b91045c809 Mon Sep 17 00:00:00 2001 From: Tom Mayel Date: Tue, 31 Oct 2023 11:56:03 +0000 Subject: [PATCH 2/2] feat(extension): rename a typescript interface --- .../providers/PostHogClientProvider/client/PostHogClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts index caac14b1e..77a4441a8 100644 --- a/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts +++ b/apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts @@ -29,7 +29,7 @@ import { Subscription, BehaviorSubject } from 'rxjs'; * https://posthog.com/docs/libraries/js */ -export interface PostHogInstance { +export interface PostHogClientParams { chain: Wallet.Cardano.ChainId; userIdService: UserIdService; backgroundServiceUtils: Pick; @@ -58,7 +58,7 @@ export class PostHogClient { laceVersion = '', view = ExtensionViews.Extended, publicPostHogHost = PUBLIC_POSTHOG_HOST - }: PostHogInstance) { + }: PostHogClientParams) { this.chain = chain; this.userIdService = userIdService; this.backgroundServiceUtils = backgroundServiceUtils; @@ -106,7 +106,7 @@ export class PostHogClient { this.subscribeToDistinctIdUpdate(); } - static getInstance(params: PostHogInstance): PostHogClient { + static getInstance(params: PostHogClientParams): PostHogClient { if (this.postHogClientInstance || !POSTHOG_ENABLED) return this.postHogClientInstance; this.postHogClientInstance = new PostHogClient(params); return this.postHogClientInstance;