feat: add dynamicconfig modal setting (#7395)

* feat: add dynamicconfig feature flags setting

* better typing

* use diff atom for configs

* fix

* add config to devflagsbox

* fix devbox intiailization

* lint
This commit is contained in:
Kristie Huang 2023-10-10 15:37:47 -04:00 committed by GitHub
parent 24ddace1eb
commit 45c3e1dc78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 12 deletions

@ -322,4 +322,40 @@
<url loc="https://app.uniswap.org/tokens/polygon/0xa9f37d84c856fda3812ad0519dad44fa0a3fe207" lastmod="2023-10-06T18:13:27.852Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0xb6a5ae40e79891e4deadad06c8a7ca47396df21c" lastmod="2023-10-06T18:13:27.852Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0x49a0400587a7f65072c87c4910449fdcc5c47242" lastmod="2023-10-06T18:13:27.852Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xf951e335afb289353dc249e82926178eac7ded78" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xd31a59c85ae9d8edefec411d448f90841571b89c" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x037a54aab062628c9bbae1fdb1583c195585fe41" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x595832f8fc6bf59c85c527fec3740a1b7a361269" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x68749665ff8d2d112fa859aa293f07a622782f38" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x0c10bf8fcb7bf5412187a595ab97a3609160b5c6" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xe41d2489571d322189246dafa5ebde1f4699f498" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x03ab458634910aad20ef5f1c8ee96f1d6ac54919" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x4bcea5e4d0f6ed53cf45e7a28febb2d3621d7438" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x767fe9edc9e0df98e07454847909b5e959d7ca0e" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x43d7e65b8ff49698d9550a7f315c87e67344fb59" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xaaa9214f675316182eaa21c85f0ca99160cc3aaa" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xbb9fd9fa4863c03c574007ff3370787b9ce65ff6" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xe80c0cd204d654cebe8dd64a4857cab6be8345a3" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xfe2e637202056d30016725477c5da089ab0a043a" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x430ef9263e76dae63c84292c3409d61c598e9682" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x320623b8e4ff03373931769a31fc52a4e78b5d70" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/arbitrum/0x51f9f9ff6cb2266d68c04ec289c7aba81378a383" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/arbitrum/0x7c8a1a80fdd00c9cccd6ebd573e9ecb49bfa2a59" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/arbitrum/0x7ca0b5ca80291b1feb2d45702ffe56a7a53e7a97" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/arbitrum/0x6c2c06790b3e3e3c38e12ee22f8183b37a13ee55" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/arbitrum/0x1622bf67e6e5747b81866fe0b85178a93c7f86e3" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/optimism/0xf98dcd95217e15e05d8638da4c91125e59590b07" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/optimism/0x14778860e937f509e651192a90589de711fb88a9" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0x9c9e5fd8bbc25984b178fdce6117defa39d2db39" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0x9c2c5fd7b07e95ee044ddeba0e97a665f142394f" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0x9dbfc1cbf7a1e711503a29b4b5f9130ebeccac96" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0x9a41e03fef7f16f552c6fba37ffa7590fb1ec0c4" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/base/0x09188484e1ab980daef53a9755241d759c5b7d60" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/bnb/0xf275e1ac303a4c9d987a2c48b8e555a77fec3f1c" lastmod="2023-10-10T17:37:14.268Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0xb0ed33f79d89541dfdcb04a8f04bc2c6be025ecc" lastmod="2023-10-10T18:21:44.153Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/ethereum/0x6468e79a80c0eab0f9a2b574c8d5bc374af59414" lastmod="2023-10-10T18:21:44.153Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/arbitrum/0x655a6beebf2361a19549a99486ff65f709bd2646" lastmod="2023-10-10T18:21:44.153Z" priority="0.8"/>
<url loc="https://app.uniswap.org/tokens/polygon/0xc708d6f2153933daa50b2d0758955be0a93a8fec" lastmod="2023-10-10T18:21:44.153Z" priority="0.8"/>
</urlset>

@ -1,5 +1,8 @@
import { ChainId } from '@uniswap/sdk-core'
import Column from 'components/Column'
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateConfig, useUpdateFlag } from 'featureFlags'
import { DynamicConfigName } from 'featureFlags/dynamicConfig'
import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains'
import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion'
import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider'
import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments'
@ -218,16 +221,48 @@ function FeatureFlagOption({ value, variant, featureFlag, label }: FeatureFlagPr
)
}
interface DynamicConfigDropdownProps {
configName: DynamicConfigName
label: string
options: any[]
selected: any[]
parser: (opt: string) => any
}
function DynamicConfigDropdown({ configName, label, options, selected, parser }: DynamicConfigDropdownProps) {
const updateConfig = useUpdateConfig()
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValues = Array.from(e.target.selectedOptions, (opt) => parser(opt.value))
// Saved to atom as { [configName]: { [configName]: values } } to match Statsig return format
updateConfig(configName, { [configName]: selectedValues })
}
return (
<Row key={configName}>
<FlagInfo>
<FlagName>{configName}</FlagName>
<FlagDescription>{label}</FlagDescription>
</FlagInfo>
<select multiple onChange={handleSelectChange}>
{options.map((opt) => (
<option key={opt} value={opt} selected={selected.includes(opt)}>
{opt}
</option>
))}
</select>
</Row>
)
}
export default function FeatureFlagModal() {
const open = useModalIsOpen(ApplicationModal.FEATURE_FLAGS)
const toggle = useToggleFeatureFlags()
const toggleModal = useToggleFeatureFlags()
return (
<Modal open={open}>
<FlagsColumn>
<Header>
Feature Flag Settings
<CloseButton onClick={toggle}>
<CloseButton onClick={toggleModal}>
<X size={24} />
</CloseButton>
</Header>
@ -262,6 +297,13 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.quickRouteMainnet}
label="Enable quick routes for Mainnet"
/>
<DynamicConfigDropdown
selected={useQuickRouteChains()}
options={Object.values(ChainId).filter((v) => !isNaN(Number(v))) as ChainId[]}
parser={Number.parseInt}
configName={DynamicConfigName.quickRouteChains}
label="Enable quick routes for these chains"
/>
</FeatureFlagGroup>
<FeatureFlagGroup name="UniswapX Flags">
<FeatureFlagOption

@ -1,4 +1,10 @@
import { BaseVariant, FeatureFlag, featureFlagSettings as featureFlagSettingsAtom } from 'featureFlags'
import {
BaseVariant,
dynamicConfigSettings as dynamicConfigSettingsAtom,
FeatureFlag,
featureFlagSettings as featureFlagSettingsAtom,
} from 'featureFlags'
import { DynamicConfigName, useDynamicConfig } from 'featureFlags/dynamicConfig'
import { useAtomValue } from 'jotai/utils'
import { useMemo, useState } from 'react'
import { useGate } from 'statsig-react'
@ -23,7 +29,7 @@ const TopBar = styled.div`
display: flex;
justify-content: space-between;
`
const Gate = (flagName: string, featureFlagSettings: Record<string, string>) => {
const Gate = (flagName: FeatureFlag, featureFlagSettings: Record<string, string>) => {
const gateResult = useGate(flagName)
if (gateResult) {
const { value: statsigValue }: { value: boolean } = gateResult
@ -40,11 +46,31 @@ const Gate = (flagName: string, featureFlagSettings: Record<string, string>) =>
return null
}
const Config = (name: DynamicConfigName, savedSettings: Record<string, any>) => {
const statsigConfig = useDynamicConfig(name)
if (statsigConfig) {
const statsigValue = statsigConfig.getValue()
const setting = savedSettings[name]
if (setting && statsigValue !== setting) {
return (
<ThemedText.LabelSmall key={name}>
{name}: {JSON.stringify(setting[name])}
</ThemedText.LabelSmall>
)
}
}
return null
}
export default function DevFlagsBox() {
const featureFlagsAtom = useAtomValue(featureFlagSettingsAtom)
const featureFlags = useMemo(() => Object.values(FeatureFlag), [])
const dynamicConfigsAtom = useAtomValue(dynamicConfigSettingsAtom)
const dynamicConfigs = useMemo(() => Object.values(DynamicConfigName), [])
const overrides = featureFlags.map((flagName) => Gate(flagName, featureFlagsAtom))
dynamicConfigs.forEach((configName) => overrides.push(Config(configName, dynamicConfigsAtom)))
const hasOverrides = overrides.some((g) => g !== null)
const [isOpen, setIsOpen] = useState(true)

@ -1,13 +1,19 @@
import { ChainId } from '@uniswap/sdk-core'
import { useFeatureFlagsContext } from 'featureFlags'
import { DynamicConfigName, useDynamicConfig } from '.'
// eslint-disable-next-line import/no-unused-modules
export function useQuickRouteChains(): ChainId[] {
const config = useDynamicConfig(DynamicConfigName.quickRouteChains)
const chains = config.get('chains', [])
const statsigConfig = useDynamicConfig(DynamicConfigName.quickRouteChains)
const featureFlagsContext = useFeatureFlagsContext()
const remoteConfigChains = statsigConfig.get(DynamicConfigName.quickRouteChains, []) as ChainId[]
const localConfigChains =
featureFlagsContext.configs[DynamicConfigName.quickRouteChains]?.[DynamicConfigName.quickRouteChains]
const chains = Array.isArray(localConfigChains) ? localConfigChains : remoteConfigChains
if (chains.every((c) => Object.values(ChainId).includes(c))) {
return chains as ChainId[]
return chains
} else {
console.error('feature flag config chains contain invalid ChainId')
return []

@ -26,11 +26,12 @@ export enum FeatureFlag {
interface FeatureFlagsContextType {
isLoaded: boolean
flags: Record<string, string>
configs: Record<string, any>
}
const FeatureFlagContext = createContext<FeatureFlagsContextType>({ isLoaded: false, flags: {} })
const FeatureFlagContext = createContext<FeatureFlagsContextType>({ isLoaded: false, flags: {}, configs: {} })
function useFeatureFlagsContext(): FeatureFlagsContextType {
export function useFeatureFlagsContext(): FeatureFlagsContextType {
const context = useContext(FeatureFlagContext)
if (!context) {
throw Error('Feature flag hooks can only be used by children of FeatureFlagProvider.')
@ -39,8 +40,9 @@ function useFeatureFlagsContext(): FeatureFlagsContextType {
}
}
/* update and save feature flag settings */
/* update and save feature flag & dynamic config settings */
export const featureFlagSettings = atomWithStorage<Record<string, string>>('featureFlags', {})
export const dynamicConfigSettings = atomWithStorage<Record<string, any>>('dynamicConfigs', {})
export function useUpdateFlag() {
const setFeatureFlags = useUpdateAtom(featureFlagSettings)
@ -56,13 +58,29 @@ export function useUpdateFlag() {
)
}
export function useUpdateConfig() {
const setConfigs = useUpdateAtom(dynamicConfigSettings)
return useCallback(
(configName: string, option: any) => {
setConfigs((configs) => ({
...configs,
[configName]: option,
}))
},
[setConfigs]
)
}
export function FeatureFlagsProvider({ children }: { children: ReactNode }) {
// TODO: `isLoaded` to `true` so `App.tsx` will render. Later, this will be dependent on
// flags loading from Amplitude, with a timeout.
const featureFlags = useAtomValue(featureFlagSettings)
const dynamicConfigs = useAtomValue(dynamicConfigSettings)
const value = {
isLoaded: true,
flags: featureFlags,
configs: dynamicConfigs,
}
return <FeatureFlagContext.Provider value={value}>{children}</FeatureFlagContext.Provider>
}