-
Notifications
You must be signed in to change notification settings - Fork 2
Feature Flags Basic Implementation #289
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
base: master
Are you sure you want to change the base?
Changes from 14 commits
e28ed5d
4635ca3
0cd0cd7
4586d99
163de5a
cc6ca5d
d3e7634
9d1f4e2
a66883d
5c01276
6355b31
3f6354f
41a26d6
97f58a7
ec6eb4a
78522ff
eddf7f9
bde4275
fd6c354
306f51e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .nav_feature_div { | ||
| display: inline-block; | ||
| } | ||
|
|
||
| .nav_feature_div:not(:empty) { | ||
| padding-right: 2em; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // React imports | ||
| import React from "react"; | ||
| // import { useDispatch, useSelector } from "react-redux"; | ||
| import { FFState, toggleFeatureFlag } from "../../../lib/featureFlagSlice"; | ||
| import { RootState } from "../../../lib/store"; | ||
| import { useAppDispatch, useAppSelector } from "../../../lib/store"; | ||
|
|
||
| const Dashboard = () => { | ||
| const dispatch = useAppDispatch(); | ||
| const featureFlagState = useAppSelector( | ||
| (state: RootState) => state.featureFlagState | ||
| ); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const handleToggle = (myflag: any, newState: FFState) => { | ||
| dispatch(toggleFeatureFlag({ flag: myflag, value: newState })); | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <p>Dashboard</p> | ||
| {Object.entries(featureFlagState).map( | ||
| ([key, value]) => | ||
| !(key[0] === "_") && ( | ||
| <div key={key}> | ||
| {Object.entries(FFState).map(([stateKey, stateValue]) => ( | ||
| <button | ||
| key={key + stateKey} | ||
| onClick={() => handleToggle(key, stateKey as FFState)} | ||
| > | ||
| {"Set " + key.slice(6, key.length) + " to " + stateKey} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| ) | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Dashboard; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { createSlice, PayloadAction } from "@reduxjs/toolkit"; | ||
|
|
||
| // const API_ADDRESS = "http://localhost:8080"; | ||
mlaws21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| export enum FFState { | ||
| Enabled = "Enabled", | ||
| Disabled = "Disabled", | ||
| Pending = "Pending", | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding typing here, I think we need some more discussions. @AndrewMuh, do you think it's a better idea if we give each flag its own type for granular state mangament? Also, do you think the rendering logic like This question is important, because it will affect how
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly thinking about it now, I think all flags should be either Enabled or Disabled (I still think enums, but only these two). Things like senior only or student only should be done through a "featureIsSeniorOnly" feature flag. This simplifies implementation in the frontend/db: just check ephmatchEnabled and ephmatchIsSeniorOnly for example.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As for rendering logic, the frontend should restrict the rendering of a component using the FF, and the backend should reject invalid requests based on the FF. |
||
| interface FeatureFlagsState { | ||
| enableAbout: FFState; | ||
| enableWiki: FFState; | ||
| enableFAQ: FFState; | ||
|
|
||
| // Add more feature flags as needed | ||
| } | ||
|
|
||
| export const initialState: FeatureFlagsState = { | ||
| enableAbout: FFState.Enabled, | ||
| enableWiki: FFState.Enabled, | ||
| enableFAQ: FFState.Enabled, | ||
| }; | ||
|
|
||
| const featureFlagSlice = createSlice({ | ||
| name: "featureFlags", | ||
| initialState, | ||
| reducers: { | ||
| toggleFeatureFlag: ( | ||
mlaws21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| state, | ||
| action: PayloadAction<{ flag: keyof FeatureFlagsState; value: FFState }> | ||
| ) => { | ||
| const { flag, value } = action.payload; | ||
| if (flag in state) { | ||
| state[flag] = value; | ||
| } | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| export const { toggleFeatureFlag } = featureFlagSlice.actions; | ||
|
|
||
| // export const getFeatureFlag = (state: FeatureFlagsState, flag: keyof FeatureFlagsState) => featureFlagsState[flag]; | ||
mlaws21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| export default featureFlagSlice.reducer; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,23 @@ | ||
| import courseReducer from "./course"; | ||
| import schedulerUtilReducer from "./schedulerUtils"; | ||
| import authReducer from "../lib/authSlice"; | ||
| import featureFlagReducer from "../lib/featureFlagSlice"; | ||
|
|
||
| import goodrichReducer from "./goodrich"; | ||
|
|
||
| const rootReducer = { | ||
| courseState: courseReducer, | ||
| schedulerUtilState: schedulerUtilReducer, | ||
| authState: authReducer, | ||
| goodrichState: goodrichReducer, | ||
| featureFlagState: featureFlagReducer, | ||
| }; | ||
|
|
||
| export default rootReducer; | ||
| export { courseReducer, schedulerUtilReducer, authReducer, goodrichReducer }; | ||
| export { | ||
| courseReducer, | ||
| schedulerUtilReducer, | ||
| authReducer, | ||
| goodrichReducer, | ||
| featureFlagReducer, | ||
| }; |
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 route name should be just
admin(since the path name is also just/admin)