@defra/forms-engine-plugin 2.1.3 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/plugins/engine/models/FormModel.js +1 -1
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.d.ts +6 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +23 -0
- package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -0
- package/.server/server/plugins/engine/outputFormatters/human/v1.d.ts +2 -2
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/index.d.ts +4 -3
- package/.server/server/plugins/engine/outputFormatters/index.js +4 -0
- package/.server/server/plugins/engine/outputFormatters/index.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.d.ts +2 -2
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +4 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +5 -4
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/services/localFormsService.d.ts +1 -1
- package/.server/server/plugins/engine/services/notifyService.d.ts +7 -2
- package/.server/server/plugins/engine/services/notifyService.js +11 -2
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +10 -0
- package/.server/server/plugins/engine/types/index.js +4 -0
- package/.server/server/plugins/engine/types/index.js.map +1 -0
- package/.server/server/plugins/engine/types/schema.d.ts +5 -0
- package/.server/server/plugins/engine/types/schema.js +24 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -0
- package/.server/server/plugins/engine/types.d.ts +37 -1
- package/.server/server/plugins/engine/types.js +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/nunjucks/filters/page.d.ts +1 -1
- package/.server/server/types.d.ts +1 -1
- package/.server/server/types.js.map +1 -1
- package/package.json +6 -1
- package/src/server/plugins/engine/models/FormModel.test.ts +64 -0
- package/src/server/plugins/engine/models/FormModel.ts +5 -1
- package/src/server/plugins/engine/outputFormatters/adapter/v1.test.ts +506 -0
- package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +53 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +6 -2
- package/src/server/plugins/engine/outputFormatters/index.ts +11 -3
- package/src/server/plugins/engine/outputFormatters/machine/v1.ts +6 -2
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +1 -1
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
- package/src/server/plugins/engine/services/notifyService.test.ts +156 -1
- package/src/server/plugins/engine/services/notifyService.ts +24 -3
- package/src/server/plugins/engine/types/index.ts +96 -0
- package/src/server/plugins/engine/types/schema.test.ts +152 -0
- package/src/server/plugins/engine/types/schema.ts +45 -0
- package/src/server/plugins/engine/types.ts +51 -1
- package/src/server/types.ts +2 -1
|
@@ -5,7 +5,16 @@ import { checkFormStatus } from "../helpers.js";
|
|
|
5
5
|
import { getFormatter } from "../outputFormatters/index.js";
|
|
6
6
|
import { sendNotification } from "../../../utils/notify.js";
|
|
7
7
|
const templateId = config.get('notifyTemplateId');
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Optional GOV.UK Notify service for consumers who want email notifications
|
|
11
|
+
* Can be disabled by not providing notifyTemplateId in config
|
|
12
|
+
* Can be overridden by providing a custom outputService in the services config
|
|
13
|
+
*/
|
|
14
|
+
export async function submit(context, request, model, emailAddress, items, submitResponse, formMetadata) {
|
|
15
|
+
if (!templateId) {
|
|
16
|
+
return Promise.resolve();
|
|
17
|
+
}
|
|
9
18
|
const logTags = ['submit', 'email'];
|
|
10
19
|
const formStatus = checkFormStatus(request.params);
|
|
11
20
|
|
|
@@ -16,7 +25,7 @@ export async function submit(context, request, model, emailAddress, items, submi
|
|
|
16
25
|
const outputAudience = model.def.output?.audience ?? 'human';
|
|
17
26
|
const outputVersion = model.def.output?.version ?? '1';
|
|
18
27
|
const outputFormatter = getFormatter(outputAudience, outputVersion);
|
|
19
|
-
let body = outputFormatter(context, items, model, submitResponse, formStatus);
|
|
28
|
+
let body = outputFormatter(context, items, model, submitResponse, formStatus, formMetadata);
|
|
20
29
|
|
|
21
30
|
// GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload
|
|
22
31
|
// This is logic specific to Notify, so we include the logic here rather than in the formatter
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifyService.js","names":["getErrorMessage","config","escapeMarkdown","checkFormStatus","getFormatter","sendNotification","templateId","get","submit","context","request","model","emailAddress","items","submitResponse","logTags","formStatus","params","logger","info","formName","name","subject","isPreview","outputAudience","def","output","audience","outputVersion","version","outputFormatter","body","Buffer","from","toString","personalisation","err","errMsg","error"],"sources":["../../../../../src/server/plugins/engine/services/notifyService.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"notifyService.js","names":["getErrorMessage","config","escapeMarkdown","checkFormStatus","getFormatter","sendNotification","templateId","get","submit","context","request","model","emailAddress","items","submitResponse","formMetadata","Promise","resolve","logTags","formStatus","params","logger","info","formName","name","subject","isPreview","outputAudience","def","output","audience","outputVersion","version","outputFormatter","body","Buffer","from","toString","personalisation","err","errMsg","error"],"sources":["../../../../../src/server/plugins/engine/services/notifyService.ts"],"sourcesContent":["import {\n getErrorMessage,\n type FormMetadata,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers.js'\nimport { checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { getFormatter } from '~/src/server/plugins/engine/outputFormatters/index.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport { type FormRequestPayload } from '~/src/server/routes/types.js'\nimport { sendNotification } from '~/src/server/utils/notify.js'\n\nconst templateId = config.get('notifyTemplateId')\n\n/**\n * Optional GOV.UK Notify service for consumers who want email notifications\n * Can be disabled by not providing notifyTemplateId in config\n * Can be overridden by providing a custom outputService in the services config\n */\nexport async function submit(\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n) {\n if (!templateId) {\n return Promise.resolve()\n }\n\n const logTags = ['submit', 'email']\n const formStatus = checkFormStatus(request.params)\n\n // Get submission email personalisation\n request.logger.info(logTags, 'Getting personalisation data')\n\n const formName = escapeMarkdown(model.name)\n const subject = formStatus.isPreview\n ? `TEST FORM SUBMISSION: ${formName}`\n : `Form submission: ${formName}`\n\n const outputAudience = model.def.output?.audience ?? 'human'\n const outputVersion = model.def.output?.version ?? '1'\n\n const outputFormatter = getFormatter(outputAudience, outputVersion)\n let body = outputFormatter(\n context,\n items,\n model,\n submitResponse,\n formStatus,\n formMetadata\n )\n\n // GOV.UK Notify transforms quotes into curly quotes, so we can't just send the raw payload\n // This is logic specific to Notify, so we include the logic here rather than in the formatter\n if (outputAudience === 'machine') {\n body = Buffer.from(body).toString('base64')\n }\n\n request.logger.info(logTags, 'Sending email')\n\n try {\n // Send submission email\n await sendNotification({\n templateId,\n emailAddress,\n personalisation: {\n subject,\n body\n }\n })\n\n request.logger.info(logTags, 'Email sent successfully')\n } catch (err) {\n const errMsg = getErrorMessage(err)\n request.logger.error(\n errMsg,\n `[emailSendFailed] Error sending notification email - templateId: ${templateId} - recipient: ${emailAddress} - ${errMsg}`\n )\n\n throw err\n }\n}\n"],"mappings":"AAAA,SACEA,eAAe,QAGV,oBAAoB;AAE3B,SAASC,MAAM;AACf,SAASC,cAAc;AACvB,SAASC,eAAe;AAGxB,SAASC,YAAY;AAGrB,SAASC,gBAAgB;AAEzB,MAAMC,UAAU,GAAGL,MAAM,CAACM,GAAG,CAAC,kBAAkB,CAAC;;AAEjD;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,MAAMA,CAC1BC,OAAoB,EACpBC,OAA2B,EAC3BC,KAAgB,EAChBC,YAAoB,EACpBC,KAAmB,EACnBC,cAAqC,EACrCC,YAA2B,EAC3B;EACA,IAAI,CAACT,UAAU,EAAE;IACf,OAAOU,OAAO,CAACC,OAAO,CAAC,CAAC;EAC1B;EAEA,MAAMC,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC;EACnC,MAAMC,UAAU,GAAGhB,eAAe,CAACO,OAAO,CAACU,MAAM,CAAC;;EAElD;EACAV,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,8BAA8B,CAAC;EAE5D,MAAMK,QAAQ,GAAGrB,cAAc,CAACS,KAAK,CAACa,IAAI,CAAC;EAC3C,MAAMC,OAAO,GAAGN,UAAU,CAACO,SAAS,GAChC,yBAAyBH,QAAQ,EAAE,GACnC,oBAAoBA,QAAQ,EAAE;EAElC,MAAMI,cAAc,GAAGhB,KAAK,CAACiB,GAAG,CAACC,MAAM,EAAEC,QAAQ,IAAI,OAAO;EAC5D,MAAMC,aAAa,GAAGpB,KAAK,CAACiB,GAAG,CAACC,MAAM,EAAEG,OAAO,IAAI,GAAG;EAEtD,MAAMC,eAAe,GAAG7B,YAAY,CAACuB,cAAc,EAAEI,aAAa,CAAC;EACnE,IAAIG,IAAI,GAAGD,eAAe,CACxBxB,OAAO,EACPI,KAAK,EACLF,KAAK,EACLG,cAAc,EACdK,UAAU,EACVJ,YACF,CAAC;;EAED;EACA;EACA,IAAIY,cAAc,KAAK,SAAS,EAAE;IAChCO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACF,IAAI,CAAC,CAACG,QAAQ,CAAC,QAAQ,CAAC;EAC7C;EAEA3B,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,eAAe,CAAC;EAE7C,IAAI;IACF;IACA,MAAMb,gBAAgB,CAAC;MACrBC,UAAU;MACVM,YAAY;MACZ0B,eAAe,EAAE;QACfb,OAAO;QACPS;MACF;IACF,CAAC,CAAC;IAEFxB,OAAO,CAACW,MAAM,CAACC,IAAI,CAACJ,OAAO,EAAE,yBAAyB,CAAC;EACzD,CAAC,CAAC,OAAOqB,GAAG,EAAE;IACZ,MAAMC,MAAM,GAAGxC,eAAe,CAACuC,GAAG,CAAC;IACnC7B,OAAO,CAACW,MAAM,CAACoB,KAAK,CAClBD,MAAM,EACN,oEAAoElC,UAAU,iBAAiBM,YAAY,MAAM4B,MAAM,EACzH,CAAC;IAED,MAAMD,GAAG;EACX;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { CheckAnswers, ErrorMessageTemplate, ErrorMessageTemplateList, FeaturedFormPageViewModel, FileState, FilterFunction, FormAdapterSubmissionMessage, FormAdapterSubmissionMessageData, FormAdapterSubmissionMessageMeta, FormAdapterSubmissionMessageMetaSerialised, FormAdapterSubmissionMessagePayload, FormAdapterSubmissionService, FormContext, FormContextRequest, FormPageViewModel, FormPayload, FormPayloadParams, FormState, FormStateValue, FormSubmissionError, FormSubmissionState, FormValidationResult, FormValue, GlobalFunction, ItemDeletePageViewModel, OnRequestCallback, PageViewModel, PageViewModelBase, PluginOptions, PreparePageEventRequestOptions, RepeatItemState, RepeatListState, RepeaterSummaryPageViewModel, SummaryList, SummaryListAction, SummaryListRow, TempFileState, UploadInitiateResponse, UploadStatusFileResponse, UploadStatusResponse } from '~/src/server/plugins/engine/types.js';
|
|
2
|
+
export { FileStatus, FormAdapterSubmissionSchemaVersion, UploadStatus } from '~/src/server/plugins/engine/types.js';
|
|
3
|
+
export type { Detail, DetailItem, DetailItemBase, DetailItemField, DetailItemRepeat, ExecutableCondition } from '~/src/server/plugins/engine/models/types.js';
|
|
4
|
+
export type { BackLink, ComponentText, ComponentViewModel, Content, DateInputItem, DatePartsState, Label, ListItem, ListItemLabel, MonthYearState, ViewModel } from '~/src/server/plugins/engine/components/types.js';
|
|
5
|
+
export type { UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js';
|
|
6
|
+
export type { FormParams, FormQuery, FormRequest, FormRequestPayload, FormRequestPayloadRefs, FormRequestRefs } from '~/src/server/routes/types.js';
|
|
7
|
+
export { FormAction, FormStatus } from '~/src/server/routes/types.js';
|
|
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
|
+
export * from '~/src/server/plugins/engine/types/schema.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["FileStatus","FormAdapterSubmissionSchemaVersion","UploadStatus","FormAction","FormStatus"],"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 {\n FileStatus,\n FormAdapterSubmissionSchemaVersion,\n UploadStatus\n} 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'\n"],"mappings":"AA2CA,SACEA,UAAU,EACVC,kCAAkC,EAClCC,YAAY;AAqCd,SAASC,UAAU,EAAEC,UAAU;AAY/B","ignoreList":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import { type FormAdapterSubmissionMessageData, type FormAdapterSubmissionMessageMeta, type FormAdapterSubmissionMessagePayload } from '~/src/server/plugins/engine/types.js';
|
|
3
|
+
export declare const formAdapterSubmissionMessageMetaSchema: Joi.ObjectSchema<FormAdapterSubmissionMessageMeta>;
|
|
4
|
+
export declare const formAdapterSubmissionMessageDataSchema: Joi.ObjectSchema<FormAdapterSubmissionMessageData>;
|
|
5
|
+
export declare const formAdapterSubmissionMessagePayloadSchema: Joi.ObjectSchema<FormAdapterSubmissionMessagePayload>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FormStatus, idSchema, notificationEmailAddressSchema, slugSchema, titleSchema } from '@defra/forms-model';
|
|
2
|
+
import Joi from 'joi';
|
|
3
|
+
import { FormAdapterSubmissionSchemaVersion } from "../types.js";
|
|
4
|
+
export const formAdapterSubmissionMessageMetaSchema = Joi.object().keys({
|
|
5
|
+
schemaVersion: Joi.string().valid(...Object.values(FormAdapterSubmissionSchemaVersion)),
|
|
6
|
+
timestamp: Joi.date().required(),
|
|
7
|
+
referenceNumber: Joi.string().required(),
|
|
8
|
+
formName: titleSchema,
|
|
9
|
+
formId: idSchema,
|
|
10
|
+
formSlug: slugSchema,
|
|
11
|
+
status: Joi.string().valid(...Object.values(FormStatus)).required(),
|
|
12
|
+
isPreview: Joi.boolean().required(),
|
|
13
|
+
notificationEmail: notificationEmailAddressSchema.required()
|
|
14
|
+
});
|
|
15
|
+
export const formAdapterSubmissionMessageDataSchema = Joi.object().keys({
|
|
16
|
+
main: Joi.object(),
|
|
17
|
+
repeaters: Joi.object(),
|
|
18
|
+
files: Joi.object()
|
|
19
|
+
});
|
|
20
|
+
export const formAdapterSubmissionMessagePayloadSchema = Joi.object().keys({
|
|
21
|
+
meta: formAdapterSubmissionMessageMetaSchema.required(),
|
|
22
|
+
data: formAdapterSubmissionMessageDataSchema.required()
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +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 {\n FormAdapterSubmissionSchemaVersion,\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,SACEC,kCAAkC;AAMpC,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":[]}
|
|
@@ -4,10 +4,11 @@ import { type JoiExpression, type ValidationErrorItem } from 'joi';
|
|
|
4
4
|
import { type Component } from '~/src/server/plugins/engine/components/helpers.js';
|
|
5
5
|
import { type BackLink, type ComponentText, type ComponentViewModel } from '~/src/server/plugins/engine/components/types.js';
|
|
6
6
|
import { type FormModel } from '~/src/server/plugins/engine/models/index.js';
|
|
7
|
+
import { type RichFormValue } from '~/src/server/plugins/engine/outputFormatters/machine/v2.js';
|
|
7
8
|
import { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js';
|
|
8
9
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js';
|
|
9
10
|
import { type ViewContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
10
|
-
import { type FormAction, type FormParams, type FormRequest, type FormRequestPayload } from '~/src/server/routes/types.js';
|
|
11
|
+
import { type FormAction, type FormParams, type FormRequest, type FormRequestPayload, type FormStatus } from '~/src/server/routes/types.js';
|
|
11
12
|
import { type RequestOptions } from '~/src/server/services/httpService.js';
|
|
12
13
|
import { type Services } from '~/src/server/types.js';
|
|
13
14
|
type RequestType = Request | FormRequest | FormRequestPayload;
|
|
@@ -285,4 +286,39 @@ export interface PluginOptions {
|
|
|
285
286
|
onRequest?: OnRequestCallback;
|
|
286
287
|
baseUrl: string;
|
|
287
288
|
}
|
|
289
|
+
export interface FormAdapterSubmissionMessageMeta {
|
|
290
|
+
schemaVersion: FormAdapterSubmissionSchemaVersion;
|
|
291
|
+
timestamp: Date;
|
|
292
|
+
referenceNumber: string;
|
|
293
|
+
formName: string;
|
|
294
|
+
formId: string;
|
|
295
|
+
formSlug: string;
|
|
296
|
+
status: FormStatus;
|
|
297
|
+
isPreview: boolean;
|
|
298
|
+
notificationEmail: string;
|
|
299
|
+
}
|
|
300
|
+
export type FormAdapterSubmissionMessageMetaSerialised = Omit<FormAdapterSubmissionMessageMeta, 'schemaVersion' | 'timestamp' | 'status'> & {
|
|
301
|
+
schemaVersion: string;
|
|
302
|
+
status: string;
|
|
303
|
+
timestamp: string;
|
|
304
|
+
};
|
|
305
|
+
export interface FormAdapterSubmissionMessageData {
|
|
306
|
+
main: Record<string, RichFormValue>;
|
|
307
|
+
repeaters: Record<string, Record<string, RichFormValue>[]>;
|
|
308
|
+
files: Record<string, Record<string, string>[]>;
|
|
309
|
+
}
|
|
310
|
+
export declare enum FormAdapterSubmissionSchemaVersion {
|
|
311
|
+
V1 = 1
|
|
312
|
+
}
|
|
313
|
+
export interface FormAdapterSubmissionMessagePayload {
|
|
314
|
+
meta: FormAdapterSubmissionMessageMeta;
|
|
315
|
+
data: FormAdapterSubmissionMessageData;
|
|
316
|
+
}
|
|
317
|
+
export interface FormAdapterSubmissionMessage extends FormAdapterSubmissionMessagePayload {
|
|
318
|
+
messageId: string;
|
|
319
|
+
recordCreatedAt: Date;
|
|
320
|
+
}
|
|
321
|
+
export interface FormAdapterSubmissionService {
|
|
322
|
+
handleFormSubmission: (submissionMessage: FormAdapterSubmissionMessage) => unknown;
|
|
323
|
+
}
|
|
288
324
|
export {};
|
|
@@ -50,4 +50,8 @@ export let FileStatus = /*#__PURE__*/function (FileStatus) {
|
|
|
50
50
|
FileStatus["pending"] = "pending";
|
|
51
51
|
return FileStatus;
|
|
52
52
|
}({});
|
|
53
|
+
export let FormAdapterSubmissionSchemaVersion = /*#__PURE__*/function (FormAdapterSubmissionSchemaVersion) {
|
|
54
|
+
FormAdapterSubmissionSchemaVersion[FormAdapterSubmissionSchemaVersion["V1"] = 1] = "V1";
|
|
55
|
+
return FormAdapterSubmissionSchemaVersion;
|
|
56
|
+
}({});
|
|
53
57
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus"],"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.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 PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.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} 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 enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\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"],"mappings":"AAkCA;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,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":["UploadStatus","FileStatus","FormAdapterSubmissionSchemaVersion"],"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.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.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 enum UploadStatus {\n initiated = 'initiated',\n pending = 'pending',\n ready = 'ready'\n}\n\nexport enum FileStatus {\n complete = 'complete',\n rejected = 'rejected',\n pending = 'pending'\n}\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: string\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 enum FormAdapterSubmissionSchemaVersion {\n V1 = 1\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":"AAoCA;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,WAAYA,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAMxB,WAAYC,UAAU,0BAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAVA,UAAU;EAAA,OAAVA,UAAU;AAAA;AA0NtB,WAAYC,kCAAkC,0BAAlCA,kCAAkC;EAAlCA,kCAAkC,CAAlCA,kCAAkC;EAAA,OAAlCA,kCAAkC;AAAA","ignoreList":[]}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* @this {NunjucksContext}
|
|
4
4
|
* @param {string} name - The name of the component
|
|
5
5
|
*/
|
|
6
|
-
export function field(this: NunjucksContext, name: string): import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/
|
|
6
|
+
export function field(this: NunjucksContext, name: string): import("../../engine/components/TextField.js").TextField | import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/UkAddressField.js").UkAddressField | import("../../engine/components/FileUploadField.js").FileUploadField | import("../../engine/components/Details.js").Details | import("../../engine/components/Html.js").Html | import("../../engine/components/InsetText.js").InsetText | import("../../engine/components/List.js").List | import("../../engine/components/Markdown.js").Markdown | undefined;
|
|
7
7
|
import type { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* @this {NunjucksContext}
|
|
4
4
|
* @param {string} path - The path of the page
|
|
5
5
|
*/
|
|
6
|
-
export function page(this: NunjucksContext, path: string): import("../../engine/pageControllers/
|
|
6
|
+
export function page(this: NunjucksContext, path: string): import("../../engine/pageControllers/StartPageController.js").StartPageController | import("../../engine/pageControllers/QuestionPageController.js").QuestionPageController | import("../../engine/pageControllers/RepeatPageController.js").RepeatPageController | undefined;
|
|
7
7
|
import type { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js';
|
|
@@ -31,5 +31,5 @@ export interface RouteConfig {
|
|
|
31
31
|
saveAndReturn?: PluginOptions['saveAndReturn'];
|
|
32
32
|
}
|
|
33
33
|
export interface OutputService {
|
|
34
|
-
submit: (context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload) => Promise<void>;
|
|
34
|
+
submit: (context: FormContext, request: FormRequestPayload, model: FormModel, emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, formMetadata?: FormMetadata) => Promise<void>;
|
|
35
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndReturn?: PluginOptions['saveAndReturn']\n}\n\nexport interface OutputService {\n submit: (\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/server/types.ts"],"sourcesContent":["import {\n type FormDefinition,\n type FormMetadata,\n type SubmitPayload,\n type SubmitResponsePayload\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type OnRequestCallback,\n type PluginOptions,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequestPayload,\n type FormStatus\n} from '~/src/server/routes/types.js'\n\nexport interface FormsService {\n getFormMetadata: (slug: string) => Promise<FormMetadata>\n getFormDefinition: (\n id: string,\n state: FormStatus\n ) => Promise<FormDefinition | undefined>\n}\n\nexport interface FormSubmissionService {\n persistFiles: (\n files: { fileId: string; initiatedRetrievalKey: string }[],\n persistedRetrievalKey: string\n ) => Promise<object>\n submit: (data: SubmitPayload) => Promise<SubmitResponsePayload | undefined>\n}\n\nexport interface Services {\n formsService: FormsService\n formSubmissionService: FormSubmissionService\n outputService: OutputService\n}\n\nexport interface RouteConfig {\n formFileName?: string\n formFilePath?: string\n enforceCsrf?: boolean\n services?: Services\n controllers?: Record<string, typeof PageController>\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n saveAndReturn?: PluginOptions['saveAndReturn']\n}\n\nexport interface OutputService {\n submit: (\n context: FormContext,\n request: FormRequestPayload,\n model: FormModel,\n emailAddress: string,\n items: DetailItem[],\n submitResponse: SubmitResponsePayload,\n formMetadata?: FormMetadata\n ) => Promise<void>\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"import": "./.server/server/plugins/engine/index.js",
|
|
15
15
|
"default": "./.server/server/plugins/engine/index.js"
|
|
16
16
|
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./.server/server/plugins/engine/types/index.d.ts",
|
|
19
|
+
"import": "./.server/server/plugins/engine/types/index.js",
|
|
20
|
+
"default": "./.server/server/plugins/engine/types/index.js"
|
|
21
|
+
},
|
|
17
22
|
"./shared.js": "./.server/client/javascripts/shared.js",
|
|
18
23
|
"./shared.min.js": "./.public/javascripts/shared.min.js",
|
|
19
24
|
"./shared.min.js.map": "./.public/javascripts/shared.min.js.map",
|
|
@@ -9,6 +9,7 @@ import { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'
|
|
|
9
9
|
import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
10
10
|
import { buildFormContextRequest } from '~/src/server/plugins/engine/pageControllers/__stubs__/request.js'
|
|
11
11
|
import { type FormContextRequest } from '~/src/server/plugins/engine/types.js'
|
|
12
|
+
import { FormAction } from '~/src/server/routes/types.js'
|
|
12
13
|
import { V2 as definitionV2 } from '~/test/form/definitions/conditions-basic.js'
|
|
13
14
|
import definition from '~/test/form/definitions/conditions-escaping.js'
|
|
14
15
|
import conditionsListDefinition from '~/test/form/definitions/conditions-list.js'
|
|
@@ -184,6 +185,69 @@ describe('FormModel', () => {
|
|
|
184
185
|
})
|
|
185
186
|
|
|
186
187
|
describe('getFormContext', () => {
|
|
188
|
+
it.each([FormAction.Validate, FormAction.SaveAndReturn, undefined])(
|
|
189
|
+
'returns a form context with the correct payload and state when action is %s',
|
|
190
|
+
(action) => {
|
|
191
|
+
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
192
|
+
basePath: '/components'
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const state = {
|
|
196
|
+
$$__referenceNumber: 'foobar'
|
|
197
|
+
}
|
|
198
|
+
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
199
|
+
|
|
200
|
+
const request: FormContextRequest = buildFormContextRequest({
|
|
201
|
+
method: 'post',
|
|
202
|
+
payload: {
|
|
203
|
+
crumb: 'dummyCrumb',
|
|
204
|
+
action,
|
|
205
|
+
textField: 'Hello world'
|
|
206
|
+
},
|
|
207
|
+
query: {},
|
|
208
|
+
path: pageUrl.pathname,
|
|
209
|
+
params: { path: 'components', slug: 'fields-required' },
|
|
210
|
+
url: pageUrl,
|
|
211
|
+
app: { model: formModel }
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const context = formModel.getFormContext(request, state)
|
|
215
|
+
|
|
216
|
+
expect(context.payload.textField).toBe('Hello world')
|
|
217
|
+
expect(context.state.textField).toBe('Hello world')
|
|
218
|
+
expect(context.referenceNumber).toEqual(expect.any(String))
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
it('returns without updating the state when the action is not validate or saveAndReturn', () => {
|
|
223
|
+
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
224
|
+
basePath: '/components'
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const state = {
|
|
228
|
+
$$__referenceNumber: 'foobar',
|
|
229
|
+
textField: 'old'
|
|
230
|
+
}
|
|
231
|
+
const pageUrl = new URL('http://example.com/components/fields-required')
|
|
232
|
+
|
|
233
|
+
const request: FormContextRequest = buildFormContextRequest({
|
|
234
|
+
method: 'post',
|
|
235
|
+
payload: { crumb: 'dummyCrumb', action: 'continue', textField: 'new' },
|
|
236
|
+
query: {},
|
|
237
|
+
path: pageUrl.pathname,
|
|
238
|
+
params: { path: 'components', slug: 'fields-required' },
|
|
239
|
+
url: pageUrl,
|
|
240
|
+
app: { model: formModel }
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const context = formModel.getFormContext(request, state)
|
|
244
|
+
|
|
245
|
+
// Early return branch should not merge payload into state
|
|
246
|
+
expect(context.state.textField).toBe('old')
|
|
247
|
+
expect(context.errors).toBeUndefined()
|
|
248
|
+
expect(context.referenceNumber).toEqual(expect.any(String))
|
|
249
|
+
})
|
|
250
|
+
|
|
187
251
|
it('clears a previous checkbox field value when the field is omitted from the payload', () => {
|
|
188
252
|
const formModel = new FormModel(fieldsRequiredDefinition, {
|
|
189
253
|
basePath: '/components'
|
|
@@ -541,7 +541,11 @@ function validateFormPayload(
|
|
|
541
541
|
const { action } = page.getFormParams(request)
|
|
542
542
|
|
|
543
543
|
// Skip validation GET requests or other actions
|
|
544
|
-
if (
|
|
544
|
+
if (
|
|
545
|
+
!request.payload ||
|
|
546
|
+
(action &&
|
|
547
|
+
![FormAction.Validate, FormAction.SaveAndReturn].includes(action))
|
|
548
|
+
) {
|
|
545
549
|
return context
|
|
546
550
|
}
|
|
547
551
|
|