@defra/forms-engine-plugin 2.1.8 → 2.1.10

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 (26) hide show
  1. package/.server/server/plugins/engine/components/helpers/components.js +1 -1
  2. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  3. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +27 -12
  4. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  5. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +1 -4
  6. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +10 -9
  7. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  8. package/.server/server/plugins/engine/types/index.d.ts +0 -1
  9. package/.server/server/plugins/engine/types/index.js.map +1 -1
  10. package/.server/server/plugins/engine/types/schema.d.ts +2 -1
  11. package/.server/server/plugins/engine/types/schema.js +13 -2
  12. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  13. package/.server/server/plugins/engine/types.d.ts +24 -3
  14. package/.server/server/plugins/engine/types.js +4 -0
  15. package/.server/server/plugins/engine/types.js.map +1 -1
  16. package/package.json +1 -1
  17. package/src/server/plugins/engine/components/MultilineTextField.test.ts +11 -0
  18. package/src/server/plugins/engine/components/helpers/components.ts +1 -1
  19. package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +192 -2
  20. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +38 -12
  21. package/src/server/plugins/engine/outputFormatters/machine/v2.test.ts +2 -0
  22. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +18 -28
  23. package/src/server/plugins/engine/types/index.ts +0 -2
  24. package/src/server/plugins/engine/types/schema.test.ts +68 -60
  25. package/src/server/plugins/engine/types/schema.ts +23 -2
  26. package/src/server/plugins/engine/types.ts +33 -3
@@ -210,7 +210,7 @@ export function getAnswerMarkdown(field, state, options = {
210
210
  }).join('');
211
211
  } else if (field instanceof Components.MultilineTextField) {
212
212
  // Preserve Multiline text new lines
213
- answerEscaped = answer.split(/\r?\n/).map(escapeMarkdown).join('\n').concat('\n');
213
+ answerEscaped = answer.split(/(?:\r?\n)+/).map(escapeMarkdown).join('\n').concat('\n');
214
214
  } else if (field instanceof Components.UkAddressField) {
215
215
  // Format UK addresses into new lines
216
216
  answerEscaped = (field.getContextValueFromState(state) ?? []).map(escapeMarkdown).join('\n').concat('\n');
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","names":["ComponentType","Marked","config","ListFormComponent","escapeMarkdown","Components","designerUrl","get","markdown","breaks","gfm","extensions","name","renderer","tokens","text","parser","parseInline","length","walkTokens","token","includes","type","hasListFormField","field","isListFieldType","allowedTypes","AutocompleteField","CheckboxesField","RadiosField","SelectField","YesNoField","createComponent","def","options","component","DatePartsField","Details","EmailAddressField","Html","InsetText","List","Markdown","MultilineTextField","NumberField","TelephoneNumberField","TextField","UkAddressField","MonthYearField","FileUploadField","Error","getAnswer","state","format","getAnswerMarkdown","context","getContextValueFromState","toString","parse","async","trim","getDisplayStringFromState","answer","answerEscaped","files","getFormValueFromState","map","status","file","form","filename","fileId","join","values","flat","items","filter","value","item","label","line","toLowerCase","split","concat"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/components.ts"],"sourcesContent":["import { ComponentType, type ComponentDef } from '@defra/forms-model'\nimport { Marked, type Token } from 'marked'\n\nimport { config } from '~/src/config/index.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport { type FormState } from '~/src/server/plugins/engine/types.js'\n\n// All component instances\nexport type Component = InstanceType<\n (typeof Components)[keyof typeof Components]\n>\n\n// Field component instances only\nexport type Field = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.RadiosField\n | typeof Components.YesNoField\n | typeof Components.CheckboxesField\n | typeof Components.DatePartsField\n | typeof Components.EmailAddressField\n | typeof Components.MonthYearField\n | typeof Components.MultilineTextField\n | typeof Components.NumberField\n | typeof Components.SelectField\n | typeof Components.TelephoneNumberField\n | typeof Components.TextField\n | typeof Components.UkAddressField\n | typeof Components.FileUploadField\n>\n\n// Guidance component instances only\nexport type Guidance = InstanceType<\n | typeof Components.Details\n | typeof Components.Html\n | typeof Components.Markdown\n | typeof Components.InsetText\n | typeof Components.List\n>\n\n// List component instances only\nexport type ListField = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.CheckboxesField\n | typeof Components.RadiosField\n | typeof Components.SelectField\n | typeof Components.YesNoField\n>\n\nexport const designerUrl = config.get('designerUrl')\n\nexport const markdown = new Marked({\n breaks: true,\n gfm: true,\n\n /**\n * Render paragraphs without `<p>` wrappers\n * for check answers summary list `<dd>`\n */\n extensions: [\n {\n name: 'paragraph',\n renderer({ tokens = [] }) {\n const text = this.parser.parseInline(tokens)\n return tokens.length > 1 ? `${text}<br>` : text\n }\n }\n ],\n\n /**\n * Restrict allowed Markdown tokens\n */\n walkTokens(token) {\n const tokens: Token['type'][] = [\n 'br',\n 'escape',\n 'list',\n 'list_item',\n 'paragraph',\n 'space',\n 'text'\n ]\n\n if (!tokens.includes(token.type)) {\n token.type = 'text'\n }\n }\n})\n\n/**\n * Filter known components with lists\n */\nexport function hasListFormField(\n field?: Partial<Component>\n): field is ListFormComponent {\n return !!field && isListFieldType(field.type)\n}\n\nexport function isListFieldType(\n type?: ComponentType\n): type is ListField['type'] {\n const allowedTypes = [\n ComponentType.AutocompleteField,\n ComponentType.CheckboxesField,\n ComponentType.RadiosField,\n ComponentType.SelectField,\n ComponentType.YesNoField\n ]\n\n return !!type && allowedTypes.includes(type)\n}\n\n/**\n * Create field instance for each {@link ComponentDef} type\n */\nexport function createComponent(\n def: ComponentDef,\n options: ConstructorParameters<typeof ComponentBase>[1]\n): Component {\n let component: Component | undefined\n\n switch (def.type) {\n case ComponentType.AutocompleteField:\n component = new Components.AutocompleteField(def, options)\n break\n\n case ComponentType.CheckboxesField:\n component = new Components.CheckboxesField(def, options)\n break\n\n case ComponentType.DatePartsField:\n component = new Components.DatePartsField(def, options)\n break\n\n case ComponentType.Details:\n component = new Components.Details(def, options)\n break\n\n case ComponentType.EmailAddressField:\n component = new Components.EmailAddressField(def, options)\n break\n\n case ComponentType.Html:\n component = new Components.Html(def, options)\n break\n\n case ComponentType.InsetText:\n component = new Components.InsetText(def, options)\n break\n\n case ComponentType.List:\n component = new Components.List(def, options)\n break\n\n case ComponentType.Markdown:\n component = new Components.Markdown(def, options)\n break\n\n case ComponentType.MultilineTextField:\n component = new Components.MultilineTextField(def, options)\n break\n\n case ComponentType.NumberField:\n component = new Components.NumberField(def, options)\n break\n\n case ComponentType.RadiosField:\n component = new Components.RadiosField(def, options)\n break\n\n case ComponentType.SelectField:\n component = new Components.SelectField(def, options)\n break\n\n case ComponentType.TelephoneNumberField:\n component = new Components.TelephoneNumberField(def, options)\n break\n\n case ComponentType.TextField:\n component = new Components.TextField(def, options)\n break\n\n case ComponentType.UkAddressField:\n component = new Components.UkAddressField(def, options)\n break\n\n case ComponentType.YesNoField:\n component = new Components.YesNoField(def, options)\n break\n\n case ComponentType.MonthYearField:\n component = new Components.MonthYearField(def, options)\n break\n\n case ComponentType.FileUploadField:\n component = new Components.FileUploadField(def, options)\n break\n }\n\n if (typeof component === 'undefined') {\n throw new Error(`Component type ${def.type} does not exist`)\n }\n\n return component\n}\n\n/**\n * Get formatted answer for a field\n */\nexport function getAnswer(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'data' // Submission data\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n // Use escaped display text for GOV.UK Notify emails\n if (options.format === 'email') {\n return getAnswerMarkdown(field, state, { format: 'email' })\n }\n\n // Use context value for submission data\n if (options.format === 'data') {\n const context = field.getContextValueFromState(state)\n return context?.toString() ?? ''\n }\n\n // Use display HTML for check answers summary (multi line)\n if (\n field instanceof ListFormComponent ||\n field instanceof Components.MultilineTextField ||\n field instanceof Components.UkAddressField\n ) {\n return markdown\n .parse(getAnswerMarkdown(field, state), { async: false })\n .trim()\n }\n\n // Use display text for check answers summary (single line)\n return field.getDisplayStringFromState(state)\n}\n\n/**\n * Get formatted answer for a field (Markdown only)\n */\nexport function getAnswerMarkdown(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n const answer = field.getDisplayStringFromState(state)\n\n // Use escaped display text\n let answerEscaped = `${escapeMarkdown(answer)}\\n`\n\n if (field instanceof Components.FileUploadField) {\n const files = field.getFormValueFromState(state)\n\n // Skip empty files\n if (!files?.length) {\n return answerEscaped\n }\n\n answerEscaped = `${escapeMarkdown(answer)}:\\n\\n`\n\n // Append bullet points\n answerEscaped += files\n .map(({ status }) => {\n const { file } = status.form\n const filename = escapeMarkdown(file.filename)\n return `* [${filename}](${designerUrl}/file-download/${file.fileId})\\n`\n })\n .join('')\n } else if (field instanceof ListFormComponent) {\n const values = [field.getContextValueFromState(state)].flat()\n const items = field.items.filter(({ value }) => values.includes(value))\n\n // Skip empty values\n if (!items.length) {\n return answerEscaped\n }\n\n answerEscaped = ''\n\n // Append bullet points\n answerEscaped += items\n .map((item) => {\n const label = escapeMarkdown(item.text)\n const value = escapeMarkdown(`(${item.value})`)\n\n let line = label\n\n // Prepend bullet points for checkboxes only\n if (field instanceof Components.CheckboxesField) {\n line = `* ${line}`\n }\n\n // Append raw values in parentheses\n // e.g. `* None of the above (false)`\n return options.format === 'email' &&\n `${item.value}`.toLowerCase() !== item.text.toLowerCase()\n ? `${line} ${value}\\n`\n : `${line}\\n`\n })\n .join('')\n } else if (field instanceof Components.MultilineTextField) {\n // Preserve Multiline text new lines\n answerEscaped = answer\n .split(/\\r?\\n/)\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (field instanceof Components.UkAddressField) {\n // Format UK addresses into new lines\n answerEscaped = (field.getContextValueFromState(state) ?? [])\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n }\n\n return answerEscaped\n}\n"],"mappings":"AAAA,SAASA,aAAa,QAA2B,oBAAoB;AACrE,SAASC,MAAM,QAAoB,QAAQ;AAE3C,SAASC,MAAM;AAEf,SAASC,iBAAiB;AAC1B,SAASC,cAAc;AACvB,OAAO,KAAKC,UAAU;;AAGtB;;AAKA;;AAkBA;;AASA;;AASA,OAAO,MAAMC,WAAW,GAAGJ,MAAM,CAACK,GAAG,CAAC,aAAa,CAAC;AAEpD,OAAO,MAAMC,QAAQ,GAAG,IAAIP,MAAM,CAAC;EACjCQ,MAAM,EAAE,IAAI;EACZC,GAAG,EAAE,IAAI;EAET;AACF;AACA;AACA;EACEC,UAAU,EAAE,CACV;IACEC,IAAI,EAAE,WAAW;IACjBC,QAAQA,CAAC;MAAEC,MAAM,GAAG;IAAG,CAAC,EAAE;MACxB,MAAMC,IAAI,GAAG,IAAI,CAACC,MAAM,CAACC,WAAW,CAACH,MAAM,CAAC;MAC5C,OAAOA,MAAM,CAACI,MAAM,GAAG,CAAC,GAAG,GAAGH,IAAI,MAAM,GAAGA,IAAI;IACjD;EACF,CAAC,CACF;EAED;AACF;AACA;EACEI,UAAUA,CAACC,KAAK,EAAE;IAChB,MAAMN,MAAuB,GAAG,CAC9B,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,WAAW,EACX,WAAW,EACX,OAAO,EACP,MAAM,CACP;IAED,IAAI,CAACA,MAAM,CAACO,QAAQ,CAACD,KAAK,CAACE,IAAI,CAAC,EAAE;MAChCF,KAAK,CAACE,IAAI,GAAG,MAAM;IACrB;EACF;AACF,CAAC,CAAC;;AAEF;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC9BC,KAA0B,EACE;EAC5B,OAAO,CAAC,CAACA,KAAK,IAAIC,eAAe,CAACD,KAAK,CAACF,IAAI,CAAC;AAC/C;AAEA,OAAO,SAASG,eAAeA,CAC7BH,IAAoB,EACO;EAC3B,MAAMI,YAAY,GAAG,CACnB1B,aAAa,CAAC2B,iBAAiB,EAC/B3B,aAAa,CAAC4B,eAAe,EAC7B5B,aAAa,CAAC6B,WAAW,EACzB7B,aAAa,CAAC8B,WAAW,EACzB9B,aAAa,CAAC+B,UAAU,CACzB;EAED,OAAO,CAAC,CAACT,IAAI,IAAII,YAAY,CAACL,QAAQ,CAACC,IAAI,CAAC;AAC9C;;AAEA;AACA;AACA;AACA,OAAO,SAASU,eAAeA,CAC7BC,GAAiB,EACjBC,OAAuD,EAC5C;EACX,IAAIC,SAAgC;EAEpC,QAAQF,GAAG,CAACX,IAAI;IACd,KAAKtB,aAAa,CAAC2B,iBAAiB;MAClCQ,SAAS,GAAG,IAAI9B,UAAU,CAACsB,iBAAiB,CAACM,GAAG,EAAEC,OAAO,CAAC;MAC1D;IAEF,KAAKlC,aAAa,CAAC4B,eAAe;MAChCO,SAAS,GAAG,IAAI9B,UAAU,CAACuB,eAAe,CAACK,GAAG,EAAEC,OAAO,CAAC;MACxD;IAEF,KAAKlC,aAAa,CAACoC,cAAc;MAC/BD,SAAS,GAAG,IAAI9B,UAAU,CAAC+B,cAAc,CAACH,GAAG,EAAEC,OAAO,CAAC;MACvD;IAEF,KAAKlC,aAAa,CAACqC,OAAO;MACxBF,SAAS,GAAG,IAAI9B,UAAU,CAACgC,OAAO,CAACJ,GAAG,EAAEC,OAAO,CAAC;MAChD;IAEF,KAAKlC,aAAa,CAACsC,iBAAiB;MAClCH,SAAS,GAAG,IAAI9B,UAAU,CAACiC,iBAAiB,CAACL,GAAG,EAAEC,OAAO,CAAC;MAC1D;IAEF,KAAKlC,aAAa,CAACuC,IAAI;MACrBJ,SAAS,GAAG,IAAI9B,UAAU,CAACkC,IAAI,CAACN,GAAG,EAAEC,OAAO,CAAC;MAC7C;IAEF,KAAKlC,aAAa,CAACwC,SAAS;MAC1BL,SAAS,GAAG,IAAI9B,UAAU,CAACmC,SAAS,CAACP,GAAG,EAAEC,OAAO,CAAC;MAClD;IAEF,KAAKlC,aAAa,CAACyC,IAAI;MACrBN,SAAS,GAAG,IAAI9B,UAAU,CAACoC,IAAI,CAACR,GAAG,EAAEC,OAAO,CAAC;MAC7C;IAEF,KAAKlC,aAAa,CAAC0C,QAAQ;MACzBP,SAAS,GAAG,IAAI9B,UAAU,CAACqC,QAAQ,CAACT,GAAG,EAAEC,OAAO,CAAC;MACjD;IAEF,KAAKlC,aAAa,CAAC2C,kBAAkB;MACnCR,SAAS,GAAG,IAAI9B,UAAU,CAACsC,kBAAkB,CAACV,GAAG,EAAEC,OAAO,CAAC;MAC3D;IAEF,KAAKlC,aAAa,CAAC4C,WAAW;MAC5BT,SAAS,GAAG,IAAI9B,UAAU,CAACuC,WAAW,CAACX,GAAG,EAAEC,OAAO,CAAC;MACpD;IAEF,KAAKlC,aAAa,CAAC6B,WAAW;MAC5BM,SAAS,GAAG,IAAI9B,UAAU,CAACwB,WAAW,CAACI,GAAG,EAAEC,OAAO,CAAC;MACpD;IAEF,KAAKlC,aAAa,CAAC8B,WAAW;MAC5BK,SAAS,GAAG,IAAI9B,UAAU,CAACyB,WAAW,CAACG,GAAG,EAAEC,OAAO,CAAC;MACpD;IAEF,KAAKlC,aAAa,CAAC6C,oBAAoB;MACrCV,SAAS,GAAG,IAAI9B,UAAU,CAACwC,oBAAoB,CAACZ,GAAG,EAAEC,OAAO,CAAC;MAC7D;IAEF,KAAKlC,aAAa,CAAC8C,SAAS;MAC1BX,SAAS,GAAG,IAAI9B,UAAU,CAACyC,SAAS,CAACb,GAAG,EAAEC,OAAO,CAAC;MAClD;IAEF,KAAKlC,aAAa,CAAC+C,cAAc;MAC/BZ,SAAS,GAAG,IAAI9B,UAAU,CAAC0C,cAAc,CAACd,GAAG,EAAEC,OAAO,CAAC;MACvD;IAEF,KAAKlC,aAAa,CAAC+B,UAAU;MAC3BI,SAAS,GAAG,IAAI9B,UAAU,CAAC0B,UAAU,CAACE,GAAG,EAAEC,OAAO,CAAC;MACnD;IAEF,KAAKlC,aAAa,CAACgD,cAAc;MAC/Bb,SAAS,GAAG,IAAI9B,UAAU,CAAC2C,cAAc,CAACf,GAAG,EAAEC,OAAO,CAAC;MACvD;IAEF,KAAKlC,aAAa,CAACiD,eAAe;MAChCd,SAAS,GAAG,IAAI9B,UAAU,CAAC4C,eAAe,CAAChB,GAAG,EAAEC,OAAO,CAAC;MACxD;EACJ;EAEA,IAAI,OAAOC,SAAS,KAAK,WAAW,EAAE;IACpC,MAAM,IAAIe,KAAK,CAAC,kBAAkBjB,GAAG,CAACX,IAAI,iBAAiB,CAAC;EAC9D;EAEA,OAAOa,SAAS;AAClB;;AAEA;AACA;AACA;AACA,OAAO,SAASgB,SAASA,CACvB3B,KAAY,EACZ4B,KAAgB,EAChBlB,OAKC,GAAG;EAAEmB,MAAM,EAAE;AAAU,CAAC,EACzB;EACA;EACA,IAAInB,OAAO,CAACmB,MAAM,KAAK,OAAO,EAAE;IAC9B,OAAOC,iBAAiB,CAAC9B,KAAK,EAAE4B,KAAK,EAAE;MAAEC,MAAM,EAAE;IAAQ,CAAC,CAAC;EAC7D;;EAEA;EACA,IAAInB,OAAO,CAACmB,MAAM,KAAK,MAAM,EAAE;IAC7B,MAAME,OAAO,GAAG/B,KAAK,CAACgC,wBAAwB,CAACJ,KAAK,CAAC;IACrD,OAAOG,OAAO,EAAEE,QAAQ,CAAC,CAAC,IAAI,EAAE;EAClC;;EAEA;EACA,IACEjC,KAAK,YAAYrB,iBAAiB,IAClCqB,KAAK,YAAYnB,UAAU,CAACsC,kBAAkB,IAC9CnB,KAAK,YAAYnB,UAAU,CAAC0C,cAAc,EAC1C;IACA,OAAOvC,QAAQ,CACZkD,KAAK,CAACJ,iBAAiB,CAAC9B,KAAK,EAAE4B,KAAK,CAAC,EAAE;MAAEO,KAAK,EAAE;IAAM,CAAC,CAAC,CACxDC,IAAI,CAAC,CAAC;EACX;;EAEA;EACA,OAAOpC,KAAK,CAACqC,yBAAyB,CAACT,KAAK,CAAC;AAC/C;;AAEA;AACA;AACA;AACA,OAAO,SAASE,iBAAiBA,CAC/B9B,KAAY,EACZ4B,KAAgB,EAChBlB,OAIC,GAAG;EAAEmB,MAAM,EAAE;AAAU,CAAC,EACzB;EACA,MAAMS,MAAM,GAAGtC,KAAK,CAACqC,yBAAyB,CAACT,KAAK,CAAC;;EAErD;EACA,IAAIW,aAAa,GAAG,GAAG3D,cAAc,CAAC0D,MAAM,CAAC,IAAI;EAEjD,IAAItC,KAAK,YAAYnB,UAAU,CAAC4C,eAAe,EAAE;IAC/C,MAAMe,KAAK,GAAGxC,KAAK,CAACyC,qBAAqB,CAACb,KAAK,CAAC;;IAEhD;IACA,IAAI,CAACY,KAAK,EAAE9C,MAAM,EAAE;MAClB,OAAO6C,aAAa;IACtB;IAEAA,aAAa,GAAG,GAAG3D,cAAc,CAAC0D,MAAM,CAAC,OAAO;;IAEhD;IACAC,aAAa,IAAIC,KAAK,CACnBE,GAAG,CAAC,CAAC;MAAEC;IAAO,CAAC,KAAK;MACnB,MAAM;QAAEC;MAAK,CAAC,GAAGD,MAAM,CAACE,IAAI;MAC5B,MAAMC,QAAQ,GAAGlE,cAAc,CAACgE,IAAI,CAACE,QAAQ,CAAC;MAC9C,OAAO,MAAMA,QAAQ,KAAKhE,WAAW,kBAAkB8D,IAAI,CAACG,MAAM,KAAK;IACzE,CAAC,CAAC,CACDC,IAAI,CAAC,EAAE,CAAC;EACb,CAAC,MAAM,IAAIhD,KAAK,YAAYrB,iBAAiB,EAAE;IAC7C,MAAMsE,MAAM,GAAG,CAACjD,KAAK,CAACgC,wBAAwB,CAACJ,KAAK,CAAC,CAAC,CAACsB,IAAI,CAAC,CAAC;IAC7D,MAAMC,KAAK,GAAGnD,KAAK,CAACmD,KAAK,CAACC,MAAM,CAAC,CAAC;MAAEC;IAAM,CAAC,KAAKJ,MAAM,CAACpD,QAAQ,CAACwD,KAAK,CAAC,CAAC;;IAEvE;IACA,IAAI,CAACF,KAAK,CAACzD,MAAM,EAAE;MACjB,OAAO6C,aAAa;IACtB;IAEAA,aAAa,GAAG,EAAE;;IAElB;IACAA,aAAa,IAAIY,KAAK,CACnBT,GAAG,CAAEY,IAAI,IAAK;MACb,MAAMC,KAAK,GAAG3E,cAAc,CAAC0E,IAAI,CAAC/D,IAAI,CAAC;MACvC,MAAM8D,KAAK,GAAGzE,cAAc,CAAC,IAAI0E,IAAI,CAACD,KAAK,GAAG,CAAC;MAE/C,IAAIG,IAAI,GAAGD,KAAK;;MAEhB;MACA,IAAIvD,KAAK,YAAYnB,UAAU,CAACuB,eAAe,EAAE;QAC/CoD,IAAI,GAAG,KAAKA,IAAI,EAAE;MACpB;;MAEA;MACA;MACA,OAAO9C,OAAO,CAACmB,MAAM,KAAK,OAAO,IAC/B,GAAGyB,IAAI,CAACD,KAAK,EAAE,CAACI,WAAW,CAAC,CAAC,KAAKH,IAAI,CAAC/D,IAAI,CAACkE,WAAW,CAAC,CAAC,GACvD,GAAGD,IAAI,IAAIH,KAAK,IAAI,GACpB,GAAGG,IAAI,IAAI;IACjB,CAAC,CAAC,CACDR,IAAI,CAAC,EAAE,CAAC;EACb,CAAC,MAAM,IAAIhD,KAAK,YAAYnB,UAAU,CAACsC,kBAAkB,EAAE;IACzD;IACAoB,aAAa,GAAGD,MAAM,CACnBoB,KAAK,CAAC,OAAO,CAAC,CACdhB,GAAG,CAAC9D,cAAc,CAAC,CACnBoE,IAAI,CAAC,IAAI,CAAC,CACVW,MAAM,CAAC,IAAI,CAAC;EACjB,CAAC,MAAM,IAAI3D,KAAK,YAAYnB,UAAU,CAAC0C,cAAc,EAAE;IACrD;IACAgB,aAAa,GAAG,CAACvC,KAAK,CAACgC,wBAAwB,CAACJ,KAAK,CAAC,IAAI,EAAE,EACzDc,GAAG,CAAC9D,cAAc,CAAC,CACnBoE,IAAI,CAAC,IAAI,CAAC,CACVW,MAAM,CAAC,IAAI,CAAC;EACjB;EAEA,OAAOpB,aAAa;AACtB","ignoreList":[]}
1
+ {"version":3,"file":"components.js","names":["ComponentType","Marked","config","ListFormComponent","escapeMarkdown","Components","designerUrl","get","markdown","breaks","gfm","extensions","name","renderer","tokens","text","parser","parseInline","length","walkTokens","token","includes","type","hasListFormField","field","isListFieldType","allowedTypes","AutocompleteField","CheckboxesField","RadiosField","SelectField","YesNoField","createComponent","def","options","component","DatePartsField","Details","EmailAddressField","Html","InsetText","List","Markdown","MultilineTextField","NumberField","TelephoneNumberField","TextField","UkAddressField","MonthYearField","FileUploadField","Error","getAnswer","state","format","getAnswerMarkdown","context","getContextValueFromState","toString","parse","async","trim","getDisplayStringFromState","answer","answerEscaped","files","getFormValueFromState","map","status","file","form","filename","fileId","join","values","flat","items","filter","value","item","label","line","toLowerCase","split","concat"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/components.ts"],"sourcesContent":["import { ComponentType, type ComponentDef } from '@defra/forms-model'\nimport { Marked, type Token } from 'marked'\n\nimport { config } from '~/src/config/index.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport { type FormState } from '~/src/server/plugins/engine/types.js'\n\n// All component instances\nexport type Component = InstanceType<\n (typeof Components)[keyof typeof Components]\n>\n\n// Field component instances only\nexport type Field = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.RadiosField\n | typeof Components.YesNoField\n | typeof Components.CheckboxesField\n | typeof Components.DatePartsField\n | typeof Components.EmailAddressField\n | typeof Components.MonthYearField\n | typeof Components.MultilineTextField\n | typeof Components.NumberField\n | typeof Components.SelectField\n | typeof Components.TelephoneNumberField\n | typeof Components.TextField\n | typeof Components.UkAddressField\n | typeof Components.FileUploadField\n>\n\n// Guidance component instances only\nexport type Guidance = InstanceType<\n | typeof Components.Details\n | typeof Components.Html\n | typeof Components.Markdown\n | typeof Components.InsetText\n | typeof Components.List\n>\n\n// List component instances only\nexport type ListField = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.CheckboxesField\n | typeof Components.RadiosField\n | typeof Components.SelectField\n | typeof Components.YesNoField\n>\n\nexport const designerUrl = config.get('designerUrl')\n\nexport const markdown = new Marked({\n breaks: true,\n gfm: true,\n\n /**\n * Render paragraphs without `<p>` wrappers\n * for check answers summary list `<dd>`\n */\n extensions: [\n {\n name: 'paragraph',\n renderer({ tokens = [] }) {\n const text = this.parser.parseInline(tokens)\n return tokens.length > 1 ? `${text}<br>` : text\n }\n }\n ],\n\n /**\n * Restrict allowed Markdown tokens\n */\n walkTokens(token) {\n const tokens: Token['type'][] = [\n 'br',\n 'escape',\n 'list',\n 'list_item',\n 'paragraph',\n 'space',\n 'text'\n ]\n\n if (!tokens.includes(token.type)) {\n token.type = 'text'\n }\n }\n})\n\n/**\n * Filter known components with lists\n */\nexport function hasListFormField(\n field?: Partial<Component>\n): field is ListFormComponent {\n return !!field && isListFieldType(field.type)\n}\n\nexport function isListFieldType(\n type?: ComponentType\n): type is ListField['type'] {\n const allowedTypes = [\n ComponentType.AutocompleteField,\n ComponentType.CheckboxesField,\n ComponentType.RadiosField,\n ComponentType.SelectField,\n ComponentType.YesNoField\n ]\n\n return !!type && allowedTypes.includes(type)\n}\n\n/**\n * Create field instance for each {@link ComponentDef} type\n */\nexport function createComponent(\n def: ComponentDef,\n options: ConstructorParameters<typeof ComponentBase>[1]\n): Component {\n let component: Component | undefined\n\n switch (def.type) {\n case ComponentType.AutocompleteField:\n component = new Components.AutocompleteField(def, options)\n break\n\n case ComponentType.CheckboxesField:\n component = new Components.CheckboxesField(def, options)\n break\n\n case ComponentType.DatePartsField:\n component = new Components.DatePartsField(def, options)\n break\n\n case ComponentType.Details:\n component = new Components.Details(def, options)\n break\n\n case ComponentType.EmailAddressField:\n component = new Components.EmailAddressField(def, options)\n break\n\n case ComponentType.Html:\n component = new Components.Html(def, options)\n break\n\n case ComponentType.InsetText:\n component = new Components.InsetText(def, options)\n break\n\n case ComponentType.List:\n component = new Components.List(def, options)\n break\n\n case ComponentType.Markdown:\n component = new Components.Markdown(def, options)\n break\n\n case ComponentType.MultilineTextField:\n component = new Components.MultilineTextField(def, options)\n break\n\n case ComponentType.NumberField:\n component = new Components.NumberField(def, options)\n break\n\n case ComponentType.RadiosField:\n component = new Components.RadiosField(def, options)\n break\n\n case ComponentType.SelectField:\n component = new Components.SelectField(def, options)\n break\n\n case ComponentType.TelephoneNumberField:\n component = new Components.TelephoneNumberField(def, options)\n break\n\n case ComponentType.TextField:\n component = new Components.TextField(def, options)\n break\n\n case ComponentType.UkAddressField:\n component = new Components.UkAddressField(def, options)\n break\n\n case ComponentType.YesNoField:\n component = new Components.YesNoField(def, options)\n break\n\n case ComponentType.MonthYearField:\n component = new Components.MonthYearField(def, options)\n break\n\n case ComponentType.FileUploadField:\n component = new Components.FileUploadField(def, options)\n break\n }\n\n if (typeof component === 'undefined') {\n throw new Error(`Component type ${def.type} does not exist`)\n }\n\n return component\n}\n\n/**\n * Get formatted answer for a field\n */\nexport function getAnswer(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'data' // Submission data\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n // Use escaped display text for GOV.UK Notify emails\n if (options.format === 'email') {\n return getAnswerMarkdown(field, state, { format: 'email' })\n }\n\n // Use context value for submission data\n if (options.format === 'data') {\n const context = field.getContextValueFromState(state)\n return context?.toString() ?? ''\n }\n\n // Use display HTML for check answers summary (multi line)\n if (\n field instanceof ListFormComponent ||\n field instanceof Components.MultilineTextField ||\n field instanceof Components.UkAddressField\n ) {\n return markdown\n .parse(getAnswerMarkdown(field, state), { async: false })\n .trim()\n }\n\n // Use display text for check answers summary (single line)\n return field.getDisplayStringFromState(state)\n}\n\n/**\n * Get formatted answer for a field (Markdown only)\n */\nexport function getAnswerMarkdown(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n const answer = field.getDisplayStringFromState(state)\n\n // Use escaped display text\n let answerEscaped = `${escapeMarkdown(answer)}\\n`\n\n if (field instanceof Components.FileUploadField) {\n const files = field.getFormValueFromState(state)\n\n // Skip empty files\n if (!files?.length) {\n return answerEscaped\n }\n\n answerEscaped = `${escapeMarkdown(answer)}:\\n\\n`\n\n // Append bullet points\n answerEscaped += files\n .map(({ status }) => {\n const { file } = status.form\n const filename = escapeMarkdown(file.filename)\n return `* [${filename}](${designerUrl}/file-download/${file.fileId})\\n`\n })\n .join('')\n } else if (field instanceof ListFormComponent) {\n const values = [field.getContextValueFromState(state)].flat()\n const items = field.items.filter(({ value }) => values.includes(value))\n\n // Skip empty values\n if (!items.length) {\n return answerEscaped\n }\n\n answerEscaped = ''\n\n // Append bullet points\n answerEscaped += items\n .map((item) => {\n const label = escapeMarkdown(item.text)\n const value = escapeMarkdown(`(${item.value})`)\n\n let line = label\n\n // Prepend bullet points for checkboxes only\n if (field instanceof Components.CheckboxesField) {\n line = `* ${line}`\n }\n\n // Append raw values in parentheses\n // e.g. `* None of the above (false)`\n return options.format === 'email' &&\n `${item.value}`.toLowerCase() !== item.text.toLowerCase()\n ? `${line} ${value}\\n`\n : `${line}\\n`\n })\n .join('')\n } else if (field instanceof Components.MultilineTextField) {\n // Preserve Multiline text new lines\n answerEscaped = answer\n .split(/(?:\\r?\\n)+/)\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (field instanceof Components.UkAddressField) {\n // Format UK addresses into new lines\n answerEscaped = (field.getContextValueFromState(state) ?? [])\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n }\n\n return answerEscaped\n}\n"],"mappings":"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","ignoreList":[]}
@@ -4,20 +4,35 @@ import { FormStatus } from "../../../../routes/types.js";
4
4
  export function format(context, items, model, submitResponse, formStatus, formMetadata) {
5
5
  const v2DataString = machineV2(context, items, model, submitResponse, formStatus);
6
6
  const v2DataParsed = JSON.parse(v2DataString);
7
+ const csvFiles = extractCsvFiles(submitResponse);
8
+ const transformedData = v2DataParsed.data;
9
+ const meta = {
10
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
11
+ timestamp: new Date(),
12
+ referenceNumber: context.referenceNumber,
13
+ formName: model.name,
14
+ formId: formMetadata?.id ?? '',
15
+ formSlug: formMetadata?.slug ?? '',
16
+ status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,
17
+ isPreview: formStatus.isPreview,
18
+ notificationEmail: formMetadata?.notificationEmail ?? ''
19
+ };
20
+ const data = transformedData;
21
+ const result = {
22
+ files: csvFiles
23
+ };
7
24
  const payload = {
8
- meta: {
9
- schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
10
- timestamp: new Date(),
11
- referenceNumber: context.referenceNumber,
12
- formName: model.name,
13
- formId: formMetadata?.id ?? '',
14
- formSlug: formMetadata?.slug ?? '',
15
- status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,
16
- isPreview: formStatus.isPreview,
17
- notificationEmail: formMetadata?.notificationEmail ?? ''
18
- },
19
- data: v2DataParsed.data
25
+ meta,
26
+ data,
27
+ result
20
28
  };
21
29
  return JSON.stringify(payload);
22
30
  }
31
+ function extractCsvFiles(submitResponse) {
32
+ const result = submitResponse.result;
33
+ return {
34
+ main: result.files?.main ?? '',
35
+ repeaters: result.files?.repeaters ?? {}
36
+ };
37
+ }
23
38
  //# sourceMappingURL=v1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v1.js","names":["format","machineV2","FormAdapterSubmissionSchemaVersion","FormStatus","context","items","model","submitResponse","formStatus","formMetadata","v2DataString","v2DataParsed","JSON","parse","payload","meta","schemaVersion","V1","timestamp","Date","referenceNumber","formName","name","formId","id","formSlug","slug","status","isPreview","Draft","Live","notificationEmail","data","stringify"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/adapter/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { format as machineV2 } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessagePayload,\n type FormContext\n} from '~/src/server/plugins/engine/types.js'\nimport { FormStatus } from '~/src/server/routes/types.js'\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n formMetadata?: FormMetadata\n): string {\n const v2DataString = machineV2(\n context,\n items,\n model,\n submitResponse,\n formStatus\n )\n const v2DataParsed = JSON.parse(v2DataString) as {\n data: FormAdapterSubmissionMessageData\n }\n\n const payload: FormAdapterSubmissionMessagePayload = {\n meta: {\n schemaVersion: FormAdapterSubmissionSchemaVersion.V1,\n timestamp: new Date(),\n referenceNumber: context.referenceNumber,\n formName: model.name,\n formId: formMetadata?.id ?? '',\n formSlug: formMetadata?.slug ?? '',\n status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,\n isPreview: formStatus.isPreview,\n notificationEmail: formMetadata?.notificationEmail ?? ''\n },\n data: v2DataParsed.data\n }\n\n return JSON.stringify(payload)\n}\n"],"mappings":"AAQA,SAASA,MAAM,IAAIC,SAAS;AAC5B,SAASC,kCAAkC;AAM3C,SAASC,UAAU;AAEnB,OAAO,SAASH,MAAMA,CACpBI,OAAoB,EACpBC,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9CC,YAA2B,EACnB;EACR,MAAMC,YAAY,GAAGT,SAAS,CAC5BG,OAAO,EACPC,KAAK,EACLC,KAAK,EACLC,cAAc,EACdC,UACF,CAAC;EACD,MAAMG,YAAY,GAAGC,IAAI,CAACC,KAAK,CAACH,YAAY,CAE3C;EAED,MAAMI,OAA4C,GAAG;IACnDC,IAAI,EAAE;MACJC,aAAa,EAAEd,kCAAkC,CAACe,EAAE;MACpDC,SAAS,EAAE,IAAIC,IAAI,CAAC,CAAC;MACrBC,eAAe,EAAEhB,OAAO,CAACgB,eAAe;MACxCC,QAAQ,EAAEf,KAAK,CAACgB,IAAI;MACpBC,MAAM,EAAEd,YAAY,EAAEe,EAAE,IAAI,EAAE;MAC9BC,QAAQ,EAAEhB,YAAY,EAAEiB,IAAI,IAAI,EAAE;MAClCC,MAAM,EAAEnB,UAAU,CAACoB,SAAS,GAAGzB,UAAU,CAAC0B,KAAK,GAAG1B,UAAU,CAAC2B,IAAI;MACjEF,SAAS,EAAEpB,UAAU,CAACoB,SAAS;MAC/BG,iBAAiB,EAAEtB,YAAY,EAAEsB,iBAAiB,IAAI;IACxD,CAAC;IACDC,IAAI,EAAErB,YAAY,CAACqB;EACrB,CAAC;EAED,OAAOpB,IAAI,CAACqB,SAAS,CAACnB,OAAO,CAAC;AAChC","ignoreList":[]}
1
+ {"version":3,"file":"v1.js","names":["format","machineV2","FormAdapterSubmissionSchemaVersion","FormStatus","context","items","model","submitResponse","formStatus","formMetadata","v2DataString","v2DataParsed","JSON","parse","csvFiles","extractCsvFiles","transformedData","data","meta","schemaVersion","V1","timestamp","Date","referenceNumber","formName","name","formId","id","formSlug","slug","status","isPreview","Draft","Live","notificationEmail","result","files","payload","stringify","main","repeaters"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/adapter/v1.ts"],"sourcesContent":["import {\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { format as machineV2 } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult,\n type FormContext\n} from '~/src/server/plugins/engine/types.js'\nimport { FormStatus } from '~/src/server/routes/types.js'\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>,\n formMetadata?: FormMetadata\n): string {\n const v2DataString = machineV2(\n context,\n items,\n model,\n submitResponse,\n formStatus\n )\n const v2DataParsed = JSON.parse(v2DataString) as {\n data: FormAdapterSubmissionMessageData\n }\n\n const csvFiles = extractCsvFiles(submitResponse)\n\n const transformedData = v2DataParsed.data\n\n const meta: FormAdapterSubmissionMessageMeta = {\n schemaVersion: FormAdapterSubmissionSchemaVersion.V1,\n timestamp: new Date(),\n referenceNumber: context.referenceNumber,\n formName: model.name,\n formId: formMetadata?.id ?? '',\n formSlug: formMetadata?.slug ?? '',\n status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,\n isPreview: formStatus.isPreview,\n notificationEmail: formMetadata?.notificationEmail ?? ''\n }\n const data: FormAdapterSubmissionMessageData = transformedData\n\n const result: FormAdapterSubmissionMessageResult = {\n files: csvFiles\n }\n\n const payload: FormAdapterSubmissionMessagePayload = {\n meta,\n data,\n result\n }\n\n return JSON.stringify(payload)\n}\n\nfunction extractCsvFiles(\n submitResponse: SubmitResponsePayload\n): FormAdapterSubmissionMessageResult['files'] {\n const result =\n submitResponse.result as Partial<FormAdapterSubmissionMessageResult>\n\n return {\n main: result.files?.main ?? '',\n repeaters: result.files?.repeaters ?? {}\n }\n}\n"],"mappings":"AAQA,SAASA,MAAM,IAAIC,SAAS;AAC5B,SAASC,kCAAkC;AAQ3C,SAASC,UAAU;AAEnB,OAAO,SAASH,MAAMA,CACpBI,OAAoB,EACpBC,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9CC,YAA2B,EACnB;EACR,MAAMC,YAAY,GAAGT,SAAS,CAC5BG,OAAO,EACPC,KAAK,EACLC,KAAK,EACLC,cAAc,EACdC,UACF,CAAC;EACD,MAAMG,YAAY,GAAGC,IAAI,CAACC,KAAK,CAACH,YAAY,CAE3C;EAED,MAAMI,QAAQ,GAAGC,eAAe,CAACR,cAAc,CAAC;EAEhD,MAAMS,eAAe,GAAGL,YAAY,CAACM,IAAI;EAEzC,MAAMC,IAAsC,GAAG;IAC7CC,aAAa,EAAEjB,kCAAkC,CAACkB,EAAE;IACpDC,SAAS,EAAE,IAAIC,IAAI,CAAC,CAAC;IACrBC,eAAe,EAAEnB,OAAO,CAACmB,eAAe;IACxCC,QAAQ,EAAElB,KAAK,CAACmB,IAAI;IACpBC,MAAM,EAAEjB,YAAY,EAAEkB,EAAE,IAAI,EAAE;IAC9BC,QAAQ,EAAEnB,YAAY,EAAEoB,IAAI,IAAI,EAAE;IAClCC,MAAM,EAAEtB,UAAU,CAACuB,SAAS,GAAG5B,UAAU,CAAC6B,KAAK,GAAG7B,UAAU,CAAC8B,IAAI;IACjEF,SAAS,EAAEvB,UAAU,CAACuB,SAAS;IAC/BG,iBAAiB,EAAEzB,YAAY,EAAEyB,iBAAiB,IAAI;EACxD,CAAC;EACD,MAAMjB,IAAsC,GAAGD,eAAe;EAE9D,MAAMmB,MAA0C,GAAG;IACjDC,KAAK,EAAEtB;EACT,CAAC;EAED,MAAMuB,OAA4C,GAAG;IACnDnB,IAAI;IACJD,IAAI;IACJkB;EACF,CAAC;EAED,OAAOvB,IAAI,CAAC0B,SAAS,CAACD,OAAO,CAAC;AAChC;AAEA,SAAStB,eAAeA,CACtBR,cAAqC,EACQ;EAC7C,MAAM4B,MAAM,GACV5B,cAAc,CAAC4B,MAAqD;EAEtE,OAAO;IACLI,IAAI,EAAEJ,MAAM,CAACC,KAAK,EAAEG,IAAI,IAAI,EAAE;IAC9BC,SAAS,EAAEL,MAAM,CAACC,KAAK,EAAEI,SAAS,IAAI,CAAC;EACzC,CAAC;AACH","ignoreList":[]}
@@ -1,9 +1,6 @@
1
1
  import { type SubmitResponsePayload } from '@defra/forms-model';
2
- import { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js';
3
- import { type DatePartsState, type MonthYearState } from '~/src/server/plugins/engine/components/types.js';
4
2
  import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js';
5
3
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
6
4
  import { type DetailItem } from '~/src/server/plugins/engine/models/types.js';
7
- import { type FormContext, type FormPayload, type FormValue } from '~/src/server/plugins/engine/types.js';
5
+ import { type FormContext } from '~/src/server/plugins/engine/types.js';
8
6
  export declare function format(context: FormContext, items: DetailItem[], model: FormModel, _submitResponse: SubmitResponsePayload, _formStatus: ReturnType<typeof checkFormStatus>): string;
9
- export type RichFormValue = FormValue | FormPayload | DatePartsState | MonthYearState | UkAddressState;
@@ -38,7 +38,8 @@ export function format(context, items, model, _submitResponse, _formStatus) {
38
38
  * fileComponentName: [
39
39
  * {
40
40
  * fileId: '123-456-789',
41
- * link: 'https://forms-designer/file-download/123-456-789'
41
+ * fileName: 'example.pdf',
42
+ * userDownloadLink: 'https://forms-designer/file-download/123-456-789'
42
43
  * }
43
44
  * ]
44
45
  * }
@@ -93,19 +94,19 @@ function extractRepeaters(item) {
93
94
  * @returns the file upload data
94
95
  */
95
96
  function extractFileUploads(item) {
96
- const fileUploadState = item.field.getContextValueFromState(item.state) ?? [];
97
- return fileUploadState.map(fileId => {
97
+ const fileUploadState = item.field.getFormValueFromState(item.state) ?? [];
98
+ return fileUploadState.map(fileState => {
99
+ const {
100
+ file
101
+ } = fileState.status.form;
98
102
  return {
99
- fileId,
100
- userDownloadLink: `${designerUrl}/file-download/${fileId}`
103
+ fileId: file.fileId,
104
+ fileName: file.filename,
105
+ userDownloadLink: `${designerUrl}/file-download/${file.fileId}`
101
106
  };
102
107
  });
103
108
  }
104
109
  function isFileUploadFieldItem(item) {
105
110
  return item.field instanceof FileUploadField;
106
111
  }
107
-
108
- /**
109
- * A detail item specifically for files
110
- */
111
112
  //# sourceMappingURL=v2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v2.js","names":["config","FileUploadField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","now","Date","categorisedData","categoriseData","data","meta","schemaVersion","timestamp","toISOString","definition","def","referenceNumber","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","state","extractRepeaters","isFileUploadFieldItem","extractFileUploads","field","getFormValueFromState","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","getContextValueFromState","map","fileId","userDownloadLink"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v2.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type FormPayload,\n type FormValue\n} from '~/src/server/plugins/engine/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const data = {\n meta: {\n schemaVersion: '2',\n timestamp: now.toISOString(),\n definition: model.def,\n referenceNumber: context.referenceNumber\n },\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * textComponentName: 'componentValue'\n * },\n * {\n * richComponentName: { foo: 'bar', 'baz': true }\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * link: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * }\n * }\n */\nfunction categoriseData(items: DetailItem[]) {\n const output: {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, Record<string, string>[]>\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n const { name, state } = item\n\n if ('subItems' in item) {\n output.repeaters[name] = extractRepeaters(item)\n } else if (isFileUploadFieldItem(item)) {\n output.files[name] = extractFileUploads(item)\n } else {\n output.main[name] = item.field.getFormValueFromState(state)\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat) {\n const repeaters: Record<string, RichFormValue>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, RichFormValue> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n const { field, state } = repeaterComponent\n\n outputRepeaterItem[repeaterComponent.name] =\n field.getFormValueFromState(state)\n })\n\n repeaters.push(outputRepeaterItem)\n })\n\n return repeaters\n}\n\n/**\n * Returns the \"files\" section of the response body\n * @param item - the file upload item in the form\n * @returns the file upload data\n */\nfunction extractFileUploads(item: FileUploadFieldDetailitem) {\n const fileUploadState = item.field.getContextValueFromState(item.state) ?? []\n\n return fileUploadState.map((fileId) => {\n return {\n fileId,\n userDownloadLink: `${designerUrl}/file-download/${fileId}`\n }\n })\n}\n\nfunction isFileUploadFieldItem(\n item: DetailItemField\n): item is FileUploadFieldDetailitem {\n return item.field instanceof FileUploadField\n}\n\n/**\n * A detail item specifically for files\n */\ntype FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\n\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"v2.js","names":["config","FileUploadField","designerUrl","get","format","context","items","model","_submitResponse","_formStatus","now","Date","categorisedData","categoriseData","data","meta","schemaVersion","timestamp","toISOString","definition","def","referenceNumber","body","JSON","stringify","output","main","repeaters","files","forEach","item","name","state","extractRepeaters","isFileUploadFieldItem","extractFileUploads","field","getFormValueFromState","subItems","inputRepeaterItem","outputRepeaterItem","repeaterComponent","push","fileUploadState","map","fileState","file","status","form","fileId","fileName","filename","userDownloadLink"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/machine/v2.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FileUploadFieldDetailitem,\n type FormAdapterFile,\n type FormContext,\n type RichFormValue\n} from '~/src/server/plugins/engine/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n context: FormContext,\n items: DetailItem[],\n model: FormModel,\n _submitResponse: SubmitResponsePayload,\n _formStatus: ReturnType<typeof checkFormStatus>\n) {\n const now = new Date()\n\n const categorisedData = categoriseData(items)\n\n const data = {\n meta: {\n schemaVersion: '2',\n timestamp: now.toISOString(),\n definition: model.def,\n referenceNumber: context.referenceNumber\n },\n data: categorisedData\n }\n\n const body = JSON.stringify(data)\n\n return body\n}\n\n/**\n * Categories the form submission data into the \"main\" body and \"repeaters\".\n *\n * {\n * main: {\n * componentName: 'componentValue',\n * },\n * repeaters: {\n * repeaterName: [\n * {\n * textComponentName: 'componentValue'\n * },\n * {\n * richComponentName: { foo: 'bar', 'baz': true }\n * }\n * ]\n * },\n * files: {\n * fileComponentName: [\n * {\n * fileId: '123-456-789',\n * fileName: 'example.pdf',\n * userDownloadLink: 'https://forms-designer/file-download/123-456-789'\n * }\n * ]\n * }\n * }\n */\nfunction categoriseData(items: DetailItem[]) {\n const output: {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<\n string,\n { fileId: string; fileName: string; userDownloadLink: string }[]\n >\n } = { main: {}, repeaters: {}, files: {} }\n\n items.forEach((item) => {\n const { name, state } = item\n\n if ('subItems' in item) {\n output.repeaters[name] = extractRepeaters(item)\n } else if (isFileUploadFieldItem(item)) {\n output.files[name] = extractFileUploads(item)\n } else {\n output.main[name] = item.field.getFormValueFromState(state)\n }\n })\n\n return output\n}\n\n/**\n * Returns the \"repeaters\" section of the response body\n * @param item - the repeater item\n * @returns the repeater item\n */\nfunction extractRepeaters(item: DetailItemRepeat) {\n const repeaters: Record<string, RichFormValue>[] = []\n\n item.subItems.forEach((inputRepeaterItem) => {\n const outputRepeaterItem: Record<string, RichFormValue> = {}\n\n inputRepeaterItem.forEach((repeaterComponent) => {\n const { field, state } = repeaterComponent\n\n outputRepeaterItem[repeaterComponent.name] =\n field.getFormValueFromState(state)\n })\n\n repeaters.push(outputRepeaterItem)\n })\n\n return repeaters\n}\n\n/**\n * Returns the \"files\" section of the response body\n * @param item - the file upload item in the form\n * @returns the file upload data\n */\nfunction extractFileUploads(\n item: FileUploadFieldDetailitem\n): FormAdapterFile[] {\n const fileUploadState = item.field.getFormValueFromState(item.state) ?? []\n\n return fileUploadState.map((fileState) => {\n const { file } = fileState.status.form\n return {\n fileId: file.fileId,\n fileName: file.filename,\n userDownloadLink: `${designerUrl}/file-download/${file.fileId}`\n }\n })\n}\n\nfunction isFileUploadFieldItem(\n item: DetailItemField\n): item is FileUploadFieldDetailitem {\n return item.field instanceof FileUploadField\n}\n"],"mappings":"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","ignoreList":[]}
@@ -6,6 +6,5 @@ export type { UkAddressState } from '~/src/server/plugins/engine/components/UkAd
6
6
  export type { FormParams, FormQuery, FormRequest, FormRequestPayload, FormRequestPayloadRefs, FormRequestRefs } from '~/src/server/routes/types.js';
7
7
  export { FormAction, FormStatus } from '~/src/server/routes/types.js';
8
8
  export type { FormSubmissionService, FormsService, OutputService, RouteConfig, Services } from '~/src/server/types.js';
9
- export type { RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js';
10
9
  export * from '~/src/server/plugins/engine/types/schema.js';
11
10
  export { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["FileStatus","UploadStatus","FormAction","FormStatus","FormAdapterSubmissionSchemaVersion"],"sources":["../../../../../src/server/plugins/engine/types/index.ts"],"sourcesContent":["export type {\n CheckAnswers,\n ErrorMessageTemplate,\n ErrorMessageTemplateList,\n FeaturedFormPageViewModel,\n FileState,\n FilterFunction,\n FormAdapterSubmissionMessage,\n FormAdapterSubmissionMessageData,\n FormAdapterSubmissionMessageMeta,\n FormAdapterSubmissionMessageMetaSerialised,\n FormAdapterSubmissionMessagePayload,\n FormAdapterSubmissionService,\n FormContext,\n FormContextRequest,\n FormPageViewModel,\n FormPayload,\n FormPayloadParams,\n FormState,\n FormStateValue,\n FormSubmissionError,\n FormSubmissionState,\n FormValidationResult,\n FormValue,\n GlobalFunction,\n ItemDeletePageViewModel,\n OnRequestCallback,\n PageViewModel,\n PageViewModelBase,\n PluginOptions,\n PreparePageEventRequestOptions,\n RepeatItemState,\n RepeatListState,\n RepeaterSummaryPageViewModel,\n SummaryList,\n SummaryListAction,\n SummaryListRow,\n TempFileState,\n UploadInitiateResponse,\n UploadStatusFileResponse,\n UploadStatusResponse\n} from '~/src/server/plugins/engine/types.js'\n\nexport { FileStatus, UploadStatus } from '~/src/server/plugins/engine/types.js'\n\nexport type {\n Detail,\n DetailItem,\n DetailItemBase,\n DetailItemField,\n DetailItemRepeat,\n ExecutableCondition\n} from '~/src/server/plugins/engine/models/types.js'\n\nexport type {\n BackLink,\n ComponentText,\n ComponentViewModel,\n Content,\n DateInputItem,\n DatePartsState,\n Label,\n ListItem,\n ListItemLabel,\n MonthYearState,\n ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\n\nexport type { UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\n\nexport type {\n FormParams,\n FormQuery,\n FormRequest,\n FormRequestPayload,\n FormRequestPayloadRefs,\n FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport type {\n FormSubmissionService,\n FormsService,\n OutputService,\n RouteConfig,\n Services\n} from '~/src/server/types.js'\n\nexport type { RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'\n\nexport * from '~/src/server/plugins/engine/types/schema.js'\nexport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\n"],"mappings":"AA2CA,SAASA,UAAU,EAAEC,YAAY;AAoCjC,SAASC,UAAU,EAAEC,UAAU;AAY/B;AACA,SAASC,kCAAkC","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["FileStatus","UploadStatus","FormAction","FormStatus","FormAdapterSubmissionSchemaVersion"],"sources":["../../../../../src/server/plugins/engine/types/index.ts"],"sourcesContent":["export type {\n CheckAnswers,\n ErrorMessageTemplate,\n ErrorMessageTemplateList,\n FeaturedFormPageViewModel,\n FileState,\n FilterFunction,\n FormAdapterSubmissionMessage,\n FormAdapterSubmissionMessageData,\n FormAdapterSubmissionMessageMeta,\n FormAdapterSubmissionMessageMetaSerialised,\n FormAdapterSubmissionMessagePayload,\n FormAdapterSubmissionService,\n FormContext,\n FormContextRequest,\n FormPageViewModel,\n FormPayload,\n FormPayloadParams,\n FormState,\n FormStateValue,\n FormSubmissionError,\n FormSubmissionState,\n FormValidationResult,\n FormValue,\n GlobalFunction,\n ItemDeletePageViewModel,\n OnRequestCallback,\n PageViewModel,\n PageViewModelBase,\n PluginOptions,\n PreparePageEventRequestOptions,\n RepeatItemState,\n RepeatListState,\n RepeaterSummaryPageViewModel,\n SummaryList,\n SummaryListAction,\n SummaryListRow,\n TempFileState,\n UploadInitiateResponse,\n UploadStatusFileResponse,\n UploadStatusResponse\n} from '~/src/server/plugins/engine/types.js'\n\nexport { FileStatus, UploadStatus } from '~/src/server/plugins/engine/types.js'\n\nexport type {\n Detail,\n DetailItem,\n DetailItemBase,\n DetailItemField,\n DetailItemRepeat,\n ExecutableCondition\n} from '~/src/server/plugins/engine/models/types.js'\n\nexport type {\n BackLink,\n ComponentText,\n ComponentViewModel,\n Content,\n DateInputItem,\n DatePartsState,\n Label,\n ListItem,\n ListItemLabel,\n MonthYearState,\n ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\n\nexport type { UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\n\nexport type {\n FormParams,\n FormQuery,\n FormRequest,\n FormRequestPayload,\n FormRequestPayloadRefs,\n FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport { FormAction, FormStatus } from '~/src/server/routes/types.js'\n\nexport type {\n FormSubmissionService,\n FormsService,\n OutputService,\n RouteConfig,\n Services\n} from '~/src/server/types.js'\n\nexport * from '~/src/server/plugins/engine/types/schema.js'\nexport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\n"],"mappings":"AA2CA,SAASA,UAAU,EAAEC,YAAY;AAoCjC,SAASC,UAAU,EAAEC,UAAU;AAU/B;AACA,SAASC,kCAAkC","ignoreList":[]}
@@ -1,5 +1,6 @@
1
1
  import Joi from 'joi';
2
- import { type FormAdapterSubmissionMessageData, type FormAdapterSubmissionMessageMeta, type FormAdapterSubmissionMessagePayload } from '~/src/server/plugins/engine/types.js';
2
+ import { type FormAdapterSubmissionMessageData, type FormAdapterSubmissionMessageMeta, type FormAdapterSubmissionMessagePayload, type FormAdapterSubmissionMessageResult } from '~/src/server/plugins/engine/types.js';
3
3
  export declare const formAdapterSubmissionMessageMetaSchema: Joi.ObjectSchema<FormAdapterSubmissionMessageMeta>;
4
4
  export declare const formAdapterSubmissionMessageDataSchema: Joi.ObjectSchema<FormAdapterSubmissionMessageData>;
5
+ export declare const formAdapterSubmissionMessageResultSchema: Joi.ObjectSchema<FormAdapterSubmissionMessageResult>;
5
6
  export declare const formAdapterSubmissionMessagePayloadSchema: Joi.ObjectSchema<FormAdapterSubmissionMessagePayload>;
@@ -15,10 +15,21 @@ export const formAdapterSubmissionMessageMetaSchema = Joi.object().keys({
15
15
  export const formAdapterSubmissionMessageDataSchema = Joi.object().keys({
16
16
  main: Joi.object(),
17
17
  repeaters: Joi.object(),
18
- files: Joi.object()
18
+ files: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.object().keys({
19
+ fileName: Joi.string().required(),
20
+ fileId: Joi.string().required(),
21
+ userDownloadLink: Joi.string().required()
22
+ })))
23
+ });
24
+ export const formAdapterSubmissionMessageResultSchema = Joi.object().keys({
25
+ files: Joi.object().keys({
26
+ main: Joi.string().required(),
27
+ repeaters: Joi.object()
28
+ }).required()
19
29
  });
20
30
  export const formAdapterSubmissionMessagePayloadSchema = Joi.object().keys({
21
31
  meta: formAdapterSubmissionMessageMetaSchema.required(),
22
- data: formAdapterSubmissionMessageDataSchema.required()
32
+ data: formAdapterSubmissionMessageDataSchema.required(),
33
+ result: formAdapterSubmissionMessageResultSchema.required()
23
34
  });
24
35
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","names":["FormStatus","idSchema","notificationEmailAddressSchema","slugSchema","titleSchema","Joi","FormAdapterSubmissionSchemaVersion","formAdapterSubmissionMessageMetaSchema","object","keys","schemaVersion","string","valid","Object","values","timestamp","date","required","referenceNumber","formName","formId","formSlug","status","isPreview","boolean","notificationEmail","formAdapterSubmissionMessageDataSchema","main","repeaters","files","formAdapterSubmissionMessagePayloadSchema","meta","data"],"sources":["../../../../../src/server/plugins/engine/types/schema.ts"],"sourcesContent":["import {\n FormStatus,\n idSchema,\n notificationEmailAddressSchema,\n slugSchema,\n titleSchema\n} from '@defra/forms-model'\nimport Joi from 'joi'\n\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload\n} from '~/src/server/plugins/engine/types.js'\n\nexport const formAdapterSubmissionMessageMetaSchema =\n Joi.object<FormAdapterSubmissionMessageMeta>().keys({\n schemaVersion: Joi.string().valid(\n ...Object.values(FormAdapterSubmissionSchemaVersion)\n ),\n timestamp: Joi.date().required(),\n referenceNumber: Joi.string().required(),\n formName: titleSchema,\n formId: idSchema,\n formSlug: slugSchema,\n status: Joi.string()\n .valid(...Object.values(FormStatus))\n .required(),\n isPreview: Joi.boolean().required(),\n notificationEmail: notificationEmailAddressSchema.required()\n })\n\nexport const formAdapterSubmissionMessageDataSchema =\n Joi.object<FormAdapterSubmissionMessageData>().keys({\n main: Joi.object(),\n repeaters: Joi.object(),\n files: Joi.object()\n })\n\nexport const formAdapterSubmissionMessagePayloadSchema =\n Joi.object<FormAdapterSubmissionMessagePayload>().keys({\n meta: formAdapterSubmissionMessageMetaSchema.required(),\n data: formAdapterSubmissionMessageDataSchema.required()\n })\n"],"mappings":"AAAA,SACEA,UAAU,EACVC,QAAQ,EACRC,8BAA8B,EAC9BC,UAAU,EACVC,WAAW,QACN,oBAAoB;AAC3B,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,kCAAkC;AAO3C,OAAO,MAAMC,sCAAsC,GACjDF,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDC,aAAa,EAAEL,GAAG,CAACM,MAAM,CAAC,CAAC,CAACC,KAAK,CAC/B,GAAGC,MAAM,CAACC,MAAM,CAACR,kCAAkC,CACrD,CAAC;EACDS,SAAS,EAAEV,GAAG,CAACW,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;EAChCC,eAAe,EAAEb,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;EACxCE,QAAQ,EAAEf,WAAW;EACrBgB,MAAM,EAAEnB,QAAQ;EAChBoB,QAAQ,EAAElB,UAAU;EACpBmB,MAAM,EAAEjB,GAAG,CAACM,MAAM,CAAC,CAAC,CACjBC,KAAK,CAAC,GAAGC,MAAM,CAACC,MAAM,CAACd,UAAU,CAAC,CAAC,CACnCiB,QAAQ,CAAC,CAAC;EACbM,SAAS,EAAElB,GAAG,CAACmB,OAAO,CAAC,CAAC,CAACP,QAAQ,CAAC,CAAC;EACnCQ,iBAAiB,EAAEvB,8BAA8B,CAACe,QAAQ,CAAC;AAC7D,CAAC,CAAC;AAEJ,OAAO,MAAMS,sCAAsC,GACjDrB,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDkB,IAAI,EAAEtB,GAAG,CAACG,MAAM,CAAC,CAAC;EAClBoB,SAAS,EAAEvB,GAAG,CAACG,MAAM,CAAC,CAAC;EACvBqB,KAAK,EAAExB,GAAG,CAACG,MAAM,CAAC;AACpB,CAAC,CAAC;AAEJ,OAAO,MAAMsB,yCAAyC,GACpDzB,GAAG,CAACG,MAAM,CAAsC,CAAC,CAACC,IAAI,CAAC;EACrDsB,IAAI,EAAExB,sCAAsC,CAACU,QAAQ,CAAC,CAAC;EACvDe,IAAI,EAAEN,sCAAsC,CAACT,QAAQ,CAAC;AACxD,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"schema.js","names":["FormStatus","idSchema","notificationEmailAddressSchema","slugSchema","titleSchema","Joi","FormAdapterSubmissionSchemaVersion","formAdapterSubmissionMessageMetaSchema","object","keys","schemaVersion","string","valid","Object","values","timestamp","date","required","referenceNumber","formName","formId","formSlug","status","isPreview","boolean","notificationEmail","formAdapterSubmissionMessageDataSchema","main","repeaters","files","pattern","array","items","fileName","fileId","userDownloadLink","formAdapterSubmissionMessageResultSchema","formAdapterSubmissionMessagePayloadSchema","meta","data","result"],"sources":["../../../../../src/server/plugins/engine/types/schema.ts"],"sourcesContent":["import {\n FormStatus,\n idSchema,\n notificationEmailAddressSchema,\n slugSchema,\n titleSchema\n} from '@defra/forms-model'\nimport Joi from 'joi'\n\nimport { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'\nimport {\n type FormAdapterSubmissionMessageData,\n type FormAdapterSubmissionMessageMeta,\n type FormAdapterSubmissionMessagePayload,\n type FormAdapterSubmissionMessageResult\n} from '~/src/server/plugins/engine/types.js'\n\nexport const formAdapterSubmissionMessageMetaSchema =\n Joi.object<FormAdapterSubmissionMessageMeta>().keys({\n schemaVersion: Joi.string().valid(\n ...Object.values(FormAdapterSubmissionSchemaVersion)\n ),\n timestamp: Joi.date().required(),\n referenceNumber: Joi.string().required(),\n formName: titleSchema,\n formId: idSchema,\n formSlug: slugSchema,\n status: Joi.string()\n .valid(...Object.values(FormStatus))\n .required(),\n isPreview: Joi.boolean().required(),\n notificationEmail: notificationEmailAddressSchema.required()\n })\n\nexport const formAdapterSubmissionMessageDataSchema =\n Joi.object<FormAdapterSubmissionMessageData>().keys({\n main: Joi.object(),\n repeaters: Joi.object(),\n files: Joi.object().pattern(\n Joi.string(),\n Joi.array().items(\n Joi.object().keys({\n fileName: Joi.string().required(),\n fileId: Joi.string().required(),\n userDownloadLink: Joi.string().required()\n })\n )\n )\n })\n\nexport const formAdapterSubmissionMessageResultSchema =\n Joi.object<FormAdapterSubmissionMessageResult>().keys({\n files: Joi.object()\n .keys({\n main: Joi.string().required(),\n repeaters: Joi.object()\n })\n .required()\n })\n\nexport const formAdapterSubmissionMessagePayloadSchema =\n Joi.object<FormAdapterSubmissionMessagePayload>().keys({\n meta: formAdapterSubmissionMessageMetaSchema.required(),\n data: formAdapterSubmissionMessageDataSchema.required(),\n result: formAdapterSubmissionMessageResultSchema.required()\n })\n"],"mappings":"AAAA,SACEA,UAAU,EACVC,QAAQ,EACRC,8BAA8B,EAC9BC,UAAU,EACVC,WAAW,QACN,oBAAoB;AAC3B,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,kCAAkC;AAQ3C,OAAO,MAAMC,sCAAsC,GACjDF,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDC,aAAa,EAAEL,GAAG,CAACM,MAAM,CAAC,CAAC,CAACC,KAAK,CAC/B,GAAGC,MAAM,CAACC,MAAM,CAACR,kCAAkC,CACrD,CAAC;EACDS,SAAS,EAAEV,GAAG,CAACW,IAAI,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;EAChCC,eAAe,EAAEb,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;EACxCE,QAAQ,EAAEf,WAAW;EACrBgB,MAAM,EAAEnB,QAAQ;EAChBoB,QAAQ,EAAElB,UAAU;EACpBmB,MAAM,EAAEjB,GAAG,CAACM,MAAM,CAAC,CAAC,CACjBC,KAAK,CAAC,GAAGC,MAAM,CAACC,MAAM,CAACd,UAAU,CAAC,CAAC,CACnCiB,QAAQ,CAAC,CAAC;EACbM,SAAS,EAAElB,GAAG,CAACmB,OAAO,CAAC,CAAC,CAACP,QAAQ,CAAC,CAAC;EACnCQ,iBAAiB,EAAEvB,8BAA8B,CAACe,QAAQ,CAAC;AAC7D,CAAC,CAAC;AAEJ,OAAO,MAAMS,sCAAsC,GACjDrB,GAAG,CAACG,MAAM,CAAmC,CAAC,CAACC,IAAI,CAAC;EAClDkB,IAAI,EAAEtB,GAAG,CAACG,MAAM,CAAC,CAAC;EAClBoB,SAAS,EAAEvB,GAAG,CAACG,MAAM,CAAC,CAAC;EACvBqB,KAAK,EAAExB,GAAG,CAACG,MAAM,CAAC,CAAC,CAACsB,OAAO,CACzBzB,GAAG,CAACM,MAAM,CAAC,CAAC,EACZN,GAAG,CAAC0B,KAAK,CAAC,CAAC,CAACC,KAAK,CACf3B,GAAG,CAACG,MAAM,CAAC,CAAC,CAACC,IAAI,CAAC;IAChBwB,QAAQ,EAAE5B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IACjCiB,MAAM,EAAE7B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IAC/BkB,gBAAgB,EAAE9B,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC;EAC1C,CAAC,CACH,CACF;AACF,CAAC,CAAC;AAEJ,OAAO,MAAMmB,wCAAwC,GACnD/B,GAAG,CAACG,MAAM,CAAqC,CAAC,CAACC,IAAI,CAAC;EACpDoB,KAAK,EAAExB,GAAG,CAACG,MAAM,CAAC,CAAC,CAChBC,IAAI,CAAC;IACJkB,IAAI,EAAEtB,GAAG,CAACM,MAAM,CAAC,CAAC,CAACM,QAAQ,CAAC,CAAC;IAC7BW,SAAS,EAAEvB,GAAG,CAACG,MAAM,CAAC;EACxB,CAAC,CAAC,CACDS,QAAQ,CAAC;AACd,CAAC,CAAC;AAEJ,OAAO,MAAMoB,yCAAyC,GACpDhC,GAAG,CAACG,MAAM,CAAsC,CAAC,CAACC,IAAI,CAAC;EACrD6B,IAAI,EAAE/B,sCAAsC,CAACU,QAAQ,CAAC,CAAC;EACvDsB,IAAI,EAAEb,sCAAsC,CAACT,QAAQ,CAAC,CAAC;EACvDuB,MAAM,EAAEJ,wCAAwC,CAACnB,QAAQ,CAAC;AAC5D,CAAC,CAAC","ignoreList":[]}
@@ -1,10 +1,12 @@
1
1
  import { type ComponentDef, type Event, type FormDefinition, type FormMetadata, type Item, type List, type Page } from '@defra/forms-model';
2
2
  import { type PluginProperties, type Request } from '@hapi/hapi';
3
3
  import { type JoiExpression, type ValidationErrorItem } from 'joi';
4
+ import { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js';
4
5
  import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js';
5
- import { type BackLink, type ComponentText, type ComponentViewModel } from '~/src/server/plugins/engine/components/types.js';
6
+ import { type FileUploadField } from '~/src/server/plugins/engine/components/index.js';
7
+ import { type BackLink, type ComponentText, type ComponentViewModel, type DatePartsState, type MonthYearState } from '~/src/server/plugins/engine/components/types.js';
6
8
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
7
- import { type RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js';
9
+ import { type DetailItemField } from '~/src/server/plugins/engine/models/types.js';
8
10
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
9
11
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
10
12
  import { type FileStatus, type FormAdapterSubmissionSchemaVersion, type UploadStatus } from '~/src/server/plugins/engine/types/enums.js';
@@ -294,14 +296,33 @@ export type FormAdapterSubmissionMessageMetaSerialised = Omit<FormAdapterSubmiss
294
296
  status: string;
295
297
  timestamp: string;
296
298
  };
299
+ export interface FormAdapterFile {
300
+ fileName: string;
301
+ fileId: string;
302
+ userDownloadLink: string;
303
+ }
304
+ export interface FormAdapterSubmissionMessageResult {
305
+ files: {
306
+ main: string;
307
+ repeaters: Record<string, string>;
308
+ };
309
+ }
310
+ /**
311
+ * A detail item specifically for files
312
+ */
313
+ export type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {
314
+ field: FileUploadField;
315
+ };
316
+ export type RichFormValue = FormValue | FormPayload | DatePartsState | MonthYearState | UkAddressState;
297
317
  export interface FormAdapterSubmissionMessageData {
298
318
  main: Record<string, RichFormValue>;
299
319
  repeaters: Record<string, Record<string, RichFormValue>[]>;
300
- files: Record<string, Record<string, string>[]>;
320
+ files: Record<string, FormAdapterFile[]>;
301
321
  }
302
322
  export interface FormAdapterSubmissionMessagePayload {
303
323
  meta: FormAdapterSubmissionMessageMeta;
304
324
  data: FormAdapterSubmissionMessageData;
325
+ result: FormAdapterSubmissionMessageResult;
305
326
  }
306
327
  export interface FormAdapterSubmissionMessage extends FormAdapterSubmissionMessagePayload {
307
328
  messageId: string;
@@ -39,4 +39,8 @@
39
39
  */
40
40
 
41
41
  export { FileStatus, UploadStatus } from "./types/enums.js";
42
+
43
+ /**
44
+ * A detail item specifically for files
45
+ */
42
46
  //# sourceMappingURL=types.js.map
@@ -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 FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.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 {\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 FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\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\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\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 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 | 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}\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\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}\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 allowSaveAndReturn: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\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: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndReturn?: {\n keyGenerator: (request: RequestType) => string\n sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister: (\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n }\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 baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\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}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, Record<string, string>[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAyCA;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;;AAmBA;AACA;AACA;AACA;;AAqGA,SACEA,UAAU,EACVC,YAAY","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 FormDefinition,\n type FormMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { type PluginProperties, type Request } from '@hapi/hapi'\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 { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/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 {\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 FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\ntype RequestType = Request | FormRequest | FormRequestPayload\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\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\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 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 | 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}\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\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}\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 allowSaveAndReturn: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\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: FormRequest | FormRequestPayload,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cacheName?: string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndReturn?: {\n keyGenerator: (request: RequestType) => string\n sessionHydrator: (request: RequestType) => Promise<FormSubmissionState>\n sessionPersister: (\n state: FormSubmissionState,\n request: RequestType\n ) => Promise<void>\n }\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 baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\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}\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 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}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AA6CA;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;;AAmBA;AACA;AACA;AACA;;AAqGA,SACEA,UAAU,EACVC,YAAY;;AA6Nd;AACA;AACA","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "2.1.8",
3
+ "version": "2.1.10",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -161,6 +161,17 @@ describe('MultilineTextField', () => {
161
161
  expect(answer2).toBe('')
162
162
  })
163
163
 
164
+ it('returns multiline text from state, collapsing multiple newlines into one', () => {
165
+ const state1 = getFormState('Line 1\r\nLine 2\r\nLine 3')
166
+ const state2 = getFormState('Line 1\r\n\r\nLine 2\r\n\r\n\r\nLine 3')
167
+
168
+ const answer1 = getAnswer(field, state1)
169
+ const answer2 = getAnswer(field, state2)
170
+
171
+ expect(answer1).toBe('Line 1<br>Line 2<br>Line 3<br>')
172
+ expect(answer2).toBe('Line 1<br>Line 2<br>Line 3<br>')
173
+ })
174
+
164
175
  it('returns payload from state', () => {
165
176
  const state1 = getFormState('Textarea')
166
177
  const state2 = getFormState(null)
@@ -315,7 +315,7 @@ export function getAnswerMarkdown(
315
315
  } else if (field instanceof Components.MultilineTextField) {
316
316
  // Preserve Multiline text new lines
317
317
  answerEscaped = answer
318
- .split(/\r?\n/)
318
+ .split(/(?:\r?\n)+/)
319
319
  .map(escapeMarkdown)
320
320
  .join('\n')
321
321
  .concat('\n')
@@ -1,4 +1,7 @@
1
- import { type FormMetadata } from '@defra/forms-model'
1
+ import {
2
+ type FormMetadata,
3
+ type SubmitResponsePayload
4
+ } from '@defra/forms-model'
2
5
 
3
6
  import { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'
4
7
  import { type Field } from '~/src/server/plugins/engine/components/helpers/components.js'
@@ -26,7 +29,7 @@ const submitResponse = {
26
29
  files: {
27
30
  main: '00000000-0000-0000-0000-000000000000',
28
31
  repeaters: {
29
- pizza: '11111111-1111-1111-1111-111111111111'
32
+ exampleRepeat: '11111111-1111-1111-1111-111111111111'
30
33
  }
31
34
  }
32
35
  }
@@ -258,15 +261,26 @@ describe('Adapter v1 formatter', () => {
258
261
  exampleFile1: [
259
262
  {
260
263
  fileId: '123-456-789',
264
+ fileName: 'foobar.txt',
261
265
  userDownloadLink: 'https://forms-designer/file-download/123-456-789'
262
266
  },
263
267
  {
264
268
  fileId: '456-789-123',
269
+ fileName: 'bazbuzz.txt',
265
270
  userDownloadLink: 'https://forms-designer/file-download/456-789-123'
266
271
  }
267
272
  ]
268
273
  }
269
274
  })
275
+
276
+ expect(parsedBody.result).toEqual({
277
+ files: {
278
+ main: '00000000-0000-0000-0000-000000000000',
279
+ repeaters: {
280
+ exampleRepeat: '11111111-1111-1111-1111-111111111111'
281
+ }
282
+ }
283
+ })
270
284
  })
271
285
 
272
286
  it('should handle preview form status correctly', () => {
@@ -385,6 +399,14 @@ describe('Adapter v1 formatter', () => {
385
399
  expect(parsedBody.data.main).toEqual({})
386
400
  expect(parsedBody.data.repeaters).toEqual({})
387
401
  expect(parsedBody.data.files).toEqual({})
402
+ expect(parsedBody.result).toEqual({
403
+ files: {
404
+ main: '00000000-0000-0000-0000-000000000000',
405
+ repeaters: {
406
+ exampleRepeat: '11111111-1111-1111-1111-111111111111'
407
+ }
408
+ }
409
+ })
388
410
  })
389
411
 
390
412
  it('should handle different form statuses', () => {
@@ -503,4 +525,172 @@ describe('Adapter v1 formatter', () => {
503
525
  expect(parsedBody.meta.formSlug).toBe('')
504
526
  expect(parsedBody.meta.notificationEmail).toBe('only-email@example.com')
505
527
  })
528
+
529
+ it('should include CSV file IDs from submitResponse.result.files', () => {
530
+ const formStatus = {
531
+ isPreview: false,
532
+ state: FormStatus.Live
533
+ }
534
+
535
+ const body = format(context, items, model, submitResponse, formStatus)
536
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
537
+
538
+ expect(parsedBody.data.main).toEqual({
539
+ exampleField: 'hello world',
540
+ exampleField2: 'hello world'
541
+ })
542
+
543
+ expect(parsedBody.data.repeaters.exampleRepeat).toEqual([
544
+ {
545
+ subItem1_1: 'hello world',
546
+ subItem1_2: 'hello world'
547
+ },
548
+ {
549
+ subItem2_1: 'hello world'
550
+ }
551
+ ])
552
+
553
+ expect(parsedBody.data.files.exampleFile1).toEqual([
554
+ {
555
+ fileId: '123-456-789',
556
+ fileName: 'foobar.txt',
557
+ userDownloadLink: 'https://forms-designer/file-download/123-456-789'
558
+ },
559
+ {
560
+ fileId: '456-789-123',
561
+ fileName: 'bazbuzz.txt',
562
+ userDownloadLink: 'https://forms-designer/file-download/456-789-123'
563
+ }
564
+ ])
565
+
566
+ expect(parsedBody.result).toEqual({
567
+ files: {
568
+ main: '00000000-0000-0000-0000-000000000000',
569
+ repeaters: {
570
+ exampleRepeat: '11111111-1111-1111-1111-111111111111'
571
+ }
572
+ }
573
+ })
574
+ })
575
+
576
+ it('should handle submitResponse without CSV file IDs gracefully', () => {
577
+ const submitResponseWithoutFiles = {
578
+ message: 'Submit completed',
579
+ result: {
580
+ files: {
581
+ main: '',
582
+ repeaters: {}
583
+ }
584
+ }
585
+ }
586
+
587
+ const formStatus = {
588
+ isPreview: false,
589
+ state: FormStatus.Live
590
+ }
591
+
592
+ const body = format(
593
+ context,
594
+ items,
595
+ model,
596
+ submitResponseWithoutFiles,
597
+ formStatus
598
+ )
599
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
600
+
601
+ expect(parsedBody.data.main).toEqual({
602
+ exampleField: 'hello world',
603
+ exampleField2: 'hello world'
604
+ })
605
+
606
+ expect(parsedBody.data.repeaters.exampleRepeat).toEqual([
607
+ {
608
+ subItem1_1: 'hello world',
609
+ subItem1_2: 'hello world'
610
+ },
611
+ {
612
+ subItem2_1: 'hello world'
613
+ }
614
+ ])
615
+ })
616
+
617
+ it('should handle submitResponse with only main CSV file ID', () => {
618
+ const submitResponseWithMainOnly = {
619
+ message: 'Submit completed',
620
+ result: {
621
+ files: {
622
+ main: 'main-only-file-id',
623
+ repeaters: {}
624
+ }
625
+ }
626
+ }
627
+
628
+ const formStatus = {
629
+ isPreview: false,
630
+ state: FormStatus.Live
631
+ }
632
+
633
+ const body = format(
634
+ context,
635
+ items,
636
+ model,
637
+ submitResponseWithMainOnly,
638
+ formStatus
639
+ )
640
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
641
+
642
+ expect(parsedBody.data.main).toEqual({
643
+ exampleField: 'hello world',
644
+ exampleField2: 'hello world'
645
+ })
646
+
647
+ expect(parsedBody.data.repeaters.exampleRepeat).toEqual([
648
+ {
649
+ subItem1_1: 'hello world',
650
+ subItem1_2: 'hello world'
651
+ },
652
+ {
653
+ subItem2_1: 'hello world'
654
+ }
655
+ ])
656
+
657
+ expect(parsedBody.result).toEqual({
658
+ files: {
659
+ main: 'main-only-file-id',
660
+ repeaters: {}
661
+ }
662
+ })
663
+ })
664
+
665
+ it('should handle submitResponse with missing repeaters property', () => {
666
+ const submitResponseWithoutRepeaters = {
667
+ message: 'Submit completed',
668
+ result: {
669
+ files: {
670
+ main: 'main-only-file-id'
671
+ }
672
+ }
673
+ }
674
+
675
+ const formStatus = {
676
+ isPreview: false,
677
+ state: FormStatus.Live
678
+ }
679
+
680
+ const body = format(
681
+ context,
682
+ items,
683
+ model,
684
+ submitResponseWithoutRepeaters as unknown as SubmitResponsePayload,
685
+ formStatus
686
+ )
687
+ const parsedBody = JSON.parse(body) as FormAdapterSubmissionMessagePayload
688
+
689
+ expect(parsedBody.result).toEqual({
690
+ files: {
691
+ main: 'main-only-file-id',
692
+ repeaters: {}
693
+ }
694
+ })
695
+ })
506
696
  })
@@ -10,7 +10,9 @@ import { format as machineV2 } from '~/src/server/plugins/engine/outputFormatter
10
10
  import { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'
11
11
  import {
12
12
  type FormAdapterSubmissionMessageData,
13
+ type FormAdapterSubmissionMessageMeta,
13
14
  type FormAdapterSubmissionMessagePayload,
15
+ type FormAdapterSubmissionMessageResult,
14
16
  type FormContext
15
17
  } from '~/src/server/plugins/engine/types.js'
16
18
  import { FormStatus } from '~/src/server/routes/types.js'
@@ -34,20 +36,44 @@ export function format(
34
36
  data: FormAdapterSubmissionMessageData
35
37
  }
36
38
 
39
+ const csvFiles = extractCsvFiles(submitResponse)
40
+
41
+ const transformedData = v2DataParsed.data
42
+
43
+ const meta: FormAdapterSubmissionMessageMeta = {
44
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
45
+ timestamp: new Date(),
46
+ referenceNumber: context.referenceNumber,
47
+ formName: model.name,
48
+ formId: formMetadata?.id ?? '',
49
+ formSlug: formMetadata?.slug ?? '',
50
+ status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,
51
+ isPreview: formStatus.isPreview,
52
+ notificationEmail: formMetadata?.notificationEmail ?? ''
53
+ }
54
+ const data: FormAdapterSubmissionMessageData = transformedData
55
+
56
+ const result: FormAdapterSubmissionMessageResult = {
57
+ files: csvFiles
58
+ }
59
+
37
60
  const payload: FormAdapterSubmissionMessagePayload = {
38
- meta: {
39
- schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
40
- timestamp: new Date(),
41
- referenceNumber: context.referenceNumber,
42
- formName: model.name,
43
- formId: formMetadata?.id ?? '',
44
- formSlug: formMetadata?.slug ?? '',
45
- status: formStatus.isPreview ? FormStatus.Draft : FormStatus.Live,
46
- isPreview: formStatus.isPreview,
47
- notificationEmail: formMetadata?.notificationEmail ?? ''
48
- },
49
- data: v2DataParsed.data
61
+ meta,
62
+ data,
63
+ result
50
64
  }
51
65
 
52
66
  return JSON.stringify(payload)
53
67
  }
68
+
69
+ function extractCsvFiles(
70
+ submitResponse: SubmitResponsePayload
71
+ ): FormAdapterSubmissionMessageResult['files'] {
72
+ const result =
73
+ submitResponse.result as Partial<FormAdapterSubmissionMessageResult>
74
+
75
+ return {
76
+ main: result.files?.main ?? '',
77
+ repeaters: result.files?.repeaters ?? {}
78
+ }
79
+ }
@@ -249,10 +249,12 @@ describe('getPersonalisation', () => {
249
249
  exampleFile1: [
250
250
  {
251
251
  fileId: '123-456-789',
252
+ fileName: 'foobar.txt',
252
253
  userDownloadLink: 'https://forms-designer/file-download/123-456-789'
253
254
  },
254
255
  {
255
256
  fileId: '456-789-123',
257
+ fileName: 'bazbuzz.txt',
256
258
  userDownloadLink: 'https://forms-designer/file-download/456-789-123'
257
259
  }
258
260
  ]
@@ -1,12 +1,7 @@
1
1
  import { type SubmitResponsePayload } from '@defra/forms-model'
2
2
 
3
3
  import { config } from '~/src/config/index.js'
4
- import { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'
5
4
  import { FileUploadField } from '~/src/server/plugins/engine/components/index.js'
6
- import {
7
- type DatePartsState,
8
- type MonthYearState
9
- } from '~/src/server/plugins/engine/components/types.js'
10
5
  import { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'
11
6
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
12
7
  import {
@@ -15,9 +10,10 @@ import {
15
10
  type DetailItemRepeat
16
11
  } from '~/src/server/plugins/engine/models/types.js'
17
12
  import {
13
+ type FileUploadFieldDetailitem,
14
+ type FormAdapterFile,
18
15
  type FormContext,
19
- type FormPayload,
20
- type FormValue
16
+ type RichFormValue
21
17
  } from '~/src/server/plugins/engine/types.js'
22
18
 
23
19
  const designerUrl = config.get('designerUrl')
@@ -69,7 +65,8 @@ export function format(
69
65
  * fileComponentName: [
70
66
  * {
71
67
  * fileId: '123-456-789',
72
- * link: 'https://forms-designer/file-download/123-456-789'
68
+ * fileName: 'example.pdf',
69
+ * userDownloadLink: 'https://forms-designer/file-download/123-456-789'
73
70
  * }
74
71
  * ]
75
72
  * }
@@ -79,7 +76,10 @@ function categoriseData(items: DetailItem[]) {
79
76
  const output: {
80
77
  main: Record<string, RichFormValue>
81
78
  repeaters: Record<string, Record<string, RichFormValue>[]>
82
- files: Record<string, Record<string, string>[]>
79
+ files: Record<
80
+ string,
81
+ { fileId: string; fileName: string; userDownloadLink: string }[]
82
+ >
83
83
  } = { main: {}, repeaters: {}, files: {} }
84
84
 
85
85
  items.forEach((item) => {
@@ -126,13 +126,17 @@ function extractRepeaters(item: DetailItemRepeat) {
126
126
  * @param item - the file upload item in the form
127
127
  * @returns the file upload data
128
128
  */
129
- function extractFileUploads(item: FileUploadFieldDetailitem) {
130
- const fileUploadState = item.field.getContextValueFromState(item.state) ?? []
129
+ function extractFileUploads(
130
+ item: FileUploadFieldDetailitem
131
+ ): FormAdapterFile[] {
132
+ const fileUploadState = item.field.getFormValueFromState(item.state) ?? []
131
133
 
132
- return fileUploadState.map((fileId) => {
134
+ return fileUploadState.map((fileState) => {
135
+ const { file } = fileState.status.form
133
136
  return {
134
- fileId,
135
- userDownloadLink: `${designerUrl}/file-download/${fileId}`
137
+ fileId: file.fileId,
138
+ fileName: file.filename,
139
+ userDownloadLink: `${designerUrl}/file-download/${file.fileId}`
136
140
  }
137
141
  })
138
142
  }
@@ -142,17 +146,3 @@ function isFileUploadFieldItem(
142
146
  ): item is FileUploadFieldDetailitem {
143
147
  return item.field instanceof FileUploadField
144
148
  }
145
-
146
- /**
147
- * A detail item specifically for files
148
- */
149
- type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {
150
- field: FileUploadField
151
- }
152
-
153
- export type RichFormValue =
154
- | FormValue
155
- | FormPayload
156
- | DatePartsState
157
- | MonthYearState
158
- | UkAddressState
@@ -87,7 +87,5 @@ export type {
87
87
  Services
88
88
  } from '~/src/server/types.js'
89
89
 
90
- export type { RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
91
-
92
90
  export * from '~/src/server/plugins/engine/types/schema.js'
93
91
  export { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/types/enums.js'
@@ -9,10 +9,56 @@ import {
9
9
  import {
10
10
  type FormAdapterSubmissionMessageData,
11
11
  type FormAdapterSubmissionMessageMeta,
12
- type FormAdapterSubmissionMessagePayload
12
+ type FormAdapterSubmissionMessagePayload,
13
+ type RichFormValue
13
14
  } from '~/src/server/plugins/engine/types.js'
14
15
 
15
16
  describe('Schema validation', () => {
17
+ const main: Record<string, RichFormValue> = {
18
+ QMwMir: 'Roman Pizza',
19
+ duOEvZ: 'Small',
20
+ DzEODf: ['Mozzarella'],
21
+ juiCfC: ['Pepperoni', 'Sausage', 'Onions', 'Basil'],
22
+ YEpypP: 'None',
23
+ JumNVc: 'Joe Bloggs',
24
+ ALNehP: '+441234567890',
25
+ vAqTmg: {
26
+ addressLine1: '1 Anywhere Street',
27
+ town: 'Anywhereville',
28
+ postcode: 'AN1 2WH'
29
+ },
30
+ IbXVGY: {
31
+ day: 22,
32
+ month: 8,
33
+ year: 2025
34
+ },
35
+ HGBWLt: ['Garlic sauce']
36
+ }
37
+
38
+ const value1: Record<string, RichFormValue> = {
39
+ IEKzko: 'dsfsdfsdf'
40
+ }
41
+ const value2: Record<string, RichFormValue> = {
42
+ IEKzko: 'dfghfgh'
43
+ }
44
+
45
+ const validData: FormAdapterSubmissionMessageData = {
46
+ main,
47
+ repeaters: {
48
+ qLVLgb: [value1, value2]
49
+ },
50
+ files: {
51
+ dLzALM: [
52
+ {
53
+ fileId: '489ecc1b-a145-4618-ba5a-b4a0d5ee2dbd',
54
+ fileName: 'file-name.json',
55
+ userDownloadLink:
56
+ 'http://localhost:3005/file-download/489ecc1b-a145-4618-ba5a-b4a0d5ee2dbd'
57
+ }
58
+ ]
59
+ }
60
+ }
61
+
16
62
  describe('formAdapterSubmissionMessageMetaSchema', () => {
17
63
  const validMeta: FormAdapterSubmissionMessageMeta = {
18
64
  schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
@@ -49,31 +95,6 @@ describe('Schema validation', () => {
49
95
  })
50
96
 
51
97
  describe('formAdapterSubmissionMessageDataSchema', () => {
52
- const validData: FormAdapterSubmissionMessageData = {
53
- main: {
54
- QMwMir: 'Roman Pizza',
55
- duOEvZ: 'Small',
56
- DzEODf: ['Mozzarella'],
57
- juiCfC: ['Pepperoni', 'Sausage', 'Onions', 'Basil'],
58
- YEpypP: 'None',
59
- JumNVc: 'Joe Bloggs',
60
- ALNehP: '+441234567890',
61
- vAqTmg: {
62
- addressLine1: '1 Anywhere Street',
63
- town: 'Anywhereville',
64
- postcode: 'AN1 2WH'
65
- },
66
- IbXVGY: {
67
- day: 22,
68
- month: 8,
69
- year: 2025
70
- },
71
- HGBWLt: ['Garlic sauce']
72
- },
73
- repeaters: {},
74
- files: {}
75
- }
76
-
77
98
  it('should validate valid data object', () => {
78
99
  const { error } =
79
100
  formAdapterSubmissionMessageDataSchema.validate(validData)
@@ -89,41 +110,28 @@ describe('Schema validation', () => {
89
110
  })
90
111
 
91
112
  describe('formAdapterSubmissionMessagePayloadSchema', () => {
113
+ const meta: FormAdapterSubmissionMessageMeta = {
114
+ schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
115
+ timestamp: new Date('2025-08-22T18:15:10.785Z'),
116
+ referenceNumber: '576-225-943',
117
+ formName: 'Order a pizza',
118
+ formId: '68a8b0449ab460290c28940a',
119
+ formSlug: 'order-a-pizza',
120
+ status: FormStatus.Live,
121
+ isPreview: false,
122
+ notificationEmail: 'info@example.com'
123
+ }
124
+
92
125
  const validPayload: FormAdapterSubmissionMessagePayload = {
93
- meta: {
94
- schemaVersion: FormAdapterSubmissionSchemaVersion.V1,
95
- timestamp: new Date('2025-08-22T18:15:10.785Z'),
96
- referenceNumber: '576-225-943',
97
- formName: 'Order a pizza',
98
- formId: '68a8b0449ab460290c28940a',
99
- formSlug: 'order-a-pizza',
100
- status: FormStatus.Live,
101
- isPreview: false,
102
- notificationEmail: 'info@example.com'
103
- },
104
- data: {
105
- main: {
106
- QMwMir: 'Roman Pizza',
107
- duOEvZ: 'Small',
108
- DzEODf: ['Mozzarella'],
109
- juiCfC: ['Pepperoni', 'Sausage', 'Onions', 'Basil'],
110
- YEpypP: 'None',
111
- JumNVc: 'Joe Bloggs',
112
- ALNehP: '+441234567890',
113
- vAqTmg: {
114
- addressLine1: '1 Anywhere Street',
115
- town: 'Anywhereville',
116
- postcode: 'AN1 2WH'
117
- },
118
- IbXVGY: {
119
- day: 22,
120
- month: 8,
121
- year: 2025
122
- },
123
- HGBWLt: ['Garlic sauce']
124
- },
125
- repeaters: {},
126
- files: {}
126
+ meta,
127
+ data: validData,
128
+ result: {
129
+ files: {
130
+ main: '3d289230-83a3-4852-a68a-cb3569e9b0fe',
131
+ repeaters: {
132
+ ImxIOP: 'Joe Bloggs'
133
+ }
134
+ }
127
135
  }
128
136
  }
129
137
 
@@ -11,7 +11,8 @@ import { FormAdapterSubmissionSchemaVersion } from '~/src/server/plugins/engine/
11
11
  import {
12
12
  type FormAdapterSubmissionMessageData,
13
13
  type FormAdapterSubmissionMessageMeta,
14
- type FormAdapterSubmissionMessagePayload
14
+ type FormAdapterSubmissionMessagePayload,
15
+ type FormAdapterSubmissionMessageResult
15
16
  } from '~/src/server/plugins/engine/types.js'
16
17
 
17
18
  export const formAdapterSubmissionMessageMetaSchema =
@@ -35,11 +36,31 @@ export const formAdapterSubmissionMessageDataSchema =
35
36
  Joi.object<FormAdapterSubmissionMessageData>().keys({
36
37
  main: Joi.object(),
37
38
  repeaters: Joi.object(),
39
+ files: Joi.object().pattern(
40
+ Joi.string(),
41
+ Joi.array().items(
42
+ Joi.object().keys({
43
+ fileName: Joi.string().required(),
44
+ fileId: Joi.string().required(),
45
+ userDownloadLink: Joi.string().required()
46
+ })
47
+ )
48
+ )
49
+ })
50
+
51
+ export const formAdapterSubmissionMessageResultSchema =
52
+ Joi.object<FormAdapterSubmissionMessageResult>().keys({
38
53
  files: Joi.object()
54
+ .keys({
55
+ main: Joi.string().required(),
56
+ repeaters: Joi.object()
57
+ })
58
+ .required()
39
59
  })
40
60
 
41
61
  export const formAdapterSubmissionMessagePayloadSchema =
42
62
  Joi.object<FormAdapterSubmissionMessagePayload>().keys({
43
63
  meta: formAdapterSubmissionMessageMetaSchema.required(),
44
- data: formAdapterSubmissionMessageDataSchema.required()
64
+ data: formAdapterSubmissionMessageDataSchema.required(),
65
+ result: formAdapterSubmissionMessageResultSchema.required()
45
66
  })
@@ -11,14 +11,18 @@ import { type PluginProperties, type Request } from '@hapi/hapi'
11
11
  import { type JoiExpression, type ValidationErrorItem } from 'joi'
12
12
 
13
13
  import { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'
14
+ import { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'
14
15
  import { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'
16
+ import { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'
15
17
  import {
16
18
  type BackLink,
17
19
  type ComponentText,
18
- type ComponentViewModel
20
+ type ComponentViewModel,
21
+ type DatePartsState,
22
+ type MonthYearState
19
23
  } from '~/src/server/plugins/engine/components/types.js'
20
24
  import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
21
- import { type RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js'
25
+ import { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'
22
26
  import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'
23
27
  import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
24
28
  import {
@@ -405,16 +409,42 @@ export type FormAdapterSubmissionMessageMetaSerialised = Omit<
405
409
  status: string
406
410
  timestamp: string
407
411
  }
412
+ export interface FormAdapterFile {
413
+ fileName: string
414
+ fileId: string
415
+ userDownloadLink: string
416
+ }
417
+
418
+ export interface FormAdapterSubmissionMessageResult {
419
+ files: {
420
+ main: string
421
+ repeaters: Record<string, string>
422
+ }
423
+ }
424
+
425
+ /**
426
+ * A detail item specifically for files
427
+ */
428
+ export type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {
429
+ field: FileUploadField
430
+ }
431
+ export type RichFormValue =
432
+ | FormValue
433
+ | FormPayload
434
+ | DatePartsState
435
+ | MonthYearState
436
+ | UkAddressState
408
437
 
409
438
  export interface FormAdapterSubmissionMessageData {
410
439
  main: Record<string, RichFormValue>
411
440
  repeaters: Record<string, Record<string, RichFormValue>[]>
412
- files: Record<string, Record<string, string>[]>
441
+ files: Record<string, FormAdapterFile[]>
413
442
  }
414
443
 
415
444
  export interface FormAdapterSubmissionMessagePayload {
416
445
  meta: FormAdapterSubmissionMessageMeta
417
446
  data: FormAdapterSubmissionMessageData
447
+ result: FormAdapterSubmissionMessageResult
418
448
  }
419
449
 
420
450
  export interface FormAdapterSubmissionMessage