Skip to content

Commit 1f5de5a

Browse files
lokanandaprabhukarthikjeeyaropenshift-cherrypick-robot
authored
[Orchestrator] Backporting bug fixes for 1.9.4 release (#2734)
* [orchestrator] Backport orchestrator fixes and enhancements to 1.9 (#2666) * feat(orchestrator): pre-populate Execute Workflow form from URL query params (#2570) * prepopulate workflow execution page form from URL query params Signed-off-by: Karthik <karthik.jk11@gmail.com> * support enum coercison for case-insenstive match and skip invalid values * add support for fields that are defined via '$ref' * add full support for json schema fields --------- Signed-off-by: Karthik <karthik.jk11@gmail.com> * fix(orchestrator-form-react): scope async validation to active step (#2602) * fix(orchestrator-form-react): scope async validation to active step Limit validate:url requests to the active step during multi-step navigation. Made-with: Cursor * fix(orchestrator-form-react): keep full formData for async validation Pass full formData to template evaluation while scoping uiSchema traversal. Made-with: Cursor * fix(orchestrator-form-react): preserve ui:hidden on objects with properties (#2653) * fix(orchestrator): honor json schema defaults in initial formData (#2654) * fix(orchestrator): honor json schema defaults Ensure extractStaticDefaults falls back to JSON Schema defaults when ui:props fetch:response:default is absent so initial formData includes schema defaults. Made-with: Cursor * chore(changeset): document schema default fix Add changeset for orchestrator form defaults update. Made-with: Cursor * fix(orchestrator): handle root defaults Avoid setting an empty key for root schema defaults and add tests to cover root default handling. Made-with: Cursor * fix(orchestrator): document and test defaults Clarify extractStaticDefaults precedence in docs and add test coverage for default handling. Made-with: Cursor * chore(orchestrator): update yarn.lock after backport Made-with: Cursor --------- Signed-off-by: Karthik <karthik.jk11@gmail.com> Co-authored-by: Karthik Jeeyar <karthik@redhat.com> * fix(orchestrator): scope SchemaUpdater replacements (#2732) Limit SchemaUpdater replacements to the originating scope, with robust path resolution for nested and array schemas, plus tests covering scoping cases. Made-with: Cursor Co-authored-by: Lokananda Prabhu <lprabhu@redhat.com> --------- Signed-off-by: Karthik <karthik.jk11@gmail.com> Co-authored-by: Karthik Jeeyar <karthik@redhat.com> Co-authored-by: OpenShift Cherrypick Robot <openshift-cherrypick-robot@redhat.com>
1 parent 09b5d84 commit 1f5de5a

21 files changed

Lines changed: 1624 additions & 23 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
3+
---
4+
5+
Include JSON Schema `default` values when computing initial form data so
6+
template expressions can resolve defaults before widgets render.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator': patch
3+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-api': patch
4+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
5+
---
6+
7+
Scope SchemaUpdater replacements to the originating step and improve scope resolution.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
3+
---
4+
5+
Scope async validate:url calls to the active step in multi-step forms.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator': patch
3+
---
4+
5+
prepopulate Execute Workflow form from URL query params
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
3+
---
4+
5+
Fix `extractUiSchema` so `ui:*` directives on object schemas that define `properties` (for example `ui:hidden` on a `workflowParams` object) are copied into the generated UI schema.

workspaces/orchestrator/docs/user-interface.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,52 @@ Workflows can also be invoked from Backstage software templates using the `orche
2121
- Monitor workflow execution status
2222
- View workflow results and outputs
2323

24+
### Execute Workflow Form Prepopulation
25+
26+
The Execute Workflow page supports prepopulating form fields from URL query parameters. When the workflow schema defines input fields, any query parameter whose name matches a schema property path will be used to prepopulate the corresponding form field.
27+
28+
**Path format**
29+
30+
- For flat schemas, use the property name directly: `?language=English&name=John`
31+
- For nested (multi-step) schemas, use dot notation: `?firstStep.fooTheFirst=test` or `?provideInputs.language=English`
32+
- For fields inside `oneOf` or `anyOf` branches, use the same dot notation: `?mode.alphaValue=test`
33+
34+
**Schema support**
35+
36+
The prepopulation logic supports the full JSON Schema draft-07 spec, including:
37+
38+
- Fields defined via `$ref` in `$defs` or `definitions`
39+
- `oneOf` and `anyOf` — the correct branch is resolved from the provided data
40+
- Array fields — use comma-separated values: `?tags=foo,bar,baz`
41+
- Type coercion for numbers, integers, and booleans
42+
43+
**Schema constraints**
44+
45+
For fields with `enum` constraints in the schema, the query param value must match one of the allowed values. Case-insensitive matching is supported (e.g. `?language=english` maps to `English` when the enum is `['English', 'Spanish']`). Values that do not match any enum option are ignored and will not prepopulate the field.
46+
47+
Query parameters that do not match any schema property path are ignored and will not be merged into the form.
48+
49+
**Reserved parameters**
50+
51+
The following query parameters are reserved for navigation and are not used for form prepopulation:
52+
53+
- `targetEntity` — Used to associate the workflow run with a catalog entity
54+
- `instanceId` — Used when re-running or viewing a specific workflow instance
55+
56+
**Examples**
57+
58+
```
59+
/orchestrator/workflows/yamlgreet/execute?targetEntity=default:component:my-app&language=English&name=alice
60+
```
61+
62+
In this example, `targetEntity` is excluded (reserved), while `language` and `name` prepopulate the form when those fields exist in the workflow schema.
63+
64+
```
65+
/orchestrator/workflows/my-workflow/execute?language=English&mode.alphaValue=prefilled&tags=a,b,c
66+
```
67+
68+
This example prepopulates a flat field (`language`), a nested field inside an `oneOf` branch (`mode.alphaValue`), and an array field (`tags`).
69+
2470
### Entity Integration
2571

2672
- Workflow tabs on entity pages

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export type OrchestratorFormContextProps = {
4646
export type OrchestratorFormDecorator = (FormComponent: React.ComponentType<FormDecoratorProps>) => React.ComponentType<OrchestratorFormContextProps>;
4747

4848
// @public
49-
export type OrchestratorFormSchemaUpdater = (chunks: SchemaChunksResponse) => void;
49+
export type OrchestratorFormSchemaUpdater = (chunks: SchemaChunksResponse, scopeId?: string) => void;
5050

5151
// @public
5252
export type SchemaChunksResponse = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export type SchemaChunksResponse = {
113113
*/
114114
export type OrchestratorFormSchemaUpdater = (
115115
chunks: SchemaChunksResponse,
116+
scopeId?: string,
116117
) => void;
117118

118119
/**

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,22 @@ const FormComponent = (decoratorProps: FormDecoratorProps) => {
9191
let _extraErrors: ErrorSchema<JsonObject> | undefined = undefined;
9292
let _validationError: Error | undefined = undefined;
9393
const activeKey = getActiveKey();
94+
const shouldScopeExtraErrors =
95+
Boolean(activeKey) && Boolean(uiSchema?.[activeKey as string]);
96+
const extraErrorsFormData = (_formData ?? formData) as JsonObject;
97+
const extraErrorsUiSchema = shouldScopeExtraErrors
98+
? ({
99+
[activeKey as string]: uiSchema?.[activeKey as string],
100+
} as OrchestratorFormContextProps['uiSchema'])
101+
: uiSchema;
94102

95103
if (decoratorProps.getExtraErrors) {
96104
try {
97105
handleValidateStarted();
98-
_extraErrors = await decoratorProps.getExtraErrors(formData, uiSchema);
106+
_extraErrors = await decoratorProps.getExtraErrors(
107+
extraErrorsFormData,
108+
extraErrorsUiSchema,
109+
);
99110

100111
if (activeKey) {
101112
setExtraErrors(
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type { JSONSchema7 } from 'json-schema';
18+
19+
import extractStaticDefaults from './extractStaticDefaults';
20+
21+
describe('extractStaticDefaults', () => {
22+
it('applies schema defaults when no fetch default', () => {
23+
const schema: JSONSchema7 = {
24+
type: 'object',
25+
properties: {
26+
name: { type: 'string', default: 'app' },
27+
},
28+
};
29+
30+
expect(extractStaticDefaults(schema)).toEqual({ name: 'app' });
31+
});
32+
33+
it('prefers fetch:response:default over schema default', () => {
34+
const schema: JSONSchema7 = {
35+
type: 'object',
36+
properties: {
37+
name: {
38+
type: 'string',
39+
default: 'schema',
40+
'ui:props': { 'fetch:response:default': 'fetch' },
41+
} as JSONSchema7 & Record<string, unknown>,
42+
},
43+
};
44+
45+
expect(extractStaticDefaults(schema)).toEqual({ name: 'fetch' });
46+
});
47+
48+
it('does not overwrite existing form data values', () => {
49+
const schema: JSONSchema7 = {
50+
type: 'object',
51+
properties: {
52+
name: { type: 'string', default: 'schema' },
53+
},
54+
};
55+
56+
expect(extractStaticDefaults(schema, { name: 'existing' })).toEqual({
57+
name: 'existing',
58+
});
59+
});
60+
61+
it('preserves falsy defaults', () => {
62+
const schema: JSONSchema7 = {
63+
type: 'object',
64+
properties: {
65+
enabled: { type: 'boolean', default: false },
66+
retries: { type: 'number', default: 0 },
67+
note: { type: 'string', default: '' },
68+
},
69+
};
70+
71+
expect(extractStaticDefaults(schema)).toEqual({
72+
enabled: false,
73+
retries: 0,
74+
note: '',
75+
});
76+
});
77+
78+
it('applies defaults from composed schemas', () => {
79+
const schema: JSONSchema7 = {
80+
type: 'object',
81+
allOf: [
82+
{
83+
type: 'object',
84+
properties: {
85+
name: { type: 'string', default: 'composed' },
86+
},
87+
},
88+
],
89+
};
90+
91+
expect(extractStaticDefaults(schema)).toEqual({ name: 'composed' });
92+
});
93+
94+
it('applies root default objects without creating empty key', () => {
95+
const schema: JSONSchema7 = {
96+
type: 'object',
97+
default: { foo: 'bar' },
98+
properties: {
99+
foo: { type: 'string' },
100+
},
101+
};
102+
103+
expect(extractStaticDefaults(schema)).toEqual({ foo: 'bar' });
104+
});
105+
106+
it('does not override existing data with root defaults', () => {
107+
const schema: JSONSchema7 = {
108+
type: 'object',
109+
default: { foo: 'bar' },
110+
properties: {
111+
foo: { type: 'string' },
112+
},
113+
};
114+
115+
expect(extractStaticDefaults(schema, { foo: 'existing' })).toEqual({
116+
foo: 'existing',
117+
});
118+
});
119+
});

0 commit comments

Comments
 (0)