diff --git a/app/src/App.tsx b/app/src/App.tsx
index 7e3264bc7..d84e74bce 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -3,17 +3,17 @@ import './styles/App.scss';
import PatientDetailsProvider from './providers/patientProvider/PatientProvider';
import SessionProvider from './providers/sessionProvider/SessionProvider';
import AppRouter from './router/AppRouter';
-import FeatureFlagsProvider from './providers/featureFlagsProvider/FeatureFlagsProvider';
+import ConfigProvider from './providers/configProvider/ConfigProvider';
function App() {
return (
-
+
-
+
);
}
diff --git a/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.test.tsx b/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.test.tsx
index 675240dcd..f2201c269 100644
--- a/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.test.tsx
+++ b/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.test.tsx
@@ -12,7 +12,7 @@ import { routes } from '../../../types/generic/routes';
import { LG_RECORD_STAGE } from '../../../types/blocks/lloydGeorgeStages';
import usePatient from '../../../helpers/hooks/usePatient';
-jest.mock('../../../helpers/hooks/useFeatureFlags');
+jest.mock('../../../helpers/hooks/useConfig');
jest.mock('../deletionConfirmationStage/DeletionConfirmationStage', () => () => (
Deletion complete
));
diff --git a/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.tsx b/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.tsx
index 3726968e0..8bbef0eb7 100644
--- a/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.tsx
+++ b/app/src/components/blocks/deleteDocumentsStage/DeleteDocumentsStage.tsx
@@ -21,7 +21,7 @@ import useBaseAPIUrl from '../../../helpers/hooks/useBaseAPIUrl';
import usePatient from '../../../helpers/hooks/usePatient';
import { errorToParams } from '../../../helpers/utils/errorToParams';
import { isMock } from '../../../helpers/utils/isLocal';
-import useFeatureFlags from '../../../helpers/hooks/useFeatureFlags';
+import useConfig from '../../../helpers/hooks/useConfig';
export type Props = {
docType: DOCUMENT_TYPE;
@@ -51,7 +51,7 @@ function DeleteDocumentsStage({
const baseUrl = useBaseAPIUrl();
const baseHeaders = useBaseAPIHeaders();
const navigate = useNavigate();
- const featureFlags = useFeatureFlags();
+ const config = useConfig();
const nhsNumber: string = patientDetails?.nhsNumber ?? '';
const formattedNhsNumber = formatNhsNumber(nhsNumber);
@@ -91,7 +91,7 @@ function DeleteDocumentsStage({
}
} catch (e) {
const error = e as AxiosError;
- if (isMock(error) && !!featureFlags.mockLocal.recordUploaded) {
+ if (isMock(error) && !!config.mockLocal.recordUploaded) {
onSuccess();
} else {
if (error.response?.status === 403) {
diff --git a/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.test.tsx b/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.test.tsx
index 5327f1190..883ced258 100644
--- a/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.test.tsx
+++ b/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.test.tsx
@@ -7,11 +7,14 @@ import userEvent from '@testing-library/user-event';
import usePatient from '../../../helpers/hooks/usePatient';
import { LinkProps } from 'react-router-dom';
import { routes } from '../../../types/generic/routes';
+import useConfig from '../../../helpers/hooks/useConfig';
+import { defaultFeatureFlags } from '../../../helpers/requests/getFeatureFlags';
-jest.mock('../../../helpers/hooks/useFeatureFlags');
+jest.mock('../../../helpers/hooks/useConfig');
const mockedUseNavigate = jest.fn();
const mockedAxios = axios as jest.Mocked;
const mockedUsePatient = usePatient as jest.Mock;
+const mockUseConfig = useConfig as jest.Mock;
const mockPdf = buildLgSearchResult();
const mockPatient = buildPatientDetails();
const mockSetStage = jest.fn();
@@ -32,6 +35,7 @@ describe('LloydGeorgeDownloadAllStage', () => {
beforeEach(() => {
process.env.REACT_APP_ENVIRONMENT = 'jest';
mockedUsePatient.mockReturnValue(mockPatient);
+ mockUseConfig.mockReturnValue({ featureFlags: defaultFeatureFlags, mockLocal: {} });
});
afterEach(() => {
jest.clearAllMocks();
diff --git a/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.tsx b/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.tsx
index 41927bb7e..3e7706baf 100644
--- a/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.tsx
+++ b/app/src/components/blocks/lloydGeorgeDownloadAllStage/LloydGeorgeDownloadAllStage.tsx
@@ -22,7 +22,7 @@ import { useNavigate, Link } from 'react-router-dom';
import { errorToParams } from '../../../helpers/utils/errorToParams';
import { AxiosError } from 'axios/index';
import { isMock } from '../../../helpers/utils/isLocal';
-import useFeatureFlags from '../../../helpers/hooks/useFeatureFlags';
+import useConfig from '../../../helpers/hooks/useConfig';
const FakeProgress = require('fake-progress');
@@ -56,7 +56,7 @@ function LloydGeorgeDownloadAllStage({
const linkRef = useRef(null);
const mounted = useRef(false);
const navigate = useNavigate();
- const featureFlags = useFeatureFlags();
+ const { mockLocal } = useConfig();
const patientDetails = usePatient();
const nhsNumber = patientDetails?.nhsNumber ?? '';
const [delayTimer, setDelayTimer] = useState();
@@ -89,7 +89,7 @@ function LloydGeorgeDownloadAllStage({
useEffect(() => {
const onFail = (error: AxiosError) => {
- if (isMock(error) && !!featureFlags.mockLocal.recordUploaded) {
+ if (isMock(error) && !!mockLocal.recordUploaded) {
if (typeof window !== 'undefined') {
const { protocol, host } = window.location;
setLinkAttributes({
@@ -149,7 +149,7 @@ function LloydGeorgeDownloadAllStage({
progressTimer,
deleteAfterDownload,
navigate,
- featureFlags,
+ mockLocal,
]);
return inProgress ? (
diff --git a/app/src/components/blocks/testPanel/TestPanel.tsx b/app/src/components/blocks/testPanel/TestPanel.tsx
index 1dfb318d0..42383a0ea 100644
--- a/app/src/components/blocks/testPanel/TestPanel.tsx
+++ b/app/src/components/blocks/testPanel/TestPanel.tsx
@@ -1,24 +1,21 @@
import React from 'react';
import 'react-toggle/style.css';
import { isLocal } from '../../../helpers/utils/isLocal';
-import {
- LocalFlags,
- useFeatureFlagsContext,
-} from '../../../providers/featureFlagsProvider/FeatureFlagsProvider';
+import { LocalFlags, useConfigContext } from '../../../providers/configProvider/ConfigProvider';
import { REPOSITORY_ROLE } from '../../../types/generic/authRole';
import TestToggle, { ToggleProps } from './TestToggle';
function TestPanel() {
- const [featureFlags, setFeatureFlags] = useFeatureFlagsContext();
- const { isBsol, recordUploaded, userRole } = featureFlags.mockLocal;
+ const [config, setConfig] = useConfigContext();
+ const { isBsol, recordUploaded, userRole } = config.mockLocal;
const updateLocalFlag = (key: keyof LocalFlags, value: boolean | REPOSITORY_ROLE) => {
- setFeatureFlags({
- ...featureFlags,
+ setConfig({
mockLocal: {
- ...featureFlags.mockLocal,
+ ...config.mockLocal,
[key]: value,
},
+ featureFlags: config.featureFlags,
});
};
diff --git a/app/src/helpers/hooks/useConfig.test.tsx b/app/src/helpers/hooks/useConfig.test.tsx
new file mode 100644
index 000000000..6b4e544f0
--- /dev/null
+++ b/app/src/helpers/hooks/useConfig.test.tsx
@@ -0,0 +1,45 @@
+import { render, screen } from '@testing-library/react';
+import useConfig from './useConfig';
+import ConfigProvider, { GlobalConfig } from '../../providers/configProvider/ConfigProvider';
+import { defaultFeatureFlags } from '../requests/getFeatureFlags';
+
+describe('useConfig', () => {
+ beforeEach(() => {
+ sessionStorage.setItem('FeatureFlags', '');
+ process.env.REACT_APP_ENVIRONMENT = 'jest';
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('returns true when feature flag in context', () => {
+ const config: GlobalConfig = {
+ featureFlags: { ...defaultFeatureFlags, testFeature1: true },
+ mockLocal: {},
+ };
+ renderHook(config);
+ expect(screen.getByText(`FLAG: true`)).toBeInTheDocument();
+ });
+
+ it('returns false when there is no feature flag in context', () => {
+ const config: GlobalConfig = {
+ featureFlags: { ...defaultFeatureFlags, testFeature1: false },
+ mockLocal: {},
+ };
+ renderHook(config);
+ expect(screen.getByText(`FLAG: false`)).toBeInTheDocument();
+ });
+});
+
+const TestApp = () => {
+ const config = useConfig();
+ return {`FLAG: ${!!config.featureFlags.testFeature1}`.normalize()}
;
+};
+
+const renderHook = (config?: GlobalConfig) => {
+ return render(
+
+
+ ,
+ );
+};
diff --git a/app/src/helpers/hooks/useConfig.tsx b/app/src/helpers/hooks/useConfig.tsx
new file mode 100644
index 000000000..4025f917b
--- /dev/null
+++ b/app/src/helpers/hooks/useConfig.tsx
@@ -0,0 +1,8 @@
+import { useConfigContext } from '../../providers/configProvider/ConfigProvider';
+
+function useConfig() {
+ const [config] = useConfigContext();
+ return config;
+}
+
+export default useConfig;
diff --git a/app/src/helpers/hooks/useFeatureFlags.test.tsx b/app/src/helpers/hooks/useFeatureFlags.test.tsx
deleted file mode 100644
index d1931c940..000000000
--- a/app/src/helpers/hooks/useFeatureFlags.test.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import useFeatureFlags from './useFeatureFlags';
-import FeatureFlagsProvider, {
- FeatureFlags,
-} from '../../providers/featureFlagsProvider/FeatureFlagsProvider';
-
-describe('useFeatureFlags', () => {
- beforeEach(() => {
- sessionStorage.setItem('FeatureFlags', '');
- process.env.REACT_APP_ENVIRONMENT = 'jest';
- });
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('returns true when feature flag in context', () => {
- const appConfig: Partial = { appConfig: { testFeature: true } };
- renderHook(appConfig);
- expect(screen.getByText(`FLAG: true`)).toBeInTheDocument();
- });
-
- it('returns false when there is no feature flag in context', () => {
- const appConfig: Partial = { appConfig: {} };
- renderHook(appConfig);
- expect(screen.getByText(`FLAG: false`)).toBeInTheDocument();
- });
-});
-
-const TestApp = () => {
- const featureFlags = useFeatureFlags();
- return {`FLAG: ${!!featureFlags.appConfig.testFeature}`.normalize()}
;
-};
-
-const renderHook = (featureFlags?: Partial) => {
- return render(
-
-
- ,
- );
-};
diff --git a/app/src/helpers/hooks/useFeatureFlags.tsx b/app/src/helpers/hooks/useFeatureFlags.tsx
deleted file mode 100644
index 115a65085..000000000
--- a/app/src/helpers/hooks/useFeatureFlags.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { useFeatureFlagsContext } from '../../providers/featureFlagsProvider/FeatureFlagsProvider';
-
-function useFeatureFlags() {
- const [featureFlags] = useFeatureFlagsContext();
- return featureFlags;
-}
-
-export default useFeatureFlags;
diff --git a/app/src/helpers/requests/getFeatureFlags.ts b/app/src/helpers/requests/getFeatureFlags.ts
new file mode 100644
index 000000000..3a45ffd78
--- /dev/null
+++ b/app/src/helpers/requests/getFeatureFlags.ts
@@ -0,0 +1,36 @@
+import { AuthHeaders } from '../../types/blocks/authHeaders';
+import { endpoints } from '../../types/generic/endpoints';
+
+import axios from 'axios';
+import { FeatureFlags } from '../../types/generic/featureFlags';
+
+type Args = {
+ baseUrl: string;
+ baseHeaders: AuthHeaders;
+};
+
+type GetFeatureFlagsResponse = {
+ data: FeatureFlags;
+};
+
+export const defaultFeatureFlags = {
+ testFeature1: false,
+ testFeature2: false,
+ testFeature3: false,
+};
+
+const getFeatureFlags = async ({ baseUrl, baseHeaders }: Args) => {
+ const gatewayUrl = baseUrl + endpoints.FEATURE_FLAGS;
+ try {
+ const { data }: GetFeatureFlagsResponse = await axios.get(gatewayUrl, {
+ headers: {
+ ...baseHeaders,
+ },
+ });
+ return data;
+ } catch (e) {
+ return defaultFeatureFlags;
+ }
+};
+
+export default getFeatureFlags;
diff --git a/app/src/pages/authCallbackPage/AuthCallbackPage.test.tsx b/app/src/pages/authCallbackPage/AuthCallbackPage.test.tsx
index 023e25a41..315adb5ee 100644
--- a/app/src/pages/authCallbackPage/AuthCallbackPage.test.tsx
+++ b/app/src/pages/authCallbackPage/AuthCallbackPage.test.tsx
@@ -1,11 +1,17 @@
import { render, screen, waitFor } from '@testing-library/react';
import AuthCallbackPage from './AuthCallbackPage';
-import SessionProvider from '../../providers/sessionProvider/SessionProvider';
+import SessionProvider, {
+ useSessionContext,
+} from '../../providers/sessionProvider/SessionProvider';
import axios from 'axios';
import { buildUserAuth } from '../../helpers/test/testBuilders';
import { routes } from '../../types/generic/routes';
+import ConfigProvider, { useConfigContext } from '../../providers/configProvider/ConfigProvider';
+import { act } from 'react-dom/test-utils';
+import { endpoints } from '../../types/generic/endpoints';
+import { defaultFeatureFlags } from '../../helpers/requests/getFeatureFlags';
-jest.mock('../../helpers/hooks/useFeatureFlags');
+jest.mock('../../helpers/hooks/useConfig');
const mockedUseNavigate = jest.fn();
jest.mock('axios');
jest.mock('react-router', () => ({
@@ -26,7 +32,7 @@ const originalWindowLocation = window.location;
describe('AuthCallbackPage', () => {
beforeEach(() => {
process.env.REACT_APP_ENVIRONMENT = 'jest';
-
+ sessionStorage.setItem('FeatureFlags', '');
Object.defineProperty(window, 'location', {
configurable: true,
enumerable: true,
@@ -42,71 +48,118 @@ describe('AuthCallbackPage', () => {
});
});
- it('returns a loading state until redirection to token request handler', async () => {
- mockedAxios.get.mockImplementation(() => Promise.resolve({ data: buildUserAuth() }));
- renderCallbackPage();
- expect(screen.getByRole('status')).toBeInTheDocument();
- expect(screen.getByText('Logging in...')).toBeInTheDocument();
-
- await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
+ describe('Rendering', () => {
+ it('returns a loading state until redirection to token request handler', async () => {
+ mockedAxios.get.mockImplementation(() => Promise.resolve({ data: buildUserAuth() }));
+ act(() => {
+ renderCallbackPage();
+ });
+ expect(screen.getByRole('status')).toBeInTheDocument();
+ expect(screen.getByText('Logging in...')).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
+ });
});
});
- it('navigates to the select role page when callback token request is successful', async () => {
- mockedAxios.get.mockImplementation(() => Promise.resolve({ data: buildUserAuth() }));
- renderCallbackPage();
+ describe('Navigation', () => {
+ it('navigates to the select role page when callback token request is successful', async () => {
+ mockedAxios.get.mockImplementation(() => Promise.resolve({ data: buildUserAuth() }));
+ renderCallbackPage();
- expect(screen.getByRole('status')).toBeInTheDocument();
- expect(screen.getByText('Logging in...')).toBeInTheDocument();
+ expect(screen.getByRole('status')).toBeInTheDocument();
+ expect(screen.getByText('Logging in...')).toBeInTheDocument();
- await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
+ });
});
- });
- it('navigates to auth error page when callback token request is unsuccessful', async () => {
- const errorResponse = {
- response: {
- status: 400,
- message: '400 Bad Request',
- },
- };
+ it('navigates to auth error page when callback token request is unsuccessful', async () => {
+ const errorResponse = {
+ response: {
+ status: 400,
+ message: '400 Bad Request',
+ },
+ };
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
- renderCallbackPage();
+ mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
+ renderCallbackPage();
- expect(screen.getByRole('status')).toBeInTheDocument();
- expect(screen.getByText('Logging in...')).toBeInTheDocument();
+ expect(screen.getByRole('status')).toBeInTheDocument();
+ expect(screen.getByText('Logging in...')).toBeInTheDocument();
- await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.AUTH_ERROR);
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.AUTH_ERROR);
+ });
+ });
+ it('navigates to unauthorised login page when callback token request is 401', async () => {
+ const errorResponse = {
+ response: {
+ status: 401,
+ message: '401 Unauthorised',
+ },
+ };
+
+ mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
+ renderCallbackPage();
+
+ expect(screen.getByRole('status')).toBeInTheDocument();
+ expect(screen.getByText('Logging in...')).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.UNAUTHORISED_LOGIN);
+ });
});
});
- it('navigates to unauthorised login page when callback token request is 401', async () => {
- const errorResponse = {
- response: {
- status: 401,
- message: '401 Unauthorised',
- },
- };
-
- mockedAxios.get.mockImplementation(() => Promise.reject(errorResponse));
- renderCallbackPage();
-
- expect(screen.getByRole('status')).toBeInTheDocument();
- expect(screen.getByText('Logging in...')).toBeInTheDocument();
-
- await waitFor(() => {
- expect(mockedUseNavigate).toHaveBeenCalledWith(routes.UNAUTHORISED_LOGIN);
+
+ describe('Config', () => {
+ it('sets session context to user is has a role', async () => {
+ mockedAxios.get.mockImplementation(() => Promise.resolve({ data: buildUserAuth() }));
+ renderCallbackPage();
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
+ });
+ expect(screen.getByText('LOGGEDIN: true')).toBeInTheDocument();
+ });
+ it('sets config context to user is has feature flags', async () => {
+ mockedAxios.get.mockImplementation((url) => {
+ if (url.includes(endpoints.AUTH)) {
+ return Promise.resolve({ data: buildUserAuth() });
+ } else {
+ return Promise.resolve({
+ data: { ...defaultFeatureFlags, testFeature1: true },
+ });
+ }
+ });
+ renderCallbackPage();
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(routes.HOME);
+ });
+ expect(screen.getByText('FLAG: true')).toBeInTheDocument();
});
});
});
+const TestApp = () => {
+ const [config] = useConfigContext();
+ const [session] = useSessionContext();
+ return (
+
+
+
{`FLAG: ${JSON.stringify(config.featureFlags.testFeature1)}`.normalize()}
;
+
{`LOGGEDIN: ${!!session.auth?.role}`.normalize()}
;
+
+ );
+};
+
const renderCallbackPage = () => {
render(
-
+
+
+
,
);
};
diff --git a/app/src/pages/authCallbackPage/AuthCallbackPage.tsx b/app/src/pages/authCallbackPage/AuthCallbackPage.tsx
index 734030a44..59807b65f 100644
--- a/app/src/pages/authCallbackPage/AuthCallbackPage.tsx
+++ b/app/src/pages/authCallbackPage/AuthCallbackPage.tsx
@@ -10,15 +10,16 @@ import { buildUserAuth } from '../../helpers/test/testBuilders';
import { UserAuth } from '../../types/blocks/userAuth';
import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl';
import { REPOSITORY_ROLE } from '../../types/generic/authRole';
-import useFeatureFlags from '../../helpers/hooks/useFeatureFlags';
+import { useConfigContext } from '../../providers/configProvider/ConfigProvider';
+import getFeatureFlags from '../../helpers/requests/getFeatureFlags';
type Props = {};
const AuthCallbackPage = (props: Props) => {
const baseUrl = useBaseAPIUrl();
const [, setSession] = useSessionContext();
+ const [{ mockLocal }, setConfig] = useConfigContext();
const navigate = useNavigate();
- const featureFlags = useFeatureFlags();
useEffect(() => {
const handleError = (error: AxiosError) => {
@@ -32,12 +33,24 @@ const AuthCallbackPage = (props: Props) => {
navigate(routes.AUTH_ERROR);
}
};
- const handleSuccess = (auth: UserAuth) => {
+ const handleSuccess = async (auth: UserAuth) => {
const { GP_ADMIN, GP_CLINICAL, PCSE } = REPOSITORY_ROLE;
setSession({
auth: auth,
isLoggedIn: true,
});
+ const jwtToken = auth.authorisation_token ?? '';
+ const featureFlagsData = await getFeatureFlags({
+ baseUrl,
+ baseHeaders: {
+ 'Content-Type': 'application/json',
+ Authorization: jwtToken,
+ },
+ });
+ setConfig({
+ mockLocal: mockLocal,
+ featureFlags: featureFlagsData,
+ });
if ([GP_ADMIN, GP_CLINICAL, PCSE].includes(auth.role)) {
navigate(routes.HOME);
@@ -49,12 +62,12 @@ const AuthCallbackPage = (props: Props) => {
const handleCallback = async (args: AuthTokenArgs) => {
try {
const authData = await getAuthToken(args);
- handleSuccess(authData);
+ await handleSuccess(authData);
} catch (e) {
const error = e as AxiosError;
if (isMock(error)) {
- const { isBsol, userRole } = featureFlags.mockLocal;
- handleSuccess(buildUserAuth({ isBSOL: !!isBsol, role: userRole }));
+ const { isBsol, userRole } = mockLocal;
+ await handleSuccess(buildUserAuth({ isBSOL: !!isBsol, role: userRole }));
} else {
handleError(error);
}
@@ -65,7 +78,7 @@ const AuthCallbackPage = (props: Props) => {
const code = urlSearchParams.get('code') ?? '';
const state = urlSearchParams.get('state') ?? '';
void handleCallback({ baseUrl, code, state });
- }, [baseUrl, setSession, navigate, featureFlags]);
+ }, [baseUrl, setSession, navigate, mockLocal, setConfig]);
return ;
};
diff --git a/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.test.tsx b/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.test.tsx
index 279c33fbd..9eb97acd9 100644
--- a/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.test.tsx
+++ b/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.test.tsx
@@ -12,7 +12,7 @@ import usePatient from '../../helpers/hooks/usePatient';
import { act } from 'react-dom/test-utils';
import { routes } from '../../types/generic/routes';
-jest.mock('../../helpers/hooks/useFeatureFlags');
+jest.mock('../../helpers/hooks/useConfig');
jest.mock('axios');
jest.mock('../../helpers/hooks/usePatient');
jest.mock('../../helpers/hooks/useBaseAPIHeaders');
diff --git a/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx b/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx
index ec73e3b76..32b89a850 100644
--- a/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx
+++ b/app/src/pages/lloydGeorgeRecordPage/LloydGeorgeRecordPage.tsx
@@ -19,7 +19,7 @@ import { useNavigate } from 'react-router';
import { errorToParams } from '../../helpers/utils/errorToParams';
import { isMock } from '../../helpers/utils/isLocal';
import moment from 'moment';
-import useFeatureFlags from '../../helpers/hooks/useFeatureFlags';
+import useConfig from '../../helpers/hooks/useConfig';
function LloydGeorgeRecordPage() {
const patientDetails = usePatient();
@@ -33,7 +33,7 @@ function LloydGeorgeRecordPage() {
const mounted = useRef(false);
const [stage, setStage] = useState(LG_RECORD_STAGE.RECORD);
const navigate = useNavigate();
- const featureFlags = useFeatureFlags();
+ const config = useConfig();
const role = useRole();
const isBSOL = useIsBSOL();
const deleteAfterDownload = role === REPOSITORY_ROLE.GP_ADMIN && isBSOL === false;
@@ -68,7 +68,7 @@ function LloydGeorgeRecordPage() {
} catch (e) {
const error = e as AxiosError;
if (isMock(error)) {
- if (!!featureFlags.mockLocal.recordUploaded) {
+ if (!!config.mockLocal.recordUploaded) {
onSuccess(1, moment().format(), '/dev/testFile.pdf', 59000);
} else {
setDownloadStage(DOWNLOAD_STAGE.NO_RECORDS);
@@ -103,7 +103,7 @@ function LloydGeorgeRecordPage() {
setNumberOfFiles,
setTotalFileSizeInByte,
navigate,
- featureFlags,
+ config,
]);
switch (stage) {
diff --git a/app/src/providers/featureFlagsProvider/FeatureFlagsProvider.test.tsx b/app/src/providers/configProvider/ConfigProvider.test.tsx
similarity index 63%
rename from app/src/providers/featureFlagsProvider/FeatureFlagsProvider.test.tsx
rename to app/src/providers/configProvider/ConfigProvider.test.tsx
index 9ad2e920a..375114bc0 100644
--- a/app/src/providers/featureFlagsProvider/FeatureFlagsProvider.test.tsx
+++ b/app/src/providers/configProvider/ConfigProvider.test.tsx
@@ -1,6 +1,7 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import FeatureFlagsProvider, { FeatureFlags, useFeatureFlagsContext } from './FeatureFlagsProvider';
+import ConfigProvider, { GlobalConfig, useConfigContext } from './ConfigProvider';
+import { defaultFeatureFlags } from '../../helpers/requests/getFeatureFlags';
describe('SessionProvider', () => {
beforeEach(() => {
process.env.REACT_APP_ENVIRONMENT = 'jest';
@@ -31,27 +32,31 @@ describe('SessionProvider', () => {
});
const TestApp = () => {
- const [featureFlags, setFeatureFlags] = useFeatureFlagsContext();
- const flagOn: FeatureFlags = {
- ...featureFlags,
- appConfig: {
- testFeature: true,
+ const [config, setConfig] = useConfigContext();
+ const flagOn: GlobalConfig = {
+ ...config,
+ featureFlags: {
+ ...defaultFeatureFlags,
+ testFeature1: true,
},
};
- const flagOff: FeatureFlags = {
- ...featureFlags,
- appConfig: {},
+ const flagOff: GlobalConfig = {
+ ...config,
+ featureFlags: {
+ ...defaultFeatureFlags,
+ testFeature1: false,
+ },
};
return (
<>
Actions
-
setFeatureFlags(flagOn)}>Flag On
-
setFeatureFlags(flagOff)}>Flag Off
+
setConfig(flagOn)}>Flag On
+
setConfig(flagOff)}>Flag Off
Flags
- testFeature - {`${!!featureFlags.appConfig.testFeature}`}
+ testFeature - {`${!!config.featureFlags.testFeature1}`}
>
);
@@ -59,8 +64,8 @@ const TestApp = () => {
const renderFeatureFlagsProvider = () => {
render(
-
+
- ,
+ ,
);
};
diff --git a/app/src/providers/configProvider/ConfigProvider.tsx b/app/src/providers/configProvider/ConfigProvider.tsx
new file mode 100644
index 000000000..aeb1d43e4
--- /dev/null
+++ b/app/src/providers/configProvider/ConfigProvider.tsx
@@ -0,0 +1,77 @@
+import { createContext, useContext, useEffect, useMemo, useState } from 'react';
+import type { Dispatch, ReactNode, SetStateAction } from 'react';
+import { REPOSITORY_ROLE } from '../../types/generic/authRole';
+import { FeatureFlags } from '../../types/generic/featureFlags';
+import { defaultFeatureFlags } from '../../helpers/requests/getFeatureFlags';
+import { isLocal } from '../../helpers/utils/isLocal';
+
+type SetConfigOverride = (config: GlobalConfig) => void;
+
+type Props = {
+ children: ReactNode;
+ configOverride?: Partial;
+ setConfigOverride?: SetConfigOverride;
+};
+
+export type LocalFlags = {
+ isBsol?: boolean;
+ recordUploaded?: boolean;
+ userRole?: REPOSITORY_ROLE;
+};
+
+export type GlobalConfig = {
+ featureFlags: FeatureFlags;
+ mockLocal: LocalFlags;
+};
+
+export type TConfigContext = [
+ GlobalConfig,
+ Dispatch> | SetConfigOverride,
+];
+
+const ConfigContext = createContext(null);
+const ConfigProvider = ({ children, configOverride, setConfigOverride }: Props) => {
+ const emptyConfig = useMemo(
+ () => ({
+ featureFlags: { ...defaultFeatureFlags, ...configOverride?.featureFlags },
+ mockLocal: {
+ ...configOverride?.mockLocal,
+ },
+ }),
+ [configOverride],
+ );
+ const defaultMockLocals = isLocal
+ ? {
+ isBsol: true,
+ recordUploaded: true,
+ userRole: REPOSITORY_ROLE.GP_ADMIN,
+ }
+ : null;
+ const storedConfig = sessionStorage.getItem('AppConfig');
+ const currentConfig: GlobalConfig = storedConfig ? JSON.parse(storedConfig) : emptyConfig;
+ const [config, setConfig] = useState({
+ mockLocal: {
+ ...defaultMockLocals,
+ ...currentConfig.mockLocal,
+ ...configOverride?.mockLocal,
+ },
+ featureFlags: {
+ ...defaultFeatureFlags,
+ ...currentConfig.featureFlags,
+ ...configOverride?.featureFlags,
+ },
+ });
+
+ useEffect(() => {
+ sessionStorage.setItem('AppConfig', JSON.stringify(config) ?? emptyConfig);
+ }, [config, emptyConfig]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default ConfigProvider;
+export const useConfigContext = () => useContext(ConfigContext) as TConfigContext;
diff --git a/app/src/providers/featureFlagsProvider/FeatureFlagsProvider.tsx b/app/src/providers/featureFlagsProvider/FeatureFlagsProvider.tsx
deleted file mode 100644
index 9f19de5d3..000000000
--- a/app/src/providers/featureFlagsProvider/FeatureFlagsProvider.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { createContext, useContext, useEffect, useMemo, useState } from 'react';
-import type { Dispatch, ReactNode, SetStateAction } from 'react';
-import { REPOSITORY_ROLE } from '../../types/generic/authRole';
-import { isLocal } from '../../helpers/utils/isLocal';
-
-type SetFeatureFlagsOverride = (featureFlags: FeatureFlags) => void;
-
-type Props = {
- children: ReactNode;
- featureFlagsOverride?: Partial;
- setFeatureFlagsOverride?: SetFeatureFlagsOverride;
-};
-
-export type LocalFlags = {
- isBsol?: boolean;
- recordUploaded?: boolean;
- userRole?: REPOSITORY_ROLE;
-};
-
-export type FeatureFlags = {
- appConfig: {
- testFeature?: true;
- testRoute?: true;
- };
- mockLocal: LocalFlags;
-};
-
-export type TFeatureFlagsContext = [
- FeatureFlags,
- Dispatch> | SetFeatureFlagsOverride,
-];
-
-const FeatureFlagsContext = createContext(null);
-const FeatureFlagsProvider = ({
- children,
- featureFlagsOverride,
- setFeatureFlagsOverride,
-}: Props) => {
- const emptyFlags = useMemo(
- () => ({
- appConfig: { ...featureFlagsOverride?.appConfig },
- mockLocal: {
- ...featureFlagsOverride?.mockLocal,
- },
- }),
- [featureFlagsOverride],
- );
- const localDefaults = isLocal
- ? {
- isBsol: true,
- recordUploaded: true,
- userRole: REPOSITORY_ROLE.GP_ADMIN,
- ...featureFlagsOverride?.mockLocal,
- }
- : null;
- const storedFlags = sessionStorage.getItem('FeatureFlags');
- const flags: FeatureFlags = storedFlags ? JSON.parse(storedFlags) : emptyFlags;
- const [featureFlags, setFeatureFlags] = useState({
- mockLocal: {
- ...localDefaults,
- ...flags.mockLocal,
- ...featureFlagsOverride?.mockLocal,
- },
- appConfig: {
- ...flags.appConfig,
- ...featureFlagsOverride?.appConfig,
- },
- });
-
- useEffect(() => {
- sessionStorage.setItem('FeatureFlags', JSON.stringify(featureFlags) ?? emptyFlags);
- }, [featureFlags, emptyFlags]);
-
- return (
-
- {children}
-
- );
-};
-
-export default FeatureFlagsProvider;
-export const useFeatureFlagsContext = () => useContext(FeatureFlagsContext) as TFeatureFlagsContext;
diff --git a/app/src/types/generic/endpoints.ts b/app/src/types/generic/endpoints.ts
index f3f386db0..a12644172 100644
--- a/app/src/types/generic/endpoints.ts
+++ b/app/src/types/generic/endpoints.ts
@@ -11,4 +11,6 @@ export enum endpoints {
LLOYDGEORGE_STITCH = '/LloydGeorgeStitch',
FEEDBACK = '/Feedback',
+
+ FEATURE_FLAGS = '/FeatureFlags',
}
diff --git a/app/src/types/generic/featureFlags.ts b/app/src/types/generic/featureFlags.ts
new file mode 100644
index 000000000..275ba9eb7
--- /dev/null
+++ b/app/src/types/generic/featureFlags.ts
@@ -0,0 +1,5 @@
+export type FeatureFlags = {
+ testFeature1: boolean;
+ testFeature2: boolean;
+ testFeature3: boolean;
+};