-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Issue #3249] Feature flag manager refactor #3374
[Issue #3249] Feature flag manager refactor #3374
Conversation
3438d42
to
fb188d1
Compare
ec9bc75
to
9016bbc
Compare
8a26362
to
971a129
Compare
|
||
return ( | ||
// Stick the footer to the bottom of the page | ||
<div className="display-flex flex-column minh-viewport"> | ||
<a className="usa-skipnav" href="#main-content"> | ||
{t("Layout.skip_to_main")} | ||
</a> | ||
<NextIntlClientProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change is unrelated but at one point I was messing with this file a lot and removing this unnecessary code simplified things for me a bit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good catch, this is already accounted for in the parent component
frontend/src/components/Header.tsx
Outdated
@@ -27,6 +30,21 @@ type Props = { | |||
|
|||
const homeRegexp = /^\/(?:e[ns])?$/; | |||
|
|||
const LoginLink = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be removed on rebase to include actual login button
const { user, isLoading, error } = useUser(); | ||
|
||
if (!mounted) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mounted
is no longer a thing
} = process.env; | ||
|
||
export const featureFlags = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be done elsewhere if we'd rather keep this file clean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They touch the envars so seems good to me.
|
||
// a workaround, as setting this in default state value results in hydration error | ||
// on feature flag admin page. Does it cause a blip on other pages? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems fine but it's a workaround just for the admin page so it may not be necessary
); | ||
}); | ||
|
||
// // do we still need to support this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may need some discussion here
3988586
to
03f25d3
Compare
searchParams?: ServerSideSearchParams, | ||
): boolean { | ||
if (!isValidFeatureFlag(name)) { | ||
throw new Error(`\`${name}\` is not a valid feature flag`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wondering about this down the road when we want to retire a feature flag like we did with search. I think this is throwing an error early while we're in that process and maybe missed a check because it's this throws when consuming the flag, not when trying to parse what is stuck in the user's cookies? Just trying to make sure we don't make it where we can't ever kill an old flag because some users have it set true and we will break them if it's removed from the defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's an interesting point. The way things are structured though, I'm not too worried about this causing a problem. The calls to this function should always be coming with feature flag names set internal to the app, not with names read dynamically from cookies or query params. The only issue I can see is if a feature flag was not fully removed - if we remove a flag from the list of defaults but are still attempting to use it to gate behavior, for instance.
In the case where we remove a flag but a user's cookies are still set, I think the concern is that their cookie value wouldn't be honored, but that's probably not a big issue.
In any case it's existing behavior, so we can make another ticket to look into it if you think that's warranted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I agree structurally we're ok, and I don't think we need to worry about not honoring them anymore once we've "dropped them" on the configurations side.
03f25d3
to
b8f15d0
Compare
|
||
const { checkFeatureFlag } = result.current; | ||
|
||
const value = checkFeatureFlag("someFakeFeature4"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, but maybe making this "nonexistantFakeFeature" or "fakeFeatureDoesNotExist" as I could see 4 getting picked up by someone wanting to extend these tests further than then that would break this test. They'd realize the problem eventually, but might be easier to try to avoid the clash proactively.
One thought, maybe for a follow up ticket. I've had issues in the past with end users getting odd stuff in their FF or even just App cookies and it can be helpful to have a URL that will just wipe out whatever they've got out there, basically just wipe clean the cookie. Often this happens on a Logout URL to other cookies, but FF ones in particular are usually not easily cleared by a non-technical user and potentially will break stuff all over the site if they're not working right. Might be worth throwing another URL arg on the flag manager or adding a _ff_clear URL argument that the middleware? would respond to by just setting an empty cookie over whatever the user had stuck in there. That way the help desk could send them a link with that special URL argument rather than as set of instructions for how to clear cookies on this site. |
frontend/src/components/Layout.tsx
Outdated
import Header from "./Header"; | ||
import Footer from "src/components/Footer"; | ||
import GrantsIdentifier from "src/components/GrantsIdentifier"; | ||
import Header from "src/components/Header"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had discussed when creating #2192 that we'd allow imports that are relative if they are in the same directory. We could revisit that, but would be good to update the es lint https://github.com/HHS/simpler-grants-gov/blob/main/frontend/.eslintrc.js#L22 so we are consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it, forgot that we were allowing that. Happy to revert this
SESSION_SECRET = "", | ||
FEATURE_SEARCH_OFF, | ||
FEATURE_OPPORTUNITY_OFF, | ||
FEATURE_AUTH_OFF, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels cleaner to grab them here and set defaults in the export. Would like to alphabetize them as well, might do that in a follow-up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree alphabetizing is a good idea, also agree it might be cleaner to do it later on
One issue I'm seeing with the |
@mdragon if I'm understanding your idea correctly, I think we already have it implemented here:
_ff=reset it should reset the feature flags. As far as what they reset to, currently it's defaults, and with this change it would be { ...defaults, ...flag values from env vars }
|
isFeatureEnabled( | ||
name: string, | ||
cookies: NextRequest["cookies"] | ReadonlyRequestCookies, | ||
searchParams?: ServerSideSearchParams, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are query params, not just for the search page AFAICT, so queryParams
as an arg would make more sense IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if you're saying that there is a better type to be using here or that we should rename the parameter. The nomenclature in the app is a bit confusing, and I confused myself enough while thinking about this that I had to look things up. https://stackoverflow.com/a/39294675 It sounds like "query params" or "search params" are not defined in any spec, but Next uses the term "search params" in their "useSearchParams" hook, and that's backed up by terminology in the URL API. "Query params" is definitely a very common phrase though. Not sure which way we'd want to go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the param name. Not sure why next uses "search" but imagine it is b/c they want to make it easier for new devs to create search pages.
Yup, exactly that, I didn't notice it was already in there. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Though I would address the flicker for the login link, either in this PR or other ticket.
…em for client side use (#3374) * rewrites the FeatureFlagManager and useFeatureFlag hook to allow syncing flags between server and client using cookies * adds the `authOn` feature flag in frontend code and terraform * refactors the `environments` setup a bit to more easily expose feature flags * splits functionality that does not benefit from being held in the FeatureFlagsManager class into a helper file * moves feature flag manager file into a nested directory
…em for client side use (#3374) * rewrites the FeatureFlagManager and useFeatureFlag hook to allow syncing flags between server and client using cookies * adds the `authOn` feature flag in frontend code and terraform * refactors the `environments` setup a bit to more easily expose feature flags * splits functionality that does not benefit from being held in the FeatureFlagsManager class into a helper file * moves feature flag manager file into a nested directory
Summary
Fixes #3249
TODOS
Time to review: 45 mins
Changes proposed
The existing feature flag system in NextJs consists of a withFeatureFlag wrapper that is used only on the server, a useFeatureFlag hook that is used only on the client, and a FeatureFlagManager that is by both of these implementations and within NextJs middleware. The FeatureFlagManager is able to read environment variables for feature flag values, but as currently written, is unable to make these values available to client side implementations of the FeatureFlagManager.
This change rewrites the FeatureFlagManager and useFeatureFlag hook to solve this issue, primarily making everything rely on all feature flag state being maintained in cookies to allow client and server to remain in sync.
It also:
authOff
feature flag in frontend code and terraformenvironments
setup a bit to more easily expose feature flagsContext for reviewers
For whoever is reviewing this, I'd appreciate an in person meeting to explain a few things and gather thoughts around the approach.
Test Steps
npm run build && npm start
npm run build && FEATURE_AUTH_OFF="true" FEATURE_OPPORTUNITY_OFF="true" npm start
Additional information
With auth enabled the header should look like this