From 3a85966e2ceb5edb0123f9624c19dced0eb6f8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 16 Oct 2023 21:28:22 +0100 Subject: [PATCH] refactor: Make redaction explicit in `ExportClient` (#162) --- src/export/csv/index.ts | 23 ----- src/export/digitalPlanning/index.ts | 11 +-- src/export/index.ts | 143 +++++++++++++++++++++++++++- src/export/oneApp/index.test.ts | 2 +- src/export/oneApp/index.ts | 11 +-- src/requests/export.ts | 105 -------------------- src/requests/index.ts | 7 +- src/types/export.ts | 7 +- 8 files changed, 155 insertions(+), 154 deletions(-) delete mode 100644 src/requests/export.ts diff --git a/src/export/csv/index.ts b/src/export/csv/index.ts index 0f1ec886..bed40ba2 100644 --- a/src/export/csv/index.ts +++ b/src/export/csv/index.ts @@ -1,36 +1,13 @@ import omit from "lodash.omit"; import type { - BOPSExportData, BOPSFullPayload, - ExportData, Passport, QuestionAndResponses, Response, } from "../../types"; export function computeCSVData({ - sessionId, - bopsExportData, - passport, -}: { - sessionId: string; - bopsExportData: BOPSExportData; - passport: Passport; -}): ExportData { - const compute = (input: BOPSFullPayload) => - computeQuestionAndResponses({ - sessionId, - bopsData: input, - passport, - }); - return { - responses: compute(bopsExportData.exportData), - redactedResponses: compute(bopsExportData.redactedExportData), - }; -} - -export function computeQuestionAndResponses({ sessionId, bopsData, passport, diff --git a/src/export/digitalPlanning/index.ts b/src/export/digitalPlanning/index.ts index 86daedd6..748a257e 100644 --- a/src/export/digitalPlanning/index.ts +++ b/src/export/digitalPlanning/index.ts @@ -1,14 +1,13 @@ -import { GraphQLClient } from "graphql-request"; - import { Passport } from "../../models/passport"; import { getSessionById } from "../../requests/session"; +import { ExportParams } from ".."; import { DigitalPlanning } from "./model"; import { DigitalPlanningDataSchema as DigitalPlanningPayload } from "./schema/types"; -export async function generateDigitalPlanningPayload( - client: GraphQLClient, - sessionId: string, -): Promise { +export async function generateDigitalPlanningPayload({ + client, + sessionId, +}: ExportParams): Promise { const session = await getSessionById(client, sessionId); if (!session) throw Error(`No session found matching ID ${sessionId}`); diff --git a/src/export/index.ts b/src/export/index.ts index 2b10a670..ac95fdf0 100644 --- a/src/export/index.ts +++ b/src/export/index.ts @@ -1 +1,142 @@ -export * from "./oneApp"; +import { GraphQLClient } from "graphql-request"; + +import { findPublishedFlowBySessionId, getFlowName } from "../requests/flow"; +import { getSessionById, getSessionPassport } from "../requests/session"; +import type { BOPSFullPayload, QuestionAndResponses } from "../types"; +import { computeBOPSParams } from "./bops"; +import { computeCSVData } from "./csv"; +import { generateDigitalPlanningPayload } from "./digitalPlanning"; +import { DigitalPlanningDataSchema } from "./digitalPlanning/schema/types"; +import { generateOneAppXML } from "./oneApp"; + +export type ExportParams = { + client: GraphQLClient; + sessionId: string; +}; + +type RedactionOptions = + | { isRedacted?: false; keysToRedact?: never } + | { isRedacted: true; keysToRedact?: string[] }; + +export type ExportWithRedactionParams = ExportParams & RedactionOptions; + +export class ExportClient { + protected client: GraphQLClient; + + constructor(client: GraphQLClient) { + this.client = client; + } + + csvData(sessionId: string): Promise { + return generateCSVData({ client: this.client, sessionId }); + } + + csvDataRedacted(sessionId: string): Promise { + return generateCSVData({ + client: this.client, + sessionId, + isRedacted: true, + }); + } + + bopsPayload(sessionId: string): Promise { + return generateBOPSPayload({ client: this.client, sessionId }); + } + + bopsPayloadRedacted(sessionId: string): Promise { + return generateBOPSPayload({ + client: this.client, + sessionId, + isRedacted: true, + }); + } + + digitalPlanningDataPayload( + sessionId: string, + ): Promise { + return generateDigitalPlanningPayload({ client: this.client, sessionId }); + } + + async oneAppPayload(sessionId: string): Promise { + return generateOneAppXML({ client: this.client, sessionId }); + } +} + +export async function generateCSVData({ + client, + sessionId, + isRedacted, +}: ExportWithRedactionParams) { + const bopsData = await generateBOPSPayload({ client, sessionId, isRedacted }); + if (!bopsData) { + throw new Error( + `Cannot fetch BOPS data for session ${sessionId} so cannot generate CSV Data`, + ); + } + + const passport = await getSessionPassport(client, sessionId); + if (!passport) { + throw new Error( + `Cannot find passport for session ${sessionId} so cannot generate CSV Data`, + ); + } + + return computeCSVData({ + sessionId, + bopsData, + passport, + }); +} + +export async function generateBOPSPayload({ + client, + sessionId, + isRedacted = false, + keysToRedact, +}: ExportWithRedactionParams): Promise { + try { + const session = await getSessionById(client, sessionId); + if (!session) throw new Error(`Cannot find session ${sessionId}`); + + const flow = await findPublishedFlowBySessionId(client, sessionId); + if (!flow) throw new Error(`Cannot get flow ${session.flowId}`); + + const flowName = await getFlowName(client, session.flowId); + const { breadcrumbs, passport } = session.data; + + if (isRedacted) { + // compute redacted export data + const defaultKeysToRedact = [ + "applicant.phone.primary", + "applicant.phone.secondary", + "applicant.email", + "applicant.agent.phone.primary", + "applicant.agent.phone.secondary", + "applicant.agent.email", + ]; + const redactedExportData = computeBOPSParams({ + sessionId, + flow, + flowName, + breadcrumbs, + passport, + keysToRedact: keysToRedact || defaultKeysToRedact, + }); + + return redactedExportData; + } + + // compute export data + const exportData = computeBOPSParams({ + sessionId, + flow, + flowName, + breadcrumbs, + passport, + }); + + return exportData; + } catch (e) { + throw new Error(`Cannot generate BOPS payload: ${e}`); + } +} diff --git a/src/export/oneApp/index.test.ts b/src/export/oneApp/index.test.ts index 4c40a583..0e7b8113 100644 --- a/src/export/oneApp/index.test.ts +++ b/src/export/oneApp/index.test.ts @@ -51,7 +51,7 @@ describe("generateOneAppXML", () => { .mockImplementationOnce(() => false) .mockImplementationOnce(() => false); - const xml = await generateOneAppXML(client, "abc123"); + const xml = await generateOneAppXML({ client, sessionId: "abc123" }); expect(xml).not.toBeUndefined(); const isValid = XMLValidator.validate(xml!); expect(isValid).toBe(true); diff --git a/src/export/oneApp/index.ts b/src/export/oneApp/index.ts index f682ec48..9b26a3ba 100644 --- a/src/export/oneApp/index.ts +++ b/src/export/oneApp/index.ts @@ -1,16 +1,15 @@ -import { GraphQLClient } from "graphql-request"; - import { Passport } from "../../models/passport"; import { getDocumentTemplateNamesForSession } from "../../requests/document-templates"; import { getSessionById } from "../../requests/session"; import { hasRequiredDataForTemplate } from "../../templates"; import { Passport as IPassport } from "../../types"; +import { ExportParams } from ".."; import { OneAppPayload } from "./model"; -export async function generateOneAppXML( - client: GraphQLClient, - sessionId: string, -): Promise { +export async function generateOneAppXML({ + client, + sessionId, +}: ExportParams): Promise { const session = await getSessionById(client, sessionId); if (!session) throw Error(`No session found matching ID ${sessionId}`); diff --git a/src/requests/export.ts b/src/requests/export.ts deleted file mode 100644 index abd08688..00000000 --- a/src/requests/export.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { GraphQLClient } from "graphql-request"; - -import { computeBOPSParams } from "../export/bops"; -import { computeCSVData } from "../export/csv"; -import { generateDigitalPlanningPayload } from "../export/digitalPlanning"; -import { DigitalPlanningDataSchema } from "../export/digitalPlanning/schema/types"; -import type { BOPSExportData, ExportData } from "../types"; -import { findPublishedFlowBySessionId, getFlowName } from "./flow"; -import { getSessionById, getSessionPassport } from "./session"; - -export class ExportClient { - protected client: GraphQLClient; - - constructor(client: GraphQLClient) { - this.client = client; - } - - csvData(sessionId: string): Promise { - return generateCSVData(this.client, sessionId); - } - - bopsPayload(sessionId: string): Promise { - return generateBOPSPayload(this.client, sessionId); - } - - digitalPlanningDataPayload( - sessionId: string, - ): Promise { - return generateDigitalPlanningPayload(this.client, sessionId); - } -} - -export async function generateCSVData( - client: GraphQLClient, - sessionId: string, -): Promise { - const bopsExportData = await generateBOPSPayload(client, sessionId); - if (!bopsExportData) { - throw new Error( - `Cannot fetch BOPS Params for session ${sessionId} so cannot generate CSV Data`, - ); - } - const passport = await getSessionPassport(client, sessionId); - if (!passport) { - throw new Error( - `Cannot find passport for session ${sessionId} so cannot generate CSV Data`, - ); - } - return computeCSVData({ - sessionId, - bopsExportData, - passport, - }); -} - -export async function generateBOPSPayload( - client: GraphQLClient, - sessionId: string, - keysToRedact?: string[], -): Promise { - try { - const session = await getSessionById(client, sessionId); - if (!session) throw new Error(`Cannot find session ${sessionId}`); - - const flow = await findPublishedFlowBySessionId(client, sessionId); - if (!flow) throw new Error(`Cannot get flow ${session.flowId}`); - - const flowName = await getFlowName(client, session.flowId); - const { breadcrumbs, passport } = session.data; - - // compute export data - const exportData = computeBOPSParams({ - sessionId, - flow, - flowName, - breadcrumbs, - passport, - }); - - // compute redacted export data - const defaultKeysToRedact = [ - "applicant.phone.primary", - "applicant.phone.secondary", - "applicant.email", - "applicant.agent.phone.primary", - "applicant.agent.phone.secondary", - "applicant.agent.email", - ]; - const redactedExportData = computeBOPSParams({ - sessionId, - flow, - flowName, - breadcrumbs, - passport, - keysToRedact: keysToRedact || defaultKeysToRedact, - }); - - return { - exportData, - redactedExportData, - }; - } catch (e) { - throw new Error(`Cannot generate BOPS payload: ${e}`); - } -} diff --git a/src/requests/index.ts b/src/requests/index.ts index f23faf72..e13dadf6 100644 --- a/src/requests/index.ts +++ b/src/requests/index.ts @@ -1,14 +1,13 @@ import type { GraphQLClient } from "graphql-request"; import slugify from "lodash.kebabcase"; -import { generateOneAppXML } from "../export/oneApp"; +import { ExportClient } from "../export"; import type { KeyPath, PaymentRequest, Session } from "../types"; import { ApplicationClient } from "./application"; import { getDocumentTemplateNamesForFlow, getDocumentTemplateNamesForSession, } from "./document-templates"; -import { ExportClient } from "./export"; import { createFlow, FlowClient, publishFlow } from "./flow"; import { Auth, getGraphQLClient } from "./graphql"; import { createPaymentRequest, PaymentRequestClient } from "./payment-request"; @@ -111,10 +110,6 @@ export class CoreDomainClient { return getDocumentTemplateNamesForSession(this.client, sessionId); } - async generateOneAppXML(sessionId: string): Promise { - return generateOneAppXML(this.client, sessionId); - } - async getSessionById(sessionId: string): Promise { return getSessionById(this.client, sessionId); } diff --git a/src/types/export.ts b/src/types/export.ts index 14a4dfaa..507f5259 100644 --- a/src/types/export.ts +++ b/src/types/export.ts @@ -1,9 +1,4 @@ -import { BOPSFullPayload, QuestionAndResponses } from "./bops"; - -export type BOPSExportData = { - exportData: BOPSFullPayload; - redactedExportData: BOPSFullPayload; -}; +import { QuestionAndResponses } from "./bops"; export type ExportData = { responses: QuestionAndResponses[];