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:
parent
24ddace1eb
commit
45c3e1dc78
@ -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>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user