From 6f0a2c79592a3d8cb9cf3a9e97ae5fb7edd9a316 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Thu, 19 Dec 2024 08:37:52 +0100 Subject: [PATCH 01/10] chore: `showInternalNotes` in editor modals for portals (#4091) Co-authored-by: augustlindemer <118665588+augustlindemer@users.noreply.github.com> --- .../components/ExternalPortal/Editor.test.tsx | 2 ++ .../components/ExternalPortal/Editor.tsx | 11 +++++------ .../components/InternalPortal/Editor.test.tsx | 2 ++ .../components/InternalPortal/Editor.tsx | 18 ++++++++++++------ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx index f59445b4ca..792e6b0907 100644 --- a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx +++ b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.test.tsx @@ -32,6 +32,7 @@ test("adding an external portal", async () => { data: { flowId: "b", tags: [], + notes: "", }, }), ); @@ -65,6 +66,7 @@ test("changing an external portal", async () => { data: { flowId: "a", tags: [], + notes: "", }, }), ); diff --git a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx index 07c796c529..47d8fafc6a 100644 --- a/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/ExternalPortal/Editor.tsx @@ -4,7 +4,7 @@ import { } from "@opensystemslab/planx-core/types"; import { useFormik } from "formik"; import React from "react"; -import { ComponentTagSelect } from "ui/editor/ComponentTagSelect"; +import { ModalFooter } from "ui/editor/ModalFooter"; import ModalSection from "ui/editor/ModalSection"; import ModalSectionContent from "ui/editor/ModalSectionContent"; @@ -18,14 +18,16 @@ interface Flow { const ExternalPortalForm: React.FC<{ id?: string; flowId?: string; + notes?: string; handleSubmit?: (val: any) => void; flows?: Array; tags?: NodeTag[]; -}> = ({ id, handleSubmit, flowId = "", flows = [], tags = [] }) => { +}> = ({ id, handleSubmit, flowId = "", flows = [], tags = [], notes = "" }) => { const formik = useFormik({ initialValues: { flowId, tags, + notes, }, onSubmit: (values) => { if (handleSubmit) { @@ -64,11 +66,8 @@ const ExternalPortalForm: React.FC<{ ))} - formik.setFieldValue("tags", value)} - /> + ); }; diff --git a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx index f95f97f9a9..dd9e3c7cc0 100644 --- a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx +++ b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.test.tsx @@ -39,6 +39,7 @@ describe("adding an internal portal", () => { flowId: "", // will be removed when saving the data text: "new internal portal", tags: [], + notes: "", }, }); }); @@ -131,6 +132,7 @@ test("updating an internal portal", async () => { flowId: "", // will be removed when saving the data text: "new val", tags: [], + notes: "", }, }); }); diff --git a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx index 8a1aa4b7e7..16c023c673 100644 --- a/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/InternalPortal/Editor.tsx @@ -5,7 +5,7 @@ import { } from "@opensystemslab/planx-core/types"; import { useFormik } from "formik"; import React from "react"; -import { ComponentTagSelect } from "ui/editor/ComponentTagSelect"; +import { ModalFooter } from "ui/editor/ModalFooter"; import ModalSection from "ui/editor/ModalSection"; import ModalSectionContent from "ui/editor/ModalSectionContent"; import SelectInput from "ui/editor/SelectInput/SelectInput"; @@ -24,15 +24,24 @@ const InternalPortalForm: React.FC<{ id?: string; text?: string; flowId?: string; + notes?: string; handleSubmit?: (val: any) => void; flows?: Array; tags?: NodeTag[]; -}> = ({ handleSubmit, text = "", flowId = "", flows = [], tags = [] }) => { +}> = ({ + handleSubmit, + text = "", + flowId = "", + flows = [], + tags = [], + notes = "", +}) => { const formik = useFormik({ initialValues: { text, flowId, tags, + notes, }, validate: (values) => { const errors: Record = {}; @@ -101,11 +110,8 @@ const InternalPortalForm: React.FC<{ )} - formik.setFieldValue("tags", value)} - /> + ); }; From e41f81060dee841cee0fa43c7fba24624d75a602 Mon Sep 17 00:00:00 2001 From: Jo Humphrey <31373245+jamdelion@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:27:12 +0000 Subject: [PATCH 02/10] chore: setup power automate tokens for Gateshead (#4094) --- .env.example | 1 + api.planx.uk/.env.test.example | 1 + api.planx.uk/modules/auth/middleware.ts | 4 ++++ docker-compose.yml | 1 + infrastructure/application/Pulumi.production.yaml | 6 ++++-- infrastructure/application/Pulumi.staging.yaml | 6 ++++-- infrastructure/application/index.ts | 4 ++++ 7 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 759d790a91..90f746b2a2 100644 --- a/.env.example +++ b/.env.example @@ -34,6 +34,7 @@ FILE_API_KEY_LAMBETH=👻 FILE_API_KEY_SOUTHWARK=👻 FILE_API_KEY_EPSOM_EWELL=👻 FILE_API_KEY_MEDWAY=👻 +FILE_API_KEY_GATESHEAD=👻 # Editor EDITOR_URL_EXT=http://localhost:3000 diff --git a/api.planx.uk/.env.test.example b/api.planx.uk/.env.test.example index bdc74b375e..60ac5e4a5d 100644 --- a/api.planx.uk/.env.test.example +++ b/api.planx.uk/.env.test.example @@ -24,6 +24,7 @@ FILE_API_KEY_LAMBETH=👻 FILE_API_KEY_SOUTHWARK=👻 FILE_API_KEY_EPSOM_EWELL=👻 FILE_API_KEY_MEDWAY=👻 +FILE_API_KEY_GATESHEAD=👻 # Editor EDITOR_URL_EXT=example.com diff --git a/api.planx.uk/modules/auth/middleware.ts b/api.planx.uk/modules/auth/middleware.ts index 5b4cdfc9d3..22e07337c8 100644 --- a/api.planx.uk/modules/auth/middleware.ts +++ b/api.planx.uk/modules/auth/middleware.ts @@ -103,6 +103,10 @@ export const useFilePermission: RequestHandler = (req, _res, next): void => { req.headers["api-key"] as string, process.env.FILE_API_KEY_MEDWAY!, ) || + isEqual( + req.headers["api-key"] as string, + process.env.FILE_API_KEY_GATESHEAD!, + ) || isEqual( req.headers["api-key"] as string, process.env.FILE_API_KEY_EPSOM_EWELL!, diff --git a/docker-compose.yml b/docker-compose.yml index b045c55b3d..5022927716 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -129,6 +129,7 @@ services: FILE_API_KEY_SOUTHWARK: ${FILE_API_KEY_SOUTHWARK} FILE_API_KEY_EPSOM_EWELL: ${FILE_API_KEY_EPSOM_EWELL} FILE_API_KEY_MEDWAY: ${FILE_API_KEY_MEDWAY} + FILE_API_KEY_GATESHEAD: ${FILE_API_KEY_GATESHEAD} FILE_API_KEY_NEXUS: ${FILE_API_KEY_NEXUS} FILE_API_KEY: ${FILE_API_KEY} GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} diff --git a/infrastructure/application/Pulumi.production.yaml b/infrastructure/application/Pulumi.production.yaml index 6aba6b28c2..87d073744c 100644 --- a/infrastructure/application/Pulumi.production.yaml +++ b/infrastructure/application/Pulumi.production.yaml @@ -12,6 +12,8 @@ config: secure: AAABANMl+fVFsRVGXvJV/aLManXO+TldXVDhp5QH6KGWJoG7O9Ket63zIW1iOiinINWJ2I5OizI= application:file-api-key-epsom-ewell: secure: AAABANvwhiVRBq8NH7ZqcToUzYn4X+KfC5Wm8WjWUKXT5TuVXqC6zHhVVKFBbmdtKjC4j5M4+bWsLiFO9dO0MLobxLpK7YCE + application:file-api-key-gateshead: + secure: AAABAIIeEiX2htVth4Obb5JMQ3fcezHG/utwUsPikaLNmX+rSmMpGQgcG3LM1pN7XDeaO8J/eZHMilG5OvxGyz7Yo+RxVZV0 application:file-api-key-lambeth: secure: AAABAMNhdCTlFx3fZH/nO71ildypZB2JR5NixlQCENsS1VqwdiOX17q/Gi1UFrCQi2qaY2sZFG4= application:file-api-key-medway: @@ -29,10 +31,10 @@ config: secure: AAABAHfDtVpAD8w32yINWTjgvuRQixWXYFf3/rEcyh59/pRSz+J4ZYCXNq5jqBiIXM2emB+7zOY= application:hasura-cpu: "512" application:hasura-memory: "2048" - application:hasura-proxy-cpu: "512" - application:hasura-proxy-memory: "2048" application:hasura-planx-api-key: secure: AAABAExsXFL7HabeK0Z1oSUJzI2NqVqEmKJ1ojYXyX4Hi8Sbt1Ht9QJc/Yn3cPBAB2r32HKa4HtqqLmfGjS+04lFB/I= + application:hasura-proxy-cpu: "512" + application:hasura-proxy-memory: "2048" application:idox-nexus-client: secure: AAABACdm6IyRjfVPrHLCS5eKQD0ixA2lFC5h04HULwcCXx3j application:idox-nexus-submission-url: todo diff --git a/infrastructure/application/Pulumi.staging.yaml b/infrastructure/application/Pulumi.staging.yaml index 49df40ad99..06ee49d150 100644 --- a/infrastructure/application/Pulumi.staging.yaml +++ b/infrastructure/application/Pulumi.staging.yaml @@ -13,6 +13,8 @@ config: secure: AAABAFpZq81zy3CKFXUgi9oEGIGp7LDVD3TNlYkZD4liX0bxOrmMJYdDpMmyGt4aGARF63nEUmo= application:file-api-key-epsom-ewell: secure: AAABAD1/nlJ2EOEglLiiNsOLbOd3KWCONhNhJAIdZQVnrSRsNIzX2luszOreQf20EYl8AZ4L1TiheqUHSt22e5z1FiLWoCtY + application:file-api-key-gateshead: + secure: AAABAC40pz4+QnXhA2QOYP2F33dc4bCnpL0Njd6hgxR4sTtzt1xF4+2HJpMGdptL2zVrbmdH+cMzrizu6cTmGELrsAoUIEvB application:file-api-key-lambeth: secure: AAABALQTeIf/uScxASJkhmoPRhewQT94Guad4iJ7GRk0DcND8wDUG0eNxDU4+XwUQZqCnL2DP+E= application:file-api-key-medway: @@ -30,10 +32,10 @@ config: secure: AAABAHsoh7ZNkr6ep3xXsUZpp/JIjshBX+tJ0KOFgGnJ4wxR0oIcB6VewVDuwSyFJRVix72YahM= application:hasura-cpu: "512" application:hasura-memory: "2048" - application:hasura-proxy-cpu: "512" - application:hasura-proxy-memory: "2048" application:hasura-planx-api-key: secure: AAABANHLs3ItPxkteh0chwMP2bKuHO3ovuRLi4FsIrCqerzXVIaTLFDqNR+4KBTeMPz4cnF5tCTwsrJv9GruZdXU+lg= + application:hasura-proxy-cpu: "512" + application:hasura-proxy-memory: "2048" application:idox-nexus-client: secure: AAABABprDQomVM9wJQkTMTVtUKvj9lVVVJLdpEBR5p3ibZYvSMedTOb2jztPa0vm6UCH2hilyOV2fsd+akYd3sP8Up5G26mkEKSLSSN4Nc9fu/Hi3Apn1rXHnw== application:idox-nexus-submission-url: https://dev.identity.idoxgroup.com/agw/submission-api diff --git a/infrastructure/application/index.ts b/infrastructure/application/index.ts index a4754a1ab5..317d680092 100644 --- a/infrastructure/application/index.ts +++ b/infrastructure/application/index.ts @@ -377,6 +377,10 @@ export = async () => { name: "FILE_API_KEY_MEDWAY", value: config.requireSecret("file-api-key-medway"), }, + { + name: "FILE_API_KEY_GATESHEAD", + value: config.requireSecret("file-api-key-gateshead"), + }, { name: "GOOGLE_CLIENT_ID", value: config.require("google-client-id"), From 4b7b450d96d3cf4c65432647679d81588ea24bee Mon Sep 17 00:00:00 2001 From: Ian Jones <51156018+ianjon3s@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:03:16 +0000 Subject: [PATCH 03/10] fix: Spacing of review tables (#4093) --- .../components/shared/Preview/SummaryList.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx b/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx index 80d2692bc6..6ee6dcdf38 100644 --- a/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx +++ b/editor.planx.uk/src/@planx/components/shared/Preview/SummaryList.tsx @@ -32,14 +32,13 @@ export const SummaryListTable = styled("dl", { ({ theme, showChangeButton, dense }) => ({ display: "grid", gridTemplateColumns: showChangeButton ? "1fr 2fr 100px" : "1fr 2fr", - gridRowGap: "10px", marginTop: dense ? theme.spacing(1) : theme.spacing(2), marginBottom: dense ? theme.spacing(2) : theme.spacing(4), fontSize: dense ? theme.typography.body2.fontSize : "inherit", "& > *": { borderBottom: `1px solid ${theme.palette.border.main}`, - paddingBottom: dense ? theme.spacing(1) : theme.spacing(2), - paddingTop: dense ? theme.spacing(1) : theme.spacing(2), + paddingBottom: dense ? theme.spacing(1) : theme.spacing(2.5), + paddingTop: dense ? theme.spacing(1) : theme.spacing(2.5), verticalAlign: "top", margin: 0, }, @@ -52,7 +51,7 @@ export const SummaryListTable = styled("dl", { // left column fontWeight: FONT_WEIGHT_SEMI_BOLD, }, - "& dd:nth-of-type(n)": { + "& dd": { // middle column paddingLeft: "10px", }, @@ -65,21 +64,23 @@ export const SummaryListTable = styled("dl", { flexDirection: "column", "& dt": { // top row - paddingLeft: theme.spacing(1), paddingTop: dense ? theme.spacing(1) : theme.spacing(2), - marginTop: theme.spacing(1), + marginTop: theme.spacing(1.5), + paddingBottom: 0, borderTop: `1px solid ${theme.palette.border.main}`, borderBottom: "none", fontWeight: FONT_WEIGHT_SEMI_BOLD, }, - "& dd:nth-of-type(n)": { - // middle row + "& dd": { textAlign: "left", - paddingTop: 0, - paddingBottom: 0, + padding: 0, margin: 0, borderBottom: "none", }, + "& dd:nth-of-type(2n-1)": { + // middle row + padding: theme.spacing(1, 0), + }, "& dd:nth-of-type(2n)": { // bottom row textAlign: "left", From 14dfb4a9a50aa25ac37d8033cacd20f870cc7a5c Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Thu, 19 Dec 2024 17:41:46 +0100 Subject: [PATCH 04/10] feat: add audit table for data migration scripts (#4095) --- hasura.planx.uk/metadata/tables.yaml | 13 +++++++++++++ .../down.sql | 1 + .../up.sql | 8 ++++++++ 3 files changed, 22 insertions(+) create mode 100644 hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/down.sql create mode 100644 hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/up.sql diff --git a/hasura.planx.uk/metadata/tables.yaml b/hasura.planx.uk/metadata/tables.yaml index 2557257e6c..806ad6332e 100644 --- a/hasura.planx.uk/metadata/tables.yaml +++ b/hasura.planx.uk/metadata/tables.yaml @@ -2464,6 +2464,19 @@ - subdomain filter: {} comment: "" +- table: + name: temp_data_migrations_audit + schema: public + object_relationships: + - name: flows + using: + manual_configuration: + column_mapping: + flow_id: id + insertion_order: null + remote_table: + name: flows + schema: public - table: name: uniform_applications schema: public diff --git a/hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/down.sql b/hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/down.sql new file mode 100644 index 0000000000..7f6f79b6ee --- /dev/null +++ b/hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."temp_data_migrations_audit"; diff --git a/hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/up.sql b/hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/up.sql new file mode 100644 index 0000000000..221ccc97a1 --- /dev/null +++ b/hasura.planx.uk/migrations/1734623060683_create_table_public_temp_data_migrations_audit/up.sql @@ -0,0 +1,8 @@ +CREATE TABLE "public"."temp_data_migrations_audit" ( + "flow_id" uuid NOT NULL, + "team_id" integer NOT NULL, + "updated" boolean NOT NULL DEFAULT false, + "updated_at" timestamptz NOT NULL DEFAULT now(), + PRIMARY KEY ("flow_id") +); +COMMENT ON TABLE "public"."temp_data_migrations_audit" IS E'Basic table structure that we can use to track status of data migrations. Contents of this table are ephemeral and may be truncated between migrations'; From 2632b7286f7ea7f978f330edce2aeabc8244cb99 Mon Sep 17 00:00:00 2001 From: Rory Doak <138574807+RODO94@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:18:07 +0000 Subject: [PATCH 05/10] fix: add metabase_id to database scripts (#4100) --- scripts/seed-database/write/teams.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/seed-database/write/teams.sql b/scripts/seed-database/write/teams.sql index d744bc9dd8..4a14b8ed50 100644 --- a/scripts/seed-database/write/teams.sql +++ b/scripts/seed-database/write/teams.sql @@ -5,7 +5,8 @@ CREATE TEMPORARY TABLE sync_teams ( slug text, created_at timestamptz, updated_at timestamptz, - domain text + domain text, + metabase_id integer ); \copy sync_teams FROM '/tmp/teams.csv' WITH (FORMAT csv, DELIMITER ';'); From 40a036c3a16d118cfce3545f6d4e0477ed957577 Mon Sep 17 00:00:00 2001 From: Ian Jones <51156018+ianjon3s@users.noreply.github.com> Date: Fri, 20 Dec 2024 12:58:19 +0000 Subject: [PATCH 06/10] fix: Responsive type sizes (#4099) --- editor.planx.uk/src/theme.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/editor.planx.uk/src/theme.ts b/editor.planx.uk/src/theme.ts index cb8d5da640..a1b54c7416 100644 --- a/editor.planx.uk/src/theme.ts +++ b/editor.planx.uk/src/theme.ts @@ -156,11 +156,17 @@ const getThemeOptions = ({ fontSize: "3rem", letterSpacing: SPACING_TIGHT, fontWeight: FONT_WEIGHT_BOLD, + "@media (max-width: 500px)": { + fontSize: "2.125rem", + }, }, h2: { fontSize: "2.25rem", letterSpacing: SPACING_TIGHT, fontWeight: FONT_WEIGHT_BOLD, + "@media (max-width: 500px)": { + fontSize: "1.65rem", + }, }, h3: { fontSize: "1.5rem", @@ -169,6 +175,9 @@ const getThemeOptions = ({ h4: { fontSize: "1.188rem", fontWeight: FONT_WEIGHT_SEMI_BOLD, + "@media (min-width: 500px) and (max-width: 768px)": { + fontSize: "1.15rem !important", + }, }, h5: { fontSize: "1rem", From 7df545214ae3527f560a9cf87a5a6a2a358ef42a Mon Sep 17 00:00:00 2001 From: Rory Doak <138574807+RODO94@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:07:29 +0000 Subject: [PATCH 07/10] feat: E2E Testing for Planning Constraints (#4063) --- .../src/create-flow-with-geospatial.spec.ts | 56 +- .../ui-driven/src/helpers/addComponent.ts | 4 + e2e/tests/ui-driven/src/helpers/context.ts | 20 + .../src/helpers/geoSpatialUserActions.ts | 40 ++ .../ui-driven/src/helpers/userActions.ts | 8 - .../ui-driven/src/invite-to-pay/helpers.ts | 2 +- .../ui-driven/src/mocks/geospatialMocks.ts | 20 + e2e/tests/ui-driven/src/mocks/gisResponse.ts | 44 ++ .../src/mocks/propertyConstraintResponse.json | 670 ++++++++++++++++++ .../components/PlanningConstraints/Modal.tsx | 2 +- 10 files changed, 851 insertions(+), 15 deletions(-) create mode 100644 e2e/tests/ui-driven/src/helpers/geoSpatialUserActions.ts create mode 100644 e2e/tests/ui-driven/src/mocks/gisResponse.ts create mode 100644 e2e/tests/ui-driven/src/mocks/propertyConstraintResponse.json diff --git a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts index 02ecfbc29e..16b46b755e 100644 --- a/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts +++ b/e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts @@ -6,11 +6,7 @@ import { } from "./helpers/context"; import { getTeamPage } from "./helpers/getPage"; import { createAuthenticatedSession } from "./helpers/globalHelpers"; -import { - answerFindProperty, - answerQuestion, - clickContinue, -} from "./helpers/userActions"; +import { answerQuestion, clickContinue } from "./helpers/userActions"; import { PlaywrightEditor } from "./pages/Editor"; import { navigateToService, @@ -37,6 +33,15 @@ import { setupOSMapsStyles, setupOSMapsVectorTiles, } from "./mocks/osMapsResponse"; +import { + planningConstraintHeadersMock, + setupGISMockResponse, + setupRoadsMockResponse, +} from "./mocks/gisResponse"; +import { + answerFindProperty, + userChallengesPlanningConstraint, +} from "./helpers/geoSpatialUserActions"; test.describe("Flow creation, publish and preview", () => { let context: TestContext = { @@ -133,6 +138,9 @@ test.describe("Flow creation, publish and preview", () => { setupOSMapsStyles(page); setupOSMapsVectorTiles(page); + await setupGISMockResponse(page); + await setupRoadsMockResponse(page); + await expect( page.locator("h1", { hasText: "Find the property" }), ).toBeVisible(); @@ -225,6 +233,44 @@ test.describe("Flow creation, publish and preview", () => { "The correct value for area comes from the map properties ", ).toBeVisible(); + await clickContinue({ page }); + + await expect( + page.locator("h1", { hasText: "Planning constraints" }), + ).toBeVisible(); + + await expect( + page.getByText( + "These are the planning constraints we think apply to this property", + ), + ).toBeVisible(); + + const listedBuildingConstraintRowItem = page.getByRole("button", { + name: "Listed building outlines", + }); + + await expect(listedBuildingConstraintRowItem).toBeVisible(); + + await listedBuildingConstraintRowItem.click(); + + await userChallengesPlanningConstraint(page); + + await expect( + listedBuildingConstraintRowItem.getByText("Marked as not applicable"), + ).toBeVisible(); + + // click to hide constraint data + await listedBuildingConstraintRowItem.click(); + + // ensure constraints that don't apply show up + await page + .getByRole("button", { name: "Constraints that don't apply" }) + .click(); + + const dontApplyHeadings = await page.getByRole("heading").allTextContents(); + + expect(dontApplyHeadings).toEqual(planningConstraintHeadersMock); + // TODO: answer uploadAndLabel // TODO: answerPropertyInfo, answerPlanningConstraints }); diff --git a/e2e/tests/ui-driven/src/helpers/addComponent.ts b/e2e/tests/ui-driven/src/helpers/addComponent.ts index 0ad8f252d1..7f9c6f10b3 100644 --- a/e2e/tests/ui-driven/src/helpers/addComponent.ts +++ b/e2e/tests/ui-driven/src/helpers/addComponent.ts @@ -87,6 +87,10 @@ const createBaseComponent = async ( await page.getByLabel("Show users a 'change' link to").click(); break; case ComponentType.PlanningConstraints: + await page + .getByRole("row", { name: "via Planning Data: conservation-area" }) + .locator("span") + .click(); break; case ComponentType.DrawBoundary: break; diff --git a/e2e/tests/ui-driven/src/helpers/context.ts b/e2e/tests/ui-driven/src/helpers/context.ts index 792a99a1ed..393895a384 100644 --- a/e2e/tests/ui-driven/src/helpers/context.ts +++ b/e2e/tests/ui-driven/src/helpers/context.ts @@ -50,6 +50,26 @@ export async function setUpTestContext( submissionEmail: context.team.settings?.submissionEmail, }, }); + + // has_planning_data needs to be altered manually for geospatial tests + await $admin.client.request( + gql` + mutation TogglePlanningData($teamId: Int) { + update_team_integrations( + where: { team_id: { _eq: $teamId } } + _set: { has_planning_data: true } + ) { + returning { + has_planning_data + team_id + } + } + } + `, + { + teamId: context.team.id, + }, + ); } if ( context.flow && diff --git a/e2e/tests/ui-driven/src/helpers/geoSpatialUserActions.ts b/e2e/tests/ui-driven/src/helpers/geoSpatialUserActions.ts new file mode 100644 index 0000000000..aea7a7b2a4 --- /dev/null +++ b/e2e/tests/ui-driven/src/helpers/geoSpatialUserActions.ts @@ -0,0 +1,40 @@ +import { expect, Page } from "@playwright/test"; +import { setupOSMockResponse } from "../mocks/osPlacesResponse"; + +export async function answerFindProperty(page: Page) { + await setupOSMockResponse(page); + await page.getByLabel("Postcode").fill("SW1 1AA"); + await page.getByLabel("Select an address").click(); + await page.getByRole("option").first().click(); +} + +export const userChallengesPlanningConstraint = async (page: Page) => { + const thisDoesNotApplyConstraintButton = page.getByRole("button", { + name: "I don't think this constraint applies to this property", + }); + + await thisDoesNotApplyConstraintButton.click(); + + const constraintDoesNotApplyDialog = page.getByRole("heading", { + name: "I don't think this constraint applies to this property", + }); + await constraintDoesNotApplyDialog.isVisible(); + + const noAddressSuppliedButton = page.getByTestId("entity-checkbox-42103309"); + await noAddressSuppliedButton.click(); + + const tellUsWhyText = page.getByRole("textbox"); + await tellUsWhyText.fill("This is the reason why"); + + const submitConstraintChallenge = page.getByTestId( + "override-modal-submit-button", + ); + await submitConstraintChallenge.click(); + + await expect( + page.getByTestId("error-message-checklist-error-inaccurate-entities"), + ).toBeHidden(); + await expect( + page.getByTestId("error-message-input-error-inaccurate-entities"), + ).toBeHidden(); +}; diff --git a/e2e/tests/ui-driven/src/helpers/userActions.ts b/e2e/tests/ui-driven/src/helpers/userActions.ts index 35d24d5ae9..6229183c02 100644 --- a/e2e/tests/ui-driven/src/helpers/userActions.ts +++ b/e2e/tests/ui-driven/src/helpers/userActions.ts @@ -1,6 +1,5 @@ import type { Locator, Page } from "@playwright/test"; import { expect } from "@playwright/test"; -import { setupOSMockResponse } from "../mocks/osPlacesResponse"; import { findSessionId, getGraphQLClient } from "./context"; import { TEST_EMAIL, log, waitForDebugLog } from "./globalHelpers"; import { TestContext } from "./types"; @@ -194,13 +193,6 @@ export async function submitCardDetails(page: Page) { await page.locator("#confirm").click(); } -export async function answerFindProperty(page: Page) { - await setupOSMockResponse(page); - await page.getByLabel("Postcode").fill("SW1 1AA"); - await page.getByLabel("Select an address").click(); - await page.getByRole("option").first().click(); -} - export async function answerContactInput( page: Page, { diff --git a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts index 40009b5b90..0d29aac6a9 100644 --- a/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts +++ b/e2e/tests/ui-driven/src/invite-to-pay/helpers.ts @@ -5,10 +5,10 @@ import { TEST_EMAIL, addSessionToContext, log } from "../helpers/globalHelpers"; import { answerChecklist, answerContactInput, - answerFindProperty, fillInEmail, } from "../helpers/userActions"; import { TestContext } from "../helpers/types"; +import { answerFindProperty } from "../helpers/geoSpatialUserActions"; /** * Navigates to pay component whilst completing the minimum requirements for an Invite to Pay flow diff --git a/e2e/tests/ui-driven/src/mocks/geospatialMocks.ts b/e2e/tests/ui-driven/src/mocks/geospatialMocks.ts index e923545f46..5354e94101 100644 --- a/e2e/tests/ui-driven/src/mocks/geospatialMocks.ts +++ b/e2e/tests/ui-driven/src/mocks/geospatialMocks.ts @@ -64,3 +64,23 @@ export const mockChangedMapGeoJson: GeoJsonChangeHandler = { "area.hectares": 0.001072, }, }; + +export const mockRoadData = { + sourceRequest: + "https://api.os.uk/features/v1/wfs?service=WFS&request=GetFeature&version=2.0.0&typeNames=Highways_RoadLink&outputFormat=GEOJSON&srsName=urn%3Aogc%3Adef%3Acrs%3AEPSG%3A%3A4326&count=1&filter=%0A++++%3Cogc%3AFilter%3E%0A++++++%3Cogc%3APropertyIsLike+wildCard%3D%22%25%22+singleChar%3D%22%23%22+escapeChar%3D%22%21%22%3E%0A++++++++%3Cogc%3APropertyName%3EFormsPartOf%3C%2Fogc%3APropertyName%3E%0A++++++++%3Cogc%3ALiteral%3E%25Street%23usrn21900651%25%3C%2Fogc%3ALiteral%3E%0A++++++%3C%2Fogc%3APropertyIsLike%3E%0A++++%3C%2Fogc%3AFilter%3E%0A++&", + metadata: { + "road.classified": { + name: "Classified road", + plural: "Classified roads", + text: "This will effect your project if you are looking to add a dropped kerb. It may also impact some agricultural or forestry projects within 25 metres of a classified road.", + }, + }, + constraints: { + "road.classified": { + fn: "road.classified", + value: false, + text: "is not on a Classified Road", + category: "General policy", + }, + }, +}; diff --git a/e2e/tests/ui-driven/src/mocks/gisResponse.ts b/e2e/tests/ui-driven/src/mocks/gisResponse.ts new file mode 100644 index 0000000000..2fe8215041 --- /dev/null +++ b/e2e/tests/ui-driven/src/mocks/gisResponse.ts @@ -0,0 +1,44 @@ +import { expect, Page } from "@playwright/test"; +import { mockRoadData } from "./geospatialMocks"; +import propertyConstraintsResponse from "./propertyConstraintResponse.json"; + +export async function setupGISMockResponse(page: Page) { + const gisDigitalLandEndpoint = "**/gis/E2E?geom*"; + await page.route(gisDigitalLandEndpoint, async (route, request) => { + const urlContainsConstraints = checkGISMockRequestUrl(request.url()); + expect(urlContainsConstraints).toEqual(true); + await route.fulfill({ + status: 200, + body: JSON.stringify(propertyConstraintsResponse), + }); + }); +} + +export function checkGISMockRequestUrl(url: string) { + const splitUrl = url.split("/").pop()?.split("%2C"); + return ( + !splitUrl?.includes("designated.conservationArea") && + splitUrl?.includes("listed") + ); +} + +export async function setupRoadsMockResponse(page: Page) { + const gisRoadsEndpoint = new RegExp(/\/roads\?.*/); + await page.route(gisRoadsEndpoint, async (route) => { + await route.fulfill({ + status: 200, + body: JSON.stringify(mockRoadData), + }); + }); +} + +export const planningConstraintHeadersMock = [ + "Planning constraints", + "These are the planning constraints we think apply to this property", + "Heritage and conservation", + "General policy", + "Heritage and conservation", + "Flooding", + "Ecology", + "Trees", +]; diff --git a/e2e/tests/ui-driven/src/mocks/propertyConstraintResponse.json b/e2e/tests/ui-driven/src/mocks/propertyConstraintResponse.json new file mode 100644 index 0000000000..cf7407b337 --- /dev/null +++ b/e2e/tests/ui-driven/src/mocks/propertyConstraintResponse.json @@ -0,0 +1,670 @@ +{ + "sourceRequest": "https://www.planning.data.gov.uk/entity.json?entries=current&geometry=POINT%28-0.1151501+51.4745098%29&geometry_relation=intersects&exclude_field=geometry%2Cpoint&limit=100&dataset=article-4-direction-area&dataset=central-activities-zone&dataset=brownfield-land&dataset=brownfield-site&dataset=area-of-outstanding-natural-beauty&dataset=green-belt&dataset=national-park&dataset=world-heritage-site&dataset=world-heritage-site-buffer-zone&dataset=flood-risk-zone&dataset=listed-building&dataset=listed-building-outline&dataset=scheduled-monument&dataset=ancient-woodland&dataset=ramsar&dataset=special-area-of-conservation&dataset=special-protection-area&dataset=site-of-special-scientific-interest&dataset=park-and-garden&dataset=tree&dataset=tree-preservation-order&dataset=tree-preservation-zone", + "constraints": { + "listed": { + "fn": "listed", + "value": true, + "text": "is, or is within, a Listed Building", + "data": [ + { + "entry-date": "2024-04-16", + "start-date": "1980-05-15", + "end-date": "", + "entity": 42103309, + "name": "No Address Supplied", + "dataset": "listed-building-outline", + "typology": "geography", + "reference": "12/435 and 963/1", + "prefix": "listed-building-outline", + "organisation-entity": "192" + } + ], + "category": "Heritage and conservation" + }, + "article4": { + "fn": "article4", + "value": false, + "text": "is not in an Article 4 direction area", + "category": "General policy" + }, + "article4.caz": { + "fn": "article4.caz", + "value": false, + "text": "is not in the Central Activities Zone", + "category": "General policy" + }, + "brownfieldSite": { + "fn": "brownfieldSite", + "value": false, + "text": "is not on Brownfield land", + "category": "General policy" + }, + "designated.AONB": { + "fn": "designated.AONB", + "value": false, + "text": "is not in an Area of Outstanding Natural Beauty", + "category": "Heritage and conservation" + }, + "greenBelt": { + "fn": "greenBelt", + "value": false, + "text": "is not in a Green Belt", + "category": "General policy" + }, + "designated.nationalPark": { + "fn": "designated.nationalPark", + "value": false, + "text": "is not in a National Park", + "category": "Heritage and conservation" + }, + "designated.nationalPark.broads": { + "fn": "designated.nationalPark.broads", + "value": false + }, + "designated.WHS": { + "fn": "designated.WHS", + "value": false, + "text": "is not an UNESCO World Heritage Site", + "category": "Heritage and conservation" + }, + "flood": { + "fn": "flood", + "value": false, + "text": "is not in a Flood Risk Zone", + "category": "Flooding" + }, + "monument": { + "fn": "monument", + "value": false, + "text": "is not the site of a Scheduled Monument", + "category": "Heritage and conservation" + }, + "nature.ASNW": { + "fn": "nature.ASNW", + "value": false, + "text": "is not in an Ancient Semi-Natural Woodland (ASNW)", + "category": "Ecology" + }, + "nature.ramsarSite": { + "fn": "nature.ramsarSite", + "value": false, + "text": "is not in a Ramsar Site", + "category": "Ecology" + }, + "nature.SAC": { + "fn": "nature.SAC", + "value": false, + "text": "is not in a Special Area of Conservation (SAC)", + "category": "Ecology" + }, + "nature.SPA": { + "fn": "nature.SPA", + "value": false, + "text": "is not in a Special Protection Area (SPA)", + "category": "Ecology" + }, + "nature.SSSI": { + "fn": "nature.SSSI", + "value": false, + "text": "is not a Site of Special Scientific Interest (SSSI)", + "category": "Ecology" + }, + "registeredPark": { + "fn": "registeredPark", + "value": false, + "text": "is not in a Historic Park or Garden", + "category": "Heritage and conservation" + }, + "tpo": { + "fn": "tpo", + "value": false, + "text": "is not in a Tree Preservation Order (TPO) Zone", + "category": "Trees" + }, + "designated": { "value": true }, + "listed.grade.I": { "fn": "listed.grade.I", "value": false }, + "listed.grade.II": { "fn": "listed.grade.II", "value": false }, + "listed.grade.II*": { "fn": "listed.grade.II*", "value": false } + }, + "metadata": { + "article4": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "article-4-direction", + "dataset": "article-4-direction-area", + "description": "Orders made by the local planning authority to remove all or some of the permitted development rights on a site in order to protect it", + "name": "Article 4 direction area", + "plural": "Article 4 direction areas", + "prefix": "", + "text": "A local planning authority may create an [article 4 direction](https://www.gov.uk/guidance/when-is-permission-required#article-4-direction) to alter or remove [permitted development rights](https://www.gov.uk/government/publications/permitted-development-rights-for-householders-technical-guidance) from a building or area.\n\nEach [article 4 direction](/dataset/article-4-direction) may apply to one or more article 4 direction areas.", + "typology": "geography", + "wikidata": "", + "wikipedia": "", + "entities": "", + "themes": ["heritage"], + "entity-count": { "dataset": "article-4-direction-area", "count": 2438 }, + "paint-options": "", + "attribution": "crown-copyright", + "attribution-text": "© Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "article-4-directions", + "github-discussion": 30, + "entity-minimum": 7010000000, + "entity-maximum": 7019999999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "3.0" + }, + "article4.caz": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "central-activities-zone", + "dataset": "central-activities-zone", + "description": "", + "name": "Central activities zone", + "plural": "Central activities zones", + "prefix": "", + "text": "The [Greater London Authority](https://www.london.gov.uk/) (GLA) designates a central area of London with [implications for planning](https://www.london.gov.uk/what-we-do/planning/implementing-london-plan/london-plan-guidance-and-spgs/central-activities-zone)\nThis dataset combines data provided by the GLA with the boundary from the individual London boroughs.", + "typology": "geography", + "wikidata": "", + "wikipedia": "", + "entities": "", + "themes": ["development"], + "entity-count": { "dataset": "central-activities-zone", "count": 10 }, + "paint-options": "", + "attribution": "crown-copyright", + "attribution-text": "© Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "central-activty-zones", + "github-discussion": "", + "entity-minimum": 2200000, + "entity-maximum": 2299999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "brownfieldSite": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "brownfield-site", + "dataset": "brownfield-site", + "description": "", + "name": "Brownfield site", + "plural": "Brownfield sites", + "prefix": "", + "text": "This is an experimental dataset of the boundaries of brownfield sites found on [data.gov.uk](https://www.data.gov.uk/search?q=brownfield)\nand local planning authority web sites.\nWe expect to combine this dataset with the [brownfield land](/dataset/brownfield-land) dataset in the near future.", + "typology": "geography", + "wikidata": "Q896586", + "wikipedia": "Brownfield_land", + "entities": "", + "themes": ["development"], + "entity-count": { "dataset": "brownfield-site", "count": 339 }, + "paint-options": { "colour": "#745729" }, + "attribution": "crown-copyright", + "attribution-text": "© Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "brownfield-land", + "github-discussion": 28, + "entity-minimum": 1800000, + "entity-maximum": 1899999, + "phase": "alpha", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "designated.AONB": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "area-of-outstanding-natural-beauty", + "dataset": "area-of-outstanding-natural-beauty", + "description": "Land protected by law to conserve and enhance its natural beauty", + "name": "Area of outstanding natural beauty", + "plural": "Areas of outstanding natural beauty", + "prefix": "", + "text": "An area of outstanding natural beauty (AONB) as designated by [Natural England](https://www.gov.uk/government/organisations/natural-england).\n\nNatural England provides [guidance](https://www.gov.uk/guidance/protected-sites-and-areas-how-to-review-planning-applications) to help local authorities decide on planning applications in protected sites and areas.", + "typology": "geography", + "wikidata": "Q174945", + "wikipedia": "Area_of_Outstanding_Natural_Beauty", + "entities": "", + "themes": ["environment"], + "entity-count": { + "dataset": "area-of-outstanding-natural-beauty", + "count": 34 + }, + "paint-options": { "colour": "#d53880" }, + "attribution": "natural-england", + "attribution-text": "© Natural England copyright. Contains Ordnance Survey data © Crown copyright and database right 2024.", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "areas-of-outstanding-natural-beauty", + "github-discussion": 31, + "entity-minimum": 1000000, + "entity-maximum": 1099999, + "phase": "live", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "greenBelt": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "green-belt", + "dataset": "green-belt", + "description": "", + "name": "Green belt", + "plural": "Green belt", + "prefix": "", + "text": "Boundaries for land designated by a local planning authority as being [green belt](https://www.gov.uk/guidance/green-belt),\ngrouped using the [greenbelt core](/dataset/greenbelt-core) category.\nThis data is compiled by the Department for Levelling Up, Housing and Communities for the purposes of gathering [green belt statistics](https://www.gov.uk/government/collections/green-belt-statistics).", + "typology": "geography", + "wikidata": "Q2734873", + "wikipedia": "Green_belt_(United_Kingdom)", + "entities": "", + "themes": ["environment"], + "entity-count": { "dataset": "green-belt", "count": 185 }, + "paint-options": { "colour": "#85994b" }, + "attribution": "os-open-data", + "attribution-text": "Your use of OS OpenData is subject to the terms at http://os.uk/opendata/licence\nContains Ordnance Survey data © Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "greenbelt", + "github-discussion": 45, + "entity-minimum": 610000, + "entity-maximum": 610999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "designated.nationalPark": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "national-park", + "dataset": "national-park", + "description": "", + "name": "National park", + "plural": "National parks", + "prefix": "statistical-geography", + "text": "The administrative boundaries of [national park authorities](/dataset/national-park-authority) in England as provided by the ONS for the purposes of producing statistics.", + "typology": "geography", + "wikidata": "Q60256727", + "wikipedia": "National_park", + "entities": "", + "themes": ["heritage"], + "entity-count": { "dataset": "national-park", "count": 10 }, + "paint-options": { "colour": "#3DA52C" }, + "attribution": "ons-boundary", + "attribution-text": "Source: Office for National Statistics licensed under the Open Government Licence v.3.0\nContains OS data © Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "national-parks-and-the-broads", + "github-discussion": "", + "entity-minimum": 520000, + "entity-maximum": 520999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "designated.WHS": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "historic-england", + "dataset": "world-heritage-site-buffer-zone", + "description": "", + "name": "World heritage site buffer zone", + "plural": "World heritage site buffer zones", + "prefix": "", + "text": "A [World Heritage Site](/dataset/world-heritage-site) may have a [buffer zone](https://whc.unesco.org/en/series/25/) with implications for planning.", + "typology": "geography", + "wikidata": "Q9259", + "wikipedia": "World_Heritage_Site", + "entities": "", + "themes": ["heritage"], + "entity-count": { + "dataset": "world-heritage-site-buffer-zone", + "count": 9 + }, + "paint-options": { "colour": "#EB1EE5", "opacity": 0.2 }, + "attribution": "historic-england", + "attribution-text": "© Historic England 2024. Contains Ordnance Survey data © Crown copyright and database right 2024.\nThe Historic England GIS Data contained in this material was obtained on [date].\nThe most publicly available up to date Historic England GIS Data can be obtained from [HistoricEngland.org.uk](https://historicengland.org.uk).", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "world-heritage-sites", + "github-discussion": "", + "entity-minimum": 16110000, + "entity-maximum": 16129999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "flood": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "flood-risk-zone", + "dataset": "flood-risk-zone", + "description": "Area identified as being at risk of flooding from rivers or the sea", + "name": "Flood risk zone", + "plural": "Flood risk zones", + "prefix": "", + "text": "Flood zones are a guide produced by the Environment Agency to demonstrate the probability of river and sea flooding in areas across England. Flood zones are based on the likelihood of an area flooding, with flood zone 1 areas least likely to flood and flood zone 3 areas more likely to flood. \n\nThe flood zones were produced to help developers, councils and communities understand the flood risks present in specific locations or regions. Despite being a very useful indicator of an area’s flood risk, the zones cannot tell you whether a location will definitely flood or to what severity.", + "typology": "geography", + "wikidata": "", + "wikipedia": "", + "entities": "", + "themes": ["environment"], + "entity-count": { "dataset": "flood-risk-zone", "count": 550621 }, + "paint-options": "", + "attribution": "crown-copyright", + "attribution-text": "© Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "flood-risk-zones", + "github-discussion": 46, + "entity-minimum": 65000000, + "entity-maximum": 65999999, + "phase": "live", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "listed": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "listed-building", + "dataset": "listed-building-outline", + "description": "boundary of a listed building", + "name": "Listed building outline", + "plural": "Listed building outlines", + "prefix": "", + "text": "The geospatial boundary for [listed buildings](https://historicengland.org.uk/listing/what-is-designation/listed-buildings) as designated by [Historic England](https://historicengland.org.uk/) as collected from local planning authorities.\n\nWe are [working with a group of local planning authorities](/about/) to help them publish their data to inform planning decisions, and to develop a [data specification for listed building outlines](https://www.digital-land.info/guidance/specifications/listed-building).\n\nWe expect to eventually merge this dataset with the [listed building](/dataset/listed-building) dataset.", + "typology": "geography", + "wikidata": "Q570600", + "wikipedia": "Listed_building", + "entities": "", + "themes": ["heritage"], + "entity-count": { "dataset": "listed-building-outline", "count": 34225 }, + "paint-options": { "colour": "#F9C744" }, + "attribution": "crown-copyright", + "attribution-text": "© Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "listed-buildings", + "github-discussion": 44, + "entity-minimum": 42100000, + "entity-maximum": 43099999, + "phase": "alpha", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "monument": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "historic-england", + "dataset": "scheduled-monument", + "description": "", + "name": "Scheduled monument", + "plural": "Scheduled monuments", + "prefix": "", + "text": "Historic buildings or sites such as Roman remains, burial mounds, castles, bridges, earthworks, the remains of deserted villages and industrial sites can be designated scheduled monuments by the Secretary of State for [Digital, Culture, Media and Sport](https://www.gov.uk/government/organisations/department-for-digital-culture-media-sport). \n\nThis list of scheduled monuments is kept and maintained by [Historic England](https://historicengland.org.uk/).", + "typology": "geography", + "wikidata": "Q219538", + "wikipedia": "Scheduled_monument", + "entities": "", + "themes": ["heritage"], + "entity-count": { "dataset": "scheduled-monument", "count": 19987 }, + "paint-options": { "colour": "#0F9CDA" }, + "attribution": "historic-england", + "attribution-text": "© Historic England 2024. Contains Ordnance Survey data © Crown copyright and database right 2024.\nThe Historic England GIS Data contained in this material was obtained on [date].\nThe most publicly available up to date Historic England GIS Data can be obtained from [HistoricEngland.org.uk](https://historicengland.org.uk).", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "scheduled-monuments", + "github-discussion": "", + "entity-minimum": 13900000, + "entity-maximum": 13999999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "nature.ASNW": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "ancient-woodland", + "dataset": "ancient-woodland", + "description": "An area that’s been wooded continuously since at least 1600 AD", + "name": "Ancient woodland", + "plural": "Ancient woodlands", + "prefix": "", + "text": "An area designated as ancient woodland by Natural England.\n\nNatural England and Forestry Commission [Guidance](https://www.gov.uk/guidance/ancient-woodland-and-veteran-trees-protection-surveys-licences) is used in planning decisions.", + "typology": "geography", + "wikidata": "Q3078732", + "wikipedia": "Ancient_woodland", + "entities": "", + "themes": ["environment"], + "entity-count": { "dataset": "ancient-woodland", "count": 44261 }, + "paint-options": { "colour": "#00703c", "opacity": 0.2 }, + "attribution": "natural-england", + "attribution-text": "© Natural England copyright. Contains Ordnance Survey data © Crown copyright and database right 2024.", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "ancient-woodlands", + "github-discussion": 32, + "entity-minimum": 110000000, + "entity-maximum": 129999999, + "phase": "live", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "nature.ramsarSite": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "ramsar", + "dataset": "ramsar", + "description": "", + "name": "Ramsar site", + "plural": "Ramsar sites", + "prefix": "", + "text": "An internationally protected site listed as a wetland of international importance.\nRamsar sites are designated by [UNESCO](https://en.unesco.org/) and managed by [Natural England](https://www.gov.uk/government/organisations/natural-england).\n\nNatural England provides [guidance ](https://www.gov.uk/guidance/protected-sites-and-areas-how-to-review-planning-applications) to help local authorities decide on planning applications in protected sites and areas.", + "typology": "geography", + "wikidata": "", + "wikipedia": "", + "entities": "", + "themes": ["environment"], + "entity-count": { "dataset": "ramsar", "count": 73 }, + "paint-options": { "colour": "#7fcdff" }, + "attribution": "natural-england", + "attribution-text": "© Natural England copyright. Contains Ordnance Survey data © Crown copyright and database right 2024.", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "ramsar", + "github-discussion": 41, + "entity-minimum": 612000, + "entity-maximum": 619999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "nature.SAC": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "special-area-of-conservation", + "dataset": "special-area-of-conservation", + "description": "", + "name": "Special area of conservation", + "plural": "Special areas of conservation", + "prefix": "", + "text": "Special areas of conservation (SACs) are area of land which have been designated by\n[DEFRA](https://www.gov.uk/government/organisations/department-for-environment-food-rural-affairs),\nwith advice from the [Joint Nature Conservation Committee](https://jncc.gov.uk/),\nto protect specific habitats and species.\n\nDEFRA and [Natural England](https://www.gov.uk/government/organisations/natural-england) publish\n[guidance](https://www.gov.uk/guidance/protected-sites-and-areas-how-to-review-planning-applications)\non how to review planning applications in protected sites and areas.", + "typology": "geography", + "wikidata": "Q1191622", + "wikipedia": "Special_Area_of_Conservation", + "entities": "", + "themes": ["environment"], + "entity-count": { + "dataset": "special-area-of-conservation", + "count": 260 + }, + "paint-options": { "colour": "#7A8705" }, + "attribution": "natural-england", + "attribution-text": "© Natural England copyright. Contains Ordnance Survey data © Crown copyright and database right 2024.", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "special-areas-of-conservation", + "github-discussion": "", + "entity-minimum": 14800000, + "entity-maximum": 14899999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "nature.SPA": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "special-protection-area", + "dataset": "special-protection-area", + "description": "", + "name": "Special protection area", + "plural": "Special protection areas", + "prefix": "", + "text": "[Special protection areas](https://naturalengland-defra.opendata.arcgis.com/maps/Defra::special-protection-areas-england/about) is an area designated \nfor the protection of birds and wildlife. This dataset is provided by [Natural England](https://www.gov.uk/government/organisations/natural-england).", + "typology": "geography", + "wikidata": "", + "wikipedia": "", + "entities": "", + "themes": ["environment"], + "entity-count": { "dataset": "special-protection-area", "count": 88 }, + "paint-options": "", + "attribution": "natural-england", + "attribution-text": "© Natural England copyright. Contains Ordnance Survey data © Crown copyright and database right 2024.", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "special-protection-areas", + "github-discussion": "", + "entity-minimum": 14900000, + "entity-maximum": 14999999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "nature.SSSI": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "site-of-special-scientific-interest", + "dataset": "site-of-special-scientific-interest", + "description": "", + "name": "Site of special scientific interest", + "plural": "Sites of special scientific interest", + "prefix": "", + "text": "Sites of special scientific interest (SSSI) are nationally protected sites that have features such as wildlife or geology. \n\nSSSIs are designated by [Natural England](https://www.gov.uk/government/organisations/natural-england).\nThere is [guidance](https://www.gov.uk/guidance/protected-areas-sites-of-special-scientific-interest) to help local authorities decide on planning applications in protected SSSIs.", + "typology": "geography", + "wikidata": "Q422211", + "wikipedia": "Site_of_Special_Scientific_Interest", + "entities": "", + "themes": ["environment"], + "entity-count": { + "dataset": "site-of-special-scientific-interest", + "count": 4128 + }, + "paint-options": { "colour": "#308fac" }, + "attribution": "natural-england", + "attribution-text": "© Natural England copyright. Contains Ordnance Survey data © Crown copyright and database right 2024.", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "sites-of-special-scientific-interest", + "github-discussion": "", + "entity-minimum": 14500000, + "entity-maximum": 14599999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "registeredPark": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "historic-england", + "dataset": "park-and-garden", + "description": "", + "name": "Historic parks and gardens", + "plural": "Parks and gardens", + "prefix": "", + "text": "Historic parks and gardens as listed by [Historic England](https://historicengland.org.uk/) in the [Register of Parks and Gardens of Special Historic Interest](https://historicengland.org.uk/listing/what-is-designation/registered-parks-and-gardens/).", + "typology": "geography", + "wikidata": "Q6975250", + "wikipedia": "Register_of_Historic_Parks_and_Gardens_of_Special_Historic_Interest_in_England", + "entities": "", + "themes": ["environment", "heritage"], + "entity-count": { "dataset": "park-and-garden", "count": 1715 }, + "paint-options": { "colour": "#0EB951" }, + "attribution": "historic-england", + "attribution-text": "© Historic England 2024. Contains Ordnance Survey data © Crown copyright and database right 2024.\nThe Historic England GIS Data contained in this material was obtained on [date].\nThe most publicly available up to date Historic England GIS Data can be obtained from [HistoricEngland.org.uk](https://historicengland.org.uk).", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "", + "github-discussion": "", + "entity-minimum": 11100000, + "entity-maximum": 11199999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "1.0" + }, + "tpo": { + "entry-date": "", + "start-date": "", + "end-date": "", + "collection": "tree-preservation-order", + "dataset": "tree-preservation-zone", + "description": "An area covered by a tree preservation order", + "name": "Tree preservation zone", + "plural": "Trees preservation zones", + "prefix": "", + "text": "A Tree Preservation Order (TPO) can be placed on single trees, groups of trees and even whole woodlands. Tree Preservation Orders are made by local planning authorities following [guidance](https://www.gov.uk/guidance/tree-preservation-orders-and-trees-in-conservation-areas) provided by the [Ministry of Housing, Communities and Local Governments](https://www.gov.uk/government/organisations/ministry-of-housing-communities-local-government).\n\nEach [tree preservation order](/dataset/tree-preservation-order) may apply to a number of tree preservation order zones, and a number of individual [trees](/dataset/tree).\n\nThis dataset contains data from [a small group of local planning authorities](/about/) who we are working with to develop a [data specification for tree preservation orders](https://www.digital-land.info/guidance/specifications/tree-preservation-order).", + "typology": "geography", + "wikidata": "Q10884", + "wikipedia": "Tree", + "entities": "", + "themes": ["environment"], + "entity-count": { "dataset": "tree-preservation-zone", "count": 13062 }, + "paint-options": "", + "attribution": "crown-copyright", + "attribution-text": "© Crown copyright and database right 2024", + "licence": "ogl3", + "licence-text": "Licensed under the [Open Government Licence v.3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).", + "consideration": "tree-preservation-orders", + "github-discussion": 43, + "entity-minimum": 19100000, + "entity-maximum": 29099999, + "phase": "beta", + "realm": "dataset", + "replacement-dataset": "", + "version": "2.0" + } + } +} diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Modal.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Modal.tsx index dabc39d5ec..412e2cae07 100644 --- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Modal.tsx +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Modal.tsx @@ -194,7 +194,7 @@ export const OverrideEntitiesModal = ({ entities?.map((e) => ( Date: Fri, 20 Dec 2024 14:40:27 +0000 Subject: [PATCH 08/10] chore: fix confirmation and invite to pay page default copy (#4101) --- .../components/Confirmation/Confirmation.stories.tsx | 7 +++---- .../src/@planx/components/Confirmation/Editor.tsx | 5 ++--- editor.planx.uk/src/pages/Pay/InviteToPay.tsx | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/editor.planx.uk/src/@planx/components/Confirmation/Confirmation.stories.tsx b/editor.planx.uk/src/@planx/components/Confirmation/Confirmation.stories.tsx index 4cd47a1676..327a847b77 100644 --- a/editor.planx.uk/src/@planx/components/Confirmation/Confirmation.stories.tsx +++ b/editor.planx.uk/src/@planx/components/Confirmation/Confirmation.stories.tsx @@ -42,12 +42,11 @@ export const Basic = { ## You will be contacted - if there is anything missing from the information you have provided so far - if any additional information is required - - to arrange a site visit, if required - - to inform you whether a certificate has been granted or not`, + - to arrange a site visit, if required`, contactInfo: ` - You can contact us at planning@lambeth.gov.uk + You can contact us at ADD YOUR COUNCIL CONTACT

- What did you think of this service? Please give us your feedback using the link in the footer below. +

What did you think of this service? Please give us your feedback on the next page.

`, data: [], }, diff --git a/editor.planx.uk/src/@planx/components/Confirmation/Editor.tsx b/editor.planx.uk/src/@planx/components/Confirmation/Editor.tsx index 2e64a68f6f..77313f210f 100644 --- a/editor.planx.uk/src/@planx/components/Confirmation/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Confirmation/Editor.tsx @@ -71,13 +71,12 @@ export default function ConfirmationEditor(props: Props) {
  • if there is anything missing from the information you have provided so far
  • if any additional information is required
  • to arrange a site visit, if required
  • -
  • to inform you whether a certificate has been granted or not
  • `, contactInfo: props.node?.data?.contactInfo || - `You can contact us at planning@lambeth.gov.uk + `You can contact us at ADD YOUR COUNCIL CONTACT

    - What did you think of this service? Please give us your feedback using the link in the footer below.`, +

    What did you think of this service? Please give us your feedback on the next page.

    `, ...parseNextSteps(props.node?.data), }, onSubmit: (values) => { diff --git a/editor.planx.uk/src/pages/Pay/InviteToPay.tsx b/editor.planx.uk/src/pages/Pay/InviteToPay.tsx index ec9d1a52c5..ea7e7c0cd5 100644 --- a/editor.planx.uk/src/pages/Pay/InviteToPay.tsx +++ b/editor.planx.uk/src/pages/Pay/InviteToPay.tsx @@ -59,7 +59,6 @@ const InviteToPay: React.FC = ({ createdAt }) => {
  • if any additional information is required
  • to arrange a site visit, if required
  • -
  • to inform you whether a certificate has been granted or not
  • From 3421ae5d5a3bd43af28f087cf2c146048683c7a1 Mon Sep 17 00:00:00 2001 From: Dan G Date: Fri, 20 Dec 2024 15:54:55 +0000 Subject: [PATCH 09/10] [infrastructure] add explicit autoscaling logic to Hasura service (#4096) --- .../application/Pulumi.production.yaml | 2 +- .../application/Pulumi.staging.yaml | 2 +- infrastructure/application/services/hasura.ts | 98 ++++++++++++++++++- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/infrastructure/application/Pulumi.production.yaml b/infrastructure/application/Pulumi.production.yaml index 87d073744c..37b51f211c 100644 --- a/infrastructure/application/Pulumi.production.yaml +++ b/infrastructure/application/Pulumi.production.yaml @@ -29,7 +29,7 @@ config: secure: AAABADo05EPv/HWj7Rkf19nBeTcPJd4pEcRi2/uhyB3agraFODpLvNMx2bXfISf5pZ4HA41GYCE4f7OLcJN6hIV6ZMWUlEriPzvkoUAixbLlz1LIERiyk73R8E4F2bV65/9aFqi4l7caLS5c8iDJrE+JAvu2i7oS application:hasura-admin-secret: secure: AAABAHfDtVpAD8w32yINWTjgvuRQixWXYFf3/rEcyh59/pRSz+J4ZYCXNq5jqBiIXM2emB+7zOY= - application:hasura-cpu: "512" + application:hasura-cpu: "1024" application:hasura-memory: "2048" application:hasura-planx-api-key: secure: AAABAExsXFL7HabeK0Z1oSUJzI2NqVqEmKJ1ojYXyX4Hi8Sbt1Ht9QJc/Yn3cPBAB2r32HKa4HtqqLmfGjS+04lFB/I= diff --git a/infrastructure/application/Pulumi.staging.yaml b/infrastructure/application/Pulumi.staging.yaml index 06ee49d150..5cc9b010c9 100644 --- a/infrastructure/application/Pulumi.staging.yaml +++ b/infrastructure/application/Pulumi.staging.yaml @@ -30,7 +30,7 @@ config: secure: AAABACgwjEmlLmE19ofRO8e/JpD8sHDV2lcDmSXbU/Mw8ZRh5gTgll8DZ3BVjpDWfQfIecBAIf2TFgeo9CsBSLjfaRJ7eJyKDSWm7i8LlMC2JN/PN+Ig8oeI0H0oLkqJIziNKKjx+e97zDiXO9LZ1CVzrywR application:hasura-admin-secret: secure: AAABAHsoh7ZNkr6ep3xXsUZpp/JIjshBX+tJ0KOFgGnJ4wxR0oIcB6VewVDuwSyFJRVix72YahM= - application:hasura-cpu: "512" + application:hasura-cpu: "1024" application:hasura-memory: "2048" application:hasura-planx-api-key: secure: AAABANHLs3ItPxkteh0chwMP2bKuHO3ovuRLi4FsIrCqerzXVIaTLFDqNR+4KBTeMPz4cnF5tCTwsrJv9GruZdXU+lg= diff --git a/infrastructure/application/services/hasura.ts b/infrastructure/application/services/hasura.ts index c4b3f63028..7e4b5a5756 100644 --- a/infrastructure/application/services/hasura.ts +++ b/infrastructure/application/services/hasura.ts @@ -35,6 +35,10 @@ export const createHasuraService = async ({ protocol: "HTTP", healthCheck: { path: "/healthz", + interval: 30, + timeout: 10, + healthyThreshold: 3, + unhealthyThreshold: 5, }, }); const hasuraListenerHttp = targetHasura.createListener("hasura-http", { protocol: "HTTP" }); @@ -51,6 +55,15 @@ export const createHasuraService = async ({ const hasuraService = new awsx.ecs.FargateService("hasura", { cluster, subnets: networking.requireOutput("publicSubnetIds"), + desiredCount: 1, + deploymentMinimumHealthyPercent: 50, + deploymentMaximumPercent: 200, + // extend service-level health check grace period to match hasura server migrations timeout + healthCheckGracePeriodSeconds: 600, + deploymentCircuitBreaker: { + enable: true, + rollback: true, + }, taskDefinitionArgs: { logGroup: new aws.cloudwatch.LogGroup("hasura", { namePrefix: "hasura", @@ -62,15 +75,38 @@ export const createHasuraService = async ({ cpu: config.requireNumber("hasura-proxy-cpu"), memory: config.requireNumber("hasura-proxy-memory"), portMappings: [hasuraListenerHttp], + // hasuraProxy should wait for the hasura container to spin up before starting + dependsOn: [{ + containerName: "hasura", + condition: "HEALTHY" + }], + healthCheck: { + // hasuraProxy health depends on hasura health + command: ["CMD-SHELL", `curl --head http://localhost:${HASURA_PROXY_PORT}/healthz || exit 1`], + interval: 15, + timeout: 3, + retries: 3, + }, environment: [ { name: "HASURA_PROXY_PORT", value: String(HASURA_PROXY_PORT) }, { name: "HASURA_NETWORK_LOCATION", value: "localhost" }, ], }, hasura: { + // hasuraProxy dependency timeout should mirror migration timeout + startTimeout: 600, + stopTimeout: 120, image: repo.buildAndPushImage("../../hasura.planx.uk"), cpu: config.requireNumber("hasura-cpu"), memory: config.requireNumber("hasura-memory"), + healthCheck: { + command: ["CMD-SHELL", "curl --head http://localhost:8080/healthz || exit 1"], + // wait 5m before running container-level health check, using same params as docker-compose + startPeriod: 300, + interval: 15, + timeout: 3, + retries: 10, + }, environment: [ { name: "HASURA_GRAPHQL_ENABLE_CONSOLE", value: "true" }, { @@ -98,7 +134,6 @@ export const createHasuraService = async ({ name: "HASURA_GRAPHQL_DATABASE_URL", value: dbRootUrl, }, - { name: "HASURA_GRAPHQL_MIGRATIONS_SERVER_TIMEOUT", value: "300" }, { name: "HASURA_PLANX_API_URL", value: `https://api.${DOMAIN}`, @@ -107,15 +142,68 @@ export const createHasuraService = async ({ name: "HASURA_PLANX_API_KEY", value: config.require("hasura-planx-api-key"), }, + // extend timeout for migrations during setup to 10 mins (default is 30s) + { + name: "HASURA_GRAPHQL_MIGRATIONS_SERVER_TIMEOUT", + value: "600", + }, + // ensure migrations run sequentially + { + name: "HASURA_GRAPHQL_MIGRATIONS_CONCURRENCY", + value: "1", + }, + // get more detailed logs during attempted migration + { + name: "HASURA_GRAPHQL_MIGRATIONS_LOG_LEVEL", + value: "debug", + }, ], }, }, }, - desiredCount: 1, - // experiment with non-zero grace period to see if it resolves scale up failure - healthCheckGracePeriodSeconds: 180, }); - + + // TODO: bump awsx to 1.x to use the FargateService scaleConfig option to replace more verbose config below + const hasuraScalingTarget = new aws.appautoscaling.Target("hasura-scaling-target", { + // start conservative, can always bump max as required + maxCapacity: 3, + minCapacity: 1, + resourceId: pulumi.interpolate`service/${cluster.cluster.name}/${hasuraService.service.name}`, + scalableDimension: "ecs:service:DesiredCount", + serviceNamespace: "ecs", + }); + + const hasuraCpuScaling = new aws.appautoscaling.Policy("hasura-cpu-scaling", { + policyType: "TargetTrackingScaling", + resourceId: hasuraScalingTarget.resourceId, + scalableDimension: hasuraScalingTarget.scalableDimension, + serviceNamespace: hasuraScalingTarget.serviceNamespace, + targetTrackingScalingPolicyConfiguration: { + predefinedMetricSpecification: { + predefinedMetricType: "ECSServiceAverageCPUUtilization", + }, + // scale out quickly for responsiveness, but scale in more slowly to avoid thrashing + targetValue: 60.0, + scaleInCooldown: 300, + scaleOutCooldown: 60, + }, + }); + + const hasuraMemoryScaling = new aws.appautoscaling.Policy("hasura-memory-scaling", { + policyType: "TargetTrackingScaling", + resourceId: hasuraScalingTarget.resourceId, + scalableDimension: hasuraScalingTarget.scalableDimension, + serviceNamespace: hasuraScalingTarget.serviceNamespace, + targetTrackingScalingPolicyConfiguration: { + predefinedMetricSpecification: { + predefinedMetricType: "ECSServiceAverageMemoryUtilization", + }, + targetValue: 75.0, + scaleInCooldown: 300, + scaleOutCooldown: 60, + }, + }); + new cloudflare.Record("hasura", { name: tldjs.getSubdomain(DOMAIN) ? `hasura.${tldjs.getSubdomain(DOMAIN)}` From 91b16eabddbf650d5da08368ae3921a31a9db733 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Fri, 20 Dec 2024 16:59:09 +0100 Subject: [PATCH 10/10] fix: add `logs` column to `temp_data_migrations_audit` table and create relationship from `flow` to `lowcal_sessions` (#4102) --- hasura.planx.uk/metadata/tables.yaml | 11 ++++++++++- .../down.sql | 1 + .../up.sql | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/down.sql create mode 100644 hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/up.sql diff --git a/hasura.planx.uk/metadata/tables.yaml b/hasura.planx.uk/metadata/tables.yaml index 806ad6332e..2dfe77979c 100644 --- a/hasura.planx.uk/metadata/tables.yaml +++ b/hasura.planx.uk/metadata/tables.yaml @@ -448,6 +448,15 @@ table: name: flow_document_templates schema: public + - name: lowcal_sessions + using: + manual_configuration: + column_mapping: + id: flow_id + insertion_order: null + remote_table: + name: lowcal_sessions + schema: public - name: operations using: foreign_key_constraint_on: @@ -2468,7 +2477,7 @@ name: temp_data_migrations_audit schema: public object_relationships: - - name: flows + - name: flow using: manual_configuration: column_mapping: diff --git a/hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/down.sql b/hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/down.sql new file mode 100644 index 0000000000..bab851a271 --- /dev/null +++ b/hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/down.sql @@ -0,0 +1 @@ +alter table "public"."temp_data_migrations_audit" drop column "logs"; diff --git a/hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/up.sql b/hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/up.sql new file mode 100644 index 0000000000..3dfe458e1c --- /dev/null +++ b/hasura.planx.uk/migrations/1734705022933_alter_table_public_temp_data_migrations_audit_add_column_logs/up.sql @@ -0,0 +1 @@ +alter table "public"."temp_data_migrations_audit" add column "logs" text null;