Skip to content

Commit 5771568

Browse files
feat(Orchestrator): add dynamic conditional visibility for ui:hidden (#1959)
* feat: add dynamic conditional visibility for ui:hidden * docs: clarify hidden fields behavior in review page and submission * feat: add translated UI note about hidden fields in review step
1 parent 85d62c9 commit 5771568

17 files changed

Lines changed: 1044 additions & 24 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': minor
3+
---
4+
5+
Add dynamic conditional visibility for ui:hidden
6+
7+
**Conditional Hiding Feature:**
8+
9+
- Add `HiddenCondition` type supporting boolean and condition objects
10+
- Implement `evaluateHiddenCondition` utility for evaluating hide conditions
11+
- Support condition objects with `when`, `is`, `isNot`, and `isEmpty` operators
12+
- Support composite conditions with `allOf` (AND) and `anyOf` (OR) logic
13+
- Support nested field paths using dot notation (e.g., `config.server.port`)
14+
- Update `HiddenFieldTemplate` to dynamically evaluate hide conditions based on form data
15+
- Update `generateReviewTableData` to respect conditional hiding in review pages
16+
- Hidden field visibility updates in real-time when form data changes
17+
18+
**Condition Object Patterns:**
19+
20+
- `{ when: "field", is: "value" }` - Hide when field equals value
21+
- `{ when: "field", is: ["val1", "val2"] }` - Hide when field equals any value (OR)
22+
- `{ when: "field", isNot: "value" }` - Hide when field does NOT equal value
23+
- `{ when: "field", isEmpty: true }` - Hide when field is empty
24+
- `{ allOf: [...] }` - Hide when ALL conditions are true (AND)
25+
- `{ anyOf: [...] }` - Hide when ANY condition is true (OR)
26+
27+
**Documentation:**
28+
29+
- Update `orchestratorFormWidgets.md` with comprehensive examples of conditional hiding
30+
- Add examples for all condition patterns and composite conditions
31+
- Include complete real-world deployment configuration example
32+
33+
**Testing:**
34+
35+
- Add comprehensive unit tests for condition evaluation
36+
- Test simple conditions, composite conditions, and nested conditions
37+
- Test edge cases (empty values, nested paths)

workspaces/orchestrator/docs/orchestratorFormWidgets.md

Lines changed: 225 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -689,11 +689,13 @@ In the future, new widgets wrapping such explicit use case can be added.
689689

690690
## Hiding Fields
691691

692-
Fields can be hidden from the form display while still maintaining their widget functionality and participating in form submission using the `"ui:hidden": true` property.
692+
Fields can be hidden from the form display while still maintaining their widget functionality and participating in form submission using the `ui:hidden` property. The `ui:hidden` property supports two modes: **static** and **conditional (dynamic)** hiding.
693693

694-
This is different from `"ui:widget": "hidden"` which changes the widget type itself. With `"ui:hidden": true`, the field keeps its original widget type (like `ActiveText`, `ActiveTextInput`, etc.) but is visually hidden from the user.
694+
This is different from `"ui:widget": "hidden"` which changes the widget type itself. With `ui:hidden`, the field keeps its original widget type (like `ActiveText`, `ActiveTextInput`, etc.) but is visually hidden from the user.
695695

696-
### Example Usage
696+
### Static Hiding
697+
698+
Hide a field permanently using a boolean value:
697699

698700
```json
699701
{
@@ -705,34 +707,240 @@ This is different from `"ui:widget": "hidden"` which changes the widget type its
705707
"ui:props": {
706708
"ui:text": "This text is hidden but still rendered"
707709
}
710+
}
711+
}
712+
```
713+
714+
### Conditional (Dynamic) Hiding
715+
716+
Hide fields based on the values of other form fields using condition objects:
717+
718+
#### Basic Conditions
719+
720+
```json
721+
{
722+
"deploymentType": {
723+
"type": "string",
724+
"title": "Deployment Type",
725+
"enum": ["simple", "advanced", "custom", "managed"]
708726
},
709-
"hiddenInput": {
727+
"advancedConfig": {
710728
"type": "string",
711-
"title": "Hidden Input",
712-
"ui:hidden": true,
713-
"default": "secret-value"
729+
"title": "Advanced Configuration",
730+
"ui:hidden": {
731+
"when": "deploymentType",
732+
"isNot": ["advanced", "custom"]
733+
}
734+
}
735+
}
736+
```
737+
738+
In this example, `advancedConfig` is hidden unless `deploymentType` is `"advanced"` or `"custom"`.
739+
740+
#### Supported Condition Patterns
741+
742+
**1. Hide when field equals value(s) (`is`)**
743+
744+
```json
745+
{
746+
"debugMode": {
747+
"type": "boolean",
748+
"title": "Enable Debug Mode",
749+
"ui:hidden": {
750+
"when": "environment",
751+
"is": "production"
752+
}
753+
}
754+
}
755+
```
756+
757+
Use an array for multiple values (OR logic):
758+
759+
```json
760+
{
761+
"basicOptions": {
762+
"type": "object",
763+
"ui:hidden": {
764+
"when": "deploymentType",
765+
"is": ["simple", "managed"]
766+
}
767+
}
768+
}
769+
```
770+
771+
**2. Hide when field does NOT equal value(s) (`isNot`)**
772+
773+
```json
774+
{
775+
"customScript": {
776+
"type": "string",
777+
"title": "Custom Deployment Script",
778+
"ui:widget": "textarea",
779+
"ui:hidden": {
780+
"when": "deploymentType",
781+
"isNot": "custom"
782+
}
783+
}
784+
}
785+
```
786+
787+
**3. Hide when field is empty (`isEmpty`)**
788+
789+
```json
790+
{
791+
"childConfig": {
792+
"type": "object",
793+
"ui:hidden": {
794+
"when": "parentField",
795+
"isEmpty": true
796+
}
797+
}
798+
}
799+
```
800+
801+
**4. Multiple conditions with AND logic (`allOf`)**
802+
803+
```json
804+
{
805+
"productionApprover": {
806+
"type": "string",
807+
"title": "Production Approver",
808+
"ui:hidden": {
809+
"allOf": [
810+
{ "when": "environment", "is": "production" },
811+
{ "when": "requiresApproval", "is": true }
812+
]
813+
}
814+
}
815+
}
816+
```
817+
818+
**5. Multiple conditions with OR logic (`anyOf`)**
819+
820+
```json
821+
{
822+
"skipValidation": {
823+
"type": "boolean",
824+
"ui:hidden": {
825+
"anyOf": [
826+
{ "when": "environment", "is": "development" },
827+
{ "when": "quickDeploy", "is": true }
828+
]
829+
}
830+
}
831+
}
832+
```
833+
834+
#### Nested Field Paths
835+
836+
You can reference nested fields using dot notation:
837+
838+
```json
839+
{
840+
"advancedPort": {
841+
"type": "integer",
842+
"ui:hidden": {
843+
"when": "config.server.useDefaultPort",
844+
"is": true
845+
}
846+
}
847+
}
848+
```
849+
850+
### Complete Example
851+
852+
```json
853+
{
854+
"title": "Deployment Configuration",
855+
"type": "object",
856+
"properties": {
857+
"deploymentType": {
858+
"type": "string",
859+
"title": "Deployment Type",
860+
"enum": ["simple", "advanced", "custom", "managed"],
861+
"default": "simple"
862+
},
863+
"environment": {
864+
"type": "string",
865+
"title": "Environment",
866+
"enum": ["development", "staging", "production"],
867+
"default": "development"
868+
},
869+
"replicas": {
870+
"type": "integer",
871+
"title": "Number of Replicas",
872+
"default": 1,
873+
"ui:hidden": {
874+
"when": "deploymentType",
875+
"is": "simple"
876+
}
877+
},
878+
"customScript": {
879+
"type": "string",
880+
"title": "Custom Deployment Script",
881+
"ui:widget": "textarea",
882+
"ui:hidden": {
883+
"when": "deploymentType",
884+
"isNot": "custom"
885+
}
886+
},
887+
"productionApprover": {
888+
"type": "string",
889+
"title": "Production Approver Email",
890+
"ui:hidden": {
891+
"anyOf": [
892+
{ "when": "environment", "isNot": "production" },
893+
{ "when": "deploymentType", "is": "simple" }
894+
]
895+
}
896+
},
897+
"advancedSettings": {
898+
"type": "object",
899+
"title": "Advanced Settings",
900+
"ui:hidden": {
901+
"when": "deploymentType",
902+
"is": ["simple", "managed"]
903+
},
904+
"properties": {
905+
"cpu": {
906+
"type": "string",
907+
"title": "CPU Limit"
908+
},
909+
"memory": {
910+
"type": "string",
911+
"title": "Memory Limit"
912+
}
913+
}
914+
}
714915
}
715916
}
716917
```
717918

718-
**Key differences:**
919+
### Key Differences
719920

720921
| Property | Behavior | Use Case |
721922
| ----------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------- |
722923
| `"ui:widget": "hidden"` | Changes widget type to hidden input | Simple hidden form values |
723924
| `"ui:hidden": true` | Keeps original widget but hides it visually | Hide widgets while preserving their functionality (e.g., ActiveText that fetches data) |
925+
| `"ui:hidden": {...}` | Conditionally hides based on form data | Dynamic forms that adapt to user input |
926+
927+
### Behavior of Hidden Fields
928+
929+
Hidden fields (regardless of hiding method):
930+
931+
- **Are not displayed** in the form
932+
- **Are not shown** in the wizard stepper navigation (multi-step forms)
933+
- **Still participate** in form validation
934+
- **Are included** in form submission
935+
- **Are excluded** from the review page (but will still be part of the request payload)
936+
- **Maintain their widget functionality** (fetching, validation, etc.)
937+
- **Update in real-time** when form data changes (for conditional hiding)
724938

725-
Hidden fields:
939+
> **Note:** Hidden fields are not displayed on the review page for clarity, but they are still included in the workflow execution request. If you need to completely exclude fields from the request payload, you can use the [`SchemaUpdater` API](./extensibleForm.md#schema-updater) to dynamically modify the schema.
726940
727-
- Are not displayed in the form
728-
- Are not shown in the wizard stepper navigation (multi-step forms)
729-
- Still participate in form validation
730-
- Are included in form submission
731-
- Are excluded from the review page
732-
- Maintain their widget functionality (fetching, validation, etc.)
941+
### Automatic Step Hiding
733942

734-
**Automatic Step Hiding:**
735-
If all inputs within a multi-step form's step are marked with `"ui:hidden": true`, the entire step will be automatically hidden from the stepper navigation. The step and its hidden fields will still be processed during form submission.
943+
If all inputs within a multi-step form's step are marked with `ui:hidden` (either statically or dynamically), the entire step will be automatically hidden from the stepper navigation. The step and its hidden fields will still be processed during form submission.
736944

737945
## Customization
738946

0 commit comments

Comments
 (0)