From e09046f4053025c71d1c7b1463652d711c0c06d5 Mon Sep 17 00:00:00 2001 From: Albert Artur Kolozsvari Date: Wed, 5 Feb 2025 16:25:51 +0000 Subject: [PATCH 1/2] Display feedback messages --- .../server/plugins/engine/components/types.ts | 5 +++- .../page-controllers/PageControllerBase.ts | 20 ++++++++++++++ runner/src/server/views/layout.html | 26 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/runner/src/server/plugins/engine/components/types.ts b/runner/src/server/plugins/engine/components/types.ts index 913f8c7d..5ee2b830 100644 --- a/runner/src/server/plugins/engine/components/types.ts +++ b/runner/src/server/plugins/engine/components/types.ts @@ -22,4 +22,7 @@ export type ClientSideFileUploadFieldViewModel = { export type AdapterDataType = DataType | "multiInput" | "freeText"; - +export type ChangeRequest = { + title: string; + messages: string[]; +} diff --git a/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts b/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts index c0edc466..59307e5f 100644 --- a/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts +++ b/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts @@ -15,6 +15,7 @@ import { HapiResponseToolkit, } from "../../../types"; import { + ChangeRequest, FormData, FormPayload, FormSubmissionErrors, @@ -153,6 +154,7 @@ export class PageControllerBase { backLinkText?: string; continueButtonText?: string; phaseTag?: string | undefined; + changeRequests?: ChangeRequest[] | undefined; } { let showTitle = true; let pageTitle = this.title; @@ -650,6 +652,24 @@ export class PageControllerBase { return evaluatedComponent; }); + viewModel.changeRequests = []; + if (state["metadata"] && state["metadata"]["change_requests"]) { + for (let componentName in state["metadata"]["change_requests"]) { + const messages = state["metadata"]["change_requests"][componentName]; + + // Find the component with the matching name + const component = this.pageDef.components.find(component => component.name === componentName); + + if (component) { + // Add an object to viewModel.changeRequests + viewModel.changeRequests.push({ + title: component.title, + messages: messages + }); + } + } + } + /** * used for when a user clicks the "back" link. Progress is stored in the state. This is a safer alternative to running javascript that pops the history `onclick`. */ diff --git a/runner/src/server/views/layout.html b/runner/src/server/views/layout.html index 3388fc7c..fda77de7 100644 --- a/runner/src/server/views/layout.html +++ b/runner/src/server/views/layout.html @@ -191,6 +191,32 @@ text: backLinkText }) }} {% endif %} + + {% if changeRequests.length > 0 %} +
+
+
+
+
+

+ Change request +

+
+
+ {% for question in changeRequests %} +

+ {{ question.title }} +

+ {% for message in question.messages %} +

{{ message }}

+ {% endfor %} + {% endfor %} +
+
+
+
+
+ {% endif %} {% endblock %} From ba6fddeed8704028eb5e01094d8caa18f308a69a Mon Sep 17 00:00:00 2001 From: Albert Artur Kolozsvari Date: Sat, 8 Feb 2025 02:29:34 +0000 Subject: [PATCH 2/2] Address feedback and add tests --- .../server/plugins/engine/components/types.ts | 5 +- .../engine/page-controllers/PageController.ts | 39 +++++++++ .../page-controllers/PageControllerBase.ts | 20 +---- runner/src/server/types.ts | 6 ++ runner/src/server/views/layout.html | 25 +----- .../partials/change-requests-banner.html | 25 ++++++ .../page-controllers/PageController.test.ts | 56 +++++++++++++ .../test/cases/server/sample-page.test.json | 84 +++++++++++++++++++ 8 files changed, 214 insertions(+), 46 deletions(-) create mode 100644 runner/src/server/views/partials/change-requests-banner.html create mode 100644 runner/test/cases/server/sample-page.test.json diff --git a/runner/src/server/plugins/engine/components/types.ts b/runner/src/server/plugins/engine/components/types.ts index 5ee2b830..913f8c7d 100644 --- a/runner/src/server/plugins/engine/components/types.ts +++ b/runner/src/server/plugins/engine/components/types.ts @@ -22,7 +22,4 @@ export type ClientSideFileUploadFieldViewModel = { export type AdapterDataType = DataType | "multiInput" | "freeText"; -export type ChangeRequest = { - title: string; - messages: string[]; -} + diff --git a/runner/src/server/plugins/engine/page-controllers/PageController.ts b/runner/src/server/plugins/engine/page-controllers/PageController.ts index 372f5e26..e3513dff 100644 --- a/runner/src/server/plugins/engine/page-controllers/PageController.ts +++ b/runner/src/server/plugins/engine/page-controllers/PageController.ts @@ -55,4 +55,43 @@ export class PageController extends PageControllerBase { }, }; } + + makeGetRouteHandler() { + return async (request: HapiRequest, h: HapiResponseToolkit) => { + // Call the parent class's makeGetRouteHandler method and get the response + const parentResponse = await super.makeGetRouteHandler()(request, h); + + const {adapterCacheService} = request.services([]); + + // @ts-ignore + const state = await adapterCacheService.getState(request); + + // Extract the viewModel from the parent's response + const viewModel = parentResponse.source.context; + + viewModel.changeRequests = []; + const changeRequests = state.metadata?.change_requests; + if (changeRequests && Object.keys(changeRequests).length > 0) { + for (let componentName in changeRequests) { + const messages = changeRequests[componentName]; + + // Find the component with the matching the change request + const pageComponents = this.pageDef.components; + const component = pageComponents.find(component => { + return component.name === componentName + }); + + if (component) { + // Add an object to viewModel.changeRequests + viewModel.changeRequests.push({ + title: component.title, + messages: messages + }); + } + } + } + + return h.view(this.viewName, viewModel); + }; + } } diff --git a/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts b/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts index 59307e5f..eda429d9 100644 --- a/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts +++ b/runner/src/server/plugins/engine/page-controllers/PageControllerBase.ts @@ -10,12 +10,12 @@ import { RelativeUrl, } from "../../../../../../digital-form-builder/runner/src/server/plugins/engine"; import { + ChangeRequest, HapiRequest, HapiResponseObject, HapiResponseToolkit, } from "../../../types"; import { - ChangeRequest, FormData, FormPayload, FormSubmissionErrors, @@ -652,24 +652,6 @@ export class PageControllerBase { return evaluatedComponent; }); - viewModel.changeRequests = []; - if (state["metadata"] && state["metadata"]["change_requests"]) { - for (let componentName in state["metadata"]["change_requests"]) { - const messages = state["metadata"]["change_requests"][componentName]; - - // Find the component with the matching name - const component = this.pageDef.components.find(component => component.name === componentName); - - if (component) { - // Add an object to viewModel.changeRequests - viewModel.changeRequests.push({ - title: component.title, - messages: messages - }); - } - } - } - /** * used for when a user clicks the "back" link. Progress is stored in the state. This is a safer alternative to running javascript that pops the history `onclick`. */ diff --git a/runner/src/server/types.ts b/runner/src/server/types.ts index d65fe60a..659ff259 100644 --- a/runner/src/server/types.ts +++ b/runner/src/server/types.ts @@ -18,6 +18,12 @@ import {AdapterStatusService} from "./services"; import {WebhookService} from "./services/WebhookService"; import {TranslationLoaderService} from "./services/TranslationLoaderService"; + +export type ChangeRequest = { + title: string; + messages: string[]; + } + export type Services = (services: string[]) => { adapterCacheService: AdapterCacheService; notifyService: NotifyService; diff --git a/runner/src/server/views/layout.html b/runner/src/server/views/layout.html index fda77de7..dc0136dc 100644 --- a/runner/src/server/views/layout.html +++ b/runner/src/server/views/layout.html @@ -4,6 +4,7 @@ {% from "footer/macro.njk" import govukFooter -%} {% from "phase-banner/macro.njk" import govukPhaseBanner %} {% from "skip-link/macro.njk" import govukSkipLink -%} +{% from "./partials/change-requests-banner.html" import changeRequestsBanner %} {% block head %} @@ -193,29 +194,7 @@ {% endif %} {% if changeRequests.length > 0 %} -
-
-
-
-
-

- Change request -

-
-
- {% for question in changeRequests %} -

- {{ question.title }} -

- {% for message in question.messages %} -

{{ message }}

- {% endfor %} - {% endfor %} -
-
-
-
-
+ {{ changeRequestsBanner(changeRequests) }} {% endif %} {% endblock %} diff --git a/runner/src/server/views/partials/change-requests-banner.html b/runner/src/server/views/partials/change-requests-banner.html new file mode 100644 index 00000000..c6260768 --- /dev/null +++ b/runner/src/server/views/partials/change-requests-banner.html @@ -0,0 +1,25 @@ +{% macro changeRequestsBanner(changeRequests) %} +
+
+
+
+
+

+ Change request +

+
+
+ {% for question in changeRequests %} +

+ {{ question.title }} +

+ {% for message in question.messages %} +

{{ message }}

+ {% endfor %} + {% endfor %} +
+
+
+
+
+{% endmacro %} diff --git a/runner/test/cases/server/plugins/engine/page-controllers/PageController.test.ts b/runner/test/cases/server/plugins/engine/page-controllers/PageController.test.ts index 980a9bd4..e86fd990 100644 --- a/runner/test/cases/server/plugins/engine/page-controllers/PageController.test.ts +++ b/runner/test/cases/server/plugins/engine/page-controllers/PageController.test.ts @@ -100,4 +100,60 @@ suite("PageController", () => { console.error("Error during test:", error); } }); + + test("should display change request banner on page with feedback", async () => { + server = await createServer({ + formFileName: "sample-page.test.json", + formFilePath: path.join(__dirname, "../../../"), + enforceCsrf: false, + }); + + const { adapterCacheService } = server.services(); + adapterCacheService.getState = () => { + return Promise.resolve({ + metadata: { + change_requests: { + "VcyKVN": ["Assessor Feedback"] + } + } + }); + }; + + const response = await server.inject({ + method: 'GET', + url: '/sample-page.test/project-name', + }); + + $ = cheerio.load(response.payload); + + expect($("h2").text()).to.contain("Change request"); + }); + + test("should no display change request banner on page without feedback", async () => { + server = await createServer({ + formFileName: "sample-page.test.json", + formFilePath: path.join(__dirname, "../../../"), + enforceCsrf: false, + }); + + const { adapterCacheService } = server.services(); + adapterCacheService.getState = () => { + return Promise.resolve({ + metadata: { + change_requests: { + "no_found": ["Assessor Feedback"] + } + } + }); + }; + + const response = await server.inject({ + method: 'GET', + url: '/sample-page.test/project-name', + }); + + $ = cheerio.load(response.payload); + + expect($("h2").text()).to.not.contain("Change request"); + }); }); diff --git a/runner/test/cases/server/sample-page.test.json b/runner/test/cases/server/sample-page.test.json new file mode 100644 index 00000000..4af643f2 --- /dev/null +++ b/runner/test/cases/server/sample-page.test.json @@ -0,0 +1,84 @@ +{ + "startPage": "/project-details", + "pages": [ + { + "path": "/summary", + "title": "Summary", + "components": [], + "next": [], + "section": "FabDefault", + "controller": "./pages/summary.js" + }, + { + "title": "Project details", + "path": "/project-details", + "components": [ + { + "name": "orUXQc", + "options": {}, + "type": "Para", + "content": "\n\n
\n
    \n
  • project name
  • \n
  • project overview
  • \n
\n
", + "schema": {} + } + ], + "next": [ + { + "path": "/project-name" + } + ], + "controller": "./pages/start.js" + }, + { + "path": "/project-overview", + "title": "Project overview", + "components": [ + { + "options": {}, + "type": "FreeTextField", + "title": "Project overview", + "hint": "Give a brief summary of your project, including what you hope to achieve", + "schema": {}, + "name": "KJtdhs" + } + ], + "next": [ + { + "path": "/summary" + } + ], + "section": "FabDefault" + }, + { + "path": "/project-name", + "title": "Project name", + "components": [ + { + "options": {}, + "type": "TextField", + "title": "Project name", + "hint": "", + "schema": {}, + "name": "VcyKVN" + } + ], + "next": [ + { + "path": "/project-overview" + } + ], + "section": "FabDefault" + } + ], + "lists": [], + "conditions": [], + "sections": [ + { + "name": "FabDefault", + "title": "Default section", + "hideTitle": true + } + ], + "outputs": [], + "skipSummary": false, + "name": "Apply for Sample Fund" +}