From ad265beb65be218fa170529937821eeac03b986b Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:22:11 +0100 Subject: [PATCH 01/20] Created files ready for editing and testing. Still need to run locally to test --- .../reissueNewDonorCaseConfirmation.test.tsx | 153 ++++++++++++++++++ .../reissueNewDonorCaseConfirmation.tsx | 73 +++++++++ .../reissueNewDonorCaseSummary.test.tsx | 38 +++++ .../reissueNewDonorCaseSummary.tsx | 42 +++++ .../helpers/apiMockObjects.ts | 7 +- 5 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx new file mode 100644 index 00000000..b9545bf9 --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx @@ -0,0 +1,153 @@ +/** + * @jest-environment jsdom + */ + +import React from "react"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate, useParams } from "react-router-dom"; +import ReissueNewDonorCaseConfirmation from "./reissueNewDonorCaseConfirmation"; +import "@testing-library/jest-dom"; +import axios from "axios"; +import { cloudFunctionAxiosError, ipsQuestionnaire, mockSuccessResponseForReissueNewDonorCase } from "../../features/step_definitions/helpers/apiMockObjects"; + +jest.mock("axios"); + +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: jest.fn() +})); + +const mockedAxios = axios as jest.Mocked; + +describe("ReissueNewDonorCaseConfirmation rendering", () => { + beforeEach(() => { + render( + + + + ); + }); + + it("displays correct prompt to reissue new donor case", () => { + expect(screen.getByText("Reissue new donor case for ?")).toBeInTheDocument(); + }); + + it("displays the correct number of breadcrumbs", () => { + expect.assertions(2); + + expect(screen.getByTestId("breadcrumb-0")).toBeInTheDocument(); + expect(screen.getByTestId("breadcrumb-1")).toBeInTheDocument(); + }); + + it("displays a button continue to reissue new donor case", () => { + expect(screen.getByRole("button", { name: "Continue" })).toBeInTheDocument(); + }); + + it("displays a button to navigate back to reissue new donor case page", () => { + expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument(); + }); + +}); + +describe("ReissueNewDonorCaseConfirmation navigation", () => { + let navigate: jest.Mock; + const routes = [ + { + path: "/reissueNewDonorCaseConfirmation", + element: , + }, + ]; + + const initialEntries = [ + { + pathname: "/reissueNewDonorCaseConfirmation", + state: { questionnaire: ipsQuestionnaire, role: "IPS Manager" }, + }, + ]; + beforeEach(() => { + navigate = jest.fn(); + (useNavigate as jest.Mock).mockReturnValue(navigate); + + const router = createMemoryRouter(routes, { + initialEntries, + initialIndex: 0, + }); + + render(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should redirect back to the questionnaire details page if user clicks Cancel", async () => { + + act(() => { + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + }); + + expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { + state: { + donorCasesResponseMessage: "", + donorCasesStatusCode: 0, + role: "", + questionnaire: ipsQuestionnaire, + }, + }); + }); + + it("calls the API endpoint correctly when the continue button is clicked", async () => { + + mockedAxios.post.mockResolvedValueOnce(mockSuccessResponseForReissueNewDonorCase); + act(() => { + fireEvent.click(screen.getByRole("button", { name: /Continue/i })); + }); + await waitFor(() => { + + expect(mockedAxios.post).toHaveBeenCalledWith( + "/api/cloudFunction/reissueNewDonorCase", + { questionnaire_name: ipsQuestionnaire.name, role: "IPS Manager" }, + { headers: { "Content-Type": "application/json" } } + ); + + expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", + { + state: { + donorCasesResponseMessage: mockSuccessResponseForReissueNewDonorCase.data, + donorCasesStatusCode: mockSuccessResponseForReissueNewDonorCase.status, + questionnaire: ipsQuestionnaire, + role: "IPS Manager" + } + }); + + }); + + }); + + it("should go back to the questionnaire details page if user clicks Continue and error panel is shown", async () => { + + mockedAxios.post.mockRejectedValue(cloudFunctionAxiosError); + act(() => { + fireEvent.click(screen.getByRole("button", { name: /Continue/i })); + }); + await waitFor(() => { + + expect(mockedAxios.post).toHaveBeenCalledWith( + "/api/cloudFunction/reissueNewDonorCase", + { questionnaire_name: ipsQuestionnaire.name, role: "IPS Manager" }, + { headers: { "Content-Type": "application/json" } } + ); + + expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", + { + state: { + donorCasesResponseMessage: (cloudFunctionAxiosError as any).response.data.message, + donorCasesStatusCode: 500, + questionnaire: ipsQuestionnaire, + role: "IPS Manager" + } + }); + }); + + }); +}); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx new file mode 100644 index 00000000..d53dd922 --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx @@ -0,0 +1,73 @@ +import React, { ReactElement } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import Breadcrumbs from "../breadcrumbs"; +import { ONSButton, ONSLoadingPanel } from "blaise-design-system-react-components"; +import axios from "axios"; +import { Questionnaire } from "blaise-api-node-client"; +import axiosConfig from "../../client/axiosConfig"; + +interface Location { + questionnaire: Questionnaire; + role: string; +} + +function ReissueNewDonorCaseConfirmation(): ReactElement { + const location = useLocation().state as Location; + const { questionnaire, role } = location || { questionnaire: "" }; + + const navigate = useNavigate(); + + const [loading, isLoading] = React.useState(false); + + async function callReissueNewDonorCaseCloudFunction() { + isLoading(true); + const payload = { questionnaire_name: questionnaire.name, role: role }; + let res; + try { + res = await axios.post("/api/cloudFunction/reissueNewDonorCase", payload, axiosConfig()); + } catch (error) { + const errorMessage = JSON.stringify((error as any).response.data.message); + res = { + data: errorMessage, + status: 500 + }; + } + isLoading(false); + navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: res.data, donorCasesStatusCode: res.status, questionnaire: questionnaire, role: role } }); + } + if (loading) { + return ; + } + return ( + <> + + +
+ { + ( + <> +

+ Reissue new donor case for {role} - {questionnaire.name}? +

+ + navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: "", donorCasesStatusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> + + ) + } +
+ + ); +} + +export default ReissueNewDonorCaseConfirmation; diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx new file mode 100644 index 00000000..d6be411b --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -0,0 +1,38 @@ +/** + * @jest-environment jsdom + */ + +import React from "react"; +import { render } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import ReissueNewDonorCaseSummary from "./reissueNewDonorCaseSummary"; + +describe("ReissueNewDonorCaseSummary", () => { + it("displays a success message when receiving a successful response from the cloud function", () => { + const props = { + donorCasesResponseMessage: "Success", + donorCasesStatusCode: 200, + role: "IPS Manager", + }; + + const { getByText } = render(); + expect( + getByText(/Donor case successfully reissued for IPS Manager/i) + ).toBeInTheDocument(); + expect(getByText(/Success/i)).toBeInTheDocument(); + }); + + it("displays an error message when receiving a failed response from the cloud function", () => { + const props = { + donorCasesResponseMessage: "Internal Server Error", + donorCasesStatusCode: 500, + role: "IPS Manager", + }; + + const { getByText } = render(); + expect( + getByText(/Error reissuing donor case for IPS Manager/i) + ).toBeInTheDocument(); + expect(getByText(/Internal Server Error/i)).toBeInTheDocument(); + }); +}); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx new file mode 100644 index 00000000..8f5e4fee --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx @@ -0,0 +1,42 @@ +import React, { ReactElement } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { ONSButton, ONSPanel } from "blaise-design-system-react-components"; + +interface Props { + donorCasesResponseMessage: string; + donorCasesStatusCode: number; + role: string; +} + +function ReissueNewDonorCaseSummary({ donorCasesResponseMessage, donorCasesStatusCode, role }: Props): ReactElement { + + return ( + <> +
+ { + (donorCasesStatusCode === 200 ? + +

+ Reissued donor case created successfully for {role} +

+
+ : + +

+ Error reissuing new donor cases for {role} +

+

+ {donorCasesResponseMessage} +

+

+ When reporting this issue to the Service Desk, please provide the questionnaire name, time and date of the failure. +

+
+ ) + } +
+ + ); +} + +export default ReissueNewDonorCaseSummary; diff --git a/src/features/step_definitions/helpers/apiMockObjects.ts b/src/features/step_definitions/helpers/apiMockObjects.ts index f56b3627..b279a0e1 100644 --- a/src/features/step_definitions/helpers/apiMockObjects.ts +++ b/src/features/step_definitions/helpers/apiMockObjects.ts @@ -87,4 +87,9 @@ cloudFunctionAxiosError.response = { export const mockSuccessResponseForDonorCasesCreation = { data: "Success", status: 200, -}; \ No newline at end of file +}; + +export const mockSuccessResponseForReissueNewDonorCase = { + data: "Success", + status: 200, +}; From 63a6772a77ed7b6b4739969166cda3ff3db7278b Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:40:10 +0100 Subject: [PATCH 02/20] Adding files necessary to reissue new donor case --- README.md | 2 +- src/app.tsx | 9 ++- .../questionnaireDetailsPage.tsx | 13 ++++ .../sections/reissueNewDonorCase.tsx | 60 +++++++++++++++++++ .../reissueNewDonorCaseConfirmation.tsx | 12 ++-- .../reissueNewDonorCaseSummary.test.tsx | 12 ++-- .../reissueNewDonorCaseSummary.tsx | 19 +++--- src/server/config.ts | 15 ++++- ...reissueNewDonorCaseCloudFunctionHandler.ts | 39 ++++++++++++ ...eNewDonorCcaseCloudFunctionHandler.test.ts | 47 +++++++++++++++ .../helpers/cloudFunctionCallerHelper.ts | 29 ++++++++- src/server/server.ts | 2 + 12 files changed, 228 insertions(+), 31 deletions(-) create mode 100644 src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx create mode 100644 src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts create mode 100644 src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts diff --git a/README.md b/README.md index a113dada..4649eced 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The .env file should be setup as below Example .env file: ```.env -BLAISE_API_URL=localhost:5011 +BLAISE_API_URL=localhost:8011 PROJECT_ID=ons-blaise-v2-dev- BUCKET_NAME=ons-blaise-v2-dev--dqs SERVER_PARK=gusty diff --git a/src/app.tsx b/src/app.tsx index a695a98e..8a392663 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -21,6 +21,7 @@ import QuestionnaireDetailsPage from "./components/questionnaireDetailsPage/ques import ChangeTOStartDate from "./components/questionnaireDetailsPage/changeTOStartDate"; import ChangeTMReleaseDate from "./components/questionnaireDetailsPage/changeTmReleaseDate"; import CreateDonorCasesConfirmation from "./components/createDonorCasePage/createDonorCasesConfirmation"; +import ReissueNewDonorCaseConfirmation from "./components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation"; import "./style.css"; import { isProduction } from "./client/env"; import { Authenticate } from "blaise-login-react/blaise-login-react-client"; @@ -101,6 +102,10 @@ function App(): ReactElement { path="/createDonorCasesConfirmation" element={}> + }> + @@ -122,7 +127,7 @@ function App(): ReactElement { {(_user, loggedIn, logOutFunction) => ( <> - Skip to content + Skip to content { isProduction(window.location.hostname) ? <> : } @@ -154,4 +159,4 @@ function App(): ReactElement { ); } -export default App; \ No newline at end of file +export default App; diff --git a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx index c5082943..cb913d8c 100644 --- a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx +++ b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx @@ -13,6 +13,8 @@ import { ONSButton, ONSLoadingPanel, ONSPanel } from "blaise-design-system-react import QuestionnaireDetails from "./sections/questionnaireDetails"; import CreateDonorCases from "./sections/createDonorCases"; import CreateDonorCasesSummary from "../createDonorCasePage/createDonorCasesSummary"; +import ReissueNewDonorCase from "./sections/reissueNewDonorCase"; +import ReissueNewDonorCaseSummary from "../reissueNewDonorCasePage/reissueNewDonorCaseSummary"; interface State { questionnaire: Questionnaire | null; @@ -21,8 +23,16 @@ interface State { role?: string; } +interface ReissueNewDonorCaseState { + questionnaire: Questionnaire | null; + reissueNewDonorCaseResponseMessage?: string; + reissueNewDonorCaseStatusCode?: number; + user?: string; +} + function QuestionnaireDetailsPage(): ReactElement { const location = useLocation().state as State; + const reissueNewDonorCaseState = useLocation().state as ReissueNewDonorCaseState; const navigate = useNavigate(); const [questionnaire, setQuestionnaire] = useState(); const [modes, setModes] = useState([]); @@ -32,6 +42,7 @@ function QuestionnaireDetailsPage(): ReactElement { const initialState = location || { questionnaire: null }; const { questionnaireName } = useParams(); const { donorCasesResponseMessage, donorCasesStatusCode, role } = location || { donorCasesResponseMessage: "", donorCasesStatusCode: 0, role: "" }; + const { reissueNewDonorCaseResponseMessage, reissueNewDonorCaseStatusCode, user } = reissueNewDonorCaseState || { reissueNewDonorCaseResponseMessage: "", reissueNewDonorCaseStatusCode: 0, user: "" }; useEffect(() => { if (initialState.questionnaire === null) { @@ -116,8 +127,10 @@ function QuestionnaireDetailsPage(): ReactElement { {donorCasesResponseMessage && donorCasesStatusCode && role && } + {reissueNewDonorCaseResponseMessage && reissueNewDonorCaseStatusCode && user && } {questionnaire.name.includes("IPS") && } + diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx new file mode 100644 index 00000000..dc6e1539 --- /dev/null +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -0,0 +1,60 @@ +import React, { ReactElement, useState } from "react"; +import { Questionnaire } from "blaise-api-node-client"; +import { Link } from "react-router-dom"; + +interface Props { + questionnaire: Questionnaire; +} + +function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { + const [username, setUsername] = useState(""); + + return ( + <> +
+
+

Reissue New Donor Case for User

+ + + + + + + + + + + + + +
DetailOutput
+
+ + setUsername(e.target.value)} + placeholder="Username" + /> +
+
+ + Reissue Donor case + +
+
+
+ + ); +} + +export default ReissueNewDonorCase; diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx index d53dd922..1ecfd306 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx @@ -8,12 +8,12 @@ import axiosConfig from "../../client/axiosConfig"; interface Location { questionnaire: Questionnaire; - role: string; + user: string; } function ReissueNewDonorCaseConfirmation(): ReactElement { const location = useLocation().state as Location; - const { questionnaire, role } = location || { questionnaire: "" }; + const { questionnaire, user } = location || { questionnaire: "", user: "" }; const navigate = useNavigate(); @@ -21,7 +21,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { async function callReissueNewDonorCaseCloudFunction() { isLoading(true); - const payload = { questionnaire_name: questionnaire.name, role: role }; + const payload = { questionnaire_name: questionnaire.name, role: user }; let res; try { res = await axios.post("/api/cloudFunction/reissueNewDonorCase", payload, axiosConfig()); @@ -33,7 +33,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { }; } isLoading(false); - navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: res.data, donorCasesStatusCode: res.status, questionnaire: questionnaire, role: role } }); + navigate(`/questionnaire/${questionnaire.name}`, { state: { reissueNewDonorCaseResponseMessage: res.data, reissueNewDonorCaseStatusCode: res.status, questionnaire: questionnaire, user: user } }); } if (loading) { return ; @@ -51,7 +51,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { ( <>

- Reissue new donor case for {role} - {questionnaire.name}? + Reissue a new donor case for {questionnaire.name} on behalf of {user}?

navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: "", donorCasesStatusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> + onClick={() => navigate(`/questionnaire/${questionnaire.name}`, { state: { reissueNewDonorCaseResponseMessage: "", reissueNewDonorCaseStatusCode: 0, questionnaire: questionnaire, user: "" } })} primary={false} /> ) } diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx index d6be411b..1873c9f6 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -10,9 +10,9 @@ import ReissueNewDonorCaseSummary from "./reissueNewDonorCaseSummary"; describe("ReissueNewDonorCaseSummary", () => { it("displays a success message when receiving a successful response from the cloud function", () => { const props = { - donorCasesResponseMessage: "Success", - donorCasesStatusCode: 200, - role: "IPS Manager", + reissueNewDonorCaseResponseMessage: "Success", + reissueNewDonorCaseStatusCode: 200, + user: "testuser1", }; const { getByText } = render(); @@ -24,9 +24,9 @@ describe("ReissueNewDonorCaseSummary", () => { it("displays an error message when receiving a failed response from the cloud function", () => { const props = { - donorCasesResponseMessage: "Internal Server Error", - donorCasesStatusCode: 500, - role: "IPS Manager", + reissueNewDonorCaseResponseMessage: "Internal Server Error", + reissueNewDonorCaseStatusCode: 500, + user: "testuser1", }; const { getByText } = render(); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx index 8f5e4fee..8a760a34 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx @@ -1,32 +1,31 @@ import React, { ReactElement } from "react"; -import { useNavigate, useLocation } from "react-router-dom"; -import { ONSButton, ONSPanel } from "blaise-design-system-react-components"; +import { ONSPanel } from "blaise-design-system-react-components"; interface Props { - donorCasesResponseMessage: string; - donorCasesStatusCode: number; - role: string; + reissueNewDonorCaseResponseMessage: string; + reissueNewDonorCaseStatusCode: number; + user: string; } -function ReissueNewDonorCaseSummary({ donorCasesResponseMessage, donorCasesStatusCode, role }: Props): ReactElement { +function ReissueNewDonorCaseSummary({ reissueNewDonorCaseResponseMessage, reissueNewDonorCaseStatusCode, user }: Props): ReactElement { return ( <>
{ - (donorCasesStatusCode === 200 ? + (reissueNewDonorCaseStatusCode === 200 ?

- Reissued donor case created successfully for {role} + Reissued donor case created successfully for {user}

:

- Error reissuing new donor cases for {role} + Error reissuing new donor case for {user}

- {donorCasesResponseMessage} + {reissueNewDonorCaseResponseMessage}

When reporting this issue to the Service Desk, please provide the questionnaire name, time and date of the failure. diff --git a/src/server/config.ts b/src/server/config.ts index a18d1947..4152c0b2 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -12,6 +12,7 @@ export interface Config extends AuthConfig { BusApiUrl: string; BusClientId: string; CreateDonorCasesCloudFunctionUrl: string; + ReissueNewDonorCaseCloudFunctionUrl: string; } export function getConfigFromEnv(): Config { @@ -25,7 +26,8 @@ export function getConfigFromEnv(): Config { BUS_API_URL, BUS_CLIENT_ID, SESSION_TIMEOUT, - CREATE_DONOR_CASES_CLOUD_FUNCTION_URL + CREATE_DONOR_CASES_CLOUD_FUNCTION_URL, + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL } = process.env; const { @@ -80,9 +82,15 @@ export function getConfigFromEnv(): Config { } if (CREATE_DONOR_CASES_CLOUD_FUNCTION_URL === undefined) { - console.error("CLOUD_FUNCTION_URL environment variable has not been set"); + console.error("CREATE_DONOR_CASES_CLOUD_FUNCTION_URL environment variable has not been set"); CREATE_DONOR_CASES_CLOUD_FUNCTION_URL = "ENV_VAR_NOT_SET"; } + + if (REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL === undefined) { + console.error("REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL environment variable has not been set"); + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL = "ENV_VAR_NOT_SET"; + } + let port = 5000; if (PORT !== undefined) { port = +PORT; @@ -101,7 +109,8 @@ export function getConfigFromEnv(): Config { SessionTimeout: SESSION_TIMEOUT, SessionSecret: sessionSecret(SESSION_SECRET), Roles: loadRoles(ROLES), - CreateDonorCasesCloudFunctionUrl: CREATE_DONOR_CASES_CLOUD_FUNCTION_URL + CreateDonorCasesCloudFunctionUrl: CREATE_DONOR_CASES_CLOUD_FUNCTION_URL, + ReissueNewDonorCaseCloudFunctionUrl: REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL }; } diff --git a/src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts b/src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts new file mode 100644 index 00000000..c8083472 --- /dev/null +++ b/src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts @@ -0,0 +1,39 @@ +import express, { Request, Response, Router } from "express"; +import { callCloudFunctionToReissueNewDonorDonorCase } from "../helpers/cloudFunctionCallerHelper"; + +export default function newCloudFunctionHandler( + ReissueNewDonorCaseCloudFunctionUrl: string +): Router { + const router = express.Router(); + const cloudFunctionHandler = new CloudFunctionHandler( + ReissueNewDonorCaseCloudFunctionUrl + ); + router.post("/api/cloudFunction/reissueNewDonorCase", cloudFunctionHandler.CallCloudFunction); + + return router; +} + +export class CloudFunctionHandler { + ReissueNewDonorCaseCloudFunctionUrl: string; + + constructor(ReissueNewDonorCaseCloudFunctionUrl: string) { + this.ReissueNewDonorCaseCloudFunctionUrl = ReissueNewDonorCaseCloudFunctionUrl; + this.CallCloudFunction = this.CallCloudFunction.bind(this); + } + + async CallCloudFunction(req: Request, res: Response): Promise { + const reqData = req.body; + req.log.info(`${this.ReissueNewDonorCaseCloudFunctionUrl} URL to invoke for Reissuing New Donor Case.`); + try { + const cloudFunctionResponse = await callCloudFunctionToReissueNewDonorDonorCase(this.ReissueNewDonorCaseCloudFunctionUrl, reqData); + return res.status(cloudFunctionResponse.status).json(cloudFunctionResponse); + + } catch (error) { + console.error("Error:", error); + return res.status(500).json({ + message: (error as any)?.response.data, + status: 500, + }); + } + } +} diff --git a/src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts b/src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts new file mode 100644 index 00000000..7d14a85c --- /dev/null +++ b/src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts @@ -0,0 +1,47 @@ +// it.skip("placeholder", () => { }); + +import { newServer } from "../server"; +import supertest from "supertest"; +import { getConfigFromEnv } from "../config"; +import createLogger from "../pino"; +import { callCloudFunctionToReissueNewDonorDonorCase } from "../helpers/cloudFunctionCallerHelper"; +import { cloudFunctionAxiosError } from "../../features/step_definitions/helpers/apiMockObjects"; + +jest.mock("../helpers/cloudFunctionCallerHelper"); +const successResponse = { + message: "Success", + status: 200, +}; + +const config = getConfigFromEnv(); +const callCloudFunctionToReissueDonorCaseMock = callCloudFunctionToReissueNewDonorDonorCase as jest.Mock>; + +describe("Call Cloud Function to reissue new donor case and return responses", () => { + let request: supertest.SuperTest; + + beforeEach(() => { + request = supertest(newServer(config, createLogger())); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should return a 200 status and a json object with message and status if successfully reissued donor case", async () => { + callCloudFunctionToReissueDonorCaseMock.mockResolvedValue(successResponse); + + const response = await request.post("/api/cloudFunction/reissueNewDonorCase"); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(successResponse); + }); + + it("should return a 500 status and a json object with message and status if cloud function failed reissuing donor case", async () => { + callCloudFunctionToReissueDonorCaseMock.mockRejectedValue(cloudFunctionAxiosError); + + const response = await request.post("/api/cloudFunction/reissueNewDonorCase"); + + expect(response.status).toEqual(500); + expect(response.body.message).toEqual((cloudFunctionAxiosError as any).response.data); + }); +}); diff --git a/src/server/helpers/cloudFunctionCallerHelper.ts b/src/server/helpers/cloudFunctionCallerHelper.ts index 30e7ad64..1492f15a 100644 --- a/src/server/helpers/cloudFunctionCallerHelper.ts +++ b/src/server/helpers/cloudFunctionCallerHelper.ts @@ -6,8 +6,7 @@ export async function getIdTokenFromMetadataServer(targetAudience: string) { const client = await googleAuth.getIdTokenClient(targetAudience); - const token = await client.idTokenProvider.fetchIdToken(targetAudience); - return token; + return await client.idTokenProvider.fetchIdToken(targetAudience); } export async function callCloudFunctionToCreateDonorCases(url: string, payload: any): Promise<{ message: string, status: number }> { @@ -33,5 +32,29 @@ export async function callCloudFunctionToCreateDonorCases(url: string, payload: status: 500 }; } +} + +export async function callCloudFunctionToReissueNewDonorDonorCase(url: string, payload: any): Promise<{ message: string, status: number }> { + + const token = await getIdTokenFromMetadataServer(url); + + try { + const res = await axios.post(url, payload, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); -} \ No newline at end of file + return { + message: res.data, + status: res.status + }; + } catch (error) { + console.error("Error:", error); + return { + message: (error as any).response.data, + status: 500 + }; + } +} diff --git a/src/server/server.ts b/src/server/server.ts index 63ac2917..33a75308 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -45,6 +45,7 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): const uploadHandler = newUploadHandler(storageManager, auth, auditLogger); const auditHandler = newAuditHandler(auditLogger); const cloudFunctionHandler = newCloudFunctionHandler(config.CreateDonorCasesCloudFunctionUrl); + const reissueNewDonorCaseHandler = newCloudFunctionHandler(config.ReissueNewDonorCaseCloudFunctionUrl); const server = express(); @@ -71,6 +72,7 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): server.use("/", busHandler); server.use("/", auditHandler); server.use("/", cloudFunctionHandler); + server.use("/", reissueNewDonorCaseHandler); server.use("/", HealthCheckHandler()); server.get("*", function (req: Request, res: Response) { From 7321d7db8275a7fd5798bf13fdf9b5ccab18c320 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 25 Oct 2024 13:53:20 +0100 Subject: [PATCH 03/20] Changed wording and look and feel of textbox --- .../sections/reissueNewDonorCase.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index dc6e1539..07d03bfb 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -7,7 +7,7 @@ interface Props { } function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { - const [username, setUsername] = useState(""); + const [user, setUser] = useState(""); return ( <> @@ -25,25 +25,25 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement {

- - setUsername(e.target.value)} - placeholder="Username" - /> +
+ + setUser(e.target.value)}/> +
- + +
+
Reissue Donor case From 1be92265ff3ffa5e94a072be9031ca2a958ba3f3 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:08:06 +0100 Subject: [PATCH 04/20] Slight changes and fixes --- .../questionnaireDetailsPage.tsx | 19 +++++-------------- .../sections/reissueNewDonorCase.tsx | 2 +- .../reissueNewDonorCaseSummary.test.tsx | 12 ++++++------ .../reissueNewDonorCaseSummary.tsx | 16 ++++++++-------- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx index cb913d8c..96d9f9fe 100644 --- a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx +++ b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx @@ -18,21 +18,13 @@ import ReissueNewDonorCaseSummary from "../reissueNewDonorCasePage/reissueNewDon interface State { questionnaire: Questionnaire | null; - donorCasesResponseMessage?: string; - donorCasesStatusCode?: number; + responseMessage?: string; + statusCode?: number; role?: string; } -interface ReissueNewDonorCaseState { - questionnaire: Questionnaire | null; - reissueNewDonorCaseResponseMessage?: string; - reissueNewDonorCaseStatusCode?: number; - user?: string; -} - function QuestionnaireDetailsPage(): ReactElement { const location = useLocation().state as State; - const reissueNewDonorCaseState = useLocation().state as ReissueNewDonorCaseState; const navigate = useNavigate(); const [questionnaire, setQuestionnaire] = useState(); const [modes, setModes] = useState([]); @@ -41,8 +33,7 @@ function QuestionnaireDetailsPage(): ReactElement { const [loaded, setLoaded] = useState(false); const initialState = location || { questionnaire: null }; const { questionnaireName } = useParams(); - const { donorCasesResponseMessage, donorCasesStatusCode, role } = location || { donorCasesResponseMessage: "", donorCasesStatusCode: 0, role: "" }; - const { reissueNewDonorCaseResponseMessage, reissueNewDonorCaseStatusCode, user } = reissueNewDonorCaseState || { reissueNewDonorCaseResponseMessage: "", reissueNewDonorCaseStatusCode: 0, user: "" }; + const { responseMessage, statusCode, role } = location || { responseMessage: "", statusCode: 0, role: "" }; useEffect(() => { if (initialState.questionnaire === null) { @@ -126,8 +117,8 @@ function QuestionnaireDetailsPage(): ReactElement { {questionnaire.name} - {donorCasesResponseMessage && donorCasesStatusCode && role && } - {reissueNewDonorCaseResponseMessage && reissueNewDonorCaseStatusCode && user && } + {responseMessage && statusCode && role && } + {responseMessage && statusCode && role && } {questionnaire.name.includes("IPS") && } diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index 07d03bfb..f08fbe0b 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -27,7 +27,7 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement {
setUser(e.target.value)}/>
diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx index 1873c9f6..8985523e 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -10,9 +10,9 @@ import ReissueNewDonorCaseSummary from "./reissueNewDonorCaseSummary"; describe("ReissueNewDonorCaseSummary", () => { it("displays a success message when receiving a successful response from the cloud function", () => { const props = { - reissueNewDonorCaseResponseMessage: "Success", - reissueNewDonorCaseStatusCode: 200, - user: "testuser1", + responseMessage: "Success", + statusCode: 200, + role: "testuser1", }; const { getByText } = render(); @@ -24,9 +24,9 @@ describe("ReissueNewDonorCaseSummary", () => { it("displays an error message when receiving a failed response from the cloud function", () => { const props = { - reissueNewDonorCaseResponseMessage: "Internal Server Error", - reissueNewDonorCaseStatusCode: 500, - user: "testuser1", + responseMessage: "Internal Server Error", + statusCode: 500, + role: "testuser1", }; const { getByText } = render(); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx index 8a760a34..589c8d6f 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx @@ -2,30 +2,30 @@ import React, { ReactElement } from "react"; import { ONSPanel } from "blaise-design-system-react-components"; interface Props { - reissueNewDonorCaseResponseMessage: string; - reissueNewDonorCaseStatusCode: number; - user: string; + responseMessage: string; + statusCode: number; + role: string; } -function ReissueNewDonorCaseSummary({ reissueNewDonorCaseResponseMessage, reissueNewDonorCaseStatusCode, user }: Props): ReactElement { +function ReissueNewDonorCaseSummary({ responseMessage, statusCode, role }: Props): ReactElement { return ( <>
{ - (reissueNewDonorCaseStatusCode === 200 ? + (statusCode === 200 ?

- Reissued donor case created successfully for {user} + Reissued donor case created successfully for {role}

:

- Error reissuing new donor case for {user} + Error reissuing new donor case for {role}

- {reissueNewDonorCaseResponseMessage} + {responseMessage}

When reporting this issue to the Service Desk, please provide the questionnaire name, time and date of the failure. From 64016e956afa07f2e34e42c9c61a4d4bbc5bc7ac Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:30:10 +0000 Subject: [PATCH 05/20] Made changes to show both error messages for create donor cases and reissue new donor case --- .../createDonorCasePage/createDonorCasesConfirmation.tsx | 6 +++--- .../questionnaireDetailsPage.tsx | 9 +++++---- .../sections/createDonorCases.tsx | 2 +- .../sections/reissueNewDonorCase.tsx | 7 +------ .../reissueNewDonorCaseConfirmation.tsx | 4 ++-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx b/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx index a003825f..e7f6b9dc 100644 --- a/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx +++ b/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx @@ -33,7 +33,7 @@ function CreateDonorCasesConfirmation(): ReactElement { }; } isLoading(false); - navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: res.data, donorCasesStatusCode: res.status, questionnaire: questionnaire, role: role } }); + navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "createDonorCases", responseMessage: res.data, statusCode: res.status, questionnaire: questionnaire, role: role } }); } if (loading) { return ; @@ -61,7 +61,7 @@ function CreateDonorCasesConfirmation(): ReactElement { /> navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: "", donorCasesStatusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> + onClick={() => navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "createDonorCases", responseMessage: "", statusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> ) } @@ -70,4 +70,4 @@ function CreateDonorCasesConfirmation(): ReactElement { ); } -export default CreateDonorCasesConfirmation; \ No newline at end of file +export default CreateDonorCasesConfirmation; diff --git a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx index 96d9f9fe..82fbba7d 100644 --- a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx +++ b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx @@ -17,6 +17,7 @@ import ReissueNewDonorCase from "./sections/reissueNewDonorCase"; import ReissueNewDonorCaseSummary from "../reissueNewDonorCasePage/reissueNewDonorCaseSummary"; interface State { + section?: string; questionnaire: Questionnaire | null; responseMessage?: string; statusCode?: number; @@ -33,7 +34,7 @@ function QuestionnaireDetailsPage(): ReactElement { const [loaded, setLoaded] = useState(false); const initialState = location || { questionnaire: null }; const { questionnaireName } = useParams(); - const { responseMessage, statusCode, role } = location || { responseMessage: "", statusCode: 0, role: "" }; + const { section, responseMessage, statusCode, role } = location || { section: "", responseMessage: "", statusCode: 0, role: "" }; useEffect(() => { if (initialState.questionnaire === null) { @@ -117,11 +118,11 @@ function QuestionnaireDetailsPage(): ReactElement { {questionnaire.name} - {responseMessage && statusCode && role && } - {responseMessage && statusCode && role && } + {section === "createDonorCases" && responseMessage && statusCode && role && } + {section === "reissueNewDonorCase" && responseMessage && statusCode && role && } {questionnaire.name.includes("IPS") && } - + {questionnaire.name.includes("IPS") && } diff --git a/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx b/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx index 2bf4219e..0cf548b6 100644 --- a/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx +++ b/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx @@ -38,7 +38,7 @@ function CreateDonorCases({ questionnaire }: Props): ReactElement { Create cases diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index f08fbe0b..8d317497 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -15,12 +15,6 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement {

Reissue New Donor Case for User

- - - - - - diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx index 8dc09ae0..b3736236 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx @@ -21,6 +21,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { async function callReissueNewDonorCaseCloudFunction() { isLoading(true); + console.log(questionnaire.name, user); const payload = { questionnaire_name: questionnaire.name, user: user }; let res; try { @@ -51,7 +52,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { ( <>

- Reissue a new donor case for {questionnaire.name} on behalf of {user}? + Reissue a new donor case for {questionnaire.name} on behalf of {user}?

@@ -24,9 +24,6 @@ function ReissueNewDonorCaseSummary({ responseMessage, statusCode, role }: Props

Error reissuing new donor case for {role}

-

- {responseMessage} -

When reporting this issue to the Service Desk, please provide the questionnaire name, time and date of the failure.

From 1201f2a068b790e8377990dd3b4a6942f3bfa99d Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:49:29 +0000 Subject: [PATCH 13/20] GUI Fixes --- .../reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx index 7873c91f..dcf4ccf2 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx @@ -25,7 +25,7 @@ function ReissueNewDonorCaseSummary({ statusCode, role }: Props): ReactElement { Error reissuing new donor case for {role}

- When reporting this issue to the Service Desk, please provide the questionnaire name, time and date of the failure. + When reporting this issue to the Service Desk, please provide the questionnaire name, user, time and date of the failure.

) From cac238793bde1dd1d29e57a0ea4ef0888caf7520 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:49:49 +0000 Subject: [PATCH 14/20] GUI Fixes --- .../reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx index dcf4ccf2..d5d5aaa1 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx @@ -25,7 +25,7 @@ function ReissueNewDonorCaseSummary({ statusCode, role }: Props): ReactElement { Error reissuing new donor case for {role}

- When reporting this issue to the Service Desk, please provide the questionnaire name, user, time and date of the failure. + When reporting this issue to the Service Desk, please provide the questionnaire name, user, time and date of the failure.

) From 9c64f3ff2b52b1e12d95318192b80dc1b3369b61 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 1 Nov 2024 08:22:30 +0000 Subject: [PATCH 15/20] Fixed failing test after GUI changes --- .../reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx index 357dee33..36c433bf 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -33,6 +33,6 @@ describe("ReissueNewDonorCaseSummary", () => { expect( getByText(/Error reissuing new donor case for testuser1/i) ).toBeInTheDocument(); - expect(getByText(/Internal Server Error/i)).toBeInTheDocument(); + expect(getByText(/When reporting this issue to the Service Desk, please provide the questionnaire name, user, yarn testtime and date of the failure./i)).toBeInTheDocument(); }); }); From 59286c1456e499c5b7d640eab88200714d75e811 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 1 Nov 2024 08:40:38 +0000 Subject: [PATCH 16/20] Added padding around error message and various UI Fixes --- .../questionnaireDetailsPage/sections/reissueNewDonorCase.tsx | 4 ++-- .../reissueNewDonorCaseSummary.test.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index 9137283b..2859cb52 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -33,7 +33,7 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { <>
-

Reissue New Donor Case for User

+

Reissue New Donor Case

DetailOutput
@@ -39,6 +33,7 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { ; @@ -61,7 +61,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { /> navigate(`/questionnaire/${questionnaire.name}`, { state: { reissueNewDonorCaseResponseMessage: "", reissueNewDonorCaseStatusCode: 0, questionnaire: questionnaire, user: "" } })} primary={false} /> + onClick={() => navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "reissueNewDonorCase", responseMessage: "", statusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> ) } From a445ce30a2376e6566cd90f58775acfad08d1286 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:26:19 +0000 Subject: [PATCH 06/20] Fixing Tests so far --- .../createDonorCasesConfirmation.test.tsx | 26 +++++++++----- .../reissueNewDonorCaseConfirmation.test.tsx | 34 ++++++++++++------- .../helpers/apiMockObjects.ts | 8 +++++ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx b/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx index 230cd838..1ea6977b 100644 --- a/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx +++ b/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx @@ -8,7 +8,12 @@ import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate, useParam import CreateDonorCasesConfirmation from "./createDonorCasesConfirmation"; import "@testing-library/jest-dom"; import axios from "axios"; -import { cloudFunctionAxiosError, ipsQuestionnaire, mockSuccessResponseForDonorCasesCreation } from "../../features/step_definitions/helpers/apiMockObjects"; +import { + cloudFunctionAxiosError, + ipsQuestionnaire, + mockSectionForDonorCasesCreation, + mockSuccessResponseForDonorCasesCreation +} from "../../features/step_definitions/helpers/apiMockObjects"; jest.mock("axios"); @@ -81,15 +86,16 @@ describe("CreateDonorCasesConfirmation navigation", () => { }); it("should redirect back to the questionnaire details page if user clicks Cancel", async () => { - + act(() => { fireEvent.click(screen.getByRole("button", { name: "Cancel" })); }); expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: "", - donorCasesStatusCode: 0, + section: "createDonorCases", + responseMessage: "", + statusCode: 0, role: "", questionnaire: ipsQuestionnaire, }, @@ -113,8 +119,9 @@ describe("CreateDonorCasesConfirmation navigation", () => { expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: mockSuccessResponseForDonorCasesCreation.data, - donorCasesStatusCode: mockSuccessResponseForDonorCasesCreation.status, + section: mockSectionForDonorCasesCreation.data, + responseMessage: mockSuccessResponseForDonorCasesCreation.data, + statusCode: mockSuccessResponseForDonorCasesCreation.status, questionnaire: ipsQuestionnaire, role: "IPS Manager" } @@ -141,8 +148,9 @@ describe("CreateDonorCasesConfirmation navigation", () => { expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: (cloudFunctionAxiosError as any).response.data.message, - donorCasesStatusCode: 500, + section: "createDonorCases", + responseMessage: (cloudFunctionAxiosError as any).response.data.message, + statusCode: 500, questionnaire: ipsQuestionnaire, role: "IPS Manager" } @@ -150,4 +158,4 @@ describe("CreateDonorCasesConfirmation navigation", () => { }); }); -}); \ No newline at end of file +}); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx index b9545bf9..c76648c3 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx @@ -8,7 +8,12 @@ import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate, useParam import ReissueNewDonorCaseConfirmation from "./reissueNewDonorCaseConfirmation"; import "@testing-library/jest-dom"; import axios from "axios"; -import { cloudFunctionAxiosError, ipsQuestionnaire, mockSuccessResponseForReissueNewDonorCase } from "../../features/step_definitions/helpers/apiMockObjects"; +import { + cloudFunctionAxiosError, + ipsQuestionnaire, + mockSectionForReissueNewDonorCase, + mockSuccessResponseForReissueNewDonorCase +} from "../../features/step_definitions/helpers/apiMockObjects"; jest.mock("axios"); @@ -29,7 +34,7 @@ describe("ReissueNewDonorCaseConfirmation rendering", () => { }); it("displays correct prompt to reissue new donor case", () => { - expect(screen.getByText("Reissue new donor case for ?")).toBeInTheDocument(); + expect(screen.getByText("Reissue a new donor case for on behalf of ?")).toBeInTheDocument(); }); it("displays the correct number of breadcrumbs", () => { @@ -61,7 +66,7 @@ describe("ReissueNewDonorCaseConfirmation navigation", () => { const initialEntries = [ { pathname: "/reissueNewDonorCaseConfirmation", - state: { questionnaire: ipsQuestionnaire, role: "IPS Manager" }, + state: { questionnaire: ipsQuestionnaire, user: "testuser" }, }, ]; beforeEach(() => { @@ -88,8 +93,9 @@ describe("ReissueNewDonorCaseConfirmation navigation", () => { expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: "", - donorCasesStatusCode: 0, + section: "reissueNewDonorCase", + responseMessage: "", + statusCode: 0, role: "", questionnaire: ipsQuestionnaire, }, @@ -106,17 +112,18 @@ describe("ReissueNewDonorCaseConfirmation navigation", () => { expect(mockedAxios.post).toHaveBeenCalledWith( "/api/cloudFunction/reissueNewDonorCase", - { questionnaire_name: ipsQuestionnaire.name, role: "IPS Manager" }, + { questionnaire_name: ipsQuestionnaire.name, role: "testuser" }, { headers: { "Content-Type": "application/json" } } ); expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: mockSuccessResponseForReissueNewDonorCase.data, - donorCasesStatusCode: mockSuccessResponseForReissueNewDonorCase.status, + section: mockSectionForReissueNewDonorCase.data, + responseMessage: mockSuccessResponseForReissueNewDonorCase.data, + statusCode: mockSuccessResponseForReissueNewDonorCase.status, questionnaire: ipsQuestionnaire, - role: "IPS Manager" + role: "testuser" } }); @@ -134,17 +141,18 @@ describe("ReissueNewDonorCaseConfirmation navigation", () => { expect(mockedAxios.post).toHaveBeenCalledWith( "/api/cloudFunction/reissueNewDonorCase", - { questionnaire_name: ipsQuestionnaire.name, role: "IPS Manager" }, + { questionnaire_name: ipsQuestionnaire.name, role: "testuser" }, { headers: { "Content-Type": "application/json" } } ); expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: (cloudFunctionAxiosError as any).response.data.message, - donorCasesStatusCode: 500, + section: "reissueNewDonorCase", + responseMessage: (cloudFunctionAxiosError as any).response.data.message, + statusCode: 500, questionnaire: ipsQuestionnaire, - role: "IPS Manager" + role: "testuser" } }); }); diff --git a/src/features/step_definitions/helpers/apiMockObjects.ts b/src/features/step_definitions/helpers/apiMockObjects.ts index b279a0e1..8db7590d 100644 --- a/src/features/step_definitions/helpers/apiMockObjects.ts +++ b/src/features/step_definitions/helpers/apiMockObjects.ts @@ -84,6 +84,14 @@ cloudFunctionAxiosError.response = { }, }; +export const mockSectionForDonorCasesCreation = { + data: "createDonorCases" +}; + +export const mockSectionForReissueNewDonorCase = { + data: "reissueNewDonorCase" +}; + export const mockSuccessResponseForDonorCasesCreation = { data: "Success", status: 200, From d4a4ef3eb33040d95966da964882e14f347e9e01 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:38:47 +0000 Subject: [PATCH 07/20] Fixed tests so far --- .../reissueNewDonorCaseSummary.test.tsx | 4 +- ...reissueNewDonorCaseCloudFunctionHandler.ts | 39 --------------- ...eNewDonorCcaseCloudFunctionHandler.test.ts | 47 ------------------- 3 files changed, 2 insertions(+), 88 deletions(-) delete mode 100644 src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts delete mode 100644 src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx index 8985523e..357dee33 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -17,7 +17,7 @@ describe("ReissueNewDonorCaseSummary", () => { const { getByText } = render(); expect( - getByText(/Donor case successfully reissued for IPS Manager/i) + getByText(/Reissued donor case created successfully for testuser1/i) ).toBeInTheDocument(); expect(getByText(/Success/i)).toBeInTheDocument(); }); @@ -31,7 +31,7 @@ describe("ReissueNewDonorCaseSummary", () => { const { getByText } = render(); expect( - getByText(/Error reissuing donor case for IPS Manager/i) + getByText(/Error reissuing new donor case for testuser1/i) ).toBeInTheDocument(); expect(getByText(/Internal Server Error/i)).toBeInTheDocument(); }); diff --git a/src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts b/src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts deleted file mode 100644 index c8083472..00000000 --- a/src/server/handlers/reissueNewDonorCaseCloudFunctionHandler.ts +++ /dev/null @@ -1,39 +0,0 @@ -import express, { Request, Response, Router } from "express"; -import { callCloudFunctionToReissueNewDonorDonorCase } from "../helpers/cloudFunctionCallerHelper"; - -export default function newCloudFunctionHandler( - ReissueNewDonorCaseCloudFunctionUrl: string -): Router { - const router = express.Router(); - const cloudFunctionHandler = new CloudFunctionHandler( - ReissueNewDonorCaseCloudFunctionUrl - ); - router.post("/api/cloudFunction/reissueNewDonorCase", cloudFunctionHandler.CallCloudFunction); - - return router; -} - -export class CloudFunctionHandler { - ReissueNewDonorCaseCloudFunctionUrl: string; - - constructor(ReissueNewDonorCaseCloudFunctionUrl: string) { - this.ReissueNewDonorCaseCloudFunctionUrl = ReissueNewDonorCaseCloudFunctionUrl; - this.CallCloudFunction = this.CallCloudFunction.bind(this); - } - - async CallCloudFunction(req: Request, res: Response): Promise { - const reqData = req.body; - req.log.info(`${this.ReissueNewDonorCaseCloudFunctionUrl} URL to invoke for Reissuing New Donor Case.`); - try { - const cloudFunctionResponse = await callCloudFunctionToReissueNewDonorDonorCase(this.ReissueNewDonorCaseCloudFunctionUrl, reqData); - return res.status(cloudFunctionResponse.status).json(cloudFunctionResponse); - - } catch (error) { - console.error("Error:", error); - return res.status(500).json({ - message: (error as any)?.response.data, - status: 500, - }); - } - } -} diff --git a/src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts b/src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts deleted file mode 100644 index 7d14a85c..00000000 --- a/src/server/handlers/reissueNewDonorCcaseCloudFunctionHandler.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -// it.skip("placeholder", () => { }); - -import { newServer } from "../server"; -import supertest from "supertest"; -import { getConfigFromEnv } from "../config"; -import createLogger from "../pino"; -import { callCloudFunctionToReissueNewDonorDonorCase } from "../helpers/cloudFunctionCallerHelper"; -import { cloudFunctionAxiosError } from "../../features/step_definitions/helpers/apiMockObjects"; - -jest.mock("../helpers/cloudFunctionCallerHelper"); -const successResponse = { - message: "Success", - status: 200, -}; - -const config = getConfigFromEnv(); -const callCloudFunctionToReissueDonorCaseMock = callCloudFunctionToReissueNewDonorDonorCase as jest.Mock>; - -describe("Call Cloud Function to reissue new donor case and return responses", () => { - let request: supertest.SuperTest; - - beforeEach(() => { - request = supertest(newServer(config, createLogger())); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should return a 200 status and a json object with message and status if successfully reissued donor case", async () => { - callCloudFunctionToReissueDonorCaseMock.mockResolvedValue(successResponse); - - const response = await request.post("/api/cloudFunction/reissueNewDonorCase"); - - expect(response.status).toEqual(200); - expect(response.body).toEqual(successResponse); - }); - - it("should return a 500 status and a json object with message and status if cloud function failed reissuing donor case", async () => { - callCloudFunctionToReissueDonorCaseMock.mockRejectedValue(cloudFunctionAxiosError); - - const response = await request.post("/api/cloudFunction/reissueNewDonorCase"); - - expect(response.status).toEqual(500); - expect(response.body.message).toEqual((cloudFunctionAxiosError as any).response.data); - }); -}); From d13da35d1447041546975e68708a0b0838ed37cc Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:16:25 +0000 Subject: [PATCH 08/20] Fixed issues with calling cloud function, now works correctly --- .../reissueNewDonorCaseConfirmation.tsx | 2 +- .../handlers/cloudFunctionHandler.test.ts | 22 +++++++-------- src/server/handlers/cloudFunctionHandler.ts | 22 +++++++++++---- .../helpers/cloudFunctionCallerHelper.test.ts | 6 ++--- .../helpers/cloudFunctionCallerHelper.ts | 27 +------------------ src/server/server.ts | 3 ++- 6 files changed, 35 insertions(+), 47 deletions(-) diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx index 2c7c82e4..8dc09ae0 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx @@ -21,7 +21,7 @@ function ReissueNewDonorCaseConfirmation(): ReactElement { async function callReissueNewDonorCaseCloudFunction() { isLoading(true); - const payload = { questionnaire_name: questionnaire.name, role: user }; + const payload = { questionnaire_name: questionnaire.name, user: user }; let res; try { res = await axios.post("/api/cloudFunction/reissueNewDonorCase", payload, axiosConfig()); diff --git a/src/server/handlers/cloudFunctionHandler.test.ts b/src/server/handlers/cloudFunctionHandler.test.ts index 15467f80..e6335cd8 100644 --- a/src/server/handlers/cloudFunctionHandler.test.ts +++ b/src/server/handlers/cloudFunctionHandler.test.ts @@ -4,7 +4,7 @@ import { newServer } from "../server"; import supertest from "supertest"; import { getConfigFromEnv } from "../config"; import createLogger from "../pino"; -import { callCloudFunctionToCreateDonorCases } from "../helpers/cloudFunctionCallerHelper"; +import { callCloudFunction } from "../helpers/cloudFunctionCallerHelper"; import { cloudFunctionAxiosError } from "../../features/step_definitions/helpers/apiMockObjects"; jest.mock("../helpers/cloudFunctionCallerHelper"); @@ -14,33 +14,33 @@ const successResponse = { }; const config = getConfigFromEnv(); -const callCloudFunctionToCreateDonorCasesMock = callCloudFunctionToCreateDonorCases as jest.Mock>; +const callCloudFunctionToCreateDonorCasesMock = callCloudFunction as jest.Mock>; describe("Call Cloud Function to create donor cases and return responses", () => { let request: supertest.SuperTest; - + beforeEach(() => { request = supertest(newServer(config, createLogger())); }); - + afterEach(() => { - jest.clearAllMocks(); + jest.clearAllMocks(); }); - + it("should return a 200 status and a json object with message and status if successfully created donor cases", async () => { callCloudFunctionToCreateDonorCasesMock.mockResolvedValue(successResponse); - + const response = await request.post("/api/cloudFunction/createDonorCases"); - + expect(response.status).toEqual(200); expect(response.body).toEqual(successResponse); }); - + it("should return a 500 status and a json object with message and status if cloud function failed creating donor cases", async () => { callCloudFunctionToCreateDonorCasesMock.mockRejectedValue(cloudFunctionAxiosError); - + const response = await request.post("/api/cloudFunction/createDonorCases"); - + expect(response.status).toEqual(500); expect(response.body.message).toEqual((cloudFunctionAxiosError as any).response.data); }); diff --git a/src/server/handlers/cloudFunctionHandler.ts b/src/server/handlers/cloudFunctionHandler.ts index 1f6ca9b1..8ac31563 100644 --- a/src/server/handlers/cloudFunctionHandler.ts +++ b/src/server/handlers/cloudFunctionHandler.ts @@ -1,5 +1,5 @@ import express, { Request, Response, Router } from "express"; -import { callCloudFunctionToCreateDonorCases } from "../helpers/cloudFunctionCallerHelper"; +import { callCloudFunction } from "../helpers/cloudFunctionCallerHelper"; export default function newCloudFunctionHandler( CreateDonorCasesCloudFunctionUrl: string @@ -13,19 +13,31 @@ export default function newCloudFunctionHandler( return router; } +export function reissueNewDonorCaseCloudFunctionHandler( + CloudFunctionUrl: string +): Router { + const router = express.Router(); + const cloudFunctionHandler = new CloudFunctionHandler( + CloudFunctionUrl + ); + router.post("/api/cloudFunction/reissueNewDonorCase", cloudFunctionHandler.CallCloudFunction); + + return router; +} + export class CloudFunctionHandler { - CreateDonorCasesCloudFunctionUrl: string; + CloudFunctionUrl: string; constructor(CreateDonorCasesCloudFunctionUrl: string) { - this.CreateDonorCasesCloudFunctionUrl = CreateDonorCasesCloudFunctionUrl; + this.CloudFunctionUrl = CreateDonorCasesCloudFunctionUrl; this.CallCloudFunction = this.CallCloudFunction.bind(this); } async CallCloudFunction(req: Request, res: Response): Promise { const reqData = req.body; - req.log.info(`${this.CreateDonorCasesCloudFunctionUrl} URL to invoke for Creating Donor Cases.`); + req.log.info(`${this.CloudFunctionUrl} URL to invoke for Creating Donor Cases.`); try { - const cloudfunctionResponse = await callCloudFunctionToCreateDonorCases(this.CreateDonorCasesCloudFunctionUrl, reqData); + const cloudfunctionResponse = await callCloudFunction(this.CloudFunctionUrl, reqData); return res.status(cloudfunctionResponse.status).json(cloudfunctionResponse); } catch (error) { diff --git a/src/server/helpers/cloudFunctionCallerHelper.test.ts b/src/server/helpers/cloudFunctionCallerHelper.test.ts index 8c4101e6..15f73781 100644 --- a/src/server/helpers/cloudFunctionCallerHelper.test.ts +++ b/src/server/helpers/cloudFunctionCallerHelper.test.ts @@ -1,5 +1,5 @@ import { getConfigFromEnv } from "../config"; -import { callCloudFunctionToCreateDonorCases } from "./cloudFunctionCallerHelper"; +import { callCloudFunction } from "./cloudFunctionCallerHelper"; import axios from "axios"; import { ipsQuestionnaire } from "../../features/step_definitions/helpers/apiMockObjects"; import { GoogleAuth } from "google-auth-library"; @@ -47,7 +47,7 @@ describe("Call Cloud Function to create donor cases and return responses", () => status: mockSuccessResponse.status, }); - const result = await callCloudFunctionToCreateDonorCases( + const result = await callCloudFunction( dummyUrl, payload ); @@ -84,7 +84,7 @@ describe("Call Cloud Function to create donor cases and return responses", () => status: mockErrorResponse.status, }); - const result = await callCloudFunctionToCreateDonorCases( + const result = await callCloudFunction( dummyUrl, payload ); diff --git a/src/server/helpers/cloudFunctionCallerHelper.ts b/src/server/helpers/cloudFunctionCallerHelper.ts index 1492f15a..42d3f774 100644 --- a/src/server/helpers/cloudFunctionCallerHelper.ts +++ b/src/server/helpers/cloudFunctionCallerHelper.ts @@ -9,32 +9,7 @@ export async function getIdTokenFromMetadataServer(targetAudience: string) { return await client.idTokenProvider.fetchIdToken(targetAudience); } -export async function callCloudFunctionToCreateDonorCases(url: string, payload: any): Promise<{ message: string, status: number }> { - - const token = await getIdTokenFromMetadataServer(url); - - try { - const res = await axios.post(url, payload, { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - - return { - message: res.data, - status: res.status - }; - } catch (error) { - console.error("Error:", error); - return { - message: (error as any).response.data, - status: 500 - }; - } -} - -export async function callCloudFunctionToReissueNewDonorDonorCase(url: string, payload: any): Promise<{ message: string, status: number }> { +export async function callCloudFunction(url: string, payload: any): Promise<{ message: string, status: number }> { const token = await getIdTokenFromMetadataServer(url); diff --git a/src/server/server.ts b/src/server/server.ts index 33a75308..c048de2d 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -18,6 +18,7 @@ import { HttpLogger } from "pino-http"; import AuditLogger from "./auditLogging/logger"; import newAuditHandler from "./handlers/auditHandler"; import newCloudFunctionHandler from "./handlers/cloudFunctionHandler"; +import { reissueNewDonorCaseCloudFunctionHandler } from "./handlers/cloudFunctionHandler"; if (process.env.NODE_ENV === "production") { import("@google-cloud/profiler").then((profiler) => { @@ -45,7 +46,7 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): const uploadHandler = newUploadHandler(storageManager, auth, auditLogger); const auditHandler = newAuditHandler(auditLogger); const cloudFunctionHandler = newCloudFunctionHandler(config.CreateDonorCasesCloudFunctionUrl); - const reissueNewDonorCaseHandler = newCloudFunctionHandler(config.ReissueNewDonorCaseCloudFunctionUrl); + const reissueNewDonorCaseHandler = reissueNewDonorCaseCloudFunctionHandler(config.ReissueNewDonorCaseCloudFunctionUrl); const server = express(); From 6effcf3bcf5017e69c3fb21815d0de80fdf47a13 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:19:39 +0000 Subject: [PATCH 09/20] Fixed failing tests --- .../reissueNewDonorCaseConfirmation.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx index c76648c3..7a0d9b7c 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx @@ -4,7 +4,7 @@ import React from "react"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate, useParams } from "react-router-dom"; +import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate } from "react-router-dom"; import ReissueNewDonorCaseConfirmation from "./reissueNewDonorCaseConfirmation"; import "@testing-library/jest-dom"; import axios from "axios"; @@ -112,7 +112,7 @@ describe("ReissueNewDonorCaseConfirmation navigation", () => { expect(mockedAxios.post).toHaveBeenCalledWith( "/api/cloudFunction/reissueNewDonorCase", - { questionnaire_name: ipsQuestionnaire.name, role: "testuser" }, + { questionnaire_name: ipsQuestionnaire.name, user: "testuser" }, { headers: { "Content-Type": "application/json" } } ); @@ -141,7 +141,7 @@ describe("ReissueNewDonorCaseConfirmation navigation", () => { expect(mockedAxios.post).toHaveBeenCalledWith( "/api/cloudFunction/reissueNewDonorCase", - { questionnaire_name: ipsQuestionnaire.name, role: "testuser" }, + { questionnaire_name: ipsQuestionnaire.name, user: "testuser" }, { headers: { "Content-Type": "application/json" } } ); From e12ddcaa6558b1d7cd1588e4e7badeae5028c94d Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:38:51 +0000 Subject: [PATCH 10/20] Cleaned up some code to be more generic --- src/server/handlers/cloudFunctionHandler.ts | 16 ++++++++-------- src/server/server.ts | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/server/handlers/cloudFunctionHandler.ts b/src/server/handlers/cloudFunctionHandler.ts index 8ac31563..2fb9376c 100644 --- a/src/server/handlers/cloudFunctionHandler.ts +++ b/src/server/handlers/cloudFunctionHandler.ts @@ -1,12 +1,12 @@ import express, { Request, Response, Router } from "express"; import { callCloudFunction } from "../helpers/cloudFunctionCallerHelper"; -export default function newCloudFunctionHandler( - CreateDonorCasesCloudFunctionUrl: string +export default function createDonorCasesCloudFunctionHandler( + CloudFunctionUrl: string ): Router { const router = express.Router(); const cloudFunctionHandler = new CloudFunctionHandler( - CreateDonorCasesCloudFunctionUrl + CloudFunctionUrl ); router.post("/api/cloudFunction/createDonorCases", cloudFunctionHandler.CallCloudFunction); @@ -28,17 +28,17 @@ export function reissueNewDonorCaseCloudFunctionHandler( export class CloudFunctionHandler { CloudFunctionUrl: string; - constructor(CreateDonorCasesCloudFunctionUrl: string) { - this.CloudFunctionUrl = CreateDonorCasesCloudFunctionUrl; + constructor(CloudFunctionUrl: string) { + this.CloudFunctionUrl = CloudFunctionUrl; this.CallCloudFunction = this.CallCloudFunction.bind(this); } async CallCloudFunction(req: Request, res: Response): Promise { const reqData = req.body; - req.log.info(`${this.CloudFunctionUrl} URL to invoke for Creating Donor Cases.`); + req.log.info(`${this.CloudFunctionUrl} URL to invoke for Cloud Function.`); try { - const cloudfunctionResponse = await callCloudFunction(this.CloudFunctionUrl, reqData); - return res.status(cloudfunctionResponse.status).json(cloudfunctionResponse); + const cloudFunctionResponse = await callCloudFunction(this.CloudFunctionUrl, reqData); + return res.status(cloudFunctionResponse.status).json(cloudFunctionResponse); } catch (error) { console.error("Error:", error); diff --git a/src/server/server.ts b/src/server/server.ts index c048de2d..066a6936 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -17,7 +17,7 @@ import createLogger from "./pino"; import { HttpLogger } from "pino-http"; import AuditLogger from "./auditLogging/logger"; import newAuditHandler from "./handlers/auditHandler"; -import newCloudFunctionHandler from "./handlers/cloudFunctionHandler"; +import createDonorCasesCloudFunctionHandler from "./handlers/cloudFunctionHandler"; import { reissueNewDonorCaseCloudFunctionHandler } from "./handlers/cloudFunctionHandler"; if (process.env.NODE_ENV === "production") { @@ -45,7 +45,7 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): const busHandler = newBusHandler(busApiClient, auth); const uploadHandler = newUploadHandler(storageManager, auth, auditLogger); const auditHandler = newAuditHandler(auditLogger); - const cloudFunctionHandler = newCloudFunctionHandler(config.CreateDonorCasesCloudFunctionUrl); + const createDonorCasesHandler = createDonorCasesCloudFunctionHandler(config.CreateDonorCasesCloudFunctionUrl); const reissueNewDonorCaseHandler = reissueNewDonorCaseCloudFunctionHandler(config.ReissueNewDonorCaseCloudFunctionUrl); const server = express(); @@ -72,7 +72,7 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): server.use("/", bimsHandler); server.use("/", busHandler); server.use("/", auditHandler); - server.use("/", cloudFunctionHandler); + server.use("/", createDonorCasesHandler); server.use("/", reissueNewDonorCaseHandler); server.use("/", HealthCheckHandler()); From c31be028d562aa35ca1f71bcc8fce49f41d42f19 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:28:22 +0000 Subject: [PATCH 11/20] Added some minor fixes to try and get cloud function working on sandbox --- appengine_templates/app.yaml.tpl | 1 + jest.config.js | 1 + 2 files changed, 2 insertions(+) diff --git a/appengine_templates/app.yaml.tpl b/appengine_templates/app.yaml.tpl index caa5875f..634a4ad2 100644 --- a/appengine_templates/app.yaml.tpl +++ b/appengine_templates/app.yaml.tpl @@ -17,6 +17,7 @@ env_variables: SESSION_SECRET: _SESSION_SECRET ROLES: _ROLES CREATE_DONOR_CASES_CLOUD_FUNCTION_URL: _CREATE_DONOR_CASES_CLOUD_FUNCTION_URL + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL: _REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL automatic_scaling: min_instances: _MIN_INSTANCES diff --git a/jest.config.js b/jest.config.js index 97385be4..05837f2b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,5 +17,6 @@ process.env = Object.assign(process.env, { BIMS_API_URL: "bims-mock-api", BIMS_CLIENT_ID: "mock-client-id", CREATE_DONOR_CASES_CLOUD_FUNCTION_URL: "mock-cloud-function-url", + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL: "mock-cloud-function-url", AUTH_TOKEN: "mock-dummy-token" }); From 63b806ac28590db83ded712a72c52fac4fa3cb86 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:48:28 +0000 Subject: [PATCH 12/20] GUI Fixes --- README.md | 26 +++++------ .../sections/reissueNewDonorCase.tsx | 43 ++++++++++++------- .../reissueNewDonorCaseConfirmation.tsx | 3 +- .../reissueNewDonorCaseSummary.tsx | 5 +-- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4649eced..2198b9ec 100644 --- a/README.md +++ b/README.md @@ -63,18 +63,19 @@ git clone https://github.com/ONSdigital/blaise-deploy-questionnaire-service.git Create a new .env file and add the following variables. -| Variable | Description | Var Example | -|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------| -| PORT | **Optional variable**, specify the Port for express server to run on. If not passed in this is set as 5000 by default.

It's best not to set this as the react project will try and use the variable as well and conflict. By default, React project locally runs on port 3000 | 5009 | -| BLAISE_API_URL | URL that the [Blaise Rest API](https://github.com/ONSdigital/blaise-api-rest) is running on to send calls to | localhost:90 | -| PROJECT_ID | GCP Project ID | ons-blaise-dev-matt55 | -| BUCKET_NAME | GCP Bucket name for the Questionnaire file to be put in | ons-blaise-dev-matt55-dqs | -| SERVER_PARK | Name of Blaise Server Park | gusty | -| BIMS_API_URL | URL that the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) is running on to send calls to set and get the live date | localhost:5011 | -| BIMS_CLIENT_ID | GCP IAP ID for the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) | randomKey0112 | -| BUS_API_CLIENT | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | -| BUS_CLIENT_ID | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | -| CREATE_DONOR_CASES_CLOUD_FUNCTION_URL | URL to trigger the Create Donor Cases Cloud Function in GCP. This is needed when you want to deploy donor cases for an IPS questionnaire. **The Cloud Function needs to be deployed in GCP**. | https://example-cloud-function-url.com | +| Variable | Description | Var Example | +|-------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------| +| PORT | **Optional variable**, specify the Port for express server to run on. If not passed in this is set as 5000 by default.

It's best not to set this as the react project will try and use the variable as well and conflict. By default, React project locally runs on port 3000 | 5009 | +| BLAISE_API_URL | URL that the [Blaise Rest API](https://github.com/ONSdigital/blaise-api-rest) is running on to send calls to | localhost:90 | +| PROJECT_ID | GCP Project ID | ons-blaise-dev-matt55 | +| BUCKET_NAME | GCP Bucket name for the Questionnaire file to be put in | ons-blaise-dev-matt55-dqs | +| SERVER_PARK | Name of Blaise Server Park | gusty | +| BIMS_API_URL | URL that the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) is running on to send calls to set and get the live date | localhost:5011 | +| BIMS_CLIENT_ID | GCP IAP ID for the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) | randomKey0112 | +| BUS_API_CLIENT | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | +| BUS_CLIENT_ID | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | +| CREATE_DONOR_CASES_CLOUD_FUNCTION_URL | URL to trigger the Create Donor Cases Cloud Function in GCP. This is needed when you want to deploy donor cases for an IPS questionnaire. **The Cloud Function needs to be deployed in GCP**. | https://example-cloud-function-url.com | +| REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL | URL to trigger the Reissue New Donor Case Cloud Function in GCP. This is needed when you want to reissue a new donor case for an IPS questionnaire. **The Cloud Function needs to be deployed in GCP**. | https://example-cloud-function-url.com | To find the `X_CLIENT_ID`, navigate to the GCP console, search for `IAP`, click the three dots on right of the service and select `OAuth`. `Client Id` will be on the right. @@ -92,6 +93,7 @@ BIMS_CLIENT_ID=randomKey0778 BUS_API_URL=FOO BUS_CLIENT_ID=FOO CREATE_DONOR_CASES_CLOUD_FUNCTION_URL= +REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL= ``` **NB** DQS environment variables for sandboxes can be found within GCP > App Engine > Versions > DQS service > Config diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index 8d317497..9137283b 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -1,6 +1,7 @@ import React, { ReactElement, useState } from "react"; import { Questionnaire } from "blaise-api-node-client"; -import { Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { ONSButton } from "blaise-design-system-react-components"; interface Props { questionnaire: Questionnaire; @@ -8,6 +9,25 @@ interface Props { function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { const [user, setUser] = useState(""); + const [error, setError] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const navigate = useNavigate(); + + function reissueNewDonorCaseButtonClicked() { + const trimmedUser = user.trim(); + setUser(trimmedUser); + + // Check if input is empty after trimming + if (trimmedUser === "") { + setErrorMessage("User input cannot be empty or contain only spaces"); + setError(true); + } else { + setError(false); + setErrorMessage(""); + navigate("/reissueNewDonorCaseConfirmation", { state: { section: "reissueNewDonorCase", questionnaire: questionnaire, user: trimmedUser } }); + } + } return ( <> @@ -23,25 +43,18 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { - setUser(e.target.value)}/> + setUser(e.target.value)}/> + {error &&

{errorMessage}

}

-
- - Reissue Donor case - +
@@ -45,7 +45,6 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { setUser(e.target.value)}/> - {error &&

{errorMessage}

} + {error &&

{errorMessage}

}
@@ -57,6 +56,7 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { />
diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx index 36c433bf..54db8153 100644 --- a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -33,6 +33,6 @@ describe("ReissueNewDonorCaseSummary", () => { expect( getByText(/Error reissuing new donor case for testuser1/i) ).toBeInTheDocument(); - expect(getByText(/When reporting this issue to the Service Desk, please provide the questionnaire name, user, yarn testtime and date of the failure./i)).toBeInTheDocument(); + expect(getByText(/When reporting this issue to the Service Desk, please provide the questionnaire name, user, time and date of the failure./i)).toBeInTheDocument(); }); }); From e556047c70c84406bf1b8392e3c9f3b4dee1b5fd Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:07:04 +0000 Subject: [PATCH 17/20] Changed text input to be below text input field --- .../sections/reissueNewDonorCase.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index 2859cb52..81816733 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -43,7 +43,13 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { - setUser(e.target.value)}/> + setUser(e.target.value)}/> +
+
+ {error && +

{errorMessage}

}
@@ -56,7 +62,6 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { /> - {error &&

{errorMessage}

} From b61807e2fed15cc3244e43991a46e96ddeb390ab Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:12:42 +0000 Subject: [PATCH 18/20] Added padding around error message to be more inline with other components --- .../sections/reissueNewDonorCase.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index 81816733..67063d59 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -1,7 +1,7 @@ import React, { ReactElement, useState } from "react"; import { Questionnaire } from "blaise-api-node-client"; import { useNavigate } from "react-router-dom"; -import { ONSButton } from "blaise-design-system-react-components"; +import { ONSButton, ONSPanel } from "blaise-design-system-react-components"; interface Props { questionnaire: Questionnaire; @@ -47,9 +47,13 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { className="ons-input ons-input--text ons-input-type__input" value={user} onChange={(e) => setUser(e.target.value)}/> -
+
{error && -

{errorMessage}

} + +

+ {errorMessage} +

+
}
From 1b5a49586de0f7640273d247f36cd4a888784604 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:22:11 +0100 Subject: [PATCH 19/20] Created files ready for editing and testing. Still need to run locally to test Adding files necessary to reissue new donor case Changed wording and look and feel of textbox Slight changes and fixes Made changes to show both error messages for create donor cases and reissue new donor case Fixing Tests so far Fixed tests so far Fixed issues with calling cloud function, now works correctly Fixed failing tests Cleaned up some code to be more generic Added some minor fixes to try and get cloud function working on sandbox GUI Fixes GUI Fixes GUI Fixes Fixed failing test after GUI changes Added padding around error message and various UI Fixes Changed text input to be below text input field --- README.md | 28 +-- appengine_templates/app.yaml.tpl | 1 + jest.config.js | 1 + src/app.tsx | 9 +- .../createDonorCasesConfirmation.test.tsx | 26 ++- .../createDonorCasesConfirmation.tsx | 6 +- .../questionnaireDetailsPage.tsx | 13 +- .../sections/createDonorCases.tsx | 2 +- .../sections/reissueNewDonorCase.tsx | 73 ++++++++ .../reissueNewDonorCaseConfirmation.test.tsx | 161 ++++++++++++++++++ .../reissueNewDonorCaseConfirmation.tsx | 74 ++++++++ .../reissueNewDonorCaseSummary.test.tsx | 38 +++++ .../reissueNewDonorCaseSummary.tsx | 38 +++++ .../helpers/apiMockObjects.ts | 15 +- src/server/config.ts | 15 +- .../handlers/cloudFunctionHandler.test.ts | 22 +-- src/server/handlers/cloudFunctionHandler.ts | 32 ++-- .../helpers/cloudFunctionCallerHelper.test.ts | 6 +- .../helpers/cloudFunctionCallerHelper.ts | 8 +- src/server/server.ts | 9 +- 20 files changed, 509 insertions(+), 68 deletions(-) create mode 100644 src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx create mode 100644 src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx diff --git a/README.md b/README.md index a113dada..2198b9ec 100644 --- a/README.md +++ b/README.md @@ -63,18 +63,19 @@ git clone https://github.com/ONSdigital/blaise-deploy-questionnaire-service.git Create a new .env file and add the following variables. -| Variable | Description | Var Example | -|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------| -| PORT | **Optional variable**, specify the Port for express server to run on. If not passed in this is set as 5000 by default.

It's best not to set this as the react project will try and use the variable as well and conflict. By default, React project locally runs on port 3000 | 5009 | -| BLAISE_API_URL | URL that the [Blaise Rest API](https://github.com/ONSdigital/blaise-api-rest) is running on to send calls to | localhost:90 | -| PROJECT_ID | GCP Project ID | ons-blaise-dev-matt55 | -| BUCKET_NAME | GCP Bucket name for the Questionnaire file to be put in | ons-blaise-dev-matt55-dqs | -| SERVER_PARK | Name of Blaise Server Park | gusty | -| BIMS_API_URL | URL that the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) is running on to send calls to set and get the live date | localhost:5011 | -| BIMS_CLIENT_ID | GCP IAP ID for the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) | randomKey0112 | -| BUS_API_CLIENT | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | -| BUS_CLIENT_ID | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | -| CREATE_DONOR_CASES_CLOUD_FUNCTION_URL | URL to trigger the Create Donor Cases Cloud Function in GCP. This is needed when you want to deploy donor cases for an IPS questionnaire. **The Cloud Function needs to be deployed in GCP**. | https://example-cloud-function-url.com | +| Variable | Description | Var Example | +|-------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------| +| PORT | **Optional variable**, specify the Port for express server to run on. If not passed in this is set as 5000 by default.

It's best not to set this as the react project will try and use the variable as well and conflict. By default, React project locally runs on port 3000 | 5009 | +| BLAISE_API_URL | URL that the [Blaise Rest API](https://github.com/ONSdigital/blaise-api-rest) is running on to send calls to | localhost:90 | +| PROJECT_ID | GCP Project ID | ons-blaise-dev-matt55 | +| BUCKET_NAME | GCP Bucket name for the Questionnaire file to be put in | ons-blaise-dev-matt55-dqs | +| SERVER_PARK | Name of Blaise Server Park | gusty | +| BIMS_API_URL | URL that the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) is running on to send calls to set and get the live date | localhost:5011 | +| BIMS_CLIENT_ID | GCP IAP ID for the [BIMS Service](https://github.com/ONSdigital/blaise-instrument-metadata-service) | randomKey0112 | +| BUS_API_CLIENT | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | +| BUS_CLIENT_ID | Not needed for local development but the config will look for this variables in the .env file and throw an error if it is not found. Hence, give them a random string | FOO | +| CREATE_DONOR_CASES_CLOUD_FUNCTION_URL | URL to trigger the Create Donor Cases Cloud Function in GCP. This is needed when you want to deploy donor cases for an IPS questionnaire. **The Cloud Function needs to be deployed in GCP**. | https://example-cloud-function-url.com | +| REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL | URL to trigger the Reissue New Donor Case Cloud Function in GCP. This is needed when you want to reissue a new donor case for an IPS questionnaire. **The Cloud Function needs to be deployed in GCP**. | https://example-cloud-function-url.com | To find the `X_CLIENT_ID`, navigate to the GCP console, search for `IAP`, click the three dots on right of the service and select `OAuth`. `Client Id` will be on the right. @@ -83,7 +84,7 @@ The .env file should be setup as below Example .env file: ```.env -BLAISE_API_URL=localhost:5011 +BLAISE_API_URL=localhost:8011 PROJECT_ID=ons-blaise-v2-dev- BUCKET_NAME=ons-blaise-v2-dev--dqs SERVER_PARK=gusty @@ -92,6 +93,7 @@ BIMS_CLIENT_ID=randomKey0778 BUS_API_URL=FOO BUS_CLIENT_ID=FOO CREATE_DONOR_CASES_CLOUD_FUNCTION_URL= +REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL= ``` **NB** DQS environment variables for sandboxes can be found within GCP > App Engine > Versions > DQS service > Config diff --git a/appengine_templates/app.yaml.tpl b/appengine_templates/app.yaml.tpl index caa5875f..634a4ad2 100644 --- a/appengine_templates/app.yaml.tpl +++ b/appengine_templates/app.yaml.tpl @@ -17,6 +17,7 @@ env_variables: SESSION_SECRET: _SESSION_SECRET ROLES: _ROLES CREATE_DONOR_CASES_CLOUD_FUNCTION_URL: _CREATE_DONOR_CASES_CLOUD_FUNCTION_URL + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL: _REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL automatic_scaling: min_instances: _MIN_INSTANCES diff --git a/jest.config.js b/jest.config.js index 97385be4..05837f2b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,5 +17,6 @@ process.env = Object.assign(process.env, { BIMS_API_URL: "bims-mock-api", BIMS_CLIENT_ID: "mock-client-id", CREATE_DONOR_CASES_CLOUD_FUNCTION_URL: "mock-cloud-function-url", + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL: "mock-cloud-function-url", AUTH_TOKEN: "mock-dummy-token" }); diff --git a/src/app.tsx b/src/app.tsx index a695a98e..8a392663 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -21,6 +21,7 @@ import QuestionnaireDetailsPage from "./components/questionnaireDetailsPage/ques import ChangeTOStartDate from "./components/questionnaireDetailsPage/changeTOStartDate"; import ChangeTMReleaseDate from "./components/questionnaireDetailsPage/changeTmReleaseDate"; import CreateDonorCasesConfirmation from "./components/createDonorCasePage/createDonorCasesConfirmation"; +import ReissueNewDonorCaseConfirmation from "./components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation"; import "./style.css"; import { isProduction } from "./client/env"; import { Authenticate } from "blaise-login-react/blaise-login-react-client"; @@ -101,6 +102,10 @@ function App(): ReactElement { path="/createDonorCasesConfirmation" element={}> + }> + @@ -122,7 +127,7 @@ function App(): ReactElement { {(_user, loggedIn, logOutFunction) => ( <> - Skip to content + Skip to content { isProduction(window.location.hostname) ? <> : } @@ -154,4 +159,4 @@ function App(): ReactElement { ); } -export default App; \ No newline at end of file +export default App; diff --git a/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx b/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx index 230cd838..1ea6977b 100644 --- a/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx +++ b/src/components/createDonorCasePage/createDonorCasesConfirmation.test.tsx @@ -8,7 +8,12 @@ import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate, useParam import CreateDonorCasesConfirmation from "./createDonorCasesConfirmation"; import "@testing-library/jest-dom"; import axios from "axios"; -import { cloudFunctionAxiosError, ipsQuestionnaire, mockSuccessResponseForDonorCasesCreation } from "../../features/step_definitions/helpers/apiMockObjects"; +import { + cloudFunctionAxiosError, + ipsQuestionnaire, + mockSectionForDonorCasesCreation, + mockSuccessResponseForDonorCasesCreation +} from "../../features/step_definitions/helpers/apiMockObjects"; jest.mock("axios"); @@ -81,15 +86,16 @@ describe("CreateDonorCasesConfirmation navigation", () => { }); it("should redirect back to the questionnaire details page if user clicks Cancel", async () => { - + act(() => { fireEvent.click(screen.getByRole("button", { name: "Cancel" })); }); expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: "", - donorCasesStatusCode: 0, + section: "createDonorCases", + responseMessage: "", + statusCode: 0, role: "", questionnaire: ipsQuestionnaire, }, @@ -113,8 +119,9 @@ describe("CreateDonorCasesConfirmation navigation", () => { expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: mockSuccessResponseForDonorCasesCreation.data, - donorCasesStatusCode: mockSuccessResponseForDonorCasesCreation.status, + section: mockSectionForDonorCasesCreation.data, + responseMessage: mockSuccessResponseForDonorCasesCreation.data, + statusCode: mockSuccessResponseForDonorCasesCreation.status, questionnaire: ipsQuestionnaire, role: "IPS Manager" } @@ -141,8 +148,9 @@ describe("CreateDonorCasesConfirmation navigation", () => { expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { state: { - donorCasesResponseMessage: (cloudFunctionAxiosError as any).response.data.message, - donorCasesStatusCode: 500, + section: "createDonorCases", + responseMessage: (cloudFunctionAxiosError as any).response.data.message, + statusCode: 500, questionnaire: ipsQuestionnaire, role: "IPS Manager" } @@ -150,4 +158,4 @@ describe("CreateDonorCasesConfirmation navigation", () => { }); }); -}); \ No newline at end of file +}); diff --git a/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx b/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx index a003825f..e7f6b9dc 100644 --- a/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx +++ b/src/components/createDonorCasePage/createDonorCasesConfirmation.tsx @@ -33,7 +33,7 @@ function CreateDonorCasesConfirmation(): ReactElement { }; } isLoading(false); - navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: res.data, donorCasesStatusCode: res.status, questionnaire: questionnaire, role: role } }); + navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "createDonorCases", responseMessage: res.data, statusCode: res.status, questionnaire: questionnaire, role: role } }); } if (loading) { return ; @@ -61,7 +61,7 @@ function CreateDonorCasesConfirmation(): ReactElement { /> navigate(`/questionnaire/${questionnaire.name}`, { state: { donorCasesResponseMessage: "", donorCasesStatusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> + onClick={() => navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "createDonorCases", responseMessage: "", statusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> ) } @@ -70,4 +70,4 @@ function CreateDonorCasesConfirmation(): ReactElement { ); } -export default CreateDonorCasesConfirmation; \ No newline at end of file +export default CreateDonorCasesConfirmation; diff --git a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx index c5082943..82fbba7d 100644 --- a/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx +++ b/src/components/questionnaireDetailsPage/questionnaireDetailsPage.tsx @@ -13,11 +13,14 @@ import { ONSButton, ONSLoadingPanel, ONSPanel } from "blaise-design-system-react import QuestionnaireDetails from "./sections/questionnaireDetails"; import CreateDonorCases from "./sections/createDonorCases"; import CreateDonorCasesSummary from "../createDonorCasePage/createDonorCasesSummary"; +import ReissueNewDonorCase from "./sections/reissueNewDonorCase"; +import ReissueNewDonorCaseSummary from "../reissueNewDonorCasePage/reissueNewDonorCaseSummary"; interface State { + section?: string; questionnaire: Questionnaire | null; - donorCasesResponseMessage?: string; - donorCasesStatusCode?: number; + responseMessage?: string; + statusCode?: number; role?: string; } @@ -31,7 +34,7 @@ function QuestionnaireDetailsPage(): ReactElement { const [loaded, setLoaded] = useState(false); const initialState = location || { questionnaire: null }; const { questionnaireName } = useParams(); - const { donorCasesResponseMessage, donorCasesStatusCode, role } = location || { donorCasesResponseMessage: "", donorCasesStatusCode: 0, role: "" }; + const { section, responseMessage, statusCode, role } = location || { section: "", responseMessage: "", statusCode: 0, role: "" }; useEffect(() => { if (initialState.questionnaire === null) { @@ -115,9 +118,11 @@ function QuestionnaireDetailsPage(): ReactElement { {questionnaire.name} - {donorCasesResponseMessage && donorCasesStatusCode && role && } + {section === "createDonorCases" && responseMessage && statusCode && role && } + {section === "reissueNewDonorCase" && responseMessage && statusCode && role && } {questionnaire.name.includes("IPS") && } + {questionnaire.name.includes("IPS") && } diff --git a/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx b/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx index 2bf4219e..0cf548b6 100644 --- a/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx +++ b/src/components/questionnaireDetailsPage/sections/createDonorCases.tsx @@ -38,7 +38,7 @@ function CreateDonorCases({ questionnaire }: Props): ReactElement { Create cases diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx new file mode 100644 index 00000000..81816733 --- /dev/null +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -0,0 +1,73 @@ +import React, { ReactElement, useState } from "react"; +import { Questionnaire } from "blaise-api-node-client"; +import { useNavigate } from "react-router-dom"; +import { ONSButton } from "blaise-design-system-react-components"; + +interface Props { + questionnaire: Questionnaire; +} + +function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { + const [user, setUser] = useState(""); + const [error, setError] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const navigate = useNavigate(); + + function reissueNewDonorCaseButtonClicked() { + const trimmedUser = user.trim(); + setUser(trimmedUser); + + // Check if input is empty after trimming + if (trimmedUser === "") { + setErrorMessage("User input cannot be empty or contain only spaces"); + setError(true); + } else { + setError(false); + setErrorMessage(""); + navigate("/reissueNewDonorCaseConfirmation", { state: { section: "reissueNewDonorCase", questionnaire: questionnaire, user: trimmedUser } }); + } + } + + return ( + <> +
+
+

Reissue New Donor Case

+ + + + + + + +
+
+
+ + setUser(e.target.value)}/> +
+
+ {error && +

{errorMessage}

} +
+
+
+
+ +
+
+
+ + ); +} + +export default ReissueNewDonorCase; diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx new file mode 100644 index 00000000..7a0d9b7c --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.test.tsx @@ -0,0 +1,161 @@ +/** + * @jest-environment jsdom + */ + +import React from "react"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { MemoryRouter, RouterProvider, createMemoryRouter, useNavigate } from "react-router-dom"; +import ReissueNewDonorCaseConfirmation from "./reissueNewDonorCaseConfirmation"; +import "@testing-library/jest-dom"; +import axios from "axios"; +import { + cloudFunctionAxiosError, + ipsQuestionnaire, + mockSectionForReissueNewDonorCase, + mockSuccessResponseForReissueNewDonorCase +} from "../../features/step_definitions/helpers/apiMockObjects"; + +jest.mock("axios"); + +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: jest.fn() +})); + +const mockedAxios = axios as jest.Mocked; + +describe("ReissueNewDonorCaseConfirmation rendering", () => { + beforeEach(() => { + render( + + + + ); + }); + + it("displays correct prompt to reissue new donor case", () => { + expect(screen.getByText("Reissue a new donor case for on behalf of ?")).toBeInTheDocument(); + }); + + it("displays the correct number of breadcrumbs", () => { + expect.assertions(2); + + expect(screen.getByTestId("breadcrumb-0")).toBeInTheDocument(); + expect(screen.getByTestId("breadcrumb-1")).toBeInTheDocument(); + }); + + it("displays a button continue to reissue new donor case", () => { + expect(screen.getByRole("button", { name: "Continue" })).toBeInTheDocument(); + }); + + it("displays a button to navigate back to reissue new donor case page", () => { + expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument(); + }); + +}); + +describe("ReissueNewDonorCaseConfirmation navigation", () => { + let navigate: jest.Mock; + const routes = [ + { + path: "/reissueNewDonorCaseConfirmation", + element: , + }, + ]; + + const initialEntries = [ + { + pathname: "/reissueNewDonorCaseConfirmation", + state: { questionnaire: ipsQuestionnaire, user: "testuser" }, + }, + ]; + beforeEach(() => { + navigate = jest.fn(); + (useNavigate as jest.Mock).mockReturnValue(navigate); + + const router = createMemoryRouter(routes, { + initialEntries, + initialIndex: 0, + }); + + render(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should redirect back to the questionnaire details page if user clicks Cancel", async () => { + + act(() => { + fireEvent.click(screen.getByRole("button", { name: "Cancel" })); + }); + + expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", { + state: { + section: "reissueNewDonorCase", + responseMessage: "", + statusCode: 0, + role: "", + questionnaire: ipsQuestionnaire, + }, + }); + }); + + it("calls the API endpoint correctly when the continue button is clicked", async () => { + + mockedAxios.post.mockResolvedValueOnce(mockSuccessResponseForReissueNewDonorCase); + act(() => { + fireEvent.click(screen.getByRole("button", { name: /Continue/i })); + }); + await waitFor(() => { + + expect(mockedAxios.post).toHaveBeenCalledWith( + "/api/cloudFunction/reissueNewDonorCase", + { questionnaire_name: ipsQuestionnaire.name, user: "testuser" }, + { headers: { "Content-Type": "application/json" } } + ); + + expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", + { + state: { + section: mockSectionForReissueNewDonorCase.data, + responseMessage: mockSuccessResponseForReissueNewDonorCase.data, + statusCode: mockSuccessResponseForReissueNewDonorCase.status, + questionnaire: ipsQuestionnaire, + role: "testuser" + } + }); + + }); + + }); + + it("should go back to the questionnaire details page if user clicks Continue and error panel is shown", async () => { + + mockedAxios.post.mockRejectedValue(cloudFunctionAxiosError); + act(() => { + fireEvent.click(screen.getByRole("button", { name: /Continue/i })); + }); + await waitFor(() => { + + expect(mockedAxios.post).toHaveBeenCalledWith( + "/api/cloudFunction/reissueNewDonorCase", + { questionnaire_name: ipsQuestionnaire.name, user: "testuser" }, + { headers: { "Content-Type": "application/json" } } + ); + + expect(navigate).toHaveBeenCalledWith("/questionnaire/IPS1337a", + { + state: { + section: "reissueNewDonorCase", + responseMessage: (cloudFunctionAxiosError as any).response.data.message, + statusCode: 500, + questionnaire: ipsQuestionnaire, + role: "testuser" + } + }); + }); + + }); +}); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx new file mode 100644 index 00000000..b3736236 --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseConfirmation.tsx @@ -0,0 +1,74 @@ +import React, { ReactElement } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import Breadcrumbs from "../breadcrumbs"; +import { ONSButton, ONSLoadingPanel } from "blaise-design-system-react-components"; +import axios from "axios"; +import { Questionnaire } from "blaise-api-node-client"; +import axiosConfig from "../../client/axiosConfig"; + +interface Location { + questionnaire: Questionnaire; + user: string; +} + +function ReissueNewDonorCaseConfirmation(): ReactElement { + const location = useLocation().state as Location; + const { questionnaire, user } = location || { questionnaire: "", user: "" }; + + const navigate = useNavigate(); + + const [loading, isLoading] = React.useState(false); + + async function callReissueNewDonorCaseCloudFunction() { + isLoading(true); + console.log(questionnaire.name, user); + const payload = { questionnaire_name: questionnaire.name, user: user }; + let res; + try { + res = await axios.post("/api/cloudFunction/reissueNewDonorCase", payload, axiosConfig()); + } catch (error) { + const errorMessage = JSON.stringify((error as any).response.data.message); + res = { + data: errorMessage, + status: 500 + }; + } + isLoading(false); + navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "reissueNewDonorCase", responseMessage: res.data, statusCode: res.status, questionnaire: questionnaire, role: user } }); + } + if (loading) { + return ; + } + return ( + <> + + +
+ { + ( + <> +

+ Reissue a new donor case for {questionnaire.name} on behalf of {user}? +

+ + navigate(`/questionnaire/${questionnaire.name}`, { state: { section: "reissueNewDonorCase", responseMessage: "", statusCode: 0, questionnaire: questionnaire, role: "" } })} primary={false} /> + + ) + } +
+ + ); +} + +export default ReissueNewDonorCaseConfirmation; diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx new file mode 100644 index 00000000..54db8153 --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.test.tsx @@ -0,0 +1,38 @@ +/** + * @jest-environment jsdom + */ + +import React from "react"; +import { render } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import ReissueNewDonorCaseSummary from "./reissueNewDonorCaseSummary"; + +describe("ReissueNewDonorCaseSummary", () => { + it("displays a success message when receiving a successful response from the cloud function", () => { + const props = { + responseMessage: "Success", + statusCode: 200, + role: "testuser1", + }; + + const { getByText } = render(); + expect( + getByText(/Reissued donor case created successfully for testuser1/i) + ).toBeInTheDocument(); + expect(getByText(/Success/i)).toBeInTheDocument(); + }); + + it("displays an error message when receiving a failed response from the cloud function", () => { + const props = { + responseMessage: "Internal Server Error", + statusCode: 500, + role: "testuser1", + }; + + const { getByText } = render(); + expect( + getByText(/Error reissuing new donor case for testuser1/i) + ).toBeInTheDocument(); + expect(getByText(/When reporting this issue to the Service Desk, please provide the questionnaire name, user, time and date of the failure./i)).toBeInTheDocument(); + }); +}); diff --git a/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx new file mode 100644 index 00000000..d5d5aaa1 --- /dev/null +++ b/src/components/reissueNewDonorCasePage/reissueNewDonorCaseSummary.tsx @@ -0,0 +1,38 @@ +import React, { ReactElement } from "react"; +import { ONSPanel } from "blaise-design-system-react-components"; + +interface Props { + responseMessage: string; + statusCode: number; + role: string; +} + +function ReissueNewDonorCaseSummary({ statusCode, role }: Props): ReactElement { + + return ( + <> +
+ { + (statusCode === 200 ? + +

+ Reissued donor case created successfully for {role} +

+
+ : + +

+ Error reissuing new donor case for {role} +

+

+ When reporting this issue to the Service Desk, please provide the questionnaire name, user, time and date of the failure. +

+
+ ) + } +
+ + ); +} + +export default ReissueNewDonorCaseSummary; diff --git a/src/features/step_definitions/helpers/apiMockObjects.ts b/src/features/step_definitions/helpers/apiMockObjects.ts index f56b3627..8db7590d 100644 --- a/src/features/step_definitions/helpers/apiMockObjects.ts +++ b/src/features/step_definitions/helpers/apiMockObjects.ts @@ -84,7 +84,20 @@ cloudFunctionAxiosError.response = { }, }; +export const mockSectionForDonorCasesCreation = { + data: "createDonorCases" +}; + +export const mockSectionForReissueNewDonorCase = { + data: "reissueNewDonorCase" +}; + export const mockSuccessResponseForDonorCasesCreation = { data: "Success", status: 200, -}; \ No newline at end of file +}; + +export const mockSuccessResponseForReissueNewDonorCase = { + data: "Success", + status: 200, +}; diff --git a/src/server/config.ts b/src/server/config.ts index a18d1947..4152c0b2 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -12,6 +12,7 @@ export interface Config extends AuthConfig { BusApiUrl: string; BusClientId: string; CreateDonorCasesCloudFunctionUrl: string; + ReissueNewDonorCaseCloudFunctionUrl: string; } export function getConfigFromEnv(): Config { @@ -25,7 +26,8 @@ export function getConfigFromEnv(): Config { BUS_API_URL, BUS_CLIENT_ID, SESSION_TIMEOUT, - CREATE_DONOR_CASES_CLOUD_FUNCTION_URL + CREATE_DONOR_CASES_CLOUD_FUNCTION_URL, + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL } = process.env; const { @@ -80,9 +82,15 @@ export function getConfigFromEnv(): Config { } if (CREATE_DONOR_CASES_CLOUD_FUNCTION_URL === undefined) { - console.error("CLOUD_FUNCTION_URL environment variable has not been set"); + console.error("CREATE_DONOR_CASES_CLOUD_FUNCTION_URL environment variable has not been set"); CREATE_DONOR_CASES_CLOUD_FUNCTION_URL = "ENV_VAR_NOT_SET"; } + + if (REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL === undefined) { + console.error("REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL environment variable has not been set"); + REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL = "ENV_VAR_NOT_SET"; + } + let port = 5000; if (PORT !== undefined) { port = +PORT; @@ -101,7 +109,8 @@ export function getConfigFromEnv(): Config { SessionTimeout: SESSION_TIMEOUT, SessionSecret: sessionSecret(SESSION_SECRET), Roles: loadRoles(ROLES), - CreateDonorCasesCloudFunctionUrl: CREATE_DONOR_CASES_CLOUD_FUNCTION_URL + CreateDonorCasesCloudFunctionUrl: CREATE_DONOR_CASES_CLOUD_FUNCTION_URL, + ReissueNewDonorCaseCloudFunctionUrl: REISSUE_NEW_DONOR_CASE_CLOUD_FUNCTION_URL }; } diff --git a/src/server/handlers/cloudFunctionHandler.test.ts b/src/server/handlers/cloudFunctionHandler.test.ts index 15467f80..e6335cd8 100644 --- a/src/server/handlers/cloudFunctionHandler.test.ts +++ b/src/server/handlers/cloudFunctionHandler.test.ts @@ -4,7 +4,7 @@ import { newServer } from "../server"; import supertest from "supertest"; import { getConfigFromEnv } from "../config"; import createLogger from "../pino"; -import { callCloudFunctionToCreateDonorCases } from "../helpers/cloudFunctionCallerHelper"; +import { callCloudFunction } from "../helpers/cloudFunctionCallerHelper"; import { cloudFunctionAxiosError } from "../../features/step_definitions/helpers/apiMockObjects"; jest.mock("../helpers/cloudFunctionCallerHelper"); @@ -14,33 +14,33 @@ const successResponse = { }; const config = getConfigFromEnv(); -const callCloudFunctionToCreateDonorCasesMock = callCloudFunctionToCreateDonorCases as jest.Mock>; +const callCloudFunctionToCreateDonorCasesMock = callCloudFunction as jest.Mock>; describe("Call Cloud Function to create donor cases and return responses", () => { let request: supertest.SuperTest; - + beforeEach(() => { request = supertest(newServer(config, createLogger())); }); - + afterEach(() => { - jest.clearAllMocks(); + jest.clearAllMocks(); }); - + it("should return a 200 status and a json object with message and status if successfully created donor cases", async () => { callCloudFunctionToCreateDonorCasesMock.mockResolvedValue(successResponse); - + const response = await request.post("/api/cloudFunction/createDonorCases"); - + expect(response.status).toEqual(200); expect(response.body).toEqual(successResponse); }); - + it("should return a 500 status and a json object with message and status if cloud function failed creating donor cases", async () => { callCloudFunctionToCreateDonorCasesMock.mockRejectedValue(cloudFunctionAxiosError); - + const response = await request.post("/api/cloudFunction/createDonorCases"); - + expect(response.status).toEqual(500); expect(response.body.message).toEqual((cloudFunctionAxiosError as any).response.data); }); diff --git a/src/server/handlers/cloudFunctionHandler.ts b/src/server/handlers/cloudFunctionHandler.ts index 1f6ca9b1..2fb9376c 100644 --- a/src/server/handlers/cloudFunctionHandler.ts +++ b/src/server/handlers/cloudFunctionHandler.ts @@ -1,32 +1,44 @@ import express, { Request, Response, Router } from "express"; -import { callCloudFunctionToCreateDonorCases } from "../helpers/cloudFunctionCallerHelper"; +import { callCloudFunction } from "../helpers/cloudFunctionCallerHelper"; -export default function newCloudFunctionHandler( - CreateDonorCasesCloudFunctionUrl: string +export default function createDonorCasesCloudFunctionHandler( + CloudFunctionUrl: string ): Router { const router = express.Router(); const cloudFunctionHandler = new CloudFunctionHandler( - CreateDonorCasesCloudFunctionUrl + CloudFunctionUrl ); router.post("/api/cloudFunction/createDonorCases", cloudFunctionHandler.CallCloudFunction); return router; } +export function reissueNewDonorCaseCloudFunctionHandler( + CloudFunctionUrl: string +): Router { + const router = express.Router(); + const cloudFunctionHandler = new CloudFunctionHandler( + CloudFunctionUrl + ); + router.post("/api/cloudFunction/reissueNewDonorCase", cloudFunctionHandler.CallCloudFunction); + + return router; +} + export class CloudFunctionHandler { - CreateDonorCasesCloudFunctionUrl: string; + CloudFunctionUrl: string; - constructor(CreateDonorCasesCloudFunctionUrl: string) { - this.CreateDonorCasesCloudFunctionUrl = CreateDonorCasesCloudFunctionUrl; + constructor(CloudFunctionUrl: string) { + this.CloudFunctionUrl = CloudFunctionUrl; this.CallCloudFunction = this.CallCloudFunction.bind(this); } async CallCloudFunction(req: Request, res: Response): Promise { const reqData = req.body; - req.log.info(`${this.CreateDonorCasesCloudFunctionUrl} URL to invoke for Creating Donor Cases.`); + req.log.info(`${this.CloudFunctionUrl} URL to invoke for Cloud Function.`); try { - const cloudfunctionResponse = await callCloudFunctionToCreateDonorCases(this.CreateDonorCasesCloudFunctionUrl, reqData); - return res.status(cloudfunctionResponse.status).json(cloudfunctionResponse); + const cloudFunctionResponse = await callCloudFunction(this.CloudFunctionUrl, reqData); + return res.status(cloudFunctionResponse.status).json(cloudFunctionResponse); } catch (error) { console.error("Error:", error); diff --git a/src/server/helpers/cloudFunctionCallerHelper.test.ts b/src/server/helpers/cloudFunctionCallerHelper.test.ts index 8c4101e6..15f73781 100644 --- a/src/server/helpers/cloudFunctionCallerHelper.test.ts +++ b/src/server/helpers/cloudFunctionCallerHelper.test.ts @@ -1,5 +1,5 @@ import { getConfigFromEnv } from "../config"; -import { callCloudFunctionToCreateDonorCases } from "./cloudFunctionCallerHelper"; +import { callCloudFunction } from "./cloudFunctionCallerHelper"; import axios from "axios"; import { ipsQuestionnaire } from "../../features/step_definitions/helpers/apiMockObjects"; import { GoogleAuth } from "google-auth-library"; @@ -47,7 +47,7 @@ describe("Call Cloud Function to create donor cases and return responses", () => status: mockSuccessResponse.status, }); - const result = await callCloudFunctionToCreateDonorCases( + const result = await callCloudFunction( dummyUrl, payload ); @@ -84,7 +84,7 @@ describe("Call Cloud Function to create donor cases and return responses", () => status: mockErrorResponse.status, }); - const result = await callCloudFunctionToCreateDonorCases( + const result = await callCloudFunction( dummyUrl, payload ); diff --git a/src/server/helpers/cloudFunctionCallerHelper.ts b/src/server/helpers/cloudFunctionCallerHelper.ts index 30e7ad64..42d3f774 100644 --- a/src/server/helpers/cloudFunctionCallerHelper.ts +++ b/src/server/helpers/cloudFunctionCallerHelper.ts @@ -6,11 +6,10 @@ export async function getIdTokenFromMetadataServer(targetAudience: string) { const client = await googleAuth.getIdTokenClient(targetAudience); - const token = await client.idTokenProvider.fetchIdToken(targetAudience); - return token; + return await client.idTokenProvider.fetchIdToken(targetAudience); } -export async function callCloudFunctionToCreateDonorCases(url: string, payload: any): Promise<{ message: string, status: number }> { +export async function callCloudFunction(url: string, payload: any): Promise<{ message: string, status: number }> { const token = await getIdTokenFromMetadataServer(url); @@ -33,5 +32,4 @@ export async function callCloudFunctionToCreateDonorCases(url: string, payload: status: 500 }; } - -} \ No newline at end of file +} diff --git a/src/server/server.ts b/src/server/server.ts index 63ac2917..066a6936 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -17,7 +17,8 @@ import createLogger from "./pino"; import { HttpLogger } from "pino-http"; import AuditLogger from "./auditLogging/logger"; import newAuditHandler from "./handlers/auditHandler"; -import newCloudFunctionHandler from "./handlers/cloudFunctionHandler"; +import createDonorCasesCloudFunctionHandler from "./handlers/cloudFunctionHandler"; +import { reissueNewDonorCaseCloudFunctionHandler } from "./handlers/cloudFunctionHandler"; if (process.env.NODE_ENV === "production") { import("@google-cloud/profiler").then((profiler) => { @@ -44,7 +45,8 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): const busHandler = newBusHandler(busApiClient, auth); const uploadHandler = newUploadHandler(storageManager, auth, auditLogger); const auditHandler = newAuditHandler(auditLogger); - const cloudFunctionHandler = newCloudFunctionHandler(config.CreateDonorCasesCloudFunctionUrl); + const createDonorCasesHandler = createDonorCasesCloudFunctionHandler(config.CreateDonorCasesCloudFunctionUrl); + const reissueNewDonorCaseHandler = reissueNewDonorCaseCloudFunctionHandler(config.ReissueNewDonorCaseCloudFunctionUrl); const server = express(); @@ -70,7 +72,8 @@ export function newServer(config: Config, logger: HttpLogger = createLogger()): server.use("/", bimsHandler); server.use("/", busHandler); server.use("/", auditHandler); - server.use("/", cloudFunctionHandler); + server.use("/", createDonorCasesHandler); + server.use("/", reissueNewDonorCaseHandler); server.use("/", HealthCheckHandler()); server.get("*", function (req: Request, res: Response) { From 34b23c0990c0ec4f8cea7f5fe2b3f07142d74926 Mon Sep 17 00:00:00 2001 From: lambeb <141648830+lambeb@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:12:42 +0000 Subject: [PATCH 20/20] Created files ready for editing and testing. Still need to run locally to test Adding files necessary to reissue new donor case Changed wording and look and feel of textbox Slight changes and fixes Made changes to show both error messages for create donor cases and reissue new donor case Fixing Tests so far Fixed tests so far Fixed issues with calling cloud function, now works correctly Fixed failing tests Cleaned up some code to be more generic Added some minor fixes to try and get cloud function working on sandbox GUI Fixes GUI Fixes GUI Fixes Fixed failing test after GUI changes Added padding around error message and various UI Fixes Changed text input to be below text input field Added padding around error message to be more inline with other components --- .../sections/reissueNewDonorCase.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx index 81816733..67063d59 100644 --- a/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx +++ b/src/components/questionnaireDetailsPage/sections/reissueNewDonorCase.tsx @@ -1,7 +1,7 @@ import React, { ReactElement, useState } from "react"; import { Questionnaire } from "blaise-api-node-client"; import { useNavigate } from "react-router-dom"; -import { ONSButton } from "blaise-design-system-react-components"; +import { ONSButton, ONSPanel } from "blaise-design-system-react-components"; interface Props { questionnaire: Questionnaire; @@ -47,9 +47,13 @@ function ReissueNewDonorCase({ questionnaire }: Props): ReactElement { className="ons-input ons-input--text ons-input-type__input" value={user} onChange={(e) => setUser(e.target.value)}/> -
+
{error && -

{errorMessage}

} + +

+ {errorMessage} +

+
}