From 25727e05a359e020a75b1f62e56b5fe968c88e45 Mon Sep 17 00:00:00 2001 From: G Date: Wed, 27 Sep 2023 11:32:36 +0100 Subject: [PATCH 1/5] MAP-178 implement new DPS header and footers --- assets/sass/application.sass | 4 +- assets/sass/components/_footer.scss | 3 + assets/sass/components/_header-bar.scss | 127 ++++++++++++++++++ assets/sass/local.sass | 100 +------------- cypress.config.ts | 6 + feature.env | 3 + helm_deploy/use-of-force/templates/_envs.tpl | 9 ++ helm_deploy/values-dev.yaml | 3 + helm_deploy/values-preprod.yaml | 3 + helm_deploy/values-prod.yaml | 3 + .../integration/commonComponents.cy.js | 29 ++++ integration-tests/integration/login.cy.js | 2 +- .../reviewer/view-completed-incidents.cy.js | 40 ------ .../statements/view-your-statements.cy.js | 30 ----- integration-tests/mockApis/components.js | 44 ++++++ .../createReport/reportUseOfForcePage.js | 4 + integration-tests/pages/page.js | 5 +- .../yourStatements/yourStatementsPage.js | 2 +- package-lock.json | 2 +- server/app.ts | 50 ++++--- server/config.js | 14 ++ server/data/feComponentsClient.test.ts | 47 +++++++ server/data/feComponentsClient.ts | 24 ++++ server/data/index.ts | 7 + .../middleware/feComponentsMiddleware.test.ts | 49 +++++++ server/middleware/feComponentsMiddleware.ts | 21 +++ server/middleware/setUpEnvironmentName.ts | 8 ++ server/routes/__test/appSetup.ts | 2 + server/services/feComponentsService.test.ts | 48 +++++++ server/services/feComponentsService.ts | 15 +++ server/services/index.ts | 5 + server/utils/nunjucksSetup.ts | 3 + server/utils/utils.ts | 8 ++ server/views/partials/footer.njk | 21 +-- server/views/partials/header.html | 55 +++++--- server/views/partials/layout.html | 24 +++- 36 files changed, 585 insertions(+), 235 deletions(-) create mode 100644 assets/sass/components/_footer.scss create mode 100644 assets/sass/components/_header-bar.scss create mode 100644 integration-tests/integration/commonComponents.cy.js create mode 100644 integration-tests/mockApis/components.js create mode 100644 server/data/feComponentsClient.test.ts create mode 100644 server/data/feComponentsClient.ts create mode 100644 server/middleware/feComponentsMiddleware.test.ts create mode 100644 server/middleware/feComponentsMiddleware.ts create mode 100644 server/middleware/setUpEnvironmentName.ts create mode 100644 server/services/feComponentsService.test.ts create mode 100644 server/services/feComponentsService.ts diff --git a/assets/sass/application.sass b/assets/sass/application.sass index 9fa60923..d5cf16e3 100755 --- a/assets/sass/application.sass +++ b/assets/sass/application.sass @@ -7,4 +7,6 @@ $path: "/assets/images/" @import 'govuk/all' @import 'moj/all' -@import './local' \ No newline at end of file +@import './local' +@import './components/header-bar' +@import './components/footer' \ No newline at end of file diff --git a/assets/sass/components/_footer.scss b/assets/sass/components/_footer.scss new file mode 100644 index 00000000..7550a831 --- /dev/null +++ b/assets/sass/components/_footer.scss @@ -0,0 +1,3 @@ +.govuk-footer { + min-height: 160px; + } \ No newline at end of file diff --git a/assets/sass/components/_header-bar.scss b/assets/sass/components/_header-bar.scss new file mode 100644 index 00000000..593f2387 --- /dev/null +++ b/assets/sass/components/_header-bar.scss @@ -0,0 +1,127 @@ +.hmpps-header { + @include govuk-responsive-padding(3, 'top'); + @include govuk-responsive-padding(3, 'bottom'); + background-color: govuk-colour('black'); + color: govuk-colour('white'); + + &__container { + @include govuk-width-container; + display: flex; + justify-content: space-between; + align-items: center; + } + + &__logo { + @include govuk-responsive-margin(2, 'right'); + position: relative; + top: -2px; + fill: govuk-colour('white'); + } + + &__title { + @include govuk-responsive-padding(3, 'right'); + display: flex; + align-items: center; + + &__organisation-name { + @include govuk-responsive-margin(2, 'right'); + @include govuk-font($size: 24, $weight: 'bold'); + display: flex; + align-items: center; + } + + &__service-name { + @include govuk-responsive-margin(2, 'right'); + @include govuk-font(24); + display: none; + + @include govuk-media-query($from: desktop) { + display: flex; + align-items: center; + } + } + } + + &__link { + @include govuk-link-common; + @include govuk-link-style-default; + + text-underline-offset: 0.1em; + + &:link, + &:visited, + &:active { + color: govuk-colour('white'); + text-decoration: none; + } + + &:hover { + text-decoration: underline; + } + + &:focus { + color: govuk-colour('black'); + + svg { + fill: govuk-colour('black'); + } + } + + &__sub-text { + @include govuk-font(16); + display: none; + + @include govuk-media-query($from: tablet) { + display: block; + } + } + } + + &__navigation { + display: flex; + flex-direction: column; + align-items: flex-end; + list-style: none; + margin: 0; + padding: 0; + + @include govuk-media-query($from: tablet) { + flex-direction: row; + align-items: center; + } + + &__item { + @include govuk-font(19); + margin-bottom: govuk-spacing(1); + text-align: right; + + @include govuk-media-query($from: tablet) { + @include govuk-responsive-margin(4, 'right'); + @include govuk-responsive-padding(4, 'right'); + margin-bottom: 0; + border-right: 1px solid govuk-colour('white'); + } + + a { + display: inline-block; + } + + &:last-child { + margin-right: 0; + border-right: 0; + padding-right: 0; + } + + } + } + + @media print { + display: none; + } + } + + .govuk-phase-banner { + @include govuk-width-container; + border: none; + } + \ No newline at end of file diff --git a/assets/sass/local.sass b/assets/sass/local.sass index e3333fa2..2b2012f5 100644 --- a/assets/sass/local.sass +++ b/assets/sass/local.sass @@ -1,107 +1,9 @@ @import "palette" -// Header - div.govuk-width-container max-width: 1150px margin-top: 15px -.header - @include govuk-responsive-padding(3, 'top') - @include govuk-responsive-padding(3, 'bottom') - background-color: govuk-colour('black') - - &__container - @include govuk-width-container - display: flex - justify-content: space-between - align-items: center - max-width: 1150px - - &__logo - @include govuk-responsive-margin(2, 'right') - position: relative - top: -2px - fill: govuk-colour('white') - - &__title - @include govuk-responsive-padding(3, 'right') - display: flex - align-items: center - - &__organisation-name - @include govuk-responsive-margin(2, 'right') - @include govuk-font($size: 24, $weight: 'bold') - display: flex - align-items: center - - &__service-name - display: none - @include govuk-font(24) - - @include govuk-media-query($from: desktop) - display: inline-block - - &__link - @include govuk-link-common - @include govuk-link-style-default - - &:link, - &:visited, - &:active - color: govuk-colour('white') - text-decoration: none - - &:hover - text-decoration: underline - - &:focus - color: govuk-colour('black') - - svg - fill: govuk-colour('black') - - &__sub-text - @include govuk-font(16) - display: none - - @include govuk-media-query($from: tablet) - display: block - - &__navigation - display: flex - flex-direction: column - align-items: flex-end - list-style: none - margin: 0 - padding: 0 - - @include govuk-media-query($from: tablet) - flex-direction: row - align-items: center - - &__item - @include govuk-font(19) - margin-bottom: govuk-spacing(1) - text-align: right - - @include govuk-media-query($from: tablet) - @include govuk-responsive-margin(4, 'right') - @include govuk-responsive-padding(4, 'right') - margin-bottom: 0 - border-right: 1px solid govuk-colour('white') - - a - display: inline-block - - &:last-child - margin-right: 0 - border-right: 0 - padding-right: 0 - - @media print - display: none - // Task list pattern .app-task-list @@ -384,7 +286,7 @@ dt.summary-list__key__wider display: none !important @media print - .no-print, .govuk-header, .govuk-footer + .no-print, .hmpps-header, .govuk-footer display: none !important .print diff --git a/cypress.config.ts b/cypress.config.ts index bdf5f477..02878cec 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -9,6 +9,8 @@ import { stringToHash } from './server/utils/hash' import db from './integration-tests/db/db' import config from './dist/server/config' +import components from './integration-tests/mockApis/components' + export default defineConfig({ chromeWebSecurity: false, fixturesFolder: 'integration-tests/fixtures', @@ -67,6 +69,10 @@ export default defineConfig({ stubVerifyToken: active => auth.stubVerifyToken(active), stubClientCredentialsToken: auth.stubClientCredentialsToken, + + stubComponents: components.stubComponents, + + stubComponentsFail: components.stubComponentsFail, }) }, baseUrl: 'http://localhost:3007', diff --git a/feature.env b/feature.env index aa3314df..5deeb834 100644 --- a/feature.env +++ b/feature.env @@ -11,6 +11,9 @@ DB_PORT=5432 NODE_ENV=development EXIT_LOCATION_URL=/ +DPS_URL=/ +COMPONENT_API_URL=http://localhost:9091/components +ENVIRONMENT_NAME=DEV REDIS_HOST=localhost REDIS_PORT=6379 diff --git a/helm_deploy/use-of-force/templates/_envs.tpl b/helm_deploy/use-of-force/templates/_envs.tpl index 0042981c..81157666 100644 --- a/helm_deploy/use-of-force/templates/_envs.tpl +++ b/helm_deploy/use-of-force/templates/_envs.tpl @@ -118,6 +118,15 @@ env: - name: FEATURE_FLAG_OUTAGE_BANNER_ENABLED value: {{ .Values.env.FEATURE_FLAG_OUTAGE_BANNER_ENABLED | quote }} + - name: DPS_URL + value: {{ .Values.env.DPS_URL | quote }} + + - name: COMPONENT_API_URL + value: {{ .Values.env.COMPONENT_API_URL | quote }} + + - name: ENVIRONMENT_NAME + value: {{ .Values.env.ENVIRONMENT_NAME | quote }} + - name: REDIS_HOST valueFrom: secretKeyRef: diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index 150d05a5..c0d88384 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -24,3 +24,6 @@ env: TOKENVERIFICATION_API_URL: https://token-verification-api-dev.prison.service.justice.gov.uk TOKENVERIFICATION_API_ENABLED: true FEATURE_FLAG_OUTAGE_BANNER_ENABLED: false + DPS_URL: https://digital-dev.prison.service.justice.gov.uk/ + COMPONENT_API_URL: "https://frontend-components-dev.hmpps.service.justice.gov.uk" + ENVIRONMENT_NAME: 'DEV' diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index 4d863bb5..0d84c56e 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -24,6 +24,9 @@ env: TOKENVERIFICATION_API_URL: https://token-verification-api-preprod.prison.service.justice.gov.uk TOKENVERIFICATION_API_ENABLED: true FEATURE_FLAG_OUTAGE_BANNER_ENABLED: false + DPS_URL: https://digital-preprod.prison.service.justice.gov.uk/ + COMPONENT_API_URL: "https://frontend-components-preprod.hmpps.service.justice.gov.uk" + ENVIRONMENT_NAME: 'PRE-PRODUCTION' allow_list: office: "217.33.148.210/32" diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index 404d2fbd..237835ac 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -24,6 +24,9 @@ env: TOKENVERIFICATION_API_URL: https://token-verification-api.prison.service.justice.gov.uk TOKENVERIFICATION_API_ENABLED: true FEATURE_FLAG_OUTAGE_BANNER_ENABLED: false + DPS_URL: https://digital.prison.service.justice.gov.uk/ + COMPONENT_API_URL: "https://frontend-components.hmpps.service.justice.gov.uk" + ENVIRONMENT_NAME: '' allow_list: office: "217.33.148.210/32" diff --git a/integration-tests/integration/commonComponents.cy.js b/integration-tests/integration/commonComponents.cy.js new file mode 100644 index 00000000..59b1e9d8 --- /dev/null +++ b/integration-tests/integration/commonComponents.cy.js @@ -0,0 +1,29 @@ +const { offender } = require('../mockApis/data') +const ReportUseOfForcePage = require('../pages/createReport/reportUseOfForcePage') + +context('Report use of force page', () => { + beforeEach(() => { + cy.task('reset') + cy.task('stubLogin', { firstName: 'James', lastName: 'Stuart' }) + cy.task('stubOffenderDetails', offender) + }) + + it('New components should exist', () => { + cy.task('stubComponents') + cy.login() + + const page = ReportUseOfForcePage.visit(offender.bookingId) + page.commonComponentsHeader().should('exist') + page.commonComponentsFooter().should('exist') + }) + + it('New components should not exist', () => { + cy.task('stubComponentsFail') + cy.login() + + const page = ReportUseOfForcePage.visit(offender.bookingId) + page.commonComponentsHeader().should('not.exist') + page.commonComponentsFooter().should('not.exist') + page.fallbackHeaderUserName().contains('J. Stuart') + }) +}) diff --git a/integration-tests/integration/login.cy.js b/integration-tests/integration/login.cy.js index 88581c69..70e97545 100644 --- a/integration-tests/integration/login.cy.js +++ b/integration-tests/integration/login.cy.js @@ -31,7 +31,7 @@ context('Login functionality', () => { cy.login() const yourStatements = YourStatements.verifyOnPage() yourStatements.loggedInName().contains('J. Stuart') - cy.request('/logout/').its('body').should('contain', 'Sign in') + cy.request('/sign-out').its('body').should('contain', 'Sign in') }) it('New user login should log current user out', () => { diff --git a/integration-tests/integration/reviewer/view-completed-incidents.cy.js b/integration-tests/integration/reviewer/view-completed-incidents.cy.js index b63574db..77a6d7aa 100644 --- a/integration-tests/integration/reviewer/view-completed-incidents.cy.js +++ b/integration-tests/integration/reviewer/view-completed-incidents.cy.js @@ -157,46 +157,6 @@ context('A use of force reviewer can view completed incidents at the current age completedIncidentsPage.getNoCompleteRows().contains('There are no completed incidents') }) - it('PII not sent to survey', () => { - cy.task('stubReviewerLogin') - cy.login() - - cy.task('stubOffenders', [offender3, offender2, offender]) - - cy.task('seedReport', { - status: ReportStatus.COMPLETE, - submittedDate: moment().toDate(), - incidentDate: moment('2019-01-22 09:57:40.000'), - agencyId: offender.agencyId, - bookingId: offender.bookingId, - sequenceNumber: 1, - involvedStaff: [ - { - username: 'TEST_USER', - name: 'TEST_USER name', - email: 'TEST_USER@gov.uk', - }, - ], - }) - - const completedIncidentsPage = CompletedIncidentsPage.goTo() - completedIncidentsPage.getCompleteRows().should('have.length', 1) - - completedIncidentsPage.filter.prisonNumber().type('A1234AC') - completedIncidentsPage.filter.reporter().type('James') - completedIncidentsPage.filter.dateFrom().type('22/01/2019') - completedIncidentsPage.filter.dateTo().type('25/01/2019') - completedIncidentsPage.filter.apply().click() - - completedIncidentsPage - .feedbackBannerLink() - .should('contain', 'Give feedback on this service') - .should('have.attr', 'href') - .then(href => { - expect(href).to.equal('https://eu.surveymonkey.com/r/GYB8Y9Q?source=localhost/completed-incidents') - }) - }) - it('A normal user cannot view all incidents', () => { cy.task('stubLogin') cy.login() diff --git a/integration-tests/integration/statements/view-your-statements.cy.js b/integration-tests/integration/statements/view-your-statements.cy.js index 2a52f775..5e61a99b 100644 --- a/integration-tests/integration/statements/view-your-statements.cy.js +++ b/integration-tests/integration/statements/view-your-statements.cy.js @@ -182,34 +182,4 @@ context('A user views their statements list', () => { ]) ) }) - - it('Page should display the feedback banner with the correct href', () => { - cy.task('stubOffenders', [offender]) - cy.login() - - cy.task( - 'seedReports', - Array.from(Array(62)).map((_, i) => ({ - status: ReportStatus.SUBMITTED, - bookingId: i, - involvedStaff: [ - { - username: 'TEST_USER', - name: 'TEST_USER name', - email: 'TEST_USER@gov.uk', - }, - ], - })) - ) - - const yourStatementsPage = YourStatementsPage.goTo() - - yourStatementsPage - .feedbackBannerLink() - .should('contain', 'Give feedback on this service') - .should('have.attr', 'href') - .then(href => { - expect(href).to.equal('https://eu.surveymonkey.com/r/GYB8Y9Q?source=localhost/your-statements') - }) - }) }) diff --git a/integration-tests/mockApis/components.js b/integration-tests/mockApis/components.js new file mode 100644 index 00000000..549178c2 --- /dev/null +++ b/integration-tests/mockApis/components.js @@ -0,0 +1,44 @@ +const { stubFor } = require('./wiremock') + +module.exports = { + stubComponents: () => { + return stubFor({ + request: { + method: 'GET', + urlPattern: '/components/components\\?component=header&component=footer', + }, + response: { + status: 200, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + jsonBody: { + header: { + html: '

Common Components Header

', + javascript: ['/common-components/header.js'], + css: ['/common-components/header.css'], + }, + footer: { + html: '', + javascript: ['/common-components/footer.js'], + css: ['/common-components/footer.css'], + }, + }, + }, + }) + }, + stubComponentsFail: () => { + return stubFor({ + request: { + method: 'GET', + urlPattern: '/components/components\\?component=header&component=footer', + }, + response: { + status: 500, + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + }, + }) + }, +} diff --git a/integration-tests/pages/createReport/reportUseOfForcePage.js b/integration-tests/pages/createReport/reportUseOfForcePage.js index 7003ec44..bb1dd916 100644 --- a/integration-tests/pages/createReport/reportUseOfForcePage.js +++ b/integration-tests/pages/createReport/reportUseOfForcePage.js @@ -53,6 +53,10 @@ const tasklistPage = () => evidence: 'NOT_STARTED', }) }, + + fallbackHeaderUserName: () => cy.get('[data-qa=header-user-name]'), + commonComponentsHeader: () => cy.get('header').contains('Common Components Header'), + commonComponentsFooter: () => cy.get('footer').contains('Common Components Footer'), }) module.exports = { diff --git a/integration-tests/pages/page.js b/integration-tests/pages/page.js index 4f9e89e2..5e3f0212 100644 --- a/integration-tests/pages/page.js +++ b/integration-tests/pages/page.js @@ -1,6 +1,5 @@ export default (name, pageObject = {}, checkOnPage = () => cy.get('h1').contains(name)) => { - const logout = () => cy.get('[data-qa=logout]') - const feedbackBannerLink = () => cy.get('[data-qa="feedback-banner"]').find('a') + const logout = () => cy.get('[data-qa=signOut]') checkOnPage() - return { ...pageObject, checkStillOnPage: checkOnPage, logout, feedbackBannerLink } + return { ...pageObject, checkStillOnPage: checkOnPage, logout } } diff --git a/integration-tests/pages/yourStatements/yourStatementsPage.js b/integration-tests/pages/yourStatements/yourStatementsPage.js index 932c037c..0ab3f514 100644 --- a/integration-tests/pages/yourStatements/yourStatementsPage.js +++ b/integration-tests/pages/yourStatements/yourStatementsPage.js @@ -24,7 +24,7 @@ const yourStatementsPage = () => .invoke('attr', 'href') .then(link => link.match(/\/(.*?)\/your-statement/)[1]), }), - loggedInName: () => cy.get('[data-qa=logged-in-name]'), + loggedInName: () => cy.get('[data-qa=header-user-name]'), }) module.exports = { diff --git a/package-lock.json b/package-lock.json index b784fd08..565863fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ }, "engines": { "node": "^18", - "npm": "^8" + "npm": "<10" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/server/app.ts b/server/app.ts index 17e21998..d3bf30e0 100755 --- a/server/app.ts +++ b/server/app.ts @@ -1,4 +1,4 @@ -import express, { Express, RequestHandler, Response } from 'express' +import express, { Express, RequestHandler, Response, Request } from 'express' import { v4 as uuidv4 } from 'uuid' import helmet from 'helmet' import noCache from 'nocache' @@ -31,6 +31,8 @@ import errorHandler from './errorHandler' import config from './config' import unauthenticatedRoutes from './routes/unauthenticated' import asyncMiddleware from './middleware/asyncMiddleware' +import getFrontendComponents from './middleware/feComponentsMiddleware' +import setUpEnvironmentName from './middleware/setUpEnvironmentName' const authenticationMiddleware: RequestHandler = authenticationMiddlewareFactory( tokenVerifierFactory(config.apis.tokenVerification) @@ -54,6 +56,8 @@ export default function createApp(services: Services): Express { // View Engine Configuration app.set('view engine', 'html') + setUpEnvironmentName(app) + nunjucksSetup(app) // Server Configuration @@ -66,23 +70,37 @@ export default function createApp(services: Services): Express { res.locals.cspNonce = crypto.randomBytes(16).toString('base64') next() }) + + const scriptSrc = [ + "'self'", + 'code.jquery.com', + "'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='", + (_req: Request, res: Response) => `'nonce-${res.locals.cspNonce}'`, + ] + const styleSrc = ["'self'", 'code.jquery.com', (_req: Request, res: Response) => `'nonce-${res.locals.cspNonce}'`] + const imgSrc = ["'self'", 'data:', 'www.googletagmanager.com', 'www.google-analytics.com', 'https://code.jquery.com'] + const fontSrc = ["'self'"] + const connectSrc = ["'self'", 'www.googletagmanager.com', 'www.google-analytics.com'] + + if (config.apis.frontendComponents.url) { + scriptSrc.push(config.apis.frontendComponents.url) + styleSrc.push(config.apis.frontendComponents.url) + imgSrc.push(config.apis.frontendComponents.url) + fontSrc.push(config.apis.frontendComponents.url) + } + app.use( helmet({ - crossOriginEmbedderPolicy: false, + crossOriginEmbedderPolicy: true, contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], // Hash allows inline script pulled in from https://github.com/alphagov/govuk-frontend/blob/master/src/govuk/template.njk - scriptSrc: [ - "'self'", - (req, res: Response) => `'nonce-${res.locals.cspNonce}'`, - 'code.jquery.com', - "'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='", - ], - imgSrc: ["'self'", 'www.googletagmanager.com', 'www.google-analytics.com', 'https://code.jquery.com'], - connectSrc: ["'self'", 'www.googletagmanager.com', 'www.google-analytics.com'], - styleSrc: ["'self'", 'code.jquery.com'], - fontSrc: ["'self'"], + scriptSrc, + imgSrc, + connectSrc, + styleSrc, + fontSrc, }, }, }) @@ -213,7 +231,7 @@ export default function createApp(services: Services): Express { // JWT token refresh app.use(async (req, res, next) => { - if (req.user && req.originalUrl !== '/logout') { + if (req.user && req.originalUrl !== '/sign-out') { const timeToRefresh = new Date() > req.user.refreshTime if (timeToRefresh) { try { @@ -229,7 +247,7 @@ export default function createApp(services: Services): Express { req.user.refreshTime = newToken.refreshTime } catch (error) { logger.error(`Token refresh error: ${req.user.username}`, error.stack) - return res.redirect('/logout') + return res.redirect('/sign-out') } } } @@ -265,7 +283,7 @@ export default function createApp(services: Services): Express { ) app.use( - '/logout', + '/sign-out', asyncMiddleware((req, res) => { if (req.user) { req.logout(() => req.session.destroy()) @@ -276,6 +294,8 @@ export default function createApp(services: Services): Express { app.use(populateCurrentUser(services.userService)) + app.get('*', getFrontendComponents(services.feComponentsService)) + app.use(unauthenticatedRoutes(services)) app.use(authorisationMiddleware) diff --git a/server/config.js b/server/config.js index 6f13efad..155a3509 100755 --- a/server/config.js +++ b/server/config.js @@ -104,6 +104,19 @@ module.exports = { }, enabled: process.env.TOKENVERIFICATION_API_ENABLED === 'true', }, + frontendComponents: { + url: get('COMPONENT_API_URL', 'http://localhost:8082', requiredInProduction), + timeout: { + response: get('COMPONENT_API_TIMEOUT_SECONDS', 2000), + deadline: get('COMPONENT_API_TIMEOUT_SECONDS', 2000), + }, + agent: { + maxSockets: 100, + maxFreeSockets: 10, + freeSocketTimeout: 30000, + }, + }, + digitalPrisonServiceUrl: get('DPS_URL', 'http://localhost:3000', requiredInProduction), }, domain: `${get('INGRESS_URL', 'http://localhost:3000', requiredInProduction)}`, links: { @@ -118,4 +131,5 @@ module.exports = { environment: get('TAG_MANAGER_ENVIRONMENT', ''), // The additional GTM snippet string that configures a non-prod environment }, featureFlagOutageBannerEnabled: get('FEATURE_FLAG_OUTAGE_BANNER_ENABLED', 'false', requiredInProduction) === 'true', + environmentName: get('ENVIRONMENT_NAME', ''), } diff --git a/server/data/feComponentsClient.test.ts b/server/data/feComponentsClient.test.ts new file mode 100644 index 00000000..f382c960 --- /dev/null +++ b/server/data/feComponentsClient.test.ts @@ -0,0 +1,47 @@ +import nock from 'nock' +import config from '../config' +import FeComponentsClient, { Component } from './feComponentsClient' +import restClientBuilder from '.' + +describe('feComponentsClient', () => { + let fakeComponentsApi + let componentsClient: FeComponentsClient + const token = 'token-1' + + beforeEach(() => { + fakeComponentsApi = nock(config.apis.frontendComponents.url) + componentsClient = restClientBuilder('feComponentApi', config.apis.frontendComponents, FeComponentsClient)(token) + }) + + afterEach(() => { + jest.resetAllMocks() + nock.cleanAll() + }) + + describe('getComponents', () => { + it('should return data from api', async () => { + const response: { data: { header: Component; footer: Component } } = { + data: { + header: { + html: '
', + css: [], + javascript: [], + }, + footer: { + html: '', + css: [], + javascript: [], + }, + }, + } + + fakeComponentsApi + .get('/components?component=header&component=footer') + .matchHeader('x-user-token', token) + .reply(200, response) + + const output = await componentsClient.getComponents(['header', 'footer'], token) + expect(output).toEqual(response) + }) + }) +}) diff --git a/server/data/feComponentsClient.ts b/server/data/feComponentsClient.ts new file mode 100644 index 00000000..2e9bb7c3 --- /dev/null +++ b/server/data/feComponentsClient.ts @@ -0,0 +1,24 @@ +import type { RestClient } from './restClient' + +export interface Component { + html: string + css: string[] + javascript: string[] +} + +export type AvailableComponent = 'header' | 'footer' + +export default class FeComponentsClient { + constructor(private restClient: RestClient) {} + + async getComponents( + components: T, + token: string + ): Promise> { + return this.restClient.get({ + path: `/components`, + query: `component=${components.join('&component=')}`, + headers: { 'x-user-token': token }, + }) as Promise> + } +} diff --git a/server/data/index.ts b/server/data/index.ts index 4362803c..f5ac0322 100755 --- a/server/data/index.ts +++ b/server/data/index.ts @@ -8,6 +8,7 @@ import DraftReportClient from './draftReportClient' import StatementsClient from './statementsClient' import PrisonClient from './prisonClient' +import FeComponentsClient from './feComponentsClient' import config from '../config' import { AuthClient, systemTokenBuilder } from './authClient' @@ -41,6 +42,11 @@ export const dataAccess = { systemToken: systemTokenBuilder(new TokenStore(createRedisClient({ legacyMode: false }))), authClientBuilder: ((token: string) => new AuthClient(token)) as RestClientBuilder, prisonClientBuilder: restClientBuilder('prisonApi', config.apis.prison, PrisonClient), + feComponentsClientBuilder: restClientBuilder( + 'feComponentApi', + config.apis.frontendComponents, + FeComponentsClient + ), prisonerSearchClientBuilder: restClientBuilder('prisonerSearchApi', config.apis.prisonerSearch, PrisonerSearchClient), } @@ -51,6 +57,7 @@ export { RestClientBuilder, IncidentClient, PrisonClient, + FeComponentsClient, PrisonerSearchClient, AuthClient, DraftReportClient, diff --git a/server/middleware/feComponentsMiddleware.test.ts b/server/middleware/feComponentsMiddleware.test.ts new file mode 100644 index 00000000..0f1f70b9 --- /dev/null +++ b/server/middleware/feComponentsMiddleware.test.ts @@ -0,0 +1,49 @@ +import { Response } from 'express' +import feComponentsMiddleware from './feComponentsMiddleware' +import FeComponentsService from '../services/feComponentsService' +import { Services } from '../services' +import config from '../config' +import logger from '../../log' + +jest.mock('../services/feComponentsService') +jest.mock('../../log') + +const feComponentsService = new FeComponentsService(null) as jest.Mocked +feComponentsService.getFeComponents = jest.fn().mockResolvedValue({}) + +let req +let res +const next = jest.fn() + +describe('feComponentsMiddleware', () => { + beforeEach(() => { + jest.resetAllMocks() + res = { locals: { user: {} } } as unknown as Response + res.locals.user.token = 'token-1' + }) + + test('Should call components service correctly', async () => { + const header = { html: '
', javascript: [], css: [] } + const footer = { html: '
', javascript: [], css: [] } + feComponentsService.getFeComponents.mockResolvedValue({ header, footer }) + + await feComponentsMiddleware(feComponentsService)(req, res, next) + + expect(feComponentsService.getFeComponents).toHaveBeenCalledWith(['header', 'footer'], 'token-1') + expect(res.locals.feComponents).toEqual({ + header: '
', + footer: '
', + cssIncludes: [], + jsIncludes: [], + }) + expect(next).toHaveBeenCalled() + }) + + test('Should log errors', async () => { + const error = new Error('Failed to retrieve front end components') + feComponentsService.getFeComponents.mockRejectedValue(error) + + await feComponentsMiddleware(feComponentsService as unknown as Services)(req, res, next) + expect(logger.error).toBeCalledWith(error, 'Failed to retrieve front end components') + }) +}) diff --git a/server/middleware/feComponentsMiddleware.ts b/server/middleware/feComponentsMiddleware.ts new file mode 100644 index 00000000..c8474e2b --- /dev/null +++ b/server/middleware/feComponentsMiddleware.ts @@ -0,0 +1,21 @@ +import type { RequestHandler } from 'express' +import logger from '../../log' + +export default function getFrontendComponents(feComponentsService): RequestHandler { + return async (req, res, next) => { + try { + const { header, footer } = await feComponentsService.getFeComponents(['header', 'footer'], res.locals.user.token) + + res.locals.feComponents = { + header: header.html, + footer: footer.html, + cssIncludes: [...header.css, ...footer.css], + jsIncludes: [...header.javascript, ...footer.javascript], + } + return next() + } catch (error) { + logger.error(error, 'Failed to retrieve front end components') + return next() + } + } +} diff --git a/server/middleware/setUpEnvironmentName.ts b/server/middleware/setUpEnvironmentName.ts new file mode 100644 index 00000000..5eb88800 --- /dev/null +++ b/server/middleware/setUpEnvironmentName.ts @@ -0,0 +1,8 @@ +/* eslint-disable no-param-reassign */ +import express from 'express' +import config from '../config' + +export default (app: express.Express) => { + app.locals.environmentName = config.environmentName + app.locals.environmentNameColour = config.environmentName === 'PRE-PRODUCTION' ? 'govuk-tag--green' : '' +} diff --git a/server/routes/__test/appSetup.ts b/server/routes/__test/appSetup.ts index 450b8caf..065d0910 100644 --- a/server/routes/__test/appSetup.ts +++ b/server/routes/__test/appSetup.ts @@ -19,6 +19,7 @@ import type { DraftReportService, LocationService, PrisonerSearchService, + FeComponentsService, } from '../../services' import UserService from '../../services/userService' import unauthenticatedRoutes from '../unauthenticated' @@ -129,6 +130,7 @@ export const appWithAllRoutes = ( userService: {} as UserService, // eslint-disable-next-line @typescript-eslint/no-explicit-any signInService: {} as any, + feComponentsService: {} as FeComponentsService, ...overrides, } const authenticated = authenticatedRoutes(authenticationMiddleware, services) diff --git a/server/services/feComponentsService.test.ts b/server/services/feComponentsService.test.ts new file mode 100644 index 00000000..97c6e1c4 --- /dev/null +++ b/server/services/feComponentsService.test.ts @@ -0,0 +1,48 @@ +import { FeComponentsClient } from '../data' +import { AvailableComponent, Component } from '../data/feComponentsClient' +import FeComponentsService from './feComponentsService' + +jest.mock('../data') + +const FeComponentsClientBuilder = jest.fn() +const token = 'token' +const components = ['header', 'footer'] +const feComponentsClient = new FeComponentsClient(null) as jest.Mocked +let feComponentsService + +beforeEach(() => { + FeComponentsClientBuilder.mockReturnValue(feComponentsClient) + feComponentsService = new FeComponentsService(FeComponentsClientBuilder) +}) + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('feComponentsService', () => { + describe('getFeComponents', () => { + it('should use token', async () => { + feComponentsClient.getComponents.mockResolvedValue({} as Record) + + await feComponentsService.getFeComponents(components, token) + expect(FeComponentsClientBuilder).toBeCalledWith(token) + }) + it('should call upstream client correctly', async () => { + const response = { + header: { + html: '', + css: [], + javascript: [], + }, + footer: { + html: '', + css: [], + javascript: [], + }, + } + feComponentsClient.getComponents.mockResolvedValue(response) + const result = await feComponentsService.getFeComponents(components, token) + expect(result).toEqual(response) + }) + }) +}) diff --git a/server/services/feComponentsService.ts b/server/services/feComponentsService.ts new file mode 100644 index 00000000..7445850e --- /dev/null +++ b/server/services/feComponentsService.ts @@ -0,0 +1,15 @@ +import { RestClientBuilder } from '../data' +import FeComponentsClient, { AvailableComponent, Component } from '../data/feComponentsClient' + +export default class FeComponentsService { + constructor(private readonly feComponentsClientBuilder: RestClientBuilder) {} + + async getFeComponents( + components: T, + token: string + ): Promise> { + const feComponentsClient = this.feComponentsClientBuilder(token) + const allComponents = await feComponentsClient.getComponents(components, token) + return allComponents + } +} diff --git a/server/services/index.ts b/server/services/index.ts index 4397e230..6aab58f6 100755 --- a/server/services/index.ts +++ b/server/services/index.ts @@ -21,12 +21,14 @@ import createSignInService from '../authentication/signInService' import { notificationServiceFactory } from './notificationService' import { DraftInvolvedStaffService } from './drafts/draftInvolvedStaffService' +import FeComponentsService from './feComponentsService' const { authClientBuilder, draftReportClient, incidentClient, prisonClientBuilder, + feComponentsClientBuilder, prisonerSearchClientBuilder, statementsClient, systemToken, @@ -97,6 +99,7 @@ const reviewService = new ReviewService( ) const prisonerSearchService = new PrisonerSearchService(prisonerSearchClientBuilder, prisonClientBuilder, systemToken) const reportDetailBuilder = new ReportDetailBuilder(involvedStaffService, locationService, offenderService, systemToken) +const feComponentsService = new FeComponentsService(feComponentsClientBuilder) export const services = { involvedStaffService, @@ -111,6 +114,7 @@ export const services = { locationService, reportDetailBuilder, draftReportService, + feComponentsService, } export type Services = typeof services @@ -126,4 +130,5 @@ export { PrisonerSearchService, ReportDetailBuilder, UserService, + FeComponentsService, } diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index c2790da4..9243d7a3 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -8,6 +8,7 @@ import config from '../config' import { PageMetaData } from './page' import { LabelledValue } from '../config/types' import { SectionStatus } from '../services/drafts/reportStatusChecker' +import { initialiseName } from './utils' const { googleTagManager: { key: tagManagerKey, environment: tagManagerEnvironment }, @@ -172,5 +173,7 @@ export default function configureNunjucks(app: Express.Application): nunjucks.En return SectionStatus.NOT_STARTED }) + njkEnv.addFilter('initialiseName', initialiseName) + return njkEnv } diff --git a/server/utils/utils.ts b/server/utils/utils.ts index 03f0d42a..6cb2aec8 100644 --- a/server/utils/utils.ts +++ b/server/utils/utils.ts @@ -49,3 +49,11 @@ export function forenameToInitial(name: string): string { if (!name) return null return `${name.charAt(0)}. ${name.split(' ').pop()}` } + +export const initialiseName = (fullName?: string): string | null => { + // this check is for the authError page + if (!fullName) return null + + const array = fullName.split(' ') + return `${array[0][0]}. ${array.reverse()[0]}` +} diff --git a/server/views/partials/footer.njk b/server/views/partials/footer.njk index 8ad91074..3d14dfa1 100644 --- a/server/views/partials/footer.njk +++ b/server/views/partials/footer.njk @@ -1,20 +1 @@ -{% from "govuk/components/footer/macro.njk" import govukFooter %} -{% from "./feedbackBanner.njk" import feedbackBanner %} - -{{ - feedbackBanner({ - currentUrlPath: currentUrlPath, - hostname: hostname - }) -}} - -{{ govukFooter({ - meta: { - items: [ - { - href: "/get-help", - text: "Get help" - } - ] - } -}) }} \ No newline at end of file +
\ No newline at end of file diff --git a/server/views/partials/header.html b/server/views/partials/header.html index afa19688..708721eb 100644 --- a/server/views/partials/header.html +++ b/server/views/partials/header.html @@ -1,34 +1,45 @@ - diff --git a/server/views/partials/layout.html b/server/views/partials/layout.html index 4322a33c..4a24fe7f 100644 --- a/server/views/partials/layout.html +++ b/server/views/partials/layout.html @@ -23,10 +23,26 @@ +{% if feComponents.jsInclude %} + {% for js in feComponents.jsIncludes %} + + {% endfor %} +{% endif %} + +{% if feComponents.cssIncludes %} + {% for css in feComponents.cssIncludes %} + + {% endfor %} +{% endif %} + {% endblock %} {% block header %} - {% include "./header.html" %} + {% if feComponents.header %} + {{ feComponents.header | safe }} + {% else %} + {% include "./header.html" %} + {% endif %} {% endblock %} {% block bodyStart %} @@ -55,5 +71,9 @@ {% endblock %} {% block footer %} - {% include "./footer.njk" %} + {% if feComponents.footer %} + {{ feComponents.footer | safe }} + {% else %} + {% include "./footer.njk" %} + {% endif %} {% endblock %} From 7f9dacbfe754bbda4a46a4d45dd0a2be3f4f073a Mon Sep 17 00:00:00 2001 From: GCheema <50441412+GurnankCheema@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:02:35 +0100 Subject: [PATCH 2/5] MAP-178 remove get help (/get-help) page as new footer has phone number for app support already --- server/config.js | 2 - .../helpReportingUseOfForce.test.ts | 32 ---------------- .../helpReportingUseOfForce.ts | 11 ------ server/routes/unauthenticated/index.ts | 3 -- server/views/help-reporting-use-of-force.html | 38 ------------------- 5 files changed, 86 deletions(-) delete mode 100644 server/routes/unauthenticated/helpReportingUseOfForce.test.ts delete mode 100644 server/routes/unauthenticated/helpReportingUseOfForce.ts delete mode 100644 server/views/help-reporting-use-of-force.html diff --git a/server/config.js b/server/config.js index 155a3509..17f06720 100755 --- a/server/config.js +++ b/server/config.js @@ -123,8 +123,6 @@ module.exports = { emailUrl: get('EMAIL_LOCATION_URL', 'http://localhost:3000', requiredInProduction), exitUrl: get('EXIT_LOCATION_URL', '/', requiredInProduction), }, - supportTelephone: '0800 917 5148', - supportExtension: '#6598', https: production, googleTagManager: { key: get('TAG_MANAGER_KEY', null), diff --git a/server/routes/unauthenticated/helpReportingUseOfForce.test.ts b/server/routes/unauthenticated/helpReportingUseOfForce.test.ts deleted file mode 100644 index 1111cb01..00000000 --- a/server/routes/unauthenticated/helpReportingUseOfForce.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import request from 'supertest' -import { appWithAllRoutes } from '../__test/appSetup' -import config from '../../config' - -describe('GET Help reporting a use of force incident', () => { - afterEach(() => { - jest.resetAllMocks() - }) - - describe('GET Help reporting a use of force incident', () => { - test("should render the 'Help reporting a use of force incident' page", () => { - return request(appWithAllRoutes(undefined, undefined, false)) - .get(`/get-help`) - .expect('Content-Type', /html/) - .expect(res => { - expect(res.status).toBe(200) - expect(res.text).toContain('Help reporting a use of force incident') - }) - }) - - test('should display the telephone numbers', () => { - return request(appWithAllRoutes(undefined, undefined, true)) - .get(`/get-help`) - .expect('Content-Type', /html/) - .expect(res => { - expect(res.status).toBe(200) - expect(res.text).toContain(config.supportTelephone) - expect(res.text).toContain(config.supportExtension) - }) - }) - }) -}) diff --git a/server/routes/unauthenticated/helpReportingUseOfForce.ts b/server/routes/unauthenticated/helpReportingUseOfForce.ts deleted file mode 100644 index 9af30c34..00000000 --- a/server/routes/unauthenticated/helpReportingUseOfForce.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Request, Response } from 'express' -import config from '../../config' - -export default function HelpReportingUseOfForce() { - return (req: Request, res: Response): void => { - res.render(`help-reporting-use-of-force.html`, { - supportTelephone: config.supportTelephone, - supportExtension: config.supportExtension, - }) - } -} diff --git a/server/routes/unauthenticated/index.ts b/server/routes/unauthenticated/index.ts index f073eb2b..3c42b2e9 100644 --- a/server/routes/unauthenticated/index.ts +++ b/server/routes/unauthenticated/index.ts @@ -5,7 +5,6 @@ import asyncMiddleware from '../../middleware/asyncMiddleware' import type { Services } from '../../services' import RemovalRequest from './requestRemoval' import csrf from '../../middleware/csrfMiddleware' -import HelpReportingUseOfForce from './helpReportingUseOfForce' export default function UnauthenticatedRoutes(services: Services): Router { const { reportService, statementService, systemToken } = services @@ -20,7 +19,5 @@ export default function UnauthenticatedRoutes(services: Services): Router { router.get('/already-removed', asyncMiddleware(removalRequest.viewAlreadyRemoved)) router.get('/removal-already-requested', asyncMiddleware(removalRequest.viewRemovalAlreadyRequested)) - router.get('/get-help', asyncMiddleware(HelpReportingUseOfForce())) - return router } diff --git a/server/views/help-reporting-use-of-force.html b/server/views/help-reporting-use-of-force.html deleted file mode 100644 index bf4f726b..00000000 --- a/server/views/help-reporting-use-of-force.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "./partials/layout.html" %} -{% from "govuk/components/footer/macro.njk" import govukFooter %} - -{% block content %} -
-
-
-

Help reporting a use of force incident

- -

You can use this service to report a use of force incident in a prison and add a statement to an existing report.

- -

Reporting use of force on a prisoner in a different establishment

-

You can report a use of force on a prisoner in any establishment.

-

Select ‘Use of force incidents’ from the DPS homepage and click ‘Report use of force on a prisoner in another prison’.

- -

Deleting duplicate reports for the same incident

-

You’ll need to contact your local use of force coordinator to ask them to delete the duplicate report.

- -

If you have been incorrectly added to a use of force report

-

You can request to be removed from a use of force report by following the link in any of the emails you have received about the incident. A use of force coordinator will review your request, and you’ll receive an email when you have been removed from the report.

- -

Adding a member of staff to an existing report

-

Contact your local use of force coordinator to add staff members to an existing report.

- -

Adding a regional resource to a report

-

If a regional resource was a witness to the use of force, you can add them as a witness on the ‘Incident details’ page.

-

If a regional resource was involved in the use of force, you'll need to add them as a staff member involved in the use of force incident. The regional resource will need a DPS account before you can add them to the report.

- -

Technical support

-

For technical support, you should contact the helpdesk by calling {{supportTelephone}}, or {{supportExtension}} from inside an establishment.

-
-
-
-{% endblock %} - -{% block footer %} - {{ govukFooter({}) }} -{% endblock %} From 5c0097eac7288c957196318de7417f44277ef38f Mon Sep 17 00:00:00 2001 From: GCheema <50441412+GurnankCheema@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:17:25 +0100 Subject: [PATCH 3/5] MAP-178 remove remaining remnants of feedback banner --- assets/sass/local.sass | 8 -------- server/views/partials/feedbackBanner.njk | 11 ----------- 2 files changed, 19 deletions(-) delete mode 100644 server/views/partials/feedbackBanner.njk diff --git a/assets/sass/local.sass b/assets/sass/local.sass index 2b2012f5..ba46af69 100644 --- a/assets/sass/local.sass +++ b/assets/sass/local.sass @@ -1,9 +1,5 @@ @import "palette" -div.govuk-width-container - max-width: 1150px - margin-top: 15px - // Task list pattern .app-task-list @@ -339,9 +335,5 @@ dt.summary-list__key__wider button margin-bottom: 0 -.feedback-banner - background: $govuk-brand-colour - padding: govuk-spacing(4) govuk-spacing(6) - .govuk-notification-banner__content > * max-width: 700px; diff --git a/server/views/partials/feedbackBanner.njk b/server/views/partials/feedbackBanner.njk deleted file mode 100644 index b96aefc1..00000000 --- a/server/views/partials/feedbackBanner.njk +++ /dev/null @@ -1,11 +0,0 @@ -{% macro feedbackBanner(params) %} - -{% endmacro %} \ No newline at end of file From ecb5b108e6d062af763b4b9e18ce796aa4485954 Mon Sep 17 00:00:00 2001 From: GCheema <50441412+GurnankCheema@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:18:16 +0100 Subject: [PATCH 4/5] MAP-178 fix dps link in fallback header and css 1. reverted css relating to contenr width because the Use of force reports' page needs a greater width to accommodate 4 tabs and a link. Have made the new headers the same width. 2. reverted crossOriginEmbedderPolicy to false as otherwise the date widget will not work. 3. Defined the digitalPrisonServiceUrldp var in nunjucks setup so that it could be accessed by the fallback header --- assets/sass/application.sass | 4 ++-- assets/sass/components/_footer.scss | 3 ++- assets/sass/local.sass | 7 +++++++ server/app.ts | 2 +- server/middleware/feComponentsMiddleware.test.ts | 1 - server/utils/nunjucksSetup.ts | 1 + server/views/partials/header.html | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/assets/sass/application.sass b/assets/sass/application.sass index d5cf16e3..dd58fd7d 100755 --- a/assets/sass/application.sass +++ b/assets/sass/application.sass @@ -7,6 +7,6 @@ $path: "/assets/images/" @import 'govuk/all' @import 'moj/all' -@import './local' @import './components/header-bar' -@import './components/footer' \ No newline at end of file +@import './components/footer' +@import './local' diff --git a/assets/sass/components/_footer.scss b/assets/sass/components/_footer.scss index 7550a831..c0dd3732 100644 --- a/assets/sass/components/_footer.scss +++ b/assets/sass/components/_footer.scss @@ -1,3 +1,4 @@ .govuk-footer { min-height: 160px; - } \ No newline at end of file + } + \ No newline at end of file diff --git a/assets/sass/local.sass b/assets/sass/local.sass index ba46af69..ce7a3981 100644 --- a/assets/sass/local.sass +++ b/assets/sass/local.sass @@ -1,5 +1,12 @@ @import "palette" +div.govuk-width-container + max-width: 1170px + margin-top: 15px + +.hmpps-header__container + max-width: 1170px + // Task list pattern .app-task-list diff --git a/server/app.ts b/server/app.ts index d3bf30e0..26421dfe 100755 --- a/server/app.ts +++ b/server/app.ts @@ -91,7 +91,7 @@ export default function createApp(services: Services): Express { app.use( helmet({ - crossOriginEmbedderPolicy: true, + crossOriginEmbedderPolicy: false, contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], diff --git a/server/middleware/feComponentsMiddleware.test.ts b/server/middleware/feComponentsMiddleware.test.ts index 0f1f70b9..0ef07639 100644 --- a/server/middleware/feComponentsMiddleware.test.ts +++ b/server/middleware/feComponentsMiddleware.test.ts @@ -2,7 +2,6 @@ import { Response } from 'express' import feComponentsMiddleware from './feComponentsMiddleware' import FeComponentsService from '../services/feComponentsService' import { Services } from '../services' -import config from '../config' import logger from '../../log' jest.mock('../services/feComponentsService') diff --git a/server/utils/nunjucksSetup.ts b/server/utils/nunjucksSetup.ts index 9243d7a3..06b62e0a 100644 --- a/server/utils/nunjucksSetup.ts +++ b/server/utils/nunjucksSetup.ts @@ -39,6 +39,7 @@ export default function configureNunjucks(app: Express.Application): nunjucks.En njkEnv.addGlobal('authUrl', config.apis.oauth2.url) njkEnv.addGlobal('apiClientId', config.apis.oauth2.apiClientId) njkEnv.addGlobal('featureFlagOutageBannerEnabled', config.featureFlagOutageBannerEnabled) + njkEnv.addGlobal('digitalPrisonServiceUrl', config.apis.digitalPrisonServiceUrl) // eslint-disable-next-line default-param-last njkEnv.addFilter('findError', (array: Error[] = [], formFieldId: string) => { diff --git a/server/views/partials/header.html b/server/views/partials/header.html index 708721eb..cb2b96ba 100644 --- a/server/views/partials/header.html +++ b/server/views/partials/header.html @@ -2,7 +2,7 @@