Skip to content
4 changes: 3 additions & 1 deletion src/server/plugins/engine/components/LocationFieldBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ export abstract class LocationFieldBase extends FormComponent {
const viewModel = super.getViewModel(context)

if (this.instructionText) {
const { tComponent } = context.translator
return {
...viewModel,
instructionText: this.instructionText
instructionText:
tComponent(this.def, 'instructionText') || this.instructionText
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/server/plugins/engine/components/LocationFieldHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,15 @@ export function getLocationFieldViewModel(
}

if (component.options.instructionText) {
const { t, tComponent } = context.translator

return {
...result,
instructionText: component.options.instructionText
instructionSummary: t('components.locationFieldBase.howToFind'),
instructionText:
tComponent(component.def, 'instructionText') ||
component.options.instructionText,
t
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/server/plugins/engine/form-availability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ describe('form-availability', () => {
expect(() =>
assertFormAvailable(
{ ...metadata, offline: false },
undefined,
FormStatus.Live,
false
)
).not.toThrow()
expect(() =>
assertFormAvailable(
{ ...metadata, offline: undefined },
undefined,
FormStatus.Live,
false
)
Expand All @@ -30,13 +32,15 @@ describe('form-availability', () => {
expect(() =>
assertFormAvailable(
{ ...metadata, offline: false },
undefined,
FormStatus.Draft,
true
)
).not.toThrow()
expect(() =>
assertFormAvailable(
{ ...metadata, offline: undefined },
undefined,
FormStatus.Live,
true
)
Expand All @@ -47,6 +51,7 @@ describe('form-availability', () => {
expect(() =>
assertFormAvailable(
{ ...metadata, offline: true },
undefined,
FormStatus.Live,
false
)
Expand Down
10 changes: 8 additions & 2 deletions src/server/plugins/engine/form-availability.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { FormStatus, type FormMetadata } from '@defra/forms-model'
import {
FormStatus,
type FormDefinition,
type FormMetadata
} from '@defra/forms-model'
import Boom from '@hapi/boom'

export interface OfflineBoomData {
offline: true
metadata: FormMetadata
definition: FormDefinition | undefined
}

/**
Expand All @@ -13,6 +18,7 @@ export interface OfflineBoomData {
*/
export function assertFormAvailable(
metadata: FormMetadata,
definition: FormDefinition | undefined,
formState: FormStatus,
isPreview: boolean
): void {
Expand All @@ -21,7 +27,7 @@ export function assertFormAvailable(
formState === FormStatus.Live &&
!isPreview
) {
const data: OfflineBoomData = { offline: true, metadata }
const data: OfflineBoomData = { offline: true, metadata, definition }
throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
statusCode: 503,
data
Expand Down
6 changes: 4 additions & 2 deletions src/server/plugins/engine/form-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ export async function getFormModel(
const formState = resolveState(state)

const metadata = await formsService.getFormMetadata(slug)
assertFormAvailable(metadata, formState, isPreview)

const definition = await formsService.getFormDefinition(
metadata.id,
formState
)

assertFormAvailable(metadata, definition, formState, isPreview)

if (!definition) {
throw Boom.notFound(
`No definition found for form metadata ${metadata.id} (${slug}) ${state}`
Expand Down Expand Up @@ -145,7 +146,6 @@ export async function resolveFormModel(
const metadata = await formsService.getFormMetadata(slug)
const formState = resolveState(state)
const isPreview = options.isPreview ?? isPreviewState(state, options)
assertFormAvailable(metadata, formState, isPreview)
const stateMetadata = metadata[formState]

if (!stateMetadata) {
Expand Down Expand Up @@ -199,6 +199,8 @@ export async function resolveFormModel(
cache.set(cacheKey, entry)
}

assertFormAvailable(metadata, entry.model.def, formState, isPreview)

return entry.model
}

Expand Down
10 changes: 10 additions & 0 deletions src/server/plugins/engine/i18n/translations/cy.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@
"bannerForce": "Rhagolwg o ffurflen [[previewMode]] yw hon. Peidiwch â rhoi gwybodaeth bersonol.",
"bannerText": "Dyma ragolwg o dudalen ffurflen [[previewMode]] rydych chi'n ei golygu.",
"previousPages": "Mae'n dibynnu ar atebion o dudalennau cynharach yn y ffurflen. Yn y fersiwn fyw, bydd angen i ddefnyddwyr gwblhau'r cwestiynau hynny yn gyntaf."
},

"formUnavailable": {
"title": "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael",
"subHeading": "Nid yw '[[formTitle]]' ar gael mwyach.",
"contactDetailsHeading": "Manylion cyswllt ar gyfer [[organisationName]]",
"telephone": "Ffôn",
"callCharges": "Dysgwch am gostau galwadau",
"email": "E-bost",
"onlineContactForm": "Ffurflen gyswllt ar-lein"
}
},

Expand Down
10 changes: 10 additions & 0 deletions src/server/plugins/engine/i18n/translations/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@
"bannerForce": "This is a preview of a [[previewMode]] form. Do not enter personal information.",
"bannerText": "This is a preview of a [[previewMode]] form page you are editing.",
"previousPages": "It depends on answers from earlier pages in the form. In the live version, users will need to complete those questions first."
},

"formUnavailable": {
"title": "Sorry, this form is unavailable",
"subHeading": "'[[formTitle]]' is no longer available.",
"contactDetailsHeading": "Contact details for [[organisationName]]",
"telephone": "Telephone",
"callCharges": "Find out about call charges",
"email": "Email",
"onlineContactForm": "Online contact form"
}
},

Expand Down
1 change: 1 addition & 0 deletions src/server/plugins/engine/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type FormDefinitionTranslations = Record<
shortDescription: string
errorDescription: string
paymentDescription: string
instructionText: string
}>
>
sections: Record<string, Partial<Pick<Section, 'title'>>>
Expand Down
33 changes: 23 additions & 10 deletions src/server/plugins/engine/models/unavailable-view-model.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import { type FormMetadata } from '@defra/forms-model'

import { CY, EN_GB } from '~/src/server/constants.js'
import { unavailableViewModel } from '~/src/server/plugins/engine/models/unavailable-view-model.js'
import { metadata } from '~/test/fixtures/form.js'

describe('unavailableViewModel', () => {
it('should return the correct view model with basic metadata', () => {
const result = unavailableViewModel(metadata)
const result = unavailableViewModel(metadata, undefined, EN_GB)
expect(result).toEqual({
pageTitle: 'Sorry, this form is unavailable',
formTitle: 'Test form',
language: 'en-GB',
languages: [
{ code: 'en-GB', name: 'English' },
{ code: 'cy', name: 'Cymraeg' }
],
organisationName: 'Defra',
phoneLines: undefined
contact: undefined,
context: expect.any(Object)
})
})

it('should strip the organisation suffix if present', () => {
const result = unavailableViewModel({
...metadata,
organisation: 'Rural Payments Agency - RPA'
} as FormMetadata)
expect(result.organisationName).toBe('Rural Payments Agency - RPA')
it('should handle Welsh', () => {
const result = unavailableViewModel(metadata, undefined, CY)
expect(result).toEqual({
pageTitle: "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael",
formTitle: 'Test form',
language: 'cy',
languages: [
{ code: 'en-GB', name: 'English' },
{ code: 'cy', name: 'Cymraeg' }
],
organisationName: 'Defra',
contact: undefined,
context: expect.any(Object)
})
})
})
31 changes: 26 additions & 5 deletions src/server/plugins/engine/models/unavailable-view-model.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model'
import {
type FormDefinition,
type FormMetadata,
type FormMetadataContact
} from '@defra/forms-model'

import { createFormTranslator } from '~/src/server/plugins/engine/i18n/createFormTranslator.js'
import { getAvailableLanguages } from '~/src/server/plugins/engine/i18n/languages.js'
import { type Translator } from '~/src/server/plugins/engine/types/index.js'

export interface UnavailableViewModel {
pageTitle: string
formTitle: string
organisationName: string
contact?: FormMetadataContact
context: { translator: Translator }
language: string
languages: { code: string; name: string }[]
}

export function unavailableViewModel(
metadata: FormMetadata
metadata: FormMetadata,
definition: FormDefinition | undefined,
language: string
): UnavailableViewModel {
const translator = createFormTranslator(metadata, definition, language)
const { t, tForm } = translator
return {
pageTitle: 'Sorry, this form is unavailable',
formTitle: metadata.title,
pageTitle: t('pages.formUnavailable.title'),
formTitle: tForm('title') || metadata.title,
organisationName: metadata.organisation,
contact: metadata.contact
contact: metadata.contact,
context: { translator },
language,
// Always get Welsh and English
languages: getAvailableLanguages({
metadata: { translations: { cy: {} } }
} as unknown as FormDefinition)
}
}
38 changes: 30 additions & 8 deletions src/server/plugins/engine/unavailable-response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
type Server
} from '@hapi/hapi'

import { EN_GB } from '~/src/server/constants.js'
import * as availability from '~/src/server/plugins/engine/form-availability.js'
import { type Translator } from '~/src/server/plugins/engine/i18n/types.js'
import * as viewModel from '~/src/server/plugins/engine/models/unavailable-view-model.js'
import { registerUnavailableResponse } from '~/src/server/plugins/engine/unavailable-response.js'
import { metadata } from '~/test/fixtures/form.js'
Expand Down Expand Up @@ -56,7 +58,14 @@ describe('registerUnavailableResponse', () => {
statusCode: 503,
data: offlineData
})
const mockRequest = { response: mockResponse } as Request
const mockRequest = {
response: mockResponse,
server: {
plugins: {
'forms-engine-plugin': {}
}
}
} as Request

const mockViewResponse = {
header: jest.fn().mockReturnThis(),
Expand All @@ -75,18 +84,31 @@ describe('registerUnavailableResponse', () => {
.mockReturnValue({
pageTitle: 'Unavailable',
formTitle: 'Test',
organisationName: 'Defra'
organisationName: 'Defra',
language: EN_GB,
languages: [
{ code: 'en-GB', name: 'English' },
{ code: 'cy', name: 'Cymraeg' }
],
context: { translator: {} as unknown as Translator }
})

const result = await extensionHandler(mockRequest, mockH)

expect(availability.isOfflineBoom).toHaveBeenCalledWith(mockResponse)
expect(viewModel.unavailableViewModel).toHaveBeenCalledWith(metadata)
expect(mockH.view).toHaveBeenCalledWith('unavailable', {
pageTitle: 'Unavailable',
formTitle: 'Test',
organisationName: 'Defra'
})
expect(viewModel.unavailableViewModel).toHaveBeenCalledWith(
metadata,
undefined,
'en-GB'
)
expect(mockH.view).toHaveBeenCalledWith(
'unavailable',
expect.objectContaining({
pageTitle: 'Unavailable',
formTitle: 'Test',
organisationName: 'Defra'
})
)
expect(mockViewResponse.header).toHaveBeenCalledWith(
'Cache-Control',
'no-store, no-cache, must-revalidate'
Expand Down
8 changes: 6 additions & 2 deletions src/server/plugins/engine/unavailable-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { type Request, type ResponseToolkit, type Server } from '@hapi/hapi'

import { isOfflineBoom } from '~/src/server/plugins/engine/form-availability.js'
import { unavailableViewModel } from '~/src/server/plugins/engine/models/unavailable-view-model.js'
import { type AnyFormRequest } from '~/src/server/plugins/engine/types.js'
import { resolveLanguage } from '~/src/server/utils/utils.js'

/**
* Registers a server-wide onPreResponse extension that intercepts the offline
Expand All @@ -17,10 +19,12 @@ export function registerUnavailableResponse(server: Server) {
return h.continue
}

const { metadata } = response.data
const { metadata, definition } = response.data

const language = resolveLanguage(request as unknown as AnyFormRequest)

return h
.view('unavailable', unavailableViewModel(metadata))
.view('unavailable', unavailableViewModel(metadata, definition, language))
.header('Cache-Control', 'no-store, no-cache, must-revalidate')
.header('X-Robots-Tag', 'noindex, nofollow')
.code(200)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
{% set detailsHtml %}
{% if component.model.instructionText %}
{{ govukDetails({
summaryText: t('components.locationFieldBase.howToFind'),
summaryText: component.model.instructionSummary,
html: component.model.instructionText | markdown | safe,
classes: "govuk-!-margin-top-3"
}) }}
Expand Down
Loading
Loading