@defra/forms-model 3.0.643 → 3.0.645

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 (58) hide show
  1. package/dist/module/form/form-audit/index.js +5 -4
  2. package/dist/module/form/form-audit/index.js.map +1 -1
  3. package/dist/module/form/form-definition/constants.js +3 -0
  4. package/dist/module/form/form-definition/constants.js.map +1 -0
  5. package/dist/module/form/form-definition/index.js +6 -9
  6. package/dist/module/form/form-definition/index.js.map +1 -1
  7. package/dist/module/form/form-editor/index.js +4 -1
  8. package/dist/module/form/form-editor/index.js.map +1 -1
  9. package/dist/module/form/form-metadata/index.js +4 -3
  10. package/dist/module/form/form-metadata/index.js.map +1 -1
  11. package/dist/module/form/form-metrics/enums.js +7 -0
  12. package/dist/module/form/form-metrics/enums.js.map +1 -0
  13. package/dist/module/form/form-metrics/types.js +2 -0
  14. package/dist/module/form/form-metrics/types.js.map +1 -0
  15. package/dist/module/form/utils/index.js +1 -0
  16. package/dist/module/form/utils/index.js.map +1 -1
  17. package/dist/module/form/utils/prevent-unicode.js +20 -0
  18. package/dist/module/form/utils/prevent-unicode.js.map +1 -0
  19. package/dist/module/index.js +2 -0
  20. package/dist/module/index.js.map +1 -1
  21. package/dist/module/pages/helpers.js +38 -2
  22. package/dist/module/pages/helpers.js.map +1 -1
  23. package/dist/module/pages/index.js +1 -1
  24. package/dist/module/pages/index.js.map +1 -1
  25. package/dist/types/form/form-definition/constants.d.ts +3 -0
  26. package/dist/types/form/form-definition/constants.d.ts.map +1 -0
  27. package/dist/types/form/form-definition/index.d.ts +1 -2
  28. package/dist/types/form/form-definition/index.d.ts.map +1 -1
  29. package/dist/types/form/form-editor/index.d.ts +2 -0
  30. package/dist/types/form/form-editor/index.d.ts.map +1 -1
  31. package/dist/types/form/form-metadata/index.d.ts.map +1 -1
  32. package/dist/types/form/form-metrics/enums.d.ts +6 -0
  33. package/dist/types/form/form-metrics/enums.d.ts.map +1 -0
  34. package/dist/types/form/form-metrics/types.d.ts +32 -0
  35. package/dist/types/form/form-metrics/types.d.ts.map +1 -0
  36. package/dist/types/form/utils/index.d.ts +1 -0
  37. package/dist/types/form/utils/index.d.ts.map +1 -1
  38. package/dist/types/form/utils/prevent-unicode.d.ts +10 -0
  39. package/dist/types/form/utils/prevent-unicode.d.ts.map +1 -0
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/types/pages/helpers.d.ts +9 -0
  43. package/dist/types/pages/helpers.d.ts.map +1 -1
  44. package/dist/types/pages/index.d.ts +1 -1
  45. package/dist/types/pages/index.d.ts.map +1 -1
  46. package/package.json +1 -1
  47. package/src/form/form-audit/index.ts +4 -4
  48. package/src/form/form-definition/constants.ts +2 -0
  49. package/src/form/form-definition/index.ts +13 -14
  50. package/src/form/form-editor/index.ts +11 -1
  51. package/src/form/form-metadata/index.ts +7 -8
  52. package/src/form/form-metrics/enums.ts +5 -0
  53. package/src/form/form-metrics/types.ts +39 -0
  54. package/src/form/utils/index.ts +1 -0
  55. package/src/form/utils/prevent-unicode.js +19 -0
  56. package/src/index.ts +2 -0
  57. package/src/pages/helpers.ts +43 -0
  58. package/src/pages/index.ts +2 -0
@@ -52,9 +52,9 @@ import {
52
52
  type FormUploadedMessageData,
53
53
  type FormsBackupRequestedMessageData
54
54
  } from '~/src/form/form-audit/types.js'
55
+ import { emailAddressNoUnicodeSchema } from '~/src/form/form-editor/index.js'
55
56
  import {
56
57
  contactSchema,
57
- emailAddressSchema,
58
58
  emailResponseTimeSchema,
59
59
  idSchema,
60
60
  notificationEmailAddressSchema,
@@ -181,7 +181,7 @@ export const formSupportOnlineChanges = Joi.object<FormSupportOnlineChanges>()
181
181
 
182
182
  export const formSupportEmailChanges = Joi.object<FormSupportEmailChanges>()
183
183
  .keys({
184
- address: emailAddressSchema.optional(),
184
+ address: emailAddressNoUnicodeSchema.optional(),
185
185
  responseTime: emailResponseTimeSchema.optional()
186
186
  })
187
187
  .description('Changes schema for FORM_SUPPORT_EMAIL_UPDATED event')
@@ -238,7 +238,7 @@ export const entitlementMessageData = Joi.object<EntitlementMessageData>().keys(
238
238
  {
239
239
  userId: Joi.string().required(),
240
240
  displayName: Joi.string().required(),
241
- email: Joi.string().email().required(),
241
+ email: emailAddressNoUnicodeSchema.required(),
242
242
  roles: Joi.array().items(Joi.string())
243
243
  }
244
244
  )
@@ -260,7 +260,7 @@ export const excelGenerationMessageData =
260
260
  .keys({
261
261
  formId: Joi.string().required(),
262
262
  formName: Joi.string().required(),
263
- notificationEmail: Joi.string().email().required()
263
+ notificationEmail: emailAddressNoUnicodeSchema.required()
264
264
  })
265
265
  .required()
266
266
  .description(
@@ -0,0 +1,2 @@
1
+ export const MIN_NUMBER_OF_REPEAT_ITEMS = 1
2
+ export const MAX_NUMBER_OF_REPEAT_ITEMS = 200
@@ -25,6 +25,10 @@ import {
25
25
  type RelativeDateValueData,
26
26
  type RelativeDateValueDataV2
27
27
  } from '~/src/conditions/types.js'
28
+ import {
29
+ MAX_NUMBER_OF_REPEAT_ITEMS,
30
+ MIN_NUMBER_OF_REPEAT_ITEMS
31
+ } from '~/src/form/form-definition/constants.js'
28
32
  import {
29
33
  isConditionListItemRefValueData,
30
34
  isFormDefinition
@@ -47,6 +51,7 @@ import {
47
51
  type RepeatSchema,
48
52
  type Section
49
53
  } from '~/src/form/form-definition/types.js'
54
+ import { emailAddressNoUnicodeSchema } from '~/src/form/form-editor/index.js'
50
55
  import { checkErrors } from '~/src/form/form-manager/errors.js'
51
56
  import {
52
57
  FormDefinitionError,
@@ -348,9 +353,6 @@ const conditionSchema = Joi.object<ConditionData>()
348
353
  )
349
354
  })
350
355
 
351
- export const MIN_NUMBER_OF_REPEAT_ITEMS = 1
352
- export const MAX_NUMBER_OF_REPEAT_ITEMS = 200
353
-
354
356
  export const conditionDataSchemaV2 = Joi.object<ConditionDataV2>()
355
357
  .description('Condition definition')
356
358
  .keys({
@@ -1041,13 +1043,7 @@ const feedbackSchema = Joi.object<FormDefinition['feedback']>()
1041
1043
  .description(
1042
1044
  'URL to an external feedback form when not using built-in feedback'
1043
1045
  ),
1044
- emailAddress: Joi.string()
1045
- .trim()
1046
- .email({
1047
- tlds: {
1048
- allow: false
1049
- }
1050
- })
1046
+ emailAddress: emailAddressNoUnicodeSchema
1051
1047
  .optional()
1052
1048
  .description('Email address where feedback is sent')
1053
1049
  })
@@ -1155,8 +1151,7 @@ export const formDefinitionSchema = Joi.object<FormDefinition>()
1155
1151
  .optional()
1156
1152
  .description('Phase banner configuration'),
1157
1153
  options: optionsSchema.optional().description('Options for the form'),
1158
- outputEmail: Joi.string()
1159
- .trim()
1154
+ outputEmail: emailAddressNoUnicodeSchema
1160
1155
  .email({ tlds: { allow: ['uk'] } })
1161
1156
  .optional()
1162
1157
  .description('Email address where form submissions are sent'),
@@ -1165,8 +1160,7 @@ export const formDefinitionSchema = Joi.object<FormDefinition>()
1165
1160
  .description('Configuration for submission output format'),
1166
1161
  outputs: Joi.array()
1167
1162
  .items({
1168
- emailAddress: Joi.string()
1169
- .trim()
1163
+ emailAddress: emailAddressNoUnicodeSchema
1170
1164
  .email({ tlds: { allow: ['uk'] } })
1171
1165
  .description('Email address where form submissions are sent'),
1172
1166
  audience: Joi.string()
@@ -1244,6 +1238,11 @@ export const formDefinitionV2Schema = formDefinitionSchema
1244
1238
  })
1245
1239
  .description('Form definition schema for V2')
1246
1240
 
1241
+ export {
1242
+ MAX_NUMBER_OF_REPEAT_ITEMS,
1243
+ MIN_NUMBER_OF_REPEAT_ITEMS
1244
+ } from '~/src/form/form-definition/constants.js'
1245
+
1247
1246
  // Maintain compatibility with legacy named export
1248
1247
  // E.g. `import { Schema } from '@defra/forms-model'`
1249
1248
  export const Schema = formDefinitionSchema
@@ -5,7 +5,7 @@ import { ComponentType } from '~/src/components/enums.js'
5
5
  import {
6
6
  MAX_NUMBER_OF_REPEAT_ITEMS,
7
7
  MIN_NUMBER_OF_REPEAT_ITEMS
8
- } from '~/src/form/form-definition/index.js'
8
+ } from '~/src/form/form-definition/constants.js'
9
9
  import {
10
10
  type FormEditorInputCheckAnswersSettings,
11
11
  type FormEditorInputPage,
@@ -16,6 +16,16 @@ import {
16
16
  type GovukFieldUsePostcodeLookup,
17
17
  type GovukStringField
18
18
  } from '~/src/form/form-editor/types.js'
19
+ import { preventUnicodeInEmail } from '~/src/form/utils/prevent-unicode.js'
20
+
21
+ export const emailAddressNoUnicodeSchema = Joi.string()
22
+ .trim()
23
+ .email()
24
+ .custom((value, helpers) => preventUnicodeInEmail(value, helpers))
25
+ .description('Email address preventing unicode characters')
26
+
27
+ export const UNICODE_EMAIL_ERROR_MESSAGE =
28
+ 'The email address you entered includes invalid characters, for example, long dashes'
19
29
 
20
30
  export enum QuestionTypeSubGroup {
21
31
  WrittenAnswerSubGroup = 'writtenAnswerSub',
@@ -1,5 +1,6 @@
1
1
  import Joi from 'joi'
2
2
 
3
+ import { emailAddressNoUnicodeSchema } from '~/src/form/form-editor/index.js'
3
4
  import {
4
5
  type FormMetadata,
5
6
  type FormMetadataAuthor,
@@ -53,7 +54,7 @@ export const teamNameSchema = Joi.string()
53
54
  .required()
54
55
  .description('Name of the team responsible for the form')
55
56
 
56
- export const teamEmailSchema = Joi.string()
57
+ export const teamEmailSchema = emailAddressNoUnicodeSchema
57
58
  .email({ tlds: { allow: ['uk'] } })
58
59
  .trim()
59
60
  .required()
@@ -63,9 +64,7 @@ export const phoneSchema = Joi.string()
63
64
  .trim()
64
65
  .description('Phone number for form-related inquiries')
65
66
 
66
- export const emailAddressSchema = Joi.string()
67
- .email()
68
- .trim()
67
+ export const emailAddressSchema = emailAddressNoUnicodeSchema
69
68
  .required()
70
69
  .description('Email address for form-related inquiries')
71
70
 
@@ -132,10 +131,10 @@ export const termsAndConditionsAgreedSchema = Joi.boolean().description(
132
131
  'Whether the data protection terms and conditions have been agreed to'
133
132
  )
134
133
 
135
- export const notificationEmailAddressSchema = Joi.string()
136
- .email()
137
- .trim()
138
- .description('Email address to receive form submission notifications')
134
+ export const notificationEmailAddressSchema =
135
+ emailAddressNoUnicodeSchema.description(
136
+ 'Email address to receive form submission notifications'
137
+ )
139
138
 
140
139
  export const authoredAtSchema = Joi.date()
141
140
  .iso()
@@ -0,0 +1,5 @@
1
+ export enum FormMetricType {
2
+ HeadlineMetric = 'headline-metric',
3
+ OverviewMetric = 'overview-metric',
4
+ TimelineMetric = 'timeline-metric'
5
+ }
@@ -0,0 +1,39 @@
1
+ import { type FormStatus } from '~/src/common/enums.js'
2
+ import { type FormMetricType } from '~/src/form/form-metrics/enums.js'
3
+
4
+ export interface FormHeadlineDetail {
5
+ count: number
6
+ countSevenDaysAgo: number
7
+ countThirtyDaysAgo: number
8
+ countOneYearAgo: number
9
+ }
10
+
11
+ export interface FormHeadlineMetric {
12
+ type: FormMetricType.HeadlineMetric
13
+ headlineCounts: Record<string, FormHeadlineDetail>
14
+ updatedAt: Date
15
+ }
16
+
17
+ export interface FormOverviewMetric {
18
+ type: FormMetricType.OverviewMetric
19
+ formId: string
20
+ formStatus: FormStatus
21
+ summaryMetrics: Record<string, number | string | string[]>
22
+ featureCounts: Record<string, number>
23
+ submissionsCount: number
24
+ updatedAt: Date
25
+ }
26
+
27
+ export interface FormTimelineMetric {
28
+ type: FormMetricType.TimelineMetric
29
+ formId: string
30
+ formStatus: FormStatus
31
+ metricName: string
32
+ metricValue: number
33
+ createdAt: Date
34
+ }
35
+
36
+ export type FormMetric =
37
+ | FormHeadlineMetric
38
+ | FormOverviewMetric
39
+ | FormTimelineMetric
@@ -1,2 +1,3 @@
1
1
  export { findStartPage } from '~/src/form/utils/find-start-page.js'
2
2
  export { findDefinitionListFromComponent } from '~/src/form/utils/list.js'
3
+ export { preventUnicodeInEmail } from '~/src/form/utils/prevent-unicode.js'
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Custom Joi validator to prevent Unicode characters in say an email address.
3
+ * Although Joi has Joi.email({ allowUnicode: false }), we can't differentiate from a general
4
+ * email format error or a Unicode error - hance the custom validator to allow a different error message
5
+ * @param {unknown} value
6
+ * @param {CustomHelpers<string>} helpers
7
+ */
8
+ export function preventUnicodeInEmail(value, helpers) {
9
+ if (!value || typeof value !== 'string') {
10
+ return helpers.error('string.empty')
11
+ }
12
+ const invalidCharsRegex = /[^a-zA-Z0-9.!#$%&'*+/=?^_`{|}~@-]/
13
+ const invalid = invalidCharsRegex.exec(value)
14
+ return invalid ? helpers.error('string.unicode') : value
15
+ }
16
+
17
+ /**
18
+ * @import { type CustomHelpers } from 'joi'
19
+ */
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export * from '~/src/form/form-definition/index.js'
10
10
  export * from '~/src/form/form-definition/types.js'
11
11
  export * from '~/src/form/form-definition/helpers.js'
12
12
  export * from '~/src/form/form-metadata/index.js'
13
+ export * from '~/src/form/form-metrics/enums.js'
14
+ export * from '~/src/form/form-metrics/types.js'
13
15
  export * from '~/src/form/form-submission/index.js'
14
16
  export * from '~/src/form/form-submission/enums.js'
15
17
  export * from '~/src/form/utils/index.js'
@@ -135,6 +135,47 @@ export function includesPaymentField(components: ComponentDef[]): boolean {
135
135
  )
136
136
  }
137
137
 
138
+ /**
139
+ * Helper function to determine if a payment question already exists in the form
140
+ */
141
+ export function hasPaymentQuestionInForm(definition: FormDefinition) {
142
+ if (definition.pages.length === 0) {
143
+ return false
144
+ }
145
+
146
+ for (const page of definition.pages) {
147
+ const hasPayment = hasComponents(page)
148
+ ? includesPaymentField(page.components)
149
+ : false
150
+ if (hasPayment) {
151
+ return true
152
+ }
153
+ }
154
+ return false
155
+ }
156
+
157
+ /**
158
+ * Helper function to determine if a specific question type exists in the form
159
+ */
160
+ export function hasSpecificQuestionTypeInForm(
161
+ definition: FormDefinition,
162
+ componentType: ComponentType
163
+ ) {
164
+ if (definition.pages.length === 0) {
165
+ return false
166
+ }
167
+
168
+ for (const page of definition.pages) {
169
+ const hasPayment = hasComponents(page)
170
+ ? page.components.some((component) => component.type === componentType)
171
+ : false
172
+ if (hasPayment) {
173
+ return true
174
+ }
175
+ }
176
+ return false
177
+ }
178
+
138
179
  const SHOW_REPEATER_CONTROLLERS = [ControllerType.Page, ControllerType.Repeat]
139
180
 
140
181
  export function showRepeaterSettings(page: Page): boolean {
@@ -207,6 +248,7 @@ export function replaceCustomControllers(definition: FormDefinition) {
207
248
  const standardControllers = new Set(
208
249
  Object.values(ControllerType)
209
250
  .filter((x) => x !== ControllerType.SummaryWithConfirmationEmail)
251
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion
210
252
  .map((x) => x.toString())
211
253
  )
212
254
 
@@ -215,6 +257,7 @@ export function replaceCustomControllers(definition: FormDefinition) {
215
257
  pages: definition.pages.map((page) => {
216
258
  if (
217
259
  !standardControllers.has(
260
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion
218
261
  (page.controller ?? ControllerType.Page).toString()
219
262
  )
220
263
  ) {
@@ -14,7 +14,9 @@ export {
14
14
  hasComponentsEvenIfNoNext,
15
15
  hasFormComponents,
16
16
  hasNext,
17
+ hasPaymentQuestionInForm,
17
18
  hasRepeater,
19
+ hasSpecificQuestionTypeInForm,
18
20
  includesFileUploadField,
19
21
  includesPaymentField,
20
22
  isControllerName,