@defra/forms-engine-plugin 5.0.0-alpha.10 → 5.0.0-alpha.11

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.
Files changed (60) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.LICENSE.txt +7 -0
  3. package/.public/javascripts/application.min.js.map +1 -1
  4. package/.public/javascripts/shared.min.js +1 -1
  5. package/.public/javascripts/shared.min.js.LICENSE.txt +7 -0
  6. package/.public/javascripts/shared.min.js.map +1 -1
  7. package/.server/client/javascripts/file-upload.js +1 -0
  8. package/.server/client/javascripts/file-upload.js.map +1 -1
  9. package/.server/client/javascripts/geospatial-map.js +2 -28
  10. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  11. package/.server/client/javascripts/govuk.js +2 -1
  12. package/.server/client/javascripts/govuk.js.map +1 -1
  13. package/.server/client/javascripts/location-map.js +3 -2
  14. package/.server/client/javascripts/location-map.js.map +1 -1
  15. package/.server/client/javascripts/map.d.ts +6 -0
  16. package/.server/client/javascripts/map.js +37 -0
  17. package/.server/client/javascripts/map.js.map +1 -1
  18. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +3 -0
  19. package/.server/server/plugins/engine/components/EastingNorthingField.js +17 -0
  20. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/FileUploadField.js +3 -1
  22. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  23. package/.server/server/plugins/engine/components/LatLongField.d.ts +2 -0
  24. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +3 -1
  25. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +3 -1
  26. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  27. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +3 -1
  28. package/.server/server/plugins/engine/components/helpers/geospatial.js +32 -0
  29. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  30. package/.server/server/plugins/engine/i18n/translations/cy.json +13 -3
  31. package/.server/server/plugins/engine/i18n/translations/en-GB.json +13 -3
  32. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +6 -0
  33. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +34 -1
  34. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  35. package/.server/server/plugins/engine/types.d.ts +4 -0
  36. package/.server/server/plugins/engine/types.js.map +1 -1
  37. package/.server/server/plugins/engine/views/components/_location-field-base.html +2 -1
  38. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  39. package/.server/server/utils/utils.d.ts +6 -0
  40. package/.server/server/utils/utils.js +18 -1
  41. package/.server/server/utils/utils.js.map +1 -1
  42. package/.server/server/utils/utils.test.js +21 -1
  43. package/.server/server/utils/utils.test.js.map +1 -1
  44. package/package.json +2 -1
  45. package/src/client/javascripts/file-upload.js +1 -0
  46. package/src/client/javascripts/geospatial-map.js +2 -31
  47. package/src/client/javascripts/govuk.js +2 -0
  48. package/src/client/javascripts/location-map.js +3 -1
  49. package/src/client/javascripts/map.js +42 -0
  50. package/src/server/plugins/engine/components/EastingNorthingField.ts +31 -0
  51. package/src/server/plugins/engine/components/FileUploadField.ts +3 -1
  52. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +6 -2
  53. package/src/server/plugins/engine/components/helpers/geospatial.ts +53 -0
  54. package/src/server/plugins/engine/i18n/translations/cy.json +13 -3
  55. package/src/server/plugins/engine/i18n/translations/en-GB.json +13 -3
  56. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +42 -1
  57. package/src/server/plugins/engine/types.ts +4 -0
  58. package/src/server/plugins/engine/views/components/_location-field-base.html +2 -1
  59. package/src/server/plugins/engine/views/file-upload.html +1 -1
  60. package/src/server/utils/utils.js +19 -1
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","StatusCodes","FileUploadField","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","at","length","badImplementation","path","indexOf","name","viewName","getStateKeys","component","pagePath","page","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","fileId","itemId","notFound","filename","translator","getTranslator","t","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","language","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","count","summaryList","rows","uploadingLabel","index","proxyUrl","uploadUrl","formAction","uploadId","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","err","isBoom","output","statusCode","NOT_FOUND","valueOf","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","processUploadedFiles","validatedItem","validatedStatus","rawFile","uploadedFiles","Array","isArray","allErrors","complete","perFileState","unshift","cacheService","server","setFlash","some","f","mergeState","filesUpdated","options","schema","getFormMetadata","services","formsService","max","Math","min","formMetadata","slug","notificationEmail","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { StatusCodes } from 'http-status-codes'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n FileUploadField,\n tempItemSchema\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FileUpload,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n /**\n * Get supplementary state keys for clearing file upload state.\n * Returns the nested upload path for FileUploadField components only.\n * @param component - The component to get supplementary state keys for\n * @returns Array containing the nested upload path, e.g., [\"upload['/page-path']\"]\n * or ['upload'] if no page path is available. Returns empty array for non-FileUploadField components.\n */\n getStateKeys(component: FormComponent): string[] {\n // Only return upload keys for FileUploadField components\n if (!(component instanceof FileUploadField)) {\n return []\n }\n\n const pagePath = component.page?.path\n return pagePath ? [`upload['${pagePath}']`] : ['upload']\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ status }) => status.form.file.fileId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n const translator = this.getTranslator(request)\n const { t } = translator\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context, t),\n pageTitle: t('pages.fileUpload.removeFileTitle'),\n itemTitle: filename,\n confirmation: { text: t('pages.fileUpload.removeFileBody') },\n buttonConfirm: { text: t('pages.fileUpload.removeFileButton') },\n buttonCancel: { text: t('pages.fileUpload.cancel') },\n t\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[], language?: string) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(language, error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext,\n translator: Translator\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n const { t } = translator\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context, translator)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n formComponent.model.upload = {\n count: formComponent.model.upload?.count ?? 0,\n summaryList: formComponent.model.upload?.summaryList ?? { rows: [] },\n uploadingLabel: t('components.fileUploadField.uploading')\n }\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl,\n t\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n\n let statusResponse\n\n try {\n statusResponse = await getUploadStatus(uploadId)\n } catch (err) {\n // if the user loads a file upload page and queries the cached upload, after the upload has\n // expired in CDP, we will get a 404 from the getUploadStatus endpoint.\n // In this case we want to initiate a new upload and return that state, so the form\n // doesn't blow up for the end user.\n if (\n Boom.isBoom(err) &&\n err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()\n ) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n throw err\n }\n\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // (e.g. changing it to a simple text field or similar).\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n // CDP returns form.file as a single object for one file,\n // or an array for multiple files. The Joi schema normalises\n // both to an array via .single().\n await this.processUploadedFiles(\n request,\n state,\n validationResult.value,\n files,\n upload\n )\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Processes the uploaded files from a CDP status response.\n * Complete files are added to state, rejected/pending files\n * have their error messages flashed.\n * @param request - the hapi request\n * @param state - the form state\n * @param validatedItem - the Joi-validated upload item\n * @param files - the current files array from state\n * @param upload - the current upload initiation response\n */\n private async processUploadedFiles(\n request: AnyFormRequest,\n state: FormSubmissionState,\n validatedItem: FileState,\n files: FileState[],\n upload: UploadInitiateResponse | undefined\n ) {\n const { uploadId } = validatedItem\n const validatedStatus = validatedItem.status\n const rawFile = validatedStatus.form.file as unknown as\n | FileUpload\n | FileUpload[]\n const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile]\n\n const allErrors: FormSubmissionError[] = []\n\n for (const file of uploadedFiles) {\n if (file.fileStatus === FileStatus.complete) {\n const perFileState: FileState = {\n uploadId,\n status: {\n ...validatedStatus,\n form: { file }\n } as FileState['status']\n }\n files.unshift(prepareFileState(perFileState))\n } else {\n // Collect the error for rejected/pending files.\n const { fileUpload } = this\n const name = fileUpload.name\n const text = file.errorMessage ?? 'Unknown error'\n allErrors.push({ path: [name], href: `#${name}`, name, text })\n }\n }\n\n if (allErrors.length) {\n const cacheService = getCacheService(request.server)\n cacheService.setFlash(request, { errors: allErrors })\n }\n\n if (uploadedFiles.some((f) => f.fileStatus === FileStatus.complete)) {\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n }\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ status }) => status.form.file.fileId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n const { getFormMetadata } = this.model.services.formsService\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const formMetadata = await getFormMetadata(request.params.slug)\n const notificationEmail =\n formMetadata.notificationEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(\n href,\n notificationEmail,\n options.accept\n )\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"AAAA,SAASA,aAAa,QAA6B,oBAAoB;AACvE,OAAOC,IAAI,MAAM,YAAY;AAC7B,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,WAAW,QAAQ,mBAAmB;AAG/C,SACEC,eAAe,EACfC,cAAc;AAGhB,SACEC,eAAe,EACfC,QAAQ,EACRC,0BAA0B;AAI5B,SAASC,sBAAsB;AAC/B,SAASC,8BAA8B;AACvC,SACEC,eAAe,EACfC,cAAc;AAEhB,SACEC,UAAU,EACVC,YAAY;AAmBd,MAAMC,WAAW,GAAG,EAAE;AACtB,MAAMC,qBAAqB,GAAG,KAAK,EAAC;;AAEpC,OAAO,SAASC,aAAaA,CAACC,MAAgC,EAAE;EAC9D,MAAMC,IAAI,GAAGD,MAAM,CAACE,IAAI,CAACD,IAAI;EAC7B,MAAME,SAAS,GAAGF,IAAI,CAACG,UAAU,KAAKT,UAAU,CAACU,OAAO;EAExD,IAAI,CAACJ,IAAI,CAACK,YAAY,IAAIH,SAAS,EAAE;IACnCF,IAAI,CAACK,YAAY,GAAG,0CAA0C;EAChE;EAEA,OAAON,MAAM;AACf;AAEA,SAASO,gBAAgBA,CAACC,SAAoB,EAAE;EAC9CT,aAAa,CAACS,SAAS,CAACR,MAAM,CAAC;EAE/B,OAAOQ,SAAS;AAClB;AAEA,OAAO,MAAMC,wBAAwB,SAASlB,sBAAsB,CAAC;EAGnEmB,UAAU;EACVC,kBAAkB,GAAG,aAAa;EAElCC,WAAWA,CAACC,KAAgB,EAAEC,OAAuB,EAAE;IACrD,KAAK,CAACD,KAAK,EAAEC,OAAO,CAAC;IAErB,MAAM;MAAEC;IAAW,CAAC,GAAG,IAAI;;IAE3B;IACA,MAAMC,WAAW,GAAGD,UAAU,CAACE,MAAM,CAACC,MAAM,CACzCC,KAAK,IACJA,KAAK,CAACC,IAAI,KAAKtC,aAAa,CAACI,eACjC,CAAC;IAED,MAAMwB,UAAU,GAAGM,WAAW,CAACK,EAAE,CAAC,CAAC,CAAC;;IAEpC;IACA,IAAI,CAACX,UAAU,IAAIM,WAAW,CAACM,MAAM,GAAG,CAAC,EAAE;MACzC,MAAMvC,IAAI,CAACwC,iBAAiB,CAC1B,oEAAoET,OAAO,CAACU,IAAI,GAClF,CAAC;IACH;;IAEA;IACA,IAAIT,UAAU,CAACE,MAAM,CAACQ,OAAO,CAACf,UAAU,CAAC,KAAK,CAAC,EAAE;MAC/C,MAAM3B,IAAI,CAACwC,iBAAiB,CAC1B,aAAab,UAAU,CAACgB,IAAI,iEAAiEZ,OAAO,CAACU,IAAI,GAC3G,CAAC;IACH;;IAEA;IACA,IAAI,CAACd,UAAU,GAAGA,UAAU;IAC5B,IAAI,CAACiB,QAAQ,GAAG,aAAa;EAC/B;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,YAAYA,CAACC,SAAwB,EAAY;IAC/C;IACA,IAAI,EAAEA,SAAS,YAAY3C,eAAe,CAAC,EAAE;MAC3C,OAAO,EAAE;IACX;IAEA,MAAM4C,QAAQ,GAAGD,SAAS,CAACE,IAAI,EAAEP,IAAI;IACrC,OAAOM,QAAQ,GAAG,CAAC,WAAWA,QAAQ,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;EAC1D;EAEAE,oBAAoBA,CAClBC,OAAuC,EACvCC,KAA0B,EAC1B;IACA,MAAM;MAAExB;IAAW,CAAC,GAAG,IAAI;IAE3B,MAAMyB,OAAO,GAAG,KAAK,CAACH,oBAAoB,CAACC,OAAO,EAAEC,KAAK,CAAC;IAC1D,MAAME,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACAC,OAAO,CAACzB,UAAU,CAACgB,IAAI,CAAC,GAAGU,KAAK,CAACd,MAAM,GAAGc,KAAK,GAAGE,SAAS;IAE3D,OAAOH,OAAO;EAChB;EAEA,MAAMI,QAAQA,CAACN,OAAuB,EAAE;IACtC,MAAM;MAAEvB;IAAW,CAAC,GAAG,IAAI;;IAE3B;IACA,MAAMwB,KAAK,GAAG,MAAM,KAAK,CAACK,QAAQ,CAACN,OAAO,CAAC;IAC3C,MAAMG,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACAA,KAAK,CAACxB,UAAU,CAACgB,IAAI,CAAC,GAAGU,KAAK;IAE9B,OAAO,IAAI,CAACI,aAAa,CAACP,OAAO,EAAEC,KAAK,CAAC;EAC3C;;EAEA;AACF;AACA;EACEG,iBAAiBA,CAACH,KAA0B,EAAE;IAC5C,MAAM;MAAEV;IAAK,CAAC,GAAG,IAAI;IAErB,MAAMiB,WAAW,GAAGP,KAAK,CAACQ,MAAM,GAAGlB,IAAI,CAAC;IACxC,OAAOiB,WAAW,EAAEL,KAAK,IAAI,EAAE;EACjC;;EAEA;AACF;AACA;EACEO,kBAAkBA,CAACT,KAA0B,EAAE;IAC7C,MAAM;MAAEV;IAAK,CAAC,GAAG,IAAI;IAErB,MAAMiB,WAAW,GAAGP,KAAK,CAACQ,MAAM,GAAGlB,IAAI,CAAC;IACxC,OAAOiB,WAAW,EAAEC,MAAM;EAC5B;EAEAE,6BAA6BA,CAAA,EAAG;IAC9B,OAAO,CACLX,OAAoB,EACpBY,OAAoB,EACpBC,CAAsB,KACnB;MACH,MAAM;QAAEC;MAAU,CAAC,GAAG,IAAI;MAC1B,MAAM;QAAEC;MAAO,CAAC,GAAGf,OAAO;MAC1B,MAAM;QAAEC;MAAM,CAAC,GAAGW,OAAO;MAEzB,MAAMT,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;MAE3C,MAAMe,YAAY,GAAGb,KAAK,CAACc,IAAI,CAC7B,CAAC;QAAElD;MAAO,CAAC,KAAKA,MAAM,CAACE,IAAI,CAACD,IAAI,CAACkD,MAAM,KAAKH,MAAM,CAACI,MACrD,CAAC;MAED,IAAI,CAACH,YAAY,EAAE;QACjB,MAAMlE,IAAI,CAACsE,QAAQ,CAAC,0BAA0B,CAAC;MACjD;MAEA,MAAM;QAAEC;MAAS,CAAC,GAAGL,YAAY,CAACjD,MAAM,CAACE,IAAI,CAACD,IAAI;MAElD,MAAMsD,UAAU,GAAG,IAAI,CAACC,aAAa,CAACvB,OAAO,CAAC;MAC9C,MAAM;QAAEwB;MAAE,CAAC,GAAGF,UAAU;MAExB,OAAOT,CAAC,CAACY,IAAI,CAAC,IAAI,CAAC/C,kBAAkB,EAAE;QACrC,GAAGoC,SAAS;QACZF,OAAO;QACPc,QAAQ,EAAE,IAAI,CAACC,WAAW,CAAC3B,OAAO,EAAEY,OAAO,EAAEY,CAAC,CAAC;QAC/CI,SAAS,EAAEJ,CAAC,CAAC,kCAAkC,CAAC;QAChDK,SAAS,EAAER,QAAQ;QACnBS,YAAY,EAAE;UAAEC,IAAI,EAAEP,CAAC,CAAC,iCAAiC;QAAE,CAAC;QAC5DQ,aAAa,EAAE;UAAED,IAAI,EAAEP,CAAC,CAAC,mCAAmC;QAAE,CAAC;QAC/DS,YAAY,EAAE;UAAEF,IAAI,EAAEP,CAAC,CAAC,yBAAyB;QAAE,CAAC;QACpDA;MACF,CAAmC,CAAC;IACtC,CAAC;EACH;EAEAU,8BAA8BA,CAAA,EAAG;IAC/B,OAAO,OACLlC,OAA2B,EAC3BY,OAAoB,EACpBC,CAAsB,KACnB;MACH,MAAM;QAAEtB;MAAK,CAAC,GAAG,IAAI;MACrB,MAAM;QAAEU;MAAM,CAAC,GAAGW,OAAO;MAEzB,MAAM;QAAEuB;MAAQ,CAAC,GAAG,IAAI,CAACC,aAAa,CAACpC,OAAO,CAAC;;MAE/C;MACA,IAAImC,OAAO,EAAE;QACX,MAAM,IAAI,CAACE,iBAAiB,CAACrC,OAAO,EAAEC,KAAK,CAAC;QAC5C,OAAO,IAAI,CAACqC,OAAO,CAACtC,OAAO,EAAEa,CAAC,EAAEtB,IAAI,CAAC;MACvC;MAEA,OAAO,IAAI,CAAC+C,OAAO,CAACtC,OAAO,EAAEa,CAAC,CAAC;IACjC,CAAC;EACH;EAEA0B,SAASA,CAACC,OAA+B,EAAEC,QAAiB,EAAE;IAC5D,MAAM;MAAEhE;IAAW,CAAC,GAAG,IAAI;IAE3B,IAAI+D,OAAO,EAAE;MACX,MAAME,MAA6B,GAAG,EAAE;MAExCF,OAAO,CAACG,OAAO,CAAEC,KAAK,IAAK;QACzB,MAAMC,aAAa,GAAGD,KAAK,CAACrD,IAAI,CAAC,CAAC,CAAC,KAAKd,UAAU,CAACgB,IAAI;QACvD,MAAMqD,iBAAiB,GAAGD,aAAa,IAAID,KAAK,CAACrD,IAAI,CAACF,MAAM,KAAK,CAAC;QAElE,IAAI,CAACwD,aAAa,IAAIC,iBAAiB,EAAE;UACvC;UACA;UACAJ,MAAM,CAACK,IAAI,CAAC3F,QAAQ,CAACqF,QAAQ,EAAEG,KAAK,CAAC,CAAC;QACxC,CAAC,MAAM;UACL,MAAM;YAAEhC,OAAO;YAAErB,IAAI;YAAEJ;UAAK,CAAC,GAAGyD,KAAK;UAErC,IAAIzD,IAAI,KAAK,gBAAgB,IAAII,IAAI,CAACH,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,cAAc,EAAE;YAC/D,MAAM4D,KAAK,GAAGpC,OAAO,EAAEoC,KAA2B;YAElD,IAAIA,KAAK,EAAE;cACT,MAAMvD,IAAI,GAAGhB,UAAU,CAACgB,IAAI;cAC5B,MAAMsC,IAAI,GAAG,OAAOiB,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAG,eAAe;cAChE,MAAMC,IAAI,GAAG,IAAIxD,IAAI,EAAE;cAEvBiD,MAAM,CAACK,IAAI,CAAC;gBAAExD,IAAI;gBAAE0D,IAAI;gBAAExD,IAAI;gBAAEsC;cAAK,CAAC,CAAC;YACzC;UACF;QACF;MACF,CAAC,CAAC;MAEF,OAAOW,MAAM;IACf;EACF;EAEAQ,YAAYA,CACVlD,OAA2B,EAC3BY,OAAoB,EACpBU,UAAsB,EACK;IAC3B,MAAM;MAAE7C;IAAW,CAAC,GAAG,IAAI;IAC3B,MAAM;MAAEwB;IAAM,CAAC,GAAGW,OAAO;IACzB,MAAM;MAAEY;IAAE,CAAC,GAAGF,UAAU;IAExB,MAAMb,MAAM,GAAG,IAAI,CAACC,kBAAkB,CAACT,KAAK,CAAC;IAE7C,MAAMa,SAAS,GAAG,KAAK,CAACoC,YAAY,CAAClD,OAAO,EAAEY,OAAO,EAAEU,UAAU,CAAC;IAClE,MAAM;MAAE6B;IAAW,CAAC,GAAGrC,SAAS;;IAEhC;IACA,MAAM,CAACsC,aAAa,CAAC,GAAGD,UAAU,CAAClE,MAAM,CACvC,CAAC;MAAEL;IAAM,CAAC,KAAKA,KAAK,CAACyE,EAAE,KAAK5E,UAAU,CAACgB,IACzC,CAAC;IAED2D,aAAa,CAACxE,KAAK,CAAC6B,MAAM,GAAG;MAC3B6C,KAAK,EAAEF,aAAa,CAACxE,KAAK,CAAC6B,MAAM,EAAE6C,KAAK,IAAI,CAAC;MAC7CC,WAAW,EAAEH,aAAa,CAACxE,KAAK,CAAC6B,MAAM,EAAE8C,WAAW,IAAI;QAAEC,IAAI,EAAE;MAAG,CAAC;MACpEC,cAAc,EAAEjC,CAAC,CAAC,sCAAsC;IAC1D,CAAC;IAED,MAAMkC,KAAK,GAAGP,UAAU,CAAC3D,OAAO,CAAC4D,aAAa,CAAC;IAE/C,MAAMO,QAAQ,GAAGpG,8BAA8B,CAACkD,MAAM,EAAEmD,SAAS,CAAC;IAElE,OAAO;MACL,GAAG9C,SAAS;MACZ+C,UAAU,EAAEpD,MAAM,EAAEmD,SAAS;MAC7BE,QAAQ,EAAErD,MAAM,EAAEqD,QAAQ;MAC1BV,aAAa;MAEb;MACAW,gBAAgB,EAAEZ,UAAU,CAACa,KAAK,CAAC,CAAC,EAAEN,KAAK,CAAC;MAC5CP,UAAU,EAAEA,UAAU,CAACa,KAAK,CAACN,KAAK,CAAC;MACnCC,QAAQ;MACRnC;IACF,CAAC;EACH;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,MAAcjB,aAAaA,CACzBP,OAAuB,EACvBC,KAA0B,EAC1B;IACAA,KAAK,GAAG,MAAM,IAAI,CAACgE,iBAAiB,CAACjE,OAAO,EAAEC,KAAK,CAAC;IAEpD,OAAOA,KAAK;EACd;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACE,MAAcgE,iBAAiBA,CAC7BjE,OAAuB,EACvBC,KAA0B,EAC1BiE,KAAK,GAAG,CAAC,EACqB;IAC9B,MAAMzD,MAAM,GAAG,IAAI,CAACC,kBAAkB,CAACT,KAAK,CAAC;IAC7C,MAAME,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACA,IAAI,CAACQ,MAAM,EAAEqD,QAAQ,EAAE;MACrB,OAAO,IAAI,CAACK,yBAAyB,CAACnE,OAAO,EAAEC,KAAK,CAAC;IACvD;IAEA,MAAM6D,QAAQ,GAAGrD,MAAM,CAACqD,QAAQ;IAEhC,IAAIM,cAAc;IAElB,IAAI;MACFA,cAAc,GAAG,MAAM5G,eAAe,CAACsG,QAAQ,CAAC;IAClD,CAAC,CAAC,OAAOO,GAAG,EAAE;MACZ;MACA;MACA;MACA;MACA,IACEvH,IAAI,CAACwH,MAAM,CAACD,GAAG,CAAC,IAChBA,GAAG,CAACE,MAAM,CAACC,UAAU,KAAKxH,WAAW,CAACyH,SAAS,CAACC,OAAO,CAAC,CAAC,EACzD;QACA,OAAO,IAAI,CAACP,yBAAyB,CAACnE,OAAO,EAAEC,KAAK,CAAC;MACvD;MACA,MAAMoE,GAAG;IACX;IAEA,IAAI,CAACD,cAAc,EAAE;MACnB,MAAMtH,IAAI,CAAC6H,UAAU,CACnB,sDAAsDb,QAAQ,EAChE,CAAC;IACH;;IAEA;IACA,IAAIM,cAAc,CAACQ,YAAY,KAAKjH,YAAY,CAACkH,SAAS,EAAE;MAC1D,OAAO5E,KAAK;IACd;IAEA,IAAImE,cAAc,CAACQ,YAAY,KAAKjH,YAAY,CAACS,OAAO,EAAE;MACxD;MACA;MACA;MACA,IAAI8F,KAAK,IAAI,CAAC,EAAE;QACd,MAAMG,GAAG,GAAG,IAAIS,KAAK,CACnB,uCAAuChB,QAAQ,YAAYI,KAAK,gCAClE,CAAC;QACDlE,OAAO,CAAC+E,MAAM,CAACnC,KAAK,CAClByB,GAAG,EACH,iEAAiEP,QAAQ,cAAcI,KAAK,6BAC9F,CAAC;QACD,MAAM,IAAI,CAACC,yBAAyB,CAACnE,OAAO,EAAEC,KAAK,CAAC;QACpD,MAAMnD,IAAI,CAACkI,cAAc,CACvB,yBAAyBlB,QAAQ,uCAAuC,CAAC,CAACjG,qBAAqB,GAAG,IAAI,IAAI,IAAI,EAAEoH,OAAO,CAAC,CAAC,CAAC,UAC5H,CAAC;MACH;MACA,MAAMC,KAAK,GAAG7H,0BAA0B,CAAC6G,KAAK,CAAC;MAC/ClE,OAAO,CAAC+E,MAAM,CAACI,IAAI,CACjB,yBAAyBD,KAAK,GAAG,IAAI,0BAA0BpB,QAAQ,8BAA8BI,KAAK,GAC5G,CAAC;MACD,MAAMnH,IAAI,CAACmI,KAAK,CAAC;MACjB,OAAO,IAAI,CAACjB,iBAAiB,CAACjE,OAAO,EAAEC,KAAK,EAAEiE,KAAK,GAAG,CAAC,CAAC;IAC1D;;IAEA;IACA;IACA;IACA,MAAMkB,gBAAgB,GAAGlI,cAAc,CAACmI,QAAQ,CAC9C;MAAEvB,QAAQ;MAAE/F,MAAM,EAAEqG;IAAe,CAAC,EACpC;MAAEkB,YAAY,EAAE;IAAK,CACvB,CAAC;IACD,MAAM1C,KAAK,GAAGwC,gBAAgB,CAACxC,KAAK;IAEpC,IAAIA,KAAK,EAAE;MACT,OAAO,IAAI,CAACuB,yBAAyB,CAACnE,OAAO,EAAEC,KAAK,CAAC;IACvD;;IAEA;IACA;IACA;IACA,MAAM,IAAI,CAACsF,oBAAoB,CAC7BvF,OAAO,EACPC,KAAK,EACLmF,gBAAgB,CAACpC,KAAK,EACtB7C,KAAK,EACLM,MACF,CAAC;IAED,OAAO,IAAI,CAAC0D,yBAAyB,CAACnE,OAAO,EAAEC,KAAK,CAAC;EACvD;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,MAAcsF,oBAAoBA,CAChCvF,OAAuB,EACvBC,KAA0B,EAC1BuF,aAAwB,EACxBrF,KAAkB,EAClBM,MAA0C,EAC1C;IACA,MAAM;MAAEqD;IAAS,CAAC,GAAG0B,aAAa;IAClC,MAAMC,eAAe,GAAGD,aAAa,CAACzH,MAAM;IAC5C,MAAM2H,OAAO,GAAGD,eAAe,CAACxH,IAAI,CAACD,IAErB;IAChB,MAAM2H,aAAa,GAAGC,KAAK,CAACC,OAAO,CAACH,OAAO,CAAC,GAAGA,OAAO,GAAG,CAACA,OAAO,CAAC;IAElE,MAAMI,SAAgC,GAAG,EAAE;IAE3C,KAAK,MAAM9H,IAAI,IAAI2H,aAAa,EAAE;MAChC,IAAI3H,IAAI,CAACG,UAAU,KAAKT,UAAU,CAACqI,QAAQ,EAAE;QAC3C,MAAMC,YAAuB,GAAG;UAC9BlC,QAAQ;UACR/F,MAAM,EAAE;YACN,GAAG0H,eAAe;YAClBxH,IAAI,EAAE;cAAED;YAAK;UACf;QACF,CAAC;QACDmC,KAAK,CAAC8F,OAAO,CAAC3H,gBAAgB,CAAC0H,YAAY,CAAC,CAAC;MAC/C,CAAC,MAAM;QACL;QACA,MAAM;UAAEvH;QAAW,CAAC,GAAG,IAAI;QAC3B,MAAMgB,IAAI,GAAGhB,UAAU,CAACgB,IAAI;QAC5B,MAAMsC,IAAI,GAAG/D,IAAI,CAACK,YAAY,IAAI,eAAe;QACjDyH,SAAS,CAAC/C,IAAI,CAAC;UAAExD,IAAI,EAAE,CAACE,IAAI,CAAC;UAAEwD,IAAI,EAAE,IAAIxD,IAAI,EAAE;UAAEA,IAAI;UAAEsC;QAAK,CAAC,CAAC;MAChE;IACF;IAEA,IAAI+D,SAAS,CAACzG,MAAM,EAAE;MACpB,MAAM6G,YAAY,GAAG/I,eAAe,CAAC6C,OAAO,CAACmG,MAAM,CAAC;MACpDD,YAAY,CAACE,QAAQ,CAACpG,OAAO,EAAE;QAAE0C,MAAM,EAAEoD;MAAU,CAAC,CAAC;IACvD;IAEA,IAAIH,aAAa,CAACU,IAAI,CAAEC,CAAC,IAAKA,CAAC,CAACnI,UAAU,KAAKT,UAAU,CAACqI,QAAQ,CAAC,EAAE;MACnE,MAAM,IAAI,CAACQ,UAAU,CAACvG,OAAO,EAAEC,KAAK,EAAE;QACpCQ,MAAM,EAAE;UAAE,CAAC,IAAI,CAAClB,IAAI,GAAG;YAAEY,KAAK;YAAEM;UAAO;QAAE;MAC3C,CAAC,CAAC;IACJ;EACF;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACE,MAAc4B,iBAAiBA,CAC7BrC,OAA2B,EAC3BC,KAA0B,EAC1B;IACA,MAAM;MAAEV;IAAK,CAAC,GAAG,IAAI;IACrB,MAAM;MAAEwB;IAAO,CAAC,GAAGf,OAAO;IAE1B,MAAMS,MAAM,GAAG,IAAI,CAACC,kBAAkB,CAACT,KAAK,CAAC;IAC7C,MAAME,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;IAE3C,MAAMuG,YAAY,GAAGrG,KAAK,CAAClB,MAAM,CAC/B,CAAC;MAAElB;IAAO,CAAC,KAAKA,MAAM,CAACE,IAAI,CAACD,IAAI,CAACkD,MAAM,KAAKH,MAAM,CAACI,MACrD,CAAC;IAED,IAAIqF,YAAY,CAACnH,MAAM,KAAKc,KAAK,CAACd,MAAM,EAAE;MACxC;IACF;IAEA,MAAM,IAAI,CAACkH,UAAU,CAACvG,OAAO,EAAEC,KAAK,EAAE;MACpCQ,MAAM,EAAE;QAAE,CAAClB,IAAI,GAAG;UAAEY,KAAK,EAAEqG,YAAY;UAAE/F;QAAO;MAAE;IACpD,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;AACA;EACE,MAAc0D,yBAAyBA,CACrCnE,OAAuB,EACvBC,KAA0B,EAC1B;IACA,MAAM;MAAExB,UAAU;MAAEwE,IAAI;MAAE1D;IAAK,CAAC,GAAG,IAAI;IACvC,MAAM;MAAEkH,OAAO;MAAEC;IAAO,CAAC,GAAGjI,UAAU;IACtC,MAAM;MAAEkI;IAAgB,CAAC,GAAG,IAAI,CAAC/H,KAAK,CAACgI,QAAQ,CAACC,YAAY;IAE5D,MAAM1G,KAAK,GAAG,IAAI,CAACC,iBAAiB,CAACH,KAAK,CAAC;;IAE3C;IACA,IAAIQ,MAA0C;;IAE9C;IACA,MAAMqG,GAAG,GAAGC,IAAI,CAACC,GAAG,CAACN,MAAM,CAACI,GAAG,IAAIlJ,WAAW,EAAEA,WAAW,CAAC;IAE5D,IAAIuC,KAAK,CAACd,MAAM,GAAGyH,GAAG,EAAE;MACtB,MAAMG,YAAY,GAAG,MAAMN,eAAe,CAAC3G,OAAO,CAACe,MAAM,CAACmG,IAAI,CAAC;MAC/D,MAAMC,iBAAiB,GACrBF,YAAY,CAACE,iBAAiB,IAAI,yBAAyB;MAE7D,MAAMC,SAAS,GAAG,MAAM3J,cAAc,CACpCwF,IAAI,EACJkE,iBAAiB,EACjBV,OAAO,CAACY,MACV,CAAC;MAED,IAAID,SAAS,KAAK/G,SAAS,EAAE;QAC3B,MAAMvD,IAAI,CAAC6H,UAAU,CAAC,+CAA+C,CAAC;MACxE;MAEAlE,MAAM,GAAG2G,SAAS;IACpB;IAEA,OAAO,IAAI,CAACb,UAAU,CAACvG,OAAO,EAAEC,KAAK,EAAE;MACrCQ,MAAM,EAAE;QAAE,CAAClB,IAAI,GAAG;UAAEY,KAAK;UAAEM;QAAO;MAAE;IACtC,CAAC,CAAC;EACJ;AACF","ignoreList":[]}
1
+ {"version":3,"file":"FileUploadPageController.js","names":["ComponentType","Boom","wait","StatusCodes","FileUploadField","tempItemSchema","getCacheService","getError","getExponentialBackoffDelay","QuestionPageController","getProxyUrlForLocalDevelopment","getUploadStatus","initiateUpload","FileStatus","UploadStatus","joinWithOr","MAX_UPLOADS","CDP_UPLOAD_TIMEOUT_MS","prepareStatus","status","file","form","isPending","fileStatus","pending","errorMessage","prepareFileState","fileState","FileUploadPageController","fileUpload","fileDeleteViewName","constructor","model","pageDef","collection","fileUploads","fields","filter","field","type","at","length","badImplementation","path","indexOf","name","viewName","getStateKeys","component","pagePath","page","getFormDataFromState","request","state","payload","files","getFilesFromState","undefined","getState","refreshUpload","uploadState","upload","getUploadFromState","makeGetItemDeleteRouteHandler","context","h","viewModel","params","fileToRemove","find","fileId","itemId","notFound","filename","translator","getTranslator","t","view","backLink","getBackLink","pageTitle","itemTitle","confirmation","text","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","getFormParams","checkRemovedFiles","proceed","getErrors","details","language","errors","forEach","error","isUploadError","isUploadRootError","push","value","href","getViewModel","components","formComponent","id","count","summaryList","rows","uploadingLabel","javascript","chooseFilesButtonText","dropInstructionText","noFileChosenText","index","proxyUrl","uploadUrl","formAction","uploadId","componentsBefore","slice","checkUploadStatus","depth","initiateAndStoreNewUpload","statusResponse","err","isBoom","output","statusCode","NOT_FOUND","valueOf","badRequest","uploadStatus","initiated","Error","logger","gatewayTimeout","toFixed","delay","info","validationResult","validate","stripUnknown","processUploadedFiles","validatedItem","validatedStatus","rawFile","uploadedFiles","Array","isArray","allErrors","complete","perFileState","unshift","errorParams","expandErrorParams","errorCode","cacheService","server","setFlash","some","f","mergeState","finalSeparator","expanded","key","Object","entries","filesUpdated","options","schema","getFormMetadata","services","formsService","max","Math","min","formMetadata","slug","notificationEmail","newUpload","accept"],"sources":["../../../../../src/server/plugins/engine/pageControllers/FileUploadPageController.ts"],"sourcesContent":["import { ComponentType, type PageFileUpload } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { wait } from '@hapi/hoek'\nimport { StatusCodes } from 'http-status-codes'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n FileUploadField,\n tempItemSchema\n} from '~/src/server/plugins/engine/components/FileUploadField.js'\nimport { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n getCacheService,\n getError,\n getExponentialBackoffDelay\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { getProxyUrlForLocalDevelopment } from '~/src/server/plugins/engine/pageControllers/helpers/index.js'\nimport {\n getUploadStatus,\n initiateUpload\n} from '~/src/server/plugins/engine/services/uploadService.js'\nimport {\n FileStatus,\n UploadStatus,\n type AnyFormRequest,\n type FeaturedFormPageViewModel,\n type FileState,\n type FileUpload,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type UploadInitiateResponse,\n type UploadStatusFileResponse\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport { joinWithOr } from '~/src/server/utils/utils.js'\n\nconst MAX_UPLOADS = 25\nconst CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute\n\nexport function prepareStatus(status: UploadStatusFileResponse) {\n const file = status.form.file\n const isPending = file.fileStatus === FileStatus.pending\n\n if (!file.errorMessage && isPending) {\n file.errorMessage = 'The selected file has not fully uploaded'\n }\n\n return status\n}\n\nfunction prepareFileState(fileState: FileState) {\n prepareStatus(fileState.status)\n\n return fileState\n}\n\nexport class FileUploadPageController extends QuestionPageController {\n declare pageDef: PageFileUpload\n\n fileUpload: FileUploadField\n fileDeleteViewName = 'item-delete'\n\n constructor(model: FormModel, pageDef: PageFileUpload) {\n super(model, pageDef)\n\n const { collection } = this\n\n // Get the file upload fields from the collection\n const fileUploads = collection.fields.filter(\n (field): field is FileUploadField =>\n field.type === ComponentType.FileUploadField\n )\n\n const fileUpload = fileUploads.at(0)\n\n // Assert we have exactly 1 file upload component\n if (!fileUpload || fileUploads.length > 1) {\n throw Boom.badImplementation(\n `Expected 1 FileUploadFieldComponent in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assert the file upload component is the first form component\n if (collection.fields.indexOf(fileUpload) !== 0) {\n throw Boom.badImplementation(\n `Expected '${fileUpload.name}' to be the first form component in FileUploadPageController '${pageDef.path}'`\n )\n }\n\n // Assign the file upload component to the controller\n this.fileUpload = fileUpload\n this.viewName = 'file-upload'\n }\n\n /**\n * Get supplementary state keys for clearing file upload state.\n * Returns the nested upload path for FileUploadField components only.\n * @param component - The component to get supplementary state keys for\n * @returns Array containing the nested upload path, e.g., [\"upload['/page-path']\"]\n * or ['upload'] if no page path is available. Returns empty array for non-FileUploadField components.\n */\n getStateKeys(component: FormComponent): string[] {\n // Only return upload keys for FileUploadField components\n if (!(component instanceof FileUploadField)) {\n return []\n }\n\n const pagePath = component.page?.path\n return pagePath ? [`upload['${pagePath}']`] : ['upload']\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { fileUpload } = this\n\n const payload = super.getFormDataFromState(request, state)\n const files = this.getFilesFromState(state)\n\n // Append the files to the payload\n payload[fileUpload.name] = files.length ? files : undefined\n\n return payload\n }\n\n async getState(request: AnyFormRequest) {\n const { fileUpload } = this\n\n // Get the actual state\n const state = await super.getState(request)\n const files = this.getFilesFromState(state)\n\n // Overwrite the files with those in the upload state\n state[fileUpload.name] = files\n\n return this.refreshUpload(request, state)\n }\n\n /**\n * Get the uploaded files from state.\n */\n getFilesFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.files ?? []\n }\n\n /**\n * Get the initiated upload from state.\n */\n getUploadFromState(state: FormSubmissionState) {\n const { path } = this\n\n const uploadState = state.upload?.[path]\n return uploadState?.upload\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { params } = request\n const { state } = context\n\n const files = this.getFilesFromState(state)\n\n const fileToRemove = files.find(\n ({ status }) => status.form.file.fileId === params.itemId\n )\n\n if (!fileToRemove) {\n throw Boom.notFound('File to delete not found')\n }\n\n const { filename } = fileToRemove.status.form.file\n\n const translator = this.getTranslator(request)\n const { t } = translator\n\n return h.view(this.fileDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context, t),\n pageTitle: t('pages.fileUpload.removeFileTitle'),\n itemTitle: filename,\n confirmation: { text: t('pages.fileUpload.removeFileBody') },\n buttonConfirm: { text: t('pages.fileUpload.removeFileButton') },\n buttonCancel: { text: t('pages.fileUpload.cancel') },\n t\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n // Check for any removed files in the POST payload\n if (confirm) {\n await this.checkRemovedFiles(request, state)\n return this.proceed(request, h, path)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getErrors(details?: ValidationErrorItem[], language?: string) {\n const { fileUpload } = this\n\n if (details) {\n const errors: FormSubmissionError[] = []\n\n details.forEach((error) => {\n const isUploadError = error.path[0] === fileUpload.name\n const isUploadRootError = isUploadError && error.path.length === 1\n\n if (!isUploadError || isUploadRootError) {\n // The error is for the root of the upload or another\n // field on the page so defer to the getError helper\n errors.push(getError(language, error))\n } else {\n const { context, path, type } = error\n\n if (type === 'object.unknown' && path.at(-1) === 'errorMessage') {\n const value = context?.value as string | undefined\n\n if (value) {\n const name = fileUpload.name\n const text = typeof value === 'string' ? value : 'Unknown error'\n const href = `#${name}`\n\n errors.push({ path, href, name, text })\n }\n }\n }\n })\n\n return errors\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext,\n translator: Translator\n ): FeaturedFormPageViewModel {\n const { fileUpload } = this\n const { state } = context\n const { t } = translator\n\n const upload = this.getUploadFromState(state)\n\n const viewModel = super.getViewModel(request, context, translator)\n const { components } = viewModel\n\n // Featured form component\n const [formComponent] = components.filter(\n ({ model }) => model.id === fileUpload.name\n )\n\n formComponent.model.upload = {\n count: formComponent.model.upload?.count ?? 0,\n summaryList: formComponent.model.upload?.summaryList ?? { rows: [] },\n uploadingLabel: t('components.fileUploadField.uploading')\n }\n formComponent.model = {\n ...formComponent.model,\n javascript: true,\n chooseFilesButtonText: t('components.fileUploadField.chooseFile'),\n dropInstructionText: t('components.fileUploadField.dropFile'),\n noFileChosenText: t('components.fileUploadField.noFileChosen')\n }\n\n const index = components.indexOf(formComponent)\n\n const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)\n\n return {\n ...viewModel,\n formAction: upload?.uploadUrl,\n uploadId: upload?.uploadId,\n formComponent,\n\n // Split out components before/after\n componentsBefore: components.slice(0, index),\n components: components.slice(index),\n proxyUrl,\n t\n }\n }\n\n /**\n * Refreshes the CDP upload and files in the\n * state and checks for any removed files.\n *\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise we initiate a new one.\n * @param request - the hapi request\n * @param state - the form state\n */\n private async refreshUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n state = await this.checkUploadStatus(request, state)\n\n return state\n }\n\n /**\n * If an upload exists and hasn't been consumed\n * it gets re-used, otherwise a new one is initiated.\n * @param request - the hapi request\n * @param state - the form state\n * @param depth - the number of retries so far\n */\n private async checkUploadStatus(\n request: AnyFormRequest,\n state: FormSubmissionState,\n depth = 1\n ): Promise<FormSubmissionState> {\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n // If no upload exists, initiate a new one.\n if (!upload?.uploadId) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n const uploadId = upload.uploadId\n\n let statusResponse\n\n try {\n statusResponse = await getUploadStatus(uploadId)\n } catch (err) {\n // if the user loads a file upload page and queries the cached upload, after the upload has\n // expired in CDP, we will get a 404 from the getUploadStatus endpoint.\n // In this case we want to initiate a new upload and return that state, so the form\n // doesn't blow up for the end user.\n if (\n Boom.isBoom(err) &&\n err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()\n ) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n throw err\n }\n\n if (!statusResponse) {\n throw Boom.badRequest(\n `Unexpected empty response from getUploadStatus for ${uploadId}`\n )\n }\n\n // Re-use the upload if it is still in the \"initiated\" state.\n if (statusResponse.uploadStatus === UploadStatus.initiated) {\n return state\n }\n\n if (statusResponse.uploadStatus === UploadStatus.pending) {\n // Using exponential backoff delays:\n // Depth 1: 2000ms, Depth 2: 4000ms, Depth 3: 8000ms, Depth 4: 16000ms, Depth 5+: 30000ms (capped)\n // A depth of 5 (or more) implies cumulative delays roughly reaching 55 seconds.\n if (depth >= 5) {\n const err = new Error(\n `Exceeded cumulative retry delay for ${uploadId} (depth: ${depth}). Re-initiating a new upload.`\n )\n request.logger.error(\n err,\n `[uploadTimeout] Exceeded cumulative retry delay for uploadId: ${uploadId} at depth: ${depth} - re-initiating new upload`\n )\n await this.initiateAndStoreNewUpload(request, state)\n throw Boom.gatewayTimeout(\n `Timed out waiting for ${uploadId} after cumulative retries exceeding ${((CDP_UPLOAD_TIMEOUT_MS - 5000) / 1000).toFixed(0)} seconds`\n )\n }\n const delay = getExponentialBackoffDelay(depth)\n request.logger.info(\n `[uploadRetry] Waiting ${delay / 1000} seconds for uploadId: ${uploadId} to complete (retry depth: ${depth})`\n )\n await wait(delay)\n return this.checkUploadStatus(request, state, depth + 1)\n }\n\n // Only add to files state if the file validates.\n // This secures against html tampering of the file input\n // (e.g. changing it to a simple text field or similar).\n const validationResult = tempItemSchema.validate(\n { uploadId, status: statusResponse },\n { stripUnknown: true }\n )\n const error = validationResult.error\n\n if (error) {\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n // CDP returns form.file as a single object for one file,\n // or an array for multiple files. The Joi schema normalises\n // both to an array via .single().\n await this.processUploadedFiles(\n request,\n state,\n validationResult.value,\n files,\n upload\n )\n\n return this.initiateAndStoreNewUpload(request, state)\n }\n\n /**\n * Processes the uploaded files from a CDP status response.\n * Complete files are added to state, rejected/pending files\n * have their error messages flashed.\n * @param request - the hapi request\n * @param state - the form state\n * @param validatedItem - the Joi-validated upload item\n * @param files - the current files array from state\n * @param upload - the current upload initiation response\n */\n private async processUploadedFiles(\n request: AnyFormRequest,\n state: FormSubmissionState,\n validatedItem: FileState,\n files: FileState[],\n upload: UploadInitiateResponse | undefined\n ) {\n const { uploadId } = validatedItem\n const validatedStatus = validatedItem.status\n const rawFile = validatedStatus.form.file as unknown as\n | FileUpload\n | FileUpload[]\n const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile]\n\n const allErrors: FormSubmissionError[] = []\n\n const translator = this.getTranslator(request)\n const { t } = translator\n\n for (const file of uploadedFiles) {\n if (file.fileStatus === FileStatus.complete) {\n const perFileState: FileState = {\n uploadId,\n status: {\n ...validatedStatus,\n form: { file }\n } as FileState['status']\n }\n files.unshift(prepareFileState(perFileState))\n } else {\n // Collect the error for rejected/pending files.\n const { fileUpload } = this\n const name = fileUpload.name\n const errorParams = this.expandErrorParams(\n file.errorParams,\n t('common.or')\n )\n const text = t(\n `pages.fileUpload.${file.errorCode ?? 'FILE_ERROR_UNKNOWN'}`,\n errorParams\n )\n allErrors.push({ path: [name], href: `#${name}`, name, text })\n }\n }\n\n if (allErrors.length) {\n const cacheService = getCacheService(request.server)\n cacheService.setFlash(request, { errors: allErrors })\n }\n\n if (uploadedFiles.some((f) => f.fileStatus === FileStatus.complete)) {\n await this.mergeState(request, state, {\n upload: { [this.path]: { files, upload } }\n })\n }\n }\n\n /**\n * Process the error parameters so any arrays get expanded (joined), ready to be passed into the error message\n * @param errorParams - received from the file upload (may be a number, a string, or a string array)\n * @param finalSeparator - 'or' in English, and 'neu' in Welsh\n */\n private expandErrorParams(\n errorParams: Record<string, number | string | string[]> | undefined,\n finalSeparator: string\n ) {\n if (!errorParams) {\n return {}\n }\n const expanded = {} as Record<string, number | string>\n for (const [key, value] of Object.entries(errorParams)) {\n if (Array.isArray(value)) {\n expanded[key] = joinWithOr(value, finalSeparator)\n } else {\n expanded[key] = value\n }\n }\n return expanded\n }\n\n /**\n * Checks the payload for a file getting removed\n * and removes it from the upload files if found\n * @param request - the hapi request\n * @param state - the form state\n * @returns updated state if any files have been removed\n */\n private async checkRemovedFiles(\n request: FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { path } = this\n const { params } = request\n\n const upload = this.getUploadFromState(state)\n const files = this.getFilesFromState(state)\n\n const filesUpdated = files.filter(\n ({ status }) => status.form.file.fileId !== params.itemId\n )\n\n if (filesUpdated.length === files.length) {\n return\n }\n\n await this.mergeState(request, state, {\n upload: { [path]: { files: filesUpdated, upload } }\n })\n }\n\n /**\n * Initiates a CDP file upload and stores in the upload state\n * @param request - the hapi request\n * @param state - the form state\n */\n private async initiateAndStoreNewUpload(\n request: AnyFormRequest,\n state: FormSubmissionState\n ) {\n const { fileUpload, href, path } = this\n const { options, schema } = fileUpload\n const { getFormMetadata } = this.model.services.formsService\n\n const files = this.getFilesFromState(state)\n\n // Reset the upload in state\n let upload: UploadInitiateResponse | undefined\n\n // Don't initiate anymore after minimum of `schema.max` or MAX_UPLOADS\n const max = Math.min(schema.max ?? MAX_UPLOADS, MAX_UPLOADS)\n\n if (files.length < max) {\n const formMetadata = await getFormMetadata(request.params.slug)\n const notificationEmail =\n formMetadata.notificationEmail ?? 'defraforms@defra.gov.uk'\n\n const newUpload = await initiateUpload(\n href,\n notificationEmail,\n options.accept\n )\n\n if (newUpload === undefined) {\n throw Boom.badRequest('Unexpected empty response from initiateUpload')\n }\n\n upload = newUpload\n }\n\n return this.mergeState(request, state, {\n upload: { [path]: { files, upload } }\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -167,10 +167,14 @@ export type FileUpload = {
167
167
  contentLength: number;
168
168
  } & ({
169
169
  fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending;
170
+ errorCode?: string;
170
171
  errorMessage?: string;
172
+ errorParams?: Record<string, number | string | string[]>;
171
173
  } | {
172
174
  fileStatus: FileStatus.complete;
175
+ errorCode?: string;
173
176
  errorMessage?: undefined;
177
+ errorParams?: Record<string, number | string | string[]>;
174
178
  });
175
179
  export interface FileUploadMetadata {
176
180
  retrievalKey: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page,\n type PaymentFieldComponent,\n type UkAddressFieldComponent\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type RequestQuery,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type Yar } from '@hapi/yar'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type FileUploadField,\n type PaymentField\n} from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type EastingNorthingState,\n type LatLongState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\nexport interface CacheRequest {\n yar: { id: string | undefined }\n params: { slug?: string; state?: string }\n}\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError extends Pick<\n ValidationErrorItem,\n 'context' | 'path'\n> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormConfirmationState {\n confirmed?: true\n formId?: string\n referenceNumber?: string\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | GeospatialState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n languages: { name: string; code: string }[]\n translator: Translator | undefined\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\n/**\n * A longitude/latitude coordinate pair in WGS84 format\n * Format: [longitude, latitude]\n */\nexport type Coordinates = [longitude: number, latitude: number]\n\n/**\n * GeoJSON Point geometry\n */\nexport interface PointGeometry {\n type: 'Point'\n coordinates: Coordinates\n}\n\n/**\n * GeoJSON LineString geometry\n */\nexport interface LineStringGeometry {\n type: 'LineString'\n coordinates: Coordinates[]\n}\n\n/**\n * GeoJSON Polygon geometry\n */\nexport interface PolygonGeometry {\n type: 'Polygon'\n coordinates: Coordinates[][]\n}\n\n/**\n * Supported geometry types\n */\nexport type Geometry = PointGeometry | LineStringGeometry | PolygonGeometry\n\n/**\n * Feature metadata\n */\nexport interface FeatureProperties {\n /**\n * Human-readable description of the feature\n */\n description: string\n /**\n * The OS grid reference of the first coordinate of the feature\n */\n coordinateGridReference?: string\n /**\n * The OS grid reference of the centroid of the feature\n */\n centroidGridReference?: string\n}\n\n/**\n * A single GeoJSON Feature\n */\nexport interface Feature {\n id: string\n type: 'Feature'\n properties: FeatureProperties\n geometry: Geometry\n}\n\n/**\n * A GeoJSON FeatureCollection\n */\nexport type FeatureCollection = Feature[]\n\nexport type GeospatialState = FeatureCollection\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n t?: (key: string, opts?: Record<string, unknown>) => string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n showSubmitButton?: boolean\n showPaymentExpiredNotification?: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n h: FormResponseToolkit,\n context: FormContext\n) =>\n | ResponseObject\n | FormResponseToolkit['continue']\n | Promise<ResponseObject | FormResponseToolkit['continue']>\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport type GetLanguageHandler = (query?: RequestQuery, yar?: Yar) => string\n\nexport interface ExternalArgs {\n component: ComponentDef\n controller: QuestionPageController\n sourceUrl: string\n actionArgs?: Record<string, string>\n isLive: boolean\n isPreview: boolean\n}\n\nexport interface PostcodeLookupExternalArgs extends ExternalArgs {\n component: UkAddressFieldComponent\n actionArgs: { step: string }\n}\n\nexport interface PaymentExternalArgs extends ExternalArgs {\n component: PaymentFieldComponent\n}\n\nexport interface ExternalStateAppendage {\n component: string\n data: FormStateValue | FormState\n}\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n getLanguage?: GetLanguageHandler\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n ordnanceSurveyApiKey?: string\n ordnanceSurveyApiSecret?: string\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n language?: string\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterPayment {\n paymentId: string\n reference: string\n amount: number\n description: string\n descriptionInEng?: string\n createdAt: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\n\n/**\n * A detail item specifically for payments\n */\nexport type PaymentFieldDetailItem = Omit<DetailItemField, 'field'> & {\n field: PaymentField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n | EastingNorthingState\n | LatLongState\n | GeospatialState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n payment?: FormAdapterPayment\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAkEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AA2BA;AACA;AACA;AACA;;AAwGA,SACEA,UAAU,EACVC,YAAY;;AAgEd;AACA;AACA;AACA;;AAGA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAGA;AACA;AACA;;AAgBA;AACA;AACA;;AAQA;AACA;AACA;;AA+MA;AACA;AACA;;AAKA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page,\n type PaymentFieldComponent,\n type UkAddressFieldComponent\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type RequestQuery,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type Yar } from '@hapi/yar'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type FileUploadField,\n type PaymentField\n} from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type EastingNorthingState,\n type LatLongState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\nexport interface CacheRequest {\n yar: { id: string | undefined }\n params: { slug?: string; state?: string }\n}\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError extends Pick<\n ValidationErrorItem,\n 'context' | 'path'\n> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormConfirmationState {\n confirmed?: true\n formId?: string\n referenceNumber?: string\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | GeospatialState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n languages: { name: string; code: string }[]\n translator: Translator | undefined\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorCode?: string\n errorMessage?: string\n errorParams?: Record<string, number | string | string[]>\n }\n | {\n fileStatus: FileStatus.complete\n errorCode?: string\n errorMessage?: undefined\n errorParams?: Record<string, number | string | string[]>\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\n/**\n * A longitude/latitude coordinate pair in WGS84 format\n * Format: [longitude, latitude]\n */\nexport type Coordinates = [longitude: number, latitude: number]\n\n/**\n * GeoJSON Point geometry\n */\nexport interface PointGeometry {\n type: 'Point'\n coordinates: Coordinates\n}\n\n/**\n * GeoJSON LineString geometry\n */\nexport interface LineStringGeometry {\n type: 'LineString'\n coordinates: Coordinates[]\n}\n\n/**\n * GeoJSON Polygon geometry\n */\nexport interface PolygonGeometry {\n type: 'Polygon'\n coordinates: Coordinates[][]\n}\n\n/**\n * Supported geometry types\n */\nexport type Geometry = PointGeometry | LineStringGeometry | PolygonGeometry\n\n/**\n * Feature metadata\n */\nexport interface FeatureProperties {\n /**\n * Human-readable description of the feature\n */\n description: string\n /**\n * The OS grid reference of the first coordinate of the feature\n */\n coordinateGridReference?: string\n /**\n * The OS grid reference of the centroid of the feature\n */\n centroidGridReference?: string\n}\n\n/**\n * A single GeoJSON Feature\n */\nexport interface Feature {\n id: string\n type: 'Feature'\n properties: FeatureProperties\n geometry: Geometry\n}\n\n/**\n * A GeoJSON FeatureCollection\n */\nexport type FeatureCollection = Feature[]\n\nexport type GeospatialState = FeatureCollection\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n t?: (key: string, opts?: Record<string, unknown>) => string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n showSubmitButton?: boolean\n showPaymentExpiredNotification?: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n h: FormResponseToolkit,\n context: FormContext\n) =>\n | ResponseObject\n | FormResponseToolkit['continue']\n | Promise<ResponseObject | FormResponseToolkit['continue']>\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport type GetLanguageHandler = (query?: RequestQuery, yar?: Yar) => string\n\nexport interface ExternalArgs {\n component: ComponentDef\n controller: QuestionPageController\n sourceUrl: string\n actionArgs?: Record<string, string>\n isLive: boolean\n isPreview: boolean\n}\n\nexport interface PostcodeLookupExternalArgs extends ExternalArgs {\n component: UkAddressFieldComponent\n actionArgs: { step: string }\n}\n\nexport interface PaymentExternalArgs extends ExternalArgs {\n component: PaymentFieldComponent\n}\n\nexport interface ExternalStateAppendage {\n component: string\n data: FormStateValue | FormState\n}\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n getLanguage?: GetLanguageHandler\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n ordnanceSurveyApiKey?: string\n ordnanceSurveyApiSecret?: string\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n language?: string\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterPayment {\n paymentId: string\n reference: string\n amount: number\n description: string\n descriptionInEng?: string\n createdAt: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\n\n/**\n * A detail item specifically for payments\n */\nexport type PaymentFieldDetailItem = Omit<DetailItemField, 'field'> & {\n field: PaymentField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n | EastingNorthingState\n | LatLongState\n | GeospatialState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n payment?: FormAdapterPayment\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAkEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AA2BA;AACA;AACA;AACA;;AAwGA,SACEA,UAAU,EACVC,YAAY;;AAoEd;AACA;AACA;AACA;;AAGA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAGA;AACA;AACA;;AAgBA;AACA;AACA;;AAQA;AACA;AACA;;AA+MA;AACA;AACA;;AAKA;AACA;AACA","ignoreList":[]}
@@ -70,8 +70,9 @@
70
70
 
71
71
  {% set hasErrors = showFieldsetError and component.model.errorMessage %}
72
72
 
73
- <div class="govuk-form-group app-location-field {{ "govuk-form-group--error" if hasErrors }}"
73
+ <div id="{{component.model.name}}" class="govuk-form-group app-location-field {{ "govuk-form-group--error" if hasErrors }}"
74
74
  data-locationtype="{{component.type | lower}}"
75
+ data-country="{{component.model.country}}"
75
76
  data-maplayers="{{component.model.mapLayers}}">
76
77
  {{ govukFieldset({
77
78
  legend: {
@@ -15,7 +15,7 @@
15
15
  {% if formAction %}
16
16
  {{ govukButton({
17
17
  text: t('pages.fileUpload.upload'),
18
- classes: "govuk-button--secondary upload-file-button",
18
+ classes: "govuk-button--secondary upload-file-button js-hidden",
19
19
  preventDoubleClick: true,
20
20
  disabled: context.isForceAccess
21
21
  }) }}
@@ -15,4 +15,10 @@ export function isValidUUID(str: string): boolean;
15
15
  * @param {AnyFormRequest} request
16
16
  */
17
17
  export function resolveLanguage(request: AnyFormRequest): string;
18
+ /**
19
+ * Create a comma-separated list with the last seperator being 'or'
20
+ * @param { string[] | undefined } items
21
+ * @param {string} [finalSeparator]
22
+ */
23
+ export function joinWithOr(items: string[] | undefined, finalSeparator?: string): string;
18
24
  import type { AnyFormRequest } from '../../server/plugins/engine/types.js';
@@ -43,7 +43,24 @@ export function resolveLanguage(request) {
43
43
  }
44
44
 
45
45
  /**
46
- * @import { FormMetadata } from '@defra/forms-model'
46
+ * Create a comma-separated list with the last seperator being 'or'
47
+ * @param { string[] | undefined } items
48
+ * @param {string} [finalSeparator]
49
+ */
50
+ export function joinWithOr(items, finalSeparator = 'or') {
51
+ if (!items || items.length === 0) {
52
+ return '';
53
+ }
54
+ if (items.length === 1) {
55
+ return items[0];
56
+ }
57
+ if (items.length === 2) {
58
+ return `${items[0]} ${finalSeparator} ${items[1]}`;
59
+ }
60
+ return `${items.slice(0, -1).join(', ')} ${finalSeparator} ${items.at(-1)}`;
61
+ }
62
+
63
+ /**
47
64
  * @import { AnyFormRequest } from '../../server/plugins/engine/types.js'
48
65
  */
49
66
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":["getTraceId","Joi","config","getPluginOptions","applyTraceHeaders","existingHeaders","header","get","traceId","headers","undefined","Object","assign","isValidUUID","str","error","string","uuid","validate","resolveLanguage","request","getLanguage","server","query","yar"],"sources":["../../../src/server/utils/utils.js"],"sourcesContent":["import { getTraceId } from '@defra/hapi-tracing'\nimport Joi from 'joi'\n\nimport { config } from '~/src/config/index.js'\nimport { getPluginOptions } from '~/src/server/plugins/engine/helpers.js'\n\n/**\n * Returns a set of headers to use in an HTTP request, merging them with any existing headers in options.\n * @param {Record<string, string> | undefined} [existingHeaders] - Optional existing headers to merge with the tracing headers.\n * @param {string} [header] - The tracing header name to use.\n * @returns {Record<string, string> | undefined} The merged headers, or undefined if no tracing header is available.\n */\nexport function applyTraceHeaders(\n existingHeaders,\n header = config.get('tracing').header\n) {\n if (!header) {\n return existingHeaders\n }\n\n const traceId = getTraceId()\n\n const headers = traceId ? { [header]: traceId } : undefined\n\n return existingHeaders ? Object.assign(existingHeaders, headers) : headers\n}\n\n/**\n * Validates if a string conforms to the uuid structure\n * @param {string} str\n * @returns\n */\nexport function isValidUUID(str) {\n const { error } = Joi.string().uuid().validate(str)\n return error === undefined\n}\n\n/**\n * @param {AnyFormRequest} request\n */\nexport function resolveLanguage(request) {\n const { getLanguage } = getPluginOptions(request.server)\n return getLanguage?.(request.query, request.yar) ?? 'en-GB'\n}\n\n/**\n * @import { FormMetadata } from '@defra/forms-model'\n * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,qBAAqB;AAChD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,MAAM;AACf,SAASC,gBAAgB;;AAEzB;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,iBAAiBA,CAC/BC,eAAe,EACfC,MAAM,GAAGJ,MAAM,CAACK,GAAG,CAAC,SAAS,CAAC,CAACD,MAAM,EACrC;EACA,IAAI,CAACA,MAAM,EAAE;IACX,OAAOD,eAAe;EACxB;EAEA,MAAMG,OAAO,GAAGR,UAAU,CAAC,CAAC;EAE5B,MAAMS,OAAO,GAAGD,OAAO,GAAG;IAAE,CAACF,MAAM,GAAGE;EAAQ,CAAC,GAAGE,SAAS;EAE3D,OAAOL,eAAe,GAAGM,MAAM,CAACC,MAAM,CAACP,eAAe,EAAEI,OAAO,CAAC,GAAGA,OAAO;AAC5E;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,WAAWA,CAACC,GAAG,EAAE;EAC/B,MAAM;IAAEC;EAAM,CAAC,GAAGd,GAAG,CAACe,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC,CAACC,QAAQ,CAACJ,GAAG,CAAC;EACnD,OAAOC,KAAK,KAAKL,SAAS;AAC5B;;AAEA;AACA;AACA;AACA,OAAO,SAASS,eAAeA,CAACC,OAAO,EAAE;EACvC,MAAM;IAAEC;EAAY,CAAC,GAAGlB,gBAAgB,CAACiB,OAAO,CAACE,MAAM,CAAC;EACxD,OAAOD,WAAW,GAAGD,OAAO,CAACG,KAAK,EAAEH,OAAO,CAACI,GAAG,CAAC,IAAI,OAAO;AAC7D;;AAEA;AACA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"utils.js","names":["getTraceId","Joi","config","getPluginOptions","applyTraceHeaders","existingHeaders","header","get","traceId","headers","undefined","Object","assign","isValidUUID","str","error","string","uuid","validate","resolveLanguage","request","getLanguage","server","query","yar","joinWithOr","items","finalSeparator","length","slice","join","at"],"sources":["../../../src/server/utils/utils.js"],"sourcesContent":["import { getTraceId } from '@defra/hapi-tracing'\nimport Joi from 'joi'\n\nimport { config } from '~/src/config/index.js'\nimport { getPluginOptions } from '~/src/server/plugins/engine/helpers.js'\n\n/**\n * Returns a set of headers to use in an HTTP request, merging them with any existing headers in options.\n * @param {Record<string, string> | undefined} [existingHeaders] - Optional existing headers to merge with the tracing headers.\n * @param {string} [header] - The tracing header name to use.\n * @returns {Record<string, string> | undefined} The merged headers, or undefined if no tracing header is available.\n */\nexport function applyTraceHeaders(\n existingHeaders,\n header = config.get('tracing').header\n) {\n if (!header) {\n return existingHeaders\n }\n\n const traceId = getTraceId()\n\n const headers = traceId ? { [header]: traceId } : undefined\n\n return existingHeaders ? Object.assign(existingHeaders, headers) : headers\n}\n\n/**\n * Validates if a string conforms to the uuid structure\n * @param {string} str\n * @returns\n */\nexport function isValidUUID(str) {\n const { error } = Joi.string().uuid().validate(str)\n return error === undefined\n}\n\n/**\n * @param {AnyFormRequest} request\n */\nexport function resolveLanguage(request) {\n const { getLanguage } = getPluginOptions(request.server)\n return getLanguage?.(request.query, request.yar) ?? 'en-GB'\n}\n\n/**\n * Create a comma-separated list with the last seperator being 'or'\n * @param { string[] | undefined } items\n * @param {string} [finalSeparator]\n */\nexport function joinWithOr(items, finalSeparator = 'or') {\n if (!items || items.length === 0) {\n return ''\n }\n if (items.length === 1) {\n return items[0]\n }\n if (items.length === 2) {\n return `${items[0]} ${finalSeparator} ${items[1]}`\n }\n\n return `${items.slice(0, -1).join(', ')} ${finalSeparator} ${items.at(-1)}`\n}\n\n/**\n * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,qBAAqB;AAChD,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,MAAM;AACf,SAASC,gBAAgB;;AAEzB;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,iBAAiBA,CAC/BC,eAAe,EACfC,MAAM,GAAGJ,MAAM,CAACK,GAAG,CAAC,SAAS,CAAC,CAACD,MAAM,EACrC;EACA,IAAI,CAACA,MAAM,EAAE;IACX,OAAOD,eAAe;EACxB;EAEA,MAAMG,OAAO,GAAGR,UAAU,CAAC,CAAC;EAE5B,MAAMS,OAAO,GAAGD,OAAO,GAAG;IAAE,CAACF,MAAM,GAAGE;EAAQ,CAAC,GAAGE,SAAS;EAE3D,OAAOL,eAAe,GAAGM,MAAM,CAACC,MAAM,CAACP,eAAe,EAAEI,OAAO,CAAC,GAAGA,OAAO;AAC5E;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,WAAWA,CAACC,GAAG,EAAE;EAC/B,MAAM;IAAEC;EAAM,CAAC,GAAGd,GAAG,CAACe,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC,CAAC,CAACC,QAAQ,CAACJ,GAAG,CAAC;EACnD,OAAOC,KAAK,KAAKL,SAAS;AAC5B;;AAEA;AACA;AACA;AACA,OAAO,SAASS,eAAeA,CAACC,OAAO,EAAE;EACvC,MAAM;IAAEC;EAAY,CAAC,GAAGlB,gBAAgB,CAACiB,OAAO,CAACE,MAAM,CAAC;EACxD,OAAOD,WAAW,GAAGD,OAAO,CAACG,KAAK,EAAEH,OAAO,CAACI,GAAG,CAAC,IAAI,OAAO;AAC7D;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACC,KAAK,EAAEC,cAAc,GAAG,IAAI,EAAE;EACvD,IAAI,CAACD,KAAK,IAAIA,KAAK,CAACE,MAAM,KAAK,CAAC,EAAE;IAChC,OAAO,EAAE;EACX;EACA,IAAIF,KAAK,CAACE,MAAM,KAAK,CAAC,EAAE;IACtB,OAAOF,KAAK,CAAC,CAAC,CAAC;EACjB;EACA,IAAIA,KAAK,CAACE,MAAM,KAAK,CAAC,EAAE;IACtB,OAAO,GAAGF,KAAK,CAAC,CAAC,CAAC,IAAIC,cAAc,IAAID,KAAK,CAAC,CAAC,CAAC,EAAE;EACpD;EAEA,OAAO,GAAGA,KAAK,CAACG,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAACC,IAAI,CAAC,IAAI,CAAC,IAAIH,cAAc,IAAID,KAAK,CAACK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;AAC7E;;AAEA;AACA;AACA","ignoreList":[]}
@@ -1,6 +1,6 @@
1
1
  import { getTraceId } from '@defra/hapi-tracing';
2
2
  import { config } from "../../config/index.js";
3
- import { applyTraceHeaders, isValidUUID } from "./utils.js";
3
+ import { applyTraceHeaders, isValidUUID, joinWithOr } from "./utils.js";
4
4
  jest.mock('@defra/hapi-tracing');
5
5
  describe('Header helper functions', () => {
6
6
  it('should include the trace id in the headers if available', () => {
@@ -81,5 +81,25 @@ describe('Header helper functions', () => {
81
81
  }) => {
82
82
  expect(isValidUUID(uuid)).toBe(valid);
83
83
  });
84
+ describe('joinWithOr', () => {
85
+ it('should handle undefined array', () => {
86
+ expect(joinWithOr(undefined)).toBe('');
87
+ });
88
+ it('should handle empty array', () => {
89
+ expect(joinWithOr([])).toBe('');
90
+ });
91
+ it('should handle one item', () => {
92
+ expect(joinWithOr(['item1'])).toBe('item1');
93
+ });
94
+ it('should handle two items', () => {
95
+ expect(joinWithOr(['item1', 'item2'])).toBe('item1 or item2');
96
+ });
97
+ it('should handle three items', () => {
98
+ expect(joinWithOr(['item1', 'item2', 'item3'])).toBe('item1, item2 or item3');
99
+ });
100
+ it('should handle a different last separator', () => {
101
+ expect(joinWithOr(['item1', 'item2', 'item3'], 'and')).toBe('item1, item2 and item3');
102
+ });
103
+ });
84
104
  });
85
105
  //# sourceMappingURL=utils.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.test.js","names":["getTraceId","config","applyTraceHeaders","isValidUUID","jest","mock","describe","it","mocked","mockReturnValue","result","expect","toEqual","get","header","toBeUndefined","existingHeaders","Authorization","toBe","each","uuid","valid"],"sources":["../../../src/server/utils/utils.test.js"],"sourcesContent":["import { getTraceId } from '@defra/hapi-tracing'\n\nimport { config } from '~/src/config/index.js'\nimport { applyTraceHeaders, isValidUUID } from '~/src/server/utils/utils.js'\n\njest.mock('@defra/hapi-tracing')\n\ndescribe('Header helper functions', () => {\n it('should include the trace id in the headers if available', () => {\n jest.mocked(getTraceId).mockReturnValue('my-trace-id')\n\n const result = applyTraceHeaders() // Updated to applyTraceHeaders\n expect(result).toEqual({\n [config.get('tracing').header]: 'my-trace-id'\n })\n })\n\n it('should exclude the trace id in the headers if missing', () => {\n jest.mocked(getTraceId).mockReturnValue(null)\n\n const result = applyTraceHeaders() // Updated to applyTraceHeaders\n expect(result).toBeUndefined()\n })\n\n it('should merge existing headers with the trace id if available', () => {\n jest.mocked(getTraceId).mockReturnValue('my-trace-id')\n\n const existingHeaders = { Authorization: 'Bearer token' }\n const result = applyTraceHeaders(existingHeaders) // Updated to applyTraceHeaders\n\n expect(result).toEqual({\n Authorization: 'Bearer token',\n [config.get('tracing').header]: 'my-trace-id'\n })\n })\n\n it('should return existing headers without modification if trace id is missing', () => {\n jest.mocked(getTraceId).mockReturnValue(null)\n\n const existingHeaders = { Authorization: 'Bearer token' }\n const result = applyTraceHeaders(existingHeaders) // Updated to applyTraceHeaders\n\n expect(result).toEqual({\n Authorization: 'Bearer token'\n })\n })\n\n it('should return existing headers if tracing header configuration is missing', () => {\n const existingHeaders = { Authorization: 'Bearer token' }\n const result = applyTraceHeaders(existingHeaders, '')\n\n expect(result).toBe(existingHeaders)\n })\n\n it.each([\n { uuid: '1f457a37-7b99-452e-8324-df9e041abff2', valid: true },\n { uuid: '0c9a2690-9a0c-4a2c-98d7-e9ef95615ac9', valid: true },\n { uuid: 'f223de3b-5ae5-44b2-8cee-ea8439adc335', valid: true },\n { uuid: '82ecc90c-bc47-4ec5-80af-1a9fc1c4c08c', valid: true },\n { uuid: 'd99ff582-ecce-474f-a44b-bc5961d977c5', valid: true },\n { uuid: '7afffc8a-81ab-4aa6-a8f5-ecf6a600a781', valid: true },\n { uuid: '7afffc8a81ab4aa6a8f5ecf6a600a781', valid: true },\n { uuid: '', valid: false },\n { uuid: 'uuid', valid: false },\n { uuid: 'h4f84ef8-b5e1-4544-94aa-1b671d50d8cb', valid: false }\n ])('should validate uuid appropriately %s', ({ uuid, valid }) => {\n expect(isValidUUID(uuid)).toBe(valid)\n })\n})\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,qBAAqB;AAEhD,SAASC,MAAM;AACf,SAASC,iBAAiB,EAAEC,WAAW;AAEvCC,IAAI,CAACC,IAAI,CAAC,qBAAqB,CAAC;AAEhCC,QAAQ,CAAC,yBAAyB,EAAE,MAAM;EACxCC,EAAE,CAAC,yDAAyD,EAAE,MAAM;IAClEH,IAAI,CAACI,MAAM,CAACR,UAAU,CAAC,CAACS,eAAe,CAAC,aAAa,CAAC;IAEtD,MAAMC,MAAM,GAAGR,iBAAiB,CAAC,CAAC,EAAC;IACnCS,MAAM,CAACD,MAAM,CAAC,CAACE,OAAO,CAAC;MACrB,CAACX,MAAM,CAACY,GAAG,CAAC,SAAS,CAAC,CAACC,MAAM,GAAG;IAClC,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFP,EAAE,CAAC,uDAAuD,EAAE,MAAM;IAChEH,IAAI,CAACI,MAAM,CAACR,UAAU,CAAC,CAACS,eAAe,CAAC,IAAI,CAAC;IAE7C,MAAMC,MAAM,GAAGR,iBAAiB,CAAC,CAAC,EAAC;IACnCS,MAAM,CAACD,MAAM,CAAC,CAACK,aAAa,CAAC,CAAC;EAChC,CAAC,CAAC;EAEFR,EAAE,CAAC,8DAA8D,EAAE,MAAM;IACvEH,IAAI,CAACI,MAAM,CAACR,UAAU,CAAC,CAACS,eAAe,CAAC,aAAa,CAAC;IAEtD,MAAMO,eAAe,GAAG;MAAEC,aAAa,EAAE;IAAe,CAAC;IACzD,MAAMP,MAAM,GAAGR,iBAAiB,CAACc,eAAe,CAAC,EAAC;;IAElDL,MAAM,CAACD,MAAM,CAAC,CAACE,OAAO,CAAC;MACrBK,aAAa,EAAE,cAAc;MAC7B,CAAChB,MAAM,CAACY,GAAG,CAAC,SAAS,CAAC,CAACC,MAAM,GAAG;IAClC,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFP,EAAE,CAAC,4EAA4E,EAAE,MAAM;IACrFH,IAAI,CAACI,MAAM,CAACR,UAAU,CAAC,CAACS,eAAe,CAAC,IAAI,CAAC;IAE7C,MAAMO,eAAe,GAAG;MAAEC,aAAa,EAAE;IAAe,CAAC;IACzD,MAAMP,MAAM,GAAGR,iBAAiB,CAACc,eAAe,CAAC,EAAC;;IAElDL,MAAM,CAACD,MAAM,CAAC,CAACE,OAAO,CAAC;MACrBK,aAAa,EAAE;IACjB,CAAC,CAAC;EACJ,CAAC,CAAC;EAEFV,EAAE,CAAC,2EAA2E,EAAE,MAAM;IACpF,MAAMS,eAAe,GAAG;MAAEC,aAAa,EAAE;IAAe,CAAC;IACzD,MAAMP,MAAM,GAAGR,iBAAiB,CAACc,eAAe,EAAE,EAAE,CAAC;IAErDL,MAAM,CAACD,MAAM,CAAC,CAACQ,IAAI,CAACF,eAAe,CAAC;EACtC,CAAC,CAAC;EAEFT,EAAE,CAACY,IAAI,CAAC,CACN;IAAEC,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAK,CAAC,EAC7D;IAAED,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAK,CAAC,EAC7D;IAAED,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAK,CAAC,EAC7D;IAAED,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAK,CAAC,EAC7D;IAAED,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAK,CAAC,EAC7D;IAAED,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAK,CAAC,EAC7D;IAAED,IAAI,EAAE,kCAAkC;IAAEC,KAAK,EAAE;EAAK,CAAC,EACzD;IAAED,IAAI,EAAE,EAAE;IAAEC,KAAK,EAAE;EAAM,CAAC,EAC1B;IAAED,IAAI,EAAE,MAAM;IAAEC,KAAK,EAAE;EAAM,CAAC,EAC9B;IAAED,IAAI,EAAE,sCAAsC;IAAEC,KAAK,EAAE;EAAM,CAAC,CAC/D,CAAC,CAAC,uCAAuC,EAAE,CAAC;IAAED,IAAI;IAAEC;EAAM,CAAC,KAAK;IAC/DV,MAAM,CAACR,WAAW,CAACiB,IAAI,CAAC,CAAC,CAACF,IAAI,CAACG,KAAK,CAAC;EACvC,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"utils.test.js","names":["getTraceId","config","applyTraceHeaders","isValidUUID","joinWithOr","jest","mock","describe","it","mocked","mockReturnValue","result","expect","toEqual","get","header","toBeUndefined","existingHeaders","Authorization","toBe","each","uuid","valid","undefined"],"sources":["../../../src/server/utils/utils.test.js"],"sourcesContent":["import { getTraceId } from '@defra/hapi-tracing'\n\nimport { config } from '~/src/config/index.js'\nimport {\n applyTraceHeaders,\n isValidUUID,\n joinWithOr\n} from '~/src/server/utils/utils.js'\n\njest.mock('@defra/hapi-tracing')\n\ndescribe('Header helper functions', () => {\n it('should include the trace id in the headers if available', () => {\n jest.mocked(getTraceId).mockReturnValue('my-trace-id')\n\n const result = applyTraceHeaders() // Updated to applyTraceHeaders\n expect(result).toEqual({\n [config.get('tracing').header]: 'my-trace-id'\n })\n })\n\n it('should exclude the trace id in the headers if missing', () => {\n jest.mocked(getTraceId).mockReturnValue(null)\n\n const result = applyTraceHeaders() // Updated to applyTraceHeaders\n expect(result).toBeUndefined()\n })\n\n it('should merge existing headers with the trace id if available', () => {\n jest.mocked(getTraceId).mockReturnValue('my-trace-id')\n\n const existingHeaders = { Authorization: 'Bearer token' }\n const result = applyTraceHeaders(existingHeaders) // Updated to applyTraceHeaders\n\n expect(result).toEqual({\n Authorization: 'Bearer token',\n [config.get('tracing').header]: 'my-trace-id'\n })\n })\n\n it('should return existing headers without modification if trace id is missing', () => {\n jest.mocked(getTraceId).mockReturnValue(null)\n\n const existingHeaders = { Authorization: 'Bearer token' }\n const result = applyTraceHeaders(existingHeaders) // Updated to applyTraceHeaders\n\n expect(result).toEqual({\n Authorization: 'Bearer token'\n })\n })\n\n it('should return existing headers if tracing header configuration is missing', () => {\n const existingHeaders = { Authorization: 'Bearer token' }\n const result = applyTraceHeaders(existingHeaders, '')\n\n expect(result).toBe(existingHeaders)\n })\n\n it.each([\n { uuid: '1f457a37-7b99-452e-8324-df9e041abff2', valid: true },\n { uuid: '0c9a2690-9a0c-4a2c-98d7-e9ef95615ac9', valid: true },\n { uuid: 'f223de3b-5ae5-44b2-8cee-ea8439adc335', valid: true },\n { uuid: '82ecc90c-bc47-4ec5-80af-1a9fc1c4c08c', valid: true },\n { uuid: 'd99ff582-ecce-474f-a44b-bc5961d977c5', valid: true },\n { uuid: '7afffc8a-81ab-4aa6-a8f5-ecf6a600a781', valid: true },\n { uuid: '7afffc8a81ab4aa6a8f5ecf6a600a781', valid: true },\n { uuid: '', valid: false },\n { uuid: 'uuid', valid: false },\n { uuid: 'h4f84ef8-b5e1-4544-94aa-1b671d50d8cb', valid: false }\n ])('should validate uuid appropriately %s', ({ uuid, valid }) => {\n expect(isValidUUID(uuid)).toBe(valid)\n })\n\n describe('joinWithOr', () => {\n it('should handle undefined array', () => {\n expect(joinWithOr(undefined)).toBe('')\n })\n it('should handle empty array', () => {\n expect(joinWithOr([])).toBe('')\n })\n it('should handle one item', () => {\n expect(joinWithOr(['item1'])).toBe('item1')\n })\n it('should handle two items', () => {\n expect(joinWithOr(['item1', 'item2'])).toBe('item1 or item2')\n })\n it('should handle three items', () => {\n expect(joinWithOr(['item1', 'item2', 'item3'])).toBe(\n 'item1, item2 or item3'\n )\n })\n it('should handle a different last separator', () => {\n expect(joinWithOr(['item1', 'item2', 'item3'], 'and')).toBe(\n 'item1, item2 and item3'\n )\n })\n })\n})\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "5.0.0-alpha.10",
3
+ "version": "5.0.0-alpha.11",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -112,6 +112,7 @@
112
112
  "@turf/bbox": "^7.3.4",
113
113
  "@turf/boolean-within": "^7.3.5",
114
114
  "@turf/centroid": "^7.3.4",
115
+ "@turf/helpers": "^7.3.5",
115
116
  "@types/humanize-duration": "^3.27.4",
116
117
  "accessible-autocomplete": "^3.0.1",
117
118
  "atob": "^2.1.2",
@@ -424,6 +424,7 @@ function initUpload() {
424
424
 
425
425
  if (fileInput.files && fileInput.files.length > 0) {
426
426
  selectedFiles = Array.from(fileInput.files)
427
+ uploadButton.click()
427
428
  }
428
429
  })
429
430
 
@@ -10,6 +10,7 @@ import {
10
10
  defaultConfig,
11
11
  getCentroidGridRef,
12
12
  getCoordinateGridRef,
13
+ getMapCountryLayers,
13
14
  getMapLayers
14
15
  } from './map.js'
15
16
  import sssiDataset from './sssi-dataset.js'
@@ -172,39 +173,9 @@ export function processGeospatial(config, geospatial, index) {
172
173
  const bounds = geojson.features.length ? getBoundingBox(geojson) : undefined
173
174
  const drawPlugin = createDrawPlugin()
174
175
  const plugins = [drawPlugin]
175
- const datasets = []
176
176
  const country = geospatial.dataset.country
177
177
  const mapLayers = getMapLayers(geospatial.dataset.maplayers)
178
-
179
- if (country) {
180
- // Add the country bounds to the datasets array to show the valid area on the map
181
- // and provide feedback to the user when they add features outside of the bounds.
182
- datasets.push(
183
- {
184
- id: 'invalid-area',
185
- label: 'Invalid areas',
186
- geojson: `${config.apiPath}/maps/countries.geojson?omit=${country}`,
187
- showInKey: false,
188
- showInMenu: false,
189
- style: {
190
- stroke: 'gray',
191
- strokeWidth: 1,
192
- fill: 'rgba(211,211,211,0.8)'
193
- }
194
- },
195
- {
196
- id: 'valid-area',
197
- label: 'Valid areas',
198
- geojson: `${config.apiPath}/maps/countries.geojson?only=${country}`,
199
- showInKey: false,
200
- showInMenu: false,
201
- style: {
202
- stroke: 'rgba(0,112,60,1)',
203
- strokeWidth: 1
204
- }
205
- }
206
- )
207
- }
178
+ const datasets = getMapCountryLayers(config.apiPath, country)
208
179
 
209
180
  if (mapLayers.includes('sssi')) {
210
181
  datasets.push(...sssiDataset)
@@ -3,6 +3,7 @@ import {
3
3
  CharacterCount,
4
4
  Checkboxes,
5
5
  ErrorSummary,
6
+ FileUpload,
6
7
  Header,
7
8
  NotificationBanner,
8
9
  Radios,
@@ -16,6 +17,7 @@ export function initAllGovuk() {
16
17
  createAll(Checkboxes)
17
18
  createAll(ErrorSummary)
18
19
  createAll(Header)
20
+ createAll(FileUpload)
19
21
  createAll(NotificationBanner)
20
22
  createAll(Radios)
21
23
  createAll(SkipLink)
@@ -7,6 +7,7 @@ import {
7
7
  createMap,
8
8
  defaultConfig,
9
9
  eastingNorthingToLatLong,
10
+ getMapCountryLayers,
10
11
  getMapLayers,
11
12
  latLongToEastingNorthing,
12
13
  latLongToOsGridRef,
@@ -423,8 +424,9 @@ export function processLocation(config, location, index) {
423
424
 
424
425
  locationInputs.after(mapContainer)
425
426
 
426
- const datasets = []
427
+ const country = location.dataset.country
427
428
  const mapLayers = getMapLayers(location.dataset.maplayers)
429
+ const datasets = getMapCountryLayers(config.apiPath, country)
428
430
 
429
431
  if (mapLayers.includes('sssi')) {
430
432
  datasets.push(...sssiDataset)
@@ -367,6 +367,48 @@ export function getMapLayers(layers) {
367
367
  return trimmed ? trimmed.split(',') : []
368
368
  }
369
369
 
370
+ /**
371
+ * Adds the country layers to a datasets array
372
+ * @param {string} apiPath
373
+ * @param {string} [country]
374
+ */
375
+ export function getMapCountryLayers(apiPath, country) {
376
+ /** @type {any[]} */
377
+ const datasets = []
378
+
379
+ if (country) {
380
+ // Add the country bounds to the datasets array to show the valid area on the map
381
+ // and provide feedback to the user when they add features outside of the bounds.
382
+ datasets.push(
383
+ {
384
+ id: 'invalid-area',
385
+ label: 'Invalid areas',
386
+ geojson: `${apiPath}/maps/countries.geojson?omit=${country}`,
387
+ showInKey: false,
388
+ showInMenu: false,
389
+ style: {
390
+ stroke: 'gray',
391
+ strokeWidth: 1,
392
+ fill: 'rgba(211,211,211,0.8)'
393
+ }
394
+ },
395
+ {
396
+ id: 'valid-area',
397
+ label: 'Valid areas',
398
+ geojson: `${apiPath}/maps/countries.geojson?only=${country}`,
399
+ showInKey: false,
400
+ showInMenu: false,
401
+ style: {
402
+ stroke: 'rgba(0,112,60,1)',
403
+ strokeWidth: 1
404
+ }
405
+ }
406
+ )
407
+ }
408
+
409
+ return datasets
410
+ }
411
+
370
412
  /**
371
413
  * @typedef {object} InteractiveMap - an instance of a InteractiveMap
372
414
  * @property {Function} on - register callback listeners to map events