From f73dffea7cdcb52bcc7f0a931037a80d78c521ae Mon Sep 17 00:00:00 2001
From: doug-s-nava <92806979+doug-s-nava@users.noreply.github.com>
Date: Mon, 6 Jan 2025 15:55:51 -0500
Subject: [PATCH] [Issue #3249] Add auth ui feature flag and refactor feature
flag system 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
---
.../dev/feature-flags/FeatureFlagsTable.tsx | 64 +--
frontend/src/components/Header.tsx | 14 +-
frontend/src/components/Layout.tsx | 16 +-
frontend/src/constants/defaultFeatureFlags.ts | 9 +
frontend/src/constants/environments.ts | 33 +-
frontend/src/constants/featureFlags.ts | 8 -
frontend/src/hoc/withFeatureFlag.tsx | 5 +-
frontend/src/hooks/useFeatureFlags.ts | 94 ++--
frontend/src/middleware.ts | 13 +-
frontend/src/services/FeatureFlagManager.ts | 308 -----------
.../featureFlags/FeatureFlagManager.ts | 148 ++++++
.../featureFlags/featureFlagHelpers.ts | 115 ++++
frontend/src/utils/generalUtils.ts | 9 +-
.../src/utils/testing/FeatureFlagTestUtils.ts | 12 +-
frontend/tests/components/Header.test.tsx | 6 +
frontend/tests/hooks/useFeatureFlags.test.ts | 81 ++-
.../page.test.tsx} | 46 +-
.../tests/services/FeatureFlagManager.test.ts | 502 ------------------
.../featureFlags/FeatureFlagManager.test.ts | 299 +++++++++++
.../featureFlags/featureFlagHelpers.test.ts | 202 +++++++
.../tests/services/withFeatureFlag.test.tsx | 4 +-
.../env-config/environment-variables.tf | 8 +-
22 files changed, 1019 insertions(+), 977 deletions(-)
create mode 100644 frontend/src/constants/defaultFeatureFlags.ts
delete mode 100644 frontend/src/constants/featureFlags.ts
delete mode 100644 frontend/src/services/FeatureFlagManager.ts
create mode 100644 frontend/src/services/featureFlags/FeatureFlagManager.ts
create mode 100644 frontend/src/services/featureFlags/featureFlagHelpers.ts
rename frontend/tests/pages/dev/{feature-flags.test.tsx => feature-flags/page.test.tsx} (60%)
delete mode 100644 frontend/tests/services/FeatureFlagManager.test.ts
create mode 100644 frontend/tests/services/featureFlags/FeatureFlagManager.test.ts
create mode 100644 frontend/tests/services/featureFlags/featureFlagHelpers.test.ts
diff --git a/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx b/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx
index b0c2fe1ae..c800fe9f1 100644
--- a/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx
+++ b/frontend/src/app/[locale]/dev/feature-flags/FeatureFlagsTable.tsx
@@ -12,13 +12,9 @@ import Loading from "src/components/Loading";
* View for managing feature flags
*/
export default function FeatureFlagsTable() {
- const { featureFlagsManager, mounted, setFeatureFlag } = useFeatureFlags();
+ const { setFeatureFlag, featureFlags } = useFeatureFlags();
const { user, isLoading, error } = useUser();
- if (!mounted) {
- return null;
- }
-
if (isLoading) {
return
(
) => {
const searchParams = props.searchParams || {};
const ComponentWithFeatureFlag = (props: P & WithFeatureFlagProps) => {
- const featureFlagsManager = new FeatureFlagsManager(cookies());
-
if (
featureFlagsManager.isFeatureEnabled(
featureFlagName,
+ cookies(),
props.searchParams,
)
) {
diff --git a/frontend/src/hooks/useFeatureFlags.ts b/frontend/src/hooks/useFeatureFlags.ts
index 9432d2848..b02bd8635 100644
--- a/frontend/src/hooks/useFeatureFlags.ts
+++ b/frontend/src/hooks/useFeatureFlags.ts
@@ -1,48 +1,70 @@
+"use client";
+
import Cookies from "js-cookie";
-import { FeatureFlagsManager } from "src/services/FeatureFlagManager";
+import { isBoolean } from "lodash";
+import {
+ defaultFeatureFlags,
+ FeatureFlags,
+} from "src/constants/defaultFeatureFlags";
+import {
+ FEATURE_FLAGS_KEY,
+ getCookieExpiration,
+} from "src/services/featureFlags/featureFlagHelpers";
-import { useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
/**
- * React hook for reading and managing feature flags in client-side code.
- *
- * ```
- * function MyComponent() {
- * const {
- * featureFlagsManager, // An instance of FeatureFlagsManager
- * mounted, // Useful for hydration
- * setFeatureFlag, // Proxy for featureFlagsManager.setFeatureFlagCookie that handles updating state
- * } = useFeatureFlags()
- *
- * if (featureFlagsManager.isFeatureEnabled("someFeatureFlag")) {
- * // Do something
- * }
- *
- * if (!mounted) {
- * // To allow hydration
- * return null
- * }
+ * Allows client components to access feature flags by
+ * - setting the cookie
+ * - reading the cookie
*
- * return (
- * ...
- * )
- * }
- * ```
*/
-export function useFeatureFlags() {
- const [featureFlagsManager, setFeatureFlagsManager] = useState(
- new FeatureFlagsManager(Cookies),
- );
- const [mounted, setMounted] = useState(false);
+export function useFeatureFlags(): {
+ setFeatureFlag: (flagName: string, value: boolean) => void;
+ checkFeatureFlag: (flagName: string) => boolean;
+ featureFlags: FeatureFlags;
+} {
+ const [featureFlags, setFeatureFlags] =
+ useState