Skip to content

fix(orchestrator): scope SchemaUpdater replacements#2725

Merged
karthikjeeyar merged 1 commit intoredhat-developer:mainfrom
lokanandaprabhu:fix/schema-updater-scope
Apr 9, 2026
Merged

fix(orchestrator): scope SchemaUpdater replacements#2725
karthikjeeyar merged 1 commit intoredhat-developer:mainfrom
lokanandaprabhu:fix/schema-updater-scope

Conversation

@lokanandaprabhu
Copy link
Copy Markdown
Member

@lokanandaprabhu lokanandaprabhu commented Apr 8, 2026

Hey, I just made a Pull Request!

Fixes: https://redhat.atlassian.net/browse/RHDHBUGS-2920

Summary

  • Scope SchemaUpdater replacements to the originating step/object
  • Improve scope resolution for nested and array schemas, with longest-key matching
  • Add unit tests covering step, nested, dot-notation, array, and ambiguous key cases

-----BEFORE-----

Screen.Recording.2026-04-08.at.7.14.19.PM.mov

----AFTER-------

Screen.Recording.2026-04-08.at.7.19.57.PM.mov
Screen.Recording.2026-04-08.at.7.10.54.PM.mov

Workflow to test

id: test-schema-updater-multi-step
version: '1.0'
specVersion: '0.8'
name: Test SchemaUpdater Multi Step
description: Reproduces SchemaUpdater cross-step replacement bug
dataInputSchema: 'schemas/test-schema-updater-multi-step.input-schema.json'
start: InjectState
states:
  - name: InjectState
    type: inject
    data:
      message: Hello from test-schema-updater-multi-step
    end: true

{
  "$id": "classpath:/schemas/test-schema-updater-multi-step.input-schema.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Test SchemaUpdater Multi Step",
  "type": "object",
  "ui:order": ["app-registration", "caas-namespace"],
  "properties": {
    "app-registration": {
      "title": "App Registration",
      "type": "object",
      "properties": {
        "xParams": {
          "type": "object",
          "title": "App Registration Inputs",
          "properties": {
            "appName": {
              "title": "App Name",
              "type": "string"
            }
          }
        },
        "schemaUpdater": {
          "type": "string",
          "title": "Schema Updater",
          "ui:widget": "SchemaUpdater",
          "ui:props": {
            "fetch:url": "data:application/json,%7B%22placeholderTwo%22:%7B%22type%22:%22string%22,%22title%22:%22App%20Registration%20placeholderTwo%20(from%20updater)%22%7D%7D"
          }
        },
        "placeholderTwo": {
          "type": "string",
          "title": "App Registration placeholderTwo (should be replaced by chunk01)"
        }
      }
    },
    "caas-namespace": {
      "title": "CaaS Namespace",
      "type": "object",
      "properties": {
        "xParams": {
          "type": "object",
          "title": "CaaS Namespace Inputs",
          "properties": {
            "namespaceName": {
              "title": "Namespace Name",
              "type": "string"
            }
          }
        },
        "schemaUpdater": {
          "type": "string",
          "title": "Schema Updater",
          "ui:widget": "SchemaUpdater",
          "ui:props": {
            "fetch:url": "data:application/json,%7B%22placeholderTwo%22:%7B%22type%22:%22string%22,%22title%22:%22CaaS%20Namespace%20placeholderTwo%20(from%20updater)%22%7D%7D"
          }
        },
        "placeholderTwo": {
          "type": "string",
          "title": "CaaS Namespace placeholderTwo (should be replaced by chunk02)"
        }
      }
    }
  }
}

Steps to reproduce

  1. Run the test-schema-updater-multi-step workflow.
  2. Step 1 (App Registration): placeholderTwo updates to “App Registration placeholderTwo (from updater)”.
  3. Move to Step 2 (CaaS Namespace).
  4. Expected: Step 2 placeholderTwo updates to “CaaS Namespace placeholderTwo (from updater)”.
  5. Actual (before fix): Step 1 gets updated again, Step 2 remains unchanged.

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

Limit SchemaUpdater replacements to the originating scope, with robust path
resolution for nested and array schemas, plus tests covering scoping cases.

Made-with: Cursor
@rhdh-gh-app
Copy link
Copy Markdown

rhdh-gh-app bot commented Apr 8, 2026

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-orchestrator-form-api workspaces/orchestrator/plugins/orchestrator-form-api patch v2.5.2
@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets workspaces/orchestrator/plugins/orchestrator-form-widgets patch v1.7.3
@red-hat-developer-hub/backstage-plugin-orchestrator workspaces/orchestrator/plugins/orchestrator patch v5.5.2

@rhdh-qodo-merge
Copy link
Copy Markdown

Review Summary by Qodo

Scope SchemaUpdater replacements to originating step

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Scope SchemaUpdater replacements to originating step/object
• Add scopeId parameter to track SchemaUpdater field location
• Implement robust path resolution for nested and array schemas
• Add comprehensive unit tests covering scoping scenarios
Diagram
flowchart LR
  A["SchemaUpdater Widget"] -->|"passes scopeId"| B["updateSchema Function"]
  B -->|"resolves scope path"| C["getScopePathFromId"]
  C -->|"returns path"| D["getScopedProperties"]
  D -->|"limits replacements"| E["deepSearchAndFirstReplace"]
  E -->|"updates schema"| F["setSchema"]
Loading

Grey Divider

File Changes

1. workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts ✨ Enhancement +1/-0

Add scopeId parameter to schema updater type

• Add optional scopeId parameter to OrchestratorFormSchemaUpdater type
• Enables tracking of SchemaUpdater field location for scoped replacements

workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts


2. workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts ✨ Enhancement +142/-12

Implement scope resolution for schema updates

• Implement getScopePathFromId function to parse scopeId and resolve path through schema
• Implement getScopedProperties function to extract target properties based on scope path
• Support both underscore and dot notation separators in scopeId
• Handle nested objects and array items during path resolution
• Apply longest-key matching to disambiguate similar property names
• Replace structuredClone with cloneDeep for consistency
• Limit replacements to scoped properties instead of entire schema

workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts


3. workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.test.ts 🧪 Tests +260/-0

Add comprehensive schema updater scoping tests

• Add test for multi-step scoping with same property names
• Add test for nested object scoping
• Add test for dot-notation scopeId support
• Add test for array items scope resolution
• Add test for longest-key matching disambiguation

workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.test.ts


View more (3)
4. workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/SchemaUpdater.tsx ✨ Enhancement +1/-1

Pass scopeId to schema update function

• Pass props.id as scopeId parameter to updateSchema function
• Enables SchemaUpdater to identify its location in schema hierarchy

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/SchemaUpdater.tsx


5. workspaces/orchestrator/.changeset/schema-updater-scope-fix.md 📝 Documentation +7/-0

Add changeset for schema updater scope fix

• Document patch version bump for three orchestrator packages
• Describe scoping improvement for SchemaUpdater replacements

workspaces/orchestrator/.changeset/schema-updater-scope-fix.md


6. workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md 📝 Documentation +1/-1

Update API documentation for scopeId parameter

• Update API documentation to reflect new optional scopeId parameter

workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md


Grey Divider

Qodo Logo

@rhdh-qodo-merge
Copy link
Copy Markdown

rhdh-qodo-merge bot commented Apr 8, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Remediation recommended

1. Array index scope unsupported 🐞 Bug ≡ Correctness
Description
getScopePathFromId() only matches path segments against JSON schema property keys, so any scopeId
segment that isn’t a property key (e.g., a numeric array index) makes scope resolution fail and
getScopedProperties() fall back to root properties. In that failure mode, getSchemaUpdater() applies
replacements at the schema root, which defeats the intended scoping for those ids.
Code

workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[R44-108]

+  const separators = ['_', '.'];
+  const matchPath = (
+    node: JSONSchema7,
+    remaining: string,
+  ): string[] | undefined => {
+    if (!remaining) {
+      return undefined;
+    }
+
+    if (node.type === 'array' && node.items && typeof node.items === 'object') {
+      return matchPath(node.items as JSONSchema7, remaining);
+    }
+
+    if (!node.properties) {
+      return undefined;
+    }
+
+    const keys = Object.keys(node.properties).sort(
+      (a, b) => b.length - a.length,
+    );
+    for (const key of keys) {
+      for (const separator of separators) {
+        if (remaining === key) {
+          return [key];
+        }
+
+        if (remaining.startsWith(`${key}${separator}`)) {
+          const next = node.properties[key] as JSONSchema7;
+          if (!next || typeof next !== 'object') {
+            continue;
+          }
+
+          const rest = remaining.slice(key.length + separator.length);
+          const childPath = matchPath(next, rest);
+          if (childPath) {
+            return [key, ...childPath];
+          }
+        }
+      }
+    }
+
+    return undefined;
+  };
+
+  return matchPath(schema, raw);
+};
+
+const getScopedProperties = (
+  schema: JSONSchema7,
+  scopeId?: string,
+): JSONSchema7['properties'] | undefined => {
+  if (!schema.properties) {
+    return undefined;
+  }
+
+  const scopePath = getScopePathFromId(schema, scopeId);
+  if (!scopePath || scopePath.length === 0) {
+    if (scopeId) {
+      // eslint-disable-next-line no-console
+      console.warn(
+        `SchemaUpdater: Failed to resolve scopeId "${scopeId}", falling back to root`,
+      );
+    }
+    return schema.properties;
+  }
Evidence
The scope resolver iterates only over Object.keys(node.properties) and never accepts/consumes any
other segment values, so a scopeId segment that is not a property key cannot be resolved and
matchPath returns undefined. When that happens, getScopedProperties explicitly returns
schema.properties (root) while warning, so the later replacement loop runs against root-level
properties rather than the intended scoped subtree.

workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[44-89]
workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[99-108]
workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[190-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getScopePathFromId()` can only resolve `scopeId` paths where every segment corresponds to a JSON Schema `properties` key. If a `scopeId` contains a segment that is not a property key (commonly an array index like `0`), scope resolution fails and `getScopedProperties()` falls back to `schema.properties` (root), which defeats scoping.

### Issue Context
The new scoping logic is intended to prevent cross-step/object replacements. A resolver failure currently degrades to global (root) updates.

### Fix Focus Areas
- workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[44-89]
- workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[99-108]
- workspaces/orchestrator/plugins/orchestrator/src/components/ExecuteWorkflowPage/schemaUpdater.ts[190-196]

### What to change
- Extend `matchPath()` to tolerate/consume non-property segments when traversing arrays (e.g., if current node is an array, strip a leading `^\d+([_.])` from `remaining` before descending into `items`).
- Consider making the failure mode “no-op when `scopeId` is provided but cannot be resolved” (or at least avoid falling back to root) to preserve the scoping guarantee.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 8, 2026

Copy link
Copy Markdown
Member

@karthikjeeyar karthikjeeyar left a comment

Choose a reason for hiding this comment

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

Image

verified locally, works as expected.

/approve
/lgtm

@openshift-ci openshift-ci bot added the lgtm label Apr 9, 2026
@karthikjeeyar karthikjeeyar merged commit 315239c into redhat-developer:main Apr 9, 2026
10 checks passed
@lokanandaprabhu
Copy link
Copy Markdown
Member Author

/cherrypick orchestrator-1.9-backport

@openshift-cherrypick-robot
Copy link
Copy Markdown

@lokanandaprabhu: new pull request created: #2732

Details

In response to this:

/cherrypick orchestrator-1.9-backport

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants