From 08f9c5bd9156517eaaa464291977720f61541607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 18 Nov 2024 10:38:08 +0000 Subject: [PATCH 1/5] fix: Only trigger `email_user_submission_confirmation` event once per session (#3967) --- .../modules/saveAndReturn/service/utils.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/api.planx.uk/modules/saveAndReturn/service/utils.ts b/api.planx.uk/modules/saveAndReturn/service/utils.ts index c5d1c20a8a..3f985bd33a 100644 --- a/api.planx.uk/modules/saveAndReturn/service/utils.ts +++ b/api.planx.uk/modules/saveAndReturn/service/utils.ts @@ -217,17 +217,26 @@ const softDeleteSession = async (sessionId: string) => { /** * Mark a lowcal_session record as submitted + * Sends confirmation emails via Hasura event trigger "email_user_submission_confirmation" * Sessions older than 6 months cleaned up nightly by cron job sanitise_application_data on Hasura */ const markSessionAsSubmitted = async (sessionId: string) => { try { const mutation = gql` mutation MarkSessionAsSubmitted($sessionId: uuid!) { - update_lowcal_sessions_by_pk( - pk_columns: { id: $sessionId } + update_lowcal_sessions( + where: { + _and: { + id: { _eq: $sessionId } + # Only trigger email on first submission + submitted_at: { _is_null: true } + } + } _set: { submitted_at: "now()" } ) { - id + returning { + id + } } } `; From dd9c5ebe6dcd042960ae4b38b2148be1398f490f Mon Sep 17 00:00:00 2001 From: ollie <107874766+zz-hh-aa@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:59:57 +0000 Subject: [PATCH 2/5] feat: restructure analytics module (#3969) --- .../{index.test.ts => analyticsLog/analyticsLog.test.ts} | 4 ++-- .../modules/analytics/{ => analyticsLog}/controller.ts | 2 +- api.planx.uk/modules/analytics/{ => analyticsLog}/service.ts | 2 +- .../modules/analytics/metabase/collection/collection.test.ts | 1 + .../modules/analytics/metabase/collection/controller.ts | 0 api.planx.uk/modules/analytics/metabase/collection/service.ts | 0 api.planx.uk/modules/analytics/metabase/collection/types.ts | 0 .../modules/analytics/metabase/dashboard/controller.ts | 0 .../modules/analytics/metabase/dashboard/dashboard.test.ts | 1 + api.planx.uk/modules/analytics/metabase/dashboard/service.ts | 0 api.planx.uk/modules/analytics/metabase/dashboard/types.ts | 0 api.planx.uk/modules/analytics/metabase/index.ts | 0 api.planx.uk/modules/analytics/metabase/shared/client.test.ts | 1 + api.planx.uk/modules/analytics/metabase/shared/client.ts | 0 api.planx.uk/modules/analytics/metabase/shared/types.ts | 0 api.planx.uk/modules/analytics/routes.ts | 2 +- 16 files changed, 8 insertions(+), 5 deletions(-) rename api.planx.uk/modules/analytics/{index.test.ts => analyticsLog/analyticsLog.test.ts} (95%) rename api.planx.uk/modules/analytics/{ => analyticsLog}/controller.ts (89%) rename api.planx.uk/modules/analytics/{ => analyticsLog}/service.ts (96%) create mode 100644 api.planx.uk/modules/analytics/metabase/collection/collection.test.ts create mode 100644 api.planx.uk/modules/analytics/metabase/collection/controller.ts create mode 100644 api.planx.uk/modules/analytics/metabase/collection/service.ts create mode 100644 api.planx.uk/modules/analytics/metabase/collection/types.ts create mode 100644 api.planx.uk/modules/analytics/metabase/dashboard/controller.ts create mode 100644 api.planx.uk/modules/analytics/metabase/dashboard/dashboard.test.ts create mode 100644 api.planx.uk/modules/analytics/metabase/dashboard/service.ts create mode 100644 api.planx.uk/modules/analytics/metabase/dashboard/types.ts create mode 100644 api.planx.uk/modules/analytics/metabase/index.ts create mode 100644 api.planx.uk/modules/analytics/metabase/shared/client.test.ts create mode 100644 api.planx.uk/modules/analytics/metabase/shared/client.ts create mode 100644 api.planx.uk/modules/analytics/metabase/shared/types.ts diff --git a/api.planx.uk/modules/analytics/index.test.ts b/api.planx.uk/modules/analytics/analyticsLog/analyticsLog.test.ts similarity index 95% rename from api.planx.uk/modules/analytics/index.test.ts rename to api.planx.uk/modules/analytics/analyticsLog/analyticsLog.test.ts index 4112fc0dae..055a1a5847 100644 --- a/api.planx.uk/modules/analytics/index.test.ts +++ b/api.planx.uk/modules/analytics/analyticsLog/analyticsLog.test.ts @@ -1,6 +1,6 @@ import supertest from "supertest"; -import app from "../../server.js"; -import { queryMock } from "../../tests/graphqlQueryMock.js"; +import app from "../../../server.js"; +import { queryMock } from "../../../tests/graphqlQueryMock.js"; describe("Logging analytics", () => { beforeEach(() => { diff --git a/api.planx.uk/modules/analytics/controller.ts b/api.planx.uk/modules/analytics/analyticsLog/controller.ts similarity index 89% rename from api.planx.uk/modules/analytics/controller.ts rename to api.planx.uk/modules/analytics/analyticsLog/controller.ts index 5c46bfbc8b..5fecfeda7e 100644 --- a/api.planx.uk/modules/analytics/controller.ts +++ b/api.planx.uk/modules/analytics/analyticsLog/controller.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { trackAnalyticsLogExit } from "./service.js"; -import type { ValidatedRequestHandler } from "../../shared/middleware/validate.js"; +import type { ValidatedRequestHandler } from "../../../shared/middleware/validate.js"; export const logAnalyticsSchema = z.object({ query: z.object({ diff --git a/api.planx.uk/modules/analytics/service.ts b/api.planx.uk/modules/analytics/analyticsLog/service.ts similarity index 96% rename from api.planx.uk/modules/analytics/service.ts rename to api.planx.uk/modules/analytics/analyticsLog/service.ts index 413d0d971b..6d2de0ef03 100644 --- a/api.planx.uk/modules/analytics/service.ts +++ b/api.planx.uk/modules/analytics/analyticsLog/service.ts @@ -1,5 +1,5 @@ import { gql } from "graphql-request"; -import { $public } from "../../client/index.js"; +import { $public } from "../../../client/index.js"; interface UpdateAnalyticsLogUserExit { analyticsLog: { diff --git a/api.planx.uk/modules/analytics/metabase/collection/collection.test.ts b/api.planx.uk/modules/analytics/metabase/collection/collection.test.ts new file mode 100644 index 0000000000..1b66e90a88 --- /dev/null +++ b/api.planx.uk/modules/analytics/metabase/collection/collection.test.ts @@ -0,0 +1 @@ +test.todo("should test collection check and creation"); diff --git a/api.planx.uk/modules/analytics/metabase/collection/controller.ts b/api.planx.uk/modules/analytics/metabase/collection/controller.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/collection/service.ts b/api.planx.uk/modules/analytics/metabase/collection/service.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/collection/types.ts b/api.planx.uk/modules/analytics/metabase/collection/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/dashboard/controller.ts b/api.planx.uk/modules/analytics/metabase/dashboard/controller.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/dashboard/dashboard.test.ts b/api.planx.uk/modules/analytics/metabase/dashboard/dashboard.test.ts new file mode 100644 index 0000000000..799df9fcd4 --- /dev/null +++ b/api.planx.uk/modules/analytics/metabase/dashboard/dashboard.test.ts @@ -0,0 +1 @@ +test.todo("should test dashboard creation, filtering and link generation"); diff --git a/api.planx.uk/modules/analytics/metabase/dashboard/service.ts b/api.planx.uk/modules/analytics/metabase/dashboard/service.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/dashboard/types.ts b/api.planx.uk/modules/analytics/metabase/dashboard/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/index.ts b/api.planx.uk/modules/analytics/metabase/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/shared/client.test.ts b/api.planx.uk/modules/analytics/metabase/shared/client.test.ts new file mode 100644 index 0000000000..43d9c7a3f9 --- /dev/null +++ b/api.planx.uk/modules/analytics/metabase/shared/client.test.ts @@ -0,0 +1 @@ +test.todo("should test configuration and errors"); diff --git a/api.planx.uk/modules/analytics/metabase/shared/client.ts b/api.planx.uk/modules/analytics/metabase/shared/client.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/metabase/shared/types.ts b/api.planx.uk/modules/analytics/metabase/shared/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api.planx.uk/modules/analytics/routes.ts b/api.planx.uk/modules/analytics/routes.ts index 22945515be..9dcaa69010 100644 --- a/api.planx.uk/modules/analytics/routes.ts +++ b/api.planx.uk/modules/analytics/routes.ts @@ -4,7 +4,7 @@ import { logAnalyticsSchema, logUserExitController, logUserResumeController, -} from "./controller.js"; +} from "./analyticsLog/controller.js"; const router = Router(); From 49a113eabc494fc60c15e2acd761c2ed192cae1e Mon Sep 17 00:00:00 2001 From: Rory Doak <138574807+RODO94@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:22:36 +0000 Subject: [PATCH 3/5] feat: add a flow description settings section (#3954) --- api.planx.uk/package.json | 2 +- api.planx.uk/pnpm-lock.yaml | 8 +- e2e/tests/api-driven/package.json | 2 +- e2e/tests/api-driven/pnpm-lock.yaml | 8 +- e2e/tests/ui-driven/package.json | 2 +- e2e/tests/ui-driven/pnpm-lock.yaml | 8 +- editor.planx.uk/package.json | 2 +- editor.planx.uk/pnpm-lock.yaml | 10 +-- .../FlowDescription/FlowDescription.tsx | 83 +++++++++++++++++++ .../ServiceSettings/FlowStatus/index.tsx | 4 +- .../FooterLinksAndLegalDisclaimer.tsx | 4 +- .../Settings/ServiceSettings/index.tsx | 2 + .../pages/FlowEditor/lib/store/settings.ts | 57 +++++++++++++ editor.planx.uk/src/pages/FlowEditor/utils.ts | 13 +++ .../src/routes/serviceSettings.tsx | 46 +--------- 15 files changed, 182 insertions(+), 69 deletions(-) create mode 100644 editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowDescription/FlowDescription.tsx diff --git a/api.planx.uk/package.json b/api.planx.uk/package.json index 88de652132..603a4117b8 100644 --- a/api.planx.uk/package.json +++ b/api.planx.uk/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@airbrake/node": "^2.1.8", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#706410d", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#ccf9ac3", "@types/isomorphic-fetch": "^0.0.36", "adm-zip": "^0.5.10", "aws-sdk": "^2.1467.0", diff --git a/api.planx.uk/pnpm-lock.yaml b/api.planx.uk/pnpm-lock.yaml index 88901bf52a..a15a671b66 100644 --- a/api.planx.uk/pnpm-lock.yaml +++ b/api.planx.uk/pnpm-lock.yaml @@ -14,8 +14,8 @@ dependencies: specifier: ^2.1.8 version: 2.1.8 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#706410d - version: github.com/theopensystemslab/planx-core/706410d + specifier: git+https://github.com/theopensystemslab/planx-core#ccf9ac3 + version: github.com/theopensystemslab/planx-core/ccf9ac3 '@types/isomorphic-fetch': specifier: ^0.0.36 version: 0.0.36 @@ -6308,8 +6308,8 @@ packages: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false - github.com/theopensystemslab/planx-core/706410d: - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/706410d} + github.com/theopensystemslab/planx-core/ccf9ac3: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/ccf9ac3} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/e2e/tests/api-driven/package.json b/e2e/tests/api-driven/package.json index 41bd559f89..074c8c301b 100644 --- a/e2e/tests/api-driven/package.json +++ b/e2e/tests/api-driven/package.json @@ -7,7 +7,7 @@ "packageManager": "pnpm@8.6.6", "dependencies": { "@cucumber/cucumber": "^9.3.0", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#706410d", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#ccf9ac3", "axios": "^1.7.4", "dotenv": "^16.3.1", "dotenv-expand": "^10.0.0", diff --git a/e2e/tests/api-driven/pnpm-lock.yaml b/e2e/tests/api-driven/pnpm-lock.yaml index 5d97ed77fc..734fbe02ee 100644 --- a/e2e/tests/api-driven/pnpm-lock.yaml +++ b/e2e/tests/api-driven/pnpm-lock.yaml @@ -9,8 +9,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#706410d - version: github.com/theopensystemslab/planx-core/706410d + specifier: git+https://github.com/theopensystemslab/planx-core#ccf9ac3 + version: github.com/theopensystemslab/planx-core/ccf9ac3 axios: specifier: ^1.7.4 version: 1.7.4 @@ -2877,8 +2877,8 @@ packages: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false - github.com/theopensystemslab/planx-core/706410d: - resolution: {commit: 706410d, repo: git+ssh://git@github.com/theopensystemslab/planx-core.git, type: git} + github.com/theopensystemslab/planx-core/ccf9ac3: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/ccf9ac3} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/e2e/tests/ui-driven/package.json b/e2e/tests/ui-driven/package.json index 99a7d2545b..03e2203caf 100644 --- a/e2e/tests/ui-driven/package.json +++ b/e2e/tests/ui-driven/package.json @@ -8,7 +8,7 @@ "postinstall": "./install-dependencies.sh" }, "dependencies": { - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#706410d", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#ccf9ac3", "axios": "^1.7.4", "dotenv": "^16.3.1", "eslint": "^8.56.0", diff --git a/e2e/tests/ui-driven/pnpm-lock.yaml b/e2e/tests/ui-driven/pnpm-lock.yaml index bb2dfab4e6..136d2446e3 100644 --- a/e2e/tests/ui-driven/pnpm-lock.yaml +++ b/e2e/tests/ui-driven/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#706410d - version: github.com/theopensystemslab/planx-core/706410d + specifier: git+https://github.com/theopensystemslab/planx-core#ccf9ac3 + version: github.com/theopensystemslab/planx-core/ccf9ac3 axios: specifier: ^1.7.4 version: 1.7.4 @@ -2652,8 +2652,8 @@ packages: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: false - github.com/theopensystemslab/planx-core/706410d: - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/706410d} + github.com/theopensystemslab/planx-core/ccf9ac3: + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/ccf9ac3} name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json index 839432be7c..0cecf5714c 100644 --- a/editor.planx.uk/package.json +++ b/editor.planx.uk/package.json @@ -15,7 +15,7 @@ "@mui/material": "^5.15.10", "@mui/utils": "^5.15.11", "@opensystemslab/map": "1.0.0-alpha.4", - "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#706410d", + "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#ccf9ac3", "@tiptap/core": "^2.4.0", "@tiptap/extension-bold": "^2.0.3", "@tiptap/extension-bubble-menu": "^2.1.13", diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml index 716ed3832e..ea740f4b84 100644 --- a/editor.planx.uk/pnpm-lock.yaml +++ b/editor.planx.uk/pnpm-lock.yaml @@ -47,8 +47,8 @@ dependencies: specifier: 1.0.0-alpha.4 version: 1.0.0-alpha.4 '@opensystemslab/planx-core': - specifier: git+https://github.com/theopensystemslab/planx-core#706410d - version: github.com/theopensystemslab/planx-core/706410d(@types/react@18.2.45) + specifier: git+https://github.com/theopensystemslab/planx-core#ccf9ac3 + version: github.com/theopensystemslab/planx-core/ccf9ac3(@types/react@18.2.45) '@tiptap/core': specifier: ^2.4.0 version: 2.4.0(@tiptap/pm@2.0.3) @@ -15433,9 +15433,9 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: false - github.com/theopensystemslab/planx-core/706410d(@types/react@18.2.45): - resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/706410d} - id: github.com/theopensystemslab/planx-core/706410d + github.com/theopensystemslab/planx-core/ccf9ac3(@types/react@18.2.45): + resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/ccf9ac3} + id: github.com/theopensystemslab/planx-core/ccf9ac3 name: '@opensystemslab/planx-core' version: 1.0.0 prepare: true diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowDescription/FlowDescription.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowDescription/FlowDescription.tsx new file mode 100644 index 0000000000..8bd43f5c0a --- /dev/null +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowDescription/FlowDescription.tsx @@ -0,0 +1,83 @@ +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { useFormik } from "formik"; +import { useToast } from "hooks/useToast"; +import React from "react"; +import InputLabel from "ui/editor/InputLabel"; +import SettingsSection from "ui/editor/SettingsSection"; +import Input from "ui/shared/Input/Input"; + +import { useStore } from "../../../../lib/store"; +import { SettingsForm } from "../../shared/SettingsForm"; + +const FlowDescription = () => { + const [flowDescription, updateFlowDescription] = useStore((state) => [ + state.flowDescription, + state.updateFlowDescription, + ]); + + const toast = useToast(); + + const formik = useFormik<{ description: string }>({ + initialValues: { + description: flowDescription || "", + }, + onSubmit: async (values, { resetForm }) => { + const isSuccess = await updateFlowDescription(values.description); + if (isSuccess) { + toast.success("Description updated successfully"); + resetForm({ values }); + } + if (!isSuccess) { + formik.setFieldError( + "description", + "We are unable to update the service description, check your internet connection and try again", + ); + } + }, + validateOnBlur: false, + validateOnChange: false, + enableReinitialize: true, + }); + + return ( + + + + Service Information + + + Useful information about this service. + + + + A short blurb on what this service is, how it should be used, and if + there are any dependencies related to this service. + + } + input={ + <> + + { + formik.setFieldValue("description", event.target.value); + }} + value={formik.values.description ?? ""} + errorMessage={formik.errors.description} + id="description" + /> + + + } + /> + + ); +}; + +export default FlowDescription; diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx index 89fff855f4..404de65fe3 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FlowStatus/index.tsx @@ -1,6 +1,5 @@ import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import { formControlLabelClasses } from "@mui/material/FormControlLabel"; import Typography from "@mui/material/Typography"; import type { FlowStatus } from "@opensystemslab/planx-core/types"; import axios from "axios"; @@ -8,7 +7,6 @@ import { useFormik } from "formik"; import { useToast } from "hooks/useToast"; import React from "react"; import { rootFlowPath } from "routes/utils"; -import { FONT_WEIGHT_BOLD } from "theme"; import SettingsDescription from "ui/editor/SettingsDescription"; import SettingsSection from "ui/editor/SettingsSection"; import { Switch } from "ui/shared/Switch"; @@ -92,7 +90,7 @@ const FlowStatus = () => { }; return ( - + Status diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FooterLinksAndLegalDisclaimer.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FooterLinksAndLegalDisclaimer.tsx index 1eee43ae02..268e05aaeb 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FooterLinksAndLegalDisclaimer.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/FooterLinksAndLegalDisclaimer.tsx @@ -1,12 +1,11 @@ import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import { formControlLabelClasses } from "@mui/material/FormControlLabel"; +// eslint-disable-next-line no-restricted-imports import { SwitchProps } from "@mui/material/Switch"; import Typography from "@mui/material/Typography"; import { useFormik } from "formik"; import { useToast } from "hooks/useToast"; import React from "react"; -import { FONT_WEIGHT_BOLD } from "theme"; import InputGroup from "ui/editor/InputGroup"; import InputLegend from "ui/editor/InputLegend"; import RichTextInput from "ui/editor/RichTextInput/RichTextInput"; @@ -112,7 +111,6 @@ export const FooterLinksAndLegalDisclaimer = () => { }, validate: () => {}, }); - return ( diff --git a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/index.tsx b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/index.tsx index dde243b1b3..70a630ac02 100644 --- a/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/components/Settings/ServiceSettings/index.tsx @@ -1,6 +1,7 @@ import Container from "@mui/material/Container"; import React from "react"; +import FlowDescription from "./FlowDescription/FlowDescription"; import FlowStatus from "./FlowStatus"; import { FooterLinksAndLegalDisclaimer } from "./FooterLinksAndLegalDisclaimer"; @@ -8,6 +9,7 @@ const ServiceSettings: React.FC = () => ( + ); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/store/settings.ts b/editor.planx.uk/src/pages/FlowEditor/lib/store/settings.ts index 74ef341a0e..2013856b67 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/store/settings.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/settings.ts @@ -2,6 +2,7 @@ import { gql } from "@apollo/client"; import { FlowStatus } from "@opensystemslab/planx-core/types"; import camelcaseKeys from "camelcase-keys"; import { client } from "lib/graphql"; +import { FlowInformation, GetFlowInformation } from "pages/FlowEditor/utils"; import { AdminPanelData, FlowSettings, @@ -14,11 +15,18 @@ import { SharedStore } from "./shared"; import { TeamStore } from "./team"; export interface SettingsStore { + getFlowInformation: ( + flowSlug: string, + teamSlug: string, + ) => Promise; flowSettings?: FlowSettings; setFlowSettings: (flowSettings?: FlowSettings) => void; flowStatus?: FlowStatus; setFlowStatus: (flowStatus: FlowStatus) => void; updateFlowStatus: (newStatus: FlowStatus) => Promise; + flowDescription?: string; + setFlowDescription: (flowDescription: string) => void; + updateFlowDescription: (newDescription: string) => Promise; globalSettings?: GlobalSettings; setGlobalSettings: (globalSettings: GlobalSettings) => void; updateFlowSettings: (newSettings: FlowSettings) => Promise; @@ -51,6 +59,55 @@ export const settingsStore: StateCreator< return Boolean(result?.id); }, + flowDescription: "", + + setFlowDescription: (flowDescription: string) => set({ flowDescription }), + + updateFlowDescription: async (newDescription: string) => { + const { id, $client } = get(); + const result = await $client.flow.setDescription({ + flow: { id }, + description: newDescription, + }); + set({ flowDescription: newDescription }); + return Boolean(result?.id); + }, + + getFlowInformation: async (flowSlug, teamSlug) => { + const { + data: { + flows: [{ settings, status, description }], + }, + } = await client.query({ + query: gql` + query GetFlow($slug: String!, $team_slug: String!) { + flows( + limit: 1 + where: { slug: { _eq: $slug }, team: { slug: { _eq: $team_slug } } } + ) { + id + settings + description + status + } + } + `, + variables: { + slug: flowSlug, + team_slug: teamSlug, + }, + fetchPolicy: "no-cache", + }); + + set({ + flowSettings: settings, + flowStatus: status, + flowDescription: description, + }); + + return { settings, status, description }; + }, + globalSettings: undefined, setGlobalSettings: (globalSettings) => { diff --git a/editor.planx.uk/src/pages/FlowEditor/utils.ts b/editor.planx.uk/src/pages/FlowEditor/utils.ts index a9f28b54ee..99282fc212 100644 --- a/editor.planx.uk/src/pages/FlowEditor/utils.ts +++ b/editor.planx.uk/src/pages/FlowEditor/utils.ts @@ -1,4 +1,17 @@ +import { FlowStatus } from "@opensystemslab/planx-core/types"; import { formatDistanceToNow } from "date-fns"; +import { FlowSettings } from "types"; + +export interface FlowInformation { + settings: FlowSettings; + status: FlowStatus; + description: string; +} + +export interface GetFlowInformation { + id: string; + flows: FlowInformation[]; +} export const formatLastEditDate = (date: string): string => { return formatDistanceToNow(new Date(date), { diff --git a/editor.planx.uk/src/routes/serviceSettings.tsx b/editor.planx.uk/src/routes/serviceSettings.tsx index fb8f223999..b691bc8a89 100644 --- a/editor.planx.uk/src/routes/serviceSettings.tsx +++ b/editor.planx.uk/src/routes/serviceSettings.tsx @@ -1,48 +1,10 @@ -import { gql } from "@apollo/client"; -import { FlowStatus } from "@opensystemslab/planx-core/types"; -import { compose, mount, NaviRequest, route, withData } from "navi"; +import { compose, mount, route, withData } from "navi"; import ServiceSettings from "pages/FlowEditor/components/Settings/ServiceSettings"; +import { useStore } from "pages/FlowEditor/lib/store"; -import { client } from "../lib/graphql"; -import { useStore } from "../pages/FlowEditor/lib/store"; -import type { FlowSettings } from "../types"; import { makeTitle } from "./utils"; -interface GetFlowSettings { - flows: { - id: string; - settings: FlowSettings; - status: FlowStatus; - }[]; -} - -export const getFlowSettings = async (req: NaviRequest) => { - const { - data: { - flows: [{ settings, status }], - }, - } = await client.query({ - query: gql` - query GetFlow($slug: String!, $team_slug: String!) { - flows( - limit: 1 - where: { slug: { _eq: $slug }, team: { slug: { _eq: $team_slug } } } - ) { - id - settings - status - } - } - `, - variables: { - slug: req.params.flow, - team_slug: req.params.team, - }, - }); - - useStore.getState().setFlowSettings(settings); - useStore.getState().setFlowStatus(status); -}; +const getFlowInformation = useStore.getState().getFlowInformation; const serviceSettingsRoutes = compose( withData((req) => ({ @@ -53,7 +15,7 @@ const serviceSettingsRoutes = compose( mount({ "/": compose( route(async (req) => ({ - getData: await getFlowSettings(req), + getData: await getFlowInformation(req.params.flow, req.params.team), title: makeTitle( [req.params.team, req.params.flow, "service"].join("/"), ), From b66cd6fcc453625c2c7c546cf749b6ae73f27831 Mon Sep 17 00:00:00 2001 From: Jo Humphrey <31373245+jamdelion@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:55:09 +0000 Subject: [PATCH 4/5] chore: finalise Barking and Dagenham subdomain setup (#3976) --- editor.planx.uk/src/airbrake.ts | 1 + editor.planx.uk/src/routes/utils.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/editor.planx.uk/src/airbrake.ts b/editor.planx.uk/src/airbrake.ts index c7797e9366..a0693a4d7d 100644 --- a/editor.planx.uk/src/airbrake.ts +++ b/editor.planx.uk/src/airbrake.ts @@ -17,6 +17,7 @@ function getEnvForAllowedHosts(host: string) { case "planningservices.gateshead.gov.uk": case "planningservices.gloucester.gov.uk": case "planningservices.lambeth.gov.uk": + case "planningservices.lbbd.gov.uk": case "planningservices.medway.gov.uk": case "planningservices.newcastle.gov.uk": case "planningservices.southwark.gov.uk": diff --git a/editor.planx.uk/src/routes/utils.ts b/editor.planx.uk/src/routes/utils.ts index c14aed17aa..eabdff55ba 100644 --- a/editor.planx.uk/src/routes/utils.ts +++ b/editor.planx.uk/src/routes/utils.ts @@ -49,14 +49,15 @@ export const setPath = (flowData: Store.Flow, req: NaviRequest) => { // So I've hard-coded these domain names until a better solution comes along. // const PREVIEW_ONLY_DOMAINS = [ - "planningservices.epsom-ewell.gov.uk", "planningservices.barnet.gov.uk", "planningservices.buckinghamshire.gov.uk", "planningservices.camden.gov.uk", "planningservices.doncaster.gov.uk", + "planningservices.epsom-ewell.gov.uk", "planningservices.gateshead.gov.uk", "planningservices.gloucester.gov.uk", "planningservices.lambeth.gov.uk", + "planningservices.lbbd.gov.uk", "planningservices.medway.gov.uk", "planningservices.newcastle.gov.uk", "planningservices.southwark.gov.uk", From 181b245466d92db0bd9364b93e8896b060d2e97c Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Tue, 19 Nov 2024 11:33:11 +0100 Subject: [PATCH 5/5] chore: retire custom ESRI GIS integrations (#3977) --- api.planx.uk/modules/gis/service/helpers.js | 213 ------ .../modules/gis/service/helpers.test.js | 315 --------- api.planx.uk/modules/gis/service/helpers.ts | 33 + api.planx.uk/modules/gis/service/index.js | 55 +- .../modules/gis/service/index.test.ts | 66 -- .../service/local_authorities/braintree.js | 147 ----- .../local_authorities/metadata/braintree.js | 202 ------ .../local_authorities/metadata/scotland.js | 104 --- .../gis/service/local_authorities/scotland.js | 119 ---- .../fetching-direct-gis-data.ed67b9a5.json | 614 ------------------ .../components/PlanningConstraints/Public.tsx | 40 +- 11 files changed, 51 insertions(+), 1857 deletions(-) delete mode 100644 api.planx.uk/modules/gis/service/helpers.js delete mode 100644 api.planx.uk/modules/gis/service/helpers.test.js create mode 100644 api.planx.uk/modules/gis/service/helpers.ts delete mode 100644 api.planx.uk/modules/gis/service/local_authorities/braintree.js delete mode 100644 api.planx.uk/modules/gis/service/local_authorities/metadata/braintree.js delete mode 100644 api.planx.uk/modules/gis/service/local_authorities/metadata/scotland.js delete mode 100644 api.planx.uk/modules/gis/service/local_authorities/scotland.js delete mode 100644 api.planx.uk/tests/nocks/fetching-direct-gis-data.ed67b9a5.json diff --git a/api.planx.uk/modules/gis/service/helpers.js b/api.planx.uk/modules/gis/service/helpers.js deleted file mode 100644 index 914a7ad0bb..0000000000 --- a/api.planx.uk/modules/gis/service/helpers.js +++ /dev/null @@ -1,213 +0,0 @@ -// Source name used in metadata templates for pre-checked/human-verified data sources -const PRECHECKED_SOURCE = "manual"; - -// Set the geometry type to polygon if we have a valid site boundary polygon drawing, -// else do an envelope query using the buffered address point -// Ref https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm -const setEsriGeometryType = (siteBoundary = []) => { - return siteBoundary.length === 0 - ? "esriGeometryEnvelope" - : "esriGeometryPolygon"; -}; - -// Set the geometry object based on our geometry type -// Ref https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm -const setEsriGeometry = (geometryType, x, y, radius, siteBoundary) => { - return geometryType === "esriGeometryEnvelope" - ? bufferPoint(x, y, radius) - : JSON.stringify({ - rings: siteBoundary, - spatialReference: { - wkid: 4326, - }, - }); -}; - -// Build up the URL used to query an ESRI feature -// Ref https://developers.arcgis.com/rest/services-reference/enterprise/query-feature-service-.htm -const makeEsriUrl = (domain, id, serverIndex = 0, overrideParams = {}) => { - let url = `${domain}/rest/services/${id}/MapServer/${serverIndex}/query`; - - const defaultParams = { - where: "1=1", - geometryType: "esriGeometryEnvelope", - inSR: 27700, - spatialRel: "esriSpatialRelIntersects", - returnGeometry: false, - outSR: 4326, - f: "json", - outFields: [], - geometry: [], - }; - - const params = { ...defaultParams, ...overrideParams }; - - if (Array.isArray(params.outFields)) - params.outFields = params.outFields.join(","); - if (Array.isArray(params.geometry)) - params.geometry = params.geometry.join(","); - - url = [ - url, - Object.keys(params) - .map((key) => key + "=" + escape(params[key])) - .join("&"), - ].join("?"); - - return url; -}; - -// Buffer an address point as a proxy for curtilage -const bufferPoint = (x, y, radius = 0.05) => { - return [x - radius, y + radius, x + radius, y - radius]; -}; - -// Build a bbox (string) around a point -const makeBbox = (x, y, radius = 1.5) => { - return `${x - radius},${y - radius},${x + radius},${y + radius}`; -}; - -// For a dictionary of planning constraint objects, return the items with preset { value: false } aka unknown data source -const getFalseConstraints = (metadata) => { - let falseConstraints = {}; - Object.keys(metadata).forEach((constraint) => { - if (metadata[constraint].value === false) { - falseConstraints[constraint] = { value: false }; - } - }); - - return falseConstraints; -}; - -// For a dictionary of planning constraint objects, return the items with known data sources -const getQueryableConstraints = (metadata) => { - let queryableConstraints = {}; - Object.keys(metadata).forEach((constraint) => { - if ( - "source" in metadata[constraint] && - metadata[constraint]["source"] !== PRECHECKED_SOURCE - ) { - queryableConstraints[constraint] = metadata[constraint]; - } - }); - - return queryableConstraints; -}; - -// For a dictionary of planning constraint objects, return the items that have been manually verified and do not apply to this geographic region -const getManualConstraints = (metadata) => { - let manualConstraints = {}; - Object.keys(metadata).forEach((constraint) => { - if ( - "source" in metadata[constraint] && - metadata[constraint]["source"] === PRECHECKED_SOURCE - ) { - // Make object shape consistent with queryable data sources - delete metadata[constraint]["source"]; - delete metadata[constraint]["key"]; - - metadata[constraint]["text"] = metadata[constraint]["neg"]; - delete metadata[constraint]["neg"]; - - metadata[constraint]["value"] = false; - metadata[constraint]["type"] = "check"; - metadata[constraint]["data"] = []; - - manualConstraints[constraint] = metadata[constraint]; - } - }); - - return manualConstraints; -}; - -// Adds "designated" variable to response object, so we can auto-answer less granular questions like "are you on designated land" -const addDesignatedVariable = (responseObject) => { - const resObjWithDesignated = { - ...responseObject, - designated: { value: false }, - }; - - const subVariables = ["conservationArea", "AONB", "nationalPark", "WHS"]; - - // If any of the subvariables are true, then set "designated" to true - subVariables.forEach((s) => { - if (resObjWithDesignated[`designated.${s}`]?.value) { - resObjWithDesignated["designated"] = { value: true }; - } - }); - - // Ensure that our response includes all the expected subVariables before returning "designated" - // so we don't incorrectly auto-answer any questions for individual layer queries that may have failed - let subVariablesFound = 0; - Object.keys(responseObject).forEach((key) => { - if (key.startsWith(`designated.`)) { - subVariablesFound++; - } - }); - - if (subVariablesFound < subVariables.length) { - return responseObject; - } else { - return resObjWithDesignated; - } -}; - -// Squash multiple layers into a single result -const squashResultLayers = (originalOb, layers, layerName) => { - const ob = { ...originalOb }; - // Check to see if we have any intersections - const match = layers.find((layer) => ob[layer].value); - // If we do, return this as the result. Otherwise take the first (negative) value. - ob[layerName] = match ? ob[match] : ob[layers[0]]; - // Tidy up the redundant layers - layers.forEach((layer) => delete ob[layer]); - return ob; -}; - -// Rollup multiple layers into a single result, whilst preserving granularity -const rollupResultLayers = (originalOb, layers, layerName) => { - const ob = { ...originalOb }; - const granularLayers = layers.filter((layer) => layer != layerName); - - if (ob[layerName]?.value) { - // If the parent layer is in the original object & intersects, preserve all properties for rendering PlanningConstraints and debugging - // ob[layerName] = ob[layerName]; - } else { - // Check to see if any granular layers intersect - const match = granularLayers.find((layer) => ob[layer].value); - // If there is a granular match, set it as the parent result. Otherwise take the first (negative) value - ob[layerName] = match ? ob[match] : ob[layers[0]]; - } - - // Return a simple view of the granular layers to avoid duplicate PlanningConstraint entries - granularLayers.forEach((layer) => (ob[layer] = { value: ob[layer].value })); - - return ob; -}; - -// Handle Article 4 subvariables -// Return an object with a simple result for each A4 subvariable -const getA4Subvariables = (features, articleFours, a4Key) => { - const result = {}; - const a4Keys = features.map((feature) => feature.attributes[a4Key]); - Object.entries(articleFours).forEach(([key, value]) => { - const isMatch = a4Keys.includes(value); - result[key] = { value: isMatch }; - }); - return result; -}; - -export { - setEsriGeometryType, - setEsriGeometry, - makeEsriUrl, - bufferPoint, - makeBbox, - getQueryableConstraints, - getFalseConstraints, - getManualConstraints, - squashResultLayers, - rollupResultLayers, - getA4Subvariables, - addDesignatedVariable, -}; diff --git a/api.planx.uk/modules/gis/service/helpers.test.js b/api.planx.uk/modules/gis/service/helpers.test.js deleted file mode 100644 index 6e6179cebc..0000000000 --- a/api.planx.uk/modules/gis/service/helpers.test.js +++ /dev/null @@ -1,315 +0,0 @@ -import { - squashResultLayers, - rollupResultLayers, - getA4Subvariables, -} from "./helpers.js"; - -describe("squashResultLayer helper function", () => { - test("It should squash the list of layers passed in", () => { - // Arrange - const input = { - "tpo.tenMeterBuffer": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.areas": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.woodland": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.group": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - }; - const layersToSquash = Object.keys(input); - const key = "tpo"; - - // Act - const result = squashResultLayers(input, layersToSquash, key); - - // Assert - expect(result).toHaveProperty(key); - layersToSquash.forEach((layer) => expect(result).not.toHaveProperty(layer)); - }); - - test("It should correctly squash layers based on their value", () => { - // Arrange - const input1 = { - "tpo.tenMeterBuffer": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.areas": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: true, // We expect our test to pick this up - type: "check", - data: { - OBJECTID: 123, - }, - }, - "tpo.woodland": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.group": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - }; - const input2 = { - "tpo.tenMeterBuffer": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: { - OBJECTID: "ABC", // We expect our test to pick this up - }, - }, - "tpo.areas": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.woodland": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - "tpo.group": { - text: "is not in a TPO (Tree Preservation Order) Zone", - value: false, - type: "check", - data: {}, - }, - }; - const layersToSquash1 = Object.keys(input1); - const layersToSquash2 = Object.keys(input2); - const key = "tpo"; - - // Act - const result1 = squashResultLayers(input1, layersToSquash1, key); - const result2 = squashResultLayers(input2, layersToSquash2, key); - - // Assert - expect(result1[key].value).toBe(true); - expect(result1[key]).toMatchObject(input1["tpo.areas"]); - expect(result2[key].value).toBe(false); - expect(result2[key]).toMatchObject(input2["tpo.tenMeterBuffer"]); - }); -}); - -describe("rollupResultLayer helper function", () => { - test("It should roll up the list of layers passed in", () => { - // Arrange - const input = { - "listed.grade1": { - text: "is not in, or within, a Listed Building", - value: false, - type: "check", - data: {}, - }, - "listed.grade2": { - text: "is, or is within, a Listed Building (Grade 2)", - description: null, - value: false, - type: "warning", - data: {}, - }, - "listed.grade2star": { - text: "is not in, or within, a Listed Building", - value: false, - type: "check", - data: {}, - }, - }; - const layersToRollup = Object.keys(input); - const key = "listed"; - - // Act - const result = rollupResultLayers(input, layersToRollup, key); - - // Assert - expect(result).toHaveProperty(key); - layersToRollup.forEach((layer) => { - expect(result).toHaveProperty([layer]); - expect(result[layer]).toMatchObject({ value: false }); - }); - }); - - test("It should correctly roll up layers which have a match", () => { - // Arrange - const input = { - "listed.grade1": { - text: "is not in, or within, a Listed Building", - value: false, - type: "check", - data: {}, - }, - "listed.grade2": { - text: "is, or is within, a Listed Building (Grade 2)", - description: null, - value: true, - type: "warning", - data: { - OBJECTID: 3398, - }, - }, - "listed.grade2star": { - text: "is not in, or within, a Listed Building", - value: false, - type: "check", - data: {}, - }, - }; - const layersToRollup = Object.keys(input); - const key = "listed"; - - // Act - const result = rollupResultLayers(input, layersToRollup, key); - - // Assert - expect(result[key]).toMatchObject(input["listed.grade2"]); - }); - - test("It should correctly roll up layers which have do not have a match", () => { - // Arrange - const input = { - "listed.grade1": { - text: "is not in, or within, a Listed Building", - value: false, - type: "check", - data: {}, - }, - "listed.grade2": { - text: "is, or is within, a Listed Building (Grade 2)", - description: null, - value: false, - type: "warning", - data: {}, - }, - "listed.grade2star": { - text: "is not in, or within, a Listed Building", - value: false, - type: "check", - data: {}, - }, - }; - const layersToRollup = Object.keys(input); - const key = "listed"; - - // Act - const result = rollupResultLayers(input, layersToRollup, key); - - // Assert - expect(result[key]).toMatchObject(input["listed.grade1"]); - }); - - test("It should correctly rollup when the layerName matches a provided layer", () => { - // Arrange - const input = { - article4: { - text: "is subject to local permitted development restrictions (known as Article 4 directions)", - description: - "BLACKFRIARS STREET 28,29,30, KING STREET 10 TO 15, MILL LANE 19,20", - value: true, - type: "warning", - data: { - OBJECTID: 72963, - REF: "Article 4 Direction 1985", - LOCATION_1: - "BLACKFRIARS STREET 28,29,30, KING STREET 10 TO 15, MILL LANE 19,20", - DESCRIPTIO: "Effective 29 November 1985", - }, - }, - "article4.canterbury.hmo": { - text: "is subject to local permitted development restrictions (known as Article 4 directions)", - description: "Canterbury and surrounding area", - value: true, - type: "warning", - data: { - OBJECTID: 73412, - REF: "The Canterbury HMO Article 4 D", - LOCATION_1: "Canterbury and surrounding area", - DESCRIPTIO: "Effective 25 February 2016", - }, - }, - }; - const layersToRollup = Object.keys(input); - const key = "article4"; - - // Act - const result = rollupResultLayers(input, layersToRollup, key); - - // Assert - expect(result[key]).toMatchObject(input["article4"]); // parent key maintains all original properties - expect(result["article4.canterbury.hmo"]).toMatchObject({ value: true }); // granular key is simplified - }); -}); - -describe("getA4Subvariables helper function", () => { - const A4_KEY = "OBJECTID"; - const articleFours = { - "article4.test.a": 1, - "article4.test.b": 5, - "article4.test.c": 13, - }; - it("returns a property for each Article 4 passed in", () => { - // Arrange - const features = [ - { attributes: { OBJECTID: 1, name: "Hackney Road" } }, - { attributes: { OBJECTID: 5, name: "Peckham Way" } }, - { attributes: { OBJECTID: 13, name: "Chelsea Park" } }, - ]; - // Act - const result = getA4Subvariables(features, articleFours, A4_KEY); - // Assert - Object.keys(articleFours).forEach((key) => - expect(result).toHaveProperty([key]), - ); - }); - - it("correctly matches features which have a hit on an Article 4", () => { - // Arrange - const features = [ - { attributes: { OBJECTID: 1, name: "Hackney Road" } }, - { attributes: { OBJECTID: 13, name: "Chelsea Park" } }, - ]; - // Act - const result = getA4Subvariables(features, articleFours, A4_KEY); - // Assert - expect(result).toMatchObject({ - ["article4.test.a"]: { value: true }, - ["article4.test.b"]: { value: false }, - ["article4.test.c"]: { value: true }, - }); - }); - - it("handles no matching Article 4 results", () => { - const result = getA4Subvariables([], articleFours, A4_KEY); - expect(result).toMatchObject({ - ["article4.test.a"]: { value: false }, - ["article4.test.b"]: { value: false }, - ["article4.test.c"]: { value: false }, - }); - }); -}); diff --git a/api.planx.uk/modules/gis/service/helpers.ts b/api.planx.uk/modules/gis/service/helpers.ts new file mode 100644 index 0000000000..4f62cf3c11 --- /dev/null +++ b/api.planx.uk/modules/gis/service/helpers.ts @@ -0,0 +1,33 @@ +// Adds "designated" variable to response object, so we can auto-answer less granular questions like "are you on designated land" +const addDesignatedVariable = (responseObject: any) => { + const resObjWithDesignated = { + ...responseObject, + designated: { value: false }, + }; + + const subVariables = ["conservationArea", "AONB", "nationalPark", "WHS"]; + + // If any of the subvariables are true, then set "designated" to true + subVariables.forEach((s) => { + if (resObjWithDesignated[`designated.${s}`]?.value) { + resObjWithDesignated["designated"] = { value: true }; + } + }); + + // Ensure that our response includes all the expected subVariables before returning "designated" + // so we don't incorrectly auto-answer any questions for individual layer queries that may have failed + let subVariablesFound = 0; + Object.keys(responseObject).forEach((key) => { + if (key.startsWith(`designated.`)) { + subVariablesFound++; + } + }); + + if (subVariablesFound < subVariables.length) { + return responseObject; + } else { + return resObjWithDesignated; + } +}; + +export { addDesignatedVariable }; diff --git a/api.planx.uk/modules/gis/service/index.js b/api.planx.uk/modules/gis/service/index.js index da53b7b081..6332259c10 100644 --- a/api.planx.uk/modules/gis/service/index.js +++ b/api.planx.uk/modules/gis/service/index.js @@ -1,8 +1,6 @@ import * as digitalLand from "./digitalLand.js"; -import * as scotland from "./local_authorities/scotland.js"; -import * as braintree from "./local_authorities/braintree.js"; -const localAuthorities = { digitalLand, braintree, scotland }; +const localAuthorities = { digitalLand }; /** * @swagger @@ -22,11 +20,10 @@ const localAuthorities = { digitalLand, braintree, scotland }; * description: Well-Known Text (WKT) formatted polygon or point */ export async function locationSearch(req, res, next) { - // 'geom' param signals this localAuthority has data available via DigitalLand, "ready" teams are configured in PlanningConstraints component in editor - // swagger doc intentionally doesn't cover legacy custom GIS hookups for Braintree and Scotland demo + // 'geom' param signals this localAuthority has data available via Planning Data if (req.query.geom) { try { - const resp = await locationSearchWithoutTimeout( + const resp = await locationSearchViaPlanningData( req.params.localAuthority, req.query, ); @@ -37,22 +34,6 @@ export async function locationSearch(req, res, next) { message: err ? err : "unknown error", }); } - // check if this local authority is supported via our custom GIS hookup - } else if (localAuthorities[req.params.localAuthority]) { - try { - const timeout = Number(process.env.TIMEOUT_DURATION) || 15000; - const resp = await locationSearchWithTimeout( - req.params.localAuthority, - req.query, - timeout, - ); - res.send(resp); - } catch (err) { - next({ - status: 500, - message: err ? err : "unknown error", - }); - } } else { next({ status: 400, @@ -61,37 +42,11 @@ export async function locationSearch(req, res, next) { } } -// Digital Land is a single request with standardized geometry, so remove timeout & simplify query params -export function locationSearchWithoutTimeout(localAuthority, queryParams) { +// Planning Data is a single request with standardized geometry, so timeout is not necessary +export function locationSearchViaPlanningData(localAuthority, queryParams) { return localAuthorities["digitalLand"].locationSearch( localAuthority, queryParams.geom, queryParams, ); } - -// custom GIS hookups require many requests to individual data layers which are more likely to timeout -export function locationSearchWithTimeout( - localAuthority, - { x, y, siteBoundary, extras = "{}" }, - time, -) { - let extraInfo = extras; - extraInfo = JSON.parse(unescape(extras)); - - const timeout = new Promise((resolve, reject) => { - const timeoutID = setTimeout(() => { - clearTimeout(timeoutID); - reject("location search timeout"); - }, time); - }); - - const promise = localAuthorities[localAuthority].locationSearch( - parseInt(x, 10), - parseInt(y, 10), - JSON.parse(siteBoundary), - extraInfo, - ); - - return Promise.race([promise, timeout]); -} diff --git a/api.planx.uk/modules/gis/service/index.test.ts b/api.planx.uk/modules/gis/service/index.test.ts index 6b6944bc5f..e35fff3f25 100644 --- a/api.planx.uk/modules/gis/service/index.test.ts +++ b/api.planx.uk/modules/gis/service/index.test.ts @@ -2,72 +2,6 @@ import supertest from "supertest"; import loadOrRecordNockRequests from "../../../tests/loadOrRecordNockRequests.js"; import app from "../../../server.js"; -import { locationSearchWithTimeout } from "./index.js"; - -// Tests commented out due to reliance on external API calls and fallibility of nocks -// Please comment in and run locally if making changes to /gis functionality -describe("locationSearchWithTimeout", () => { - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - test.skip("a successful call", async () => { - const timeout = 500; - const localAuthority = "braintree"; - const promise = locationSearchWithTimeout( - localAuthority, - { x: 50, y: 50, siteBoundary: "[]" }, - timeout, - ); - await expect(promise).resolves.toStrictEqual(expect.any(Object)); - }); - - test.skip("an immediate timeout", async () => { - const timeout = 500; - const localAuthority = "braintree"; - const promise = locationSearchWithTimeout( - localAuthority, - { x: 50, y: 50, siteBoundary: "[]" }, - timeout, - ); - vi.runAllTimers(); - await expect(promise).rejects.toEqual("location search timeout"); - }); -}); - -describe.skip("fetching GIS data from local authorities directly", () => { - const locations = [ - { - council: "braintree", - x: 575629.54, - y: 223122.85, - siteBoundary: [], - }, - ]; - - loadOrRecordNockRequests("fetching-direct-gis-data", locations); - - locations.forEach((location) => { - it(`returns MVP planning constraints for ${location.council}`, async () => { - await supertest(app) - .get( - `/gis/${location.council}?x=${location.x}&y=${ - location.y - }&siteBoundary=${JSON.stringify(location.siteBoundary)}`, - ) - .expect(200) - .then((res) => { - expect(res.body["article4"]).toBeDefined(); - expect(res.body["listed"]).toBeDefined(); - expect(res.body["designated.conservationArea"]).toBeDefined(); - }); - }, 20_000); // 20s request timeout - }); -}); describe.skip("fetching GIS data from Digital Land for supported local authorities", () => { const locations = [ diff --git a/api.planx.uk/modules/gis/service/local_authorities/braintree.js b/api.planx.uk/modules/gis/service/local_authorities/braintree.js deleted file mode 100644 index 4e01d2e5dc..0000000000 --- a/api.planx.uk/modules/gis/service/local_authorities/braintree.js +++ /dev/null @@ -1,147 +0,0 @@ -import "isomorphic-fetch"; -import { - getQueryableConstraints, - getManualConstraints, - makeEsriUrl, - setEsriGeometryType, - setEsriGeometry, - squashResultLayers, - rollupResultLayers, - addDesignatedVariable, -} from "../helpers.js"; -import { planningConstraints, A4_KEY } from "./metadata/braintree.js"; - -// Process local authority metadata -const gisLayers = getQueryableConstraints(planningConstraints); -const preCheckedLayers = getManualConstraints(planningConstraints); -const articleFours = planningConstraints.article4.records; - -// Fetch a data layer -async function search( - mapServer, - featureName, - serverIndex, - outFields, - geometry, - geometryType, -) { - const { id } = planningConstraints[featureName]; - - let url = makeEsriUrl(mapServer, id, serverIndex, { - outFields, - geometry, - geometryType, - }); - - return fetch(url) - .then((response) => response.text()) - .then((data) => [featureName, data]) - .catch((error) => { - console.log("Error:", error); - }); -} - -// For this location, iterate through our planning constraints and aggregate/format the responses -async function go(x, y, siteBoundary, extras) { - // If we have a siteBoundary from drawing, - // then query using the polygon, else fallback to the buffered address point - const geomType = setEsriGeometryType(siteBoundary); - const geom = setEsriGeometry(geomType, x, y, 0.05, siteBoundary); - - const results = await Promise.all( - // TODO: Better to have logic here to iterate over listed buildings and TPOs? - Object.keys(gisLayers).map((layer) => - search( - gisLayers[layer].source, - layer, - gisLayers[layer].serverIndex, - gisLayers[layer].fields, - geom, - geomType, - ), - ), - ); - - const ob = results - .filter(([_key, result]) => !(result instanceof Error)) - .reduce( - (acc, [key, result]) => { - const data = JSON.parse(result); - const k = `${planningConstraints[key].key}`; - - try { - if (data.features.length > 0) { - const { attributes: properties } = data.features[0]; - acc[k] = { - ...planningConstraints[key].pos(properties), - value: true, - type: "warning", - data: properties, - category: planningConstraints[key].category, - }; - } else { - if (!acc[k]) { - acc[k] = { - text: planningConstraints[key].neg, - value: false, - type: "check", - data: {}, - category: planningConstraints[key].category, - }; - } - } - } catch (e) { - console.log(e); - } - - return acc; - }, - { - ...preCheckedLayers, - ...extras, - }, - ); - - // Set granular A4s, accounting for one variable matching to many possible shapes - if (ob["article4"].value) { - Object.keys(articleFours).forEach((key) => { - if ( - articleFours[key].includes(ob["article4"].data[A4_KEY]) || - ob[key]?.value - ) { - ob[key] = { value: true }; - } else { - ob[key] = { value: false }; - } - }); - } - - // Braintree host multiple TPO layers, but we only want to return a single result - const tpoLayers = [ - "tpo.tenMeterBuffer", - "tpo.areas", - "tpo.woodland", - "tpo.group", - ]; - const obWithSingleTPO = squashResultLayers(ob, tpoLayers, "tpo"); - - // Likewise, multiple layers are provided for "listed" - // Roll these up to preserve their granularity - const listedLayers = ["listed.grade1", "listed.grade2", "listed.grade2star"]; - const obWithSingleListed = rollupResultLayers( - obWithSingleTPO, - listedLayers, - "listed", - ); - - // Add summary "designated" key to response - const obWithDesignated = addDesignatedVariable(obWithSingleListed); - - return obWithDesignated; -} - -async function locationSearch(x, y, siteBoundary, extras) { - return go(x, y, siteBoundary, extras); -} - -export { locationSearch }; diff --git a/api.planx.uk/modules/gis/service/local_authorities/metadata/braintree.js b/api.planx.uk/modules/gis/service/local_authorities/metadata/braintree.js deleted file mode 100644 index 5cc21ab898..0000000000 --- a/api.planx.uk/modules/gis/service/local_authorities/metadata/braintree.js +++ /dev/null @@ -1,202 +0,0 @@ -/* -LAD20CD: E07000067 -LAD20NM: Braintree -LAD20NMW: -FID: 239 - -https://mapping.braintree.gov.uk/arcgis/rest/services/PlanX/PlanX/FeatureServer -https://environment.data.gov.uk/arcgis/rest/services -*/ - -const braintreeDomain = "https://mapping.braintree.gov.uk/arcgis"; -const _environmentDomain = "https://environment.data.gov.uk"; -const A4_KEY = "PARISH"; - -const planningConstraints = { - "listed.grade1": { - key: "listed.grade1", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 0, - fields: ["OBJECTID"], - neg: "is not in, or within, a Listed Building", - pos: (_data) => ({ - text: "is, or is within, a Listed Building (Grade 1)", - description: null, - }), - category: "Heritage and conservation", - }, - "listed.grade2": { - key: "listed.grade2", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 1, - fields: ["OBJECTID"], - neg: "is not in, or within, a Listed Building", - pos: (_data) => ({ - text: "is, or is within, a Listed Building (Grade 2)", - description: null, - }), - category: "Heritage and conservation", - }, - "listed.grade2star": { - key: "listed.grade2star", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 2, - fields: ["OBJECTID"], - neg: "is not in, or within, a Listed Building", - pos: (_data) => ({ - text: "is, or is within, a Listed Building (Grade 2*)", - description: null, - }), - category: "Heritage and conservation", - }, - "designated.conservationArea": { - key: "designated.conservationArea", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 3, - fields: ["OBJECTID", "DESIGNATED"], - neg: "is not in a Conservation Area", - pos: (_data) => ({ - text: "is in a Conservation Area", - description: null, - }), - category: "Heritage and conservation", - }, - "nature.SSSI": { - key: "nature.SSSI", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 8, - fields: ["OBJECTID", "LOCATION"], - neg: "is not a Site of Special Scientific Interest", - pos: (data) => ({ - text: "is a Site of Special Scientific Interest", - description: data.LOCATION, - }), - category: "Ecology", - }, - monument: { - key: "monument", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 9, - fields: ["OBJECTID", "LOCATION"], - neg: "is not the site of a Scheduled Ancient Monument", - pos: (data) => ({ - text: "is the site of a Scheduled Ancient Monument", - description: data.LOCATION, - }), - category: "Heritage and conservation", - }, - "tpo.tenMeterBuffer": { - key: "tpo.tenMeterBuffer", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 4, - fields: ["OBJECTID"], - neg: "is not in a TPO (Tree Preservation Order) Zone", - pos: (_data) => ({ - text: "is in a TPO (Tree Preservation Order) Zone", - description: null, - }), - category: "Trees", - }, - "tpo.areas": { - key: "tpo.areas", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 5, - fields: ["OBJECTID", "AREAS"], - neg: "is not in a TPO (Tree Preservation Order) Zone", - pos: (_data) => ({ - text: "is in a TPO (Tree Preservation Order) Zone", - description: null, - }), - category: "Trees", - }, - "tpo.woodland": { - key: "tpo.woodland", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 6, - fields: ["OBJECTID", "SPECIES", "REFERENCE_"], - neg: "is not in a TPO (Tree Preservation Order) Zone", - pos: (_data) => ({ - text: "is in a TPO (Tree Preservation Order) Zone", - description: null, - }), - category: "Trees", - }, - "tpo.group": { - key: "tpo.group", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 7, - fields: ["OBJECTID", "GROUPS", "SPECIES"], - neg: "is not in a TPO (Tree Preservation Order) Zone", - pos: (_data) => ({ - text: "is in a TPO (Tree Preservation Order) Zone", - description: null, - }), - category: "Trees", - }, - article4: { - key: "article4", - source: braintreeDomain, - id: "PlanX/PlanX", - serverIndex: 10, - fields: ["OBJECTID", "PARISH", "INFORMATIO"], - neg: "is not subject to local permitted development restrictions (known as Article 4 directions)", - pos: (data) => ({ - text: "is subject to local permitted development restrictions (known as Article 4 directions)", - description: data.INFORMATIO, - }), - records: { - // planx value to "PARISH" lookup - "article4.braintree.silverEnd": ["Silver End"], - "article4.braintree.stisted": [ - "Stisted", - "Braintree", - "Gosfield", - "Middleton", - "Shalford", - "Greenstead Green", - "Coggeshall", - "Ashen", - ], - }, - category: "General policy", - }, - "designated.AONB": { - key: "designated.AONB", - source: "manual", // there are no AONBs in Braintree - neg: "is not in an Area of Outstanding Natural Beauty", - category: "Heritage and conservation", - }, - "designated.nationalPark": { - key: "designated.nationalPark", - source: "manual", // there are no National Parks in Braintree - neg: "is not in a National Park", - category: "Heritage and conservation", - }, - "designated.broads": { - key: "designated.broads", - source: "manual", // there are no Broads in Braintree - neg: "is not in a Broad", - category: "Heritage and conservation", - }, - "designated.WHS": { - key: "designated.WHS", - source: "manual", // there are no WHS in Braintree - neg: "is not an UNESCO World Heritage Site", - category: "Heritage and conservation", - }, - "defence.explosives": { value: false }, - "defence.safeguarded": { value: false }, - hazard: { value: false }, -}; - -export { planningConstraints, A4_KEY }; diff --git a/api.planx.uk/modules/gis/service/local_authorities/metadata/scotland.js b/api.planx.uk/modules/gis/service/local_authorities/metadata/scotland.js deleted file mode 100644 index 6769d0f48c..0000000000 --- a/api.planx.uk/modules/gis/service/local_authorities/metadata/scotland.js +++ /dev/null @@ -1,104 +0,0 @@ -/* -TEST ONLY currently for TPX Impact -*/ - -// const scotGovDomain = "https://maps.gov.scot/server"; -const inspireHESDomain = "https://inspire.hes.scot/arcgis"; - -const planningConstraints = { - article4: { value: false }, - "designated.conservationArea": { - key: "designated.conservationArea", - source: inspireHESDomain, - id: "HES/HES_Designations", - serverIndex: 2, - fields: ["OBJECTID", "DES_TITLE", "LINK"], - neg: "is not in a Conservation Area", - pos: (data) => ({ - text: "is in a Conservation Area", - description: data.DES_TITLE, - }), - category: "Heritage and conservation", - }, - // "designated.nationalPark.cairngorms": { - // key: "designated.nationalPark.cairngorms", - // source: scotGovDomain, - // id: "ScotGov/ProtectedSites", - // serverIndex: 0, - // fields: ["objectid", "npcode", "npname"], - // neg: "is not in a National Park", - // pos: (data) => ({ - // text: "is in Cairngorms National Park", - // description: data.npname, - // }), - // category: "Heritage and conservation", - // }, - // "designated.nationalPark.lochLomondTrossachs": { - // key: "designated.nationalPark.lochLomondTrossachs", - // source: scotGovDomain, - // id: "ScotGov/ProtectedSites", - // serverIndex: 1, - // fields: ["objectid", "npcode", "npname"], - // neg: "is not in a National Park", - // pos: (data) => ({ - // text: "is in Loch Lomond and The Trossachs National Park", - // description: data.npname, - // }), - // category: "Heritage and conservation", - // }, - // "designated.nationalScenicArea": { - // key: "designated.nationalScenicArea", - // source: scotGovDomain, - // id: "ScotGov/ProtectedSites", - // serverIndex: 3, - // fields: ["objectid", "nsacode", "nsaname"], - // neg: "is not in a National Scenic Area", - // pos: (data) => ({ - // text: "is in a National Scenic Area", - // description: data.nsaname, - // }), - // category: "Heritage and conservation", - // }, - "designated.WHS": { - key: "designated.WHS", - source: inspireHESDomain, - id: "HES/HES_Designations", - serverIndex: 6, - fields: ["DES_REF", "DES_TITLE"], - neg: "is not, or is not within, an UNESCO World Heritage Site", - pos: (data) => ({ - text: "is, or is within, an UNESCO World Heritage Site", - description: data.DES_TITLE, - }), - category: "Heritage and conservation", - }, - listed: { - key: "listed", - source: inspireHESDomain, - id: "HES/HES_Designations", - serverIndex: 7, - fields: ["DES_REF", "DES_TITLE", "CATEGORY", "LINK"], - neg: "is not, or is not within, a Listed Building", - pos: (data) => ({ - text: `is, or is within, a Listed Building (Category ${data.CATEGORY})`, - description: data.DES_TITLE, - }), - category: "Heritage and conservation", - }, - monument: { - key: "monument", - source: inspireHESDomain, - id: "HES/HES_Designations", - serverIndex: 5, - fields: ["DES_REF", "DES_TITLE", "LINK"], - neg: "is not the site of a Scheduled Monument", - pos: (data) => ({ - text: "is the site of a Scheduled Monument", - description: data.DES_TITLE, - }), - category: "Heritage and conservation", - }, - tpo: { value: false }, -}; - -export { planningConstraints }; diff --git a/api.planx.uk/modules/gis/service/local_authorities/scotland.js b/api.planx.uk/modules/gis/service/local_authorities/scotland.js deleted file mode 100644 index eef564475e..0000000000 --- a/api.planx.uk/modules/gis/service/local_authorities/scotland.js +++ /dev/null @@ -1,119 +0,0 @@ -import "isomorphic-fetch"; -import { - getQueryableConstraints, - makeEsriUrl, - setEsriGeometryType, - setEsriGeometry, - rollupResultLayers, - addDesignatedVariable, -} from "../helpers.js"; -import { planningConstraints } from "./metadata/scotland.js"; - -// Process local authority metadata -const gisLayers = getQueryableConstraints(planningConstraints); - -// Fetch a data layer -async function search( - mapServer, - featureName, - serverIndex, - outFields, - geometry, - geometryType, -) { - const { id } = planningConstraints[featureName]; - - let url = makeEsriUrl(mapServer, id, serverIndex, { - outFields, - geometry, - geometryType, - }); - - return fetch(url) - .then((response) => response.text()) - .then((data) => [featureName, data]) - .catch((error) => { - console.log("Error:", error); - }); -} - -// For this location, iterate through our planning constraints and aggregate/format the responses -async function go(x, y, siteBoundary, extras) { - // If we have a siteBoundary from drawing, - // then query using the polygon, else fallback to the buffered address point - const geomType = setEsriGeometryType(siteBoundary); - const geom = setEsriGeometry(geomType, x, y, 0.05, siteBoundary); - - const results = await Promise.all( - Object.keys(gisLayers).map((layer) => - search( - gisLayers[layer].source, - layer, - gisLayers[layer].serverIndex, - gisLayers[layer].fields, - geom, - geomType, - ), - ), - ); - - const ob = results.filter(Boolean).reduce( - (acc, [key, result]) => { - const data = JSON.parse(result); - const k = `${planningConstraints[key].key}`; - - try { - if (data.features.length > 0) { - const { attributes: properties } = data.features[0]; - acc[k] = { - ...planningConstraints[key].pos(properties), - value: true, - type: "warning", - data: properties, - category: planningConstraints[key].category, - }; - } else { - if (!acc[k]) { - acc[k] = { - text: planningConstraints[key].neg, - value: false, - type: "check", - data: {}, - category: planningConstraints[key].category, - }; - } - } - } catch (e) { - console.log(e); - } - - return acc; - }, - { - ...extras, - }, - ); - - // Scotland hosts multiple national park layers - // Roll these up to preserve their granularity when true - const nationalParkLayers = [ - // "designated.nationalPark.cairngorms", - // "designated.nationalPark.lochLomondTrossachs", - ]; - const obWithOneNationalPark = rollupResultLayers( - ob, - nationalParkLayers, - "designated.nationalPark", - ); - - // Add summary "designated" key to response - const obWithDesignated = addDesignatedVariable(obWithOneNationalPark); - - return obWithDesignated; -} - -async function locationSearch(x, y, siteBoundary, extras) { - return go(x, y, siteBoundary, extras); -} - -export { locationSearch }; diff --git a/api.planx.uk/tests/nocks/fetching-direct-gis-data.ed67b9a5.json b/api.planx.uk/tests/nocks/fetching-direct-gis-data.ed67b9a5.json deleted file mode 100644 index 1ac8016059..0000000000 --- a/api.planx.uk/tests/nocks/fetching-direct-gis-data.ed67b9a5.json +++ /dev/null @@ -1,614 +0,0 @@ -[ - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/0/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b2520a700cf20cf650d2514a03093ae6642616a7162b59552bf93b79b93a8778ba00d5c099b550654005d1d54a791013e0b23a4a2595052091d4e2a24cb01d2140be3f58261164308a51b140b352134b4a8b40d645c7d6020011d8e66a9e000000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "1a0b6423", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "126" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/1/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b2520a700cf20cf650d2514a03093ae6642616a7162b59552bf93b79b93a8778ba00d5c099b550654005d1d54a791013e0b23a4a2595052091d4e2a24cb01d2140be3f58261164308a51b140b352134b4a8b40d645c7d6020011d8e66a9e000000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "1a0b6423", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "126" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/3/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CDESIGNATED&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b08000000000000006d8fb10a83301086dfe56641a545aa9badb6d84107dd8a438a571b4845923888e4dd1bb54822dd2e77f77df96f82868a9e91f14a913539f9204490a46576cbe32a4dc081d73c8819250205441314e77b7aa9b244ef6da5632216af7e028d3e26e856bfc1c9b19f3b28385d1254fa5d2c13327f692e2b67135801ff294ac969d71a168b60d8b5f20dd1d153b5ce87440e1cd784446af239c8fdad87d3ee463f74bdc0f5c32004a56af505d59c2d4948010000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "8dcca684", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "188" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/10/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CPARISH%2CINFORMATIO&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b2520a700cf20cf650d2514a03093ae6642616a7162b59552bf93b79b93a8778ba00d5c0993a30e548fa3cfddcfc837c1d433cfd81a2489c5aa89140c3a2ab95f220b621995452590012492d2eca04bb2704c8f707cb24821c81acb856076e00dc5a6cda834b8a32f3d2914c80abce49cd4b2fc950b2323640320bc9b1449a87a20366a6b9416d2cd0afa98925a545a0a08b8ead05003204d7d96a010000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "5ef885e1", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "187" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/2/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b2520a700cf20cf650d2514a03093ae6642616a7162b59552bf93b79b93a8778ba00d5c099b550654005d1d54a791013e0b23a4a2595052091d4e2a24cb01d2140be3f58261164308a51b140b352134b4a8b40d645c7d6020011d8e66a9e000000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "1a0b6423", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "126" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/4/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b252f2730d77f374f57151d2514a03093be6642616a7162b59552bf93b79b93a8778ba0055c199b550654005d1d54a791033e0b23a4a2595052091d4e2a24cb02d2140be3f58261164308a51b140b352134b4a8b40d645c7d6020054cb60e7a0000000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "4f0f77a6", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "128" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/7/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CGROUPS%2CSPECIES&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b252720ff20f0d0856d2514a03093ae6642616a7162b59552bf93b79b93a8778ba00d5c0993a30e548fa82035c9d3d5d414230562dd430a031d1d54a79107b90cc28a92c0089a4161765825d1202e4fb83651241d6232baed5811b00b7109bf6e092a2ccbc742413e0aa7352f3d24b3294ac8c0c90cc82b99448c310ca61a6191a18d4c602bd999a58525a040aafe8d85a007c45e3fd5f010000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "a53e735b", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "183" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/8/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CLOCATION&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b2520a700cf20cf650d2514a03093ae6642616a7162b59552bf93b79b93a8778ba00d5c0993a4a3efece8e219efe7e405138b316aa19a82dba5a290f622e929e92ca0290486a715126d8e61020df1f2c9308b20e5971ad0edc00b8f1d80d082e29cacc4b473203497d4e6a5e7a49869295b9416d2cd06da98925a545204f45c7d602008a2779f204010000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "e3cbe4df", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "168" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/9/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CLOCATION&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b252f2f177760cf1f4f753d2514a03093be6642616a7162b59552bf93b79b93a8778ba0055c1993a080d487a6ba19a81daa2ab95f2202623e929a92c0089a416176582ed0e01f2fdc1328920eb9015d7eac00d40721a3603824b8a32f3d291cc40529f939a975e92a1646562501b0b745b6a62496911c853d1b1b500999c6bee06010000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "bfee46e4", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "161" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/5/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CAREAS&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b0800000000000000ab564ac92c2ec849ac74cb4ccd49f14bcc4d55b252720c72750c56d2514a038939e6642616a7162b59552bf93b79b93a8778ba0095c0993a50d5305db5506d400dd1d54a79100391549754168044528b8b32c1568600f9fe6099449045c88a6b75e006c05c844d77704951665e3a920130c539a979e925194a564606b5b14057a526969416813c121d5b0b00a81ef63cf7000000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "b7115859", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "158" - ], - "responseIsBinary": false - }, - { - "scope": "https://mapping.braintree.gov.uk:443", - "method": "GET", - "path": "//arcgis/rest/services/PlanX/PlanX/MapServer/6/query?where=1%3D1&geometryType=esriGeometryEnvelope&inSR=27700&spatialRel=esriSpatialRelIntersects&returnGeometry=false&outSR=4326&f=json&outFields=OBJECTID%2CSPECIES%2CREFERENCE_&geometry=575628.95%2C223122.05%2C575629.05%2C223121.95", - "body": "", - "status": 200, - "response": [ - "1f8b08000000000000008d903d0f82301086ffcbcd1d30c6854df148700043d908314da8d8a41242eb4048ffbb2d0a94c4c1ed3ede7bdebb1ba116aa936c88059775ca9e1c42c831c61cd3086f40e0ee1a472998e20ac211b2d305a322395bdd1212a0578c12a4b63847c4c76c98e60bb5b87284f6e3e9b1f4d0b90a57bd98b62a6c9e4d1de6d6f0c5862c80d5f7d73cd5bd681b0fb1ca256f1bfd807017041e6ef383bf889b8919ba3f98ca9ecb997ef5ee7f6565dea4e3b94a73010000" - ], - "rawHeaders": [ - "Cache-Control", - "public, must-revalidate, max-age=0", - "Content-Type", - "application/json;charset=UTF-8", - "Content-Encoding", - "gzip", - "Expires", - "Thu, 01 Jan 1970 00:00:00 GMT", - "ETag", - "80e16265", - "Vary", - "Origin", - "Server", - "Microsoft-IIS/10.0", - "X-Content-Type-Options", - "nosniff", - "X-XSS-Protection", - "1; mode=block", - "x-esri-ftiles-cache-compress", - "true", - "Set-Cookie", - "AGS_ROLES=\"419jqfa+uOZgYod4xPOQ8Q==\"; Version=1; Max-Age=60; Expires=Mon, 20-Jun-2022 11:45:22 GMT; Path=/arcgis/rest; Secure; HttpOnly", - "Server", - "", - "X-AspNet-Version", - "4.0.30319", - "X-Powered-By", - "ASP.NET", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close", - "Content-Length", - "190" - ], - "responseIsBinary": false - }, - { - "scope": "http://127.0.0.1:50475", - "method": "GET", - "path": "/gis/braintree?x=575629.54&y=223122.85&siteBoundary=[]", - "body": "", - "status": 200, - "response": { - "designated.AONB": { - "text": "is not in an Area of Outstanding Natural Beauty", - "value": false, - "type": "check", - "data": [] - }, - "designated.nationalPark": { - "text": "is not in a National Park", - "value": false, - "type": "check", - "data": [] - }, - "designated.broads": { - "text": "is not in a Broad", - "value": false, - "type": "check", - "data": [] - }, - "designated.WHS": { - "text": "is not an UNESCO World Heritage Site", - "value": false, - "type": "check", - "data": [] - }, - "listed.grade1": { "value": false }, - "listed.grade2": { "value": false }, - "listed.grade2star": { "value": false }, - "designated.conservationArea": { - "text": "is in a Conservation Area", - "description": null, - "value": true, - "type": "warning", - "data": { "OBJECTID": 38, "DESIGNATED": "19/06/1969" } - }, - "nature.SSSI": { - "text": "is not a Site of Special Scientific Interest", - "value": false, - "type": "check", - "data": {} - }, - "monument": { - "text": "is not the site of a Scheduled Ancient Monument", - "value": false, - "type": "check", - "data": {} - }, - "article4": { - "text": "is not subject to local permitted development restrictions (known as Article 4 directions)", - "value": false, - "type": "check", - "data": {} - }, - "tpo": { - "text": "is not in a TPO (Tree Preservation Order) Zone", - "value": false, - "type": "check", - "data": {} - }, - "listed": { - "text": "is not in, or within, a Listed Building", - "value": false, - "type": "check", - "data": {} - }, - "designated": { "value": true } - }, - "rawHeaders": [ - "X-Powered-By", - "Express", - "Access-Control-Allow-Origin", - "*", - "Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept", - "Access-Control-Allow-Credentials", - "true", - "Content-Type", - "application/json; charset=utf-8", - "Content-Length", - "1230", - "ETag", - "W/\"4ce-65F+71DwzZzn+tgPMmDSUgzd+j0\"", - "Date", - "Mon, 20 Jun 2022 11:44:22 GMT", - "Connection", - "close" - ], - "responseIsBinary": false - } -] diff --git a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx index f622093b59..34ae0fd68e 100644 --- a/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx +++ b/editor.planx.uk/src/@planx/components/PlanningConstraints/Public.tsx @@ -38,22 +38,22 @@ function Component(props: Props) { currentCardId, cachedBreadcrumbs, teamSlug, - siteBoundary, - { x, y, longitude, latitude, usrn }, hasPlanningData, + siteBoundary, priorOverrides, + { longitude, latitude, usrn }, ] = useStore((state) => [ state.currentCard?.id, state.cachedBreadcrumbs, state.teamSlug, - state.computePassport().data?.["property.boundary.site"], - (state.computePassport().data?.["_address"] as SiteAddress) || {}, state.teamIntegrations?.hasPlanningData, + state.computePassport().data?.["property.boundary.site"], state.computePassport().data?.["_overrides"], + (state.computePassport().data?.["_address"] as SiteAddress) || {}, ]); // PlanningConstraints must come after at least a FindProperty in the graph - const showGraphError = !x || !y || !longitude || !latitude; + const showGraphError = !longitude || !latitude; if (showGraphError) throw new GraphError("mapInputFieldMustFollowFindProperty"); @@ -69,32 +69,22 @@ function Component(props: Props) { const urlSearchParams = new URLSearchParams(window.location.search); const params = Object.fromEntries(urlSearchParams.entries()); - // Get the coordinates of the site boundary drawing if they exist, fallback on x & y if file was uploaded - // Coords should match Esri's "rings" type https://developers.arcgis.com/javascript/3/jsapi/polygon-amd.html#rings - const coordinates: number[][][] = siteBoundary?.geometry?.coordinates || []; - - // Get the WKT representation of the site boundary drawing or address point to pass to Digital Land, when applicable + // Get the WKT representation of the site boundary drawing or address point to pass to Planning Data const wktPoint = `POINT(${longitude} ${latitude})`; const wktPolygon: string | undefined = siteBoundary && stringify(siteBoundary); - const digitalLandParams: Record = { + const planningDataParams: Record = { geom: wktPolygon || wktPoint, ...params, }; - const customGisParams: Record = { - x: x?.toString(), - y: y?.toString(), - siteBoundary: JSON.stringify(coordinates), - version: "1", - }; // Fetch planning constraints data for a given local authority const root = `${import.meta.env.VITE_APP_API_URL}/gis/${teamSlug}?`; const teamGisEndpoint: string = root + new URLSearchParams( - hasPlanningData ? digitalLandParams : customGisParams, + hasPlanningData ? planningDataParams : undefined, ).toString(); const fetcher: Fetcher = ( @@ -105,11 +95,9 @@ function Component(props: Props) { mutate, error: dataError, isValidating, - } = useSWR( - () => (x && y && latitude && longitude ? teamGisEndpoint : null), - fetcher, - { revalidateOnFocus: false }, - ); + } = useSWR(() => (latitude && longitude ? teamGisEndpoint : null), fetcher, { + revalidateOnFocus: false, + }); // If an OS address was selected, additionally fetch classified roads (available nationally) using the USRN identifier, // skip if the applicant plotted a new non-UPRN address on the map @@ -127,9 +115,9 @@ function Component(props: Props) { { revalidateOnFocus: false }, ); - // XXX handle both/either Digital Land response and custom GIS hookup responses; merge roads for a unified list of constraints + // Merge Planning Data and Roads responses for a unified list of constraints const constraints: GISResponse["constraints"] | Record = { - ...(data?.constraints || data), + ...data?.constraints, ...roads?.constraints, }; @@ -154,8 +142,6 @@ function Component(props: Props) { ...roads, planxRequest: classifiedRoadsEndpoint, } as EnhancedGISResponse); - } else { - if (data) _constraints.push(data as GISResponse["constraints"]); } const hasInaccurateConstraints =