Skip to content

Commit

Permalink
feat: statsig integration PoC (#747)
Browse files Browse the repository at this point in the history
  • Loading branch information
yogurtandjam authored Jul 1, 2024
1 parent 0ffe236 commit ffa2f97
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 35 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"lint": "eslint --ext .ts,.tsx src/",
"fix-lint": "eslint --fix --ext .ts,.tsx src/",
"tag": "node scripts/generate-tag.js",
"test": "vitest run",
"test": "VITE_DISABLE_STATSIG=true vitest run",
"tsc": "tsc",
"postinstall": "tar -xzC public -f tradingview/tradingview.tgz",
"prepare": "husky",
Expand All @@ -49,7 +49,7 @@
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "1.8.6",
"@dydxprotocol/v4-abacus": "1.8.15",
"@dydxprotocol/v4-client-js": "^1.1.24",
"@dydxprotocol/v4-localization": "^1.1.139",
"@ethersproject/providers": "^5.7.2",
Expand Down
9 changes: 4 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications';
import { PotentialMarketsProvider } from '@/hooks/usePotentialMarkets';
import { RestrictionProvider } from '@/hooks/useRestrictions';
import { StatSigProvider } from '@/hooks/useStatsig';
import { StatsigProvider } from '@/hooks/useStatsig';
import { SubaccountProvider } from '@/hooks/useSubaccount';

import '@/styles/constants.css';
Expand Down Expand Up @@ -160,7 +160,7 @@ const providers = [
clientId: import.meta.env.VITE_PRIVY_APP_CLIENT_ID,
config: privyConfig,
}),
wrapProvider(StatSigProvider),
wrapProvider(StatsigProvider),
wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider),
wrapProvider(PrivyWagmiConnector, { wagmiChainsConfig: configureChainsConfig }),
Expand Down
1 change: 1 addition & 0 deletions src/constants/abacus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export type MarketHistoricalFunding = Abacus.exchange.dydx.abacus.output.MarketH
export const PerpetualMarketType = Abacus.exchange.dydx.abacus.output.PerpetualMarketType;

// ------ Configs ------ //
export const StatsigConfig = Abacus.exchange.dydx.abacus.state.manager.StatsigConfig;
export type Configs = Abacus.exchange.dydx.abacus.output.Configs;
export type FeeDiscount = Abacus.exchange.dydx.abacus.output.FeeDiscount;
export type FeeTier = Abacus.exchange.dydx.abacus.output.FeeTier;
Expand Down
5 changes: 3 additions & 2 deletions src/constants/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { StatSigFlags } from '@/types/statsig';
import { RecordOf, TagsOf, UnionOf, ofType, unionize } from 'unionize';

import type { AbacusApiStatus, HumanReadablePlaceOrderPayload } from './abacus';
Expand Down Expand Up @@ -52,7 +53,7 @@ export const AnalyticsUserProperties = unionize(
Version: ofType<string | null>(),

// StatSigFlags
ffSkipMigration: ofType<boolean>(),
StatsigFlags: ofType<{ [key in StatSigFlags]?: boolean }>(),

// Network
Network: ofType<DydxNetwork>(),
Expand All @@ -73,7 +74,7 @@ export const AnalyticsUserPropertyLoggableTypes = {
Locale: 'selectedLocale',
Breakpoint: 'breakpoint',
Version: 'version',
ffSkipMigration: 'ffSkipMigration',
StatsigFlags: 'statsigFlags',
Network: 'network',
WalletType: 'walletType',
WalletConnectionType: 'walletConnectionType',
Expand Down
11 changes: 5 additions & 6 deletions src/hooks/useAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ import { useApiState } from './useApiState';
import { useBreakpoints } from './useBreakpoints';
import { useDydxClient } from './useDydxClient';
import { useSelectedNetwork } from './useSelectedNetwork';
import { StatSigFlags, useStatSigGateValue } from './useStatsig';
import { useAllStatsigGateValues } from './useStatsig';

export const useAnalytics = () => {
const latestTag = import.meta.env.VITE_LAST_TAG;
const { walletType, walletConnectionType, evmAddress, dydxAddress, selectedWalletType } =
useAccounts();
const { indexerClient } = useDydxClient();
const isSkipEnabled = useStatSigGateValue(StatSigFlags.ffSkipMigration);

const statsigConfig = useAllStatsigGateValues();
/** User properties */

// AnalyticsUserProperty.Breakpoint
Expand Down Expand Up @@ -69,10 +68,10 @@ export const useAnalytics = () => {
}
}, [latestTag]);

// AnalyticsUserProperty.ffSkipMigration
// AnalyticsUserProperty.StatsigConfigs
useEffect(() => {
identify(AnalyticsUserProperties.ffSkipMigration(isSkipEnabled));
}, [isSkipEnabled]);
identify(AnalyticsUserProperties.StatsigFlags(statsigConfig));
}, [statsigConfig]);

// AnalyticsUserProperty.Network
const { selectedNetwork } = useSelectedNetwork();
Expand Down
5 changes: 4 additions & 1 deletion src/hooks/useInitializePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import abacusStateManager from '@/lib/abacus';
import { validateAgainstAvailableEnvironments } from '@/lib/network';

import { useLocalStorage } from './useLocalStorage';
import { useAllStatsigGateValues } from './useStatsig';

export const useInitializePage = () => {
const dispatch = useAppDispatch();
Expand All @@ -20,9 +21,11 @@ export const useInitializePage = () => {
defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: validateAgainstAvailableEnvironments,
});
const statsigConfig = useAllStatsigGateValues();

useEffect(() => {
dispatch(initializeLocalization());
abacusStateManager.setStatsigConfigs(statsigConfig);
abacusStateManager.start({ network: localStorageNetwork });
}, []);
}, [dispatch, localStorageNetwork, statsigConfig]);
};
5 changes: 3 additions & 2 deletions src/hooks/useLocalNotifications.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, useCallback, useContext, useEffect } from 'react';

import { StatSigFlags } from '@/types/statsig';
import { useQuery } from '@tanstack/react-query';

import { AnalyticsEvents } from '@/constants/analytics';
Expand All @@ -13,7 +14,7 @@ import { STATUS_ERROR_GRACE_PERIOD, fetchTransferStatus, trackSkipTx } from '@/l

import { useEndpointsConfig } from './useEndpointsConfig';
import { useLocalStorage } from './useLocalStorage';
import { StatSigFlags, useStatSigGateValue } from './useStatsig';
import { useStatsigGateValue } from './useStatsig';

const LocalNotificationsContext = createContext<
ReturnType<typeof useLocalNotificationsContext> | undefined
Expand All @@ -32,7 +33,7 @@ const ERROR_COUNT_THRESHOLD = 3;

const useLocalNotificationsContext = () => {
const { skip } = useEndpointsConfig();
const useSkip = useStatSigGateValue(StatSigFlags.ffSkipMigration);
const useSkip = useStatsigGateValue(StatSigFlags.ffSkipMigration);

const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
[key: `dydx${string}`]: TransferNotifcation[];
Expand Down
44 changes: 31 additions & 13 deletions src/hooks/useStatsig.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import { StatsigClient } from '@statsig/js-client';
import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings';
import { useEffect, useMemo, useState } from 'react';

export enum StatSigFlags {
// When adding a flag here, make sure to add an analytics tracker in useAnalytics.ts
ffSkipMigration = 'ff_skip_migration',
}
import { StatSigFlags, StatsigConfigType } from '@/types/statsig';
import { StatsigClient } from '@statsig/js-client';
import {
StatsigProvider as StatsigProviderInternal,
useStatsigClient,
} from '@statsig/react-bindings';

export const StatSigProvider = ({ children }: { children: React.ReactNode }) => {
const client = new StatsigClient(`${import.meta.env.VITE_STATSIG_CLIENT_KEY}`, {
// TODO: fill in with ip address
});
client.initializeSync();
import { initStatsig } from '@/lib/statsig';

return <StatsigProvider client={client}> {children} </StatsigProvider>;
export const StatsigProvider = ({ children }: { children: React.ReactNode }) => {
const [client, setClient] = useState<StatsigClient | null>(null);
useEffect(() => {
const setAsyncClient = async () => {
const statsigClient = await initStatsig();
setClient(statsigClient);
};
setAsyncClient();
}, [initStatsig]);
// if no client, render without provider until a client exists
if (!client) return <div>{children}</div>;
return <StatsigProviderInternal client={client}> {children} </StatsigProviderInternal>;
};

export const useStatSigGateValue = (gate: StatSigFlags) => {
export const useStatsigGateValue = (gate: StatSigFlags) => {
const { checkGate } = useStatsigClient();
return checkGate(gate);
};

export const useAllStatsigGateValues = () => {
const { checkGate } = useStatsigClient();
const allGateValues = useMemo(() => {
return Object.values(StatSigFlags).reduce((acc, gate) => {
return { ...acc, [gate]: checkGate(gate) };
}, {} as StatsigConfigType);
}, []);
return allGateValues;
};
20 changes: 18 additions & 2 deletions src/lib/abacus/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// eslint-disable-next-line import/no-cycle
import { StatSigFlags } from '@/types/statsig';
import type { LocalWallet, SelectedGasDenom } from '@dydxprotocol/v4-client-js';

import type {
Expand Down Expand Up @@ -29,6 +31,7 @@ import {
HistoricalPnlPeriod,
IOImplementations,
OnboardingConfig,
StatsigConfig,
TradeInputField,
TransferInputField,
TransferType,
Expand All @@ -46,7 +49,6 @@ import { getInputTradeOptions, getTransferInputs } from '@/state/inputsSelectors

import { LocaleSeparators } from '../numbers';
import AbacusAnalytics from './analytics';
// eslint-disable-next-line import/no-cycle
import AbacusChainTransaction from './dydxChainTransactions';
import AbacusFileSystem from './filesystem';
import AbacusFormatter from './formatter';
Expand Down Expand Up @@ -103,7 +105,6 @@ class AbacusStateManager {

const appConfigs = AbacusAppConfig.Companion.forWebAppWithIsolatedMargins;
appConfigs.onboardingConfigs.squidVersion = OnboardingConfig.SquidVersion.V2;

this.stateManager = new AsyncAbacusStateManager(
'',
CURRENT_ABACUS_DEPLOYMENT,
Expand Down Expand Up @@ -409,6 +410,21 @@ class AbacusStateManager {
getChainById = (chainId: string) => {
return this.stateManager.getChainById(chainId);
};

/**
*
* Updates Abacus' global StatsigConfig object.
* You must destructure the new flag you want to use from the config and set
* the relevant property on the StatsigConfig object.
*
* TODO: establish standardized naming conventions between
* statsig FF name and boolean propery in abacus StatsigConfig
* https://linear.app/dydx/project/feature-experimentation-6853beb333d7/overview
*/
setStatsigConfigs = (statsigConfig: { [key in StatSigFlags]?: boolean }) => {
const { [StatSigFlags.ffSkipMigration]: useSkip = false } = statsigConfig;
StatsigConfig.useSkip = useSkip;
};
}

const abacusStateManager = new AbacusStateManager();
Expand Down
17 changes: 17 additions & 0 deletions src/lib/statsig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StatsigClient } from '@statsig/js-client';

let statsigClient: StatsigClient;

export const initStatsig = async () => {
if (statsigClient) return statsigClient;
statsigClient = new StatsigClient(
import.meta.env.VITE_STATSIG_CLIENT_KEY,
{},
{
disableLogging: import.meta.env.VITE_DISABLE_STATSIG,
disableStorage: import.meta.env.VITE_DISABLE_STATSIG,
}
);
await statsigClient.initializeAsync();
return statsigClient;
};
5 changes: 5 additions & 0 deletions src/types/statsig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type StatsigConfigType = Record<StatSigFlags, boolean>;

export enum StatSigFlags {
ffSkipMigration = 'ff_skip_migration',
}

0 comments on commit ffa2f97

Please sign in to comment.