Adding Simple URL-based feature flags (#117)

* Fixes #66: As a developer, I want to limit the audience
that sees new features, so that we can control
the message and positioning of our tool.
Implements simple feature flagging via URL parameters.
Provide "?flags=x,y,z" to enable flags x, y, and z.
* Fixing type to use Location instead of URL
* Updating README with info on how to use feature flags
This commit is contained in:
Nat Hillard 2021-06-09 15:32:59 -04:00 committed by GitHub
commit 7ab14c7f3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 202 additions and 31 deletions

View file

@ -0,0 +1,39 @@
import * as React from 'react';
import {render, screen} from '@testing-library/react';
import {URLFlagProvider, useFlags} from './FlagContext';
describe('URL params are parsed and passed to children', () => {
describe('when the URL has a "flags" parameter set', () => {
// We artificially set the URL to localhost?flags=1,2,3
beforeEach(() => {
window.history.pushState({}, 'Test Title', '/?flags=1,2,3');
});
describe('when using useFlags', () => {
beforeEach(() => {
const FlagConsumer = () => {
const flags = useFlags();
return (
<>
<div>{flags.includes('1') ? 'yes1' : 'no1'}</div>
<div>{flags.includes('2') ? 'yes2' : 'no2'}</div>
<div>{flags.includes('3') ? 'yes3' : 'no3'}</div>
<div>{flags.includes('4') ? 'yes4' : 'no4'}</div>
</>
);
};
render(
<URLFlagProvider location={location}>
<FlagConsumer />
</URLFlagProvider>,
);
});
it('gives child components the flag values', async () => {
expect(screen.queryByText('yes1')).toBeInTheDocument();
expect(screen.queryByText('yes2')).toBeInTheDocument();
expect(screen.queryByText('yes3')).toBeInTheDocument();
expect(screen.queryByText('yes4')).not.toBeInTheDocument();
});
});
});
});

View file

@ -0,0 +1,56 @@
import * as React from 'react';
import * as queryString from 'query-string';
/**
* FlagContext stores feature flags and passes them to consumers
*/
interface IFlagContext {
/**
* Contains a list of all currently-active flags
*/
flags: string[];
}
const FlagContext = React.createContext<IFlagContext>({flags: []});
/**
* `useFlags` returns all feature flags.
*
* @return {Flags[]} flags All project feature flags
*/
const useFlags = () : string[] => {
const {flags} = React.useContext(FlagContext);
return flags;
};
interface IURLFlagProviderProps {
children: React.ReactNode,
location: Location
}
/**
* `URLFlagProvider` is a provider for FlagContext.
* It is passed the current URL and parses the
* "flags" parameter, assumed to be a comma-separated
* list of currently-active flags.
* @param {URL} location : the current URL object
* @param {ReactNode} children : the children components
* @return {ReactNode} URLFlagProvider component
**/
const URLFlagProvider = ({children, location}: IURLFlagProviderProps) => {
const flagString = queryString.parse(location.search).flags;
let flags: string[] = [];
if (flagString && typeof flagString === 'string') {
flags = (flagString as string).split(',');
}
console.log(JSON.stringify(location), JSON.stringify(flags));
return (
<FlagContext.Provider
value={{flags}}>
{children}
</FlagContext.Provider>
);
};
export {FlagContext, URLFlagProvider, useFlags};