bpmn-io / bpmn-js-element-templates

The element template extension for bpmn-js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Connectors element templates: conditional field rendering based on placement

igpetrov opened this issue · comments

Is your feature request related to a problem? Please describe.

Allow conditional field rendering based on element position on a diagram.

Describe the solution you'd like

Consider a message start event. When it is attached to the process, there is no need to provide correlation variables. But if it is attached to an event subprocess, user has to specify correlation variables, for example, as follows:

    {
      "label": "Correlation key (process)",
      "type": "String",
      "group": "correlation",
      "feel": "required",
      "description": "Sets up the correlation key from process variables",
      "binding": {
        "type": "bpmn:Message#zeebe:subscription#property",
        "name": "correlationKey"
      },
      "constraints": {
        "notEmpty": true
      }
    }

We would like to have a field rendered conditionally, based on its placement, something like:

    {
      "label": "Correlation key (process)",
      "type": "String",
      "group": "correlation",
      "feel": "required",
      "description": "Sets up the correlation key from process variables",
      "binding": {
        "type": "bpmn:Message#zeebe:subscription#property",
        "name": "correlationKey"
      },
      "constraints": {
        "notEmpty": true
      },
      "condition": {
        "inSubprocess": true
      }
    }
See current template implementation with hack:
{
  "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json",
  "name": "Webhook Message Start Event Connector",
  "id": "io.camunda.connectors.webhook.WebhookConnectorStartMessage.v1",
  "version": 1,
  "description": "Configure webhook to receive callbacks",
  "documentationRef": "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/",
  "category": {
    "id": "connectors",
    "name": "Connectors"
  },
  "appliesTo": [
    "bpmn:StartEvent"
  ],
  "elementType": {
    "value": "bpmn:StartEvent",
    "eventDefinition": "bpmn:MessageEventDefinition"
  },
  "groups": [
    {
      "id": "endpoint",
      "label": "Webhook configuration"
    },
    {
      "id": "authentication",
      "label": "Authentication"
    },
    {
      "id": "authorization",
      "label": "Authorization"
    },
    {
      "id": "activation",
      "label": "Activation"
    },
    {
      "id": "correlation",
      "label": "Subprocess correlation"
    },
    {
      "id": "variable-mapping",
      "label": "Variable mapping"
    },
    {
      "id": "webhookResponse",
      "label": "Webhook response"
    }
  ],
  "properties": [
    {
      "type": "Hidden",
      "value": "io.camunda:webhook:1",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.type"
      }
    },
    {
      "type": "Hidden",
      "generatedValue": {
        "type": "uuid"
      },
      "binding": {
        "type": "bpmn:Message#property",
        "name": "name"
      }
    },
    {
      "type": "Hidden",
      "value": "ConfigurableInboundWebhook",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.subtype"
      }
    },
    {
      "id": "webhookMethod",
      "label": "Webhook method",
      "group": "endpoint",
      "description": "Select HTTP method",
      "value": "any",
      "type": "Dropdown",
      "choices": [
        {
          "name": "Any",
          "value": "any"
        },
        {
          "name": "Get",
          "value": "get"
        },
        {
          "name": "Post",
          "value": "post"
        },
        {
          "name": "Put",
          "value": "put"
        },
        {
          "name": "Delete",
          "value": "delete"
        }
      ],
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.method"
      }
    },
    {
      "label": "Webhook ID",
      "type": "String",
      "group": "endpoint",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.context"
      },
      "description": "The webhook ID is a part of the URL",
      "constraints": {
        "notEmpty": true
      }
    },
    {
      "id": "shouldValidateHmac",
      "label": "HMAC authentication",
      "group": "authentication",
      "description": "Choose whether HMAC verification is enabled. <a href='https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/#make-your-http-webhook-connector-for-receiving-messages-executable' target='_blank'>See documentation</a> and <a href='https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/#example' target='_blank'>example</a> that explains how to use HMAC-related fields",
      "value": "disabled",
      "type": "Dropdown",
      "choices": [
        {
          "name": "Enabled",
          "value": "enabled"
        },
        {
          "name": "Disabled",
          "value": "disabled"
        }
      ],
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.shouldValidateHmac"
      }
    },
    {
      "label": "HMAC secret key",
      "description": "Shared secret key",
      "type": "String",
      "group": "authentication",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.hmacSecret"
      },
      "condition": {
        "property": "shouldValidateHmac",
        "equals": "enabled"
      }
    },
    {
      "label": "HMAC header",
      "description": "Name of header attribute that will contain the HMAC value",
      "type": "String",
      "group": "authentication",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.hmacHeader"
      },
      "condition": {
        "property": "shouldValidateHmac",
        "equals": "enabled"
      }
    },
    {
      "label": "HMAC algorithm",
      "group": "authentication",
      "description": "Choose HMAC algorithm",
      "value": "sha_256",
      "type": "Dropdown",
      "choices": [
        {
          "name": "SHA-1",
          "value": "sha_1"
        },
        {
          "name": "SHA-256",
          "value": "sha_256"
        },
        {
          "name": "SHA-512",
          "value": "sha_512"
        }
      ],
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.hmacAlgorithm"
      },
      "condition": {
        "property": "shouldValidateHmac",
        "equals": "enabled"
      }
    },
    {
      "label": "HMAC scopes",
      "group": "authentication",
      "description": "Set HMAC scopes for calculating signature data. See <a href='https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/' target='_blank'>documentation</a>",
      "feel": "required",
      "type": "String",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.hmacScopes"
      },
      "condition": {
        "property": "shouldValidateHmac",
        "equals": "enabled"
      }
    },
    {
      "id": "authorizationType",
      "label": "Authorization type",
      "group": "authorization",
      "description": "Choose the authorization type.",
      "value": "NONE",
      "type": "Dropdown",
      "choices": [
        {
          "name": "None",
          "value": "NONE"
        },
        {
          "name": "JWT",
          "value": "JWT"
        },
        {
          "name": "Basic",
          "value": "BASIC"
        },
        {
          "name": "API Key",
          "value": "APIKEY"
        }
      ],
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.type"
      }
    },
    {
      "label": "JWK url",
      "description": "Well-known url of JWKs",
      "type": "String",
      "group": "authorization",
      "feel": "optional",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.jwt.jwkUrl"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "JWT"
      }
    },
    {
      "label": "JWT role property expression",
      "description": "Expression to extract the roles from the JWT token. <a href='https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/#how-to-extract-roles-from-jwt-data'>See documentation</a>",
      "type": "String",
      "group": "authorization",
      "feel": "required",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.jwt.permissionsExpression"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "JWT"
      }
    },
    {
      "label": "Required roles",
      "description": "List of roles to test JWT roles against",
      "type": "String",
      "group": "authorization",
      "feel": "required",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.jwt.requiredPermissions"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "JWT"
      }
    },
    {
      "label": "Username",
      "description": "Username for basic authentication",
      "type": "String",
      "group": "authorization",
      "feel": "optional",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.username"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "BASIC"
      },
      "constraints": {
        "notEmpty": true
      }
    },
    {
      "label": "Password",
      "description": "Password for basic authentication",
      "type": "String",
      "group": "authorization",
      "feel": "optional",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.password"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "BASIC"
      },
      "constraints": {
        "notEmpty": true
      }
    },
    {
      "label": "API Key",
      "description": "Expected API key",
      "type": "String",
      "group": "authorization",
      "feel": "optional",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.apiKey"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "APIKEY"
      },
      "constraints": {
        "notEmpty": true
      }
    },
    {
      "label": "API Key locator",
      "description": "A FEEL expression that extracts API key from the request. <a href='https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/#how-to-configure-api-key-authorization'>See documentation</a>",
      "type": "String",
      "group": "authorization",
      "feel": "required",
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.auth.apiKeyLocator"
      },
      "condition": {
        "property": "authorizationType",
        "equals": "APIKEY"
      },
      "constraints": {
        "notEmpty": true
      },
      "value": "=split(request.headers.authorization, \" \")[2]"
    },
    {
      "label": "Message ID expression",
      "feel": "required",
      "type": "String",
      "optional": true,
      "group": "activation",
      "binding": {
        "type": "zeebe:property",
        "name": "messageIdExpression"
      },
      "description": "Expression to extract unique identifier of a message"
    },
    {
      "label": "Condition",
      "type": "String",
      "group": "activation",
      "feel": "required",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "activationCondition"
      },
      "description": "Condition under which the connector triggers. Leave empty to catch all events. <a href='https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/http-webhook/#make-your-http-webhook-connector-for-receiving-messages-executable' target='_blank'>See documentation</a>"
    },
    {
      "label": "Correlation required",
      "description": "Indicates whether correlation is required. This is needed for event-based subprocess message start events",
      "id": "correlationRequired",
      "group": "correlation",
      "type": "Dropdown",
      "value": "notRequired",
      "choices": [
        {
          "name": "Correlation not required",
          "value": "notRequired"
        },
        {
          "name": "Correlation required",
          "value": "required"
        }
      ],
      "binding": {
        "type": "zeebe:property",
        "name": "correlationRequired"
      }
    },
    {
      "label": "Correlation key (process)",
      "type": "String",
      "group": "correlation",
      "feel": "required",
      "description": "Sets up the correlation key from process variables",
      "binding": {
        "type": "bpmn:Message#zeebe:subscription#property",
        "name": "correlationKey"
      },
      "constraints": {
        "notEmpty": true
      },
      "condition": {
        "property": "correlationRequired",
        "equals": "required"
      }
    },
    {
      "label": "Correlation key (payload)",
      "type": "String",
      "group": "correlation",
      "feel": "required",
      "binding": {
        "type": "zeebe:property",
        "name": "correlationKeyExpression"
      },
      "description": "Extracts the correlation key from the incoming message payload",
      "constraints": {
        "notEmpty": true
      },
      "condition": {
        "property": "correlationRequired",
        "equals": "required"
      }
    },
    {
      "label": "Result variable",
      "type": "String",
      "group": "variable-mapping",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "resultVariable"
      },
      "description": "Name of variable to store the result of the connector in"
    },
    {
      "label": "Result expression",
      "type": "String",
      "group": "variable-mapping",
      "feel": "required",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "resultExpression"
      },
      "description": "Expression to map the inbound payload to process variables"
    },
    {
      "label": "Response body expression",
      "type": "Text",
      "group": "webhookResponse",
      "feel": "required",
      "optional": true,
      "binding": {
        "type": "zeebe:property",
        "name": "inbound.responseBodyExpression"
      },
      "description": "Specify condition and response"
    }
  ],
  "icon": {
    "contents": "data:image/svg+xml,%3Csvg id='icon' xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 32 32'%3E%3Cdefs%3E%3Cstyle%3E .cls-1 %7B fill: none; %7D %3C/style%3E%3C/defs%3E%3Cpath d='M24,26a3,3,0,1,0-2.8164-4H13v1a5,5,0,1,1-5-5V16a7,7,0,1,0,6.9287,8h6.2549A2.9914,2.9914,0,0,0,24,26Z'/%3E%3Cpath d='M24,16a7.024,7.024,0,0,0-2.57.4873l-3.1656-5.5395a3.0469,3.0469,0,1,0-1.7326.9985l4.1189,7.2085.8686-.4976a5.0006,5.0006,0,1,1-1.851,6.8418L17.937,26.501A7.0005,7.0005,0,1,0,24,16Z'/%3E%3Cpath d='M8.532,20.0537a3.03,3.03,0,1,0,1.7326.9985C11.74,18.47,13.86,14.7607,13.89,14.708l.4976-.8682-.8677-.497a5,5,0,1,1,6.812-1.8438l1.7315,1.002a7.0008,7.0008,0,1,0-10.3462,2.0356c-.457.7427-1.1021,1.8716-2.0737,3.5728Z'/%3E%3Crect id='_Transparent_Rectangle_' data-name='&lt;Transparent Rectangle&gt;' class='cls-1' width='32' height='32'/%3E%3C/svg%3E"
  }
}

Describe alternatives you've considered

So far we use a workaround with a dropdown but that may be very confusing for a user.

Additional context

See context here: https://github.com/camunda/product-gaps/issues/109#issuecomment-1744889234

Hi,

Thanks for opening the issue. If I understand you correctly, your goal is to toggle properties based not only on other properties' values, but also on the container in which an element is placed. Are there any other examples except from Event Subprocess/Process which could apply here?

I am moving the issue to ready so that we discuss it in the next iteration planning.

@barmac I cannot come up with other concrete example. In addition to event subprocess, I would suggest also supporting regular subprocess, and maybe transaction.

I can imagine this could be something like:

{
  "label": "Correlation key (process)",
  "type": "String",
  "group": "correlation",
  "feel": "required",
  "description": "Sets up the correlation key from process variables",
  "binding": {
    "type": "bpmn:Message#zeebe:subscription#property",
    "name": "correlationKey"
  },
  "constraints": {
    "notEmpty": true
  },
  "condition": {
    "type": "containment",
    "container": "bpmn:subProcess"
    "triggeredByEvent": true
  }
}

Out of the properties, "triggeredByEvent" should only apply to subprocesses. "container" could take also bpmn:transaction and bpmn:process as values.

This might be related to camunda/camunda-modeler#3971

As I understand it, the root problem is that we have a Property (correlation key) that is allowed in some cases (Event-based subprocess start event) but not in others (normal start event). Although not part of the moodle, it is still part of the spec that zeebe expects.

The conditional rendering for placement would just ensure that all element template properties are valid for the element it is applied to. Alternatively, the editor could ensure that the properties shown are valid for the element.

Basically, we have 2 options to look at it:

  1. The Element template author knows best - we display all properties, no matter if the binding is valid for the current element type. In this case, we need all the options in the editor for conditions, as highlighted here.
  2. The Editor knows best - We filter out properties with bindings that are not supported by this element and do not display them. This could be confusing to the element template author, but would not require special conditions in the element templates to replicate bpmn behavior.

IMO, it would be a better user experience for the element template editors if the modeler verifies the properties before displaying, at is removes the need to add conditions as shown in this editor.

Optional: For debugging, it would make sense to print a warning (console, maybe?) if properties are filtered out. We could only enable this when a debugging flag is set.

I tested out approach 2️⃣ and this is feasible.

/cc @igpetrov to verify that this approach would also solve your problem.

Example

The following Template is applied:

Template
  {
    "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json",
    "name": "Conditional rendering correlation key",
    "id": "asdfasdgeryt45yhsdfgsfdgfdzb",
    "appliesTo": [
      "bpmn:Event"
    ],
    "elementType": {
      "value": "bpmn:StartEvent",
      "eventDefinition": "bpmn:MessageEventDefinition"
    },
    "properties": [
      {
        "type": "String",
        "label": "Message name",
        "binding": {
          "type": "bpmn:Message#property",
          "name": "name"
        }
      },
      {
        "type": "String",
        "label": "Message Correlation key",
        "binding": {
          "type": "bpmn:Message#zeebe:subscription#property",
          "name": "correlationKey"
        }
      }
    ]
  }

It does not contain any defined conditions. The correlation key field is only displayed when the element supports the property. This would also apply to other properties, like inputs on a receive task.
Recording 2023-11-13 at 11 13 05

I see that you have a second input in your example connector: Correlation key (payload) that binds to a zeebe Property. As zeebe properties are valid on all elements, we would need to add a condition to this one. I propose we introduce a new condition that allows conditionally showing a property if another property is shown:

  "condition": {
    "property": "correlationKey",
    "shown": true
  }

@marstamm, thank you for spending time on this! From the CX perspective it will work out. From a template point of view it seems to be not really obvious that it is going to get hidden in a subprocess.

I just checked the BPMN specification for the Receive Task. It can act as a Message Start Event, but only if property called instantiate is set to true. It cannot have incoming flows though:

Receive Tasks can be defined as the instantiation mechanism for the
Process with the instantiate attribute. This attribute MAY be set to true if
the Task is the first Activity (i.e., there are no incoming Sequence Flows).
Multiple Tasks MAY have this attribute set to true.

image

However, we don't support modeling such tasks yet.

Descision:

  • We will hide the correlationKey property implicitly
  • We introduce a new property to "chain" conditions
 "condition": {
   "property": "correlationKey",
   "isShown": true
 }
  • We only apply this for correlationKeys, not for all BPMN elements

We have changed the condition name from isVisible to isActive to account for fields of type Hidden.

The integration PR for the changes is in #31
Documentation is in draft and can be checked out here