This guide provides an in-depth explanation of the custom transformation engine used within our UI Schema (uiSchema
), specifically through the ui:options.transformation
object. This powerful mechanism allows certain widgets to dynamically fetch, filter, and update their configuration (like available options) based on the state of other fields in the form or external data sources.
This enables complex dependency scenarios, such as populating an “Application Version” dropdown based on the selected “Application”, or filtering a list of “Metadata Keys” based on a chosen “Service Instance”.
Important: The ui:options.transformation
configuration is only processed by the following specific widgets:
SelectWidget
TextWidget
select
autocompleteMulti
templating
editableSelect
Duration
AvatarUploadWidget
CheckboxGroup
For these widgets, the primary purpose of using transformations is to dynamically populate the widget’s enum
(the actual values) and enumNames
(the displayed labels) or to provide data for features like autocompletion and templating suggestions based on data fetched or computed according to the transformation rules. Applying ui:options.transformation
to other widgets will have no effect.
ui:options.transformation
The transformation logic for a widget is defined within its ui:options.transformation
property in the uiSchema
. The structure generally follows this pattern:
{
"fieldName": {
"ui:widget": "SelectWidget", // Or templating, CheckboxGroup, etc.
"ui:options": {
"transformation": {
"dataset": { /* ... Data Source Definitions ... */ },
"select": { /* ... Data Extraction Logic ... */ },
"updates": [ /* ... Target Widget Attribute Updates ... */ ]
}
}
}
}
These three key parts work together:
dataset
: Defines the raw data sources needed for the transformation. It specifies what data to fetch.select
: Extracts and potentially mutates specific values from the fetched datasets using JSONPath. It specifies how to process the raw data.updates
: Specifies which attributes of the target widget (e.g., enum
, enumNames
) should be updated with the processed data from the select
step.dataset
- Defining Data SourcesThe dataset
object contains one or more key-value pairs. Each key is a local alias for the data source within this transformation, and the value describes the dataset configuration.
// Dataset Configuration Structure
{
name: string; // Unique identifier for the data source type (e.g., 'applications', 'metadata_keys')
sourceName?: string; // Optional: Alias if different from 'name'
observes?: ObserveProperty[]; // Defines dependencies on other form fields
params?: Record<string, any>; // Static parameters for the data source request
}
name
: Identifies the type of data to fetch (e.g., 'applications'
, 'time_series_names'
, 'metadata_keys'
). This corresponds to a registered data source fetcher.sourceName
: Optional alias. If provided, the fetched data will be available under this name in the context; otherwise, it uses name
.params
: Static parameters passed directly to the data source fetcher. Useful for pre-filtering.
observes
, these parameters are fixed for this transformation and do not change based on user input in other fields. They provide constant context for the data fetch.metadata_keys
, you might use static params to always filter for keys associated with a specific service type, regardless of other form selections: "params": { "serviceType": "EPR" }
.observes
: (Crucial for dynamic behavior) An array defining which other form fields this data source depends on.dataset.observes
- Observing Other FieldsThe observes
array is key to making transformations dynamic. Each element describes an observed property.
// Observe Property Structure
{
name: string; // Name of the parameter expected by the data source fetcher
valueFrom: ValueFrom; // How to get the value for this parameter from the form
isArray?: boolean; // Treat the observed value(s) as an array
hideValues?: string[]; // Only trigger fetch if observed value is NOT in this list
showValues?: string[]; // Only trigger fetch if observed value IS in this list
}
name
: The parameter name the data source fetcher expects (e.g., appName
, serviceInstanceName
).valueFrom
: Defines how to retrieve the value for this parameter from the current form state. This is the core of the dependency logic.isArray
, hideValues
, showValues
: Optional flags to control fetching based on the observed value.observes.valueFrom
- Getting Dependent ValuesThe valueFrom
property can take several forms:
Simple JSONPath String: Retrieves a value from the form data using JSONPath notation (relative to the form’s root data object). Example: "$.serviceIntegration.applicationName"
retrieves the value of the applicationName
field within the serviceIntegration
object.
Array of JSONPath Strings: Retrieves values from multiple paths. The transformation uses the first non-null value found. Example: ["$.someOptionalField", "$.someRequiredField"]
.
Conditional Object: Selects a value based on conditions met by another field.
// Conditional ValueFrom Structure
{
oneOf: [
// ... ValueFromCondition objects ...
]
}
// ValueFromCondition Structure
{
fieldValue: string; // JSONPath to the field whose value determines the condition
match: string; // The value the 'fieldValue' path must match
valueFrom?: string; // JSONPath to get the value from if condition matches
value?: string; // Static value to use if condition matches
isArray?: boolean;
}
Example (getting appName
based on applicationSelection
):
{
"name": "appName",
"valueFrom": {
"oneOf": [
{
"fieldValue": "$.serviceIntegration.applicationSelection",
"match": "Single",
"valueFrom": "$.serviceIntegration.applicationName"
},
{
"fieldValue": "$.serviceIntegration.applicationSelection",
"match": "Multi",
"valueFrom": "$.serviceIntegration.applications"
}
]
}
}
This reads: “The appName
parameter for the data source depends on serviceIntegration.applicationSelection
. If it’s 'Single'
, use the value from serviceIntegration.applicationName
. If it’s 'Multi'
, use the value from serviceIntegration.applications
.”
Array Item Context ($item
): When a transformation is defined within the uiSchema
for an items
object (i.e., inside an array field), you can use $item
as a prefix in the JSONPath to refer to fields within the current array item. This allows a transformation for one item to depend on other values within that specific item.
Example (conceptual): Imagine an array of seriesConfiguration
items, where each item has a name
(time series name) and valuePath
(path within that time series). To populate the valuePath
dropdown based on the selected name
for that specific series:
"columns": {
"type": "array",
"items": { // Schema for each item
"type": "object",
"properties": {
"name": { "type": "string", "title": "Time Series Name" },
"valuePath": { "type": "string", "title": "Value Path" }
}
}
},
// In the uiSchema:
"columns": {
"items": {
"valuePath": {
"ui:widget": "SelectWidget",
"ui:options": {
"transformation": {
"dataset": {
"timeSeriesValuePaths": {
"name": "time_series_value_path", // Data source type
"observes": [
{
"name": "timeSeriesName", // Param data source expects
"valueFrom": "$item.name" // Get value from 'name' field in this item
}
// ... other observes like appName, serviceInstance ...
]
}
},
"select": {
"paths": { "type": "JSONPath", "value": "$.timeSeriesValuePaths.*" }
},
"updates": [
{ "attribute": "enum", "value": "${paths}" },
{ "attribute": "enumNames", "value": "${paths}" }
]
}
}
}
}
}
Here, valueFrom: "$item.name"
ensures that the time_series_value_path
dataset is fetched using the name
value specifically from the array item currently being rendered or edited.
select
- Extracting Data with JSONPathOnce the dataset
is fetched (potentially triggered by observes
), the select
object extracts the relevant parts using JSONPath.
// Select Structure
{
type: 'JSONPath';
value: string; // The JSONPath expression to apply to the fetched dataset(s)
mutation?: string; // Optional template string to format the extracted value(s)
observes?: ObserveProperty[]; // Can also observe form fields directly
}
type: 'JSONPath'
: Currently the only supported select type.value
: A JSONPath expression evaluated against the context containing the fetched datasets (aliased by their keys in the dataset
object). Example: "$.applications.*.name"
selects all name
properties from all objects within the applications
dataset.mutation
: An optional template string. If provided, each value selected by the value
path is interpolated into this template. Example: If value
selects ["id1", "id2"]
and mutation
is "prefix_${value}"
, the result becomes ["prefix_id1", "prefix_id2"]
.Example (getting application names):
"select": {
"applicationNames": {
"type": "JSONPath",
"value": "$.applications.*.name" // Select names from the 'applications' dataset
},
"applicationDisplayNames": {
"type": "JSONPath",
"value": "$.applications.*.displayName" // Select display names
}
}
Here, two selections are defined: applicationNames
and applicationDisplayNames
, extracting different fields from the same applications
dataset defined earlier.
updates
- Applying Data to the WidgetThe final step is updates
. This array defines how the data extracted by select
should be applied to the target widget’s properties.
// Update Structure
{
attribute: string; // The attribute of the widget to update (e.g., 'enum', 'enumNames', 'value')
value: string; // Template string referencing selected data (e.g., '${applicationNames}')
template?: string; // Optional: Further template applied to the value
target?: 'ui:options'; // Optional: Target sub-object (rarely needed)
isArray?: boolean; // Treat the value as an array
}
attribute
: The name of the property on the widget’s schema or UI schema options to update. Common targets are enum
(for values) and enumNames
(for display labels).value
: A template string that references the results of the select
step using ${selectKey}
notation. Example: "${applicationNames}"
uses the data selected by the applicationNames
key in the select
object. This provides the initial data for the update.template
: (Optional) A more sophisticated template string, often leveraging a built-in templating engine (using |
for filters/functions like filterBy
, mapBy
, json
). This template operates on the data context (including results from the select
step, often accessed via a params
object like ${params.selectKey}
). It allows complex filtering, mapping, and formatting of the selected data before it gets applied to the target attribute
. If omitted, the raw value from the value
property (if defined) or the direct result of the select
step is typically used.target
: (Optional) Specifies a nested object within the widget’s schema or UI options where the attribute
should be updated (e.g., 'ui:options'
). Rarely needed.isArray
: (Optional) If true
, ensures the final value applied to the attribute
is treated as an array.Example (applying application names - simpler case using value
):
"select": {
"appNames": { "type": "JSONPath", "value": "$.applications.*.name" },
"appDisplayNames": { "type": "JSONPath", "value": "$.applications.*.displayName" }
},
"updates": [
{ "attribute": "enum", "value": "${appNames}" },
{ "attribute": "enumNames", "value": "${appDisplayNames}" }
]
Example (using advanced template
for filtering/mapping):
This example fetches endpoints but then uses the template
to filter them based on metadata.type
and map the results to different attributes for enum
and enumNames
.
{
"transformation": {
"dataset": {
"endpointsData": { // Alias for the dataset result
"name": "endpoints",
"params": {
"serviceName": "EPR",
"appName": "<your_application_name>", // Masked app name
"withMetadata": true
}
}
},
"select": {
"allEndpoints": { // Select all fetched endpoint objects
"type": "JSONPath",
"value": "$.endpointsData.*"
}
},
"updates": [
{
"attribute": "enum", // Target the values
// Process the selected endpoints:
// 1. Filter where metadata.type is 'light_switch'
// 2. Map the filtered list to get only the endpointId
// 3. Format the result as JSON (likely resulting in an array of strings)
"template": "${params.allEndpoints | filterBy('metadata.type', 'light_switch') | mapBy('endpointId') | json}"
},
{
"attribute": "enumNames", // Target the display names
// Process the selected endpoints:
// 1. Filter where metadata.type is 'light_switch'
// 2. Map the filtered list to get the metadata.location
// 3. Format the result as JSON (likely resulting in an array of strings)
"template": "${params.allEndpoints | filterBy('metadata.type', 'light_switch') | mapBy('metadata.location') | json}"
}
]
}
}
The transformation system provides a declarative way to manage complex data dependencies in forms:
dataset
): Specify what data is required, potentially with static params
.observes
): Specify when to fetch/re-fetch data based on changes in other form fields, using valueFrom
(simple path, array, or conditional object) to extract dependent values.select
): Use JSONPath (value
) and optional mutation
to pull specific pieces from the fetched data.updates
): Use template strings (value
) referencing selected data to modify target widget attributes (attribute
, usually enum
and enumNames
).By combining these steps, sophisticated dynamic forms can be built, reacting to user input and external data sources in real-time.
Below is a list of common data source names that can be used in the dataset.name
field within a transformation configuration. Each source fetches specific types of data from the platform.
Note: The observes
parameters listed are typically required to provide context for the data fetch (e.g., which application to query). Static params
offer additional filtering options for the specific data source.
service_instances
params
: serviceName
(Optional: Filter by service type, e.g., “EPR”).applications
observes
: None.application_versions
observes
: appName
(Required).endpoints
params
: withMetadata
(Optional: Include endpoint metadata in the result), isEndpointsDisplayNameEnabled
(Optional: Format endpoint name with metadata), limit
, offset
, searchExpression
(Optional: for server-side filtering/pagination).observes
: (serviceInstanceName
or serviceName
) (Required), appName
(Optional: Filter by application name/s), appVersionName
(Optional: Filter by version name).metadata_keys
params
: onlyEditableFields
(Optional: If true, returns only keys marked as editable).observes
: (serviceInstanceName
or serviceName
) (Required), (appName
or endpointId
) (One is required).editable_metadata_keys
observes
: (serviceInstanceName
or serviceName
) (Required), (appName
or endpointId
) (One is required).metadata
observes
: endpointId
(Required), serviceInstanceName
(Required, defaults to ‘epr’).time_series_names
observes
: (serviceInstanceName
or serviceName
) (Required), appName
(Required).time_series_value_path
values.temperature
, timestamp
).observes
: serviceInstanceName
(Required), appName
(Required), timeSeriesName
(Required).time_series_values
temperature
, humidity
). (Note: Use time_series_value_path
for full paths).observes
: (serviceInstanceName
or serviceName
) (Required), appName
(Required), timeSeriesName
(Required).filters
observes
: serviceInstanceName
(Required), appName
(Required).dashboards_paths
observes
: None.configurations
params
: configurationType
(Optional: ‘default’ or ‘current’), defaultConfig
(Optional: boolean).observes
: appVersionName
(Required), endpointId
(Required if not fetching default config).config_keys
params
: Same as configurations
.observes
: Same as configurations
.system_configurations
observes : systemConfigLevel (Required: ‘tenant’, ‘application’, ‘appVersion’, ‘endpoint’), (appName |
appVersionName |
endpointId |
tenantId ) (Required based on level). |
system_configuration
observes
: systemConfigLevel
(Required), systemConfigEntityId
(Required, supports variables), configName
(Required, supports variables).actions
observes
: actionType
(Required).rules
observes
: None.alertsTypes
observes
: entityType
(Required).alert_metadata_keys
observes
: alertMetadataKeyType
(Required).trigger_types
observes
: triggerType
(Required).recipients
observes
: None.pools
observes
: None.tenant_users
observes
: None.asset_types
observes
: None.asset_type_keys
observes
: assetTypeId
(Required).assets
params
: flattenPayload
(Optional: Flatten the asset definition).observes
: assetTypeId
(Required), filter
(Optional: {key, value}).related_assets
params
: flattenPayload
(Optional).observes
: assetId
(Required), assetRelationDirection
(Required: ‘fromEntityId’ or ‘toEntityId’), assetTypeId
(Optional: Filter related assets by type).relation_asset_types
observes
: (assetTypeId
or endpointId
) (One is required).relation_endpoints
params
: withMetadata
, isEndpointsDisplayNameEnabled
(Optional).observes
: (assetTypeId
or endpointId
) (One is required).form_payload
observes
: None.report_templates
observes
: None.timezones_list
observes
: None.vpn_clients
observes
: None.vnc_tunnels
observes
: vpnClientId
(Required).