@defra/forms-engine-plugin 4.0.3 → 4.0.5

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.
@@ -12,3 +12,9 @@
12
12
  .govuk-header__container {
13
13
  border-bottom: 10px solid #003d16;
14
14
  }
15
+
16
+ @media print {
17
+ .govuk-link[href]::after {
18
+ content: none;
19
+ }
20
+ }
@@ -82,14 +82,19 @@ async function importExternalComponentState(request, page, state) {
82
82
  };
83
83
 
84
84
  // Save the external component state immediately
85
- const updatedState = await page.mergeState(request, state, componentState);
85
+ const pageState = page.getStateFromValidForm(request, state, componentState);
86
+ const savedState = await page.mergeState(request, state, pageState);
86
87
 
87
- // Merge the stashed payload into the local state
88
+ // Merge any stashed payload into the local state
88
89
  const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD);
89
90
  const stashedPayload = Array.isArray(payload) ? {} : payload;
90
- return {
91
+ const localState = page.getStateFromValidForm(request, savedState, {
91
92
  ...stashedPayload,
92
- ...updatedState
93
+ ...componentState
94
+ });
95
+ return {
96
+ ...savedState,
97
+ ...localState
93
98
  };
94
99
  }
95
100
  export function makeLoadFormPreHandler(server, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Boom","isEqual","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","PREVIEW_PATH_PREFIX","FormComponent","isFormState","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","FormModel","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","Object","fromEntries","entries","map","key","value","updatedState","payload","stashedPayload","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","formsService","handler","slug","isPreview","formState","getFormMetadata","id","item","models","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","versionNumber","versions","set","dispatchHandler","servicePath"],"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'\nimport { isEqual } from 'date-fns'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD,\n PREVIEW_PATH_PREFIX\n} from '~/src/server/constants.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.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 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 const componentState = isFormState(stateAppendage)\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 immediately\n const updatedState = await page.mergeState(request, state, componentState)\n\n // Merge the 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 return { ...stashedPayload, ...updatedState }\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 const { formsService } = services\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 // Get the form metadata using the `slug` param\n const metadata = await formsService.getFormMetadata(slug)\n\n const { id, [formState]: state } = metadata\n\n // Check the metadata supports the requested state\n if (!state) {\n throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)\n }\n\n // Cache the models based on id, state and whether\n // it's a preview or not. There could be up to 3 models\n // cached for a single form:\n // \"{id}_live_false\" (live/live)\n // \"{id}_live_true\" (live/preview)\n // \"{id}_draft_true\" (draft/preview)\n const key = `${id}_${formState}_${isPreview}`\n let item = server.app.models.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)\n\n // Get the form definition using the `id` from the metadata\n const definition = await formsService.getFormDefinition(id, formState)\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${id} (${slug}) ${formState}`\n )\n }\n\n const emailAddress = metadata.notificationEmail ?? definition.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Build the form model\n server.logger.info(\n `Building model for form definition ${id} (${slug}) ${formState}`\n )\n\n // Set up the basePath for the model\n const basePath = (\n isPreview\n ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`\n : `${prefix}/${slug}`\n ).substring(1)\n\n const versionNumber = metadata.versions?.[0]?.versionNumber\n\n // Construct the form model\n const model = new FormModel(\n definition,\n { basePath, versionNumber, ordnanceSurveyApiKey },\n services,\n controllers\n )\n\n // Create new item and add it to the item cache\n item = { model, updatedAt: state.updatedAt }\n server.app.models.set(key, item)\n }\n\n // Assign the model to the request data\n // for use in the downstream handler\n request.app.model = item.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","isEqual","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","PREVIEW_PATH_PREFIX","FormComponent","isFormState","checkEmailAddressForLiveFormSubmission","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","FormModel","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","Object","fromEntries","entries","map","key","value","pageState","getStateFromValidForm","savedState","payload","stashedPayload","localState","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","formsService","handler","slug","isPreview","formState","getFormMetadata","id","item","models","updatedAt","logger","info","definition","getFormDefinition","emailAddress","notificationEmail","outputEmail","basePath","substring","versionNumber","versions","set","dispatchHandler","servicePath"],"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'\nimport { isEqual } from 'date-fns'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD,\n PREVIEW_PATH_PREFIX\n} from '~/src/server/constants.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { FormModel } from '~/src/server/plugins/engine/models/index.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 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 const componentState = isFormState(stateAppendage)\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 immediately\n const pageState = page.getStateFromValidForm(\n request,\n state,\n componentState as FormPayload\n )\n const savedState = await page.mergeState(request, state, pageState)\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 const { formsService } = services\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 // Get the form metadata using the `slug` param\n const metadata = await formsService.getFormMetadata(slug)\n\n const { id, [formState]: state } = metadata\n\n // Check the metadata supports the requested state\n if (!state) {\n throw Boom.notFound(`No '${formState}' state for form metadata ${id}`)\n }\n\n // Cache the models based on id, state and whether\n // it's a preview or not. There could be up to 3 models\n // cached for a single form:\n // \"{id}_live_false\" (live/live)\n // \"{id}_live_true\" (live/preview)\n // \"{id}_draft_true\" (draft/preview)\n const key = `${id}_${formState}_${isPreview}`\n let item = server.app.models.get(key)\n\n if (!item || !isEqual(item.updatedAt, state.updatedAt)) {\n server.logger.info(`Getting form definition ${id} (${slug}) ${formState}`)\n\n // Get the form definition using the `id` from the metadata\n const definition = await formsService.getFormDefinition(id, formState)\n\n if (!definition) {\n throw Boom.notFound(\n `No definition found for form metadata ${id} (${slug}) ${formState}`\n )\n }\n\n const emailAddress = metadata.notificationEmail ?? definition.outputEmail\n\n checkEmailAddressForLiveFormSubmission(emailAddress, isPreview)\n\n // Build the form model\n server.logger.info(\n `Building model for form definition ${id} (${slug}) ${formState}`\n )\n\n // Set up the basePath for the model\n const basePath = (\n isPreview\n ? `${prefix}${PREVIEW_PATH_PREFIX}/${formState}/${slug}`\n : `${prefix}/${slug}`\n ).substring(1)\n\n const versionNumber = metadata.versions?.[0]?.versionNumber\n\n // Construct the form model\n const model = new FormModel(\n definition,\n { basePath, versionNumber, ordnanceSurveyApiKey },\n services,\n controllers\n )\n\n // Create new item and add it to the item cache\n item = { model, updatedAt: state.updatedAt }\n server.app.models.set(key, item)\n }\n\n // Assign the model to the request data\n // for use in the downstream handler\n request.app.model = item.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":[]}
@@ -63,6 +63,10 @@
63
63
  }) }}
64
64
  <p class="govuk-body govuk-!-margin-bottom-0">or <button class="govuk-link govuk-button--link govuk-!-margin-right-1 govuk-!-margin-bottom-0" name="action" value="external-{{component.model.name}}--step:manual">enter address manually</button></p>
65
65
  </div>
66
+ {# Include a line break if this is the last component #}
67
+ {% if components[components.length - 1] == component %}
68
+ <hr class="govuk-section-break govuk-section-break--l govuk-section-break--visible">
69
+ {% endif %}
66
70
  {% endif %}
67
71
  {% endif %}
68
72
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -12,3 +12,9 @@
12
12
  .govuk-header__container {
13
13
  border-bottom: 10px solid #003d16;
14
14
  }
15
+
16
+ @media print {
17
+ .govuk-link[href]::after {
18
+ content: none;
19
+ }
20
+ }
@@ -150,13 +150,23 @@ async function importExternalComponentState(
150
150
  : { [componentName]: stateAppendage }
151
151
 
152
152
  // Save the external component state immediately
153
- const updatedState = await page.mergeState(request, state, componentState)
154
-
155
- // Merge the stashed payload into the local state
153
+ const pageState = page.getStateFromValidForm(
154
+ request,
155
+ state,
156
+ componentState as FormPayload
157
+ )
158
+ const savedState = await page.mergeState(request, state, pageState)
159
+
160
+ // Merge any stashed payload into the local state
156
161
  const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)
157
162
  const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)
158
163
 
159
- return { ...stashedPayload, ...updatedState }
164
+ const localState = page.getStateFromValidForm(request, savedState, {
165
+ ...stashedPayload,
166
+ ...componentState
167
+ } as FormPayload)
168
+
169
+ return { ...savedState, ...localState }
160
170
  }
161
171
 
162
172
  export function makeLoadFormPreHandler(server: Server, options: PluginOptions) {
@@ -63,6 +63,10 @@
63
63
  }) }}
64
64
  <p class="govuk-body govuk-!-margin-bottom-0">or <button class="govuk-link govuk-button--link govuk-!-margin-right-1 govuk-!-margin-bottom-0" name="action" value="external-{{component.model.name}}--step:manual">enter address manually</button></p>
65
65
  </div>
66
+ {# Include a line break if this is the last component #}
67
+ {% if components[components.length - 1] == component %}
68
+ <hr class="govuk-section-break govuk-section-break--l govuk-section-break--visible">
69
+ {% endif %}
66
70
  {% endif %}
67
71
  {% endif %}
68
72
  </div>