Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/485768 page conditions #669

Merged
merged 10 commits into from
Jan 21, 2025
7 changes: 6 additions & 1 deletion src/server/plugins/engine/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ControllerPath } from '@defra/forms-model'
import { ControllerPath, Engine } from '@defra/forms-model'
import Boom from '@hapi/boom'
import { type ResponseToolkit } from '@hapi/hapi'
import { format, parseISO } from 'date-fns'
Expand Down Expand Up @@ -155,6 +155,11 @@
}

export function getStartPath(model?: FormModel) {
if (model?.engine === Engine.V2) {

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Component view models › hides the page title for single form component pages

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:178:15)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Component view models › returns the component view models for the page

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:178:15)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Condition evaluation context › filters state by journey pages

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:296:38)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Condition evaluation context › combines state values for date fields

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:381:40)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Form journey › Next getter › returns the next page links

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:417:23)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Form journey › Next › returns the next page path

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:417:23)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Form journey › Summary › returns the summary path

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:417:23)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Route handlers › supports GET route handler

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:505:15)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Component view models › hides the page title for single form component pages

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:178:15)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Component view models › returns the component view models for the page

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:178:15)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Condition evaluation context › filters state by journey pages

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:296:38)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Condition evaluation context › combines state values for date fields

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:381:40)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Form journey › Next getter › returns the next page links

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:417:23)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Form journey › Next › returns the next page path

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:417:23)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Form journey › Summary › returns the summary path

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:417:23)

Check failure on line 158 in src/server/plugins/engine/helpers.ts

View workflow job for this annotation

GitHub Actions / Unit tests

QuestionPageController › Route handlers › supports GET route handler

TypeError: Cannot read properties of undefined (reading 'V2') at V2 (src/server/plugins/engine/helpers.ts:158:32) at QuestionPageController.getStartPath (src/server/plugins/engine/pageControllers/PageController.ts:139:24) at FormModel.getStartPath [as getFormContext] (src/server/plugins/engine/models/FormModel.ts:214:28) at Object.getFormContext (src/server/plugins/engine/pageControllers/QuestionPageController.test.ts:505:15)
const startPath = normalisePath(model.def.pages.at(0)?.path)
return startPath ? `/${startPath}` : ControllerPath.Start
}

const startPath = normalisePath(model?.def.startPage)
return startPath ? `/${startPath}` : ControllerPath.Start
}
Expand Down
21 changes: 18 additions & 3 deletions src/server/plugins/engine/models/FormModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ConditionsModel,
ControllerPath,
ControllerType,
Engine,
formDefinitionSchema,
hasRepeater,
type ConditionWrapper,
Expand Down Expand Up @@ -34,10 +35,12 @@ import {
import { FormAction } from '~/src/server/routes/types.js'
import { merge } from '~/src/server/services/cacheService.js'

/**
* Responsible for instantiating the {@link PageControllerClass} and condition context from a form JSON
*/
export class FormModel {
/**
* Responsible for instantiating the {@link PageControllerClass} and condition context from a form JSON
*/
/** The runtime engine that should be used */
engine: Engine

/** the entire form JSON as an object */
def: FormDefinition
Expand Down Expand Up @@ -78,6 +81,7 @@ export class FormModel {
]
})

this.engine = def.engine
this.def = def
this.lists = def.lists
this.sections = def.sections
Expand Down Expand Up @@ -228,6 +232,17 @@ export class FormModel {
// Find start page
let nextPage = findPage(this, startPath)

// For the V2 engine, we need to initialise `evaluationState` to null
// for all keys. This is because the current condition evaluation
// library (eval-expr) will throw if an expression uses a key that is undefined.
if (this.engine === Engine.V2) {
for (const page of this.pages) {
for (const key of page.keys) {
context.evaluationState[key] = null
}
}
}

// Walk form pages from start
while (nextPage) {
const { collection, pageDef } = nextPage
Expand Down
7 changes: 7 additions & 0 deletions src/server/plugins/engine/pageControllers/PageController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
normalisePath
} from '~/src/server/plugins/engine/helpers.js'
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
import { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'
import {
type FormContext,
type PageViewModelBase
Expand All @@ -39,6 +40,7 @@ export class PageController {
pageDef: Page
title: string
section?: Section
condition?: ExecutableCondition
collection?: ComponentCollection
viewName = 'index'

Expand All @@ -55,6 +57,11 @@ export class PageController {
this.section = model.sections.find(
(section) => section.name === pageDef.section
)

// Resolve condition
if (pageDef.condition) {
this.condition = model.conditions[pageDef.condition]
}
}

get path() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {
ComponentType,
ControllerType,
Engine,
hasComponents,
hasNext,
hasRepeater,
Expand Down Expand Up @@ -181,6 +183,33 @@ export class QuestionPageController extends PageController {
// Walk from summary page (no next links) to status page
let defaultPath = path === summaryPath ? statusPath : undefined

if (model.engine === Engine.V2) {
if (this.pageDef.controller !== ControllerType.Terminal) {
const { pages } = this.model
const pageIndex = pages.indexOf(this)

// The "next" page is the first found after the current which is
// either unconditional or has a condition that evaluates to "true"
const nextPage = pages.slice(pageIndex + 1).find((page) => {
const { condition } = page

if (condition) {
const conditionResult = condition.fn(evaluationState)

if (!conditionResult) {
return false
}
}

return true
})

return nextPage?.path ?? defaultPath
} else {
return defaultPath
}
}

const nextLink = next.find((link) => {
const { condition } = link

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type PageTerminal } from '@defra/forms-model'
import Boom from '@hapi/boom'
import { type ResponseObject, type ResponseToolkit } from '@hapi/hapi'

import { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
import { type FormContext } from '~/src/server/plugins/engine/types.js'
import { type FormRequestPayload } from '~/src/server/routes/types.js'

export class TerminalPageController extends QuestionPageController {
declare pageDef: PageTerminal

makePostRouteHandler(): (
request: FormRequestPayload,
context: FormContext,
h: Pick<ResponseToolkit, 'redirect' | 'view'>
) => Promise<ResponseObject> {
throw Boom.methodNotAllowed('POST method not allowed for terminal pages')
}
}
7 changes: 6 additions & 1 deletion src/server/plugins/engine/pageControllers/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
RepeatPageController,
StartPageController,
StatusPageController,
SummaryPageController
SummaryPageController,
TerminalPageController
} from '~/src/server/plugins/engine/pageControllers/index.js'
import definition from '~/test/form/definitions/blank.js'

Expand All @@ -37,6 +38,10 @@ describe('Page controller helpers', () => {
controller = QuestionPageController
break

case ControllerType.Terminal:
controller = TerminalPageController
break

case ControllerType.Repeat:
controller = RepeatPageController
break
Expand Down
4 changes: 4 additions & 0 deletions src/server/plugins/engine/pageControllers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export function createPage(model: FormModel, pageDef: Page) {
controller = new PageControllers.QuestionPageController(model, pageDef)
break

case ControllerType.Terminal:
controller = new PageControllers.TerminalPageController(model, pageDef)
break

case ControllerType.Summary:
controller = new PageControllers.SummaryPageController(model, pageDef)
break
Expand Down
1 change: 1 addition & 0 deletions src/server/plugins/engine/pageControllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export {
QuestionPageController, // Export alongside alias
QuestionPageController as PageController
} from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'
export { TerminalPageController } from '~/src/server/plugins/engine/pageControllers/TerminalPageController.js'
export { SummaryPageController } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'
export { StatusPageController } from '~/src/server/plugins/engine/pageControllers/StatusPageController.js'
export { FileUploadPageController } from '~/src/server/plugins/engine/pageControllers/FileUploadPageController.js'
Expand Down
Loading