mirror of
https://github.com/placeholder-soft/web.git
synced 2026-01-12 17:02:57 +08:00
Feat: Implement Amplitude Experiment infra (#639)
* Added Amplitude Experiments Initialization to initCCA * create useVariant hook to pull experimental variants * refactored useVariant to create simpler interface * linted * automated defaultDeploymentKey logic * added defaultDeploymentKey for prod env * Created ExperimentsContext for web app * added more specificity to amplitude domains for CSP * additional properties on eventData * added amplitude deployment keys to constants * deleted unused useVariant hook * refactored experiment initialization in initCCA * refactored Experiments context * refactored usage of Experiments provider * refactored ampDeploymentKey logic * removed experiment initialization from initCCA * restored index page to prior state * refactored initCCA to pull constants into dedicated file * created Experiments context in base-docs * implemented experiments context in base-docs Root * moved Experiments context to libs * updated Experiments context integration in base-web * fixed import statement * deleted unused context in favor of shared version in libs * implemented shared experiments context * moved Experiments context to be innermost context provider in base-web * removed unused imports from initCCA * refactored Amplitude Experiments package into libs * fixed yarn issue * added type declaration for * refactored window type declaration
This commit is contained in:
committed by
GitHub
parent
ddcd2bc5db
commit
b6df0ab1cb
9
apps/base-docs/src/constants.ts
Normal file
9
apps/base-docs/src/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { customFields } from './utils/docusaurusCustomFields';
|
||||
|
||||
export const isDevelopment = customFields.nodeEnv === 'development';
|
||||
export const amplitudeApiKey = isDevelopment
|
||||
? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
|
||||
: '2b38c7ac93c0dccc83ebf9acc5107413';
|
||||
export const ampDeploymentKey = isDevelopment
|
||||
? 'client-Wvf63OdaukDZyCBtwgbOvHgGTuASBZFG'
|
||||
: 'client-agFoQg5AOvZ2ZiOChny9RrGk21jG3VrH';
|
||||
@@ -28,6 +28,7 @@ import { createClient } from 'viem';
|
||||
|
||||
import useSprig from 'base-ui/hooks/useSprig';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import ExperimentsProvider from 'base-ui/contexts/Experiments';
|
||||
|
||||
coinbaseWallet.preference = 'all';
|
||||
|
||||
@@ -204,18 +205,20 @@ export default function Root({ children }) {
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<CookieManagerProvider
|
||||
projectName="base_docs"
|
||||
locale="en"
|
||||
region={Region.DEFAULT}
|
||||
log={console.log}
|
||||
onError={handleLogError}
|
||||
onPreferenceChange={setTrackingPreference}
|
||||
config={cookieManagerConfig}
|
||||
>
|
||||
{children}
|
||||
<CookieBanner companyName="Base" link="/cookie-policy" theme={cookieBannerTheme} />
|
||||
</CookieManagerProvider>
|
||||
<ExperimentsProvider>
|
||||
<CookieManagerProvider
|
||||
projectName="base_docs"
|
||||
locale="en"
|
||||
region={Region.DEFAULT}
|
||||
log={console.log}
|
||||
onError={handleLogError}
|
||||
onPreferenceChange={setTrackingPreference}
|
||||
config={cookieManagerConfig}
|
||||
>
|
||||
{children}
|
||||
<CookieBanner companyName="Base" link="/cookie-policy" theme={cookieBannerTheme} />
|
||||
</CookieManagerProvider>
|
||||
</ExperimentsProvider>
|
||||
</RainbowKitProvider>
|
||||
</QueryClientProvider>
|
||||
</WagmiProvider>
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import { customFields } from './docusaurusCustomFields';
|
||||
import { setCookie, getCookie, deserializeCookie } from './cookieManagement';
|
||||
import { TrackingPreference } from '@coinbase/cookie-manager';
|
||||
import { isDevelopment, amplitudeApiKey } from '../constants';
|
||||
|
||||
const isDevelopment = customFields.nodeEnv === 'development';
|
||||
|
||||
// Initialize Client Analytics
|
||||
const initCCA = () => {
|
||||
@@ -31,9 +30,7 @@ const initCCA = () => {
|
||||
|
||||
init({
|
||||
isProd: !isDevelopment,
|
||||
amplitudeApiKey: isDevelopment
|
||||
? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
|
||||
: '2b38c7ac93c0dccc83ebf9acc5107413',
|
||||
amplitudeApiKey: amplitudeApiKey,
|
||||
platform: PlatformName.web,
|
||||
projectName: 'base_docs',
|
||||
showDebugLogging: isDevelopment,
|
||||
|
||||
@@ -89,6 +89,8 @@ const contentSecurityPolicy = {
|
||||
'https://api.opensea.io', // enables getting ENS avatars
|
||||
isLocalDevelopment ? 'ws://localhost:3000/' : '',
|
||||
isLocalDevelopment ? 'http://localhost:3000/' : '',
|
||||
'https://flag.lab.amplitude.com/sdk/v2/flags',
|
||||
'https://api.lab.amplitude.com/sdk/v2/vardata',
|
||||
],
|
||||
'frame-ancestors': ["'self'", baseXYZDomains],
|
||||
'form-action': ["'self'", baseXYZDomains],
|
||||
|
||||
@@ -25,6 +25,7 @@ import { base, baseSepolia, mainnet, sepolia } from 'wagmi/chains';
|
||||
import ClientAnalyticsScript from '../src/components/ClientAnalyticsScript/ClientAnalyticsScript';
|
||||
import { Layout } from '../src/components/Layout/Layout';
|
||||
import { cookieManagerConfig } from '../src/utils/cookieManagerConfig';
|
||||
import ExperimentsProvider from 'base-ui/contexts/Experiments';
|
||||
|
||||
coinbaseWallet.preference = 'all';
|
||||
|
||||
@@ -115,9 +116,11 @@ export default function StaticApp({ Component, pageProps }: AppProps) {
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RainbowKitProvider modalSize="compact">
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
<ExperimentsProvider>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</ExperimentsProvider>
|
||||
</RainbowKitProvider>
|
||||
</QueryClientProvider>
|
||||
</WagmiProvider>
|
||||
|
||||
@@ -7,3 +7,6 @@ export const mainnetLaunchBlogPostURL =
|
||||
process.env.MAINNET_LAUNCH_BLOG_POST_URL ?? 'https://base.mirror.xyz/';
|
||||
export const mainnetLaunchFlag = process.env.MAINNET_LAUNCH_FLAG ?? 'false';
|
||||
export const isDevelopment = nodeEnv === 'development';
|
||||
export const ampDeploymentKey = isDevelopment
|
||||
? 'client-Wvf63OdaukDZyCBtwgbOvHgGTuASBZFG'
|
||||
: 'client-agFoQg5AOvZ2ZiOChny9RrGk21jG3VrH';
|
||||
|
||||
@@ -16,6 +16,9 @@ const initCCA = (
|
||||
) => {
|
||||
let deviceId: string | undefined = deviceIdCookie;
|
||||
const trackingAllowed: boolean = trackingPreference?.consent.includes('performance');
|
||||
const amplitudeApiKey: string = isDevelopment
|
||||
? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
|
||||
: '2b38c7ac93c0dccc83ebf9acc5107413';
|
||||
|
||||
if (!trackingAllowed) {
|
||||
deviceId = 'base_web_device_id';
|
||||
@@ -29,9 +32,7 @@ const initCCA = (
|
||||
|
||||
init({
|
||||
isProd: !isDevelopment,
|
||||
amplitudeApiKey: isDevelopment
|
||||
? 'ca92bbcb548f7ec4b8ebe9194b8eda81'
|
||||
: '2b38c7ac93c0dccc83ebf9acc5107413',
|
||||
amplitudeApiKey,
|
||||
platform: PlatformName.web,
|
||||
projectName: 'base_web',
|
||||
showDebugLogging: isDevelopment,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"next-env.d.ts",
|
||||
"**/*",
|
||||
"**/*.json",
|
||||
"../../types/**/*"
|
||||
"../../types/**/*",
|
||||
],
|
||||
"exclude": [],
|
||||
"references": [
|
||||
|
||||
12
libs/base-ui/constants.ts
Normal file
12
libs/base-ui/constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const nodeEnv = process.env.NODE_ENV;
|
||||
export const docsUrl = process.env.DOCS_URL ?? 'https://docs.base.org';
|
||||
export const bridgeUrl = process.env.BRIDGE_URL ?? 'https://bridge.base.org';
|
||||
export const greenhouseApiUrl =
|
||||
process.env.GREENHOUSE_HTTPS ?? 'https://boards-api.greenhouse.io/v1';
|
||||
export const mainnetLaunchBlogPostURL =
|
||||
process.env.MAINNET_LAUNCH_BLOG_POST_URL ?? 'https://base.mirror.xyz/';
|
||||
export const mainnetLaunchFlag = process.env.MAINNET_LAUNCH_FLAG ?? 'false';
|
||||
export const isDevelopment = nodeEnv === 'development';
|
||||
export const ampDeploymentKey = isDevelopment
|
||||
? 'client-Wvf63OdaukDZyCBtwgbOvHgGTuASBZFG'
|
||||
: 'client-agFoQg5AOvZ2ZiOChny9RrGk21jG3VrH';
|
||||
110
libs/base-ui/contexts/Experiments.tsx
Normal file
110
libs/base-ui/contexts/Experiments.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { Experiment, ExperimentClient } from '@amplitude/experiment-js-client';
|
||||
|
||||
import { ampDeploymentKey } from '../constants';
|
||||
import logEvent, { AnalyticsEventImportance } from '../utils/logEvent';
|
||||
|
||||
declare const window: WindowWithAnalytics;
|
||||
|
||||
const ExperimentsContext = createContext<ExperimentsContextProps>({
|
||||
experimentClient: null,
|
||||
isReady: false,
|
||||
getUserVariant: () => '',
|
||||
});
|
||||
|
||||
const experimentClient = Experiment.initialize(ampDeploymentKey, {
|
||||
exposureTrackingProvider: {
|
||||
track: (exposure) => {
|
||||
logEvent('$exposure', exposure, AnalyticsEventImportance.high);
|
||||
},
|
||||
},
|
||||
userProvider: {
|
||||
getUser: () => {
|
||||
return {
|
||||
user_id: window.ClientAnalytics.identity.userId,
|
||||
device_id: window.ClientAnalytics.identity.deviceId,
|
||||
os: window.ClientAnalytics.identity.device_os,
|
||||
language: window.ClientAnalytics.identity.languageCode,
|
||||
country: window.ClientAnalytics.identity.countryCode,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default function ExperimentsProvider({ children }: ExperimentsProviderProps) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function startExperiments() {
|
||||
try {
|
||||
await experimentClient.start();
|
||||
setIsReady(true);
|
||||
} catch (exception) {
|
||||
console.log(`Error starting experiments for ${ampDeploymentKey}:`, exception);
|
||||
}
|
||||
}
|
||||
void startExperiments();
|
||||
}, []);
|
||||
|
||||
const getUserVariant = useCallback(
|
||||
(flagKey: string): string => {
|
||||
if (!isReady) {
|
||||
return '';
|
||||
}
|
||||
if (!experimentClient) {
|
||||
console.error('No experiment clients found');
|
||||
return '';
|
||||
}
|
||||
const variant = experimentClient.variant(flagKey);
|
||||
return variant.value ?? '';
|
||||
},
|
||||
[isReady],
|
||||
);
|
||||
|
||||
const values = useMemo(() => {
|
||||
return { experimentClient, isReady, getUserVariant };
|
||||
}, [isReady, getUserVariant]);
|
||||
|
||||
return <ExperimentsContext.Provider value={values}>{children}</ExperimentsContext.Provider>;
|
||||
}
|
||||
|
||||
const useExperiments = () => {
|
||||
const context = useContext(ExperimentsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useExperiments must be used within an ExperimentsProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export { useExperiments };
|
||||
|
||||
type WindowWithAnalytics = Window &
|
||||
typeof globalThis & {
|
||||
ClientAnalytics: {
|
||||
identity: {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
device_os: string;
|
||||
languageCode: string;
|
||||
countryCode: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type ExperimentsContextProps = {
|
||||
experimentClient: ExperimentClient | null;
|
||||
isReady: boolean;
|
||||
getUserVariant: (flagKey: string) => string;
|
||||
};
|
||||
|
||||
type ExperimentsProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
"types": "./index.ts",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@amplitude/experiment-js-client": "^1.11.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-select": "^1.2.1",
|
||||
"@sprig-technologies/sprig-browser": "^2.29.0",
|
||||
|
||||
@@ -65,6 +65,9 @@ type CCAEventData = {
|
||||
context?: string;
|
||||
userId?: string;
|
||||
wallet_type?: string;
|
||||
flag_key?: string;
|
||||
variant?: string | undefined;
|
||||
experiment_key?: string | undefined;
|
||||
};
|
||||
|
||||
type AnalyticsEventData = {
|
||||
|
||||
53
yarn.lock
53
yarn.lock
@@ -215,6 +215,42 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@amplitude/analytics-connector@npm:^1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "@amplitude/analytics-connector@npm:1.5.0"
|
||||
checksum: 157115b642e8b254a88184fe3294150321fdd482388f262bdc08f9ab0c68b01b5f10d2e35a924edb8a8380f0944f8b1363133c2a3f42b59e33da319bfe21c8fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@amplitude/experiment-core@npm:^0.8.0":
|
||||
version: 0.8.0
|
||||
resolution: "@amplitude/experiment-core@npm:0.8.0"
|
||||
dependencies:
|
||||
js-base64: ^3.7.5
|
||||
checksum: 2e2c28f187b4f8072fe30589af95378c220917e949ae8a4c444dd8a6fdbe8bd9c8d0d105808b37e5e5fd59f16cbfa486c66f60847c211a0e424c6e7deb1693aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@amplitude/experiment-js-client@npm:^1.11.0":
|
||||
version: 1.11.0
|
||||
resolution: "@amplitude/experiment-js-client@npm:1.11.0"
|
||||
dependencies:
|
||||
"@amplitude/analytics-connector": ^1.5.0
|
||||
"@amplitude/experiment-core": ^0.8.0
|
||||
"@amplitude/ua-parser-js": ^0.7.31
|
||||
base64-js: 1.5.1
|
||||
unfetch: 4.1.0
|
||||
checksum: bc7ee5a521248ac83903b079fa61c7a884a3e27bd3573e4cedbb9f455ebe7af1e6fdc16a3e62988bf807ab9d74319de729fde78c42d4b0825f7c1a52d7753ff1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@amplitude/ua-parser-js@npm:^0.7.31":
|
||||
version: 0.7.33
|
||||
resolution: "@amplitude/ua-parser-js@npm:0.7.33"
|
||||
checksum: b08ce4cd4e96fed9eebffadb4060d24751cea80caf4265e6b829b7cfdb6561431386015674b3ac330a8987b990e2ca70725d4298eb1438beccb85264848b0b34
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ampproject/remapping@npm:^2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "@ampproject/remapping@npm:2.2.1"
|
||||
@@ -9563,6 +9599,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "base-ui@workspace:libs/base-ui"
|
||||
dependencies:
|
||||
"@amplitude/experiment-js-client": ^1.11.0
|
||||
"@radix-ui/react-icons": ^1.3.0
|
||||
"@radix-ui/react-select": ^1.2.1
|
||||
"@sprig-technologies/sprig-browser": ^2.29.0
|
||||
@@ -9582,7 +9619,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base64-js@npm:^1.3.1":
|
||||
"base64-js@npm:1.5.1, base64-js@npm:^1.3.1":
|
||||
version: 1.5.1
|
||||
resolution: "base64-js@npm:1.5.1"
|
||||
checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005
|
||||
@@ -16235,6 +16272,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-base64@npm:^3.7.5":
|
||||
version: 3.7.7
|
||||
resolution: "js-base64@npm:3.7.7"
|
||||
checksum: d1b02971db9dc0fd35baecfaf6ba499731fb44fe3373e7e1d6681fbd3ba665f29e8d9d17910254ef8104e2cb8b44117fe4202d3dc54c7cafe9ba300fe5433358
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-cookie@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "js-cookie@npm:3.0.5"
|
||||
@@ -23082,6 +23126,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unfetch@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "unfetch@npm:4.1.0"
|
||||
checksum: 8a0fee1e0f6ad8b3a2966fa199d07716affc3682d8e1c2c0cc138e5e5d2a2e0627d8c3321a4529a79e8a58332955bf80ac6c018f1dcc6de652026a7c3257d726
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unfetch@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "unfetch@npm:4.2.0"
|
||||
|
||||
Reference in New Issue
Block a user