Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/server/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const FORM_VERSION_METADATA_KEY = '$$__formVersion'
export const PREVIEW_PATH_PREFIX = '/preview'
export const FORM_PREFIX = ''
export const EXTERNAL_STATE_PAYLOAD = 'EXTERNAL_STATE_PAYLOAD'
Expand Down
30 changes: 22 additions & 8 deletions src/server/plugins/engine/beta/form-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jest.mock('../pageControllers/index.ts', () => {
})

jest.mock('../helpers.ts', () => ({
__esModule: true,
...jest.requireActual('../helpers.ts'),
getCacheService: (...args: unknown[]) => mockGetCacheService(...args),
checkEmailAddressForLiveFormSubmission: (...args: unknown[]) =>
mockCheckEmailAddressForLiveFormSubmission(...args)
Expand Down Expand Up @@ -134,10 +134,17 @@ describe('getFormModel helper', () => {
class CustomController extends PageController {}
const controllers = { CustomController }
const metadata = {
id: 'form-meta-123',
versions: [{ versionNumber: 17 }]
id: 'form-meta-123'
}
const definition = {
pages: [{ path: '/start' }],
metadata: {
$$__formVersion: {
versionNumber: 17,
createdAt: new Date('2024-10-15T10:00:00Z')
}
}
}
const definition = { pages: [{ path: '/start' }] }
let formsService: FormsService
let services: Services
let formModelInstance: { id: string }
Expand Down Expand Up @@ -176,7 +183,7 @@ describe('getFormModel helper', () => {
definition,
{
basePath: slug,
versionNumber: metadata.versions[0].versionNumber,
versionNumber: 17,
ordnanceSurveyApiKey: undefined,
formId: metadata.id
},
Expand Down Expand Up @@ -210,11 +217,18 @@ describe('getFormModel helper', () => {

describe('resolveFormModel helper', () => {
const slug = 'tb-origin'
const definition = { pages: [] }
const definition = {
pages: [],
metadata: {
$$__formVersion: {
versionNumber: 9,
createdAt: new Date('2024-10-15T10:00:00Z')
}
}
}
const metadata = {
id: 'metadata-123',
live: { updatedAt: new Date('2024-10-15T10:00:00Z') },
versions: [{ versionNumber: 9 }],
notificationEmail: 'enrique.chase@defra.gov.uk'
}
let server: Request['server']
Expand Down Expand Up @@ -274,7 +288,7 @@ describe('resolveFormModel helper', () => {
definition,
expect.objectContaining({
basePath: 'forms/preview/live/tb-origin',
versionNumber: metadata.versions[0].versionNumber,
versionNumber: 9,
ordnanceSurveyApiKey: 'os-api-key',
formId: metadata.id
}),
Expand Down
13 changes: 7 additions & 6 deletions src/server/plugins/engine/beta/form-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { isEqual } from 'date-fns'
import { PREVIEW_PATH_PREFIX } from '~/src/server/constants.js'
import {
checkEmailAddressForLiveFormSubmission,
getCacheService
getCacheService,
getFormVersion
} from '~/src/server/plugins/engine/helpers.js'
import { FormModel } from '~/src/server/plugins/engine/models/index.js'
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
Expand All @@ -27,7 +28,6 @@ export interface FormModelOptions {
services?: Services
controllers?: Record<string, typeof PageController>
basePath?: string
versionNumber?: number
ordnanceSurveyApiKey?: string
formId?: string
routePrefix?: string
Expand All @@ -53,8 +53,6 @@ export async function getFormModel(
const formState = resolveState(state)

const metadata = await formsService.getFormMetadata(slug)
const versionNumber =
options.versionNumber ?? metadata.versions?.[0]?.versionNumber

const definition = await formsService.getFormDefinition(
metadata.id,
Expand All @@ -67,6 +65,8 @@ export async function getFormModel(
)
}

const versionNumber = getFormVersion(definition)?.versionNumber

return new FormModel(
definition,
{
Expand Down Expand Up @@ -182,14 +182,15 @@ export async function resolveFormModel(
const routePrefix =
options.routePrefix ?? server.realm.modifiers.route.prefix

const versionNumber = getFormVersion(definition)?.versionNumber

const model = new FormModel(
definition,
{
basePath:
options.basePath ??
buildBasePath(routePrefix, slug, formState, isPreview),
versionNumber:
options.versionNumber ?? metadata.versions?.[0]?.versionNumber,
versionNumber,
ordnanceSurveyApiKey: options.ordnanceSurveyApiKey,
formId: options.formId ?? metadata.id
},
Expand Down
17 changes: 17 additions & 0 deletions src/server/plugins/engine/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { type Schema, type ValidationErrorItem } from 'joi'
import { Liquid } from 'liquidjs'

import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
import { FORM_VERSION_METADATA_KEY } from '~/src/server/constants.js'
import {
getAnswer,
type Field
Expand Down Expand Up @@ -416,6 +417,22 @@ export function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {
* If the page doesn't have a title, set it from the title of the first form component
* @param def - the form definition
*/
export interface FormVersionMetadata {
versionNumber: number
createdAt: Date
}

/**
* Extracts form version metadata from a form definition
*/
export function getFormVersion(
definition: Pick<FormDefinition, 'metadata'>
): FormVersionMetadata | undefined {
return definition.metadata?.[FORM_VERSION_METADATA_KEY] as
| FormVersionMetadata
| undefined
}

export function setPageTitles(def: FormDefinition) {
def.pages.forEach((page) => {
if (!page.title) {
Expand Down
54 changes: 54 additions & 0 deletions src/server/plugins/engine/outputFormatters/adapter/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,60 @@ describe('Adapter v1 formatter', () => {
})

describe('version metadata handling', () => {
it('should prefer $$__formVersion from definition metadata over formMetadata.versions', () => {
const definitionWithFormVersion = {
...definition,
metadata: {
$$__formVersion: {
versionNumber: 42,
createdAt: new Date('2024-06-01T00:00:00.000Z')
}
}
}

const modelWithFormVersion = new FormModel(definitionWithFormVersion, {
basePath: 'test'
})

const contextWithFormVersion = modelWithFormVersion.getFormContext(
request,
state
)

const formMetadata: Partial<FormMetadata> = {
id: 'form-123',
slug: 'test-form',
title: 'Test Form',
notificationEmail: 'test@example.com',
versions: [
{
versionNumber: 1,
createdAt: new Date('2024-01-01T00:00:00.000Z')
}
]
}

const formStatus = {
isPreview: false,
state: FormStatus.Live
}

const body = format(
contextWithFormVersion,
items,
modelWithFormVersion,
submitResponse,
formStatus,
formMetadata as FormMetadata
)
const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload

expect(parsedBody.meta.versionMetadata).toEqual({
versionNumber: 42,
createdAt: '2024-06-01T00:00:00.000Z'
})
})

it('should include versionMetadata when context has submittedVersionNumber and formMetadata has versions', () => {
const formMetadata: Partial<FormMetadata> = {
id: 'form-123',
Expand Down
12 changes: 7 additions & 5 deletions src/server/plugins/engine/outputFormatters/adapter/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import {
type SubmitResponsePayload
} from '@defra/forms-model'

import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
import {
getFormVersion,
type checkFormStatus
} from '~/src/server/plugins/engine/helpers.js'
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
import { type DetailItem } from '~/src/server/plugins/engine/models/types.js'
import { categoriseData } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
Expand All @@ -28,10 +31,9 @@ export function format(

const { main: v2Main, ...v2Data } = categoriseData(items)

const versionMetadata = getVersionMetadata(
context.submittedVersionNumber,
formMetadata
)
const versionMetadata =
getFormVersion(model.def) ??
getVersionMetadata(context.submittedVersionNumber, formMetadata)

const meta: FormAdapterSubmissionMessageMeta = {
schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
Expand Down
Loading