Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Make redaction explicit in ExportClient #162

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions src/export/csv/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -41,7 +18,7 @@
}): QuestionAndResponses[] {
// format dedicated BOPS properties (eg `applicant_email`) as list of questions & responses to match proposal_details
// omitting debug data and payload keys already in confirmation details
const summary: Record<string, any> = {

Check warning on line 21 in src/export/csv/index.ts

View workflow job for this annotation

GitHub Actions / Test

Unexpected any. Specify a different type
...omit(bopsData, ["planx_debug_data", "files", "proposal_details"]),
};
const formattedSummary: {
Expand Down
11 changes: 5 additions & 6 deletions src/export/digitalPlanning/index.ts
Original file line number Diff line number Diff line change
@@ -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<DigitalPlanningPayload> {
export async function generateDigitalPlanningPayload({
client,
sessionId,
}: ExportParams): Promise<DigitalPlanningPayload> {
const session = await getSessionById(client, sessionId);
if (!session) throw Error(`No session found matching ID ${sessionId}`);

Expand Down
143 changes: 142 additions & 1 deletion src/export/index.ts
Original file line number Diff line number Diff line change
@@ -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<QuestionAndResponses[]> {
return generateCSVData({ client: this.client, sessionId });
}

csvDataRedacted(sessionId: string): Promise<QuestionAndResponses[]> {
return generateCSVData({
client: this.client,
sessionId,
isRedacted: true,
});
}

bopsPayload(sessionId: string): Promise<BOPSFullPayload> {
return generateBOPSPayload({ client: this.client, sessionId });
}

bopsPayloadRedacted(sessionId: string): Promise<BOPSFullPayload> {
return generateBOPSPayload({
client: this.client,
sessionId,
isRedacted: true,
});
}

digitalPlanningDataPayload(
sessionId: string,
): Promise<DigitalPlanningDataSchema> {
return generateDigitalPlanningPayload({ client: this.client, sessionId });
}

async oneAppPayload(sessionId: string): Promise<string> {
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<BOPSFullPayload> {
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}`);
}
}
2 changes: 1 addition & 1 deletion src/export/oneApp/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 5 additions & 6 deletions src/export/oneApp/index.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
export async function generateOneAppXML({
client,
sessionId,
}: ExportParams): Promise<string> {
const session = await getSessionById(client, sessionId);
if (!session) throw Error(`No session found matching ID ${sessionId}`);

Expand Down
105 changes: 0 additions & 105 deletions src/requests/export.ts

This file was deleted.

7 changes: 1 addition & 6 deletions src/requests/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -111,10 +110,6 @@ export class CoreDomainClient {
return getDocumentTemplateNamesForSession(this.client, sessionId);
}

async generateOneAppXML(sessionId: string): Promise<string> {
return generateOneAppXML(this.client, sessionId);
}

async getSessionById(sessionId: string): Promise<Session | null> {
return getSessionById(this.client, sessionId);
}
Expand Down
7 changes: 1 addition & 6 deletions src/types/export.ts
Original file line number Diff line number Diff line change
@@ -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[];
Expand Down
Loading