Skip to content

Commit 3478c41

Browse files
feat: add toggle on settings for beta program opt in (#1451)
1 parent 603b620 commit 3478c41

File tree

8 files changed

+122
-43
lines changed

8 files changed

+122
-43
lines changed

apps/browser-extension-wallet/src/lib/scripts/types/storage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface BackgroundStorage {
3131
usePersistentUserId?: boolean;
3232
featureFlags?: Record<number, Record<ExperimentName, string | boolean>>;
3333
customSubmitTxUrl?: string;
34+
optedInBeta?: boolean;
3435
}
3536

3637
export type BackgroundStorageKeys = keyof BackgroundStorage;

apps/browser-extension-wallet/src/providers/AnalyticsProvider/analyticsTracker/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export enum TxCreationType {
3333
export type PostHogPersonProperties = {
3434
$set: {
3535
user_tracking_type: UserTrackingType;
36+
opted_in_beta: boolean;
3637
};
3738
};
3839
export type PostHogMetadata = {

apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import { BehaviorSubject, ReplaySubject } from 'rxjs';
1010
import { waitFor } from '@testing-library/react';
1111

1212
const mockSentDate = new Date('2023-07-25T15:31:10.275000+00:00');
13-
const mockBackgroundStorageUtil = { getBackgroundStorage: jest.fn(), setBackgroundStorage: jest.fn() };
13+
const mockBackgroundStorageUtil = {
14+
getBackgroundStorage: jest.fn(() => Promise.resolve({})),
15+
setBackgroundStorage: jest.fn()
16+
};
1417
const mockUserId$ = new ReplaySubject<UserId>();
1518

1619
jest.mock('posthog-js');
@@ -205,6 +208,8 @@ describe('PostHogClient', () => {
205208
event,
206209
expect.objectContaining({
207210
$set: {
211+
// eslint-disable-next-line camelcase
212+
opted_in_beta: false,
208213
// eslint-disable-next-line camelcase
209214
user_tracking_type: 'enhanced'
210215
}
@@ -232,6 +237,8 @@ describe('PostHogClient', () => {
232237
event,
233238
expect.objectContaining({
234239
$set: {
240+
// eslint-disable-next-line camelcase
241+
opted_in_beta: false,
235242
// eslint-disable-next-line camelcase
236243
user_tracking_type: 'enhanced'
237244
}
@@ -246,6 +253,8 @@ describe('PostHogClient', () => {
246253
event,
247254
expect.objectContaining({
248255
$set: {
256+
// eslint-disable-next-line camelcase
257+
opted_in_beta: false,
249258
// eslint-disable-next-line camelcase
250259
user_tracking_type: 'basic'
251260
}

apps/browser-extension-wallet/src/providers/PostHogClientProvider/client/PostHogClient.ts

Lines changed: 68 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable camelcase, max-depth, sonarjs/cognitive-complexity */
1+
/* eslint-disable camelcase, max-depth, sonarjs/cognitive-complexity, promise/no-nesting */
22
import posthog, { JsonRecord, JsonType } from 'posthog-js';
33
import dayjs from 'dayjs';
44
import { Wallet } from '@lace/cardano';
@@ -22,7 +22,7 @@ import {
2222
import { BackgroundService, UserIdService } from '@lib/scripts/types';
2323
import { experiments, getDefaultFeatureFlags } from '@providers/ExperimentsProvider/config';
2424
import { ExperimentName } from '@providers/ExperimentsProvider/types';
25-
import { BehaviorSubject, Subscription } from 'rxjs';
25+
import { BehaviorSubject, distinctUntilChanged, Observable, Subscription } from 'rxjs';
2626
import { PostHogAction, PostHogProperties } from '@lace/common';
2727

2828
type FeatureFlag = 'create-paper-wallet' | 'restore-paper-wallet' | 'shared-wallets';
@@ -46,6 +46,8 @@ export class PostHogClient<Action extends string = string> {
4646
private hasPostHogInitialized$: BehaviorSubject<boolean>;
4747
private subscription: Subscription;
4848
private initSuccess: Promise<boolean>;
49+
private readonly optedInBeta$: BehaviorSubject<boolean>;
50+
private updatePersonaProperties = false;
4951
featureFlags: GroupedFeatureFlags;
5052
constructor(
5153
private chain: Wallet.Cardano.ChainId,
@@ -58,45 +60,52 @@ export class PostHogClient<Action extends string = string> {
5860
const token = this.getApiToken();
5961
if (!token) throw new Error(`posthog token has not been provided for chain: ${this.chain.networkId}`);
6062
this.hasPostHogInitialized$ = new BehaviorSubject(false);
61-
62-
this.initSuccess = this.userIdService
63-
.getUserId(chain.networkMagic)
64-
.then((id) => {
65-
posthog.init(token, {
66-
request_batching: false,
67-
api_host: this.postHogHost,
68-
autocapture: false,
69-
opt_out_useragent_filter: true,
70-
disable_compression: true,
71-
disable_session_recording: true,
72-
capture_pageview: false,
73-
capture_pageleave: false,
74-
// Disables PostHog user ID persistence - we manage ID ourselves with userIdService
75-
disable_persistence: true,
76-
disable_cookie: true,
77-
persistence: 'memory',
78-
bootstrap: {
79-
distinctID: id,
80-
isIdentifiedID: true
81-
},
82-
property_blacklist: [
83-
'$autocapture_disabled_server_side',
84-
'$console_log_recording_enabled_server_side',
85-
'$device_id',
86-
'$session_recording_recorder_version_server_side',
87-
'$time'
88-
]
89-
});
63+
this.optedInBeta$ = new BehaviorSubject(false);
64+
65+
this.backgroundServiceUtils
66+
.getBackgroundStorage()
67+
.then((storage) => {
68+
this.optedInBeta$.next(storage?.optedInBeta ?? false);
69+
70+
this.initSuccess = this.userIdService
71+
.getUserId(chain.networkMagic)
72+
.then((id) => {
73+
posthog.init(token, {
74+
request_batching: false,
75+
api_host: this.postHogHost,
76+
autocapture: false,
77+
opt_out_useragent_filter: true,
78+
disable_compression: true,
79+
disable_session_recording: true,
80+
capture_pageview: false,
81+
capture_pageleave: false,
82+
// Disables PostHog user ID persistence - we manage ID ourselves with userIdService
83+
disable_persistence: true,
84+
disable_cookie: true,
85+
persistence: 'memory',
86+
bootstrap: {
87+
distinctID: id,
88+
isIdentifiedID: true
89+
},
90+
property_blacklist: [
91+
'$autocapture_disabled_server_side',
92+
'$console_log_recording_enabled_server_side',
93+
'$device_id',
94+
'$session_recording_recorder_version_server_side',
95+
'$time'
96+
]
97+
});
98+
})
99+
.then(() => true);
100+
101+
this.subscribeToDistinctIdUpdate();
102+
this.loadExperiments();
90103
})
91-
.then(() => true)
92104
.catch(() => {
93105
console.warn('Analytics failed');
94106
return false;
95107
});
96108

97-
this.subscribeToDistinctIdUpdate();
98-
this.loadExperiments();
99-
100109
this.featureFlags = {
101110
[Wallet.Cardano.NetworkMagics.Mainnet]: getDefaultFeatureFlags(),
102111
[Wallet.Cardano.NetworkMagics.Preprod]: getDefaultFeatureFlags(),
@@ -203,6 +212,21 @@ export class PostHogClient<Action extends string = string> {
203212
});
204213
}
205214

215+
hasOptedInBeta(): Observable<boolean> {
216+
return this.optedInBeta$.pipe(distinctUntilChanged());
217+
}
218+
219+
async setOptedInBeta(optedInBeta: boolean): Promise<void> {
220+
await this.backgroundServiceUtils.setBackgroundStorage({
221+
optedInBeta
222+
});
223+
224+
this.updatePersonaProperties = true;
225+
this.optedInBeta$.next(optedInBeta);
226+
227+
console.debug('[ANALYTICS] Changing Opted In Beta', optedInBeta);
228+
}
229+
206230
isFeatureFlagEnabled(feature: string): boolean {
207231
return this.featureFlags[this.chain.networkMagic]?.[feature as FeatureFlag] || false;
208232
}
@@ -281,6 +305,8 @@ export class PostHogClient<Action extends string = string> {
281305
backgroundStorage?.featureFlags[this.chain.networkMagic] || getDefaultFeatureFlags()
282306
);
283307
}
308+
309+
this.optedInBeta$.next(backgroundStorage?.optedInBeta);
284310
});
285311
this.hasPostHogInitialized$.next(true);
286312
}
@@ -307,15 +333,17 @@ export class PostHogClient<Action extends string = string> {
307333
protected async getPersonProperties(): Promise<PostHogPersonProperties | undefined> {
308334
if (!this.userTrackingType) {
309335
this.userTrackingType = this.currentUserTrackingType;
310-
// set user_tracking_type in the first event
311-
return { $set: { user_tracking_type: this.userTrackingType } };
336+
// set user_tracking_type and opted_in_beta in the first event
337+
return { $set: { user_tracking_type: this.userTrackingType, opted_in_beta: this.optedInBeta$.value } };
312338
}
313339

314340
// eslint-disable-next-line consistent-return
315-
if (this.currentUserTrackingType === this.userTrackingType) return;
341+
if (this.currentUserTrackingType === this.userTrackingType && !this.updatePersonaProperties) return;
342+
343+
this.updatePersonaProperties = false;
316344
this.userTrackingType = this.currentUserTrackingType;
317-
// update user_tracking_type if tracking type has changed
318-
return { $set: { user_tracking_type: this.userTrackingType } };
345+
346+
return { $set: { user_tracking_type: this.userTrackingType, opted_in_beta: this.optedInBeta$.value } };
319347
}
320348

321349
static getAllowedNetworksMapFromPayloads(payloads: Record<string, JsonType>): Record<string, string[]> {

apps/browser-extension-wallet/src/utils/mocks/test-helpers.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,9 @@ export const postHogClientMocks: Record<keyof typeof PostHogClient.prototype, je
646646
sendSessionStartEvent: jest.fn(),
647647
sendMergeEvent: jest.fn(),
648648
isFeatureEnabled: jest.fn(),
649-
featureFlags: jest.fn()
649+
featureFlags: jest.fn(),
650+
hasOptedInBeta: jest.fn(),
651+
setOptedInBeta: jest.fn()
650652
};
651653

652654
export const mockAnalyticsTracker: Record<keyof typeof AnalyticsTracker.prototype, jest.Mock> = {

apps/browser-extension-wallet/src/views/browser-view/features/settings/components/SettingsPreferences/SettingsPreferences.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { SettingsCard, SettingsLink } from '..';
33
import { useTranslation } from 'react-i18next';
44
import { Typography } from 'antd';
@@ -8,6 +8,8 @@ import { ThemeSwitch } from '@components/MainMenu/DropdownMenuOverlay/components
88
import { CurrencyDrawer } from './CurrencyDrawer';
99
import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker';
1010
import { currencyCode } from '@providers/currency/constants';
11+
import { Switch } from '@lace/common';
12+
import { usePostHogClientContext } from '@providers/PostHogClientProvider';
1113

1214
const { Title } = Typography;
1315

@@ -20,6 +22,18 @@ export const SettingsPreferences = ({ popupView = false }: SettingsPreferencesPr
2022
const [isCurrrencyChoiceDrawerOpen, setIsCurrrencyChoiceDrawerOpen] = useState(false);
2123
const { t } = useTranslation();
2224
const { fiatCurrency } = useCurrencyStore();
25+
const posthog = usePostHogClientContext();
26+
const [isOptInBeta, setIsOptInBeta] = useState(false);
27+
28+
useEffect(() => {
29+
const subscription = posthog.hasOptedInBeta().subscribe((optInStatus) => {
30+
setIsOptInBeta(optInStatus);
31+
});
32+
33+
return () => {
34+
subscription.unsubscribe();
35+
};
36+
}, [posthog]);
2337

2438
const handleOpenCurrencyDrawer = () => {
2539
setIsCurrrencyChoiceDrawerOpen(true);
@@ -34,6 +48,13 @@ export const SettingsPreferences = ({ popupView = false }: SettingsPreferencesPr
3448
const handleSendCurrencyChangeEvent = (currency: currencyCode) =>
3549
analytics.sendEventToPostHog(PostHogAction.SettingsCurrencySelectCurrencyClick, { currency });
3650

51+
const handleBetaOptInChoice = async (isOptedIn: boolean) => {
52+
await posthog.setOptedInBeta(isOptedIn);
53+
setIsOptInBeta(isOptedIn);
54+
55+
await analytics.sendEventToPostHog(PostHogAction.SettingsBetaProgramClick, { isOptedIn });
56+
};
57+
3758
return (
3859
<>
3960
<CurrencyDrawer
@@ -61,6 +82,20 @@ export const SettingsPreferences = ({ popupView = false }: SettingsPreferencesPr
6182
>
6283
{t('browserView.settings.preferences.theme.title')}
6384
</SettingsLink>
85+
<SettingsLink
86+
description={t('browserView.settings.preferences.betaProgram.description')}
87+
addon={
88+
<Switch
89+
testId="settings-beta-program-switch"
90+
checked={isOptInBeta}
91+
onChange={handleBetaOptInChoice}
92+
className={styles.analyticsSwitch}
93+
/>
94+
}
95+
data-testid="settings-beta-program-section"
96+
>
97+
{t('browserView.settings.preferences.betaProgram.title')}
98+
</SettingsLink>
6499
</SettingsCard>
65100
</>
66101
);

packages/common/src/analytics/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ export enum PostHogAction {
202202
SettingsCurrencyClick = 'settings | currency | click',
203203
SettingsCurrencySelectCurrencyClick = 'settings | currency | select currency | click',
204204
SettingsCurrencyXClick = 'settings | currency | x | click',
205+
SettingsBetaProgramClick = 'settings | beta program | click',
205206
SettingsThemeLightModeClick = 'settings | theme | light mode | click',
206207
SettingsThemeDarkModeClick = 'settings | theme | dark mode | click',
207208
SettingsShowRecoveryPhraseClick = 'settings | show recovery phrase | click',

packages/translation/src/lib/translations/browser-extension-wallet/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@
259259
"browserView.settings.preferences.theme.description": "Choose between light or dark for your workstation's theme",
260260
"browserView.settings.preferences.theme.title": "Theme",
261261
"browserView.settings.preferences.title": "Preferences",
262+
"browserView.settings.preferences.betaProgram.description": "Test out new features and help us improve the product experience",
263+
"browserView.settings.preferences.betaProgram.title": "Beta Program",
262264
"browserView.settings.security.2fa": "2-Factor authentication",
263265
"browserView.settings.security.analytics.description": "We'll collect anonymous analytics info from your browser extension to help us improve the quality and performance of Lace",
264266
"browserView.settings.security.analytics.title": "Analytics",

0 commit comments

Comments
 (0)