From 35bb5d18cdfe1d6a8317d1955a204f710c429a8a Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Tue, 12 Dec 2023 15:07:21 +0000 Subject: [PATCH 1/7] feat: add basic metadata to GOV.UK Pay requests (#2556) --- api.planx.uk/modules/pay/index.test.ts | 5 +++++ api.planx.uk/modules/pay/middleware.ts | 12 ++++++++++++ editor.planx.uk/src/@planx/components/Pay/model.ts | 11 ++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/api.planx.uk/modules/pay/index.test.ts b/api.planx.uk/modules/pay/index.test.ts index ab4a129d37..4f7b5a0e01 100644 --- a/api.planx.uk/modules/pay/index.test.ts +++ b/api.planx.uk/modules/pay/index.test.ts @@ -53,6 +53,11 @@ describe("sending a payment to GOV.UK Pay", () => { reference: "12343543", description: "New application", return_url: "https://editor.planx.uk", + metadata: { + source: "PlanX", + flow: "apply-for-a-lawful-development-certificate", + inviteToPay: false, + }, }) .expect(200) .then((res) => { diff --git a/api.planx.uk/modules/pay/middleware.ts b/api.planx.uk/modules/pay/middleware.ts index f0a205899a..8bd6ad50b6 100644 --- a/api.planx.uk/modules/pay/middleware.ts +++ b/api.planx.uk/modules/pay/middleware.ts @@ -90,6 +90,11 @@ interface GovPayCreatePayment { reference: string; description: string; return_url: string; + metadata?: { + source: "PlanX"; + flow: string; + inviteToPay: boolean; + }; } export async function buildPaymentPayload( @@ -120,6 +125,13 @@ export async function buildPaymentPayload( reference: req.query.sessionId as string, description: "New application (nominated payee)", return_url: req.query.returnURL as string, + metadata: { + source: "PlanX", + flow: + new URL(req.query.returnURL as string).pathname.split("/")[0] || + (req.query.returnURL as string), + inviteToPay: true, + }, }; req.body = createPaymentBody; diff --git a/editor.planx.uk/src/@planx/components/Pay/model.ts b/editor.planx.uk/src/@planx/components/Pay/model.ts index 9d0fe202d3..983f6a21d8 100644 --- a/editor.planx.uk/src/@planx/components/Pay/model.ts +++ b/editor.planx.uk/src/@planx/components/Pay/model.ts @@ -40,7 +40,11 @@ export interface GovUKCreatePaymentPayload { }; }; language?: string; - metadata?: any; + metadata?: { + source: "PlanX"; + flow: string; + inviteToPay: boolean; + }; } export const toPence = (decimal: number) => Math.trunc(decimal * 100); @@ -63,6 +67,11 @@ export const createPayload = ( reference, description: "New application", return_url: getReturnURL(reference), + metadata: { + source: "PlanX", + flow: useStore.getState().flowSlug, + inviteToPay: false, + }, }); /** From f9785de3dda3dec0f01cf8b571ff5d2e0b710e85 Mon Sep 17 00:00:00 2001 From: Ian Jones <51156018+ianjon3s@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:09:00 +0000 Subject: [PATCH 2/7] fix: Long URL wrapping in editor (#2552) * fix: Long URL wrapping in editor * fix: Long URL wrapping in editor --- editor.planx.uk/src/ui/RichTextInput.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/editor.planx.uk/src/ui/RichTextInput.tsx b/editor.planx.uk/src/ui/RichTextInput.tsx index 325046ae1d..144afae381 100644 --- a/editor.planx.uk/src/ui/RichTextInput.tsx +++ b/editor.planx.uk/src/ui/RichTextInput.tsx @@ -72,6 +72,7 @@ export const RichContentContainer = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "column", justifyContent: "center", + wordBreak: "break-word", "& a": { color: "currentColor", }, From dd2dff1344f2e4b3eca4f136dec24b4eeb33783b Mon Sep 17 00:00:00 2001 From: Ian Jones <51156018+ianjon3s@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:09:13 +0000 Subject: [PATCH 3/7] fix: Editor layout for small screens (#2554) --- .../src/@planx/components/Checklist/Editor.tsx | 2 +- .../src/@planx/components/Question/Editor.tsx | 2 +- editor.planx.uk/src/ui/Input.tsx | 1 - editor.planx.uk/src/ui/ModalSectionContent.tsx | 14 ++++++++++++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx b/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx index 6e14019033..21fdc11cf4 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Editor.tsx @@ -178,7 +178,7 @@ const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => { - + { diff --git a/editor.planx.uk/src/@planx/components/Question/Editor.tsx b/editor.planx.uk/src/@planx/components/Question/Editor.tsx index 35dc0d8825..4847bc60ad 100644 --- a/editor.planx.uk/src/@planx/components/Question/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Question/Editor.tsx @@ -86,7 +86,7 @@ const OptionEditor: React.FC<{ }, }); }} - sx={{ width: "160px", maxWidth: "160px" }} + sx={{ width: { md: "160px" }, maxWidth: "160px" }} /> diff --git a/editor.planx.uk/src/ui/Input.tsx b/editor.planx.uk/src/ui/Input.tsx index abb5b40396..a02ffe85d8 100644 --- a/editor.planx.uk/src/ui/Input.tsx +++ b/editor.planx.uk/src/ui/Input.tsx @@ -67,7 +67,6 @@ const StyledInputBase = styled(InputBase, { ...(format === "large" && { backgroundColor: theme.palette.common.white, height: 50, - fontSize: 25, width: "100%", fontWeight: FONT_WEIGHT_SEMI_BOLD, }), diff --git a/editor.planx.uk/src/ui/ModalSectionContent.tsx b/editor.planx.uk/src/ui/ModalSectionContent.tsx index f8739275ab..4bfe0e18fd 100644 --- a/editor.planx.uk/src/ui/ModalSectionContent.tsx +++ b/editor.planx.uk/src/ui/ModalSectionContent.tsx @@ -16,16 +16,26 @@ const SectionContentGrid = styled(Grid)(({ theme }) => ({ paddingTop: theme.spacing(2), paddingBottom: theme.spacing(2), flexWrap: "nowrap", + [theme.breakpoints.down("md")]: { + flexDirection: "column", + alignItems: "flex-start", + }, })); const LeftGutter = styled(Grid)(({ theme }) => ({ - flex: `0 0 ${theme.spacing(6)}`, + flex: `0 0 ${theme.spacing(3)}`, textAlign: "center", + [theme.breakpoints.up("md")]: { + flex: `0 0 ${theme.spacing(6)}`, + }, })); const SectionContent = styled(Grid)(({ theme }) => ({ flexGrow: 1, - paddingRight: theme.spacing(6), + width: "100%", + [theme.breakpoints.up("md")]: { + paddingRight: theme.spacing(6), + }, })); const Title = styled(Typography)(({ theme }) => ({ From 24ec540421851027fa130f1049120b3e3607a7a7 Mon Sep 17 00:00:00 2001 From: Ian Jones <51156018+ianjon3s@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:09:32 +0000 Subject: [PATCH 4/7] fix: Touch area for MUI Sellect (#2555) --- editor.planx.uk/src/ui/SelectInput.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/editor.planx.uk/src/ui/SelectInput.tsx b/editor.planx.uk/src/ui/SelectInput.tsx index a58134c27f..e85e2dc218 100644 --- a/editor.planx.uk/src/ui/SelectInput.tsx +++ b/editor.planx.uk/src/ui/SelectInput.tsx @@ -22,9 +22,13 @@ const classes = { const Root = styled(Select)(({ theme }) => ({ width: "100%", - padding: theme.spacing(0, 1.5), + padding: 0, height: 50, backgroundColor: "#fff", + "& .MuiSelect-select": { + width: "100%", + padding: theme.spacing(1, 1.5), + }, [`&.${classes.rootSelect}`]: { paddingRight: theme.spacing(6), boxSizing: "border-box", From d885b24d3983e79b9902e0a3ad34bd513fc82c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 12 Dec 2023 15:14:07 +0000 Subject: [PATCH 5/7] chore: Setup prod Nexus key [skip pizza] (#2557) --- infrastructure/application/Pulumi.production.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/application/Pulumi.production.yaml b/infrastructure/application/Pulumi.production.yaml index 911a99da91..4107b674bd 100644 --- a/infrastructure/application/Pulumi.production.yaml +++ b/infrastructure/application/Pulumi.production.yaml @@ -9,6 +9,8 @@ config: application:cloudflare-zone-id: a9b9933f28e786ec4cfd4bb596f5a519 application:file-api-key: secure: AAABAGyTfLujGho+V0tEhFXQRET5FjYK6txyaFTB3gY/VaKzq8yNlocJTAM5nt8mBhF6T+AeQD2GxW63 + application:file-api-key-nexus: + secure: AAABAB2cv4GAf8RqN1hHbRbO68p8o4kLJYWsip9BoPdobrNtQB787M3s+gJnKKl9DfyXRHOXHGc= application:google-client-id: 987324067365-vpsk3kgeq5n32ihjn760ihf8l7m5rhh8.apps.googleusercontent.com application:google-client-secret: secure: AAABAN5E+De3A3HtpLVaSNTDwk9Uz4r2d5g8SIRVbNOd2fj3eU+lGJXjVbEAnxezr14hwabbfwW2ptjcFzqkhG7OmQ== From 96dddb36dc5d68c5c7423816ad1ee9307029ce72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Tue, 12 Dec 2023 16:12:11 +0000 Subject: [PATCH 6/7] test: Add basic tests for isEqual function [skip pizza] (#2558) --- api.planx.uk/modules/auth/middleware.test.ts | 36 ++++++++++++++++++++ api.planx.uk/modules/auth/middleware.ts | 5 ++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 api.planx.uk/modules/auth/middleware.test.ts diff --git a/api.planx.uk/modules/auth/middleware.test.ts b/api.planx.uk/modules/auth/middleware.test.ts new file mode 100644 index 0000000000..dae48a8ba9 --- /dev/null +++ b/api.planx.uk/modules/auth/middleware.test.ts @@ -0,0 +1,36 @@ +import { isEqual } from "./middleware"; + +describe("isEqual() helper function", () => { + it("handles undefined secrets", () => { + const req = { headers: { "api-key": undefined } }; + const result = isEqual(req.headers["api-key"], process.env.UNSET_SECRET!); + expect(result).toBe(false); + }); + + it("handles null values", () => { + const req = { headers: { "api-key": null } }; + // @ts-expect-error "api-key" purposefully set to wrong type + const result = isEqual(req.headers["api-key"], null!); + expect(result).toBe(false); + }); + + it("handles undefined headers", () => { + const req = { headers: { "some-other-header": "test123" } }; + // @ts-expect-error "api-key" purposefully not set + const result = isEqual(req.headers["api-key"]!, process.env.UNSET_SECRET!); + expect(result).toBe(false); + }); + + it("handles empty strings", () => { + const req = { headers: { "api-key": "" } }; + expect(isEqual(req.headers["api-key"], "")).toBe(false); + }); + + it("matches equal values", () => { + expect(isEqual("square", "square")).toBe(true); + }); + + it("does not match different values", () => { + expect(isEqual("circle", "triangle")).toBe(false); + }); +}); diff --git a/api.planx.uk/modules/auth/middleware.ts b/api.planx.uk/modules/auth/middleware.ts index b098427fcb..bf0f412e13 100644 --- a/api.planx.uk/modules/auth/middleware.ts +++ b/api.planx.uk/modules/auth/middleware.ts @@ -16,7 +16,10 @@ export const userContext = new AsyncLocalStorage<{ user: Express.User }>(); /** * Validate that a provided string (e.g. API key) matches the expected value */ -const isEqual = (provided = "", expected: string): boolean => { +export const isEqual = (provided = "", expected: string): boolean => { + // Reject test against falsey values - could indicate unset secret + if (!expected) return false; + const hash = crypto.createHash("SHA512"); return crypto.timingSafeEqual( hash.copy().update(provided).digest(), From e787e0eb68d4dba52660dd2c2ca8ecf839c31b16 Mon Sep 17 00:00:00 2001 From: Mike <36415632+Mike-Heneghan@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:59:56 +0000 Subject: [PATCH 7/7] feature: add analytics session lenght data to view (#2550) - Found that it wasn't possible to replicate the ["Average time spent"](https://metabase.editor.planx.uk/question/15-average-time-spent?flow_id=0e9d79ec-9cf3-497d-a1a1-8e70469bb52d) card without analytics session length data - Add analytics.ended_at to the view - Add time_spent_on_analytics_session_in_minutes to the view --- .../down.sql | 24 +++++++++++++++++++ .../up.sql | 24 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/down.sql create mode 100644 hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/up.sql diff --git a/hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/down.sql b/hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/down.sql new file mode 100644 index 0000000000..f15af04ae4 --- /dev/null +++ b/hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/down.sql @@ -0,0 +1,24 @@ +DROP VIEW public.analytics_summary CASCADE; + +CREATE OR REPLACE VIEW public.analytics_summary AS +select + a.id as analytics_id, + al.id as analytics_log_id, + f.slug as service_slug, + t.slug as team_slug, + a.type as analytics_type, + a.created_at as analytics_created_at, + user_agent, + referrer, + flow_direction, + metadata, + al.user_exit as is_user_exit, + node_type, + node_title, + has_clicked_help, + input_errors, + CAST(EXTRACT(EPOCH FROM (al.next_log_created_at - al.created_at)) as numeric (10, 1)) as time_spent_on_node_seconds +from analytics a + left join analytics_logs al on a.id = al.analytics_id + left join flows f on a.flow_id = f.id + left join teams t on t.id = f.team_id; \ No newline at end of file diff --git a/hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/up.sql b/hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/up.sql new file mode 100644 index 0000000000..e9e4ee87d8 --- /dev/null +++ b/hasura.planx.uk/migrations/1702312905340_alter_view_analytics_summary_add_analytics_session_length_data/up.sql @@ -0,0 +1,24 @@ +CREATE OR REPLACE VIEW public.analytics_summary AS +select + a.id as analytics_id, + al.id as analytics_log_id, + f.slug as service_slug, + t.slug as team_slug, + a.type as analytics_type, + a.created_at as analytics_created_at, + user_agent, + referrer, + flow_direction, + metadata, + al.user_exit as is_user_exit, + node_type, + node_title, + has_clicked_help, + input_errors, + CAST(EXTRACT(EPOCH FROM (al.next_log_created_at - al.created_at)) as numeric (10, 1)) as time_spent_on_node_seconds, + a.ended_at as analytics_ended_at, + CAST(EXTRACT(EPOCH FROM (a.ended_at - a.created_at))/60 as numeric (10, 1)) as time_spent_on_analytics_session_minutes +from analytics a + left join analytics_logs al on a.id = al.analytics_id + left join flows f on a.flow_id = f.id + left join teams t on t.id = f.team_id; \ No newline at end of file