@defra/forms-engine-plugin 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/plugins/engine/models/FormModel.js +0 -4
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -4
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/state.d.ts +8 -7
- package/.server/server/plugins/engine/pageControllers/helpers/state.js +39 -12
- package/.server/server/plugins/engine/pageControllers/helpers/state.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +8 -1
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/models/FormModel.ts +0 -4
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -6
- package/src/server/plugins/engine/pageControllers/helpers/state.test.ts +80 -16
- package/src/server/plugins/engine/pageControllers/helpers/state.ts +57 -17
- package/src/server/plugins/engine/routes/index.test.ts +4 -2
- package/src/server/plugins/engine/routes/index.ts +13 -1
|
@@ -8,7 +8,6 @@ import { hasListFormField } from "../components/helpers/components.js";
|
|
|
8
8
|
import { todayAsDateOnly } from "../date-helper.js";
|
|
9
9
|
import { findPage, getError, getPage, setPageTitles } from "../helpers.js";
|
|
10
10
|
import { createPage } from "../pageControllers/helpers/pages.js";
|
|
11
|
-
import { copyNotYetValidatedState } from "../pageControllers/helpers/state.js";
|
|
12
11
|
import { validationOptions as opts } from "../pageControllers/validationOptions.js";
|
|
13
12
|
import * as defaultServices from "../services/index.js";
|
|
14
13
|
import { FormAction } from "../../../routes/types.js";
|
|
@@ -265,9 +264,6 @@ export class FormModel {
|
|
|
265
264
|
|
|
266
265
|
// Add paths for navigation
|
|
267
266
|
this.assignPaths(context);
|
|
268
|
-
|
|
269
|
-
// Handle restoration of payload from say a 'save-and-exit' request
|
|
270
|
-
copyNotYetValidatedState(request, context);
|
|
271
267
|
return context;
|
|
272
268
|
}
|
|
273
269
|
initialiseContext(context) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","copyNotYetValidatedState","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","formId","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getSection","section","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval-fork'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { copyNotYetValidatedState } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n formId: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n formId?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.formId = options.formId ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n getSection(nameOrId: string): Section | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.sections.find((section) => section.name === nameOrId)\n : this.sections.find((section) => section.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormSubmissionState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n // Handle restoration of payload from say a 'save-and-exit' request\n copyNotYetValidatedState(request, context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n action !== FormAction.Validate &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormSubmissionState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"AAAA,SACEA,aAAa,EACbC,eAAe,EACfC,cAAc,EACdC,cAAc,EACdC,aAAa,EACbC,6BAA6B,EAC7BC,oBAAoB,EACpBC,sBAAsB,EACtBC,sBAAsB,EACtBC,aAAa,EACbC,WAAW,EACXC,oBAAoB,EACpBC,WAAW,EACXC,aAAa,QAWR,oBAAoB;AAC3B,SAASC,GAAG,EAAEC,MAAM,QAAQ,UAAU;AACtC,SAASC,MAAM,QAAoB,gBAAgB;AACnD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB;AACA,SACEC,gBAAgB;AAGlB,SAASC,eAAe;AACxB,SACEC,QAAQ,EACRC,QAAQ,EACRC,OAAO,EACPC,aAAa;AAIf,SACEC,UAAU;AAGZ,SAASC,wBAAwB;AACjC,SAASC,iBAAiB,IAAIC,IAAI;AAClC,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AACnB,SAASC,KAAK;AAGd,MAAMC,MAAM,GAAGd,YAAY,CAAC,CAAC;AAE7B,OAAO,MAAMe,SAAS,CAAC;EACrB;EACAC,MAAM;EAENC,aAAa;;EAEb;EACAC,GAAG;EAEHC,KAAK;EACLC,QAAQ,GAA+B,EAAE;EACzCC,IAAI;EACJC,MAAM;EACNC,MAAM;EACNC,QAAQ;EACRC,aAAa;EACbC,oBAAoB;EACpBC,UAAU;EACVC,KAAK;EACLC,QAAQ;EAERC,WAAW;EACXC,UAAU;EAEVC,UAAU;EACVC,YAAY;EAEZC,eAAe;EACfC,iBAAiB;EAEjBC,OAAO;EACPC,YAAY;EAEZC,WAAWA,CACTpB,GAAoB,EACpBqB,OAKC,EACDV,QAAkB,GAAGlB,eAAe,EACpCmB,WAAmD,EACnD;IACA,IAAIU,MAAM,GAAGnD,sBAAsB;IAEnC,IAAI,CAAC6B,GAAG,CAACsB,MAAM,IAAItB,GAAG,CAACsB,MAAM,KAAKtD,aAAa,CAACuD,EAAE,EAAE;MAClD3B,MAAM,CAAC4B,IAAI,CACT,8BAA8BxB,GAAG,CAACG,IAAI,2HACxC,CAAC;MACDmB,MAAM,GAAGpD,oBAAoB;IAC/B;IAEA,MAAMuD,MAAM,GAAGH,MAAM,CAACI,QAAQ,CAAC1B,GAAG,EAAE;MAAE2B,UAAU,EAAE;IAAM,CAAC,CAAC;IAE1D,IAAIF,MAAM,CAACG,KAAK,EAAE;MAChB,MAAMH,MAAM,CAACG,KAAK;IACpB;;IAEA;IACA;IACA5B,GAAG,GAAG6B,eAAe,CAACJ,MAAM,CAACK,KAAK,CAAC;;IAEnC;IACA9B,GAAG,CAACC,KAAK,CAAC8B,IAAI,CAAC;MACbC,EAAE,EAAEhC,GAAG,CAACsB,MAAM,KAAKtD,aAAa,CAACuD,EAAE,GAAG9C,aAAa,GAAGD,WAAW;MACjE2B,IAAI,EAAE,SAAS;MACf8B,KAAK,EAAE,QAAQ;MACfC,IAAI,EAAE,SAAS;MACfC,KAAK,EAAE,CACL;QACEH,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,KAAK;QACXN,KAAK,EAAE;MACT,CAAC,EACD;QACEE,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,IAAI;QACVN,KAAK,EAAE;MACT,CAAC;IAEL,CAAC,CAAC;;IAEF;IACA1C,aAAa,CAACY,GAAG,CAAC;IAElB,IAAI,CAACF,MAAM,GAAGE,GAAG,CAACF,MAAM;IACxB,IAAI,CAACC,aAAa,GAAGC,GAAG,CAACsB,MAAM,IAAItD,aAAa,CAACuD,EAAE;IACnD,IAAI,CAACvB,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,KAAK,GAAGD,GAAG,CAACC,KAAK;IACtB,IAAI,CAACC,QAAQ,GAAGF,GAAG,CAACE,QAAQ;IAC5B,IAAI,CAACC,IAAI,GAAGH,GAAG,CAACG,IAAI,IAAI,EAAE;IAC1B,IAAI,CAACC,MAAM,GAAGiB,OAAO,CAACjB,MAAM,IAAI,EAAE;IAClC,IAAI,CAACC,MAAM,GAAGoB,MAAM,CAACK,KAAK;IAC1B,IAAI,CAACxB,QAAQ,GAAGe,OAAO,CAACf,QAAQ;IAChC,IAAI,CAACC,aAAa,GAAGc,OAAO,CAACd,aAAa;IAC1C,IAAI,CAACC,oBAAoB,GAAGa,OAAO,CAACb,oBAAoB;IACxD,IAAI,CAACC,UAAU,GAAG,CAAC,CAAC;IACpB,IAAI,CAACE,QAAQ,GAAGA,QAAQ;IACxB,IAAI,CAACC,WAAW,GAAGA,WAAW;IAE9B,IAAI,CAACC,UAAU,GAAG,IAAIwB,GAAG,CAACrC,GAAG,CAACU,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAACzB,UAAU,GAAG,IAAIuB,GAAG,CAACrC,GAAG,CAACC,KAAK,CAACqC,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACtC,IAAI,EAAEsC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC1B,YAAY,GAAG,IAAIsB,GAAG,CACzBrC,GAAG,CAACC,KAAK,CACNyC,MAAM,CAAED,IAAI,IAAKA,IAAI,CAACT,EAAE,CAAC,CAAC;IAC3B;IAAA,CACCM,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACT,EAAE,EAAYS,IAAI,CAAC,CAC5C,CAAC;IACD,IAAI,CAACzB,eAAe,GAAG,IAAIqB,GAAG,CAC5BrC,GAAG,CAACU,KAAK,CACNgC,MAAM,CAACrE,aAAa,CAAC,CACrBsE,OAAO,CAAEJ,IAAI,IACZA,IAAI,CAACK,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAACA,SAAS,CAAC1C,IAAI,EAAE0C,SAAS,CAAC,CAChE,CACJ,CAAC;IACD,IAAI,CAAC5B,iBAAiB,GAAG,IAAIoB,GAAG,CAC9BrC,GAAG,CAACU,KAAK,CAACgC,MAAM,CAACrE,aAAa,CAAC,CAACsE,OAAO,CAAEJ,IAAI,IAC3CA,IAAI,CAACK,UAAU,CACZF,MAAM,CAAEG,SAAS,IAAKA,SAAS,CAACb,EAAE,CAAC,CAAC;IAAA,CACpCM,GAAG,CAAEO,SAAS,IAAK;MAClB;MACA,OAAO,CAACA,SAAS,CAACb,EAAE,EAAYa,SAAS,CAAC;IAC5C,CAAC,CACL,CACF,CAAC;IAED7C,GAAG,CAACS,UAAU,CAACqC,OAAO,CAAEC,YAAY,IAAK;MACvC,MAAMC,SAAS,GAAG,IAAI,CAACC,aAAa,CAClC1E,oBAAoB,CAACwE,YAAY,CAAC,GAC9B9E,6BAA6B,CAAC8E,YAAY,EAAE,IAAI,CAAC,GACjDA,YACN,CAAC;MACD,IAAI,CAACtC,UAAU,CAACuC,SAAS,CAAC7C,IAAI,CAAC,GAAG6C,SAAS;IAC7C,CAAC,CAAC;IAEF,IAAI,CAACtC,KAAK,GAAGV,GAAG,CAACU,KAAK,CAAC4B,GAAG,CAAEY,OAAO,IAAK7D,UAAU,CAAC,IAAI,EAAE6D,OAAO,CAAC,CAAC;IAElE,IACE,CAAClD,GAAG,CAACU,KAAK,CAACyC,IAAI,CACb,CAAC;MAAEC;IAAW,CAAC;IACb;IACAA,UAAU,KAAKrF,cAAc,CAACsF,MAClC,CAAC,EACD;MACA,IAAI,CAAC3C,KAAK,CAACqB,IAAI,CACb1C,UAAU,CAAC,IAAI,EAAE;QACf4C,KAAK,EAAE,gBAAgB;QACvBO,IAAI,EAAE1E,cAAc,CAACuF,MAAM;QAC3BD,UAAU,EAAErF,cAAc,CAACsF;MAC7B,CAAC,CACH,CAAC;IACH;IAEA,IAAI,CAACnC,OAAO,GAAG,IAAImB,GAAG,CAAC,IAAI,CAAC3B,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAACpB,YAAY,GAAG,IAAIkB,GAAG,CACzB,IAAI,CAAC3B,KAAK,CAACiC,OAAO,CAAEJ,IAAI,IACtBA,IAAI,CAACe,UAAU,CAACV,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAC5CA,SAAS,CAAC1C,IAAI,EACd0C,SAAS,CACV,CACH,CACF,CAAC;EACH;;EAEA;AACF;AACA;AACA;EACEU,kBAAkBA,CAACC,aAAoC,EAAE;IACvD;IACA;IACA,IAAIlC,MAAM,GAAGzC,GAAG,CAAC4E,MAAM,CAAsB,CAAC,CAACC,QAAQ,CAAC,CAAC;IAEzDF,aAAa,CAACV,OAAO,CAAEP,IAAI,IAAK;MAC9BjB,MAAM,GAAGA,MAAM,CAACqC,MAAM,CAACpB,IAAI,CAACe,UAAU,CAACM,WAAW,CAAC;IACrD,CAAC,CAAC;IAEF,OAAOtC,MAAM;EACf;;EAEA;AACF;AACA;AACA;EACE2B,aAAaA,CAACD,SAA2B,EAAuB;IAC9D,MAAMa,MAAM,GAAG,IAAIjF,MAAM,CAAC;MACxBkF,SAAS,EAAE;QACTC,OAAO,EAAE;MACX;IACF,CAAC,CAAC;IAEFC,MAAM,CAACC,MAAM,CAACJ,MAAM,CAACK,SAAS,EAAE;MAC9BC,iBAAiBA,CAACC,UAAkB,EAAEC,QAAmB,EAAE;QACzD;QACA;QACA;QACA;QACA,OAAO1F,MAAM,CACXD,GAAG,CAACM,eAAe,CAAC,CAAC,EAAE;UAAE,CAACqF,QAAQ,GAAGD;QAAW,CAAC,CAAC,EAClD,YACF,CAAC;MACH;IACF,CAAC,CAAC;IAEF,MAAM;MAAEjE,IAAI;MAAEmE,WAAW;MAAExC;IAAM,CAAC,GAAGkB,SAAS;IAC9C,MAAMuB,IAAI,GAAG,IAAI,CAACC,qBAAqB,CAAC1C,KAAK,EAAE+B,MAAM,CAAC;IAEtD,MAAMY,EAAE,GAAIC,eAA0B,IAAK;MACzC,MAAMC,GAAG,GAAG,IAAI,CAACC,kBAAkB,CAACF,eAAe,EAAE,IAAI,CAACjE,UAAU,CAAC;MACrE,IAAI;QACF,OAAO8D,IAAI,CAACM,QAAQ,CAACF,GAAG,CAAC;MAC3B,CAAC,CAAC,MAAM;QACN,OAAO,KAAK;MACd;IACF,CAAC;IAED,OAAO;MACLxE,IAAI;MACJmE,WAAW;MACXxC,KAAK;MACLyC,IAAI;MACJE;IACF,CAAC;EACH;EAEAG,kBAAkBA,CAChBF,eAA0B,EAC1BjE,UAAwD,EACxD;IACA,MAAMqE,OAAO,GAAG;MAAE,GAAGJ;IAAgB,CAAC;IAEtC,KAAK,MAAMK,WAAW,IAAItE,UAAU,EAAE;MACpC,MAAMuE,YAAY,GAChB,IAAI,CAACjF,aAAa,KAAK/B,aAAa,CAACiH,EAAE,GACnC7G,sBAAsB,CAAC2G,WAAW,CAAC,GACnCA,WAAW;MAEjBf,MAAM,CAACkB,cAAc,CAACJ,OAAO,EAAEE,YAAY,EAAE;QAC3CG,GAAGA,CAAA,EAAG;UACJ,OAAO1E,UAAU,CAACsE,WAAW,CAAC,EAAEN,EAAE,CAACC,eAAe,CAAC;QACrD;MACF,CAAC,CAAC;IACJ;IAEA,OAAOI,OAAO;EAChB;EAEAN,qBAAqBA,CAAC1C,KAA0B,EAAE+B,MAAc,EAAE;IAChE,MAAMpD,UAAU,GAAG5C,eAAe,CAACuH,IAAI,CAACtD,KAAK,CAAC;IAC9C,OAAO+B,MAAM,CAACwB,KAAK,CAAC5E,UAAU,CAAC6E,YAAY,CAAC,CAAC,CAAC;EAChD;EAEAC,OAAOA,CAACC,QAAgB,EAAoB;IAC1C,OAAO,IAAI,CAACzF,aAAa,KAAK/B,aAAa,CAACuD,EAAE,GAC1C,IAAI,CAACtB,KAAK,CAACwF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACtC,IAAI,KAAKqF,QAAQ,CAAC,GACjD,IAAI,CAACvF,KAAK,CAACwF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACT,EAAE,KAAKwD,QAAQ,CAAC;EACrD;EAEAE,UAAUA,CAACF,QAAgB,EAAuB;IAChD,OAAO,IAAI,CAACzF,aAAa,KAAK/B,aAAa,CAACuD,EAAE,GAC1C,IAAI,CAACrB,QAAQ,CAACuF,IAAI,CAAEE,OAAO,IAAKA,OAAO,CAACxF,IAAI,KAAKqF,QAAQ,CAAC,GAC1D,IAAI,CAACtF,QAAQ,CAACuF,IAAI,CAAEE,OAAO,IAAKA,OAAO,CAAC3D,EAAE,KAAKwD,QAAQ,CAAC;EAC9D;;EAEA;AACF;AACA;EACEI,cAAcA,CACZC,OAA2B,EAC3BC,KAA0B,EAC1BC,MAA8B,EACjB;IACb,MAAM;MAAEC;IAAM,CAAC,GAAGH,OAAO;IAEzB,MAAMtD,IAAI,GAAGpD,OAAO,CAAC,IAAI,EAAE0G,OAAO,CAAC;;IAEnC;IACA,MAAMI,WAAW,GAAG1D,IAAI,CAACC,IAAI;IAC7B,MAAM0D,SAAS,GAAG3D,IAAI,CAAC4D,YAAY,CAAC,CAAC;;IAErC;IACA,MAAMC,aAAa,GAAG,OAAO,IAAIJ,KAAK;IAEtC,IAAIlB,OAAoB,GAAG;MACzBJ,eAAe,EAAE,CAAC,CAAC;MACnB2B,aAAa,EAAE,CAAC,CAAC;MACjB7C,aAAa,EAAE,EAAE;MACjB8C,OAAO,EAAE/D,IAAI,CAACgE,oBAAoB,CAACV,OAAO,EAAEC,KAAK,CAAC;MAClDA,KAAK;MACLU,KAAK,EAAE,EAAE;MACTT,MAAM;MACNK,aAAa;MACbK,IAAI,EAAE,CAAC,CAAC;MACR5F,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BE,eAAe,EAAE,IAAI,CAACA,eAAe;MACrCE,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBC,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BuF,eAAe,EAAEC,kBAAkB,CAACb,KAAK,CAAC;MAC1Cc,sBAAsB,EAAE,IAAI,CAACrG;IAC/B,CAAC;;IAED;IACAuE,OAAO,GAAG+B,mBAAmB,CAAChB,OAAO,EAAEtD,IAAI,EAAEuC,OAAO,CAAC;;IAErD;IACA,IAAIgC,QAAQ,GAAG7H,QAAQ,CAAC,IAAI,EAAEiH,SAAS,CAAC;IAExC,IAAI,CAACa,iBAAiB,CAACjC,OAAO,CAAC;;IAE/B;IACA,OAAOgC,QAAQ,EAAE;MACf;MACAhC,OAAO,CAACtB,aAAa,CAACzB,IAAI,CAAC+E,QAAQ,CAAC;MAEpC,IAAI,CAACE,qBAAqB,CAAClC,OAAO,EAAEgC,QAAQ,CAAC;MAE7C,IAAI,CAACG,mBAAmB,CAACnC,OAAO,EAAEgC,QAAQ,CAAC;;MAE3C;MACA,IACE,IAAI,CAACI,kBAAkB,CAACpC,OAAO,EAAEgC,QAAQ,CAAC,IAC1CA,QAAQ,CAACtE,IAAI,KAAKyD,WAAW,EAC7B;QACA;MACF;;MAEA;MACAa,QAAQ,GAAG7H,QAAQ,CAAC,IAAI,EAAE6H,QAAQ,CAACK,WAAW,CAACrC,OAAO,CAAC,CAAC;IAC1D;;IAEA;IACAA,OAAO,GAAGsC,iBAAiB,CAACvB,OAAO,EAAEtD,IAAI,EAAEuC,OAAO,CAAC;;IAEnD;IACA,IAAI,CAACuC,WAAW,CAACvC,OAAO,CAAC;;IAEzB;IACAxF,wBAAwB,CAACuG,OAAO,EAAEf,OAAO,CAAC;IAE1C,OAAOA,OAAO;EAChB;EAEQiC,iBAAiBA,CAACjC,OAAoB,EAAE;IAC9C;IACA;IACA;IACA,MAAMwC,UAAU,GAAGtD,MAAM,CAACuD,MAAM,CAAC,CAAC,CAAC,CAAC;IAEpC,KAAK,MAAMhF,IAAI,IAAI,IAAI,CAAC7B,KAAK,EAAE;MAC7B,MAAM;QAAE4C,UAAU;QAAEJ;MAAQ,CAAC,GAAGX,IAAI;MAEpC,IAAI,CAACjE,WAAW,CAAC4E,OAAO,CAAC,EAAE;QACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAACkE,wBAAwB,CAACF,UAAU,CAChD,CAAC;MACH;IACF;EACF;EAEQN,qBAAqBA,CAC3BlC,OAAoB,EACpBvC,IAAyB,EACzB;IACA,MAAM;MAAEe,UAAU;MAAEJ;IAAQ,CAAC,GAAGX,IAAI;IACpC;;IAEA,IAAI,CAACjE,WAAW,CAAC4E,OAAO,CAAC,EAAE;MACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAACkE,wBAAwB,CAAC1C,OAAO,CAACgB,KAAK,CACnD,CAAC;IACH;EACF;EAEQmB,mBAAmBA,CAACnC,OAAoB,EAAEvC,IAAyB,EAAE;IAC3E;IACA,KAAK,MAAMkF,GAAG,IAAIlF,IAAI,CAACmF,IAAI,EAAE;MAC3B,IAAI,OAAO5C,OAAO,CAACgB,KAAK,CAAC2B,GAAG,CAAC,KAAK,WAAW,EAAE;QAC7C3C,OAAO,CAACuB,aAAa,CAACoB,GAAG,CAAC,GAAG3C,OAAO,CAACgB,KAAK,CAAC2B,GAAG,CAAC;MACjD;IACF;EACF;EAEQP,kBAAkBA,CAACpC,OAAoB,EAAEvC,IAAyB,EAAE;IAC1E;IACA,MAAMoF,UAAU,GAAGpF,IAAI,CAACe,UAAU,CAACsE,MAAM,CAAClF,MAAM,CAAC3D,gBAAgB,CAAC;;IAElE;IACA;IACA;IACA,KAAK,MAAM8I,KAAK,IAAIF,UAAU,EAAE;MAC9B,MAAMlF,IAAI,GAAGoF,KAAK,CAACpF,IAAI;;MAEvB;MACA,IAAIA,IAAI,KAAKqF,SAAS,IAAID,KAAK,CAAC3F,IAAI,KAAKtE,aAAa,CAACmK,UAAU,EAAE;QACjE,MAAMC,gBAAgB,GACpBvF,IAAI,CAACN,KAAK,CAACO,MAAM,CAAEuF,IAAI,IAAKA,IAAI,CAACjF,SAAS,CAAC,CAACkF,MAAM,GAAG,CAAC;QAExD,IAAIF,gBAAgB,EAAE;UACpB,OAAO,IAAI,CAACG,mBAAmB,CAACrD,OAAO,EAAE+C,KAAK,EAAEpF,IAAI,CAAC;QACvD;MACF;IACF;EACF;EAEQ0F,mBAAmBA,CACzBrD,OAAoB,EACpB+C,KAAwB,EACxBpF,IAAU,EACV;IACA,MAAM;MAAEiC,eAAe;MAAEoB;IAAM,CAAC,GAAGhB,OAAO;IAE1C,MAAMsD,WAAW,GAAG3F,IAAI,CAACN,KAAK,CAC3BO,MAAM,CAAEuF,IAAI,IACXA,IAAI,CAACjF,SAAS,GACV,IAAI,CAACvC,UAAU,CAACwH,IAAI,CAACjF,SAAS,CAAC,EAAEyB,EAAE,CAACC,eAAe,CAAC,GACpD,IACN,CAAC,CACApC,GAAG,CAAE2F,IAAI,IAAKA,IAAI,CAACnG,KAAK,CAAC;;IAE5B;IACA,MAAMuG,UAAU,GAAGR,KAAK,CAACS,qBAAqB,CAACxC,KAAK,CAAC;IAErD,IAAIuC,UAAU,KAAKP,SAAS,EAAE;MAC5B,IAAIS,SAAS,GAAG,KAAK;MACrB,MAAMC,OAAO,GAAGC,KAAK,CAACD,OAAO,CAACH,UAAU,CAAC;;MAEzC;MACA;MACA,IAAIG,OAAO,EAAE;QACXD,SAAS,GAAG,CAACF,UAAU,CAACK,KAAK,CAAET,IAAI,IAAKG,WAAW,CAACO,QAAQ,CAACV,IAAI,CAAC,CAAC;MACrE,CAAC,MAAM;QACLM,SAAS,GAAG,CAACH,WAAW,CAACO,QAAQ,CAACN,UAAU,CAAC;MAC/C;MAEA,IAAIE,SAAS,EAAE;QACbzD,OAAO,CAACiB,MAAM,KAAK,EAAE;QAErB,MAAM3D,IAAI,GACR,6DAA6D;QAE/D0C,OAAO,CAACiB,MAAM,CAAChE,IAAI,CAAC;UAClBK,IAAI;UACJjC,IAAI,EAAE0H,KAAK,CAAC1H,IAAI;UAChByI,IAAI,EAAE,IAAIf,KAAK,CAAC1H,IAAI,EAAE;UACtBqC,IAAI,EAAE,CAAC,IAAIqF,KAAK,CAAC1H,IAAI,EAAE;QACzB,CAAC,CAAC;MACJ;MAEA,OAAOoI,SAAS;IAClB;EACF;EAEQlB,WAAWA,CAACvC,OAAoB,EAAE;IACxC,KAAK,MAAM;MAAE4C,IAAI;MAAElF;IAAK,CAAC,IAAIsC,OAAO,CAACtB,aAAa,EAAE;MAClDsB,OAAO,CAAC0B,KAAK,CAACzE,IAAI,CAACS,IAAI,CAAC;;MAExB;MACA,IACEsC,OAAO,CAACiB,MAAM,EAAE5C,IAAI,CAAC,CAAC;QAAEhD,IAAI;QAAEqC;MAAK,CAAC,KAAK;QACvC,OAAOkF,IAAI,CAACiB,QAAQ,CAACxI,IAAI,CAAC,IAAIuH,IAAI,CAACvE,IAAI,CAAEsE,GAAG,IAAKjF,IAAI,CAACmG,QAAQ,CAAClB,GAAG,CAAC,CAAC;MACtE,CAAC,CAAC,EACF;QACA;MACF;IACF;EACF;EAEAoB,gBAAgBA,CAACC,WAAmB,EAA4B;IAC9D,OAAO,IAAI,CAAC7H,iBAAiB,CAACkE,GAAG,CAAC2D,WAAW,CAAC;EAChD;EAEAC,WAAWA,CAACC,MAAc,EAAoB;IAC5C,OAAO,IAAI,CAACjI,YAAY,CAACoE,GAAG,CAAC6D,MAAM,CAAC;EACtC;;EAEA;AACF;AACA;AACA;AACA;EACEC,gBAAgBA,CAAClE,WAAmB,EAAkC;IACpE,OAAO,IAAI,CAAC/E,GAAG,CAACS,UAAU,CACvBiC,MAAM,CAACnE,oBAAoB,CAAC,CAC5BkH,IAAI,CAAEzC,SAAS,IAAKA,SAAS,CAAChB,EAAE,KAAK+C,WAAW,CAAC;EACtD;AACF;;AAEA;AACA;AACA;AACA,SAAS8B,mBAAmBA,CAC1BhB,OAA2B,EAC3BtD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAExB;EAAW,CAAC,GAAGf,IAAI;EAC3B,MAAM;IAAE+D,OAAO;IAAER;EAAM,CAAC,GAAGhB,OAAO;EAElC,MAAM;IAAEoE;EAAO,CAAC,GAAG3G,IAAI,CAAC4G,aAAa,CAACtD,OAAO,CAAC;;EAE9C;EACA,IACE,CAACA,OAAO,CAACS,OAAO,IACf4C,MAAM,IACLA,MAAM,KAAKxJ,UAAU,CAAC0J,QAAQ,IAC9B,CAACF,MAAM,CAACG,UAAU,CAAC3J,UAAU,CAAC4J,QAAQ,CAAE,EAC1C;IACA,OAAOxE,OAAO;EAChB;;EAEA;EACA;EACA;EACA,MAAMyE,MAAM,GAAG;IAAE,GAAG1D,OAAO,CAACS;EAAQ,CAAC;EACrChD,UAAU,CAACsE,MAAM,CAAC9E,OAAO,CAAE+E,KAAK,IAAK;IACnC,IACEA,KAAK,CAAC3F,IAAI,KAAKtE,aAAa,CAAC4L,eAAe,IAC5C,EAAE3B,KAAK,CAAC1H,IAAI,IAAIoJ,MAAM,CAAC,EACvB;MACAA,MAAM,CAAC1B,KAAK,CAAC1H,IAAI,CAAC,GAAG2H,SAAS;IAChC;EACF,CAAC,CAAC;EAEF,MAAM;IAAEhG,KAAK;IAAEiE;EAAO,CAAC,GAAGzC,UAAU,CAAC5B,QAAQ,CAAC;IAC5C,GAAG4E,OAAO;IACV,GAAGiD;EACL,CAAC,CAAC;;EAEF;EACA,MAAME,SAAS,GAAGlH,IAAI,CAACmH,qBAAqB,CAAC7D,OAAO,EAAEC,KAAK,EAAEhE,KAAK,CAAC;EAEnE,OAAO;IACL,GAAGgD,OAAO;IACVwB,OAAO,EAAE3G,KAAK,CAAC2G,OAAO,EAAExE,KAAK,CAAC;IAC9BgE,KAAK,EAAEnG,KAAK,CAACmG,KAAK,EAAE2D,SAAS,CAAC;IAC9B1D;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA,SAASqB,iBAAiBA,CACxBvB,OAA2B,EAC3BtD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAEiB,MAAM,GAAG,EAAE;IAAEvC,aAAa;IAAE6C;EAAc,CAAC,GAAGvB,OAAO;;EAE7D;EACA,MAAM6E,aAAa,GAAGnG,aAAa,CAACd,MAAM,CACvCkH,YAAY,IAAKA,YAAY,KAAKrH,IACrC,CAAC;;EAED;EACA,MAAM;IAAEX;EAAM,CAAC,GAAGW,IAAI,CAACsH,KAAK,CACzBtG,kBAAkB,CAACoG,aAAa,CAAC,CACjCjI,QAAQ,CAAC2E,aAAa,EAAE;IAAE,GAAG7G,IAAI;IAAEsK,YAAY,EAAE;EAAK,CAAC,CAAC;;EAE3D;EACA,IAAIlI,KAAK,EAAE;IACT,MAAMmI,WAAW,GAAGnI,KAAK,CAACoI,OAAO,CAAC1H,GAAG,CAACpD,QAAQ,CAAC;IAC/C,OAAO;MAAE,GAAG4F,OAAO;MAAEiB,MAAM,EAAEA,MAAM,CAACpC,MAAM,CAACoG,WAAW;IAAE,CAAC;EAC3D;EAEA,OAAOjF,OAAO;AAChB;AAEA,SAAS6B,kBAAkBA,CAACb,KAA0B,EAAU;EAC9D,IACE,CAACA,KAAK,CAACmE,mBAAmB,IAC1B,OAAOnE,KAAK,CAACmE,mBAAmB,KAAK,QAAQ,EAC7C;IACA,MAAMC,KAAK,CAAC,0CAA0C,CAAC;EACzD;EAEA,OAAOpE,KAAK,CAACmE,mBAAmB;AAClC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","formId","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getSection","section","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval-fork'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n formId: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n formId?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.formId = options.formId ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n getSection(nameOrId: string): Section | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.sections.find((section) => section.name === nameOrId)\n : this.sections.find((section) => section.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormSubmissionState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n action !== FormAction.Validate &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormSubmissionState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -5,7 +5,7 @@ import { ComponentCollection } from "../components/ComponentCollection.js";
|
|
|
5
5
|
import { optionalText } from "../components/constants.js";
|
|
6
6
|
import { checkFormStatus, getCacheService, getErrors, getSaveAndExitHelpers, normalisePath, proceed } from "../helpers.js";
|
|
7
7
|
import { PageController } from "./PageController.js";
|
|
8
|
-
import {
|
|
8
|
+
import { prefillStateFromQueryParameters } from "./helpers/state.js";
|
|
9
9
|
import { getComponentsByType } from "../validationHelpers.js";
|
|
10
10
|
import { FormAction, FormStatus } from "../../../routes/types.js";
|
|
11
11
|
import { actionSchema, crumbSchema, paramsSchema } from "../../../schemas/index.js";
|
|
@@ -278,9 +278,7 @@ export class QuestionPageController extends PageController {
|
|
|
278
278
|
return state;
|
|
279
279
|
}
|
|
280
280
|
const cacheService = getCacheService(request.server);
|
|
281
|
-
|
|
282
|
-
// Clear any 'not yet validated' state before saving to cache
|
|
283
|
-
return cacheService.setState(request, clearNotYetValidatedState(state));
|
|
281
|
+
return cacheService.setState(request, state);
|
|
284
282
|
}
|
|
285
283
|
async mergeState(request, state, update) {
|
|
286
284
|
const {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","Boom","COMPONENT_STATE_ERROR","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","optionalText","checkFormStatus","getCacheService","getErrors","getSaveAndExitHelpers","normalisePath","proceed","PageController","clearNotYetValidatedState","prefillStateFromQueryParameters","getComponentsByType","FormAction","FormStatus","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","allowSaveAndExit","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","hasIncompletePayment","paymentState","preAuth","status","backLink","getBackLink","shouldShowSaveAndExit","server","showSubmitButton","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","redirect","url","origin","pathname","getViewErrors","flashedError","yar","flash","flashedErrors","concat","paymentExpiredFlash","showPaymentExpiredNotification","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","startsWith","External","dispatchExternal","SaveAndExit","handleSaveAndExit","externalComponents","externalActionsWithArgs","split","externalActionArgs","arg","args","Object","fromEntries","componentName","componentDefMap","get","componentType","internal","selectedComponent","stashedPayload","clear","isPreview","isLive","Live","dispatcher","sourceUrl","toString","actionArgs","nextPath","nextUrl","saveAndExit","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n COMPONENT_STATE_ERROR,\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n checkFormStatus,\n getCacheService,\n getErrors,\n getSaveAndExitHelpers,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n clearNotYetValidatedState,\n prefillStateFromQueryParameters\n} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormPayloadParams,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { getComponentsByType } from '~/src/server/plugins/engine/validationHelpers.js'\nimport {\n FormAction,\n FormStatus,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n allowSaveAndExit = true\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n const hasIncompletePayment = components.some(({ model }) => {\n if ('paymentState' in model) {\n const paymentState = model.paymentState as\n | { preAuth?: { status?: string } }\n | undefined\n return !paymentState?.preAuth?.status\n }\n return false\n })\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors,\n allowSaveAndExit: this.shouldShowSaveAndExit(request.server),\n showSubmitButton: !hasIncompletePayment\n }\n }\n\n getRelevantPath(request: AnyFormRequest, context: FormContext) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormPayloadParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormPayloadParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: AnyFormRequest) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n // Clear any 'not yet validated' state before saving to cache\n return cacheService.setState(request, clearNotYetValidatedState(state))\n }\n\n async mergeState(\n request: AnyFormRequest,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n // Copy any URL params into the form state (if not already done so)\n if (await prefillStateFromQueryParameters(request, this)) {\n // Forward to same page without query string\n return h.redirect(`${request.url.origin}${request.url.pathname}`)\n }\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n const flashedError = request.yar.flash(COMPONENT_STATE_ERROR)\n const flashedErrors = !Array.isArray(flashedError) ? [flashedError] : []\n\n viewModel.errors = (viewModel.errors ?? []).concat(flashedErrors)\n\n const paymentExpiredFlash = request.yar.flash(\n PAYMENT_EXPIRED_NOTIFICATION\n )\n viewModel.showPaymentExpiredNotification =\n !Array.isArray(paymentExpiredFlash)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n const action = request.payload.action\n\n if (action?.startsWith(FormAction.External)) {\n return await this.dispatchExternal(request, h, context)\n }\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save state\n await this.setState(request, state)\n\n // Check if this is a save-and-exit action\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n // Proceed to the next page\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n private async dispatchExternal(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n ) {\n const { externalComponents } = getComponentsByType()\n const action = request.payload.action ?? ''\n\n // Find the external action and arguments\n // `external-{componentName}--{argname1}:{argvalue1}--{argname2}:{argvalue2}`\n // E.g. external-abcdef--amount:10--step:manual\n const externalActionsWithArgs = action\n .slice(`${FormAction.External}-`.length)\n .split('--')\n\n const externalActionArgs = externalActionsWithArgs\n .slice(1)\n .map((arg) => arg.split(':'))\n\n const args = Object.fromEntries(externalActionArgs) as Record<\n string,\n string\n >\n\n const componentName = externalActionsWithArgs[0]\n const component = this.model.componentDefMap.get(componentName)\n const componentType = component?.type\n\n if (!componentType) {\n throw Boom.internal(\n `External component of type ${componentType} not found`\n )\n }\n\n const selectedComponent = externalComponents.get(componentType)\n\n if (!selectedComponent) {\n throw Boom.internal(`External component ${componentName} not found`)\n }\n\n // Stash payload without crumb and action\n const stashedPayload = {\n ...context.payload,\n crumb: undefined,\n action: undefined\n }\n request.yar.flash(EXTERNAL_STATE_PAYLOAD, stashedPayload, true)\n\n // Clear any previous state appendage\n request.yar.clear(EXTERNAL_STATE_APPENDAGE)\n\n // Determine if this is a live form (not preview/draft)\n const { state, isPreview } = checkFormStatus(request.params)\n const isLive = state === FormStatus.Live\n\n return await selectedComponent.dispatcher(request, h, {\n component,\n controller: this,\n sourceUrl: request.url.toString(),\n actionArgs: args,\n isLive,\n isPreview\n })\n }\n\n proceed(\n request: FormContextRequest,\n h: FormResponseToolkit,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * Handle save-and-exit action\n */\n handleSaveAndExit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const saveAndExit = getSaveAndExitHelpers(request.server)\n\n if (!saveAndExit) {\n throw Boom.internal('Server misconfigured for save and exit')\n }\n\n return saveAndExit(request, h, context)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","Boom","COMPONENT_STATE_ERROR","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","optionalText","checkFormStatus","getCacheService","getErrors","getSaveAndExitHelpers","normalisePath","proceed","PageController","prefillStateFromQueryParameters","getComponentsByType","FormAction","FormStatus","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","allowSaveAndExit","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","hasIncompletePayment","paymentState","preAuth","status","backLink","getBackLink","shouldShowSaveAndExit","server","showSubmitButton","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","redirect","url","origin","pathname","getViewErrors","flashedError","yar","flash","flashedErrors","concat","paymentExpiredFlash","showPaymentExpiredNotification","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","startsWith","External","dispatchExternal","SaveAndExit","handleSaveAndExit","externalComponents","externalActionsWithArgs","split","externalActionArgs","arg","args","Object","fromEntries","componentName","componentDefMap","get","componentType","internal","selectedComponent","stashedPayload","clear","isPreview","isLive","Live","dispatcher","sourceUrl","toString","actionArgs","nextPath","nextUrl","saveAndExit","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n COMPONENT_STATE_ERROR,\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n checkFormStatus,\n getCacheService,\n getErrors,\n getSaveAndExitHelpers,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { prefillStateFromQueryParameters } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormPayloadParams,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { getComponentsByType } from '~/src/server/plugins/engine/validationHelpers.js'\nimport {\n FormAction,\n FormStatus,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n allowSaveAndExit = true\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n const hasIncompletePayment = components.some(({ model }) => {\n if ('paymentState' in model) {\n const paymentState = model.paymentState as\n | { preAuth?: { status?: string } }\n | undefined\n return !paymentState?.preAuth?.status\n }\n return false\n })\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors,\n allowSaveAndExit: this.shouldShowSaveAndExit(request.server),\n showSubmitButton: !hasIncompletePayment\n }\n }\n\n getRelevantPath(request: AnyFormRequest, context: FormContext) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormPayloadParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormPayloadParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: AnyFormRequest) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: AnyFormRequest,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n // Copy any URL params into the form state (if not already done so)\n if (await prefillStateFromQueryParameters(request, this)) {\n // Forward to same page without query string\n return h.redirect(`${request.url.origin}${request.url.pathname}`)\n }\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n const flashedError = request.yar.flash(COMPONENT_STATE_ERROR)\n const flashedErrors = !Array.isArray(flashedError) ? [flashedError] : []\n\n viewModel.errors = (viewModel.errors ?? []).concat(flashedErrors)\n\n const paymentExpiredFlash = request.yar.flash(\n PAYMENT_EXPIRED_NOTIFICATION\n )\n viewModel.showPaymentExpiredNotification =\n !Array.isArray(paymentExpiredFlash)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n const action = request.payload.action\n\n if (action?.startsWith(FormAction.External)) {\n return await this.dispatchExternal(request, h, context)\n }\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save state\n await this.setState(request, state)\n\n // Check if this is a save-and-exit action\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n // Proceed to the next page\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n private async dispatchExternal(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n ) {\n const { externalComponents } = getComponentsByType()\n const action = request.payload.action ?? ''\n\n // Find the external action and arguments\n // `external-{componentName}--{argname1}:{argvalue1}--{argname2}:{argvalue2}`\n // E.g. external-abcdef--amount:10--step:manual\n const externalActionsWithArgs = action\n .slice(`${FormAction.External}-`.length)\n .split('--')\n\n const externalActionArgs = externalActionsWithArgs\n .slice(1)\n .map((arg) => arg.split(':'))\n\n const args = Object.fromEntries(externalActionArgs) as Record<\n string,\n string\n >\n\n const componentName = externalActionsWithArgs[0]\n const component = this.model.componentDefMap.get(componentName)\n const componentType = component?.type\n\n if (!componentType) {\n throw Boom.internal(\n `External component of type ${componentType} not found`\n )\n }\n\n const selectedComponent = externalComponents.get(componentType)\n\n if (!selectedComponent) {\n throw Boom.internal(`External component ${componentName} not found`)\n }\n\n // Stash payload without crumb and action\n const stashedPayload = {\n ...context.payload,\n crumb: undefined,\n action: undefined\n }\n request.yar.flash(EXTERNAL_STATE_PAYLOAD, stashedPayload, true)\n\n // Clear any previous state appendage\n request.yar.clear(EXTERNAL_STATE_APPENDAGE)\n\n // Determine if this is a live form (not preview/draft)\n const { state, isPreview } = checkFormStatus(request.params)\n const isLive = state === FormStatus.Live\n\n return await selectedComponent.dispatcher(request, h, {\n component,\n controller: this,\n sourceUrl: request.url.toString(),\n actionArgs: args,\n isLive,\n isPreview\n })\n }\n\n proceed(\n request: FormContextRequest,\n h: FormResponseToolkit,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * Handle save-and-exit action\n */\n handleSaveAndExit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const saveAndExit = getSaveAndExitHelpers(request.server)\n\n if (!saveAndExit) {\n throw Boom.internal('Server misconfigured for save and exit')\n }\n\n return saveAndExit(request, h, context)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js';
|
|
1
2
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
|
|
2
|
-
import { type AnyFormRequest, type FormContext
|
|
3
|
+
import { type AnyFormRequest, type FormContext } from '~/src/server/plugins/engine/types.js';
|
|
3
4
|
import { type FormQuery } from '~/src/server/routes/types.js';
|
|
4
5
|
export declare function stripParam(query: FormQuery, paramToRemove: string): FormQuery | undefined;
|
|
5
6
|
/**
|
|
@@ -9,15 +10,15 @@ export declare function stripParam(query: FormQuery, paramToRemove: string): For
|
|
|
9
10
|
* @param model
|
|
10
11
|
*/
|
|
11
12
|
export declare function prefillStateFromQueryParameters(request: AnyFormRequest, page: PageControllerClass): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether the save-and-exit finished on a repeater with partial state
|
|
15
|
+
* @param context - the form context
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkSaveAndExitRepeater(context: FormContext, model: FormModel): string | undefined;
|
|
12
18
|
/**
|
|
13
19
|
* Copies any potentially invalid state into the payload, and removes those values from state
|
|
14
20
|
* NOTE - this method has a side-effect on 'context.state' and 'context.payload'
|
|
15
21
|
* @param request - the form request
|
|
16
22
|
* @param context - the form context
|
|
17
23
|
*/
|
|
18
|
-
export declare function copyNotYetValidatedState(request:
|
|
19
|
-
/**
|
|
20
|
-
* Remove any temporary 'not yet validated' state now that it's been validated
|
|
21
|
-
* @param state - the form state
|
|
22
|
-
*/
|
|
23
|
-
export declare function clearNotYetValidatedState(state: FormSubmissionState): FormSubmissionState;
|
|
24
|
+
export declare function copyNotYetValidatedState(request: AnyFormRequest, context: FormContext): Promise<void>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { getHiddenFields } from '@defra/forms-model';
|
|
1
|
+
import { ControllerType, getHiddenFields } from '@defra/forms-model';
|
|
2
|
+
import { validate as isValidUUID } from 'uuid';
|
|
3
|
+
import { getCacheService } from "../../helpers.js";
|
|
2
4
|
import { CURRENT_PAGE_PATH_KEY, STATE_NOT_YET_VALIDATED } from "../../index.js";
|
|
5
|
+
const GUID_LENGTH = 36;
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* A series of functions that can transform a pre-fill input parameter e.g lookup a form title based on form id
|
|
5
9
|
*/
|
|
@@ -65,13 +69,40 @@ export async function prefillStateFromQueryParameters(request, page) {
|
|
|
65
69
|
return true;
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Checks whether the save-and-exit finished on a repeater with partial state
|
|
74
|
+
* @param context - the form context
|
|
75
|
+
*/
|
|
76
|
+
export function checkSaveAndExitRepeater(context, model) {
|
|
77
|
+
const potentiallyInvalidState = context.state[STATE_NOT_YET_VALIDATED];
|
|
78
|
+
if (!potentiallyInvalidState) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const originalPath = potentiallyInvalidState[CURRENT_PAGE_PATH_KEY];
|
|
82
|
+
const repeaterPaths = model.def.pages.filter(page => page.controller === ControllerType.Repeat).map(p => `/${model.basePath}${p.path}/`);
|
|
83
|
+
if (typeof originalPath !== 'string') {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const segments = originalPath.split('/');
|
|
87
|
+
const lastSegment = segments.at(-1) ?? '';
|
|
88
|
+
if (!isValidUUID(lastSegment)) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const guidStartIndex = originalPath.length - GUID_LENGTH;
|
|
92
|
+
const originalPathWithoutGuid = originalPath.substring(0, guidStartIndex);
|
|
93
|
+
if (!repeaterPaths.includes(originalPathWithoutGuid)) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return originalPath;
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
/**
|
|
69
100
|
* Copies any potentially invalid state into the payload, and removes those values from state
|
|
70
101
|
* NOTE - this method has a side-effect on 'context.state' and 'context.payload'
|
|
71
102
|
* @param request - the form request
|
|
72
103
|
* @param context - the form context
|
|
73
104
|
*/
|
|
74
|
-
export function copyNotYetValidatedState(request, context) {
|
|
105
|
+
export async function copyNotYetValidatedState(request, context) {
|
|
75
106
|
const potentiallyInvalidState = context.state[STATE_NOT_YET_VALIDATED];
|
|
76
107
|
if (!potentiallyInvalidState) {
|
|
77
108
|
return;
|
|
@@ -83,17 +114,13 @@ export function copyNotYetValidatedState(request, context) {
|
|
|
83
114
|
...potentiallyInvalidState,
|
|
84
115
|
[CURRENT_PAGE_PATH_KEY]: undefined
|
|
85
116
|
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
117
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
state[STATE_NOT_YET_VALIDATED] = undefined;
|
|
118
|
+
// Remove any temporary 'not yet validated' state now it's been copied to the payload
|
|
119
|
+
if (context.state[STATE_NOT_YET_VALIDATED]) {
|
|
120
|
+
context.state[STATE_NOT_YET_VALIDATED] = undefined;
|
|
121
|
+
}
|
|
122
|
+
const cacheService = getCacheService(request.server);
|
|
123
|
+
await cacheService.setState(request, context.state);
|
|
96
124
|
}
|
|
97
|
-
return state;
|
|
98
125
|
}
|
|
99
126
|
//# sourceMappingURL=state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","names":["getHiddenFields","CURRENT_PAGE_PATH_KEY","STATE_NOT_YET_VALIDATED","paramLookupFunctions","formId","val","services","formTitle","meta","formsService","getFormMetadataById","title","key","value","stripParam","query","paramToRemove","params","Object","entries","keys","length","undefined","prefillStateFromQueryParameters","request","page","model","hiddenFieldNames","Set","def","map","field","name","size","has","lookupFunc","res","formData","getState","mergeState","
|
|
1
|
+
{"version":3,"file":"state.js","names":["ControllerType","getHiddenFields","validate","isValidUUID","getCacheService","CURRENT_PAGE_PATH_KEY","STATE_NOT_YET_VALIDATED","GUID_LENGTH","paramLookupFunctions","formId","val","services","formTitle","meta","formsService","getFormMetadataById","title","key","value","stripParam","query","paramToRemove","params","Object","entries","keys","length","undefined","prefillStateFromQueryParameters","request","page","model","hiddenFieldNames","Set","def","map","field","name","size","has","lookupFunc","res","formData","getState","mergeState","checkSaveAndExitRepeater","context","potentiallyInvalidState","state","originalPath","repeaterPaths","pages","filter","controller","Repeat","p","basePath","path","segments","split","lastSegment","at","guidStartIndex","originalPathWithoutGuid","substring","includes","copyNotYetValidatedState","url","pathname","payload","cacheService","server","setState"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/helpers/state.ts"],"sourcesContent":["import { ControllerType, getHiddenFields } from '@defra/forms-model'\nimport { validate as isValidUUID } from 'uuid'\n\nimport { getCacheService } from '~/src/server/plugins/engine/helpers.js'\nimport {\n CURRENT_PAGE_PATH_KEY,\n STATE_NOT_YET_VALIDATED\n} from '~/src/server/plugins/engine/index.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormStateValue,\n type FormValue\n} from '~/src/server/plugins/engine/types.js'\nimport { type FormQuery } from '~/src/server/routes/types.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst GUID_LENGTH = 36\n\n/**\n * A series of functions that can transform a pre-fill input parameter e.g lookup a form title based on form id\n */\nconst paramLookupFunctions = {\n formId: async (val: string, services: Services) => {\n let formTitle\n if (val) {\n const meta = await services.formsService.getFormMetadataById(val)\n formTitle = meta.title\n }\n return {\n key: 'formName',\n value: formTitle\n }\n }\n} as Partial<\n Record<\n string,\n (\n val: string,\n services: Services\n ) => Promise<{ key: string; value: string | undefined }>\n >\n>\n\nexport function stripParam(query: FormQuery, paramToRemove: string) {\n const params = {} as Record<string, FormStateValue | undefined>\n for (const [key, value = ''] of Object.entries(query)) {\n if (key !== paramToRemove) {\n params[key] = value\n }\n }\n return Object.keys(params).length ? (params as FormQuery) : undefined\n}\n\n/**\n * Any hidden parameters defined in the FormDefinition may be pre-filled by URL parameter values.\n * Other parameters are ignored for security reasons.\n * @param request\n * @param model\n */\nexport async function prefillStateFromQueryParameters(\n request: AnyFormRequest,\n page: PageControllerClass\n): Promise<boolean> {\n const { model } = page\n\n const hiddenFieldNames = new Set(\n getHiddenFields(model.def).map((field) => field.name)\n )\n\n if (!hiddenFieldNames.size) {\n return false\n }\n\n // Remove 'returnUrl' param\n const query = stripParam(request.query, 'returnUrl')\n\n if (!query) {\n return false\n }\n\n const params = {} as Record<string, FormStateValue | undefined>\n\n for (const [key, value = ''] of Object.entries(query)) {\n if (hiddenFieldNames.has(key)) {\n const lookupFunc = paramLookupFunctions[key]\n if (lookupFunc) {\n const res = await lookupFunc(value, model.services)\n // Store original value and result\n params[key] = value\n params[res.key] = res.value\n } else {\n params[key] = value\n }\n }\n }\n\n const formData = await page.getState(request)\n await page.mergeState(request, formData, params)\n\n return true\n}\n\n/**\n * Checks whether the save-and-exit finished on a repeater with partial state\n * @param context - the form context\n */\nexport function checkSaveAndExitRepeater(\n context: FormContext,\n model: FormModel\n) {\n const potentiallyInvalidState = context.state[STATE_NOT_YET_VALIDATED] as\n | Record<string, FormValue>\n | undefined\n if (!potentiallyInvalidState) {\n return\n }\n\n const originalPath = potentiallyInvalidState[CURRENT_PAGE_PATH_KEY]\n\n const repeaterPaths = model.def.pages\n .filter((page) => page.controller === ControllerType.Repeat)\n .map((p) => `/${model.basePath}${p.path}/`)\n\n if (typeof originalPath !== 'string') {\n return undefined\n }\n\n const segments = originalPath.split('/')\n const lastSegment = segments.at(-1) ?? ''\n\n if (!isValidUUID(lastSegment)) {\n return undefined\n }\n\n const guidStartIndex = originalPath.length - GUID_LENGTH\n const originalPathWithoutGuid = originalPath.substring(0, guidStartIndex)\n\n if (!repeaterPaths.includes(originalPathWithoutGuid)) {\n return undefined\n }\n\n return originalPath\n}\n\n/**\n * Copies any potentially invalid state into the payload, and removes those values from state\n * NOTE - this method has a side-effect on 'context.state' and 'context.payload'\n * @param request - the form request\n * @param context - the form context\n */\nexport async function copyNotYetValidatedState(\n request: AnyFormRequest,\n context: FormContext\n) {\n const potentiallyInvalidState = context.state[STATE_NOT_YET_VALIDATED] as\n | Record<string, FormValue>\n | undefined\n if (!potentiallyInvalidState) {\n return\n }\n\n const originalPath = potentiallyInvalidState[CURRENT_PAGE_PATH_KEY]\n\n if (originalPath && originalPath === request.url.pathname) {\n context.payload = {\n ...context.payload,\n ...potentiallyInvalidState,\n [CURRENT_PAGE_PATH_KEY]: undefined\n }\n\n // Remove any temporary 'not yet validated' state now it's been copied to the payload\n if (context.state[STATE_NOT_YET_VALIDATED]) {\n context.state[STATE_NOT_YET_VALIDATED] = undefined\n }\n\n const cacheService = getCacheService(request.server)\n await cacheService.setState(request, context.state)\n }\n}\n"],"mappings":"AAAA,SAASA,cAAc,EAAEC,eAAe,QAAQ,oBAAoB;AACpE,SAASC,QAAQ,IAAIC,WAAW,QAAQ,MAAM;AAE9C,SAASC,eAAe;AACxB,SACEC,qBAAqB,EACrBC,uBAAuB;AAazB,MAAMC,WAAW,GAAG,EAAE;;AAEtB;AACA;AACA;AACA,MAAMC,oBAAoB,GAAG;EAC3BC,MAAM,EAAE,MAAAA,CAAOC,GAAW,EAAEC,QAAkB,KAAK;IACjD,IAAIC,SAAS;IACb,IAAIF,GAAG,EAAE;MACP,MAAMG,IAAI,GAAG,MAAMF,QAAQ,CAACG,YAAY,CAACC,mBAAmB,CAACL,GAAG,CAAC;MACjEE,SAAS,GAAGC,IAAI,CAACG,KAAK;IACxB;IACA,OAAO;MACLC,GAAG,EAAE,UAAU;MACfC,KAAK,EAAEN;IACT,CAAC;EACH;AACF,CAQC;AAED,OAAO,SAASO,UAAUA,CAACC,KAAgB,EAAEC,aAAqB,EAAE;EAClE,MAAMC,MAAM,GAAG,CAAC,CAA+C;EAC/D,KAAK,MAAM,CAACL,GAAG,EAAEC,KAAK,GAAG,EAAE,CAAC,IAAIK,MAAM,CAACC,OAAO,CAACJ,KAAK,CAAC,EAAE;IACrD,IAAIH,GAAG,KAAKI,aAAa,EAAE;MACzBC,MAAM,CAACL,GAAG,CAAC,GAAGC,KAAK;IACrB;EACF;EACA,OAAOK,MAAM,CAACE,IAAI,CAACH,MAAM,CAAC,CAACI,MAAM,GAAIJ,MAAM,GAAiBK,SAAS;AACvE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,+BAA+BA,CACnDC,OAAuB,EACvBC,IAAyB,EACP;EAClB,MAAM;IAAEC;EAAM,CAAC,GAAGD,IAAI;EAEtB,MAAME,gBAAgB,GAAG,IAAIC,GAAG,CAC9BhC,eAAe,CAAC8B,KAAK,CAACG,GAAG,CAAC,CAACC,GAAG,CAAEC,KAAK,IAAKA,KAAK,CAACC,IAAI,CACtD,CAAC;EAED,IAAI,CAACL,gBAAgB,CAACM,IAAI,EAAE;IAC1B,OAAO,KAAK;EACd;;EAEA;EACA,MAAMlB,KAAK,GAAGD,UAAU,CAACU,OAAO,CAACT,KAAK,EAAE,WAAW,CAAC;EAEpD,IAAI,CAACA,KAAK,EAAE;IACV,OAAO,KAAK;EACd;EAEA,MAAME,MAAM,GAAG,CAAC,CAA+C;EAE/D,KAAK,MAAM,CAACL,GAAG,EAAEC,KAAK,GAAG,EAAE,CAAC,IAAIK,MAAM,CAACC,OAAO,CAACJ,KAAK,CAAC,EAAE;IACrD,IAAIY,gBAAgB,CAACO,GAAG,CAACtB,GAAG,CAAC,EAAE;MAC7B,MAAMuB,UAAU,GAAGhC,oBAAoB,CAACS,GAAG,CAAC;MAC5C,IAAIuB,UAAU,EAAE;QACd,MAAMC,GAAG,GAAG,MAAMD,UAAU,CAACtB,KAAK,EAAEa,KAAK,CAACpB,QAAQ,CAAC;QACnD;QACAW,MAAM,CAACL,GAAG,CAAC,GAAGC,KAAK;QACnBI,MAAM,CAACmB,GAAG,CAACxB,GAAG,CAAC,GAAGwB,GAAG,CAACvB,KAAK;MAC7B,CAAC,MAAM;QACLI,MAAM,CAACL,GAAG,CAAC,GAAGC,KAAK;MACrB;IACF;EACF;EAEA,MAAMwB,QAAQ,GAAG,MAAMZ,IAAI,CAACa,QAAQ,CAACd,OAAO,CAAC;EAC7C,MAAMC,IAAI,CAACc,UAAU,CAACf,OAAO,EAAEa,QAAQ,EAAEpB,MAAM,CAAC;EAEhD,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASuB,wBAAwBA,CACtCC,OAAoB,EACpBf,KAAgB,EAChB;EACA,MAAMgB,uBAAuB,GAAGD,OAAO,CAACE,KAAK,CAAC1C,uBAAuB,CAExD;EACb,IAAI,CAACyC,uBAAuB,EAAE;IAC5B;EACF;EAEA,MAAME,YAAY,GAAGF,uBAAuB,CAAC1C,qBAAqB,CAAC;EAEnE,MAAM6C,aAAa,GAAGnB,KAAK,CAACG,GAAG,CAACiB,KAAK,CAClCC,MAAM,CAAEtB,IAAI,IAAKA,IAAI,CAACuB,UAAU,KAAKrD,cAAc,CAACsD,MAAM,CAAC,CAC3DnB,GAAG,CAAEoB,CAAC,IAAK,IAAIxB,KAAK,CAACyB,QAAQ,GAAGD,CAAC,CAACE,IAAI,GAAG,CAAC;EAE7C,IAAI,OAAOR,YAAY,KAAK,QAAQ,EAAE;IACpC,OAAOtB,SAAS;EAClB;EAEA,MAAM+B,QAAQ,GAAGT,YAAY,CAACU,KAAK,CAAC,GAAG,CAAC;EACxC,MAAMC,WAAW,GAAGF,QAAQ,CAACG,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;EAEzC,IAAI,CAAC1D,WAAW,CAACyD,WAAW,CAAC,EAAE;IAC7B,OAAOjC,SAAS;EAClB;EAEA,MAAMmC,cAAc,GAAGb,YAAY,CAACvB,MAAM,GAAGnB,WAAW;EACxD,MAAMwD,uBAAuB,GAAGd,YAAY,CAACe,SAAS,CAAC,CAAC,EAAEF,cAAc,CAAC;EAEzE,IAAI,CAACZ,aAAa,CAACe,QAAQ,CAACF,uBAAuB,CAAC,EAAE;IACpD,OAAOpC,SAAS;EAClB;EAEA,OAAOsB,YAAY;AACrB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeiB,wBAAwBA,CAC5CrC,OAAuB,EACvBiB,OAAoB,EACpB;EACA,MAAMC,uBAAuB,GAAGD,OAAO,CAACE,KAAK,CAAC1C,uBAAuB,CAExD;EACb,IAAI,CAACyC,uBAAuB,EAAE;IAC5B;EACF;EAEA,MAAME,YAAY,GAAGF,uBAAuB,CAAC1C,qBAAqB,CAAC;EAEnE,IAAI4C,YAAY,IAAIA,YAAY,KAAKpB,OAAO,CAACsC,GAAG,CAACC,QAAQ,EAAE;IACzDtB,OAAO,CAACuB,OAAO,GAAG;MAChB,GAAGvB,OAAO,CAACuB,OAAO;MAClB,GAAGtB,uBAAuB;MAC1B,CAAC1C,qBAAqB,GAAGsB;IAC3B,CAAC;;IAED;IACA,IAAImB,OAAO,CAACE,KAAK,CAAC1C,uBAAuB,CAAC,EAAE;MAC1CwC,OAAO,CAACE,KAAK,CAAC1C,uBAAuB,CAAC,GAAGqB,SAAS;IACpD;IAEA,MAAM2C,YAAY,GAAGlE,eAAe,CAACyB,OAAO,CAAC0C,MAAM,CAAC;IACpD,MAAMD,YAAY,CAACE,QAAQ,CAAC3C,OAAO,EAAEiB,OAAO,CAACE,KAAK,CAAC;EACrD;AACF","ignoreList":[]}
|
|
@@ -3,6 +3,7 @@ import { EXTERNAL_STATE_APPENDAGE, EXTERNAL_STATE_PAYLOAD } from "../../../const
|
|
|
3
3
|
import { resolveFormModel } from "../beta/form-context.js";
|
|
4
4
|
import { FormComponent, isFormState } from "../components/FormComponent.js";
|
|
5
5
|
import { checkFormStatus, findPage, getCacheService, getPage, getStartPath, proceed } from "../helpers.js";
|
|
6
|
+
import { checkSaveAndExitRepeater, copyNotYetValidatedState } from "../pageControllers/helpers/state.js";
|
|
6
7
|
import { generateUniqueReference } from "../referenceNumbers.js";
|
|
7
8
|
import * as defaultServices from "../services/index.js";
|
|
8
9
|
export async function redirectOrMakeHandler(request, h, onRequest, makeHandler) {
|
|
@@ -32,6 +33,7 @@ export async function redirectOrMakeHandler(request, h, onRequest, makeHandler)
|
|
|
32
33
|
state = await importExternalComponentState(request, page, state);
|
|
33
34
|
const flash = cacheService.getFlash(request);
|
|
34
35
|
const context = model.getFormContext(request, state, flash?.errors);
|
|
36
|
+
await copyNotYetValidatedState(request, context);
|
|
35
37
|
const relevantPath = page.getRelevantPath(request, context);
|
|
36
38
|
const summaryPath = page.getSummaryPath();
|
|
37
39
|
|
|
@@ -43,6 +45,12 @@ export async function redirectOrMakeHandler(request, h, onRequest, makeHandler)
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
// Check whether save-and-exit should resume from within a repeater
|
|
49
|
+
const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model);
|
|
50
|
+
if (resumeInRepeaterUrl) {
|
|
51
|
+
return proceed(request, h, resumeInRepeaterUrl);
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
// Return handler for relevant pages or preview URL direct access
|
|
47
55
|
if (relevantPath.startsWith(page.path) || context.isForceAccess) {
|
|
48
56
|
return makeHandler(page, context);
|
|
@@ -50,7 +58,6 @@ export async function redirectOrMakeHandler(request, h, onRequest, makeHandler)
|
|
|
50
58
|
|
|
51
59
|
// Redirect back to last relevant page
|
|
52
60
|
const redirectTo = findPage(model, relevantPath);
|
|
53
|
-
|
|
54
61
|
// Set the return URL unless an exit page
|
|
55
62
|
if (redirectTo?.next.length) {
|
|
56
63
|
request.query.returnUrl = page.getHref(summaryPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","resolveFormModel","FormComponent","isFormState","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","onRequest","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","importExternalComponentState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","result","continue","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","isAppendageStateSingleObject","Object","fromEntries","entries","map","key","value","savedState","payload","stashedPayload","localState","getStateFromValidForm","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","handler","slug","isPreview","formState","routePrefix","dispatchHandler","servicePath","basePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { resolveFormModel } from '~/src/server/plugins/engine/beta/form-context.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyFormRequest,\n type ExternalStateAppendage,\n type FormContext,\n type FormPayload,\n type FormSubmissionState,\n type OnRequestCallback,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: AnyFormRequest,\n h: FormResponseToolkit,\n onRequest: OnRequestCallback | undefined,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n state = await importExternalComponentState(request, page, state)\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n const result = await onRequest(request, h, context)\n if (result !== h.continue) {\n return result\n }\n }\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nasync function importExternalComponentState(\n request: AnyFormRequest,\n page: PageControllerClass,\n state: FormSubmissionState\n): Promise<FormSubmissionState> {\n const externalComponentData = request.yar.flash(EXTERNAL_STATE_APPENDAGE)\n\n if (Array.isArray(externalComponentData)) {\n return state\n }\n\n const typedStateAppendage = externalComponentData as ExternalStateAppendage\n const componentName = typedStateAppendage.component\n const stateAppendage = typedStateAppendage.data\n\n const component = request.app.model?.componentMap.get(componentName)\n\n if (!component) {\n throw new Error(`Component ${componentName} not found in form`)\n }\n\n if (!(component instanceof FormComponent)) {\n throw new TypeError(\n `Component ${componentName} is not a FormComponent and does not support isState`\n )\n }\n\n const isStateValid = component.isState(stateAppendage)\n\n if (!isStateValid) {\n throw new Error(`State for component ${componentName} is invalid`)\n }\n\n // Create state structure from appendage state\n // Some components use a record structure with properties of the format of '<compName>__<fieldName>'\n // e.g. UKAddressField\n // Some components use a single object structure e.g. PaymentField\n const componentState =\n isFormState(stateAppendage) && !component.isAppendageStateSingleObject\n ? Object.fromEntries(\n Object.entries(stateAppendage).map(([key, value]) => [\n `${componentName}__${key}`,\n value\n ])\n )\n : { [componentName]: stateAppendage }\n\n // Save the external component state directly (already has correct key format)\n const savedState = await page.mergeState(request, state, componentState)\n\n // Merge any stashed payload into the local state\n const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)\n const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)\n\n const localState = page.getStateFromValidForm(request, savedState, {\n ...stashedPayload,\n ...componentState\n } as FormPayload)\n\n return { ...savedState, ...localState }\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const {\n services = defaultServices,\n controllers,\n ordnanceSurveyApiKey\n } = options\n\n async function handler(request: AnyFormRequest, h: ResponseToolkit) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n const model = await resolveFormModel(server, slug, formState, {\n services,\n controllers,\n ordnanceSurveyApiKey,\n routePrefix: prefix,\n isPreview\n })\n\n request.app.model = model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(request: FormRequest, h: FormResponseToolkit) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","resolveFormModel","FormComponent","isFormState","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","checkSaveAndExitRepeater","copyNotYetValidatedState","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","onRequest","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","importExternalComponentState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","result","continue","resumeInRepeaterUrl","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","isAppendageStateSingleObject","Object","fromEntries","entries","map","key","value","savedState","payload","stashedPayload","localState","getStateFromValidForm","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","handler","slug","isPreview","formState","routePrefix","dispatchHandler","servicePath","basePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { resolveFormModel } from '~/src/server/plugins/engine/beta/form-context.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n checkSaveAndExitRepeater,\n copyNotYetValidatedState\n} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyFormRequest,\n type ExternalStateAppendage,\n type FormContext,\n type FormPayload,\n type FormSubmissionState,\n type OnRequestCallback,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: AnyFormRequest,\n h: FormResponseToolkit,\n onRequest: OnRequestCallback | undefined,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n state = await importExternalComponentState(request, page, state)\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n\n await copyNotYetValidatedState(request, context)\n\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n const result = await onRequest(request, h, context)\n if (result !== h.continue) {\n return result\n }\n }\n\n // Check whether save-and-exit should resume from within a repeater\n const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model)\n if (resumeInRepeaterUrl) {\n return proceed(request, h, resumeInRepeaterUrl)\n }\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nasync function importExternalComponentState(\n request: AnyFormRequest,\n page: PageControllerClass,\n state: FormSubmissionState\n): Promise<FormSubmissionState> {\n const externalComponentData = request.yar.flash(EXTERNAL_STATE_APPENDAGE)\n\n if (Array.isArray(externalComponentData)) {\n return state\n }\n\n const typedStateAppendage = externalComponentData as ExternalStateAppendage\n const componentName = typedStateAppendage.component\n const stateAppendage = typedStateAppendage.data\n\n const component = request.app.model?.componentMap.get(componentName)\n\n if (!component) {\n throw new Error(`Component ${componentName} not found in form`)\n }\n\n if (!(component instanceof FormComponent)) {\n throw new TypeError(\n `Component ${componentName} is not a FormComponent and does not support isState`\n )\n }\n\n const isStateValid = component.isState(stateAppendage)\n\n if (!isStateValid) {\n throw new Error(`State for component ${componentName} is invalid`)\n }\n\n // Create state structure from appendage state\n // Some components use a record structure with properties of the format of '<compName>__<fieldName>'\n // e.g. UKAddressField\n // Some components use a single object structure e.g. PaymentField\n const componentState =\n isFormState(stateAppendage) && !component.isAppendageStateSingleObject\n ? Object.fromEntries(\n Object.entries(stateAppendage).map(([key, value]) => [\n `${componentName}__${key}`,\n value\n ])\n )\n : { [componentName]: stateAppendage }\n\n // Save the external component state directly (already has correct key format)\n const savedState = await page.mergeState(request, state, componentState)\n\n // Merge any stashed payload into the local state\n const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)\n const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)\n\n const localState = page.getStateFromValidForm(request, savedState, {\n ...stashedPayload,\n ...componentState\n } as FormPayload)\n\n return { ...savedState, ...localState }\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const {\n services = defaultServices,\n controllers,\n ordnanceSurveyApiKey\n } = options\n\n async function handler(request: AnyFormRequest, h: ResponseToolkit) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n const model = await resolveFormModel(server, slug, formState, {\n services,\n controllers,\n ordnanceSurveyApiKey,\n routePrefix: prefix,\n isPreview\n })\n\n request.app.model = model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(request: FormRequest, h: FormResponseToolkit) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"AAAA,OAAOA,IAAI,MAAM,YAAY;AAO7B,SACEC,wBAAwB,EACxBC,sBAAsB;AAExB,SAASC,gBAAgB;AACzB,SACEC,aAAa,EACbC,WAAW;AAEb,SACEC,eAAe,EACfC,QAAQ,EACRC,eAAe,EACfC,OAAO,EACPC,YAAY,EACZC,OAAO;AAGT,SACEC,wBAAwB,EACxBC,wBAAwB;AAE1B,SAASC,uBAAuB;AAChC,OAAO,KAAKC,eAAe;AAe3B,OAAO,eAAeC,qBAAqBA,CACzCC,OAAuB,EACvBC,CAAsB,EACtBC,SAAwC,EACxCC,WAG6C,EAC7C;EACA,MAAM;IAAEC,GAAG;IAAEC;EAAO,CAAC,GAAGL,OAAO;EAC/B,MAAM;IAAEM;EAAM,CAAC,GAAGF,GAAG;EAErB,IAAI,CAACE,KAAK,EAAE;IACV,MAAMvB,IAAI,CAACwB,QAAQ,CAAC,uBAAuBF,MAAM,CAACG,IAAI,EAAE,CAAC;EAC3D;EAEA,MAAMC,YAAY,GAAGlB,eAAe,CAACS,OAAO,CAACU,MAAM,CAAC;EACpD,MAAMC,IAAI,GAAGnB,OAAO,CAACc,KAAK,EAAEN,OAAO,CAAC;EACpC,IAAIY,KAAK,GAAG,MAAMD,IAAI,CAACE,QAAQ,CAACb,OAAO,CAAC;EAExC,IAAI,CAACY,KAAK,CAACE,mBAAmB,EAAE;IAC9B,MAAMC,MAAM,GAAGT,KAAK,CAACU,GAAG,CAACC,QAAQ,EAAEC,qBAAqB,IAAI,EAAE;IAE9D,IAAI,OAAOH,MAAM,KAAK,QAAQ,EAAE;MAC9B,MAAMhC,IAAI,CAACoC,iBAAiB,CAC1B,uDACF,CAAC;IACH;IAEA,MAAMC,eAAe,GAAGvB,uBAAuB,CAACkB,MAAM,CAAC;IACvDH,KAAK,GAAG,MAAMD,IAAI,CAACU,UAAU,CAACrB,OAAO,EAAEY,KAAK,EAAE;MAC5CE,mBAAmB,EAAEM;IACvB,CAAC,CAAC;EACJ;EAEAR,KAAK,GAAG,MAAMU,4BAA4B,CAACtB,OAAO,EAAEW,IAAI,EAAEC,KAAK,CAAC;EAEhE,MAAMW,KAAK,GAAGd,YAAY,CAACe,QAAQ,CAACxB,OAAO,CAAC;EAC5C,MAAMyB,OAAO,GAAGnB,KAAK,CAACoB,cAAc,CAAC1B,OAAO,EAAEY,KAAK,EAAEW,KAAK,EAAEI,MAAM,CAAC;EAEnE,MAAM/B,wBAAwB,CAACI,OAAO,EAAEyB,OAAO,CAAC;EAEhD,MAAMG,YAAY,GAAGjB,IAAI,CAACkB,eAAe,CAAC7B,OAAO,EAAEyB,OAAO,CAAC;EAC3D,MAAMK,WAAW,GAAGnB,IAAI,CAACoB,cAAc,CAAC,CAAC;;EAEzC;EACA,IAAI7B,SAAS,EAAE;IACb,MAAM8B,MAAM,GAAG,MAAM9B,SAAS,CAACF,OAAO,EAAEC,CAAC,EAAEwB,OAAO,CAAC;IACnD,IAAIO,MAAM,KAAK/B,CAAC,CAACgC,QAAQ,EAAE;MACzB,OAAOD,MAAM;IACf;EACF;;EAEA;EACA,MAAME,mBAAmB,GAAGvC,wBAAwB,CAAC8B,OAAO,EAAEnB,KAAK,CAAC;EACpE,IAAI4B,mBAAmB,EAAE;IACvB,OAAOxC,OAAO,CAACM,OAAO,EAAEC,CAAC,EAAEiC,mBAAmB,CAAC;EACjD;;EAEA;EACA,IAAIN,YAAY,CAACO,UAAU,CAACxB,IAAI,CAACH,IAAI,CAAC,IAAIiB,OAAO,CAACW,aAAa,EAAE;IAC/D,OAAOjC,WAAW,CAACQ,IAAI,EAAEc,OAAO,CAAC;EACnC;;EAEA;EACA,MAAMY,UAAU,GAAG/C,QAAQ,CAACgB,KAAK,EAAEsB,YAAY,CAAC;EAChD;EACA,IAAIS,UAAU,EAAEC,IAAI,CAACC,MAAM,EAAE;IAC3BvC,OAAO,CAACwC,KAAK,CAACC,SAAS,GAAG9B,IAAI,CAAC+B,OAAO,CAACZ,WAAW,CAAC;EACrD;EAEA,OAAOpC,OAAO,CAACM,OAAO,EAAEC,CAAC,EAAEU,IAAI,CAAC+B,OAAO,CAACd,YAAY,CAAC,CAAC;AACxD;AAEA,eAAeN,4BAA4BA,CACzCtB,OAAuB,EACvBW,IAAyB,EACzBC,KAA0B,EACI;EAC9B,MAAM+B,qBAAqB,GAAG3C,OAAO,CAAC4C,GAAG,CAACrB,KAAK,CAACvC,wBAAwB,CAAC;EAEzE,IAAI6D,KAAK,CAACC,OAAO,CAACH,qBAAqB,CAAC,EAAE;IACxC,OAAO/B,KAAK;EACd;EAEA,MAAMmC,mBAAmB,GAAGJ,qBAA+C;EAC3E,MAAMK,aAAa,GAAGD,mBAAmB,CAACE,SAAS;EACnD,MAAMC,cAAc,GAAGH,mBAAmB,CAACI,IAAI;EAE/C,MAAMF,SAAS,GAAGjD,OAAO,CAACI,GAAG,CAACE,KAAK,EAAE8C,YAAY,CAACC,GAAG,CAACL,aAAa,CAAC;EAEpE,IAAI,CAACC,SAAS,EAAE;IACd,MAAM,IAAIK,KAAK,CAAC,aAAaN,aAAa,oBAAoB,CAAC;EACjE;EAEA,IAAI,EAAEC,SAAS,YAAY9D,aAAa,CAAC,EAAE;IACzC,MAAM,IAAIoE,SAAS,CACjB,aAAaP,aAAa,sDAC5B,CAAC;EACH;EAEA,MAAMQ,YAAY,GAAGP,SAAS,CAACQ,OAAO,CAACP,cAAc,CAAC;EAEtD,IAAI,CAACM,YAAY,EAAE;IACjB,MAAM,IAAIF,KAAK,CAAC,uBAAuBN,aAAa,aAAa,CAAC;EACpE;;EAEA;EACA;EACA;EACA;EACA,MAAMU,cAAc,GAClBtE,WAAW,CAAC8D,cAAc,CAAC,IAAI,CAACD,SAAS,CAACU,4BAA4B,GAClEC,MAAM,CAACC,WAAW,CAChBD,MAAM,CAACE,OAAO,CAACZ,cAAc,CAAC,CAACa,GAAG,CAAC,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK,CACnD,GAAGjB,aAAa,KAAKgB,GAAG,EAAE,EAC1BC,KAAK,CACN,CACH,CAAC,GACD;IAAE,CAACjB,aAAa,GAAGE;EAAe,CAAC;;EAEzC;EACA,MAAMgB,UAAU,GAAG,MAAMvD,IAAI,CAACU,UAAU,CAACrB,OAAO,EAAEY,KAAK,EAAE8C,cAAc,CAAC;;EAExE;EACA,MAAMS,OAAO,GAAGnE,OAAO,CAAC4C,GAAG,CAACrB,KAAK,CAACtC,sBAAsB,CAAC;EACzD,MAAMmF,cAAc,GAAGvB,KAAK,CAACC,OAAO,CAACqB,OAAO,CAAC,GAAG,CAAC,CAAC,GAAIA,OAAuB;EAE7E,MAAME,UAAU,GAAG1D,IAAI,CAAC2D,qBAAqB,CAACtE,OAAO,EAAEkE,UAAU,EAAE;IACjE,GAAGE,cAAc;IACjB,GAAGV;EACL,CAAgB,CAAC;EAEjB,OAAO;IAAE,GAAGQ,UAAU;IAAE,GAAGG;EAAW,CAAC;AACzC;AAEA,OAAO,SAASE,sBAAsBA,CAAC7D,MAAc,EAAE8D,OAAsB,EAAE;EAC7E;EACA,MAAMzD,MAAM,GAAGL,MAAM,CAAC+D,KAAK,CAACC,SAAS,CAACC,KAAK,CAAC5D,MAAM,IAAI,EAAE;EAExD,MAAM;IACJ6D,QAAQ,GAAG9E,eAAe;IAC1B+E,WAAW;IACXC;EACF,CAAC,GAAGN,OAAO;EAEX,eAAeO,OAAOA,CAAC/E,OAAuB,EAAEC,CAAkB,EAAE;IAClE,IAAIS,MAAM,CAACN,GAAG,CAACE,KAAK,EAAE;MACpBN,OAAO,CAACI,GAAG,CAACE,KAAK,GAAGI,MAAM,CAACN,GAAG,CAACE,KAAK;MAEpC,OAAOL,CAAC,CAACgC,QAAQ;IACnB;IAEA,MAAM;MAAE5B;IAAO,CAAC,GAAGL,OAAO;IAC1B,MAAM;MAAEgF;IAAK,CAAC,GAAG3E,MAAM;IACvB,MAAM;MAAE4E,SAAS;MAAErE,KAAK,EAAEsE;IAAU,CAAC,GAAG7F,eAAe,CAACgB,MAAM,CAAC;IAE/D,MAAMC,KAAK,GAAG,MAAMpB,gBAAgB,CAACwB,MAAM,EAAEsE,IAAI,EAAEE,SAAS,EAAE;MAC5DN,QAAQ;MACRC,WAAW;MACXC,oBAAoB;MACpBK,WAAW,EAAEpE,MAAM;MACnBkE;IACF,CAAC,CAAC;IAEFjF,OAAO,CAACI,GAAG,CAACE,KAAK,GAAGA,KAAK;IAEzB,OAAOL,CAAC,CAACgC,QAAQ;EACnB;EAEA,OAAO8C,OAAO;AAChB;AAEA,OAAO,SAASK,eAAeA,CAACpF,OAAoB,EAAEC,CAAsB,EAAE;EAC5E,MAAM;IAAEK;EAAM,CAAC,GAAGN,OAAO,CAACI,GAAG;EAE7B,MAAMiF,WAAW,GAAG/E,KAAK,GAAG,IAAIA,KAAK,CAACgF,QAAQ,EAAE,GAAG,EAAE;EACrD,OAAO5F,OAAO,CAACM,OAAO,EAAEC,CAAC,EAAE,GAAGoF,WAAW,GAAG5F,YAAY,CAACa,KAAK,CAAC,EAAE,CAAC;AACpE","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -48,7 +48,6 @@ import {
|
|
|
48
48
|
createPage,
|
|
49
49
|
type PageControllerClass
|
|
50
50
|
} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
51
|
-
import { copyNotYetValidatedState } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
52
51
|
import { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'
|
|
53
52
|
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
54
53
|
import {
|
|
@@ -402,9 +401,6 @@ export class FormModel {
|
|
|
402
401
|
// Add paths for navigation
|
|
403
402
|
this.assignPaths(context)
|
|
404
403
|
|
|
405
|
-
// Handle restoration of payload from say a 'save-and-exit' request
|
|
406
|
-
copyNotYetValidatedState(request, context)
|
|
407
|
-
|
|
408
404
|
return context
|
|
409
405
|
}
|
|
410
406
|
|
|
@@ -31,10 +31,7 @@ import {
|
|
|
31
31
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
32
32
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
33
33
|
import { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
|
|
34
|
-
import {
|
|
35
|
-
clearNotYetValidatedState,
|
|
36
|
-
prefillStateFromQueryParameters
|
|
37
|
-
} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
34
|
+
import { prefillStateFromQueryParameters } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
38
35
|
import {
|
|
39
36
|
type AnyFormRequest,
|
|
40
37
|
type FormContext,
|
|
@@ -342,8 +339,7 @@ export class QuestionPageController extends PageController {
|
|
|
342
339
|
|
|
343
340
|
const cacheService = getCacheService(request.server)
|
|
344
341
|
|
|
345
|
-
|
|
346
|
-
return cacheService.setState(request, clearNotYetValidatedState(state))
|
|
342
|
+
return cacheService.setState(request, state)
|
|
347
343
|
}
|
|
348
344
|
|
|
349
345
|
async mergeState(
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
import { ComponentType, type Page } from '@defra/forms-model'
|
|
1
|
+
import { ComponentType, ControllerType, type Page } from '@defra/forms-model'
|
|
2
2
|
|
|
3
3
|
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
4
4
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
5
5
|
import {
|
|
6
|
+
checkSaveAndExitRepeater,
|
|
6
7
|
copyNotYetValidatedState,
|
|
7
8
|
prefillStateFromQueryParameters,
|
|
8
9
|
stripParam
|
|
9
10
|
} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
10
11
|
import {
|
|
11
12
|
type AnyFormRequest,
|
|
12
|
-
type FormContext
|
|
13
|
-
type FormContextRequest
|
|
13
|
+
type FormContext
|
|
14
14
|
} from '~/src/server/plugins/engine/types.js'
|
|
15
15
|
import { type FormsService, type Services } from '~/src/server/types.js'
|
|
16
16
|
|
|
17
|
+
const mockGetCacheService = jest.fn()
|
|
18
|
+
const mockCacheService = { setState: jest.fn() }
|
|
19
|
+
|
|
20
|
+
jest.mock('~/src/server/plugins/engine/helpers.ts', () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
getCacheService: (...args: unknown[]) => mockGetCacheService(...args)
|
|
23
|
+
}))
|
|
24
|
+
|
|
17
25
|
function buildMockPage(
|
|
18
26
|
pagesOverride = {},
|
|
19
27
|
stateOverride = {},
|
|
@@ -225,23 +233,26 @@ describe('State helpers', () => {
|
|
|
225
233
|
})
|
|
226
234
|
|
|
227
235
|
describe('copyNotYetValidatedState', () => {
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
beforeEach(() => {
|
|
237
|
+
mockGetCacheService.mockReturnValue(mockCacheService)
|
|
238
|
+
})
|
|
239
|
+
it('should ignore if no invalid state', async () => {
|
|
240
|
+
const mockRequest = {} as AnyFormRequest
|
|
230
241
|
const mockContext = {
|
|
231
242
|
state: { abc: '123' },
|
|
232
243
|
payload: {}
|
|
233
244
|
} as unknown as FormContext
|
|
234
|
-
copyNotYetValidatedState(mockRequest, mockContext)
|
|
245
|
+
await copyNotYetValidatedState(mockRequest, mockContext)
|
|
235
246
|
expect(mockContext.state).toEqual({ abc: '123' })
|
|
236
247
|
expect(mockContext.payload).toEqual({})
|
|
237
248
|
})
|
|
238
249
|
|
|
239
|
-
it('should ignore if wrong path', () => {
|
|
250
|
+
it('should ignore if wrong path', async () => {
|
|
240
251
|
const mockRequest = {
|
|
241
252
|
url: {
|
|
242
253
|
pathname: '/form-page1'
|
|
243
254
|
}
|
|
244
|
-
} as unknown as
|
|
255
|
+
} as unknown as AnyFormRequest
|
|
245
256
|
const mockContext = {
|
|
246
257
|
state: {
|
|
247
258
|
abc: '123',
|
|
@@ -252,7 +263,7 @@ describe('State helpers', () => {
|
|
|
252
263
|
},
|
|
253
264
|
payload: {}
|
|
254
265
|
} as unknown as FormContext
|
|
255
|
-
copyNotYetValidatedState(mockRequest, mockContext)
|
|
266
|
+
await copyNotYetValidatedState(mockRequest, mockContext)
|
|
256
267
|
expect(mockContext.state).toEqual({
|
|
257
268
|
abc: '123',
|
|
258
269
|
__stateNotYetValidated: {
|
|
@@ -263,12 +274,12 @@ describe('State helpers', () => {
|
|
|
263
274
|
expect(mockContext.payload).toEqual({})
|
|
264
275
|
})
|
|
265
276
|
|
|
266
|
-
it('should apply if correct path', () => {
|
|
277
|
+
it('should apply if correct path', async () => {
|
|
267
278
|
const mockRequest = {
|
|
268
279
|
url: {
|
|
269
280
|
pathname: '/form-page1'
|
|
270
281
|
}
|
|
271
|
-
} as unknown as
|
|
282
|
+
} as unknown as AnyFormRequest
|
|
272
283
|
const mockContext = {
|
|
273
284
|
state: {
|
|
274
285
|
abc: '123',
|
|
@@ -279,17 +290,70 @@ describe('State helpers', () => {
|
|
|
279
290
|
},
|
|
280
291
|
payload: {}
|
|
281
292
|
} as unknown as FormContext
|
|
282
|
-
copyNotYetValidatedState(mockRequest, mockContext)
|
|
293
|
+
await copyNotYetValidatedState(mockRequest, mockContext)
|
|
283
294
|
expect(mockContext.state).toEqual({
|
|
284
295
|
abc: '123',
|
|
285
|
-
__stateNotYetValidated:
|
|
286
|
-
def: '456',
|
|
287
|
-
__currentPagePath: '/form-page1'
|
|
288
|
-
}
|
|
296
|
+
__stateNotYetValidated: undefined
|
|
289
297
|
})
|
|
290
298
|
expect(mockContext.payload).toEqual({
|
|
291
299
|
def: '456'
|
|
292
300
|
})
|
|
293
301
|
})
|
|
294
302
|
})
|
|
303
|
+
|
|
304
|
+
describe('checkSaveAndExitRepeater', () => {
|
|
305
|
+
function createMockContextWithPath(path: string) {
|
|
306
|
+
return {
|
|
307
|
+
state: {
|
|
308
|
+
abc: '123',
|
|
309
|
+
__stateNotYetValidated: {
|
|
310
|
+
def: '456',
|
|
311
|
+
__currentPagePath: path
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
payload: {}
|
|
315
|
+
} as unknown as FormContext
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const mockModel = {
|
|
319
|
+
def: {
|
|
320
|
+
pages: [
|
|
321
|
+
{
|
|
322
|
+
controller: ControllerType.Repeat,
|
|
323
|
+
path: '/personal_details'
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
basePath: 'form/preview/draft/repeater-test'
|
|
328
|
+
} as unknown as FormModel
|
|
329
|
+
|
|
330
|
+
it('should return undefined if url does not end in a guid', () => {
|
|
331
|
+
const mockContext = createMockContextWithPath(
|
|
332
|
+
'/form/preview/draft/repeater-test/personal_details'
|
|
333
|
+
)
|
|
334
|
+
expect(checkSaveAndExitRepeater(mockContext, mockModel)).toBeUndefined()
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should return undefined if url ends in a guid but not a repeater path', () => {
|
|
338
|
+
const mockContext = createMockContextWithPath(
|
|
339
|
+
'/form/preview/draft/repeater-test/wrong_page/7d27fe6e-73e8-4265-84bd-1e118c92470b'
|
|
340
|
+
)
|
|
341
|
+
expect(checkSaveAndExitRepeater(mockContext, mockModel)).toBeUndefined()
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('should return undefined if url is not a string', () => {
|
|
345
|
+
// @ts-expect-error - invalid dataype on purpose for this test
|
|
346
|
+
const mockContext = createMockContextWithPath({})
|
|
347
|
+
expect(checkSaveAndExitRepeater(mockContext, mockModel)).toBeUndefined()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('should return correct urls if url ends in a guid and is a repeater path', () => {
|
|
351
|
+
const mockContext = createMockContextWithPath(
|
|
352
|
+
'/form/preview/draft/repeater-test/personal_details/7d27fe6e-73e8-4265-84bd-1e118c92470b'
|
|
353
|
+
)
|
|
354
|
+
expect(checkSaveAndExitRepeater(mockContext, mockModel)).toBe(
|
|
355
|
+
'/form/preview/draft/repeater-test/personal_details/7d27fe6e-73e8-4265-84bd-1e118c92470b'
|
|
356
|
+
)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
295
359
|
})
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { getHiddenFields } from '@defra/forms-model'
|
|
1
|
+
import { ControllerType, getHiddenFields } from '@defra/forms-model'
|
|
2
|
+
import { validate as isValidUUID } from 'uuid'
|
|
2
3
|
|
|
4
|
+
import { getCacheService } from '~/src/server/plugins/engine/helpers.js'
|
|
3
5
|
import {
|
|
4
6
|
CURRENT_PAGE_PATH_KEY,
|
|
5
7
|
STATE_NOT_YET_VALIDATED
|
|
6
8
|
} from '~/src/server/plugins/engine/index.js'
|
|
9
|
+
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
7
10
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
8
11
|
import {
|
|
9
12
|
type AnyFormRequest,
|
|
10
13
|
type FormContext,
|
|
11
|
-
type FormContextRequest,
|
|
12
14
|
type FormStateValue,
|
|
13
|
-
type FormSubmissionState,
|
|
14
15
|
type FormValue
|
|
15
16
|
} from '~/src/server/plugins/engine/types.js'
|
|
16
17
|
import { type FormQuery } from '~/src/server/routes/types.js'
|
|
17
18
|
import { type Services } from '~/src/server/types.js'
|
|
18
19
|
|
|
20
|
+
const GUID_LENGTH = 36
|
|
21
|
+
|
|
19
22
|
/**
|
|
20
23
|
* A series of functions that can transform a pre-fill input parameter e.g lookup a form title based on form id
|
|
21
24
|
*/
|
|
@@ -100,14 +103,56 @@ export async function prefillStateFromQueryParameters(
|
|
|
100
103
|
return true
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Checks whether the save-and-exit finished on a repeater with partial state
|
|
108
|
+
* @param context - the form context
|
|
109
|
+
*/
|
|
110
|
+
export function checkSaveAndExitRepeater(
|
|
111
|
+
context: FormContext,
|
|
112
|
+
model: FormModel
|
|
113
|
+
) {
|
|
114
|
+
const potentiallyInvalidState = context.state[STATE_NOT_YET_VALIDATED] as
|
|
115
|
+
| Record<string, FormValue>
|
|
116
|
+
| undefined
|
|
117
|
+
if (!potentiallyInvalidState) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const originalPath = potentiallyInvalidState[CURRENT_PAGE_PATH_KEY]
|
|
122
|
+
|
|
123
|
+
const repeaterPaths = model.def.pages
|
|
124
|
+
.filter((page) => page.controller === ControllerType.Repeat)
|
|
125
|
+
.map((p) => `/${model.basePath}${p.path}/`)
|
|
126
|
+
|
|
127
|
+
if (typeof originalPath !== 'string') {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const segments = originalPath.split('/')
|
|
132
|
+
const lastSegment = segments.at(-1) ?? ''
|
|
133
|
+
|
|
134
|
+
if (!isValidUUID(lastSegment)) {
|
|
135
|
+
return undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const guidStartIndex = originalPath.length - GUID_LENGTH
|
|
139
|
+
const originalPathWithoutGuid = originalPath.substring(0, guidStartIndex)
|
|
140
|
+
|
|
141
|
+
if (!repeaterPaths.includes(originalPathWithoutGuid)) {
|
|
142
|
+
return undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return originalPath
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
/**
|
|
104
149
|
* Copies any potentially invalid state into the payload, and removes those values from state
|
|
105
150
|
* NOTE - this method has a side-effect on 'context.state' and 'context.payload'
|
|
106
151
|
* @param request - the form request
|
|
107
152
|
* @param context - the form context
|
|
108
153
|
*/
|
|
109
|
-
export function copyNotYetValidatedState(
|
|
110
|
-
request:
|
|
154
|
+
export async function copyNotYetValidatedState(
|
|
155
|
+
request: AnyFormRequest,
|
|
111
156
|
context: FormContext
|
|
112
157
|
) {
|
|
113
158
|
const potentiallyInvalidState = context.state[STATE_NOT_YET_VALIDATED] as
|
|
@@ -125,18 +170,13 @@ export function copyNotYetValidatedState(
|
|
|
125
170
|
...potentiallyInvalidState,
|
|
126
171
|
[CURRENT_PAGE_PATH_KEY]: undefined
|
|
127
172
|
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
173
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (state[STATE_NOT_YET_VALIDATED]) {
|
|
139
|
-
state[STATE_NOT_YET_VALIDATED] = undefined
|
|
174
|
+
// Remove any temporary 'not yet validated' state now it's been copied to the payload
|
|
175
|
+
if (context.state[STATE_NOT_YET_VALIDATED]) {
|
|
176
|
+
context.state[STATE_NOT_YET_VALIDATED] = undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const cacheService = getCacheService(request.server)
|
|
180
|
+
await cacheService.setState(request, context.state)
|
|
140
181
|
}
|
|
141
|
-
return state
|
|
142
182
|
}
|
|
@@ -86,7 +86,8 @@ describe('redirectOrMakeHandler', () => {
|
|
|
86
86
|
// Reset mock model
|
|
87
87
|
mockModel.getFormContext = jest.fn().mockReturnValue({
|
|
88
88
|
isForceAccess: false,
|
|
89
|
-
data: {}
|
|
89
|
+
data: {},
|
|
90
|
+
state: {}
|
|
90
91
|
})
|
|
91
92
|
|
|
92
93
|
// Setup mocks
|
|
@@ -225,7 +226,8 @@ describe('redirectOrMakeHandler', () => {
|
|
|
225
226
|
it('should call makeHandler when context has force access', async () => {
|
|
226
227
|
mockModel.getFormContext = jest.fn().mockReturnValue({
|
|
227
228
|
isForceAccess: true,
|
|
228
|
-
data: {}
|
|
229
|
+
data: {},
|
|
230
|
+
state: {}
|
|
229
231
|
})
|
|
230
232
|
|
|
231
233
|
await redirectOrMakeHandler(
|
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
proceed
|
|
24
24
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
25
25
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
26
|
+
import {
|
|
27
|
+
checkSaveAndExitRepeater,
|
|
28
|
+
copyNotYetValidatedState
|
|
29
|
+
} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
26
30
|
import { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'
|
|
27
31
|
import * as defaultServices from '~/src/server/plugins/engine/services/index.js'
|
|
28
32
|
import {
|
|
@@ -78,6 +82,9 @@ export async function redirectOrMakeHandler(
|
|
|
78
82
|
|
|
79
83
|
const flash = cacheService.getFlash(request)
|
|
80
84
|
const context = model.getFormContext(request, state, flash?.errors)
|
|
85
|
+
|
|
86
|
+
await copyNotYetValidatedState(request, context)
|
|
87
|
+
|
|
81
88
|
const relevantPath = page.getRelevantPath(request, context)
|
|
82
89
|
const summaryPath = page.getSummaryPath()
|
|
83
90
|
|
|
@@ -89,6 +96,12 @@ export async function redirectOrMakeHandler(
|
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
// Check whether save-and-exit should resume from within a repeater
|
|
100
|
+
const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model)
|
|
101
|
+
if (resumeInRepeaterUrl) {
|
|
102
|
+
return proceed(request, h, resumeInRepeaterUrl)
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
// Return handler for relevant pages or preview URL direct access
|
|
93
106
|
if (relevantPath.startsWith(page.path) || context.isForceAccess) {
|
|
94
107
|
return makeHandler(page, context)
|
|
@@ -96,7 +109,6 @@ export async function redirectOrMakeHandler(
|
|
|
96
109
|
|
|
97
110
|
// Redirect back to last relevant page
|
|
98
111
|
const redirectTo = findPage(model, relevantPath)
|
|
99
|
-
|
|
100
112
|
// Set the return URL unless an exit page
|
|
101
113
|
if (redirectTo?.next.length) {
|
|
102
114
|
request.query.returnUrl = page.getHref(summaryPath)
|