forked from flyteorg/flyte
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: feature flags support for parallel development (flyteorg#267)
* chore: feature flags support for paralel development * Update Contributing.md with FeatureFlags usage * Update README.md to include feature flags Signed-off-by: Nastya Rusina <[email protected]>
- Loading branch information
Showing
11 changed files
with
382 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import * as React from 'react'; | ||
import { render, screen, waitFor } from '@testing-library/react'; | ||
|
||
import { FeatureFlagsProvider, useFeatureFlag } from '.'; | ||
import { FeatureFlag } from './defaultConfig'; | ||
|
||
function TestContent() { | ||
const enabledTestFlag = useFeatureFlag(FeatureFlag.TestFlagUndefined); | ||
return ( | ||
<FeatureFlagsProvider> | ||
<button>{enabledTestFlag ? 'Enabled' : 'Disabled'}</button> | ||
</FeatureFlagsProvider> | ||
); | ||
} | ||
|
||
function TestPage() { | ||
return ( | ||
<FeatureFlagsProvider> | ||
<TestContent /> | ||
</FeatureFlagsProvider> | ||
); | ||
} | ||
|
||
declare global { | ||
interface Window { | ||
setFeatureFlag: (flag: FeatureFlag, newValue: boolean) => void; | ||
getFeatureFlag: (flag: FeatureFlag) => boolean; | ||
clearRuntimeConfig: () => void; | ||
} | ||
} | ||
|
||
describe('FeatureFlags', () => { | ||
beforeEach(() => { | ||
render(<TestPage />); | ||
}); | ||
|
||
afterEach(() => { | ||
window.clearRuntimeConfig(); | ||
}); | ||
|
||
it('Feature flags can be read/set from dev tools', async () => { | ||
// flag defined and return proper value | ||
expect(window.getFeatureFlag(FeatureFlag.TestFlagTrue)).toBeTruthy(); | ||
// flag undefined and returns false | ||
expect( | ||
window.getFeatureFlag(FeatureFlag.TestFlagUndefined) | ||
).toBeFalsy(); | ||
|
||
window.setFeatureFlag(FeatureFlag.TestFlagUndefined, true); | ||
await waitFor(() => { | ||
// check that flag cghanged value | ||
expect( | ||
window.getFeatureFlag(FeatureFlag.TestFlagUndefined) | ||
).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
it('useFeatureFlags returns proper live value', async () => { | ||
// default value - flag is disabled | ||
expect(screen.getByText(/Disabled/i)).toBeTruthy(); | ||
|
||
// Enable flag | ||
window.setFeatureFlag(FeatureFlag.TestFlagUndefined, true); | ||
await waitFor(() => { | ||
// check that component was updated accordingly | ||
expect(screen.getByText(/Enabled/i)).toBeTruthy(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* Default Feature Flag config - used for features in developement. | ||
*/ | ||
|
||
export enum FeatureFlag { | ||
// Test flag is created only for unit-tests | ||
TestFlagUndefined = 'test-flag-undefined', | ||
TestFlagTrue = 'test-flag-true', | ||
|
||
// Production flags | ||
LaunchPlan = 'launch-plan' | ||
} | ||
|
||
export type FeatureFlagConfig = { [k: string]: boolean }; | ||
|
||
export const defaultFlagConfig: FeatureFlagConfig = { | ||
// Test | ||
'test-flag-true': true, | ||
|
||
// Production - new code should be turned off by default | ||
// If you need to turn it on locally -> update runtimeConfig in ./index.tsx file | ||
'launch-plan': false | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* Feature Flag provider - allows a multi-stage development. | ||
*/ | ||
import * as React from 'react'; | ||
import { | ||
createContext, | ||
useCallback, | ||
useContext, | ||
useEffect, | ||
useState | ||
} from 'react'; | ||
import { isDevEnv, isTestEnv } from 'common/env'; | ||
import { | ||
defaultFlagConfig, | ||
FeatureFlag, | ||
FeatureFlagConfig | ||
} from './defaultConfig'; | ||
|
||
export { FeatureFlag } from './defaultConfig'; | ||
|
||
// To turn on flag for local development only - update flag value here | ||
// REMOVE change prior to commit | ||
let runtimeConfig: FeatureFlagConfig = { | ||
...defaultFlagConfig | ||
// 'test-flag-true': true, <== locally turns flag on | ||
}; | ||
|
||
interface FeatureFlagState { | ||
flags: FeatureFlagConfig; | ||
setFeatureFlag: (flag: FeatureFlag, newValue: boolean) => void; | ||
getFeatureFlag: (flag: FeatureFlag) => boolean; | ||
} | ||
|
||
interface FeatureFlagProviderProps { | ||
children?: React.ReactNode; | ||
} | ||
|
||
/** FeatureFlagContext - used only if ContextProvider wasn't initialized */ | ||
const FeatureFlagContext = createContext<FeatureFlagState>({ | ||
flags: defaultFlagConfig, | ||
setFeatureFlag: () => { | ||
/* Provider is not initialized */ | ||
}, | ||
getFeatureFlag: () => false | ||
}); | ||
|
||
/** useFeatureFlag - should be used to get flag value */ | ||
export const useFeatureFlag = (flag: FeatureFlag) => | ||
useContext(FeatureFlagContext).getFeatureFlag(flag); | ||
|
||
/** useFatureFlagContext - could be used to set flags from code */ | ||
export const useFatureFlagContext = () => useContext(FeatureFlagContext); | ||
|
||
/** FeatureFlagsProvider - should wrap top level component for Production or feature flag related testing*/ | ||
export const FeatureFlagsProvider = (props: FeatureFlagProviderProps) => { | ||
const [flags, setFlags] = useState<FeatureFlagConfig>({ | ||
...defaultFlagConfig, | ||
...runtimeConfig | ||
}); | ||
|
||
const setFeatureFlag = useCallback( | ||
(flag: FeatureFlag, newValue: boolean) => { | ||
runtimeConfig[flag] = newValue; | ||
setFlags({ ...defaultFlagConfig, ...runtimeConfig }); | ||
}, | ||
[] | ||
); | ||
|
||
const getFeatureFlag = useCallback( | ||
(flag: FeatureFlag) => { | ||
if (isDevEnv() && flags[flag] === undefined) { | ||
throw `Default config value is absent for ${flag}`; | ||
} | ||
return flags[flag] ?? false; | ||
}, | ||
[flags] | ||
); | ||
|
||
const clearRuntimeConfig = useCallback(() => { | ||
runtimeConfig = { ...defaultFlagConfig }; | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (isDevEnv() || isTestEnv()) { | ||
// Allow manual change of feature flags from devtools | ||
window['setFeatureFlag'] = setFeatureFlag; | ||
window['getFeatureFlag'] = getFeatureFlag; | ||
if (isTestEnv()) { | ||
// allow reset flags to default - should be used in testing environment only | ||
window['clearRuntimeConfig'] = clearRuntimeConfig; | ||
} | ||
} | ||
}, [setFeatureFlag, getFeatureFlag, clearRuntimeConfig]); | ||
|
||
return ( | ||
<FeatureFlagContext.Provider | ||
value={{ flags, setFeatureFlag, getFeatureFlag }} | ||
> | ||
{props.children} | ||
</FeatureFlagContext.Provider> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.