diff --git a/e2e/tests/api-driven/package.json b/e2e/tests/api-driven/package.json
index 3f52e24cc1..5421a8e761 100644
--- a/e2e/tests/api-driven/package.json
+++ b/e2e/tests/api-driven/package.json
@@ -6,7 +6,7 @@
},
"dependencies": {
"@cucumber/cucumber": "^9.3.0",
- "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#550634a",
+ "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#5710d52",
"axios": "^1.6.8",
"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 184f04ac00..3005fa893d 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#550634a
- version: github.com/theopensystemslab/planx-core/550634a
+ specifier: git+https://github.com/theopensystemslab/planx-core#5710d52
+ version: github.com/theopensystemslab/planx-core/5710d52
axios:
specifier: ^1.6.8
version: 1.6.8
@@ -2935,8 +2935,8 @@ packages:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
dev: false
- github.com/theopensystemslab/planx-core/550634a:
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/550634a}
+ github.com/theopensystemslab/planx-core/5710d52:
+ resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/5710d52}
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
diff --git a/e2e/tests/api-driven/src/invite-to-pay/helpers.ts b/e2e/tests/api-driven/src/invite-to-pay/helpers.ts
index c12649c4d9..302be5dd57 100644
--- a/e2e/tests/api-driven/src/invite-to-pay/helpers.ts
+++ b/e2e/tests/api-driven/src/invite-to-pay/helpers.ts
@@ -45,6 +45,7 @@ export async function buildITPFlow({
const flowId: string = await $admin.flow.create({
teamId,
slug: `test-invite-to-pay-flow-with-send-to-${destination.toLowerCase()}`,
+ status: "online",
data: flowGraph,
});
const publishedFlowId = await $admin.flow.publish({
diff --git a/e2e/tests/ui-driven/package.json b/e2e/tests/ui-driven/package.json
index e472938bcd..7957fa2819 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#550634a",
+ "@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#5710d52",
"axios": "^1.6.8",
"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 a0e6dbe733..551c17f74f 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#550634a
- version: github.com/theopensystemslab/planx-core/550634a
+ specifier: git+https://github.com/theopensystemslab/planx-core#5710d52
+ version: github.com/theopensystemslab/planx-core/5710d52
axios:
specifier: ^1.6.8
version: 1.6.8
@@ -2684,8 +2684,8 @@ packages:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
dev: false
- github.com/theopensystemslab/planx-core/550634a:
- resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/550634a}
+ github.com/theopensystemslab/planx-core/5710d52:
+ resolution: {tarball: https://codeload.github.com/theopensystemslab/planx-core/tar.gz/5710d52}
name: '@opensystemslab/planx-core'
version: 1.0.0
prepare: true
diff --git a/e2e/tests/ui-driven/src/context.ts b/e2e/tests/ui-driven/src/context.ts
index ac06eab02e..b9369b6e02 100644
--- a/e2e/tests/ui-driven/src/context.ts
+++ b/e2e/tests/ui-driven/src/context.ts
@@ -74,6 +74,7 @@ export async function setUpTestContext(
slug: context.flow.slug,
teamId: context.team.id,
data: context.flow!.data!,
+ status: "online",
});
context.flow.publishedId = await $admin.flow.publish({
flow: {
diff --git a/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts b/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts
index 8c10cffec2..7d86cb19a9 100644
--- a/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts
+++ b/e2e/tests/ui-driven/src/create-flow/create-flow.spec.ts
@@ -179,6 +179,54 @@ test.describe("Navigation", () => {
await expect(previewLink).toBeVisible();
});
+ test("Cannot preview an offline flow", async ({
+ browser,
+ }: {
+ browser: Browser;
+ }) => {
+ const page = await createAuthenticatedSession({
+ browser,
+ userId: context.user!.id!,
+ });
+
+ await page.goto(
+ `/${context.team.slug}/${serviceProps.slug}/published?analytics=false`,
+ );
+
+ await expect(
+ page.getByRole("heading", { level: 1, name: "Offline" }),
+ ).toBeVisible();
+ });
+
+ test("Turn a flow online", async ({ browser }) => {
+ const page = await createAuthenticatedSession({
+ browser,
+ userId: context.user!.id!,
+ });
+
+ await page.goto(`/${context.team.slug}/${serviceProps.slug}`);
+
+ // Open flow settings
+ // TODO: Access via sidebar when EDITOR_NAVIGATION flag is removed
+ page.getByLabel("Toggle Menu").click();
+ page.getByText("Flow Settings").click();
+
+ // Toggle flow online
+ page.getByLabel("Offline").click();
+ page.getByRole("button", { name: "Save", disabled: false }).click();
+ await expect(
+ page.getByText("Service settings updated successfully"),
+ ).toBeVisible();
+
+ // Exit back to main Editor page
+ page.getByRole("link", { name: "Close" }).click();
+
+ const previewLink = page.getByRole("link", {
+ name: "Open published service",
+ });
+ await expect(previewLink).toBeVisible();
+ });
+
test("Can preview a published flow", async ({
browser,
}: {
diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/store/shared.ts b/editor.planx.uk/src/pages/FlowEditor/lib/store/shared.ts
index d7143dfa3e..3b4381a463 100644
--- a/editor.planx.uk/src/pages/FlowEditor/lib/store/shared.ts
+++ b/editor.planx.uk/src/pages/FlowEditor/lib/store/shared.ts
@@ -1,5 +1,6 @@
import { CoreDomainClient } from "@opensystemslab/planx-core";
import { Auth } from "@opensystemslab/planx-core/dist/requests/graphql";
+import { FlowStatus } from "@opensystemslab/planx-core/types";
import { ROOT_NODE_KEY } from "@planx/graph";
import { capitalize } from "lodash";
import { removeSessionIdSearchParam } from "utils";
@@ -23,10 +24,12 @@ export interface SharedStore extends Store.Store {
id,
flow,
flowSlug,
+ flowStatus,
}: {
id: string;
flow: Store.flow;
flowSlug: string;
+ flowStatus?: FlowStatus;
}) => void;
wasVisited: (id: Store.nodeId) => boolean;
previewEnvironment: PreviewEnvironment;
@@ -88,9 +91,9 @@ export const sharedStore: StateCreator<
removeSessionIdSearchParam();
},
- setFlow({ id, flow, flowSlug }) {
+ setFlow({ id, flow, flowSlug, flowStatus }) {
this.setFlowNameFromSlug(flowSlug);
- set({ id, flow, flowSlug });
+ set({ id, flow, flowSlug, flowStatus });
get().initNavigationStore();
},
diff --git a/editor.planx.uk/src/pages/OfflinePage.tsx b/editor.planx.uk/src/pages/OfflinePage.tsx
new file mode 100644
index 0000000000..7ffcc9c85c
--- /dev/null
+++ b/editor.planx.uk/src/pages/OfflinePage.tsx
@@ -0,0 +1,16 @@
+import Typography from "@mui/material/Typography";
+import StatusPage from "pages/Preview/StatusPage";
+import React from "react";
+
+export const OfflinePage: React.FC = () => (
+
+
+ This service is not currently available to new applicants. Please check
+ back later.
+
+
+ If you're resuming an application you previously started, please use the
+ link sent to you via email.
+
+
+);
diff --git a/editor.planx.uk/src/pages/layout/OfflineLayout.tsx b/editor.planx.uk/src/pages/layout/OfflineLayout.tsx
new file mode 100644
index 0000000000..2279c28f58
--- /dev/null
+++ b/editor.planx.uk/src/pages/layout/OfflineLayout.tsx
@@ -0,0 +1,16 @@
+import { useStore } from "pages/FlowEditor/lib/store";
+import { OfflinePage } from "pages/OfflinePage";
+import React, { PropsWithChildren } from "react";
+
+const OfflineLayout = ({ children }: PropsWithChildren) => {
+ const isFlowOnline = useStore.getState().flowStatus === "online";
+ const searchParams = new URLSearchParams(window.location.search);
+ const isUserResuming = Boolean(searchParams.get("sessionId"));
+
+ // Allow users to complete Save & Return journeys, even if a flow is offline
+ const isFlowAccessible = isFlowOnline || isUserResuming;
+
+ return isFlowAccessible ? children : ;
+};
+
+export default OfflineLayout;
diff --git a/editor.planx.uk/src/routes/views/published.tsx b/editor.planx.uk/src/routes/views/published.tsx
index d22deb94b7..5f2b26e470 100644
--- a/editor.planx.uk/src/routes/views/published.tsx
+++ b/editor.planx.uk/src/routes/views/published.tsx
@@ -4,6 +4,7 @@ import { NaviRequest } from "navi";
import { NotFoundError } from "navi";
import { useStore } from "pages/FlowEditor/lib/store";
import { Store } from "pages/FlowEditor/lib/store";
+import OfflineLayout from "pages/layout/OfflineLayout";
import PublicLayout from "pages/layout/PublicLayout";
import SaveAndReturnLayout from "pages/layout/SaveAndReturnLayout";
import React from "react";
@@ -43,16 +44,23 @@ export const publishedView = async (req: NaviRequest) => {
const state = useStore.getState();
// XXX: necessary as long as not every flow is published; aim to remove dataMergedHotfix.ts in future
// load pre-flattened published flow if exists, else load & flatten flow
- state.setFlow({ id: flow.id, flow: publishedFlow, flowSlug });
+ state.setFlow({
+ id: flow.id,
+ flow: publishedFlow,
+ flowSlug,
+ flowStatus: flow.status,
+ });
state.setGlobalSettings(data.globalSettings[0]);
state.setFlowSettings(flow.settings);
state.setTeam(flow.team);
return (
-
-
-
+
+
+
+
+
);
};
@@ -94,6 +102,7 @@ export const fetchSettingsForPublishedView = async (
boundaryBBox: boundary_bbox
}
settings
+ status
publishedFlows: published_flows(
limit: 1
order_by: { created_at: desc }
diff --git a/editor.planx.uk/src/types.ts b/editor.planx.uk/src/types.ts
index c2b0b36e3d..ccb534ba1e 100644
--- a/editor.planx.uk/src/types.ts
+++ b/editor.planx.uk/src/types.ts
@@ -1,13 +1,14 @@
import {
+ FlowStatus,
GovUKPayment,
NotifyPersonalisation,
Team,
} from "@opensystemslab/planx-core/types";
+import { OT } from "@planx/graph/types";
import { useFormik } from "formik";
import { Store } from "./pages/FlowEditor/lib/store/index";
import { SharedStore } from "./pages/FlowEditor/lib/store/shared";
-import { OT } from "@planx/graph/types";
export type Maybe = T | undefined;
@@ -18,6 +19,7 @@ export interface Flow {
slug: string;
team: Team;
settings?: FlowSettings;
+ status?: FlowStatus;
}
export interface GlobalSettings {
footerContent?: { [key: string]: TextContent };
@@ -133,4 +135,4 @@ export interface Operation {
lastName: string;
};
data: Array;
-}
\ No newline at end of file
+}
diff --git a/hasura.planx.uk/metadata/tables.yaml b/hasura.planx.uk/metadata/tables.yaml
index e8c8717c4b..cd5f0e6376 100644
--- a/hasura.planx.uk/metadata/tables.yaml
+++ b/hasura.planx.uk/metadata/tables.yaml
@@ -1,10 +1,17 @@
- table:
name: analytics
schema: public
+ object_relationships:
+ - name: flow
+ using:
+ foreign_key_constraint_on: flow_id
insert_permissions:
- role: public
permission:
- check: {}
+ check:
+ flow:
+ status:
+ _eq: online
columns:
- created_at
- flow_id
@@ -23,15 +30,26 @@
permission:
columns:
- ended_at
- filter: {}
+ filter:
+ flow:
+ status:
+ _eq: online
check: null
- table:
name: analytics_logs
schema: public
+ object_relationships:
+ - name: analytic
+ using:
+ foreign_key_constraint_on: analytics_id
insert_permissions:
- role: public
permission:
- check: {}
+ check:
+ analytic:
+ flow:
+ status:
+ _eq: online
columns:
- analytics_id
- created_at
@@ -65,7 +83,11 @@
- metadata
- next_log_created_at
- user_exit
- filter: {}
+ filter:
+ analytic:
+ flow:
+ status:
+ _eq: online
check: null
- table:
name: analytics_summary