Skip to content

Conversation

@mlaws21
Copy link
Contributor

@mlaws21 mlaws21 commented Jan 9, 2024

Basic implementation of feature flags and UI to use them, I implemented them for the about, wiki, and FAQ pages, there is also infrastructure to implement the feature flags for nav elements too. Big picture you add flags to components/FeatureFlagsConfig and then can edit them in the admin dashboard.

Sorry for the big PR, I got a bit carried away

@mlaws21 mlaws21 requested review from AndrewMuh and yechs January 9, 2024 20:12
Copy link
Member

@yechs yechs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: Use Redux instead of React Context

Redux is complicated and there are many ways of writing them. We should talk tomorrow or some other time to introduce you to them

@yechs
Copy link
Member

yechs commented Jan 11, 2024

However, if you are curious about how Redux current works in our implementation. You might want to look at these files. At this point, I might as well write a short doc on it.

HowTo: work with Modern Redux states in WSO-React

Key Concepts

The official Redux tutorial is also helpful. Read the following article to make sure you understand these terms: reducer, action, dispatch, selectors.
https://redux.js.org/tutorials/essentials/part-1-overview-concepts

Make sure to read modern Redux (using Redux Toolkit, or RTK)

If you are searching for external documentation/tutorial. Make sure you are reading the modern Redux codes (with Redux Toolkit) instead of the old Redux (using React-Redux, which requires lots of boilerplates). Specifically, we are adopting the Slices style. Read more:
https://redux.js.org/tutorials/fundamentals/part-7-standard-patterns
https://redux.js.org/tutorials/fundamentals/part-8-modern-redux

Reducer and Action definition

Redux store configuration & Persistence

The Redux store is configured in src/lib/store.ts.

const store = configureStore({
reducer: {
courseState: courseReducer,
schedulerUtilState: persistedCourseSchedulerReducer,
authState: persistedAuthReducer,
},

In the file we also persist the reducer so its states are persisted through page refreshes.
const persistedAuthReducer = persistReducer(
{ key: "auth", storage, whitelist: ["identityToken"] },
authReducer
);

The file creates two React Hooks (useAppDispatch and useAppSelector) to be used in actual pages
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

Usage Example

An example usage is in App.tsx
Read (with selector):

const App = () => {
const dispatch = useAppDispatch();
const apiToken = useAppSelector(getAPIToken);
const identityToken = useAppSelector(getIdentityToken);
const wso = useAppSelector(getWSO);

Write (by dispatching actions):
if (identityToken !== "") {
try {
const apiTokenResponse = await wso.authService.getAPIToken(
identityToken
);
const newAPIToken = apiTokenResponse.token;
dispatch(updateAPIToken(newAPIToken));
} catch (error) {

Copy link
Member

@yechs yechs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR:

  • Redux hooks and typing
  • scope protect the admin page

Comment on lines 25 to 33
toggleFeatureFlag: (
state,
action: PayloadAction<keyof FeatureFlagsState>
) => {
const flagKey = action.payload;
if (flagKey in state) {
state[flagKey] = !state[flagKey];
}
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little worried about toggling, because it is not idempotent (and also because redux is async).
Maybe the action should take in both flagKey and newState?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the idea here, the values can only be "on" or "off", why does toggling not make sense?

@mlaws21
Copy link
Contributor Author

mlaws21 commented Jan 12, 2024

all changes requested changes are implemented besides changing the toggle, I don't understand exactly why that change would be necessary. A given feature should be on or off, and there is nothing to change to besides the other?

@yechs
Copy link
Member

yechs commented Jan 12, 2024

A given feature should be on or off, and there is nothing to change to besides the other?

True, but the toggle action is not idempotent.

I did some more search and it looks like redux actions are not strictly required to be idempotent.
However, writing it in an idempotent way makes your code more robust, especially in asynchronous contexts.
For example, you may dispatch two toggle actions, or two actions that set the same value to true.
The result would be different.

Also imagine the case where the data is corrupted (you never know what the browser/user would do to your stored data...).
Having it set to true/false instead of negating it would be more robust against unforeseen conditions.

@AndrewMuh
Copy link
Contributor

I would also add that boolean states are generally bad practice. It's best to store Enum states for clarity and in case we would like to support other feature states. For example we might want to support a senior-only state or a student-only state.

Copy link
Member

@yechs yechs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To Change:

  • Route name
  • avoid magic keywords

Need Discussion:

  • typing of states
  • frontend/backend logic divide

<Route
path="admin"
element={
<RequireScope token={apiToken} name="admin.dashboard">
Copy link
Member

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)

Comment on lines 5 to 10
export enum FFState {
Enabled = "Enabled",
Disabled = "Disabled",
Pending = "Pending",
}

Copy link
Member

Choose a reason for hiding this comment

The 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?
If so, should we implement it in union types? (given that enum cannot be extended) Or totally separate types?

Also, do you think the rendering logic like senior-only should be handled at backend or front-end?

This question is important, because it will affect how FeatureFlagElement should be implemented (in Nav.tsx)

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

@AndrewMuh AndrewMuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix small comments

@mlaws21 mlaws21 requested review from AndrewMuh and yechs January 23, 2024 15:52
@mlaws21
Copy link
Contributor Author

mlaws21 commented Jan 23, 2024

I added some styling to dashboard.tsx -- I probably should have made a new pr but I didn't want to branch off of another pr

Copy link
Member

@yechs yechs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far so good.

Some more code will need to be added on backend integration

  • Admin Dashboard: when flags are changed, send request to update them in backend API server
  • All pages (perhaps in App.tsx, similar to updateAPI()): on page load, send request to backend API for current feature flags

Related backend PR: https://github.com/WilliamsStudentsOnline/wso-go/pull/201

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants