From 0ac6914515dd1712afae39f4900f2787cd396bcf Mon Sep 17 00:00:00 2001 From: Pavel Kotelevsky Date: Fri, 12 Apr 2024 09:51:36 +0200 Subject: [PATCH] feat(generator): support inbound deduplication; boolean props; enhanced conditions --- .../rabbitmq-inbound-connector-boundary.json | 62 ++++++++- ...bbitmq-inbound-connector-intermediate.json | 62 ++++++++- ...bitmq-inbound-connector-message-start.json | 62 ++++++++- ...abbitmq-inbound-connector-start-event.json | 62 ++++++++- connectors/rabbitmq/pom.xml | 3 + .../rabbitmq/inbound/RabbitMqExecutable.java | 2 +- .../generator/cli/command/ConGen.java | 4 +- .../generator/api/GeneratorConfiguration.java | 11 +- .../generator/dsl/BooleanProperty.java | 14 +- .../generator/dsl/CommonProperties.java | 41 ++++++ .../generator/dsl/DropdownProperty.java | 8 +- .../generator/dsl/HiddenProperty.java | 6 +- .../connector/generator/dsl/Property.java | 22 ++- .../generator/dsl/PropertyBuilder.java | 10 +- .../generator/dsl/PropertyCondition.java | 2 +- .../generator/dsl/PropertyGroup.java | 37 +++++- .../generator/dsl/StringProperty.java | 12 +- .../connector/generator/dsl/TextProperty.java | 12 +- .../java/ClassBasedTemplateGenerator.java | 12 +- .../java/annotation/ElementTemplate.java | 4 + .../java/annotation/TemplateProperty.java | 29 ++++ .../TemplatePropertyFieldProcessor.java | 79 ++++++++++- .../java/util/ConfigurationUtil.java | 4 +- .../java/util/TemplatePropertiesUtil.java | 6 +- .../dsl/ElementTemplateSerializationTest.java | 1 + ...nboundClassBasedTemplateGeneratorTest.java | 125 +++++++++++++++++- ...tboundClassBasedTemplateGeneratorTest.java | 69 +++++++++- .../example/outbound/MyConnectorFunction.java | 6 +- .../example/outbound/MyConnectorInput.java | 19 ++- .../test/resources/test-element-template.json | 7 +- .../connector/generator/ConnectorConfig.java | 11 ++ .../ElementTemplateGeneratorMojo.java | 26 +++- .../OpenApiOutboundTemplateGenerator.java | 11 +- ...anCollectionOutboundTemplateGenerator.java | 11 +- ...CollectionsGeneratorDryRunExampleTest.java | 5 +- 35 files changed, 789 insertions(+), 68 deletions(-) diff --git a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-boundary.json b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-boundary.json index 456b3ccdce..570608ebfb 100644 --- a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-boundary.json +++ b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-boundary.json @@ -4,7 +4,7 @@ "id" : "io.camunda.connectors.inbound.RabbitMQ.Boundary.v1", "description" : "Receive a message from RabbitMQ", "documentationRef" : "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/rabbitmq/?rabbitmq=inbound", - "version" : 6, + "version" : 7, "category" : { "id" : "connectors", "name" : "Connectors" @@ -29,6 +29,10 @@ }, { "id" : "correlation", "label" : "Correlation" + }, { + "id" : "deduplication", + "label" : "Deduplication", + "tooltip" : "Deduplication allows you to configure multiple inbound connector elements to reuse the same backend (consumer/thread/endpoint) by sharing the same deduplication ID." }, { "id" : "output", "label" : "Output mapping" @@ -291,6 +295,62 @@ "type" : "bpmn:Message#property" }, "type" : "Hidden" + }, { + "id" : "deduplicationModeManualFlag", + "label" : "Manual mode", + "description" : "By default, similar connectors receive the same deduplication ID. Customize by activating manual mode", + "value" : false, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationModeManualFlag", + "type" : "zeebe:property" + }, + "type" : "Boolean" + }, { + "id" : "deduplicationModeManual", + "value" : "MANUAL", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationModeAuto", + "value" : "AUTO", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : false, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationId", + "label" : "Deduplication ID", + "constraints" : { + "notEmpty" : true + }, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationId", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "String" }, { "id" : "resultVariable", "label" : "Result variable", diff --git a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-intermediate.json b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-intermediate.json index 1413672582..44ea501453 100644 --- a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-intermediate.json +++ b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-intermediate.json @@ -4,7 +4,7 @@ "id" : "io.camunda.connectors.inbound.RabbitMQ.Intermediate.v1", "description" : "Receive a message from RabbitMQ", "documentationRef" : "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/rabbitmq/?rabbitmq=inbound", - "version" : 6, + "version" : 7, "category" : { "id" : "connectors", "name" : "Connectors" @@ -29,6 +29,10 @@ }, { "id" : "correlation", "label" : "Correlation" + }, { + "id" : "deduplication", + "label" : "Deduplication", + "tooltip" : "Deduplication allows you to configure multiple inbound connector elements to reuse the same backend (consumer/thread/endpoint) by sharing the same deduplication ID." }, { "id" : "output", "label" : "Output mapping" @@ -291,6 +295,62 @@ "type" : "bpmn:Message#property" }, "type" : "Hidden" + }, { + "id" : "deduplicationModeManualFlag", + "label" : "Manual mode", + "description" : "By default, similar connectors receive the same deduplication ID. Customize by activating manual mode", + "value" : false, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationModeManualFlag", + "type" : "zeebe:property" + }, + "type" : "Boolean" + }, { + "id" : "deduplicationModeManual", + "value" : "MANUAL", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationModeAuto", + "value" : "AUTO", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : false, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationId", + "label" : "Deduplication ID", + "constraints" : { + "notEmpty" : true + }, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationId", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "String" }, { "id" : "resultVariable", "label" : "Result variable", diff --git a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-message-start.json b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-message-start.json index 87c566ac33..5b9bd3754e 100644 --- a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-message-start.json +++ b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-message-start.json @@ -4,7 +4,7 @@ "id" : "io.camunda.connectors.inbound.RabbitMQ.MessageStart.v1", "description" : "Receive a message from RabbitMQ", "documentationRef" : "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/rabbitmq/?rabbitmq=inbound", - "version" : 6, + "version" : 7, "category" : { "id" : "connectors", "name" : "Connectors" @@ -29,6 +29,10 @@ }, { "id" : "correlation", "label" : "Correlation" + }, { + "id" : "deduplication", + "label" : "Deduplication", + "tooltip" : "Deduplication allows you to configure multiple inbound connector elements to reuse the same backend (consumer/thread/endpoint) by sharing the same deduplication ID." }, { "id" : "output", "label" : "Output mapping" @@ -319,6 +323,62 @@ "type" : "bpmn:Message#property" }, "type" : "Hidden" + }, { + "id" : "deduplicationModeManualFlag", + "label" : "Manual mode", + "description" : "By default, similar connectors receive the same deduplication ID. Customize by activating manual mode", + "value" : false, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationModeManualFlag", + "type" : "zeebe:property" + }, + "type" : "Boolean" + }, { + "id" : "deduplicationModeManual", + "value" : "MANUAL", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationModeAuto", + "value" : "AUTO", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : false, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationId", + "label" : "Deduplication ID", + "constraints" : { + "notEmpty" : true + }, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationId", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "String" }, { "id" : "resultVariable", "label" : "Result variable", diff --git a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-start-event.json b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-start-event.json index 65e3dede74..4a913543d7 100644 --- a/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-start-event.json +++ b/connectors/rabbitmq/element-templates/rabbitmq-inbound-connector-start-event.json @@ -4,7 +4,7 @@ "id" : "io.camunda.connectors.inbound.RabbitMQ.StartEvent.v1", "description" : "Receive a message from RabbitMQ", "documentationRef" : "https://docs.camunda.io/docs/components/connectors/out-of-the-box-connectors/rabbitmq/?rabbitmq=inbound", - "version" : 6, + "version" : 7, "category" : { "id" : "connectors", "name" : "Connectors" @@ -25,6 +25,10 @@ }, { "id" : "activation", "label" : "Activation" + }, { + "id" : "deduplication", + "label" : "Deduplication", + "tooltip" : "Deduplication allows you to configure multiple inbound connector elements to reuse the same backend (consumer/thread/endpoint) by sharing the same deduplication ID." }, { "id" : "output", "label" : "Output mapping" @@ -236,6 +240,62 @@ "type" : "zeebe:property" }, "type" : "String" + }, { + "id" : "deduplicationModeManualFlag", + "label" : "Manual mode", + "description" : "By default, similar connectors receive the same deduplication ID. Customize by activating manual mode", + "value" : false, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationModeManualFlag", + "type" : "zeebe:property" + }, + "type" : "Boolean" + }, { + "id" : "deduplicationModeManual", + "value" : "MANUAL", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationModeAuto", + "value" : "AUTO", + "group" : "deduplication", + "binding" : { + "name" : "deduplicationMode", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : false, + "type" : "simple" + }, + "type" : "Hidden" + }, { + "id" : "deduplicationId", + "label" : "Deduplication ID", + "constraints" : { + "notEmpty" : true + }, + "group" : "deduplication", + "binding" : { + "name" : "deduplicationId", + "type" : "zeebe:property" + }, + "condition" : { + "property" : "deduplicationModeManualFlag", + "equals" : true, + "type" : "simple" + }, + "type" : "String" }, { "id" : "resultVariable", "label" : "Result variable", diff --git a/connectors/rabbitmq/pom.xml b/connectors/rabbitmq/pom.xml index d224e47f45..6e238de84f 100644 --- a/connectors/rabbitmq/pom.xml +++ b/connectors/rabbitmq/pom.xml @@ -84,6 +84,9 @@ rabbitmq-inbound-connector-boundary.json + + true + diff --git a/connectors/rabbitmq/src/main/java/io/camunda/connector/rabbitmq/inbound/RabbitMqExecutable.java b/connectors/rabbitmq/src/main/java/io/camunda/connector/rabbitmq/inbound/RabbitMqExecutable.java index 2a89898e89..b148c806db 100644 --- a/connectors/rabbitmq/src/main/java/io/camunda/connector/rabbitmq/inbound/RabbitMqExecutable.java +++ b/connectors/rabbitmq/src/main/java/io/camunda/connector/rabbitmq/inbound/RabbitMqExecutable.java @@ -29,7 +29,7 @@ id = "io.camunda.connectors.inbound.RabbitMQ", name = "RabbitMQ Connector", icon = "icon.svg", - version = 6, + version = 7, inputDataClass = RabbitMqInboundProperties.class, description = "Receive a message from RabbitMQ", documentationRef = diff --git a/element-template-generator/congen-cli/src/main/java/io/camunda/connector/generator/cli/command/ConGen.java b/element-template-generator/congen-cli/src/main/java/io/camunda/connector/generator/cli/command/ConGen.java index fd937f013d..bef2bd9cd8 100644 --- a/element-template-generator/congen-cli/src/main/java/io/camunda/connector/generator/cli/command/ConGen.java +++ b/element-template-generator/congen-cli/src/main/java/io/camunda/connector/generator/cli/command/ConGen.java @@ -22,6 +22,7 @@ import io.camunda.connector.generator.dsl.BpmnType; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -91,7 +92,8 @@ GeneratorConfiguration generatorConfiguration() { templateId, templateName, null, - bpmnTypes); + bpmnTypes, + Map.of()); // todo: do we need to support feature overrides from the CLI? } private BpmnType parseBpmnType(String type) { diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/api/GeneratorConfiguration.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/api/GeneratorConfiguration.java index f61cb9fbb3..b8859cfab5 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/api/GeneratorConfiguration.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/api/GeneratorConfiguration.java @@ -18,6 +18,7 @@ import io.camunda.connector.generator.dsl.BpmnType; import java.util.Collections; +import java.util.Map; import java.util.Set; /** Configuration for the element template generator */ @@ -26,7 +27,8 @@ public record GeneratorConfiguration( String templateId, String templateName, Integer templateVersion, - Set elementTypes) { + Set elementTypes, + Map features) { /** * Connectors in hybrid mode have a configurable task definition type (for outbound), or a @@ -38,8 +40,13 @@ public enum ConnectorMode { HYBRID } + public enum GenerationFeature { + INBOUND_DEDUPLICATION + } + public static final GeneratorConfiguration DEFAULT = - new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Collections.emptySet()); + new GeneratorConfiguration( + ConnectorMode.NORMAL, null, null, null, Collections.emptySet(), Collections.emptyMap()); public record ConnectorElementType( Set appliesTo, diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/BooleanProperty.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/BooleanProperty.java index 55a0b33e4d..64ee2f8509 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/BooleanProperty.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/BooleanProperty.java @@ -25,13 +25,14 @@ public BooleanProperty( String label, String description, Boolean required, - String value, + Boolean value, GeneratedValue generatedValue, PropertyConstraints constraints, FeelMode feel, String group, PropertyBinding binding, - PropertyCondition condition) { + PropertyCondition condition, + String tooltip) { super( name, label, @@ -44,6 +45,7 @@ public BooleanProperty( group, binding, condition, + tooltip, TYPE); } @@ -56,18 +58,22 @@ public static class BooleanPropertyBuilder extends PropertyBuilder { private BooleanPropertyBuilder() {} public BooleanProperty build() { + if (value != null && !(value instanceof Boolean)) { + throw new IllegalStateException("Value of a boolean property must be a boolean"); + } return new BooleanProperty( id, label, description, optional, - value, + (Boolean) value, generatedValue, constraints, feel, group, binding, - condition); + condition, + tooltip); } } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java index 58366d841a..291ea7c2ea 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java @@ -20,6 +20,7 @@ import io.camunda.connector.generator.dsl.Property.FeelMode; import io.camunda.connector.generator.dsl.PropertyBinding.ZeebeProperty; import io.camunda.connector.generator.dsl.PropertyBinding.ZeebeSubscriptionProperty; +import io.camunda.connector.generator.dsl.PropertyCondition.Equals; import java.util.List; public class CommonProperties { @@ -137,4 +138,44 @@ public static PropertyBuilder correlationRequiredDropdown() { .value("notRequired") .binding(new ZeebeProperty("correlationRequired")); } + + public static PropertyBuilder deduplicationModeManualFlag() { + return BooleanProperty.builder() + .id("deduplicationModeManualFlag") + .label("Manual mode") + .group("deduplication") + .description( + "By default, similar connectors receive the same deduplication ID. Customize by activating manual mode") + .value(false) + .binding(new ZeebeProperty("deduplicationModeManualFlag")); + } + + public static PropertyBuilder deduplicationModeManual() { + return HiddenProperty.builder() + .id("deduplicationModeManual") + .group("deduplication") + .value("MANUAL") + .condition(new Equals("deduplicationModeManualFlag", true)) + .binding(new ZeebeProperty("deduplicationMode")); + } + + public static PropertyBuilder deduplicationModeAuto() { + return HiddenProperty.builder() + .id("deduplicationModeAuto") + .group("deduplication") + .value("AUTO") + .condition(new Equals("deduplicationModeManualFlag", false)) + .binding(new ZeebeProperty("deduplicationMode")); + } + + public static PropertyBuilder deduplicationId() { + return StringProperty.builder() + .id("deduplicationId") + .label("Deduplication ID") + .group("deduplication") + .feel(FeelMode.disabled) + .binding(new ZeebeProperty("deduplicationId")) + .constraints(PropertyConstraints.builder().notEmpty(true).build()) + .condition(new Equals("deduplicationModeManualFlag", true)); + } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/DropdownProperty.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/DropdownProperty.java index a1dc029d83..d8276f4f4a 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/DropdownProperty.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/DropdownProperty.java @@ -36,6 +36,7 @@ public DropdownProperty( String group, PropertyBinding binding, PropertyCondition condition, + String tooltip, List choices) { super( name, @@ -49,6 +50,7 @@ public DropdownProperty( group, binding, condition, + tooltip, TYPE); this.choices = choices; } @@ -75,18 +77,22 @@ public DropdownPropertyBuilder choices(List choices) { } public DropdownProperty build() { + if (value != null && !(value instanceof String)) { + throw new IllegalStateException("Value of a dropdown property must be a string"); + } return new DropdownProperty( id, label, description, optional, - value, + (String) value, generatedValue, constraints, feel, group, binding, condition, + tooltip, choices); } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/HiddenProperty.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/HiddenProperty.java index ba2cf55b4d..8c6b44b8cc 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/HiddenProperty.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/HiddenProperty.java @@ -44,6 +44,7 @@ public HiddenProperty( group, binding, condition, + null, TYPE); } @@ -56,12 +57,15 @@ public static class HiddenPropertyBuilder extends PropertyBuilder { private HiddenPropertyBuilder() {} public HiddenProperty build() { + if (value != null && !(value instanceof String)) { + throw new IllegalStateException("Value of a hidden property must be a string"); + } return new HiddenProperty( id, label, description, optional, - value, + (String) value, generatedValue, constraints, feel, diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/Property.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/Property.java index 814d729ff4..22ef2ae552 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/Property.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/Property.java @@ -29,13 +29,14 @@ public abstract sealed class Property protected final String label; protected final String description; protected final Boolean optional; - protected final String value; + protected final Object value; protected final GeneratedValue generatedValue; protected final PropertyConstraints constraints; protected final FeelMode feel; protected final String group; protected final PropertyBinding binding; protected final PropertyCondition condition; + protected final String tooltip; protected final String type; @@ -55,13 +56,14 @@ public Property( String label, String description, Boolean optional, - String value, + Object value, GeneratedValue generatedValue, PropertyConstraints constraints, FeelMode feel, String group, PropertyBinding binding, PropertyCondition condition, + String tooltip, String type) { this.id = id; this.label = label; @@ -74,6 +76,7 @@ public Property( this.group = group; this.binding = binding; this.condition = condition; + this.tooltip = tooltip; this.type = type; } @@ -93,7 +96,7 @@ public Boolean isOptional() { return optional; } - public String getValue() { + public Object getValue() { return value; } @@ -128,6 +131,10 @@ public PropertyCondition getCondition() { return condition; } + public String getTooltip() { + return tooltip; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -163,7 +170,8 @@ public int hashCode() { feel, group, binding, - type); + type, + tooltip); } @Override @@ -198,6 +206,12 @@ public String toString() { + ", type='" + type + '\'' + + ", condition='" + + condition + + '\'' + + ", tooltip='" + + tooltip + + '\'' + '}'; } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyBuilder.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyBuilder.java index 6f74f84bab..6ba617995a 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyBuilder.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyBuilder.java @@ -24,7 +24,7 @@ public abstract class PropertyBuilder { protected String label; protected String description; protected Boolean optional; - protected String value; + protected Object value; protected Property.GeneratedValue generatedValue; protected PropertyConstraints constraints; protected FeelMode feel; @@ -32,6 +32,7 @@ public abstract class PropertyBuilder { protected PropertyBinding binding; protected String type; protected PropertyCondition condition; + protected String tooltip; protected PropertyBuilder() {} @@ -67,7 +68,7 @@ public PropertyBuilder optional(boolean optional) { return this; } - public PropertyBuilder value(String value) { + public PropertyBuilder value(Object value) { if (generatedValue != null) { throw new IllegalStateException("Generated value is already set"); } @@ -108,5 +109,10 @@ public PropertyBuilder group(String group) { return this; } + public PropertyBuilder tooltip(String tooltip) { + this.tooltip = tooltip; + return this; + } + public abstract Property build(); } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyCondition.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyCondition.java index 010ce78d56..251f0a1568 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyCondition.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyCondition.java @@ -28,7 +28,7 @@ public String getType() { } } - record Equals(String property, String equals) implements PropertyCondition { + record Equals(String property, Object equals) implements PropertyCondition { public String getType() { return "simple"; diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyGroup.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyGroup.java index 342ee91fd3..a99d6fd148 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyGroup.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/PropertyGroup.java @@ -17,6 +17,8 @@ package io.camunda.connector.generator.dsl; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import io.camunda.connector.generator.dsl.PropertyBinding.ZeebeProperty; import io.camunda.connector.generator.dsl.PropertyBinding.ZeebeTaskDefinition; import io.camunda.connector.generator.dsl.PropertyBinding.ZeebeTaskHeader; @@ -26,7 +28,13 @@ import java.util.List; import java.util.stream.Stream; -public record PropertyGroup(String id, String label, @JsonIgnore List properties) { +@JsonInclude(Include.NON_NULL) +public record PropertyGroup( + String id, + String label, + @JsonIgnore List properties, + String tooltip, + Boolean openByDefault) { public static PropertyGroup OUTPUT_GROUP_OUTBOUND = PropertyGroup.builder() @@ -112,6 +120,19 @@ public record PropertyGroup(String id, String label, @JsonIgnore List CommonProperties.messageNameUuidHidden().build()) .build(); + public static PropertyGroup DEDUPLICATION_GROUP = + PropertyGroup.builder() + .id("deduplication") + .label("Deduplication") + .tooltip( + "Deduplication allows you to configure multiple inbound connector elements to reuse the same backend (consumer/thread/endpoint) by sharing the same deduplication ID.") + .properties( + CommonProperties.deduplicationModeManualFlag().build(), + CommonProperties.deduplicationModeManual().build(), + CommonProperties.deduplicationModeAuto().build(), + CommonProperties.deduplicationId().build()) + .build(); + public PropertyGroup { if (id == null) { throw new IllegalArgumentException("id is required"); @@ -132,6 +153,8 @@ public static final class PropertyGroupBuilder { private String id; private String label; + private String tooltip; + private Boolean openByDefault; private final List properties = new ArrayList<>(); private PropertyGroupBuilder() {} @@ -146,6 +169,16 @@ public PropertyGroupBuilder label(String label) { return this; } + public PropertyGroupBuilder tooltip(String tooltip) { + this.tooltip = tooltip; + return this; + } + + public PropertyGroupBuilder openByDefault(Boolean openByDefault) { + this.openByDefault = openByDefault; + return this; + } + public PropertyGroupBuilder properties(PropertyBuilder... properties) { requireIdSet(); this.properties.addAll( @@ -180,7 +213,7 @@ public PropertyGroupBuilder properties(Property... properties) { } public PropertyGroup build() { - return new PropertyGroup(id, label, properties); + return new PropertyGroup(id, label, properties, tooltip, openByDefault); } private void requireIdSet() { diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/StringProperty.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/StringProperty.java index ecbaa7f359..31e9c878d2 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/StringProperty.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/StringProperty.java @@ -31,7 +31,8 @@ public StringProperty( FeelMode feel, String group, PropertyBinding binding, - PropertyCondition condition) { + PropertyCondition condition, + String tooltip) { super( name, label, @@ -44,6 +45,7 @@ public StringProperty( group, binding, condition, + tooltip, TYPE); } @@ -59,18 +61,22 @@ public StringProperty build() { if (feel == null) { feel = FeelMode.optional; } + if (value != null && !(value instanceof String)) { + throw new IllegalStateException("Value of a string property must be a string"); + } return new StringProperty( id, label, description, optional, - value, + (String) value, generatedValue, constraints, feel, group, binding, - condition); + condition, + tooltip); } } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/TextProperty.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/TextProperty.java index d7031d5729..c44ec75014 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/TextProperty.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/TextProperty.java @@ -31,7 +31,8 @@ public TextProperty( FeelMode feel, String group, PropertyBinding binding, - PropertyCondition condition) { + PropertyCondition condition, + String tooltip) { super( name, label, @@ -44,6 +45,7 @@ public TextProperty( group, binding, condition, + tooltip, TYPE); } @@ -59,18 +61,22 @@ public TextProperty build() { if (feel == null) { feel = FeelMode.optional; } + if (value != null && !(value instanceof String)) { + throw new IllegalStateException("Value of a text property must be a string"); + } return new TextProperty( id, label, description, optional, - value, + (String) value, generatedValue, constraints, feel, group, binding, - condition); + condition, + tooltip); } } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/ClassBasedTemplateGenerator.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/ClassBasedTemplateGenerator.java index 5e0a809637..c2130e74ad 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/ClassBasedTemplateGenerator.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/ClassBasedTemplateGenerator.java @@ -22,6 +22,7 @@ import io.camunda.connector.generator.api.GeneratorConfiguration; import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorElementType; import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorMode; +import io.camunda.connector.generator.api.GeneratorConfiguration.GenerationFeature; import io.camunda.connector.generator.dsl.BpmnType; import io.camunda.connector.generator.dsl.ElementTemplateBuilder; import io.camunda.connector.generator.dsl.ElementTemplateIcon; @@ -86,6 +87,8 @@ public List generate( PropertyGroup.builder() .id(group.id()) .label(group.label()) + .tooltip(group.tooltip().isBlank() ? null : group.tooltip()) + .openByDefault(group.openByDefault() == Boolean.TRUE ? null : false) .properties(groupDefinedInProperties.get().build().properties()) .build()); } @@ -132,7 +135,8 @@ public List generate( template.documentationRef().isEmpty() ? null : template.documentationRef()) .description(template.description().isEmpty() ? null : template.description()) .properties(nonGroupedProperties.stream().map(PropertyBuilder::build).toList()) - .propertyGroups(addServiceProperties(mergedGroups, context, elementType)) + .propertyGroups( + addServiceProperties(mergedGroups, context, elementType, configuration)) .build(); }) .toList(); @@ -166,7 +170,8 @@ private static String createName( private List addServiceProperties( List groups, TemplateGenerationContext context, - ConnectorElementType elementType) { + ConnectorElementType elementType, + GeneratorConfiguration configuration) { var newGroups = new ArrayList<>(groups); if (context instanceof Outbound) { newGroups.add(PropertyGroup.OUTPUT_GROUP_OUTBOUND); @@ -180,6 +185,9 @@ private List addServiceProperties( || elementType.elementType().equals(BpmnType.BOUNDARY_EVENT)) { newGroups.add(PropertyGroup.CORRELATION_GROUP_INTERMEDIATE_CATCH_EVENT_OR_BOUNDARY); } + if (configuration.features().get(GenerationFeature.INBOUND_DEDUPLICATION) == Boolean.TRUE) { + newGroups.add(PropertyGroup.DEDUPLICATION_GROUP); + } newGroups.add(PropertyGroup.OUTPUT_GROUP_INBOUND); } return newGroups; diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/ElementTemplate.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/ElementTemplate.java index 35876bf9fc..77a32d1874 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/ElementTemplate.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/ElementTemplate.java @@ -96,6 +96,10 @@ String id(); String label() default ""; + + String tooltip() default ""; + + boolean openByDefault() default true; } @interface ConnectorElementType { diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/TemplateProperty.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/TemplateProperty.java index 7c326dc2f5..bd9dd662b9 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/TemplateProperty.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/annotation/TemplateProperty.java @@ -78,6 +78,9 @@ /** Default value for the property */ String defaultValue() default ""; + /** Resulting JSON type for the default value */ + DefaultValueType defaultValueType() default DefaultValueType.String; + /** * Group ID for the property. * @@ -108,6 +111,9 @@ */ PropertyConstraints constraints() default @PropertyConstraints; + /** Tooltip for the property */ + String tooltip() default ""; + enum PropertyType { Boolean, Dropdown, @@ -117,6 +123,11 @@ enum PropertyType { Unknown } + enum DefaultValueType { + String, + Boolean + } + @interface PropertyBinding { String name(); } @@ -124,8 +135,26 @@ enum PropertyType { @interface PropertyCondition { String property(); + /** For string properties */ String equals() default ""; + /** For boolean properties */ + boolean equalsBoolean() default true; + + String[] oneOf() default {}; + + NestedPropertyCondition[] allMatch() default {}; + } + + @interface NestedPropertyCondition { + String property(); + + /** For string properties */ + String equals() default ""; + + /** For boolean properties */ + boolean equalsBoolean() default true; + String[] oneOf() default {}; } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/processor/TemplatePropertyFieldProcessor.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/processor/TemplatePropertyFieldProcessor.java index b50bec4393..7509a27121 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/processor/TemplatePropertyFieldProcessor.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/processor/TemplatePropertyFieldProcessor.java @@ -22,6 +22,7 @@ import io.camunda.connector.generator.dsl.PropertyCondition; import io.camunda.connector.generator.dsl.PropertyConstraints; import io.camunda.connector.generator.java.annotation.TemplateProperty; +import io.camunda.connector.generator.java.annotation.TemplateProperty.NestedPropertyCondition; import io.camunda.connector.generator.java.util.TemplateGenerationContext; import java.lang.reflect.Field; import java.util.Arrays; @@ -53,7 +54,17 @@ public void process( builder.description(annotation.description()); } if (!annotation.defaultValue().isBlank()) { - builder.value(annotation.defaultValue()); + var value = annotation.defaultValue(); + switch (annotation.defaultValueType()) { + case Boolean: + builder.value(Boolean.parseBoolean(value)); + break; + case String: + builder.value(value); + break; + default: + throw new IllegalStateException("Unexpected value: " + annotation.defaultValueType()); + } } if (!annotation.group().isBlank()) { builder.group(annotation.group()); @@ -71,12 +82,14 @@ private Property.FeelMode determineDefaultFeelModeBasedOnContext( private PropertyCondition buildCondition(TemplateProperty propertyAnnotation) { var conditionAnnotation = propertyAnnotation.condition(); - if (conditionAnnotation.property().isBlank()) { + if (conditionAnnotation.property().isBlank() && conditionAnnotation.allMatch().length == 0) { return null; } - if (conditionAnnotation.equals().isBlank() && conditionAnnotation.oneOf().length == 0) { - throw new IllegalStateException("InvalidCondition must have either 'equals' or 'oneOf' set"); - } + validateCondition( + conditionAnnotation.property(), + conditionAnnotation.equals(), + conditionAnnotation.oneOf(), + conditionAnnotation.allMatch()); return transformToCondition(conditionAnnotation); } @@ -86,9 +99,65 @@ public static PropertyCondition transformToCondition( if (!conditionAnnotation.equals().isBlank()) { return new PropertyCondition.Equals( conditionAnnotation.property(), conditionAnnotation.equals()); + } else if (conditionAnnotation.oneOf().length > 0) { + return new PropertyCondition.OneOf( + conditionAnnotation.property(), Arrays.asList(conditionAnnotation.oneOf())); + } else if (conditionAnnotation.allMatch().length > 0) { + return new PropertyCondition.AllMatch( + Arrays.stream(conditionAnnotation.allMatch()) + .map(TemplatePropertyFieldProcessor::transformToNestedCondition) + .toList()); } else { + return new PropertyCondition.Equals( + conditionAnnotation.property(), conditionAnnotation.equalsBoolean()); + } + } + + public static PropertyCondition transformToNestedCondition( + TemplateProperty.NestedPropertyCondition conditionAnnotation) { + validateCondition( + conditionAnnotation.property(), + conditionAnnotation.equals(), + conditionAnnotation.oneOf(), + new NestedPropertyCondition[] {}); + if (!conditionAnnotation.equals().isBlank()) { + return new PropertyCondition.Equals( + conditionAnnotation.property(), conditionAnnotation.equals()); + } else if (conditionAnnotation.oneOf().length > 0) { return new PropertyCondition.OneOf( conditionAnnotation.property(), Arrays.asList(conditionAnnotation.oneOf())); + } else { + return new PropertyCondition.Equals( + conditionAnnotation.property(), conditionAnnotation.equalsBoolean()); + } + } + + private static void validateCondition( + String property, String equals, String[] oneOf, NestedPropertyCondition[] allMatch) { + var equalsSet = !equals.isBlank(); + var oneOfSet = oneOf != null && oneOf.length > 0; + var allMatchSet = allMatch != null && allMatch.length > 0; + // equalsBoolean always has a value, so it's not included in the check + // if everything else is not set, we consider it an equalsBoolean condition + + if (equalsSet && oneOfSet || equalsSet && allMatchSet || oneOfSet && allMatchSet) { + throw new IllegalStateException( + "Condition must have only one of 'equals', 'oneOf', 'isActive', or 'allMatch' set"); + } + if (equalsSet && property.isBlank()) { + throw new IllegalStateException("Condition 'equals' must have 'property' set"); + } + + if (oneOfSet && property.isBlank()) { + throw new IllegalStateException("Condition 'oneOf' must have 'property' set"); + } + + if (allMatchSet && !property.isBlank()) { + throw new IllegalStateException("Condition 'allMatch' must not have 'property' set"); + } + + if (!equalsSet && !oneOfSet && !allMatchSet && property.isBlank()) { + throw new IllegalStateException("Condition 'isActive' must have 'property' set"); } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/ConfigurationUtil.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/ConfigurationUtil.java index 19ea763119..5c42697f44 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/ConfigurationUtil.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/ConfigurationUtil.java @@ -20,6 +20,7 @@ import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorElementType; import io.camunda.connector.generator.java.annotation.ElementTemplate; import java.util.Arrays; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -48,7 +49,8 @@ public static GeneratorConfiguration fromAnnotation( if (override.elementTypes() != null && !override.elementTypes().isEmpty()) { elementTypes = override.elementTypes(); } + var features = Optional.ofNullable(override.features()).orElseGet(Map::of); return new GeneratorConfiguration( - connectorMode, templateId, templateName, templateVersion, elementTypes); + connectorMode, templateId, templateName, templateVersion, elementTypes, features); } } diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/TemplatePropertiesUtil.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/TemplatePropertiesUtil.java index 57051c9aeb..2bbbdeb7dd 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/TemplatePropertiesUtil.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/java/util/TemplatePropertiesUtil.java @@ -172,7 +172,7 @@ public static List groupProperties(List p private static PropertyBuilder buildProperty(Field field, TemplateGenerationContext context) { var annotation = field.getAnnotation(TemplateProperty.class); - String name, label; + String name, label, tooltip = null; String bindingName = field.getName(); if (annotation != null) { if (annotation.ignore()) { @@ -191,6 +191,9 @@ private static PropertyBuilder buildProperty(Field field, TemplateGenerationCont if (!annotation.binding().name().isBlank()) { bindingName = annotation.binding().name(); } + if (!annotation.tooltip().isBlank()) { + tooltip = annotation.tooltip(); + } } else { name = field.getName(); label = transformIdIntoLabel(name); @@ -200,6 +203,7 @@ private static PropertyBuilder buildProperty(Field field, TemplateGenerationCont createPropertyBuilder(field, annotation) .id(name) .label(label) + .tooltip(tooltip) .binding(createBinding(bindingName, context)); for (FieldProcessor processor : fieldProcessors) { diff --git a/element-template-generator/core/src/test/java/io/camunda/connector/generator/dsl/ElementTemplateSerializationTest.java b/element-template-generator/core/src/test/java/io/camunda/connector/generator/dsl/ElementTemplateSerializationTest.java index 41a3919079..fe3be8b8dd 100644 --- a/element-template-generator/core/src/test/java/io/camunda/connector/generator/dsl/ElementTemplateSerializationTest.java +++ b/element-template-generator/core/src/test/java/io/camunda/connector/generator/dsl/ElementTemplateSerializationTest.java @@ -101,6 +101,7 @@ void serializationTest() throws Exception { PropertyGroup.builder() .id("output") .label("Output Mapping") + .tooltip("Map the response to process variables") .properties( StringProperty.builder() .label("Result Variable") diff --git a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/InboundClassBasedTemplateGeneratorTest.java b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/InboundClassBasedTemplateGeneratorTest.java index 5cb7e96e39..c6c127d434 100644 --- a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/InboundClassBasedTemplateGeneratorTest.java +++ b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/InboundClassBasedTemplateGeneratorTest.java @@ -23,9 +23,11 @@ import io.camunda.connector.generator.api.GeneratorConfiguration; import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorElementType; import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorMode; +import io.camunda.connector.generator.api.GeneratorConfiguration.GenerationFeature; import io.camunda.connector.generator.dsl.BpmnType; import io.camunda.connector.generator.dsl.DropdownProperty; import io.camunda.connector.generator.dsl.DropdownProperty.DropdownChoice; +import io.camunda.connector.generator.dsl.ElementTemplate; import io.camunda.connector.generator.dsl.Property.FeelMode; import io.camunda.connector.generator.dsl.PropertyBinding; import io.camunda.connector.generator.dsl.PropertyBinding.MessageProperty; @@ -35,6 +37,7 @@ import io.camunda.connector.generator.dsl.StringProperty; import io.camunda.connector.generator.java.example.inbound.MyConnectorExecutable; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -138,7 +141,9 @@ void messageTypes_haveMessageIdProperty() { var type = new ConnectorElementType( Set.of(BpmnType.START_EVENT), BpmnType.MESSAGE_START_EVENT, null, null); - var config = new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Set.of(type)); + var config = + new GeneratorConfiguration( + ConnectorMode.NORMAL, null, null, null, Set.of(type), Map.of()); // when var templates = generator.generate(MyConnectorExecutable.class, config); @@ -159,7 +164,9 @@ void nonMessageTypes_dontHaveMessageIdProperty() { // given var type = new ConnectorElementType(Set.of(BpmnType.START_EVENT), BpmnType.START_EVENT, null, null); - var config = new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Set.of(type)); + var config = + new GeneratorConfiguration( + ConnectorMode.NORMAL, null, null, null, Set.of(type), Map.of()); // when var templates = generator.generate(MyConnectorExecutable.class, config); @@ -183,7 +190,8 @@ void intermediateMessageEvents_haveCorrelationProperties() { new ConnectorElementType( Set.of(BpmnType.BOUNDARY_EVENT), BpmnType.BOUNDARY_EVENT, null, null); var config = - new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Set.of(type1, type2)); + new GeneratorConfiguration( + ConnectorMode.NORMAL, null, null, null, Set.of(type1, type2), Map.of()); // when var templates = generator.generate(MyConnectorExecutable.class, config); @@ -216,7 +224,9 @@ void messageStartEvent_hasCorrelationProperties() { var type = new ConnectorElementType( Set.of(BpmnType.START_EVENT), BpmnType.MESSAGE_START_EVENT, null, null); - var config = new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Set.of(type)); + var config = + new GeneratorConfiguration( + ConnectorMode.NORMAL, null, null, null, Set.of(type), Map.of()); // when var templates = generator.generate(MyConnectorExecutable.class, config); @@ -267,7 +277,8 @@ void stringProperty_hasCorrectDefaults() { var type = new ConnectorElementType( Set.of(BpmnType.START_EVENT), BpmnType.MESSAGE_START_EVENT, null, null); - var config = new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Set.of(type)); + var config = + new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, Set.of(type), Map.of()); // when var template = generator.generate(MyConnectorExecutable.class, config).getFirst(); @@ -281,4 +292,108 @@ void stringProperty_hasCorrectDefaults() { assertThat(property.getBinding()).isEqualTo(new PropertyBinding.ZeebeProperty("prop1")); assertThat(property.getConstraints()).isNull(); } + + @Nested + class Deduplication { + + @Test + void deduplicationFeatureFlagNotSet_shouldNotAddDeduplicationProperties() { + // given + var type = + new ConnectorElementType( + Set.of(BpmnType.START_EVENT), BpmnType.MESSAGE_START_EVENT, null, null); + var config = + new GeneratorConfiguration( + ConnectorMode.NORMAL, null, null, null, Set.of(type), Map.of()); + + // when + var template = generator.generate(MyConnectorExecutable.class, config).getFirst(); + + // then + assertThrows(Exception.class, () -> assertDeduplicationProperties(template)); + } + + @Test + void deduplicationFeatureFlagTrue_shouldAddDeduplicationProperties() { + // given + var type = + new ConnectorElementType( + Set.of(BpmnType.START_EVENT), BpmnType.MESSAGE_START_EVENT, null, null); + var config = + new GeneratorConfiguration( + ConnectorMode.NORMAL, + null, + null, + null, + Set.of(type), + Map.of(GenerationFeature.INBOUND_DEDUPLICATION, true)); + + // when + var template = generator.generate(MyConnectorExecutable.class, config).getFirst(); + + // then + assertDeduplicationProperties(template); + } + + @Test + void deduplicationFeatureFlagFalse_shouldNotAddDeduplicationProperties() { + // given + var type = + new ConnectorElementType( + Set.of(BpmnType.START_EVENT), BpmnType.MESSAGE_START_EVENT, null, null); + var config = + new GeneratorConfiguration( + ConnectorMode.NORMAL, + null, + null, + null, + Set.of(type), + Map.of(GenerationFeature.INBOUND_DEDUPLICATION, false)); + + // when + var template = generator.generate(MyConnectorExecutable.class, config).getFirst(); + + // then + assertThrows(Exception.class, () -> assertDeduplicationProperties(template)); + } + + private void assertDeduplicationProperties(ElementTemplate template) { + var manualModeFlagProperty = getPropertyById("deduplicationModeManualFlag", template); + assertThat(manualModeFlagProperty).isNotNull(); + assertThat(manualModeFlagProperty.getType()).isEqualTo("Boolean"); + assertThat(manualModeFlagProperty.getBinding().type()).isEqualTo("zeebe:property"); + assertThat(((ZeebeProperty) manualModeFlagProperty.getBinding()).name()) + .isEqualTo("deduplicationModeManualFlag"); + assertThat(manualModeFlagProperty.getValue()).isEqualTo(Boolean.FALSE); + + var manualModeProperty = getPropertyById("deduplicationModeManual", template); + assertThat(manualModeProperty).isNotNull(); + assertThat(manualModeProperty.getType()).isEqualTo("Hidden"); + assertThat(manualModeProperty.getBinding().type()).isEqualTo("zeebe:property"); + assertThat(((ZeebeProperty) manualModeProperty.getBinding()).name()) + .isEqualTo("deduplicationMode"); + assertThat(manualModeProperty.getValue()).isEqualTo("MANUAL"); + assertThat(manualModeProperty.getCondition()) + .isEqualTo(new Equals("deduplicationModeManualFlag", true)); + + var autoModeProperty = getPropertyById("deduplicationModeAuto", template); + assertThat(autoModeProperty).isNotNull(); + assertThat(autoModeProperty.getType()).isEqualTo("Hidden"); + assertThat(autoModeProperty.getBinding().type()).isEqualTo("zeebe:property"); + assertThat(((ZeebeProperty) autoModeProperty.getBinding()).name()) + .isEqualTo("deduplicationMode"); + assertThat(autoModeProperty.getValue()).isEqualTo("AUTO"); + assertThat(autoModeProperty.getCondition()) + .isEqualTo(new Equals("deduplicationModeManualFlag", false)); + + var deduplicationKeyProperty = getPropertyById("deduplicationId", template); + assertThat(deduplicationKeyProperty).isNotNull(); + assertThat(deduplicationKeyProperty.getType()).isEqualTo("String"); + assertThat(deduplicationKeyProperty.getBinding().type()).isEqualTo("zeebe:property"); + assertThat(((ZeebeProperty) deduplicationKeyProperty.getBinding()).name()) + .isEqualTo("deduplicationId"); + assertThat(deduplicationKeyProperty.getCondition()) + .isEqualTo(new Equals("deduplicationModeManualFlag", true)); + } + } } diff --git a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/OutboundClassBasedTemplateGeneratorTest.java b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/OutboundClassBasedTemplateGeneratorTest.java index ffe28cc232..dec0aa9fae 100644 --- a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/OutboundClassBasedTemplateGeneratorTest.java +++ b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/OutboundClassBasedTemplateGeneratorTest.java @@ -191,7 +191,8 @@ void hybridMode_taskDefinitionTypePropertyPresent() { generator .generate( MyConnectorFunction.MinimallyAnnotated.class, - new GeneratorConfiguration(ConnectorMode.HYBRID, null, null, null, null)) + new GeneratorConfiguration( + ConnectorMode.HYBRID, null, null, null, null, Map.of())) .getFirst(); var property = getPropertyById("taskDefinitionType", template); assertThat(property.getType()).isEqualTo("String"); @@ -216,7 +217,8 @@ void singleElementType_hasCorrectNameAndId() { @Test void multipleElementTypes_definedInAnnotation() { - var config = new GeneratorConfiguration(ConnectorMode.HYBRID, null, null, null, null); + var config = + new GeneratorConfiguration(ConnectorMode.HYBRID, null, null, null, null, Map.of()); var templates = generator.generate(MyConnectorFunction.WithMultipleElementTypes.class, config); boolean hasServiceTask = false, @@ -287,7 +289,8 @@ void multipleElementTypes_definedInConfig() { Set.of(BpmnType.INTERMEDIATE_THROW_EVENT), BpmnType.INTERMEDIATE_THROW_EVENT, null, - null))); + null)), + Map.of()); var templates = generator.generate(MyConnectorFunction.FullyAnnotated.class, config); boolean hasServiceTask = false, hasMessageThrowEvent = false; for (var template : templates) { @@ -314,7 +317,8 @@ void multipleElementTypes_overriddenInConfig() { null, Set.of( new ConnectorElementType( - Set.of(BpmnType.TASK), BpmnType.SERVICE_TASK, null, null))); + Set.of(BpmnType.TASK), BpmnType.SERVICE_TASK, null, null)), + Map.of()); var templates = generator.generate(MyConnectorFunction.WithMultipleElementTypes.class, config); boolean hasServiceTask = false, @@ -358,7 +362,8 @@ void invalidElementType_throwsException() { Set.of(BpmnType.INTERMEDIATE_CATCH_EVENT), BpmnType.INTERMEDIATE_CATCH_EVENT, null, - null))); + null)), + Map.of()); var exception = assertThrows( IllegalArgumentException.class, @@ -528,6 +533,33 @@ void dateProperty_defaultsToStringType() { var property = getPropertyByLabel("Date property", template); assertThat(property.getType()).isEqualTo("String"); } + + @Test + void annotatedProperty_tooltipPresent() { + var template = generator.generate(MyConnectorFunction.MinimallyAnnotated.class).getFirst(); + var property = getPropertyById("annotatedStringProperty", template); + assertThat(property.getTooltip()).isEqualTo("tooltip"); + } + + @Test + void booleanProperty() { + var template = generator.generate(MyConnectorFunction.MinimallyAnnotated.class).getFirst(); + var property = getPropertyById("booleanProperty", template); + assertThat(property.getType()).isEqualTo("Boolean"); + assertThat(property.getBinding()).isEqualTo(new ZeebeInput("booleanProperty")); + assertThat(property.getValue()).isEqualTo(Boolean.FALSE); + } + + @Test + void booleanProperty_dependants() { + var template = generator.generate(MyConnectorFunction.MinimallyAnnotated.class).getFirst(); + var dependsOnTrue = getPropertyById("dependsOnBooleanPropertyTrue", template); + assertThat(dependsOnTrue.getCondition()) + .isEqualTo(new Equals("booleanProperty", Boolean.TRUE)); + var dependsOnFalse = getPropertyById("dependsOnBooleanPropertyFalse", template); + assertThat(dependsOnFalse.getCondition()) + .isEqualTo(new Equals("booleanProperty", Boolean.FALSE)); + } } @Nested @@ -722,7 +754,8 @@ void hybridMode_groupPresentAndIsOnTop() { generator .generate( MyConnectorFunction.MinimallyAnnotated.class, - new GeneratorConfiguration(ConnectorMode.HYBRID, null, null, null, null)) + new GeneratorConfiguration( + ConnectorMode.HYBRID, null, null, null, null, Map.of())) .getFirst(); checkPropertyGroups( List.of( @@ -736,6 +769,30 @@ void hybridMode_groupPresentAndIsOnTop() { template, true); } + + @Test + void tooltip_definedByPropertyGroupAnnotation() { + var template = generator.generate(MyConnectorFunction.FullyAnnotated.class).getFirst(); + var group1 = + template.groups().stream().filter(g -> "group1".equals(g.id())).findFirst().orElseThrow(); + assertThat(group1.tooltip()).isEqualTo("Group One Tooltip"); + + var group2 = + template.groups().stream().filter(g -> "group2".equals(g.id())).findFirst().orElseThrow(); + assertThat(group2.tooltip()).isNull(); + } + + @Test + void openByDefault_definedByPropertyGroupAnnotation() { + var template = generator.generate(MyConnectorFunction.FullyAnnotated.class).getFirst(); + var group1 = + template.groups().stream().filter(g -> "group1".equals(g.id())).findFirst().orElseThrow(); + assertThat(group1.openByDefault()).isFalse(); + + var group2 = + template.groups().stream().filter(g -> "group2".equals(g.id())).findFirst().orElseThrow(); + assertThat(group2.openByDefault()).isNull(); + } } @Nested diff --git a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorFunction.java b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorFunction.java index 67721bd7d7..b6cca89e04 100644 --- a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorFunction.java +++ b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorFunction.java @@ -54,7 +54,11 @@ public Object execute(OutboundConnectorContext context) { inputDataClass = MyConnectorInput.class, propertyGroups = { @PropertyGroup(id = "group2", label = "Group Two"), - @PropertyGroup(id = "group1", label = "Group One") + @PropertyGroup( + id = "group1", + label = "Group One", + openByDefault = false, + tooltip = "Group One Tooltip") }) public static class FullyAnnotated extends MyConnectorFunction {} diff --git a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorInput.java b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorInput.java index 54161f00e4..a9f68266bb 100644 --- a/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorInput.java +++ b/element-template-generator/core/src/test/java/io/camunda/connector/generator/java/example/outbound/MyConnectorInput.java @@ -20,6 +20,7 @@ import io.camunda.connector.generator.java.annotation.NestedProperties; import io.camunda.connector.generator.java.annotation.TemplateDiscriminatorProperty; import io.camunda.connector.generator.java.annotation.TemplateProperty; +import io.camunda.connector.generator.java.annotation.TemplateProperty.DefaultValueType; import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyCondition; import io.camunda.connector.generator.java.annotation.TemplateProperty.PropertyType; import io.camunda.connector.generator.java.annotation.TemplateSubType; @@ -47,7 +48,8 @@ public record MyConnectorInput( type = PropertyType.Text, group = "group1", description = "description", - constraints = @TemplateProperty.PropertyConstraints(notEmpty = true)) + constraints = @TemplateProperty.PropertyConstraints(notEmpty = true), + tooltip = "tooltip") String annotatedStringProperty, String notAnnotatedStringProperty, Object objectProperty, @@ -84,7 +86,20 @@ public record MyConnectorInput( @Size(min = Integer.MIN_VALUE, max = 10) String propertyWithMaxSize, @NotEmpty String stringPropertyWithNotEmpty, @NotBlank String stringPropertyWithNotBlank, - @NotNull Object objectPropertyWithNotNull) { + @NotNull Object objectPropertyWithNotNull, + @TemplateProperty( + id = "booleanProperty", + defaultValue = "false", + defaultValueType = DefaultValueType.Boolean) + Boolean booleanProperty, + @TemplateProperty( + id = "dependsOnBooleanPropertyFalse", + condition = @PropertyCondition(property = "booleanProperty", equalsBoolean = false)) + String dependsOnBooleanPropertyFalse, + @TemplateProperty( + id = "dependsOnBooleanPropertyTrue", + condition = @PropertyCondition(property = "booleanProperty")) + String dependsOnBooleanPropertyTrue) { sealed interface NonAnnotatedSealedType permits FirstSubType, NestedSealedType, SecondSubType { diff --git a/element-template-generator/core/src/test/resources/test-element-template.json b/element-template-generator/core/src/test/resources/test-element-template.json index c32b849e62..201737b452 100644 --- a/element-template-generator/core/src/test/resources/test-element-template.json +++ b/element-template-generator/core/src/test/resources/test-element-template.json @@ -26,7 +26,8 @@ }, { "id": "output", - "label": "Output Mapping" + "label": "Output Mapping", + "tooltip": "Map the response to process variables" }, { "id": "errors", @@ -37,8 +38,8 @@ { "value": "io.camunda:template:1", "binding": { - "type": "zeebe:taskDefinition", - "property": "type" + "property": "type", + "type": "zeebe:taskDefinition" }, "type": "Hidden" }, diff --git a/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ConnectorConfig.java b/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ConnectorConfig.java index 11d384ed01..fd7ce59f87 100644 --- a/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ConnectorConfig.java +++ b/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ConnectorConfig.java @@ -17,6 +17,7 @@ package io.camunda.connector.generator; import java.util.List; +import java.util.Map; public class ConnectorConfig { @@ -26,6 +27,8 @@ public class ConnectorConfig { private List files = List.of(); + private Map features = Map.of(); + public static class FileNameById { private String templateId; private String templateFileName; @@ -74,4 +77,12 @@ public void setFiles(List files) { public void setGenerateHybridTemplates(boolean generateHybridTemplates) { this.generateHybridTemplates = generateHybridTemplates; } + + public Map getFeatures() { + return features; + } + + public void setFeatures(Map features) { + this.features = features; + } } diff --git a/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ElementTemplateGeneratorMojo.java b/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ElementTemplateGeneratorMojo.java index b13ff1ef12..eeea70ba98 100644 --- a/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ElementTemplateGeneratorMojo.java +++ b/element-template-generator/maven-plugin/src/main/java/io/camunda/connector/generator/ElementTemplateGeneratorMojo.java @@ -23,6 +23,7 @@ import io.camunda.connector.generator.ConnectorConfig.FileNameById; import io.camunda.connector.generator.api.GeneratorConfiguration; import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorMode; +import io.camunda.connector.generator.api.GeneratorConfiguration.GenerationFeature; import io.camunda.connector.generator.dsl.ElementTemplate; import io.camunda.connector.generator.java.ClassBasedTemplateGenerator; import java.io.File; @@ -31,7 +32,10 @@ import java.net.URLClassLoader; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; @@ -136,14 +140,32 @@ public void execute() throws MojoFailureException { private void generateElementTemplates(ConnectorConfig config, ClassLoader classLoader) throws ClassNotFoundException { var clazz = classLoader.loadClass(config.getConnectorClass()); - var generatorConfig = new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, null); + var features = + config.getFeatures().entrySet().stream() + .map( + e -> { + try { + var feature = GenerationFeature.valueOf(e.getKey()); + return Map.entry(feature, e.getValue()); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException( + "Unknown feature: " + + e.getKey() + + ". Known features are: " + + Arrays.toString(GenerationFeature.values())); + } + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + var generatorConfig = + new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, null, null, features); var generator = new ClassBasedTemplateGenerator(classLoader); var templates = generator.generate(clazz, generatorConfig); writeElementTemplates(templates, false, config.getFiles()); if (config.isGenerateHybridTemplates()) { var hybridGeneratorConfig = - new GeneratorConfiguration(ConnectorMode.HYBRID, null, null, null, null); + new GeneratorConfiguration(ConnectorMode.HYBRID, null, null, null, null, features); var hybridTemplates = generator.generate(clazz, hybridGeneratorConfig); writeElementTemplates(hybridTemplates, true, config.getFiles()); } diff --git a/element-template-generator/openapi-parser/src/main/java/io/camunda/connector/generator/openapi/OpenApiOutboundTemplateGenerator.java b/element-template-generator/openapi-parser/src/main/java/io/camunda/connector/generator/openapi/OpenApiOutboundTemplateGenerator.java index cb436d4992..eee2828566 100644 --- a/element-template-generator/openapi-parser/src/main/java/io/camunda/connector/generator/openapi/OpenApiOutboundTemplateGenerator.java +++ b/element-template-generator/openapi-parser/src/main/java/io/camunda/connector/generator/openapi/OpenApiOutboundTemplateGenerator.java @@ -91,11 +91,12 @@ public ScanResult scan(OpenApiGenerationSource input) { template.id(), template.name(), template.version(), - template.properties().stream() - .filter(p -> p.getBinding().equals(ZeebeTaskDefinition.TYPE)) - .findFirst() - .orElseThrow() - .getValue(), + (String) + template.properties().stream() + .filter(p -> p.getBinding().equals(ZeebeTaskDefinition.TYPE)) + .findFirst() + .orElseThrow() + .getValue(), operations); } diff --git a/element-template-generator/postman-collections-parser/src/main/java/io/camunda/connector/generator/postman/PostmanCollectionOutboundTemplateGenerator.java b/element-template-generator/postman-collections-parser/src/main/java/io/camunda/connector/generator/postman/PostmanCollectionOutboundTemplateGenerator.java index 90102a4a63..7487cb0311 100644 --- a/element-template-generator/postman-collections-parser/src/main/java/io/camunda/connector/generator/postman/PostmanCollectionOutboundTemplateGenerator.java +++ b/element-template-generator/postman-collections-parser/src/main/java/io/camunda/connector/generator/postman/PostmanCollectionOutboundTemplateGenerator.java @@ -82,11 +82,12 @@ public ScanResult scan(PostmanCollectionsGenerationSource input) { firstTemplate.id(), firstTemplate.name(), firstTemplate.version(), - firstTemplate.properties().stream() - .filter(p -> p.getBinding().equals(ZeebeTaskDefinition.TYPE)) - .findFirst() - .orElseThrow() - .getValue(), + (String) + firstTemplate.properties().stream() + .filter(p -> p.getBinding().equals(ZeebeTaskDefinition.TYPE)) + .findFirst() + .orElseThrow() + .getValue(), supportedOperations); } diff --git a/element-template-generator/postman-collections-parser/src/test/java/io/camunda/connector/generator/postman/PostmanCollectionsGeneratorDryRunExampleTest.java b/element-template-generator/postman-collections-parser/src/test/java/io/camunda/connector/generator/postman/PostmanCollectionsGeneratorDryRunExampleTest.java index 25dbfe290f..704a38742d 100644 --- a/element-template-generator/postman-collections-parser/src/test/java/io/camunda/connector/generator/postman/PostmanCollectionsGeneratorDryRunExampleTest.java +++ b/element-template-generator/postman-collections-parser/src/test/java/io/camunda/connector/generator/postman/PostmanCollectionsGeneratorDryRunExampleTest.java @@ -21,6 +21,7 @@ import io.camunda.connector.generator.api.GeneratorConfiguration.ConnectorMode; import io.camunda.connector.generator.postman.utils.ObjectMapperProvider; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; @@ -35,7 +36,9 @@ void generate(List args) throws JsonProcessingException { var source = new PostmanCollectionsGenerationSource(args); var gen = new PostmanCollectionOutboundTemplateGenerator(); var templates = - gen.generate(source, new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, 1, null)); + gen.generate( + source, + new GeneratorConfiguration(ConnectorMode.NORMAL, null, null, 1, null, Map.of())); var resultString = ObjectMapperProvider.getInstance() .writerWithDefaultPrettyPrinter()