KaaIoT, MikroTik & Teltonika Webinar. Build and Upgrade Equipment with IoT for Remote Access & Control
Kaa Documentation

Transformations Guide

Transformations Guide

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.

Core Concept: 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:

  1. dataset: Defines the raw data sources needed for the transformation. It specifies what data to fetch.
  2. select: Extracts and potentially mutates specific values from the fetched datasets using JSONPath. It specifies how to process the raw data.
  3. updates: Specifies which attributes of the target widget (e.g., enum, enumNames) should be updated with the processed data from the select step.

1. dataset - Defining Data Sources

The 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.
    • Unlike 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.
    • Example: If fetching 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 Fields

The 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 Values

The valueFrom property can take several forms:

  1. 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.

  2. Array of JSONPath Strings: Retrieves values from multiple paths. The transformation uses the first non-null value found. Example: ["$.someOptionalField", "$.someRequiredField"].

  3. 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.”

  4. 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.

2. select - Extracting Data with JSONPath

Once 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.

3. updates - Applying Data to the Widget

The 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}"
      }
    ]
  }
}

Summary

The transformation system provides a declarative way to manage complex data dependencies in forms:

  1. Define Data Needs (dataset): Specify what data is required, potentially with static params.
  2. Define Dependencies (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.
  3. Extract Relevant Data (select): Use JSONPath (value) and optional mutation to pull specific pieces from the fetched data.
  4. Apply Updates (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.


Available Dataset Sources

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
    • Description: Returns a list of configured service instances (like EPR, EPTS, ECR).
    • params: serviceName (Optional: Filter by service type, e.g., “EPR”).
  • applications
    • Description: Returns a list of available applications (solutions).
    • observes: None.
  • application_versions
    • Description: Returns the versions available for a specific application.
    • observes: appName (Required).
  • endpoints
    • Description: Returns a list of endpoints, potentially filtered by application, version, or search term.
    • 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
    • Description: Returns all known metadata attribute keys for a specific application or endpoint.
    • 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
    • Description: Returns only the editable metadata attribute keys for a specific application or endpoint.
    • observes: (serviceInstanceName or serviceName) (Required), (appName or endpointId) (One is required).
  • metadata
    • Description: Returns the full metadata object for a specific endpoint, along with a list of its flattened keys.
    • observes: endpointId (Required), serviceInstanceName (Required, defaults to ‘epr’).
  • time_series_names
    • Description: Returns the names of time series configured for an application.
    • observes: (serviceInstanceName or serviceName) (Required), appName (Required).
  • time_series_value_path
    • Description: Returns the available value paths within a specific time series (e.g., values.temperature, timestamp).
    • observes: serviceInstanceName (Required), appName (Required), timeSeriesName (Required).
  • time_series_values
    • Description: Returns the available value keys within a specific time series (e.g., temperature, humidity). (Note: Use time_series_value_path for full paths).
    • observes: (serviceInstanceName or serviceName) (Required), appName (Required), timeSeriesName (Required).
  • filters
    • Description: Returns saved endpoint filters for an application.
    • observes: serviceInstanceName (Required), appName (Required).
  • dashboards_paths
    • Description: Returns the paths of available dashboards.
    • observes: None.
  • configurations
    • Description: Returns the ECR configuration content (default or endpoint-specific).
    • params: configurationType (Optional: ‘default’ or ‘current’), defaultConfig (Optional: boolean).
    • observes: appVersionName (Required), endpointId (Required if not fetching default config).
  • config_keys
    • Description: Returns flattened keys from an ECR configuration.
    • params: Same as configurations.
    • observes: Same as configurations.
  • system_configurations
    • Description: Returns available system configurations for a specific entity level.
    • observes: systemConfigLevel (Required: ‘tenant’, ‘application’, ‘appVersion’, ‘endpoint’), (appName appVersionName endpointId tenantId) (Required based on level).
  • system_configuration
    • Description: Returns the content of a specific system configuration.
    • observes: systemConfigLevel (Required), systemConfigEntityId (Required, supports variables), configName (Required, supports variables).
  • actions
    • Description: Returns Rule Engine actions.
    • observes: actionType (Required).
  • rules
    • Description: Returns Rule Engine rules.
    • observes: None.
  • alertsTypes
    • Description: Returns alert types for a specific entity type.
    • observes: entityType (Required).
  • alert_metadata_keys
    • Description: Returns metadata keys relevant for alerts based on a specific type.
    • observes: alertMetadataKeyType (Required).
  • trigger_types
    • Description: Returns specific trigger types within a category.
    • observes: triggerType (Required).
  • recipients
    • Description: Returns configured notification recipients.
    • observes: None.
  • pools
    • Description: Returns IAMCore identity pools.
    • observes: None.
  • tenant_users
    • Description: Returns users within the current tenant.
    • observes: None.
  • asset_types
    • Description: Returns defined asset types.
    • observes: None.
  • asset_type_keys
    • Description: Returns attribute keys defined in an asset type’s schema.
    • observes: assetTypeId (Required).
  • assets
    • Description: Returns assets, optionally filtered by type.
    • params: flattenPayload (Optional: Flatten the asset definition).
    • observes: assetTypeId (Required), filter (Optional: {key, value}).
  • related_assets
    • Description: Returns assets related to a specific asset.
    • params: flattenPayload (Optional).
    • observes: assetId (Required), assetRelationDirection (Required: ‘fromEntityId’ or ‘toEntityId’), assetTypeId (Optional: Filter related assets by type).
  • relation_asset_types
    • Description: Returns asset types related to another asset type or an endpoint’s application.
    • observes: (assetTypeId or endpointId) (One is required).
  • relation_endpoints
    • Description: Returns endpoints related to an asset type or another endpoint’s application.
    • params: withMetadata, isEndpointsDisplayNameEnabled (Optional).
    • observes: (assetTypeId or endpointId) (One is required).
  • form_payload
    • Description: Returns the entire current form data object. Useful for complex internal logic within a transformation.
    • observes: None.
  • report_templates
    • Description: Returns available report templates.
    • observes: None.
  • timezones_list
    • Description: Returns a list of timezones with labels and offsets.
    • observes: None.
  • vpn_clients
    • Description: Returns configured VPN clients.
    • observes: None.
  • vnc_tunnels
    • Description: Returns VNC tunnels associated with a specific VPN client.
    • observes: vpnClientId (Required).