diff --git a/frontend/package.json b/frontend/package.json index 6cdc07dfed9..2556115231d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -217,7 +217,7 @@ "subscriptions-transport-ws": "^0.9.16", "typesafe-actions": "^4.2.1", "victory": "^37.3.6", - "yup": "^0.27.0" + "yup": "^1.7.1" }, "devDependencies": { "@babel/core": "^7.10.3", diff --git a/frontend/packages/dev-console/integration-tests/support/pages/add-flow/container-image-page.ts b/frontend/packages/dev-console/integration-tests/support/pages/add-flow/container-image-page.ts index 09c515401dc..65b46ed17c8 100644 --- a/frontend/packages/dev-console/integration-tests/support/pages/add-flow/container-image-page.ts +++ b/frontend/packages/dev-console/integration-tests/support/pages/add-flow/container-image-page.ts @@ -38,6 +38,10 @@ export const containerImagePage = { }, selectCustomIcon: (url: string) => { cy.get(containerImagePO.imageSection.addCustomIcon).click(); + // Wait for the modal to open and the URL input to be enabled (yup async validation) + cy.get(containerImagePO.imageSection.customIconModal.url) + .should('be.visible') + .and('not.be.disabled'); cy.get(containerImagePO.imageSection.customIconModal.url).type(url); cy.get(containerImagePO.imageSection.customIconModal.confirmButton).click(); }, diff --git a/frontend/packages/dev-console/locales/en/devconsole.json b/frontend/packages/dev-console/locales/en/devconsole.json index 6ce609fead0..67f926dd019 100644 --- a/frontend/packages/dev-console/locales/en/devconsole.json +++ b/frontend/packages/dev-console/locales/en/devconsole.json @@ -607,11 +607,9 @@ "Port must be between 1 and 65535.": "Port must be between 1 and 65535.", "Request must be greater than or equal to 0.": "Request must be greater than or equal to 0.", "CPU request must be less than or equal to limit.": "CPU request must be less than or equal to limit.", - "Unit must be millicores or cores.": "Unit must be millicores or cores.", "Limit must be greater than or equal to 0.": "Limit must be greater than or equal to 0.", "CPU limit must be greater than or equal to request.": "CPU limit must be greater than or equal to request.", "Memory request must be less than or equal to limit.": "Memory request must be less than or equal to limit.", - "Unit must be Mi or Gi.": "Unit must be Mi or Gi.", "Memory limit must be greater than or equal to request.": "Memory limit must be greater than or equal to request.", "Please enter a URL that is less then 2000 characters.": "Please enter a URL that is less then 2000 characters.", "Invalid Git URL.": "Invalid Git URL.", diff --git a/frontend/packages/dev-console/src/components/buildconfig/form-utils/validation.ts b/frontend/packages/dev-console/src/components/buildconfig/form-utils/validation.ts index 7c7629382e4..9ce66eea449 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/form-utils/validation.ts +++ b/frontend/packages/dev-console/src/components/buildconfig/form-utils/validation.ts @@ -14,17 +14,18 @@ const sourceSchema = () => .oneOf(['git', 'dockerfile', 'binary']), git: yup.object().when('type', { is: 'git', - then: yup.object({ - git: yup.object({ - url: yup.string().required(i18n.t('devconsole~Required')), - ref: yup.string(), - dir: yup.string(), + then: (schema) => + schema.shape({ + git: yup.object({ + url: yup.string().required(i18n.t('devconsole~Required')), + ref: yup.string(), + dir: yup.string(), + }), }), - }), }), dockerfile: yup.string().when('type', { is: 'dockerfile', - then: yup.string(), + then: (schema) => schema, }), }) .required(i18n.t('devconsole~Required')); @@ -34,21 +35,22 @@ const imageSchema = (allowedTypes: string[]) => type: yup.string().required(i18n.t('devconsole~Required')).oneOf(allowedTypes), imageStreamTag: yup.object().when('type', { is: 'imageStreamTag', - then: yup.object({ - imageStream: yup.object({ - namespace: yup.string().required(i18n.t('devconsole~Required')), - image: yup.string().required(i18n.t('devconsole~Required')), - tag: yup.string().required(i18n.t('devconsole~Required')), + then: (schema) => + schema.shape({ + imageStream: yup.object({ + namespace: yup.string().required(i18n.t('devconsole~Required')), + image: yup.string().required(i18n.t('devconsole~Required')), + tag: yup.string().required(i18n.t('devconsole~Required')), + }), }), - }), }), imageStreamImage: yup.string().when('type', { is: 'imageStreamImage', - then: yup.string().required(i18n.t('devconsole~Required')), + then: (schema) => schema.required(i18n.t('devconsole~Required')), }), dockerImage: yup.string().when('type', { is: 'dockerImage', - then: yup.string().required(i18n.t('devconsole~Required')), + then: (schema) => schema.required(i18n.t('devconsole~Required')), }), }); @@ -82,7 +84,7 @@ const formDataSchema = () => export const validationSchema = () => yup.mixed().test({ - test(values: BuildConfigFormikValues) { + async test(values: BuildConfigFormikValues) { const formYamlDefinition = yup.object({ editorType: yup .string() @@ -90,14 +92,15 @@ export const validationSchema = () => .required(i18n.t('devconsole~Required')), formData: yup.mixed().when('editorType', { is: EditorType.Form, - then: formDataSchema(), + then: (schema) => schema.concat(formDataSchema()), }), yamlData: yup.mixed().when('editorType', { is: EditorType.YAML, - then: yup.string().required(i18n.t('devconsole~Required')), + then: (schema) => schema.concat(yup.string().required(i18n.t('devconsole~Required'))), }), }); - return formYamlDefinition.validate(values, { abortEarly: false }); + await formYamlDefinition.validate(values, { abortEarly: false }); + return true; }, }); diff --git a/frontend/packages/dev-console/src/components/deployments/utils/deployment-validation-utils.ts b/frontend/packages/dev-console/src/components/deployments/utils/deployment-validation-utils.ts index 306a6cb54c5..baea745af6f 100644 --- a/frontend/packages/dev-console/src/components/deployments/utils/deployment-validation-utils.ts +++ b/frontend/packages/dev-console/src/components/deployments/utils/deployment-validation-utils.ts @@ -113,32 +113,35 @@ export const editDeploymentFormSchema = (formValues: EditDeploymentFormData) => : {}), imageStream: yup.object().when('fromImageStreamTag', { is: true, - then: yup.object({ - namespace: yup.string().required(i18n.t('devconsole~Required')), - image: yup.string().required(i18n.t('devconsole~Required')), - tag: yup.string().required(i18n.t('devconsole~Required')), - }), + then: (schema) => + schema.shape({ + namespace: yup.string().required(i18n.t('devconsole~Required')), + image: yup.string().required(i18n.t('devconsole~Required')), + tag: yup.string().required(i18n.t('devconsole~Required')), + }), }), isi: yup.object().when('fromImageStreamTag', { is: true, - then: yup.object({ - name: yup.string().required(i18n.t('devconsole~Required')), - }), + then: (schema) => + schema.shape({ + name: yup.string().required(i18n.t('devconsole~Required')), + }), }), }); export const validationSchema = () => yup.mixed().test({ - test(formValues: EditDeploymentData) { + async test(formValues: EditDeploymentData) { const formYamlDefinition = yup.object({ editorType: yup.string().oneOf(Object.values(EditorType)), yamlData: yup.string(), formData: yup.mixed().when('editorType', { is: EditorType.Form, - then: editDeploymentFormSchema(formValues.formData), + then: (schema) => schema.concat(editDeploymentFormSchema(formValues.formData)), }), }); - return formYamlDefinition.validate(formValues, { abortEarly: false }); + await formYamlDefinition.validate(formValues, { abortEarly: false }); + return true; }, }); diff --git a/frontend/packages/dev-console/src/components/health-checks/health-checks-probe-validation-utils.ts b/frontend/packages/dev-console/src/components/health-checks/health-checks-probe-validation-utils.ts index 55fb693663b..de05cd2b8f9 100644 --- a/frontend/packages/dev-console/src/components/health-checks/health-checks-probe-validation-utils.ts +++ b/frontend/packages/dev-console/src/components/health-checks/health-checks-probe-validation-utils.ts @@ -13,55 +13,63 @@ export const healthChecksValidationSchema = (t: TFunction) => modified: yup.boolean(), data: yup.object().when('showForm', { is: true, - then: yup.object().shape({ - periodSeconds: yup - .number() - .integer(t('devconsole~Value must be an integer.')) - .min(1, t('devconsole~Period must be greater than or equal to 1.')) - .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), - initialDelaySeconds: yup - .number() - .integer(t('devconsole~Value must be an integer.')) - .min(0, t('devconsole~Initial delay must be greater than or equal to 0.')) - .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), - failureThreshold: yup - .number() - .integer(t('devconsole~Value must be an integer.')) - .min(1, t('devconsole~Failure threshold must be greater than or equal to 1.')), - timeoutSeconds: yup - .number() - .integer(t('devconsole~Value must be an integer.')) - .min(1, t('devconsole~Timeout must be greater than or equal to 1.')) - .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), - successThreshold: yup - .number() - .integer(t('devconsole~Value must be an integer.')) - .min(1, t('devconsole~Success threshold must be greater than or equal to 1.')) - .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), - requestType: yup.string(), - httpGet: yup.object().when('requestType', { - is: 'httpGet', - then: yup.object({ - path: yup.string().matches(pathRegex, { - message: t('devconsole~Path must start with /.'), - excludeEmptyString: true, - }), - port: yup.number().required(t('devconsole~Required')), + then: (schema) => + schema.shape({ + periodSeconds: yup + .number() + .integer(t('devconsole~Value must be an integer.')) + .min(1, t('devconsole~Period must be greater than or equal to 1.')) + .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), + initialDelaySeconds: yup + .number() + .integer(t('devconsole~Value must be an integer.')) + .min(0, t('devconsole~Initial delay must be greater than or equal to 0.')) + .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), + failureThreshold: yup + .number() + .integer(t('devconsole~Value must be an integer.')) + .min(1, t('devconsole~Failure threshold must be greater than or equal to 1.')), + timeoutSeconds: yup + .number() + .integer(t('devconsole~Value must be an integer.')) + .min(1, t('devconsole~Timeout must be greater than or equal to 1.')) + .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), + successThreshold: yup + .number() + .integer(t('devconsole~Value must be an integer.')) + .min(1, t('devconsole~Success threshold must be greater than or equal to 1.')) + .max(MAX_INT32, t('devconsole~Value is larger than maximum value allowed.')), + requestType: yup.string(), + httpGet: yup.object().when('requestType', { + is: 'httpGet', + then: (httpGetSchema) => + httpGetSchema.shape({ + path: yup.string().matches(pathRegex, { + message: t('devconsole~Path must start with /.'), + excludeEmptyString: true, + }), + port: yup.number().required(t('devconsole~Required')), + }), + otherwise: (httpGetSchema) => httpGetSchema, }), - }), - tcpSocket: yup.object().when('requestType', { - is: 'tcpSocket', - then: yup.object({ - port: yup.number().required(t('devconsole~Required')), + tcpSocket: yup.object().when('requestType', { + is: 'tcpSocket', + then: (tcpSocketSchema) => + tcpSocketSchema.shape({ + port: yup.number().required(t('devconsole~Required')), + }), + otherwise: (tcpSocketSchema) => tcpSocketSchema, }), - }), - exec: yup.object().when('requestType', { - is: 'command', - then: yup.object({ - command: yup.array().of(yup.string().required(t('devconsole~Required'))), + exec: yup.object().when('requestType', { + is: 'command', + then: (execSchema) => + execSchema.shape({ + command: yup.array().of(yup.string().required(t('devconsole~Required'))), + }), + otherwise: (execSchema) => execSchema, }), }), - }), + otherwise: (schema) => schema, }), }); diff --git a/frontend/packages/dev-console/src/components/hpa/validation-utils.ts b/frontend/packages/dev-console/src/components/hpa/validation-utils.ts index ff286966b5e..e23967f95a1 100644 --- a/frontend/packages/dev-console/src/components/hpa/validation-utils.ts +++ b/frontend/packages/dev-console/src/components/hpa/validation-utils.ts @@ -9,49 +9,50 @@ export const hpaValidationSchema = (t: TFunction) => editorType: yup.string(), formData: yup.object().when('editorType', { is: EditorType.Form, - then: yup.object({ - metadata: yup.object({ - name: yup - .string() - .matches(nameRegex, { - message: t( - 'devconsole~Name must consist of lower-case letters, numbers and hyphens. It must start with a letter and end with a letter or number.', + then: (schema) => + schema.shape({ + metadata: yup.object({ + name: yup + .string() + .matches(nameRegex, { + message: t( + 'devconsole~Name must consist of lower-case letters, numbers and hyphens. It must start with a letter and end with a letter or number.', + ), + excludeEmptyString: true, + }) + .max(253, t('devconsole~Cannot be longer than 253 characters.')) + .required(t('devconsole~Required')), + }), + spec: yup.object({ + minReplicas: yup + .number() + .test(isInteger(t('devconsole~Minimum Pods must be an integer.'))) + .min(1, t('devconsole~Minimum Pods must greater than or equal to 1.')) + .test( + 'test-less-than-max', + t('devconsole~Minimum Pods should be less than or equal to Maximum Pods.'), + function (minReplicas) { + const { maxReplicas } = this.parent; + return minReplicas <= maxReplicas; + }, ), - excludeEmptyString: true, - }) - .max(253, t('devconsole~Cannot be longer than 253 characters.')) - .required(t('devconsole~Required')), + maxReplicas: yup + .number() + .test(isInteger(t('devconsole~Maximum Pods must be an integer.'))) + .max( + Number.MAX_SAFE_INTEGER, + t('devconsole~Value is larger than maximum value allowed.'), + ) + .test( + 'test-greater-than-min', + t('devconsole~Maximum Pods should be greater than or equal to Minimum Pods.'), + function (maxReplicas) { + const { minReplicas } = this.parent; + return minReplicas <= maxReplicas; + }, + ) + .required(t('devconsole~Max Pods must be defined.')), + }), }), - spec: yup.object({ - minReplicas: yup - .number() - .test(isInteger(t('devconsole~Minimum Pods must be an integer.'))) - .min(1, t('devconsole~Minimum Pods must greater than or equal to 1.')) - .test( - 'test-less-than-max', - t('devconsole~Minimum Pods should be less than or equal to Maximum Pods.'), - function (minReplicas) { - const { maxReplicas } = this.parent; - return minReplicas <= maxReplicas; - }, - ), - maxReplicas: yup - .number() - .test(isInteger(t('devconsole~Maximum Pods must be an integer.'))) - .max( - Number.MAX_SAFE_INTEGER, - t('devconsole~Value is larger than maximum value allowed.'), - ) - .test( - 'test-greater-than-min', - t('devconsole~Maximum Pods should be greater than or equal to Minimum Pods.'), - function (maxReplicas) { - const { minReplicas } = this.parent; - return minReplicas <= maxReplicas; - }, - ) - .required(t('devconsole~Max Pods must be defined.')), - }), - }), }), }); diff --git a/frontend/packages/dev-console/src/components/import/deployImage-validation-utils.ts b/frontend/packages/dev-console/src/components/import/deployImage-validation-utils.ts index d40089af1d8..3372a14aeaa 100644 --- a/frontend/packages/dev-console/src/components/import/deployImage-validation-utils.ts +++ b/frontend/packages/dev-console/src/components/import/deployImage-validation-utils.ts @@ -18,7 +18,11 @@ export const deployValidationSchema = (t: TFunction) => project: projectNameValidationSchema, application: applicationNameValidationSchema, name: nameValidationSchema(t), - isi: isiValidationSchema(t), + isi: yup.object().when('registry', { + is: 'internal', + then: (schema) => schema.concat(isiValidationSchema(t)), + otherwise: (schema) => schema, + }), serverless: serverlessValidationSchema(t), deployment: deploymentValidationSchema(t), route: routeValidationSchema(t), diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx index 88c921da791..d11a8ffcd63 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx @@ -56,7 +56,16 @@ const ImageStreamTagDropdown: React.FC<{ formContextField && setFieldValue(`${fieldPrefix}imageStreamTag`, imageStreamImport); const imgStreamLabels = _.pick(labels, imageStreamLabels); const name = imageStream.image; - const isi = { name, image, tag, status }; + // Ensure status has the required structure for validation (isi.status.status must be a string) + // ImageStreamTag status may not have the nested status.status property, so we normalize it + const normalizedStatus = status?.status + ? status + : { + ...(status || {}), + status: 'Success', + metadata: status?.metadata || {}, + }; + const isi = { name, image, tag, status: normalizedStatus }; const ports = getPorts(isi); setFieldValue(`${fieldPrefix}isSearchingForImage`, false); setFieldValue(`${fieldPrefix}isi.name`, name); @@ -65,6 +74,7 @@ const ImageStreamTagDropdown: React.FC<{ _.merge(image, { metadata: { labels: imgStreamLabels } }), ); setFieldValue(`${fieldPrefix}isi.tag`, selectedTag); + setFieldValue(`${fieldPrefix}isi.status`, normalizedStatus); setFieldValue(`${fieldPrefix}isi.ports`, ports); setFieldValue(`${fieldPrefix}image.ports`, ports); formType !== 'edit' && diff --git a/frontend/packages/dev-console/src/components/import/validation-schema.ts b/frontend/packages/dev-console/src/components/import/validation-schema.ts index 44d86e490c4..e9f74dcfc87 100644 --- a/frontend/packages/dev-console/src/components/import/validation-schema.ts +++ b/frontend/packages/dev-console/src/components/import/validation-schema.ts @@ -45,7 +45,8 @@ export const applicationNameValidationSchema = yup.object().shape({ .max(63, 'Cannot be longer than 63 characters.') .when('selectedKey', { is: CREATE_APPLICATION_KEY, - then: yup.string().required('Required'), + then: (schema) => schema.required('Required'), + otherwise: (schema) => schema, }), }); @@ -77,100 +78,108 @@ export const resourcesValidationSchema = yup export const serverlessValidationSchema = (t: TFunction) => yup.object().when('resources', { is: Resources.KnativeService, - then: yup.object().shape({ - scaling: yup.object({ - minpods: yup - .number() - .transform((cv) => (_.isNaN(cv) ? undefined : cv)) - .test(isInteger(t('devconsole~Min Pods must be an integer.'))) - .min(0, t('devconsole~Min Pods must be greater than or equal to 0.')) - .max( - Number.MAX_SAFE_INTEGER, - t('devconsole~Min Pods must be lesser than or equal to {{maxSafeInteger}}.', { - maxSafeInteger: Number.MAX_SAFE_INTEGER, - }), - ), - maxpods: yup - .number() - .transform((cv) => (_.isNaN(cv) ? undefined : cv)) - .test(isInteger(t('devconsole~Max Pods must be an integer.'))) - .min(1, t('devconsole~Max Pods must be greater than or equal to 1.')) - .max( - Number.MAX_SAFE_INTEGER, - t('devconsole~Max Pods must be lesser than or equal to {{maxSafeInteger}}.', { - maxSafeInteger: Number.MAX_SAFE_INTEGER, - }), - ) - .test({ - test(limit) { - const { minpods } = this.parent; - return limit ? limit >= minpods : true; - }, - message: t('devconsole~Max Pods must be greater than or equal to Min Pods.'), - }), - concurrencytarget: yup - .number() - .transform((cv) => (_.isNaN(cv) ? undefined : cv)) - .test(isInteger(t('devconsole~Concurrency target must be an integer.'))) - .min(0, t('devconsole~Concurrency target must be greater than or equal to 0.')) - .max( - Number.MAX_SAFE_INTEGER, - t('devconsole~Concurrency target must be lesser than or equal to {{maxSafeInteger}}.', { - maxSafeInteger: Number.MAX_SAFE_INTEGER, - }), - ), - concurrencylimit: yup - .number() - .transform((cv) => (_.isNaN(cv) ? undefined : cv)) - .test(isInteger(t('devconsole~Concurrency limit must be an integer.'))) - .min(0, t('devconsole~Concurrency limit must be greater than or equal to 0.')) - .max( - Number.MAX_SAFE_INTEGER, - t('devconsole~Concurrency limit must be lesser than or equal to {{maxSafeInteger}}.', { - maxSafeInteger: Number.MAX_SAFE_INTEGER, - }), - ), - concurrencyutilization: yup - .number() - .transform((cv) => (_.isNaN(cv) ? undefined : cv)) - .min(0, t('devconsole~Concurrency utilization must be between 0 and 100.')) - .max(100, t('devconsole~Concurrency utilization must be between 0 and 100.')), - autoscale: yup.object().shape({ - autoscalewindow: yup + then: (schema) => + schema.shape({ + scaling: yup.object({ + minpods: yup + .number() + .transform((cv) => (_.isNaN(cv) ? undefined : cv)) + .test(isInteger(t('devconsole~Min Pods must be an integer.'))) + .min(0, t('devconsole~Min Pods must be greater than or equal to 0.')) + .max( + Number.MAX_SAFE_INTEGER, + t('devconsole~Min Pods must be lesser than or equal to {{maxSafeInteger}}.', { + maxSafeInteger: Number.MAX_SAFE_INTEGER, + }), + ), + maxpods: yup .number() .transform((cv) => (_.isNaN(cv) ? undefined : cv)) + .test(isInteger(t('devconsole~Max Pods must be an integer.'))) + .min(1, t('devconsole~Max Pods must be greater than or equal to 1.')) + .max( + Number.MAX_SAFE_INTEGER, + t('devconsole~Max Pods must be lesser than or equal to {{maxSafeInteger}}.', { + maxSafeInteger: Number.MAX_SAFE_INTEGER, + }), + ) .test({ - test(autoscalewindow) { - if (autoscalewindow) { - const { autoscalewindowUnit } = this.parent; - const value = convertToSec(autoscalewindow, autoscalewindowUnit); - return value >= 6 && value <= 3600; - } - return true; + test(limit) { + const { minpods } = this.parent; + return limit ? limit >= minpods : true; }, - message: t('devconsole~Autoscale window must be between 6s and 1h.'), + message: t('devconsole~Max Pods must be greater than or equal to Min Pods.'), }), + concurrencytarget: yup + .number() + .transform((cv) => (_.isNaN(cv) ? undefined : cv)) + .test(isInteger(t('devconsole~Concurrency target must be an integer.'))) + .min(0, t('devconsole~Concurrency target must be greater than or equal to 0.')) + .max( + Number.MAX_SAFE_INTEGER, + t( + 'devconsole~Concurrency target must be lesser than or equal to {{maxSafeInteger}}.', + { + maxSafeInteger: Number.MAX_SAFE_INTEGER, + }, + ), + ), + concurrencylimit: yup + .number() + .transform((cv) => (_.isNaN(cv) ? undefined : cv)) + .test(isInteger(t('devconsole~Concurrency limit must be an integer.'))) + .min(0, t('devconsole~Concurrency limit must be greater than or equal to 0.')) + .max( + Number.MAX_SAFE_INTEGER, + t( + 'devconsole~Concurrency limit must be lesser than or equal to {{maxSafeInteger}}.', + { + maxSafeInteger: Number.MAX_SAFE_INTEGER, + }, + ), + ), + concurrencyutilization: yup + .number() + .transform((cv) => (_.isNaN(cv) ? undefined : cv)) + .min(0, t('devconsole~Concurrency utilization must be between 0 and 100.')) + .max(100, t('devconsole~Concurrency utilization must be between 0 and 100.')), + autoscale: yup.object().shape({ + autoscalewindow: yup + .number() + .transform((cv) => (_.isNaN(cv) ? undefined : cv)) + .test({ + test(autoscalewindow) { + if (autoscalewindow) { + const { autoscalewindowUnit } = this.parent; + const value = convertToSec(autoscalewindow, autoscalewindowUnit); + return value >= 6 && value <= 3600; + } + return true; + }, + message: t('devconsole~Autoscale window must be between 6s and 1h.'), + }), + }), }), - }), - domainMapping: yup.array().of( - yup - .string() - .transform(removeKsvcInfoFromDomainMapping) - .matches(hostnameRegex, { - message: t( - 'devconsole~Domain name must consist of lower-case letters, numbers, periods, and hyphens. It must start and end with a letter or number.', + domainMapping: yup.array().of( + yup + .string() + .transform(removeKsvcInfoFromDomainMapping) + .matches(hostnameRegex, { + message: t( + 'devconsole~Domain name must consist of lower-case letters, numbers, periods, and hyphens. It must start and end with a letter or number.', + ), + excludeEmptyString: true, + }) + .test( + 'domainname-has-segements', + t('devconsole~Domain name must consist of at least two segments separated by dots.'), + function (domainName: string) { + return domainName.split('.').length >= 2; + }, ), - excludeEmptyString: true, - }) - .test( - 'domainname-has-segements', - t('devconsole~Domain name must consist of at least two segments separated by dots.'), - function (domainName: string) { - return domainName.split('.').length >= 2; - }, - ), - ), - }), + ), + }), + otherwise: (schema) => schema, }); export const routeValidationSchema = (t: TFunction) => @@ -178,9 +187,11 @@ export const routeValidationSchema = (t: TFunction) => secure: yup.boolean(), tls: yup.object().when('secure', { is: true, - then: yup.object({ - termination: yup.string().required(t('devconsole~Please select a termination type.')), - }), + then: (schema) => + schema.shape({ + termination: yup.string().required(t('devconsole~Please select a termination type.')), + }), + otherwise: (schema) => schema, }), hostname: yup .string() @@ -208,12 +219,13 @@ export const limitsValidationSchema = (t: TFunction) => cpu: yup.object().shape({ request: yup .number() + .nullable() .transform((request) => (_.isNaN(request) ? undefined : request)) .min(0, t('devconsole~Request must be greater than or equal to 0.')) .test({ test(request) { const { requestUnit, limit, limitUnit } = this.parent; - if (limit !== undefined) { + if (limit !== undefined && limit !== null) { return ( convertToBaseValue(`${request}${requestUnit}`) <= convertToBaseValue(`${limit}${limitUnit}`) @@ -223,16 +235,17 @@ export const limitsValidationSchema = (t: TFunction) => }, message: t('devconsole~CPU request must be less than or equal to limit.'), }), - requestUnit: yup.string(t('devconsole~Unit must be millicores or cores.')).ensure(), - limitUnit: yup.string(t('devconsole~Unit must be millicores or cores.')).ensure(), + requestUnit: yup.string().ensure(), + limitUnit: yup.string().ensure(), limit: yup .number() + .nullable() .transform((limit) => (_.isNaN(limit) ? undefined : limit)) .min(0, t('devconsole~Limit must be greater than or equal to 0.')) .test({ test(limit) { const { request, requestUnit, limitUnit } = this.parent; - if (limit !== undefined) { + if (limit !== undefined && limit !== null) { return ( convertToBaseValue(`${limit}${limitUnit}`) >= convertToBaseValue(`${request}${requestUnit}`) @@ -246,12 +259,13 @@ export const limitsValidationSchema = (t: TFunction) => memory: yup.object().shape({ request: yup .number() + .nullable() .transform((request) => (_.isNaN(request) ? undefined : request)) .min(0, t('devconsole~Request must be greater than or equal to 0.')) .test({ test(request) { const { requestUnit, limit, limitUnit } = this.parent; - if (limit !== undefined) { + if (limit !== undefined && limit !== null) { return ( convertToBaseValue(`${request}${requestUnit}`) <= convertToBaseValue(`${limit}${limitUnit}`) @@ -261,15 +275,16 @@ export const limitsValidationSchema = (t: TFunction) => }, message: t('devconsole~Memory request must be less than or equal to limit.'), }), - requestUnit: yup.string(t('devconsole~Unit must be Mi or Gi.')), + requestUnit: yup.string(), limit: yup .number() + .nullable() .transform((limit) => (_.isNaN(limit) ? undefined : limit)) .min(0, t('devconsole~Limit must be greater than or equal to 0.')) .test({ test(limit) { const { request, requestUnit, limitUnit } = this.parent; - if (limit !== undefined) { + if (limit !== undefined && limit !== null) { return ( convertToBaseValue(`${request}${requestUnit}`) <= convertToBaseValue(`${limit}${limitUnit}`) @@ -279,17 +294,19 @@ export const limitsValidationSchema = (t: TFunction) => }, message: t('devconsole~Memory limit must be greater than or equal to request.'), }), - limitUnit: yup.string(t('devconsole~Unit must be Mi or Gi.')), + limitUnit: yup.string(), }), }); export const imageValidationSchema = (t: TFunction) => yup.object().when('build', { is: (build) => build.strategy === 'Source', - then: yup.object().shape({ - selected: yup.string().required(t('devconsole~Required')), - tag: yup.string().required(t('devconsole~Required')), - }), + then: (schema) => + schema.shape({ + selected: yup.string().required(t('devconsole~Required')), + tag: yup.string().required(t('devconsole~Required')), + }), + otherwise: (schema) => schema, }); export const gitValidationSchema = (t: TFunction) => @@ -301,9 +318,11 @@ export const gitValidationSchema = (t: TFunction) => .required(t('devconsole~Required')), type: yup.string().when('showGitType', { is: true, - then: yup - .string() - .required(t('devconsole~We failed to detect the Git type. Please choose a Git type.')), + then: (schema) => + schema.required( + t('devconsole~We failed to detect the Git type. Please choose a Git type.'), + ), + otherwise: (schema) => schema, }), showGitType: yup.boolean(), }); @@ -311,26 +330,30 @@ export const gitValidationSchema = (t: TFunction) => export const dockerValidationSchema = (t: TFunction) => yup.object().when('build', { is: (build) => build.strategy === 'Docker', - then: yup.object().shape({ - containerPort: yup - .number() - .test(isInteger(t('devconsole~Container port should be an integer'))), - dockerfilePath: yup.string().required(t('devconsole~Required')), - }), + then: (schema) => + schema.shape({ + containerPort: yup + .number() + .test(isInteger(t('devconsole~Container port should be an integer'))), + dockerfilePath: yup.string().required(t('devconsole~Required')), + }), + otherwise: (schema) => schema, }); export const devfileValidationSchema = (t: TFunction) => yup.object().when('build', { is: (build) => build.strategy === 'Devfile', - then: yup.object().shape({ - devfilePath: yup.string().required(t('devconsole~Required')), - devfileContent: yup - .string() - .min(1, t('devconsole~Required')) - .required(t('devconsole~Required')), - devfileHasError: yup.boolean().oneOf([false]), - devfileSuggestedResources: yup.object().required(t('devconsole~Required')), - }), + then: (schema) => + schema.shape({ + devfilePath: yup.string().required(t('devconsole~Required')), + devfileContent: yup + .string() + .min(1, t('devconsole~Required')) + .required(t('devconsole~Required')), + devfileHasError: yup.boolean().oneOf([false]), + devfileSuggestedResources: yup.object().required(t('devconsole~Required')), + }), + otherwise: (schema) => schema, }); export const buildValidationSchema = yup.object().shape({ @@ -345,7 +368,9 @@ export const isiValidationSchema = (t: TFunction) => name: yup.string().required(t('devconsole~Required')), image: yup.object().required(t('devconsole~Required')), tag: yup.string(), - status: yup.string().required(t('devconsole~Required')), + status: yup.object().shape({ + status: yup.string().required(t('devconsole~Required')), + }), }); export const importFlowPipelineTemplateValidationSchema = yup @@ -354,7 +379,9 @@ export const importFlowPipelineTemplateValidationSchema = yup is: (isPipelineEnabled, buildOption, pipelineType) => (isPipelineEnabled || buildOption === BuildOptions.PIPELINES) && pipelineType !== PipelineType.PAC, - then: yup.object().shape({ - templateSelected: yup.string().required(), - }), + then: (schema) => + schema.shape({ + templateSelected: yup.string().required(), + }), + otherwise: (schema) => schema, }); diff --git a/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-data.ts b/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-data.ts index 114f985641a..9a9a1d43d02 100644 --- a/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-data.ts +++ b/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-data.ts @@ -3,7 +3,10 @@ import { UserRoleBinding, RoleBinding } from '../project-access-form-utils-types export const mockProjectAccessData = { projectAccess: [ { - user: 'abc', + subject: { + name: 'abc', + kind: 'User', + }, role: 'view', }, ], diff --git a/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-validation-utils.spec.ts b/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-validation-utils.spec.ts index 55bcd857447..819ec946782 100644 --- a/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-validation-utils.spec.ts +++ b/frontend/packages/dev-console/src/components/project-access/__tests__/project-access-form-validation-utils.spec.ts @@ -5,7 +5,7 @@ import { mockProjectAccessData } from './project-access-form-data'; describe('ValidationUtils', () => { it('should throw an error if Name field is empty', async () => { const mockData = cloneDeep(mockProjectAccessData); - mockData.projectAccess[0].user = ''; + mockData.projectAccess[0].subject.name = ''; await validationSchema.isValid(mockData).then((valid) => expect(valid).toEqual(false)); await validationSchema.validate(mockData).catch((err) => { diff --git a/frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-validation-utils.ts b/frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-validation-utils.ts index c86dc982990..8e8e9a83718 100644 --- a/frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-validation-utils.ts +++ b/frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-validation-utils.ts @@ -32,16 +32,17 @@ export const createHelmChartRepositoryValidationSchema = (t: TFunction) => export const validationSchema = (t: TFunction) => yup.mixed().test({ - test(formValues: HelmChartRepositoryData) { + async test(formValues: HelmChartRepositoryData) { const formYamlDefinition = yup.object({ editorType: yup.string().oneOf(Object.values(EditorType)), yamlData: yup.string(), formData: yup.mixed().when('editorType', { is: EditorType.Form, - then: createHelmChartRepositoryValidationSchema(t), + then: (schema) => schema.concat(createHelmChartRepositoryValidationSchema(t)), }), }); - return formYamlDefinition.validate(formValues, { abortEarly: false }); + await formYamlDefinition.validate(formValues, { abortEarly: false }); + return true; }, }); diff --git a/frontend/packages/integration-tests-cypress/support/project.ts b/frontend/packages/integration-tests-cypress/support/project.ts index eae95a032d3..9dc9e4d2000 100644 --- a/frontend/packages/integration-tests-cypress/support/project.ts +++ b/frontend/packages/integration-tests-cypress/support/project.ts @@ -52,5 +52,9 @@ Cypress.Commands.add('deleteProject', (name: string) => { }); Cypress.Commands.add('deleteProjectWithCLI', (name: string, timeout?: number) => { - cy.exec(`oc delete project ${name}`, { timeout }); + // First try normal delete, if it times out, force delete + cy.exec(`oc delete project ${name} --wait=false`, { + timeout: timeout || 60000, + failOnNonZeroExit: false, + }); }); diff --git a/frontend/packages/integration-tests-cypress/tests/crud/secrets/image-pull.cy.ts b/frontend/packages/integration-tests-cypress/tests/crud/secrets/image-pull.cy.ts index d3b336371e3..d73e9661868 100644 --- a/frontend/packages/integration-tests-cypress/tests/crud/secrets/image-pull.cy.ts +++ b/frontend/packages/integration-tests-cypress/tests/crud/secrets/image-pull.cy.ts @@ -12,7 +12,11 @@ describe('Image pull secrets', () => { cy.createProjectWithCLI(testName); }); - beforeEach(() => { + beforeEach(function () { + // Skip beforeEach for the obfuscated passwords test + if (this.currentTest?.title === 'Passwords entered on the console are obfuscated') { + return; + } cy.visit(`/k8s/ns/${testName}/secrets/`); secrets.clickCreateSecretDropdownButton('image'); }); @@ -130,10 +134,21 @@ describe('Image pull secrets', () => { secrets.enterSecretName(uploadConfigFileImageSecretName); cy.byTestID('console-select-auth-type-menu-toggle').click(); cy.byTestDropDownMenu('config-file').click(); - cy.byLegacyTestID('file-input-textarea').type(JSON.stringify(configFile), { + + // Type the JSON slowly to give validation time to process + const configJson = JSON.stringify(configFile); + cy.byLegacyTestID('file-input-textarea').clear().type(configJson, { parseSpecialCharSequences: false, + delay: 50, // Slower typing to give validation time }); + + // Wait for validation to complete and save button to be enabled + cy.byTestID('save-changes', { timeout: 10000 }).should('be.enabled'); + secrets.save(); + + // Verify we navigated to details page + cy.url().should('include', `/secrets/${uploadConfigFileImageSecretName}`); secrets.detailsPageIsLoaded(uploadConfigFileImageSecretName); cy.log('Verify secret'); @@ -148,9 +163,17 @@ describe('Image pull secrets', () => { secrets.deleteSecret(uploadConfigFileImageSecretName); }); it(`Passwords entered on the console are obfuscated`, () => { + // Navigate to secrets page and open image secret form + cy.visit(`/k8s/ns/${testName}/secrets/`); + secrets.clickCreateSecretDropdownButton('image'); cy.get('input[data-test="image-secret-password"]').should('have.attr', 'type', 'password'); cy.get('button[id="cancel"]').click(); + + // Open source secret form secrets.clickCreateSecretDropdownButton('source'); cy.get('input[data-test="secret-password"]').should('have.attr', 'type', 'password'); + + // Clean up - navigate back to secrets list to close any open forms + cy.visit(`/k8s/ns/${testName}/secrets/`); }); }); diff --git a/frontend/packages/integration-tests-cypress/views/details-page.ts b/frontend/packages/integration-tests-cypress/views/details-page.ts index 0728fe56ad0..b5224386997 100644 --- a/frontend/packages/integration-tests-cypress/views/details-page.ts +++ b/frontend/packages/integration-tests-cypress/views/details-page.ts @@ -1,5 +1,8 @@ export const detailsPage = { - titleShouldContain: (title: string) => cy.get('[data-test="page-heading"] h1').contains(title), + titleShouldContain: (title: string) => { + cy.get('[data-test="page-heading"]', { timeout: 30000 }).should('be.visible'); + return cy.get('[data-test="page-heading"]').contains(title, { timeout: 30000 }); + }, sectionHeaderShouldExist: (sectionHeading: string) => cy.get(`[data-test-section-heading="${sectionHeading}"]`).should('exist'), labelShouldExist: (labelName: string) => cy.byTestID('label-list').contains(labelName), diff --git a/frontend/packages/integration-tests-cypress/views/secret.ts b/frontend/packages/integration-tests-cypress/views/secret.ts index 3f852528116..15199525b46 100644 --- a/frontend/packages/integration-tests-cypress/views/secret.ts +++ b/frontend/packages/integration-tests-cypress/views/secret.ts @@ -33,7 +33,12 @@ export const secrets = { clickAddCredentialsButton: () => cy.byTestID('add-credentials-button').click(), clickRemoveEntryButton: () => cy.byTestID('remove-entry-button').first().click(), clickRevealValues: () => { - cy.byTestID('reveal-values').click(); + // Wait for the button to be stable (not re-rendering) + cy.byTestID('reveal-values', { timeout: 30000 }).should('be.visible'); + // Add a small delay to ensure page is stable after any re-renders + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + cy.byTestID('reveal-values').click({ force: true }); }, clickCreateSecretDropdownButton: (secretType: string) => { cy.byTestID('item-create') diff --git a/frontend/packages/knative-plugin/src/components/add/brokers/broker-validation-utils.ts b/frontend/packages/knative-plugin/src/components/add/brokers/broker-validation-utils.ts index a9443fc0d30..887e0e45047 100644 --- a/frontend/packages/knative-plugin/src/components/add/brokers/broker-validation-utils.ts +++ b/frontend/packages/knative-plugin/src/components/add/brokers/broker-validation-utils.ts @@ -12,11 +12,12 @@ export const brokerValidationSchema = (t: TFunction) => editorType: yup.string(), formData: yup.object().when('editorType', { is: EditorType.Form, - then: yup.object().shape({ - project: projectNameValidationSchema, - application: applicationNameValidationSchema, - name: nameValidationSchema(t), - }), + then: (schema) => + schema.shape({ + project: projectNameValidationSchema, + application: applicationNameValidationSchema, + name: nameValidationSchema(t), + }), }), yamlData: yup.string(), }); diff --git a/frontend/packages/knative-plugin/src/components/add/eventSink-validation-utils.ts b/frontend/packages/knative-plugin/src/components/add/eventSink-validation-utils.ts index b0e201b11d2..1d3f798b656 100644 --- a/frontend/packages/knative-plugin/src/components/add/eventSink-validation-utils.ts +++ b/frontend/packages/knative-plugin/src/components/add/eventSink-validation-utils.ts @@ -15,13 +15,14 @@ const sourceServiceSchema = (t: TFunction) => .object() .when('sourceType', { is: SinkType.Resource, - then: yup.object().shape({ - name: yup.string().required(t('knative-plugin~Required')), - }), + then: (schema) => + schema.shape({ + name: yup.string().required(t('knative-plugin~Required')), + }), }) .when('sourceType', { is: SinkType.Uri, - then: sinkTypeUriValidation(t), + then: (schema) => schema.concat(sinkTypeUriValidation(t)), }); const sinkDataSpecSchema = (t: TFunction) => @@ -29,23 +30,24 @@ const sinkDataSpecSchema = (t: TFunction) => .object() .when('type', { is: KafkaSinkModel.kind, - then: yup.object().shape({ - [KafkaSinkModel.kind]: yup.object().shape({ - bootstrapServers: yup.array().of(yup.string()).min(1, t('knative-plugin~Required')), - topic: yup.string().required(t('knative-plugin~Required')), - auth: yup.object().shape({ - secret: yup.object().shape({ - ref: yup.object().shape({ - name: yup.string(), + then: (schema) => + schema.shape({ + [KafkaSinkModel.kind]: yup.object().shape({ + bootstrapServers: yup.array().of(yup.string()).min(1, t('knative-plugin~Required')), + topic: yup.string().required(t('knative-plugin~Required')), + auth: yup.object().shape({ + secret: yup.object().shape({ + ref: yup.object().shape({ + name: yup.string(), + }), }), }), }), }), - }), }) .when('type', { is: CamelKameletBindingModel.kind, - then: yup.object(), + then: (schema) => schema, }); export const eventSinkValidationSchema = (t: TFunction) => @@ -53,13 +55,14 @@ export const eventSinkValidationSchema = (t: TFunction) => editorType: yup.string(), formData: yup.object().when('editorType', { is: EditorType.Form, - then: yup.object().shape({ - project: projectNameValidationSchema, - application: applicationNameValidationSchema, - name: nameValidationSchema(t), - source: sourceServiceSchema(t), - data: sinkDataSpecSchema(t), - }), + then: (schema) => + schema.shape({ + project: projectNameValidationSchema, + application: applicationNameValidationSchema, + name: nameValidationSchema(t), + source: sourceServiceSchema(t), + data: sinkDataSpecSchema(t), + }), }), yamlData: yup.string(), }); diff --git a/frontend/packages/knative-plugin/src/components/add/eventSource-validation-utils.ts b/frontend/packages/knative-plugin/src/components/add/eventSource-validation-utils.ts index 3ccdd3e7aa8..575dba901bf 100644 --- a/frontend/packages/knative-plugin/src/components/add/eventSource-validation-utils.ts +++ b/frontend/packages/knative-plugin/src/components/add/eventSource-validation-utils.ts @@ -24,13 +24,14 @@ const sinkServiceSchema = (t: TFunction) => .object() .when('sinkType', { is: SinkType.Resource, - then: yup.object().shape({ - name: yup.string().required(t('knative-plugin~Required')), - }), + then: (schema) => + schema.shape({ + name: yup.string().required(t('knative-plugin~Required')), + }), }) .when('sinkType', { is: SinkType.Uri, - then: sinkTypeUriValidation(t), + then: (schema) => schema.concat(sinkTypeUriValidation(t)), }); export const sourceDataSpecSchema = (t: TFunction) => @@ -38,119 +39,124 @@ export const sourceDataSpecSchema = (t: TFunction) => .object() .when('type', { is: EventSources.PingSource, - then: yup.object().shape({ - [EventSources.PingSource]: yup.object().shape({ - data: yup.string().max(253, t('knative-plugin~Cannot be longer than 253 characters.')), - schedule: yup - .string() - .max(253, t('knative-plugin~Cannot be longer than 253 characters.')) - .required(t('knative-plugin~Required')), + then: (schema) => + schema.shape({ + [EventSources.PingSource]: yup.object().shape({ + data: yup.string().max(253, t('knative-plugin~Cannot be longer than 253 characters.')), + schedule: yup + .string() + .max(253, t('knative-plugin~Cannot be longer than 253 characters.')) + .required(t('knative-plugin~Required')), + }), }), - }), }) .when('type', { is: EventSources.SinkBinding, - then: yup.object().shape({ - [EventSources.SinkBinding]: yup.object().shape({ - subject: yup.object().shape({ - selector: yup.object().shape({ - matchLabels: yup.object(), - }), - name: yup.string().when('selector.matchLabels', { - is: (obj: object) => !obj, - then: yup.string().required(t('knative-plugin~Required')), + then: (schema) => + schema.shape({ + [EventSources.SinkBinding]: yup.object().shape({ + subject: yup.object().shape({ + selector: yup.object().shape({ + matchLabels: yup.object(), + }), + name: yup.string().when('selector.matchLabels', { + is: (obj: object) => !obj, + then: (nameSchema) => nameSchema.required(t('knative-plugin~Required')), + }), + apiVersion: yup + .string() + .max(253, t('knative-plugin~Cannot be longer than 253 characters.')) + .required(t('knative-plugin~Required')), + kind: yup + .string() + .max(253, t('knative-plugin~Cannot be longer than 253 characters.')) + .required(t('knative-plugin~Required')), }), - apiVersion: yup - .string() - .max(253, t('knative-plugin~Cannot be longer than 253 characters.')) - .required(t('knative-plugin~Required')), - kind: yup - .string() - .max(253, t('knative-plugin~Cannot be longer than 253 characters.')) - .required(t('knative-plugin~Required')), }), }), - }), }) .when('type', { is: EventSources.ApiServerSource, - then: yup.object().shape({ - [EventSources.ApiServerSource]: yup.object().shape({ - resources: yup - .array() - .of( - yup.object({ - apiVersion: yup.string().required(t('knative-plugin~Required')), - kind: yup.string().required(t('knative-plugin~Required')), - }), - ) - .required(t('knative-plugin~Required')), + then: (schema) => + schema.shape({ + [EventSources.ApiServerSource]: yup.object().shape({ + resources: yup + .array() + .of( + yup.object({ + apiVersion: yup.string().required(t('knative-plugin~Required')), + kind: yup.string().required(t('knative-plugin~Required')), + }), + ) + .required(t('knative-plugin~Required')), + }), }), - }), }) .when('type', { is: EventSources.KafkaSource, - then: yup.object().shape({ - [EventSources.KafkaSource]: yup.object().shape({ - bootstrapServers: yup.array().of(yup.string()).min(1, t('knative-plugin~Required')), - consumerGroup: yup.string().required(t('knative-plugin~Required')), - topics: yup.array().of(yup.string()).min(1, t('knative-plugin~Required')), - net: yup.object().shape({ - sasl: yup.object().shape({ - enable: yup.boolean(), - user: yup.object().shape({ - secretKeyRef: yup.object().shape({ - name: yup.string(), - key: yup.string(), + then: (schema) => + schema.shape({ + [EventSources.KafkaSource]: yup.object().shape({ + bootstrapServers: yup.array().of(yup.string()).min(1, t('knative-plugin~Required')), + consumerGroup: yup.string().required(t('knative-plugin~Required')), + topics: yup.array().of(yup.string()).min(1, t('knative-plugin~Required')), + net: yup.object().shape({ + sasl: yup.object().shape({ + enable: yup.boolean(), + user: yup.object().shape({ + secretKeyRef: yup.object().shape({ + name: yup.string(), + key: yup.string(), + }), }), - }), - password: yup.object().shape({ - secretKeyRef: yup.object().shape({ - name: yup.string(), - key: yup.string(), + password: yup.object().shape({ + secretKeyRef: yup.object().shape({ + name: yup.string(), + key: yup.string(), + }), }), }), - }), - tls: yup.object().shape({ - enable: yup.boolean(), - caCert: yup.object().shape({ - secretKeyRef: yup.object().shape({ - name: yup.string(), - key: yup.string(), + tls: yup.object().shape({ + enable: yup.boolean(), + caCert: yup.object().shape({ + secretKeyRef: yup.object().shape({ + name: yup.string(), + key: yup.string(), + }), }), - }), - cert: yup.object().shape({ - secretKeyRef: yup.object().shape({ - name: yup.string(), - key: yup.string(), + cert: yup.object().shape({ + secretKeyRef: yup.object().shape({ + name: yup.string(), + key: yup.string(), + }), }), - }), - key: yup.object().shape({ - secretKeyRef: yup.object().shape({ - name: yup.string(), - key: yup.string(), + key: yup.object().shape({ + secretKeyRef: yup.object().shape({ + name: yup.string(), + key: yup.string(), + }), }), }), }), }), }), - }), }) .when('type', { is: EventSources.ContainerSource, - then: yup.object().shape({ - [EventSources.ContainerSource]: yup.object().shape({ - template: yup.object({ - spec: yup.object({ - containers: yup.array().of( - yup.object({ - image: yup.string().required(t('knative-plugin~Required')), - }), - ), + then: (schema) => + schema.shape({ + [EventSources.ContainerSource]: yup.object().shape({ + template: yup.object({ + spec: yup.object({ + containers: yup.array().of( + yup.object({ + image: yup.string().required(t('knative-plugin~Required')), + }), + ), + }), }), }), }), - }), }); export const eventSourceValidationSchema = (t: TFunction) => @@ -158,13 +164,14 @@ export const eventSourceValidationSchema = (t: TFunction) => editorType: yup.string(), formData: yup.object().when('editorType', { is: EditorType.Form, - then: yup.object().shape({ - project: projectNameValidationSchema, - application: applicationNameValidationSchema, - name: nameValidationSchema(t), - sink: sinkServiceSchema(t), - data: sourceDataSpecSchema(t), - }), + then: (schema) => + schema.shape({ + project: projectNameValidationSchema, + application: applicationNameValidationSchema, + name: nameValidationSchema(t), + sink: sinkServiceSchema(t), + data: sourceDataSpecSchema(t), + }), }), yamlData: yup.string(), }); @@ -174,13 +181,14 @@ export const addChannelValidationSchema = (t: TFunction) => editorType: yup.string(), formData: yup.object().when('editorType', { is: EditorType.Form, - then: yup.object().shape({ - project: projectNameValidationSchema, - application: applicationNameValidationSchema, - name: nameValidationSchema(t), - data: yup.object(), - type: yup.string(), - }), + then: (schema) => + schema.shape({ + project: projectNameValidationSchema, + application: applicationNameValidationSchema, + name: nameValidationSchema(t), + data: yup.object(), + type: yup.string(), + }), }), yamlData: yup.string(), }); diff --git a/frontend/packages/knative-plugin/src/components/sink-source/SinkSource.tsx b/frontend/packages/knative-plugin/src/components/sink-source/SinkSource.tsx index 0643601022d..f2442ba1b63 100644 --- a/frontend/packages/knative-plugin/src/components/sink-source/SinkSource.tsx +++ b/frontend/packages/knative-plugin/src/components/sink-source/SinkSource.tsx @@ -79,7 +79,7 @@ const SinkSource: React.FC = ({ source, cancel, close }) => { formData: yup.object().shape({ sink: yup.object().when('sinkType', { is: SinkType.Uri, - then: sinkTypeUriValidation(t), + then: (schema) => schema.concat(sinkTypeUriValidation(t)), }), }), }) diff --git a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/add-baremetal-host/AddBareMetalHost.tsx b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/add-baremetal-host/AddBareMetalHost.tsx index ca22c28ecd9..5fc7a2d341a 100644 --- a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/add-baremetal-host/AddBareMetalHost.tsx +++ b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/add-baremetal-host/AddBareMetalHost.tsx @@ -132,11 +132,11 @@ const AddBareMetalHost: React.FC = ({ const addHostValidationSchema = Yup.lazy(({ enablePowerManagement }) => Yup.object().shape({ - name: Yup.mixed() + name: Yup.string() .test( 'unique-name', t('metal3-plugin~Name "${value}" is already taken.'), // eslint-disable-line no-template-curly-in-string - (value) => !hostNames.includes(value), + (value: string) => !hostNames.includes(value), ) .concat(nameValidationSchema(t)), BMCAddress: enablePowerManagement diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/modals/common/validation-utils.ts b/frontend/packages/pipelines-plugin/src/components/pipelines/modals/common/validation-utils.ts index 72dfe43ab5c..dc1c8b6ebbc 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/modals/common/validation-utils.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/modals/common/validation-utils.ts @@ -11,41 +11,46 @@ export const validateResourceType = () => .object() .when('type', { is: PipelineResourceType.git, - then: yup.object({ - url: yup.string().required(i18next.t('pipelines-plugin~Required')), - revision: yup.string(), - }), + then: (schema) => + schema.shape({ + url: yup.string().required(i18next.t('pipelines-plugin~Required')), + revision: yup.string(), + }), }) .when('type', { is: PipelineResourceType.image, - then: yup.object({ - url: yup.string().required(i18next.t('pipelines-plugin~Required')), - }), + then: (schema) => + schema.shape({ + url: yup.string().required(i18next.t('pipelines-plugin~Required')), + }), }) .when('type', { is: PipelineResourceType.storage, - then: yup.object({ - type: yup.string().required(i18next.t('pipelines-plugin~Required')), - location: yup.string().required(i18next.t('pipelines-plugin~Required')), - dir: yup.string(), - }), + then: (schema) => + schema.shape({ + type: yup.string().required(i18next.t('pipelines-plugin~Required')), + location: yup.string().required(i18next.t('pipelines-plugin~Required')), + dir: yup.string(), + }), }) .when('type', { is: PipelineResourceType.cluster, - then: yup.object({ - name: yup.string().required(i18next.t('pipelines-plugin~Required')), - url: yup.string().required(i18next.t('pipelines-plugin~Required')), - username: yup.string().required(i18next.t('pipelines-plugin~Required')), - password: yup.string(), - insecure: yup.string(), - }), + then: (schema) => + schema.shape({ + name: yup.string().required(i18next.t('pipelines-plugin~Required')), + url: yup.string().required(i18next.t('pipelines-plugin~Required')), + username: yup.string().required(i18next.t('pipelines-plugin~Required')), + password: yup.string(), + insecure: yup.string(), + }), }), secrets: yup.object().when('type', { is: PipelineResourceType.cluster, - then: yup.object({ - cadata: yup.string().required(i18next.t('pipelines-plugin~Required')), - token: yup.string(), - }), + then: (schema) => + schema.shape({ + cadata: yup.string().required(i18next.t('pipelines-plugin~Required')), + token: yup.string(), + }), }), }); @@ -56,7 +61,7 @@ export const formResources = () => selection: yup.string().required(i18next.t('pipelines-plugin~Required')), data: yup.object().when('selection', { is: CREATE_PIPELINE_RESOURCE, - then: validateResourceType(), + then: (schema) => schema.concat(validateResourceType()), }), }), ); @@ -66,58 +71,62 @@ const volumeTypeSchema = () => .object() .when('type', { is: (type) => VolumeTypes[type] === VolumeTypes.Secret, - then: yup.object().shape({ - secret: yup.object().shape({ - secretName: yup.string().required(i18next.t('pipelines-plugin~Required')), - items: yup.array().of( - yup.object().shape({ - key: yup.string().required(i18next.t('pipelines-plugin~Required')), - path: yup.string().required(i18next.t('pipelines-plugin~Required')), - }), - ), + then: (schema) => + schema.shape({ + secret: yup.object().shape({ + secretName: yup.string().required(i18next.t('pipelines-plugin~Required')), + items: yup.array().of( + yup.object().shape({ + key: yup.string().required(i18next.t('pipelines-plugin~Required')), + path: yup.string().required(i18next.t('pipelines-plugin~Required')), + }), + ), + }), }), - }), }) .when('type', { is: (type) => VolumeTypes[type] === VolumeTypes.ConfigMap, - then: yup.object().shape({ - configMap: yup.object().shape({ - name: yup.string().required(i18next.t('pipelines-plugin~Required')), - items: yup.array().of( - yup.object().shape({ - key: yup.string().required(i18next.t('pipelines-plugin~Required')), - path: yup.string().required(i18next.t('pipelines-plugin~Required')), - }), - ), + then: (schema) => + schema.shape({ + configMap: yup.object().shape({ + name: yup.string().required(i18next.t('pipelines-plugin~Required')), + items: yup.array().of( + yup.object().shape({ + key: yup.string().required(i18next.t('pipelines-plugin~Required')), + path: yup.string().required(i18next.t('pipelines-plugin~Required')), + }), + ), + }), }), - }), }) .when('type', { is: (type) => VolumeTypes[type] === VolumeTypes.PVC, - then: yup.object().shape({ - persistentVolumeClaim: yup.object().shape({ - claimName: yup.string().required(i18next.t('pipelines-plugin~Required')), + then: (schema) => + schema.shape({ + persistentVolumeClaim: yup.object().shape({ + claimName: yup.string().required(i18next.t('pipelines-plugin~Required')), + }), }), - }), }) .when('type', { is: (type) => VolumeTypes[type] === VolumeTypes.VolumeClaimTemplate, - then: yup.object().shape({ - volumeClaimTemplate: yup.object().shape({ - spec: yup.object().shape({ - accessModes: yup - .array() - .of(yup.string().required(i18next.t('pipelines-plugin~Required'))), - resources: yup.object().shape({ - requests: yup - .object() - .shape({ storage: yup.string().required(i18next.t('pipelines-plugin~Required')) }), + then: (schema) => + schema.shape({ + volumeClaimTemplate: yup.object().shape({ + spec: yup.object().shape({ + accessModes: yup + .array() + .of(yup.string().required(i18next.t('pipelines-plugin~Required'))), + resources: yup.object().shape({ + requests: yup.object().shape({ + storage: yup.string().required(i18next.t('pipelines-plugin~Required')), + }), + }), + storageClassName: yup.string().required(i18next.t('pipelines-plugin~Required')), + volumeMode: yup.string().required(i18next.t('pipelines-plugin~Required')), }), - storageClassName: yup.string().required(i18next.t('pipelines-plugin~Required')), - volumeMode: yup.string().required(i18next.t('pipelines-plugin~Required')), }), }), - }), }); const commonPipelineSchema = () => diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/__tests__/form-switcher-validation.spec.ts b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/__tests__/form-switcher-validation.spec.ts index ec6ad98e281..32f63d2afeb 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/__tests__/form-switcher-validation.spec.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/__tests__/form-switcher-validation.spec.ts @@ -1,4 +1,5 @@ import * as _ from 'lodash'; +import { ValidationError } from 'yup'; import { initialPipelineFormData } from '../const'; import { getFormData, @@ -103,7 +104,7 @@ describe('getValidatedFormAndYaml', () => { const invalidYaml = updateYAML('spec.tasks', [ { notName: 'test', taskRef: { name: 'external-task' } }, ]); - const error = { inner: [{ path: 'spec.tasks[0].name' }] }; + const error = { inner: [{ path: 'spec.tasks[0].name' }] } as ValidationError; const finalFormData = getFormData(formDataBasicPassState, updateYAML('spec.tasks', [])); const [sanitizedFormData] = await getValidatedFormAndYaml( formDataBasicPassState, @@ -119,7 +120,7 @@ describe('getValidatedFormAndYaml', () => { { name: 'task2', taskRef: { name: 'external-task2' }, runAfter: ['task2'] }, ]; const invalidYaml = updateYAML('spec.tasks', tasks); - const error = { inner: [{ path: 'spec.tasks[1].runAfter[0]' }] }; + const error = { inner: [{ path: 'spec.tasks[1].runAfter[0]' }] } as ValidationError; const finalFormData = getFormData(formDataBasicPassState, updateYAML('spec.tasks', tasks)); const [sanitizedFormData] = await getValidatedFormAndYaml( formDataBasicPassState, @@ -137,7 +138,7 @@ describe('handleSanitizeToFormError', () => { { name: 'task2', taskRef: { name: 'external-task2' }, runAfter: ['task2'] }, ]; const invalidYaml = updateYAML('spec.tasks', tasks); - const error = { inner: [{ path: 'spec.tasks[1].runAfter[0]' }] }; + const error = { inner: [{ path: 'spec.tasks[1].runAfter[0]' }] } as ValidationError; const finalFormData = getFormData(formDataBasicPassState, updateYAML('spec.tasks', tasks)); const sanitizedFormData = await handleSanitizeToFormError( formDataBasicPassState, @@ -160,7 +161,7 @@ describe('handleSanitizeToFormError', () => { ]; const invalidYaml = updateYAML('spec.tasks', tasks); - const error = { inner: [{ path: 'spec.tasks[1].name' }] }; + const error = { inner: [{ path: 'spec.tasks[1].name' }] } as ValidationError; const finalFormData = getFormData( formDataBasicPassState, updateYAML('spec.tasks', sanitizedTasks), diff --git a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/validation-utils.ts b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/validation-utils.ts index 5a686132d98..41740b09858 100644 --- a/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/validation-utils.ts +++ b/frontend/packages/pipelines-plugin/src/components/pipelines/pipeline-builder/validation-utils.ts @@ -279,7 +279,7 @@ const resourceDefinition = (formValues: PipelineBuilderFormYamlValues, taskType: resourceValue?: string, ) { const resource = findResource(formValues, this.path, this.parent.name, taskType); - return !resource || resource.optional || resourceValue; + return !resource || resource.optional || !!resourceValue; }) .test( 'is-resource-link-broken', @@ -391,7 +391,7 @@ const taskValidation = (formValues: PipelineBuilderFormYamlValues, taskType: Tas workspaceValue?: string, ) { const workspace = findWorkspace(formValues, this.path, this.parent.name); - return !workspace || workspace.optional || workspaceValue; + return !workspace || workspace.optional || !!workspaceValue; }) .test( 'are-workspaces-available', @@ -476,16 +476,17 @@ const pipelineBuilderFormSchema = (formValues: PipelineBuilderFormYamlValues) => export const validationSchema = () => yup.mixed().test({ - test(formValues: PipelineBuilderFormYamlValues) { + async test(formValues: PipelineBuilderFormYamlValues) { const formYamlDefinition = yup.object({ editorType: yup.string().oneOf(Object.values(EditorType)), yamlData: yup.string(), formData: yup.mixed().when('editorType', { is: EditorType.Form, - then: pipelineBuilderFormSchema(formValues), + then: (schema) => schema.concat(pipelineBuilderFormSchema(formValues)), }), }); - return formYamlDefinition.validate(formValues, { abortEarly: false }); + await formYamlDefinition.validate(formValues, { abortEarly: false }); + return true; }, }); diff --git a/frontend/packages/pipelines-plugin/src/components/repository/repository-form-utils.ts b/frontend/packages/pipelines-plugin/src/components/repository/repository-form-utils.ts index 41638293840..22e0bb14b14 100644 --- a/frontend/packages/pipelines-plugin/src/components/repository/repository-form-utils.ts +++ b/frontend/packages/pipelines-plugin/src/components/repository/repository-form-utils.ts @@ -45,29 +45,31 @@ export const repositoryValidationSchema = (t: TFunction) => .object() .when('gitProvider', { is: GitProvider.BITBUCKET, - then: yup.object().shape({ - user: yup - .string() - .matches(bitBucketUserNameRegex, { - message: t( - 'pipelines-plugin~Name must consist of lower-case letters, numbers, underscores and hyphens. It must start with a letter and end with a letter or number.', - ), - excludeEmptyString: true, - }) - .required(t('pipelines-plugin~Required')), - }), + then: (schema) => + schema.shape({ + user: yup + .string() + .matches(bitBucketUserNameRegex, { + message: t( + 'pipelines-plugin~Name must consist of lower-case letters, numbers, underscores and hyphens. It must start with a letter and end with a letter or number.', + ), + excludeEmptyString: true, + }) + .required(t('pipelines-plugin~Required')), + }), }) .when(['method', 'gitProvider', 'gitUrl'], { is: (method, gitProvider, gitUrl) => gitUrl && !(gitProvider === GitProvider.GITHUB && method === GitProvider.GITHUB), - then: yup.object().shape({ - token: yup.string().test('oneOfRequired', 'Required', function () { - return this.parent.token || this.parent.secretRef; - }), - secretRef: yup.string().test('oneOfRequired', 'Required', function () { - return this.parent.token || this.parent.secretRef; + then: (schema) => + schema.shape({ + token: yup.string().test('oneOfRequired', 'Required', function () { + return this.parent.token || this.parent.secretRef; + }), + secretRef: yup.string().test('oneOfRequired', 'Required', function () { + return this.parent.token || this.parent.secretRef; + }), }), - }), }), }); @@ -77,31 +79,33 @@ export const pipelinesAccessTokenValidationSchema = (t: TFunction) => .object() .when('gitProvider', { is: GitProvider.BITBUCKET, - then: yup.object().shape({ - user: yup - .string() - .matches(nameRegex, { - message: t( - 'pipelines-plugin~Name must consist of lower-case letters, numbers and hyphens. It must start with a letter and end with a letter or number.', - ), - excludeEmptyString: true, - }) - .required(t('pipelines-plugin~Required')), - }), + then: (schema) => + schema.shape({ + user: yup + .string() + .matches(bitBucketUserNameRegex, { + message: t( + 'pipelines-plugin~Name must consist of lower-case letters, numbers, underscores and hyphens. It must start with a letter and end with a letter or number.', + ), + excludeEmptyString: true, + }) + .required(t('pipelines-plugin~Required')), + }), }) .when(['method', 'gitProvider', 'gitUrl'], { is: (method, gitProvider, gitUrl) => gitUrl && gitProvider && !(gitProvider === GitProvider.GITHUB && method === GitProvider.GITHUB), - then: yup.object().shape({ - token: yup.string().test('oneOfRequired', 'Required', function () { - return this.parent.token || this.parent.secretRef; - }), - secretRef: yup.string().test('oneOfRequired', 'Required', function () { - return this.parent.token || this.parent.secretRef; + then: (schema) => + schema.shape({ + token: yup.string().test('oneOfRequired', 'Required', function () { + return this.parent.token || this.parent.secretRef; + }), + secretRef: yup.string().test('oneOfRequired', 'Required', function () { + return this.parent.token || this.parent.secretRef; + }), }), - }), }), }); @@ -109,7 +113,7 @@ export const importFlowRepositoryValidationSchema = (t: TFunction) => { return yup.object().shape({ repository: yup.object().when(['pipelineType', 'pipelineEnabled'], { is: (pipelineType, pipelineEnabled) => pipelineType === PipelineType.PAC && pipelineEnabled, - then: pipelinesAccessTokenValidationSchema(t), + then: (schema) => schema.concat(pipelinesAccessTokenValidationSchema(t)), }), }); }; diff --git a/frontend/packages/shipwright-plugin/src/components/build-form/validation.ts b/frontend/packages/shipwright-plugin/src/components/build-form/validation.ts index 5dc94115c3c..f0c61c00795 100644 --- a/frontend/packages/shipwright-plugin/src/components/build-form/validation.ts +++ b/frontend/packages/shipwright-plugin/src/components/build-form/validation.ts @@ -43,7 +43,7 @@ export const formDataSchema = () => export const validationSchema = () => yup.mixed().test({ - test(values: BuildFormikValues) { + async test(values: BuildFormikValues) { const formYamlDefinition = yup.object({ editorType: yup .string() @@ -51,14 +51,16 @@ export const validationSchema = () => .required(i18n.t('shipwright-plugin~Required')), formData: yup.mixed().when('editorType', { is: EditorType.Form, - then: formDataSchema(), + then: (schema) => schema.concat(formDataSchema()), }), yamlData: yup.mixed().when('editorType', { is: EditorType.YAML, - then: yup.string().required(i18n.t('shipwright-plugin~Required')), + then: (schema) => + schema.concat(yup.string().required(i18n.t('shipwright-plugin~Required'))), }), }); - return formYamlDefinition.validate(values, { abortEarly: false }); + await formYamlDefinition.validate(values, { abortEarly: false }); + return true; }, }); diff --git a/frontend/packages/topology/integration-tests/support/pages/topology/topology-page.ts b/frontend/packages/topology/integration-tests/support/pages/topology/topology-page.ts index 94c79482c52..e7e289759a8 100644 --- a/frontend/packages/topology/integration-tests/support/pages/topology/topology-page.ts +++ b/frontend/packages/topology/integration-tests/support/pages/topology/topology-page.ts @@ -76,7 +76,9 @@ export const topologyPage = { }); }, verifyToplogyPageNotEmpty: () => { - cy.get('*[class="odc-topology"]').then(($el) => { + // Wait for topology page to load completely - increased timeout for yup async validation + cy.get('[class*="odc-topology"]', { timeout: 60000 }).should('be.visible'); + cy.get('[class*="odc-topology"]').then(($el) => { if ($el.find('[data-test="no-resources-found"]').length) { throw new Error(`No workload has been created till now.`); } diff --git a/frontend/packages/vsphere-plugin/package.json b/frontend/packages/vsphere-plugin/package.json index b9813eb523a..f4c4a0774ae 100644 --- a/frontend/packages/vsphere-plugin/package.json +++ b/frontend/packages/vsphere-plugin/package.json @@ -9,7 +9,7 @@ "dependencies": { "@console/dynamic-plugin-sdk": "0.0.0-fixed", "formik": "^2.4.5", - "yup": "^0.27.0" + "yup": "^1.7.1" }, "consolePlugin": { "exposedModules": { diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/setup/cloud-shell-setup-utils.ts b/frontend/packages/webterminal-plugin/src/components/cloud-shell/setup/cloud-shell-setup-utils.ts index 0f92373f6b5..e297250a75b 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/setup/cloud-shell-setup-utils.ts +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/setup/cloud-shell-setup-utils.ts @@ -20,13 +20,13 @@ const projectNameRegex = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/; export const newNamespaceValidationSchema = yup.string().when('namespace', { is: CREATE_NAMESPACE_KEY, - then: yup - .string() - .matches( - projectNameRegex, - "Name must consist of lower case alphanumeric characters or '-' and must start and end with an alphanumeric character.", - ) - .required('Required'), + then: (schema) => + schema + .matches( + projectNameRegex, + "Name must consist of lower case alphanumeric characters or '-' and must start and end with an alphanumeric character.", + ) + .required('Required'), }); export const advancedOptionsValidationSchema = yup.object().shape({ diff --git a/frontend/public/components/configmaps/configmap-utils.ts b/frontend/public/components/configmaps/configmap-utils.ts index 6206b85186d..55ecc0f66a3 100644 --- a/frontend/public/components/configmaps/configmap-utils.ts +++ b/frontend/public/components/configmaps/configmap-utils.ts @@ -197,7 +197,7 @@ const formDataSchema = (values: ConfigMapFormInitialValues) => export const validationSchema = () => yup.mixed().test({ - test(values: ConfigMapFormInitialValues) { + async test(values: ConfigMapFormInitialValues) { const formYamlDefinition = yup.object({ editorType: yup .string() @@ -205,14 +205,15 @@ export const validationSchema = () => .required(i18next.t('public~Required')), formData: yup.mixed().when('editorType', { is: EditorType.Form, - then: formDataSchema(values), + then: (schema) => schema.concat(formDataSchema(values)), }), yamlData: yup.mixed().when('editorType', { is: EditorType.YAML, - then: yup.string().required(i18next.t('public~Required')), + then: (schema) => schema.concat(yup.string().required(i18next.t('public~Required'))), }), }); - return formYamlDefinition.validate(values, { abortEarly: false }); + await formYamlDefinition.validate(values, { abortEarly: false }); + return true; }, }); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 372016ea4d6..037effea695 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -8788,11 +8788,6 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== -fn-name@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" - integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= - focus-trap@7.6.4: version "7.6.4" resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-7.6.4.tgz#455ec5c51fee5ae99604ca15142409ffbbf84db9" @@ -14625,12 +14620,7 @@ propagate@^2.0.0: resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== -property-expr@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" - integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== - -property-expr@^2.0.4: +property-expr@^2.0.4, property-expr@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== @@ -17109,11 +17099,6 @@ symlink-or-copy@^1.1.8, symlink-or-copy@^1.2.0, symlink-or-copy@^1.3.1: resolved "https://registry.yarnpkg.com/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz#9506dd64d8e98fa21dcbf4018d1eab23e77f71fe" integrity sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA== -synchronous-promise@^2.0.6: - version "2.0.9" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.9.tgz#b83db98e9e7ae826bf9c8261fd8ac859126c780a" - integrity sha512-LO95GIW16x69LuND1nuuwM4pjgFGupg7pZ/4lU86AmchPKrhk0o2tpMU2unXRrqo81iAFe1YJ0nAGEVwsrZAgg== - syntax-error@^1.1.1: version "1.4.0" resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" @@ -17319,6 +17304,11 @@ timers-browserify@^2.0.12: dependencies: setimmediate "^1.0.4" +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + tiny-glob@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.6.tgz#9e056e169d9788fe8a734dfa1ff02e9b92ed7eda" @@ -17636,6 +17626,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + type-fest@^4.18.2: version "4.27.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.27.0.tgz#57329aae32e7b27b942b961e3ef861f0873c4b1b" @@ -19352,18 +19347,6 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -yup@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7" - integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ== - dependencies: - "@babel/runtime" "^7.0.0" - fn-name "~2.0.1" - lodash "^4.17.11" - property-expr "^1.5.0" - synchronous-promise "^2.0.6" - toposort "^2.0.2" - yup@^0.32.11: version "0.32.11" resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" @@ -19377,6 +19360,16 @@ yup@^0.32.11: property-expr "^2.0.4" toposort "^2.0.2" +yup@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.7.1.tgz#4c47c6bb367df08d4bc597f8c4c4f5fc4277f6ab" + integrity sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0" + zen-observable-ts@^0.8.21: version "0.8.21" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d"