diff --git a/client/.vscode/launch.json b/client/.vscode/launch.json index 24adb9d2..5936d0ab 100644 --- a/client/.vscode/launch.json +++ b/client/.vscode/launch.json @@ -29,6 +29,19 @@ "stopOnEntry": false, "runtimeArgs": ["--nolazy"], "sourceMaps": false + }, + { + "name": "Debug Jest Tests", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--inspect-brk", + "--inspect", + "${workspaceRoot}/node_modules/.bin/jest" + ], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "port": 9229 } ] } diff --git a/client/README.md b/client/README.md index 72288093..db8fa46f 100644 --- a/client/README.md +++ b/client/README.md @@ -61,3 +61,26 @@ From there, send `src/intl/en.json` to translators. (Depending on the TMS (Trans To access a translated version of a page, e.g. `pages/index.js`, add the locale as a portion of the URL path, as follows: - English: `localhost:8000/en/`, or `localhost:8000/` (the default fallback is English) + +## Feature Toggling + +We have implemented very simple feature flagging for this app, accessible via URL parameters. + +There are a lot of benefits to using feature toggles -- see [Martin Fowler](https://martinfowler.com/articles/feature-toggles.html) for a longer justification, but in short, they enable shipping in-progress work to production without enabling particular features for all users. + +### Viewing Features + +To view features, add the `flags` parameter to the URL, and set the value to a comma-delimited list of features to enable, e.g. `localhost:8000?flags=1,2,3` will enable features 1, 2, and 3. + +In the future we may add other means of audience-targeting, but for now we will be sharing links with flags enabled as a means of sharing in-development funcitonality + +### Using Flags + +When developing, to use a flag: + +1. Pass the Gatsby-provided `location` variable to your component. You have several options here: + 1. If your page uses the `Layout` [component](src/components/layout.tsx), you automatically get `URLFlagProvider` (see [FlagContext](src/contexts/FlagContext.tsx) for more info). + 2. If your page does not use `Layout`, you need to surround your component with a `URLFlagProvider` component and pass `location`. You can get `location` from the default props of the page (more [here](https://www.gatsbyjs.com/docs/location-data-from-props/)). See [Index.tsx](src/pages/index.tsx) for an example. +2. Use the `useFlags()` hook to get access to an array of flags, and check this array for the presence of the correct feature identifier. See [J40Header](src/components/J40Header.tsx) for an example. + +And that's it! diff --git a/client/package-lock.json b/client/package-lock.json index 7e082298..d641efbe 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -5904,8 +5904,7 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "3.3.0", @@ -8133,8 +8132,7 @@ "filter-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", - "dev": true + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=" }, "finalhandler": { "version": "1.1.2", @@ -8826,6 +8824,18 @@ } } }, + "query-string": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -15314,6 +15324,18 @@ "requires": { "side-channel": "^1.0.4" } + }, + "query-string": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } } } }, @@ -16212,10 +16234,9 @@ "dev": true }, "query-string": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.0.tgz", + "integrity": "sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==", "requires": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", @@ -18433,8 +18454,7 @@ "split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" }, "split-string": { "version": "3.1.0", @@ -18623,8 +18643,7 @@ "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-env-interpolation": { "version": "1.0.1", diff --git a/client/package.json b/client/package.json index d336b695..a4202910 100644 --- a/client/package.json +++ b/client/package.json @@ -68,6 +68,7 @@ }, "dependencies": { "@trussworks/react-uswds": "github:nathillardusds/react-uswds#nathillardusds/ssr", + "query-string": "^7.0.0", "react": "^17.0.1", "react-dom": "^17.0.1", "react-helmet": "^6.1.0", diff --git a/client/src/components/J40Header.tsx b/client/src/components/J40Header.tsx index 4f877377..e7456aa6 100644 --- a/client/src/components/J40Header.tsx +++ b/client/src/components/J40Header.tsx @@ -4,13 +4,21 @@ import {GovBanner, Title, PrimaryNav, SiteAlert} from '@trussworks/react-uswds'; -import {useIntl} from 'gatsby-plugin-intl'; +import {useIntl, Link} from 'gatsby-plugin-intl'; import {Helmet} from 'react-helmet'; -const headerLinks = [ - <>>, -]; +import {useFlags} from '../contexts/FlagContext'; + +const headerLinks = (flags: string[] | undefined) => { + const timelineLink = Timeline ; + const links = []; + if (flags && flags.includes('timeline')) { + links.push(timelineLink); + } + return links; +}; const J40Header = () => { + const flags = useFlags(); const intl = useIntl(); const title = intl.formatMessage({ id: '71L0pp', @@ -39,7 +47,7 @@ const J40Header = () => {