Skip to content

Commit

Permalink
Add feature flag check to ARF journey
Browse files Browse the repository at this point in the history
  • Loading branch information
abbas-khan10 committed Feb 26, 2024
1 parent 449839a commit 4348a47
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 46 deletions.
8 changes: 4 additions & 4 deletions app/src/helpers/hooks/useConfig.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import useConfig from './useConfig';
import ConfigProvider, { GlobalConfig } from '../../providers/configProvider/ConfigProvider';
import { defaultFeatureFlags } from '../requests/getFeatureFlags';
import { defaultFeatureFlags } from '../../types/generic/featureFlags';

describe('useConfig', () => {
beforeEach(() => {
Expand All @@ -14,7 +14,7 @@ describe('useConfig', () => {

it('returns true when feature flag in context', () => {
const config: GlobalConfig = {
featureFlags: { ...defaultFeatureFlags, testFeature1: true },
featureFlags: { ...defaultFeatureFlags, uploadLloydGeorgeWorkflowEnabled: true },
mockLocal: {},
};
renderHook(config);
Expand All @@ -23,7 +23,7 @@ describe('useConfig', () => {

it('returns false when there is no feature flag in context', () => {
const config: GlobalConfig = {
featureFlags: { ...defaultFeatureFlags, testFeature1: false },
featureFlags: { ...defaultFeatureFlags, uploadLloydGeorgeWorkflowEnabled: false },
mockLocal: {},
};
renderHook(config);
Expand All @@ -33,7 +33,7 @@ describe('useConfig', () => {

const TestApp = () => {
const config = useConfig();
return <div>{`FLAG: ${!!config.featureFlags.testFeature1}`.normalize()}</div>;
return <div>{`FLAG: ${config.featureFlags.uploadLloydGeorgeWorkflowEnabled}`.normalize()}</div>;
};

const renderHook = (config?: GlobalConfig) => {
Expand Down
8 changes: 1 addition & 7 deletions app/src/helpers/requests/getFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AuthHeaders } from '../../types/blocks/authHeaders';
import { endpoints } from '../../types/generic/endpoints';

import axios from 'axios';
import { FeatureFlags } from '../../types/generic/featureFlags';
import { defaultFeatureFlags, FeatureFlags } from '../../types/generic/featureFlags';

type Args = {
baseUrl: string;
Expand All @@ -13,12 +13,6 @@ 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 {
Expand Down
25 changes: 25 additions & 0 deletions app/src/helpers/test/testBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { LloydGeorgeStitchResult } from '../requests/getLloydGeorgeRecord';
import { REPOSITORY_ROLE } from '../../types/generic/authRole';
import { v4 as uuidv4 } from 'uuid';
import moment from 'moment';
import { FeatureFlags } from '../../types/generic/featureFlags';
import { GlobalConfig, LocalFlags } from '../../providers/configProvider/ConfigProvider';

const buildUserAuth = (userAuthOverride?: Partial<UserAuth>) => {
const auth: UserAuth = {
Expand Down Expand Up @@ -110,6 +112,28 @@ const buildLgSearchResult = () => {
return result;
};

const buildConfig = (
localFlagsOverride?: Partial<LocalFlags>,
featureFlagsOverride?: Partial<FeatureFlags>,
) => {
const globalConfig: GlobalConfig = {
mockLocal: {
isBsol: true,
recordUploaded: true,
userRole: REPOSITORY_ROLE.GP_ADMIN,
...localFlagsOverride,
},
featureFlags: {
uploadLloydGeorgeWorkflowEnabled: false,
uploadLambdaEnabled: false,
uploadArfWorkflowEnabled: false,
...featureFlagsOverride,
},
};

return globalConfig;
};

export {
buildPatientDetails,
buildTextFile,
Expand All @@ -118,4 +142,5 @@ export {
buildLgSearchResult,
buildUserAuth,
buildLgFile,
buildConfig,
};
3 changes: 3 additions & 0 deletions app/src/helpers/utils/errorCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const errorCodes: { [key: string]: string } = {
GWY_5001: 'Failed to utilise AWS client/resource',
SFB_5001: 'Error occur when sending email by SES',
SFB_5002: 'Failed to fetch parameters for sending email from SSM param store',
FFL_5001: 'Failed to parse feature flag/s from AppConfig response',
FFL_5002: 'Failed to retrieve feature flag/s from AppConfig profile',
FFL_5003: 'Feature is not enabled',
};

export default errorCodes;
12 changes: 8 additions & 4 deletions app/src/pages/authCallbackPage/AuthCallbackPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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';
import { defaultFeatureFlags } from '../../types/generic/featureFlags';

jest.mock('../../helpers/hooks/useConfig');
const mockedUseNavigate = jest.fn();
Expand Down Expand Up @@ -129,7 +129,7 @@ describe('AuthCallbackPage', () => {
return Promise.resolve({ data: buildUserAuth() });
} else {
return Promise.resolve({
data: { ...defaultFeatureFlags, testFeature1: true },
data: { ...defaultFeatureFlags, uploadLloydGeorgeWorkflowEnabled: true },
});
}
});
Expand All @@ -148,8 +148,12 @@ const TestApp = () => {
return (
<div>
<AuthCallbackPage />
<div>{`FLAG: ${JSON.stringify(config.featureFlags.testFeature1)}`.normalize()}</div>;
<div>{`LOGGEDIN: ${!!session.auth?.role}`.normalize()}</div>;
<div>
{`FLAG: ${JSON.stringify(
config.featureFlags.uploadLloydGeorgeWorkflowEnabled,
)}`.normalize()}
</div>
;<div>{`LOGGEDIN: ${!!session.auth?.role}`.normalize()}</div>;
</div>
);
};
Expand Down
56 changes: 42 additions & 14 deletions app/src/pages/uploadDocumentsPage/UploadDocumentsPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import UploadDocumentsPage from './UploadDocumentsPage';
import usePatient from '../../helpers/hooks/usePatient';
import { buildPatientDetails } from '../../helpers/test/testBuilders';
import { buildConfig } from '../../helpers/test/testBuilders';
import { UPLOAD_STAGE } from '../../types/pages/UploadDocumentsPage/types';
import { useState } from 'react';
jest.mock('../../helpers/hooks/useBaseAPIHeaders');
jest.mock('../../helpers/hooks/useBaseAPIUrl');
jest.mock('../../helpers/hooks/usePatient');
jest.mock('react-router');
const mockedUsePatient = usePatient as jest.Mock;
import useConfig from '../../helpers/hooks/useConfig';
import { routes } from '../../types/generic/routes';

const mockUseState = useState as jest.Mock;
const mockConfigContext = useConfig as jest.Mock;
const mockedUseNavigate = jest.fn();

const mockPatient = buildPatientDetails();
jest.mock('react-router', () => ({
useNavigate: () => mockedUseNavigate,
useLocation: () => jest.fn(),
}));
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
Expand All @@ -25,39 +27,65 @@ jest.mock('../../components/blocks/_arf/uploadingStage/UploadingStage', () => ()
jest.mock('../../components/blocks/_arf/completeStage/CompleteStage', () => () => (
<h1>Mock complete stage</h1>
));
jest.mock('../../helpers/hooks/useConfig');

describe('UploadDocumentsPage', () => {
beforeEach(() => {
process.env.REACT_APP_ENVIRONMENT = 'jest';
mockedUsePatient.mockReturnValue(mockPatient);
mockConfigContext.mockReturnValue(buildConfig({}, { uploadArfWorkflowEnabled: true }));
mockUseState.mockImplementation(() => [UPLOAD_STAGE.Selecting, jest.fn()]);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('renders initial file input stage', () => {
it('renders initial file input stage', async () => {
render(<UploadDocumentsPage />);

expect(
screen.getByRole('heading', { name: 'Mock file input stage' }),
).toBeInTheDocument();
await waitFor(() => {
expect(mockedUseNavigate).not.toHaveBeenCalledWith(routes.UNAUTHORISED);
});
});

it('renders uploading stage when state is set', () => {
it('renders uploading stage when state is set', async () => {
mockUseState.mockImplementation(() => [UPLOAD_STAGE.Uploading, jest.fn()]);

render(<UploadDocumentsPage />);

expect(
screen.getByRole('heading', { name: 'Mock files are uploading stage' }),
).toBeInTheDocument();
await waitFor(() => {
expect(mockedUseNavigate).not.toHaveBeenCalledWith(routes.UNAUTHORISED);
});
});

it('renders upload complete stage when state is set', () => {
it('renders upload complete stage when state is set', async () => {
mockUseState.mockImplementation(() => [UPLOAD_STAGE.Complete, jest.fn()]);

render(<UploadDocumentsPage />);

expect(
screen.getByRole('heading', { name: 'Mock complete stage' }),
).toBeInTheDocument();
await waitFor(() => {
expect(mockedUseNavigate).not.toHaveBeenCalledWith(routes.UNAUTHORISED);
});
});
});

describe('Navigation', () => {
it('redirects to unauthorised page if feature toggled off', async () => {
mockConfigContext.mockReturnValue(buildConfig({}, { uploadArfWorkflowEnabled: false }));

render(<UploadDocumentsPage />);

await waitFor(() => {
expect(mockedUseNavigate).toHaveBeenCalledWith(routes.UNAUTHORISED);
});
});
});
describe('Navigation', () => {});
});
21 changes: 20 additions & 1 deletion app/src/pages/uploadDocumentsPage/UploadDocumentsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import React, { useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { UPLOAD_STAGE, UploadDocument } from '../../types/pages/UploadDocumentsPage/types';
import SelectStage from '../../components/blocks/_arf/selectStage/SelectStage';
import UploadingStage from '../../components/blocks/_arf/uploadingStage/UploadingStage';
import CompleteStage from '../../components/blocks/_arf/completeStage/CompleteStage';
import { routes } from '../../types/generic/routes';
import { useNavigate } from 'react-router';
import useConfig from '../../helpers/hooks/useConfig';

function UploadDocumentsPage() {
const [stage, setStage] = useState<UPLOAD_STAGE>(UPLOAD_STAGE.Selecting);
const [documents, setDocuments] = useState<Array<UploadDocument>>([]);
const config = useConfig();
const navigate = useNavigate();

const mounted = useRef(false);
useEffect(() => {
const onPageLoad = () => {
if (!config.featureFlags.uploadArfWorkflowEnabled) {
navigate(routes.UNAUTHORISED);
}
};

if (!mounted.current) {
mounted.current = true;
void onPageLoad();
}
}, [navigate, config]);

if (stage === UPLOAD_STAGE.Selecting) {
return (
Expand Down
11 changes: 7 additions & 4 deletions app/src/providers/configProvider/ConfigProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ConfigProvider, { GlobalConfig, useConfigContext } from './ConfigProvider';
import { defaultFeatureFlags } from '../../helpers/requests/getFeatureFlags';
import { defaultFeatureFlags } from '../../types/generic/featureFlags';

describe('SessionProvider', () => {
beforeEach(() => {
process.env.REACT_APP_ENVIRONMENT = 'jest';
Expand Down Expand Up @@ -37,14 +38,14 @@ const TestApp = () => {
...config,
featureFlags: {
...defaultFeatureFlags,
testFeature1: true,
uploadLloydGeorgeWorkflowEnabled: true,
},
};
const flagOff: GlobalConfig = {
...config,
featureFlags: {
...defaultFeatureFlags,
testFeature1: false,
uploadLloydGeorgeWorkflowEnabled: false,
},
};
return (
Expand All @@ -56,7 +57,9 @@ const TestApp = () => {
</div>
<div>
<h1>Flags</h1>
<span>testFeature - {`${!!config.featureFlags.testFeature1}`}</span>
<span>
testFeature - {`${config.featureFlags.uploadLloydGeorgeWorkflowEnabled}`}
</span>
</div>
</>
);
Expand Down
3 changes: 1 addition & 2 deletions app/src/providers/configProvider/ConfigProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
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 { FeatureFlags, defaultFeatureFlags } from '../../types/generic/featureFlags';
import { isLocal } from '../../helpers/utils/isLocal';

type SetConfigOverride = (config: GlobalConfig) => void;
Expand Down
12 changes: 9 additions & 3 deletions app/src/types/generic/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
export type FeatureFlags = {
testFeature1: boolean;
testFeature2: boolean;
testFeature3: boolean;
uploadLloydGeorgeWorkflowEnabled: boolean;
uploadLambdaEnabled: boolean;
uploadArfWorkflowEnabled: boolean;
};

export const defaultFeatureFlags = {
uploadLloydGeorgeWorkflowEnabled: false,
uploadLambdaEnabled: false,
uploadArfWorkflowEnabled: false,
};
5 changes: 5 additions & 0 deletions lambdas/enums/lambda_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ def to_str(self) -> str:
"message": "Failed to retrieve feature flag/s from AppConfig profile",
}

FeatureFlagDisabled = {
"err_code": "FFL_5003",
"message": "Feature is not enabled",
}

"""
Errors with no exception
"""
Expand Down
Loading

0 comments on commit 4348a47

Please sign in to comment.