-
-
Notifications
You must be signed in to change notification settings - Fork 29
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
feature flag framework #308
base: dev
Are you sure you want to change the base?
Conversation
@HYDRO2070 is attempting to deploy a commit to the thieflord06's projects Team on Vercel. A member of the Team first needs to authorize it. |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
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 pushed some minor stylistic updates myself, but this looked great already! Thanks for taking care of this!
@noahm @thieflord06 |
Looks like the functionality is working with the rollout 🔥 |
|
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 think the ideal api that's not quite present here yet is a useFeatureFlag
hook that accepts a string feature flag name, looks up the device id, hashes a concatenation of the id and feature name so each gets its own separate "roll". Ultimately it should just return a boolean for whether the feature is enabled.
That should be easy to accomplish with all the pieces that are already here, and it will clean up the render logic in the individual feature components greatly.
If I understand correctly that's already in place, if you use the same API and pass the {feature} it returns the status. Returns all features available: Returns if a specific feature is available: |
I didn't understand lol I think this referring to the frontend. |
Right we do have a hook by that name in this pr, but it only calls out to the feature specific backend endpoint. It hasn't been updated since we switched to a client side rollout design. |
@noahm |
@noahm |
Yes, processing should be done inside hooks to keep the render logic in actual components cleaner.
You're right, it is a bit wasteful to have to re-calculate every time, but that's just a matter of adding a caching layer of some kind. We can take care of that as a final step after everything else is in place and working with a clean API shape. |
Firstly can you please describe like how it's working will be inside the hook? And for second one for storing rollout in local. How would you suggest to handle that. Right now? |
Yes, there will be too hooks. One for fetching which we already have finished (this is to pseudo-code that hook a bit: /**
* returns null if backend is still fetching or returned an error,
* otherwise, returns true if given flag is enabled, or false if disabled
*/
function useFeatureFlag(flagName: string): boolean | null {
const {isLoading, isErrored, data: backendData } = useAllFeatures();
// all remaining work in this hook is only re-calculated if backend data changes
return useMemo(() => {
if (isLoading || isErrored) return null;
const selectedFlag = backendData[flagName];
// there is no rollout specified, so just return the baseline status
if (!selectedFlag || !selectedFlag.rollout) return selectedFlag?.status || false;
// there is a rollout specified, so we need to use our deviceId to determine enrollment
const deviceId = getOrCreateDeviceId();
// note the hash shouldn't be based on deviceId alone, but also on this specific flag.
const percentage = hashStringToPercentage(deviceId + ':' + flagName);
// return the final boolean on whether this flag is enabled or not for this device
return percentage < selectedFlag.rollout;
}, [isLoading, isErrored, backendData]);
}
No, we never want to store the calculated rollout in storage. Only the device id gets saved to localstorage. |
@noahm |
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.
everything looks really good here now! just a couple of very minor things that need to be addressed now.
@@ -34,31 +35,37 @@ export function HomeStatsMain({ | |||
|
|||
{loading ? undefined : ( | |||
<Button size="small" className="toggle-table" onClick={onToggleTable}> | |||
<ViewList style={{ color: 'gray' }} /> | |||
{useFeatureFlag('stats-page') && <ViewList style={{ color: 'gray' }} />} |
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.
because this is used underneath the loading ?
check just above, this is conditionally calling a hook which is against the rules. This needs to be hoisted up and have the return value saved as a variable to reference here.
{stats && ( | ||
<> | ||
<TopBlocked | ||
blocked={stats.topLists.total.blocked} | ||
blocked24={stats.topLists['24h'].blocked} | ||
/> | ||
{useFeatureFlag('top-blocked') && ( | ||
<TopBlocked | ||
blocked={stats.topLists.total.blocked} | ||
blocked24={stats.topLists['24h'].blocked} | ||
/> | ||
)} | ||
|
||
<TopBlockers | ||
blockers={stats.topLists.total.blockers} | ||
blockers24={stats.topLists['24h'].blockers} | ||
/> | ||
{useFeatureFlag('top-blockers') && ( |
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.
same for these two. they're also conditional (per the stats &&
check) and need to be hoisted up.
@@ -22,7 +21,9 @@ export function HomeStatsMain({ | |||
loading, | |||
stats, | |||
onToggleTable, | |||
features, |
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 is unused and can be removed
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.
all changes in this file can be reverted now that we have the more unified feature flag hook.
PR Description:
1.Created API endpoints to fetch all features or a specific feature.
2.Implemented a Feature Flag Context using React Context API to manage feature states globally.
3.Added a provider to wrap the app and enable access to feature flags across components.
For example, it is used for the profile description. Similarly, we can use it across different pages.