From 54b32a8f72ffa080aebb063713dc3c497480db9c Mon Sep 17 00:00:00 2001 From: Ashraf Masarwa Date: Tue, 2 Jun 2026 11:44:45 +0300 Subject: [PATCH 1/2] Enhance Validations and add Code-Highlighter --- .../form-validation-enhancements.md | 5 + workspaces/dcm/plugins/dcm/package.json | 2 + .../CatalogItemInstancesTabContent.tsx | 2 +- .../components/InstanceFormFields.tsx | 41 +- .../instanceFormTypes.ts | 89 +++- .../catalog-items/CatalogItemsTabContent.tsx | 2 +- .../catalog-items/catalogItemFormTypes.ts | 116 ++++- .../components/CatalogItemFormFields.tsx | 423 ++++++++++++------ .../policies/components/PolicyFormFields.tsx | 25 +- .../pages/policies/policyFormTypes.test.ts | 55 ++- .../dcm/src/pages/policies/policyFormTypes.ts | 30 +- workspaces/dcm/yarn.lock | 11 + 12 files changed, 648 insertions(+), 153 deletions(-) create mode 100644 workspaces/dcm/.changeset/form-validation-enhancements.md diff --git a/workspaces/dcm/.changeset/form-validation-enhancements.md b/workspaces/dcm/.changeset/form-validation-enhancements.md new file mode 100644 index 0000000000..679dbb2dab --- /dev/null +++ b/workspaces/dcm/.changeset/form-validation-enhancements.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-dcm': patch +--- + +Catalog Item: validate duplicate paths, block adding empty field rows, live-validate JSON in schema modal, surface invalid default_value / validation_schema inline. Policy: add structural Rego validation (package declaration + selected_provider reference) and monospace font to the code editor. Catalog Instance: enforce required/min/max constraints from validation_schema on user-value fields and enable submit-button gating. diff --git a/workspaces/dcm/plugins/dcm/package.json b/workspaces/dcm/plugins/dcm/package.json index 0343a9d24b..c578068932 100644 --- a/workspaces/dcm/plugins/dcm/package.json +++ b/workspaces/dcm/plugins/dcm/package.json @@ -43,6 +43,7 @@ "@material-ui/lab": "4.0.0-alpha.61", "@red-hat-developer-hub/backstage-plugin-dcm-common": "workspace:^", "js-yaml": "^4.1.1", + "react-syntax-highlighter": "^15.4.5", "react-use": "^17.2.4", "yup": "^1.7.1" }, @@ -60,6 +61,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", "@types/js-yaml": "^4.0.9", + "@types/react-syntax-highlighter": "^15", "msw": "^1.0.0", "react": "^16.13.1 || ^17.0.0 || ^18.0.0", "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx index bfd969a492..3cde73fe04 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/CatalogItemInstancesTabContent.tsx @@ -292,7 +292,7 @@ export function CatalogItemInstancesTabContent() { onCancel={crud.handleCloseCreate} submitLabel="Create" submitting={crud.createSubmitting} - disabled={false} + disabled={!isInstanceFormValid(crud.createForm)} /> } > diff --git a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/components/InstanceFormFields.tsx b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/components/InstanceFormFields.tsx index 61349cd219..1c84b67b21 100644 --- a/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/components/InstanceFormFields.tsx +++ b/workspaces/dcm/plugins/dcm/src/pages/catalog-item-instances/components/InstanceFormFields.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { Box, Divider, @@ -43,6 +43,7 @@ import { buildUserValueRows, InstanceForm, validateInstanceForm, + validateUserValues, } from '../instanceFormTypes'; import type { UserValueRow } from '../instanceFormTypes'; @@ -59,6 +60,7 @@ export type InstanceFormFieldsProps = Readonly<{ function fieldHelperText(row: UserValueRow): string { const parts: string[] = [`path: ${row.path}`]; + if (row.required) parts.push('required'); if (row.schemaType) parts.push(`type: ${row.schemaType}`); if (row.schemaMin !== undefined) parts.push(`min: ${row.schemaMin}`); if (row.schemaMax !== undefined) parts.push(`max: ${row.schemaMax}`); @@ -74,6 +76,13 @@ export function InstanceFormFields({ }: InstanceFormFieldsProps) { const classes = useStyles(); const errors = useMemo(() => validateInstanceForm(form), [form]); + const userValueErrors = useMemo( + () => validateUserValues(form.user_values), + [form.user_values], + ); + const [userValuesTouched, setUserValuesTouched] = useState< + Record + >({}); const selectedItem = catalogItems.find(ci => ci.uid === form.catalog_item_id); const handleCatalogItemChange = (id: string) => { @@ -84,6 +93,7 @@ export function InstanceFormFields({ user_values: buildUserValueRows(item), })); setTouched(prev => ({ ...prev, catalog_item_id: true })); + setUserValuesTouched({}); }; const setUserValue = (index: number, value: string) => @@ -93,6 +103,9 @@ export function InstanceFormFields({ return { ...prev, user_values: updated }; }); + const touchUserValue = (index: number) => + setUserValuesTouched(prev => ({ ...prev, [index]: true })); + return ( {form.user_values.map((row, i) => { - const helperText = fieldHelperText(row); + const isTouched = Boolean(userValuesTouched[i]); + const fieldError = isTouched ? userValueErrors[i] : undefined; + const label = row.required + ? `${row.displayName} *` + : row.displayName; + const helperText = fieldError ?? fieldHelperText(row); /* Enum field → Select */ if (row.enumValues && row.enumValues.length > 0) { @@ -199,12 +217,17 @@ export function InstanceFormFields({ variant="outlined" size="small" fullWidth + error={Boolean(fieldError)} > - {row.displayName} + {label}