Skip to content

NO-ISSUE: Refactor create vm/cluster/bm wizards - #99

Open
rawagner wants to merge 2 commits into
osac-project:mainfrom
rawagner:wizard_generic
Open

NO-ISSUE: Refactor create vm/cluster/bm wizards#99
rawagner wants to merge 2 commits into
osac-project:mainfrom
rawagner:wizard_generic

Conversation

@rawagner

@rawagner rawagner commented Jul 24, 2026

Copy link
Copy Markdown
Contributor
  • use JSX composition instead of adapter

  • fix field_definitions handling - due to BE breaking changes

    • only fields in field_definitions are allowed, all other must not be in the request
    • enable/disable wizard fields based on the presence of the field path + enabled=true
    • parse JSON Schema validation and add it to Yup schema
  • fix cluster node sets

    • only node sets defined in the cluster template are allowed
    • host type cannot be selected, only node set size can be adjusted (if field_definition allows it)
  • Add generic, reusable Wizard Footer component

@openshift-ci-robot

Copy link
Copy Markdown

@rawagner: This pull request explicitly references no jira issue.

Details

In response to this:

add Generic wizard components

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rawagner

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci
openshift-ci Bot requested review from ElayAharoni and liatb-rh July 24, 2026 13:39
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 109 files, which is 9 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7a62a9e5-d0c8-4374-ae57-acd394e1ff04

📥 Commits

Reviewing files that changed from the base of the PR and between 98312d8 and ddb4454.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (166)
  • apps/app-frontend/src/main.tsx
  • apps/app-frontend/src/shell/AppShell.tsx
  • libs/i18n/locales/en/translation.json
  • libs/types/src/osac/private/v1/baremetal_instance_catalog_item_type_pb.ts
  • libs/types/src/osac/private/v1/baremetal_instance_template_type_pb.ts
  • libs/types/src/osac/private/v1/cluster_catalog_item_type_pb.ts
  • libs/types/src/osac/private/v1/cluster_template_type_pb.ts
  • libs/types/src/osac/private/v1/cluster_type_pb.ts
  • libs/types/src/osac/private/v1/cluster_version_type_pb.ts
  • libs/types/src/osac/private/v1/compute_instance_catalog_item_type_pb.ts
  • libs/types/src/osac/private/v1/compute_instance_template_type_pb.ts
  • libs/types/src/osac/private/v1/compute_instance_type_pb.ts
  • libs/types/src/osac/private/v1/event_type_pb.ts
  • libs/types/src/osac/private/v1/host_type_type_pb.ts
  • libs/types/src/osac/private/v1/network_class_type_pb.ts
  • libs/types/src/osac/private/v1/project_membership_type_pb.ts
  • libs/types/src/osac/private/v1/public_ip_type_pb.ts
  • libs/types/src/osac/private/v1/secret_type_pb.ts
  • libs/types/src/osac/private/v1/secrets_service_pb.ts
  • libs/types/src/osac/public/v1/baremetal_instance_catalog_item_type_pb.ts
  • libs/types/src/osac/public/v1/baremetal_instance_template_type_pb.ts
  • libs/types/src/osac/public/v1/cluster_catalog_item_type_pb.ts
  • libs/types/src/osac/public/v1/cluster_template_type_pb.ts
  • libs/types/src/osac/public/v1/cluster_type_pb.ts
  • libs/types/src/osac/public/v1/cluster_version_type_pb.ts
  • libs/types/src/osac/public/v1/compute_instance_catalog_item_type_pb.ts
  • libs/types/src/osac/public/v1/compute_instance_template_type_pb.ts
  • libs/types/src/osac/public/v1/compute_instance_type_pb.ts
  • libs/types/src/osac/public/v1/event_type_pb.ts
  • libs/types/src/osac/public/v1/host_type_type_pb.ts
  • libs/types/src/osac/public/v1/network_class_type_pb.ts
  • libs/types/src/osac/public/v1/project_membership_type_pb.ts
  • libs/types/src/osac/public/v1/public_ip_type_pb.ts
  • libs/types/src/osac/public/v1/secret_type_pb.ts
  • libs/types/src/osac/public/v1/secrets_service_pb.ts
  • libs/types/src/osac/public/v1/tenant_type_pb.ts
  • libs/ui-components/package.json
  • libs/ui-components/src/NetworkTopologyPage.tsx
  • libs/ui-components/src/api/v1/cluster-templates.ts
  • libs/ui-components/src/api/v1/host-types.ts
  • libs/ui-components/src/api/v1/networking.ts
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/BareMetalInstanceCreateWizard.tsx
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/payload.ts
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/steps/BareMetalConfigurationStep.tsx
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/steps/BareMetalGeneralStep.tsx
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/steps/BareMetalReview.tsx
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/validation.ts
  • libs/ui-components/src/components/BareMetalInstance/CreateWizard/values.ts
  • libs/ui-components/src/components/Cluster/ClusterStatusLabel.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/ClusterCreateWizard.test.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/ClusterCreateWizard.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/payload.ts
  • libs/ui-components/src/components/Cluster/CreateWizard/steps/ClusterConfigurationStep.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/steps/ClusterGeneralStep.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/steps/ClusterNetworkingStep.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/steps/ClusterReview.tsx
  • libs/ui-components/src/components/Cluster/CreateWizard/validation.ts
  • libs/ui-components/src/components/Cluster/CreateWizard/values.ts
  • libs/ui-components/src/components/Form/FieldValidationContext.tsx
  • libs/ui-components/src/components/Form/InputField.tsx
  • libs/ui-components/src/components/Form/LeaveFormConfirmation.tsx
  • libs/ui-components/src/components/Form/MultiSelectField.tsx
  • libs/ui-components/src/components/Form/RadioButtonField.tsx
  • libs/ui-components/src/components/Form/SelectField.test.tsx
  • libs/ui-components/src/components/Form/SelectField.tsx
  • libs/ui-components/src/components/Form/labeledResourceRef.ts
  • libs/ui-components/src/components/Form/labeledResourceRefSchema.ts
  • libs/ui-components/src/components/Wizard/OSACWizardFooter.tsx
  • libs/ui-components/src/components/catalog/CatalogItemCard.tsx
  • libs/ui-components/src/components/catalog/CatalogItemDetailContent.tsx
  • libs/ui-components/src/components/catalog/catalogItemDisplay.test.ts
  • libs/ui-components/src/components/catalog/catalogItemDisplay.ts
  • libs/ui-components/src/components/catalogProvision/CatalogProvisionWizard.test.tsx
  • libs/ui-components/src/components/catalogProvision/CatalogProvisionWizard.tsx
  • libs/ui-components/src/components/catalogProvision/NameField.tsx
  • libs/ui-components/src/components/catalogProvision/SshKeyField.tsx
  • libs/ui-components/src/components/catalogProvision/UserDataField.tsx
  • libs/ui-components/src/components/catalogProvision/catalogFieldDefinition.ts
  • libs/ui-components/src/components/catalogProvision/catalogProvisionTypes.ts
  • libs/ui-components/src/components/catalogProvision/protobuf-value.ts
  • libs/ui-components/src/components/catalogProvision/shared/CatalogStepContent.tsx
  • libs/ui-components/src/components/catalogProvision/utils.ts
  • libs/ui-components/src/components/catalogProvision/validation.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/bareMetalInstance/BareMetalConfigurationStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/bareMetalInstance/BareMetalGeneralStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/bareMetalInstance/fields.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/bareMetalInstance/payload.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/bareMetalInstance/schemas.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/bareMetalInstanceAdapter.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/ClusterConfigurationStep.test.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/ClusterConfigurationStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/ClusterGeneralStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/ClusterNetworkingStep.test.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/ClusterNetworkingStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/ClusterNodeSetsArrayField.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/applyCatalogDefaults.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/applyCatalogGeneralDefaults.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/applyCatalogNetworkingDefaults.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/fields.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/payload.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/payload.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/schemas.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/cluster/schemas.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/clusterAdapter.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/VmConfigurationStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/VmGeneralStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/applyCatalogDefaults.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/applyCatalogGeneralDefaults.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/fields.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/generalFields.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/payload.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/schemas.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstance/schemas.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/computeInstanceAdapter.ts
  • libs/ui-components/src/components/catalogProvision/wizard/adapters/types.ts
  • libs/ui-components/src/components/catalogProvision/wizard/catalogOverlay.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/catalogOverlay.ts
  • libs/ui-components/src/components/catalogProvision/wizard/constants.ts
  • libs/ui-components/src/components/catalogProvision/wizard/fields/ClusterPoolSizeField.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/fields/PullSecretField.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/fields/SshKeyField.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/fields/UserDataField.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/fields/credentialValidation.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/fields/credentialValidation.ts
  • libs/ui-components/src/components/catalogProvision/wizard/metadataNameSchema.test.ts
  • libs/ui-components/src/components/catalogProvision/wizard/stepIds.ts
  • libs/ui-components/src/components/catalogProvision/wizard/steps/CatalogStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/catalogProvision/wizard/steps/WizardSteps.tsx
  • libs/ui-components/src/components/networking/SecurityGroupCreateModal.tsx
  • libs/ui-components/src/components/networking/SecurityGroupRuleForm.tsx
  • libs/ui-components/src/components/networking/SecurityGroupRuleModal.tsx
  • libs/ui-components/src/components/vm/CreateWizard/ComputeInstanceProvisionWizard.tsx
  • libs/ui-components/src/components/vm/CreateWizard/payload.ts
  • libs/ui-components/src/components/vm/CreateWizard/schemas.ts
  • libs/ui-components/src/components/vm/CreateWizard/steps/VmConfigurationStep.tsx
  • libs/ui-components/src/components/vm/CreateWizard/steps/VmGeneralStep.tsx
  • libs/ui-components/src/components/vm/CreateWizard/steps/VmNetworkingStep.tsx
  • libs/ui-components/src/components/vm/CreateWizard/steps/VmReview.tsx
  • libs/ui-components/src/components/vm/CreateWizard/values.ts
  • libs/ui-components/src/components/vm/DetailsPage/VmDetailsActionButtons.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmDetailsCard.test.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmDetailsCard.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmDetailsSummary.test.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmDetailsSummary.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmNetworkingTab.test.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmUserDataCard.test.tsx
  • libs/ui-components/src/components/vm/DetailsPage/VmUserDataCard.tsx
  • libs/ui-components/src/components/vm/DetailsPage/useVmDetailsDisplay.ts
  • libs/ui-components/src/components/vm/VmTable.test.tsx
  • libs/ui-components/src/components/vm/VmTable.tsx
  • libs/ui-components/src/pages/admin/BareMetalInstanceCatalogManagementPanel.tsx
  • libs/ui-components/src/pages/admin/ClusterCatalogManagementPanel.tsx
  • libs/ui-components/src/pages/admin/ComputeInstanceCatalogManagementPanel.tsx
  • libs/ui-components/src/pages/tenant/BareMetalCreatePage.tsx
  • libs/ui-components/src/pages/tenant/BareMetalRoutes.tsx
  • libs/ui-components/src/pages/tenant/CatalogPage.tsx
  • libs/ui-components/src/pages/tenant/ClusterCreatePage.tsx
  • libs/ui-components/src/pages/tenant/ClusterRoutes.tsx
  • libs/ui-components/src/pages/tenant/VmCreatePage.tsx
  • libs/ui-components/src/pages/tenant/VmListPage.test.tsx
  • libs/ui-components/src/test-utils/createMockConnectTransport.ts
  • libs/ui-components/src/utils/snakeCase.ts
  • libs/ui-components/src/validation/name.ts
  • libs/ui-components/src/validation/ssh-public-key.ts
  • proxy/bridge/connectjson.go

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@batzionb

Copy link
Copy Markdown
Contributor

Please update the title and description of the PR, currently seems they don't reflect the scope
This is a refactor

@rawagner
rawagner force-pushed the wizard_generic branch 2 times, most recently from ff5f6bc to ed4e3a6 Compare July 27, 2026 09:19
@rawagner rawagner changed the title NO-ISSUE: Fix field_definitions logic NO-ISSUE: Refactor create vm/cluster/bm wizards Jul 27, 2026
@rawagner

Copy link
Copy Markdown
Contributor Author

Please update the title and description of the PR, currently seems they don't reflect the scope This is a refactor

sorry about that, title/desc updated :)

@batzionb

batzionb commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
  • only node sets defined in the cluster template are allowed
    this is not correct anymore, unfortunately PR for fixing the PRD and design weren't merged yet
    also if the cluster template has no default the user should be able to enter different host type and sizes
    This is the PR that documents the change, we should definitely get it merged :)
    OSAC-1421: Tenant-managed cluster node_sets enhancement-proposals#108

@batzionb

batzionb commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
  • only node sets defined in the cluster template are allowed
    this is not correct anymore, unfortunately PR for fixing the PRD and design weren't merged yet
    also if the cluster template has no default the user should be able to enter different host type and sizes
    This is the PR that documents the change, we should definitely get it merged :)
    [PRD: Tenant-managed cluster node_sets (OSAC-1421) enhancement-proposals#108](OSAC-1421: Tenant-managed cluster node_sets enhancement-proposals#108)

Now the PRD change is merged
It'll need to be updated again after this is merged, if we want to keep docs in sync to not confuse agent

@rawagner

rawagner commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author
  • only node sets defined in the cluster template are allowed
    this is not correct anymore, unfortunately PR for fixing the PRD and design weren't merged yet
    also if the cluster template has no default the user should be able to enter different host type and sizes
    This is the PR that documents the change, we should definitely get it merged :)
    OSAC-1421: Tenant-managed cluster node_sets enhancement-proposals#108

you are right, we are diverging from the current PRD.

however this is based on what BE allows. It currently enforces us to use only the node sets from the template. If the template has none, then none are allowed.
Im not sure if this changed between 0.1 -> 0.2 or if the enforcement was always there. I will try to find out.

Note that the field definitions updates are due to https://redhat.atlassian.net/browse/OSAC-1416

@batzionb

Copy link
Copy Markdown
Contributor

I think issue might be the field_definition examples are incorrect
There should be one field definition for node_sets and that's how we are planning to implement the catalogitems wizard.
And then IIUC it'll be allowed

@rawagner

Copy link
Copy Markdown
Contributor Author

I think issue might be the field_definition examples are incorrect There should be one field definition for node_sets and that's how we are planning to implement the catalogitems wizard. And then IIUC it'll be allowed

which example exactly ?

see https://redhat.atlassian.net/browse/OSAC-1416 and osac-project/fulfillment-service#59

@rawagner

Copy link
Copy Markdown
Contributor Author

also this is the BE behavior regarding the field_definitions now

Field Definitions

How field_definitions on catalog items control resource creation in the fulfillment-service.


Structure

Each FieldDefinition has:

Field Type Description
path string Dot-notation path into the resource spec (e.g. instance_type, network.pod_cidr)
display_name string Human-friendly label for the UI
editable bool Whether the user may set this field; defaults to false (protobuf zero value)
default google.protobuf.Value Default value, applied when the user omits the field (or always if non-editable)
validation_schema string Optional JSON Schema (draft 2020-12) validated against user-provided values

Editability and defaults

editable default present User sends value Result
false yes (required) yes Rejected — non-editable fields cannot be set
false yes (required) no Default applied unconditionally
false no Rejected at catalog item create/update time — non-editable fields must have a default
true yes yes User value kept, validated against validation_schema if present
true yes no Default applied as fallback
true no yes User value kept, validated
true no no Rejected — effectively required

There is no explicit required flag. Requiredness is implicit: an editable field without a default is required.


Pipeline order

When a resource is created via a catalog item, the backend processes steps in this order:

  1. Protovalidate interceptor — structural proto constraints (buf.validate annotations)
  2. Auth interceptors — authentication, authorization
  3. Fetch catalog item — look up by ID/name, verify published and not deleted
  4. applyFieldDefinitions — the core enforcement step (see below)
  5. Copy template reference from catalog item onto the spec
  6. Fetch template and apply template spec defaults — fills spec-level gaps (image, network config, etc.); user values take precedence
  7. Business validation — required fields present after defaulting, format checks, instance type state, etc.
  8. Template parameter validation and defaulting
  9. Persist — sets creator, tenant, saves to DB

Field definitions are self-contained. They are fully evaluated before template defaults are merged. A field_definition cannot rely on template defaults to supply a missing value — the check at step 4 runs before step 6.


applyFieldDefinitions behavior

  1. Marshal the spec to JSON (protobuf field names)
  2. Collect all leaf paths the user set in the spec
  3. Reject unlisted fields — any spec path not in field_definitions (except catalog_item, template, template_parameters) is an error
  4. Process each field_definition in iteration order:
    • Non-editable + user value → reject
    • Non-editable + no user value → apply default
    • Editable + user value → validate against validation_schema if present
    • Editable + no user value + default → apply default
    • Editable + no user value + no default → reject (required)
  5. Unmarshal updated JSON back into the protobuf spec

Nested paths

Paths use dot notation (e.g. network.pod_cidr). Both parent and child paths can have their own field_definitions, and both are evaluated.

Parent and child interaction

There is no deep merge. When the backend checks whether a user provided a value for a parent path (e.g. network), it looks up the whole object at that path. If the user sent any child field (e.g. network.pod_cidr), the parent path resolves to a non-nil object, which counts as "user provided a value."

Example: network has default {"pod_cidr": "10.128.0.0/14", "service_cidr": "172.30.0.0/16"}, and network.pod_cidr is editable. If the user sends only network.pod_cidr:

  • network sees a value (the object containing pod_cidr) → default is not applied
  • network.pod_cidr sees the user value → kept
  • service_cidr is missing — the parent default was never merged in

To ensure all sibling fields are populated, define separate field_definitions for each leaf field with their own defaults.

Non-editable parent with editable children

A non-editable parent makes children effectively non-editable regardless of their own editable flag and regardless of ordering. Any user-provided child value causes the parent object to be non-nil, which the parent's non-editable check rejects.

The order only affects which error message is returned first:

  • Child before parent: child validation runs and passes, then the parent rejects ("not editable")
  • Parent before child: parent rejects immediately, child never runs

Either way, the request fails.

Evaluation order

Field definitions are processed in the order they appear in the protobuf repeated field. There is no depth-based sorting.


Array fields

Paths do not support array indexing. There is no [0] or * wildcard syntax. getNestedValue and setNestedValue operate on map[string]any only — array elements cannot be individually targeted.

A repeated/list field can be referenced as a whole (e.g. node_sets) to set a default or validate the entire array via JSON Schema, but individual elements cannot be addressed.


Validation schema

The validation_schema field contains a JSON Schema string (draft 2020-12). It is:

  • Validated for valid JSON when the catalog item is created/updated
  • Compiled and evaluated against the user's value at resource creation time
  • Only applied to editable fields where the user provided a value (not applied to defaults)

Catalog item validation (admin-time)

When an admin creates or updates a catalog item, validateFieldDefinitions checks:

  • Non-editable fields must have a default value
  • validation_schema strings must be valid JSON

No other structural validation is performed on field_definitions at this stage (e.g. path validity, overlap between parent/child paths, or consistency with the referenced template).

@rawagner

Copy link
Copy Markdown
Contributor Author

just for the reference, the is the node sets enforcement osac-project/enhancement-proposals#112

@batzionb

Copy link
Copy Markdown
Contributor

I think issue might be the field_definition examples are incorrect There should be one field definition for node_sets and that's how we are planning to implement the catalogitems wizard. And then IIUC it'll be allowed

which example exactly ?

see https://redhat.atlassian.net/browse/OSAC-1416 and osac-project/fulfillment-service#59

For the sake of closure, we discussed this offline, I understood that following a backend change indeed the node sets should accept only hosts that are in the template

- use JSX composition instead of adapter
- fix field_definitions handling - due to BE breaking changes
   - only fields in field_definitions are allowed, all other must not be in the request
   - enable/disable wizard fields based on the presence of the field path + enabled=true
   - parse JSON Schema validation and add it to Yup schema
- fix cluster node sets
   - only node sets defined in the cluster template are allowed
   - host type cannot be selected, only node set size can be adjusted (if field_definition allows it)

- Add generic, reusable Wizard Footer component

@ElayAharoni ElayAharoni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good refactor overall — the move from adapter pattern to JSX composition is clean, field_definitions-to-Yup conversion is solid, and the router migration / protobuf registry / Go proxy changes look correct.

A few issues to address before merging, organized by severity below.

[ClusterState.READY]: { status: 'ready', text: 'Ready' },
[ClusterState.FAILED]: { status: 'failed', text: 'Failed' },
[ClusterState.DELETING]: { status: 'progressing', text: 'Deleting' },
[ClusterState.DELETE_FAILED]: { status: 'failed', text: 'Delete failed' },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH — Correctness: CLUSTER_STATUS_MAP now includes DELETING and DELETE_FAILED, but the resolveClusterStatus switch statement below was not updated with matching cases. Clusters in these states will fall through to the default case and display as "Unknown" instead of "Deleting" / "Delete failed".

Either add case ClusterState.DELETING: and case ClusterState.DELETE_FAILED: to the switch, or refactor to use a direct map lookup:

return CLUSTER_STATUS_MAP[state] ?? CLUSTER_STATUS_MAP[ClusterState.UNSPECIFIED];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the resolveClusterStatus function was actually not needed - removed. Im accessing the CLUSTER_STATUS_MAP directly now.

name: buildMetadataNameSchema(t),
}),
spec: Yup.object({
runStrategy: fieldSchema('run_strategy'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — Correctness: runStrategy uses fieldSchema('run_strategy') with no base schema, which defaults to Yup.mixed() — no required() check. The initial value is undefined and SelectField has 3 options (no auto-select), so a user can submit without selecting a run strategy.

The VM wizard correctly uses:

runStrategy: fieldSchema(
  'run_strategy',
  Yup.string().required(t('Run strategy is required')),
),

Consider applying the same pattern here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case of VM, runStrategy is required field.
in case of BM, it is optional - so the validation & the fom is valid.
it is aligned with the API

<Spinner aria-label={t('Loading catalog')} />
</Bullseye>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — Dead code + UX inconsistency: This unconditional if (isLoading) returns a spinner for all loading states, making the if (catalogItemId && isLoading) guard at line 65 unreachable.

The VM wizard uses only if (catalogItemId && isLoading) and the Cluster wizard uses if ((catalogItemId && isLoading) || templatesLoading). This broader check means the BM wizard blocks rendering the entire wizard shell during any loading — unlike the other two which render immediately and let CatalogStepContent handle its own loading state.

Suggestion: remove this unconditional check and keep only the if (catalogItemId && isLoading) guard below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I aligned all wizards to block until we load catalog items / templates

<Button variant="link" isInline onClick={() => navigate('/vms')}>
{t('Virtual Machines')}
</Button>
</BreadcrumbItem>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — Pattern consistency / Accessibility: Two inconsistencies with the BM and Cluster wizards:

  1. This header PageSection doesn't wrap Breadcrumb/Title/Content in <Stack hasGutter>, causing tighter vertical spacing than the other wizards.
  2. The wizard PageSection at line 102 lacks aria-label — BM wizard has aria-label={t('Bare metal provisioning wizard')} and Cluster has aria-label={t('Create cluster wizard')}. Missing aria-label reduces accessibility for screen readers navigating by landmark.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. The Stack component is removed from all.

provisionError:
err instanceof Error ? err.message : t('Provisioning failed. Please try again.'),
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — UX regression: The old VmCreatePage called qc.setQueryData(apiQueryKey('v1/compute_instances', [instance.id]), instance) before navigating, so the details page rendered immediately. Now only navigate() is called — the details page will show a brief loading flash. Same applies to ClusterCreateWizard.

Nice-to-have: add qc.setQueryData() before navigating using useApiQueryClient().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VM wizard was exception. No other wizard did this. I prefer to remove this behavior to have all wizards aligned.

We can introduce it later but on the hook level - anytime a create hook is called, it should handle adding the response to cache. This is something we can do in a followup.

acc[curr.name] = {
size: curr.size,
};
return acc;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Code quality: When isFieldEditable('node_sets', fds) is true, isFieldEditable('node_sets.${name}.size', fds) also resolves to true (path prefix inheritance). Both this block and the per-set loop at line 68 fire, with the loop redundantly overwriting the same data.

No data corruption, but this should be an if/else: write all node sets if top-level is editable, otherwise fall through to per-set size checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this should be fixed now.


const defaultSshKey = getStringDefaultValue('ssh_public_key', fds);
if (defaultSshKey) {
base.spec.sshPublicKey = defaultSshKey;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Latent correctness: if (defaultBootDisk) uses a truthiness check, but getNumberDefaultValue can return 0 which is falsy. Works today because the empty form default for sizeGib is also 0, but will break if that changes.

The cluster wizard correctly uses if (defSize !== undefined) for the same pattern. Same issue at line 95 with defaultRunStrategy.

Suggestion: use !== undefined checks instead of truthiness.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - using !== undefined check


const fieldName = 'spec.sshPublicKey';

const SshKeyField = ({ fieldDefinitions }: SshKeyFieldProps) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Dead code: path?: string is declared but never consumed — the component destructures only { fieldDefinitions } and uses the hardcoded constant fieldName = 'spec.sshPublicKey'. No call site passes this prop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, path prop is removed.

import { clearSchemaCache } from '../../catalogProvision/validation';

export interface ComputeInstanceWizardValues {
catalogItem: ComputeInstanceCatalogItem | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Conventions: ComputeInstanceCatalogItem is used only in type positions — should be import type { ComputeInstanceCatalogItem }. Same pattern in several other new files:

  • BareMetalConfigurationStep.tsx: import { TFunction }import type
  • ssh-public-key.ts: import { TFunction }import type
  • SshKeyField.tsx, ClusterGeneralStep.tsx, UserDataField.tsx, validation.ts: import { FieldDefinition }import type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be fixed now

@rawagner

Copy link
Copy Markdown
Contributor Author

/hold

@openshift-ci

openshift-ci Bot commented Jul 30, 2026

Copy link
Copy Markdown

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants