diff --git a/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessor.java b/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessor.java
index d0efb442..5256bad0 100644
--- a/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessor.java
+++ b/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessor.java
@@ -226,6 +226,8 @@ public String getDataFormatCatalog() throws Exception {
var json = JsonMapper.asJsonObject(dataFormatCatalog).toJson();
var catalogTree = (ObjectNode) jsonMapper.readTree(json);
catalogTree.set("propertiesSchema", dataFormatSchema);
+ // setting required property to all the dataformats schema
+ setRequiredToPropertiesSchema(dataFormatSchema, catalogTree);
answer.set(dataFormatName, catalogTree);
}
StringWriter writer = new StringWriter();
@@ -267,6 +269,8 @@ public String getLanguageCatalog() throws Exception {
var json = JsonMapper.asJsonObject(languageCatalog).toJson();
var catalogTree = (ObjectNode) jsonMapper.readTree(json);
catalogTree.set("propertiesSchema", languageSchema);
+ // setting required property to all the languages schema
+ setRequiredToPropertiesSchema(languageSchema, catalogTree);
answer.set(languageName, catalogTree);
}
StringWriter writer = new StringWriter();
@@ -709,6 +713,8 @@ public String getLoadBalancerCatalog() throws Exception {
var json = JsonMapper.asJsonObject(loadBalancerCatalog).toJson();
var catalogTree = (ObjectNode) jsonMapper.readTree(json);
catalogTree.set("propertiesSchema", loadBalancerSchema);
+ // setting required property to all the load-balancers schema
+ setRequiredToPropertiesSchema(loadBalancerSchema, catalogTree);
answer.set(loadBalancerName, catalogTree);
}
StringWriter writer = new StringWriter();
@@ -716,4 +722,17 @@ public String getLoadBalancerCatalog() throws Exception {
jsonMapper.writeTree(jsonGenerator, answer);
return writer.toString();
}
+
+ private void setRequiredToPropertiesSchema(ObjectNode camelYamlDslSchema, ObjectNode catalogModel) {
+ List required = new ArrayList<>();
+ var camelYamlDslProperties = camelYamlDslSchema.withObject("/properties").properties().stream()
+ .map(Map.Entry::getKey).toList();
+ for (var propertyName : camelYamlDslProperties) {
+ var catalogPropertySchema = catalogModel.withObject("/properties").withObject("/" + propertyName);
+ if (catalogPropertySchema.has("required") && catalogPropertySchema.get("required").asBoolean()) {
+ required.add(propertyName);
+ }
+ }
+ catalogModel.withObject("/propertiesSchema").set("required", jsonMapper.valueToTree(required));
+ }
}
diff --git a/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessor.java b/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessor.java
index a97ffba8..53f961b9 100644
--- a/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessor.java
+++ b/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessor.java
@@ -15,42 +15,34 @@
*/
package io.kaoto.camelcatalog.generator;
-import java.io.StringWriter;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.StringWriter;
+import java.util.*;
+
/**
* Process camelYamlDsl.json file, aka Camel YAML DSL JSON schema.
*/
public class CamelYamlDslSchemaProcessor {
private static final String PROCESSOR_DEFINITION = "org.apache.camel.model.ProcessorDefinition";
+ private static final String TOKENIZER_DEFINITION = "org.apache.camel.model.TokenizerDefinition";
private static final String ROUTE_CONFIGURATION_DEFINITION = "org.apache.camel.model.RouteConfigurationDefinition";
private static final String LOAD_BALANCE_DEFINITION = "org.apache.camel.model.LoadBalanceDefinition";
- private static final String EXPRESSION_SUB_ELEMENT_DEFINITION = "org.apache.camel.model.ExpressionSubElementDefinition";
+ private static final String EXPRESSION_SUB_ELEMENT_DEFINITION =
+ "org.apache.camel.model.ExpressionSubElementDefinition";
private static final String SAGA_DEFINITION = "org.apache.camel.model.SagaDefinition";
private static final String PROPERTY_EXPRESSION_DEFINITION = "org.apache.camel.model.PropertyExpressionDefinition";
private static final String ERROR_HANDLER_DEFINITION = "org.apache.camel.model.ErrorHandlerDefinition";
- private static final String ERROR_HANDLER_DESERIALIZER = "org.apache.camel.dsl.yaml.deserializers.ErrorHandlerBuilderDeserializer";
+ private static final String ERROR_HANDLER_DESERIALIZER =
+ "org.apache.camel.dsl.yaml.deserializers.ErrorHandlerBuilderDeserializer";
private final ObjectMapper jsonMapper;
private final ObjectNode yamlDslSchema;
- private final List processorBlocklist = List.of(
- "org.apache.camel.model.KameletDefinition"
- // reactivate entries once we have a better handling of how to add WHEN and
- // OTHERWISE without Catalog
- // "Otherwise",
- // "when",
- // "doCatch",
- // ""doFinally"
- );
+ private final List processorBlocklist = List.of("org.apache.camel.model.KameletDefinition");
/**
* The processor properties those should be handled separately, i.e. remove from
@@ -67,8 +59,7 @@ public class CamelYamlDslSchemaProcessor {
List.of("uri", "parameters"),
"org.apache.camel.model.WireTapDefinition",
List.of("uri", "parameters"));
- private final List processorReferenceBlockList = List.of(
- PROCESSOR_DEFINITION);
+ private final List processorReferenceBlockList = List.of(PROCESSOR_DEFINITION);
public CamelYamlDslSchemaProcessor(ObjectMapper mapper, ObjectNode yamlDslSchema) throws Exception {
this.jsonMapper = mapper;
@@ -107,11 +98,14 @@ private String doProcessSubSchema(
var answer = (ObjectNode) prop.getValue().deepCopy();
if (answer.has("$ref") && definitions.has(getNameFromRef(answer))) {
answer = definitions.withObject("/" + getNameFromRef(answer)).deepCopy();
-
}
+
extractSingleOneOfFromAnyOf(answer);
+ removeEmptyProperties(answer);
+ removeNotFromOneOf(answer);
answer.set("$schema", rootSchema.get("$schema"));
populateDefinitions(answer, definitions);
+
var writer = new StringWriter();
try {
JsonGenerator gen = new JsonFactory().createGenerator(writer).useDefaultPrettyPrinter();
@@ -122,6 +116,58 @@ private String doProcessSubSchema(
}
}
+ /**
+ * Remove the empty properties from the definition, as it's not supported by the
+ * form library. This happens when the properties are supposed to be handled
+ * separately, in a oneOf definition, for example.
+ * An example:
+ * ```
+ * {
+ * properties: {
+ * deadLetterChannel: { },
+ * defaultErrorHandler: { }
+ * }
+ * }
+ *
+ * @param definitionWithEmptyProperties the definition containing the empty properties to be removed
+ */
+ private void removeEmptyProperties(ObjectNode definitionWithEmptyProperties) {
+ if (!definitionWithEmptyProperties.has("properties")) {
+ return;
+ }
+
+ var propertiesObject = definitionWithEmptyProperties.withObject("/properties");
+ List propToRemove = new ArrayList<>();
+ propertiesObject.fields().forEachRemaining(field -> {
+ if (field.getValue().isEmpty()) {
+ propToRemove.add(field.getKey());
+ }
+ });
+ propToRemove.forEach(propertiesObject::remove);
+ }
+
+ /**
+ * Remove "not" from the OneOf definition, as it's not supported by the
+ * form library. The "not" is only useful for the Source code editor.
+ *
+ * @param definitionContainingOneOf the definition to be cleaned
+ */
+ private void removeNotFromOneOf(ObjectNode definitionContainingOneOf) {
+ if (!definitionContainingOneOf.has("oneOf")) {
+ return;
+ }
+
+ ArrayNode cleanAnyOf = jsonMapper.createArrayNode();
+ var oneOfDefinitionArray = definitionContainingOneOf.withArray("/oneOf");
+ for (var def : oneOfDefinitionArray) {
+ if (def.has("not")) {
+ continue;
+ }
+ cleanAnyOf.add(def);
+ }
+ definitionContainingOneOf.set("oneOf", cleanAnyOf);
+ }
+
private String getNameFromRef(ObjectNode parent) {
var ref = parent.get("$ref").asText();
return ref.contains("items") ? ref.replace("#/items/definitions/", "")
@@ -152,9 +198,7 @@ private void populateDefinitions(ObjectNode schema, ObjectNode definitions) {
* root definitions.
* It's a workaround for the current Camel YAML DSL JSON schema, where some
* AnyOf definition
- * contains only one OneOf definition. This can be removed once
- * https://github.com/KaotoIO/kaoto/issues/948
- * is resolved.
+ * contains only one OneOf definition.
* This is done mostly for the errorHandler definition, f.i.
* ```
* {
@@ -214,7 +258,7 @@ private void extractSingleOneOfFromAnyOf(ObjectNode definition) {
* "$comment" in the property schema
*
*
- * @return
+ * @return A map of processor definitions
*/
public Map getProcessors() throws Exception {
var definitions = yamlDslSchema
@@ -235,8 +279,16 @@ public Map getProcessors() throws Exception {
var processor = relocatedDefinitions.withObject("/" + processorFQCN);
processor = extractFromOneOf(processorFQCN, processor);
processor.remove("oneOf");
- processor = extractFromAnyOfOneOf(processorFQCN, processor);
- processor.remove("anyOf");
+
+ /* Preparation for TokenizerDefinition, this could be propagated to all EIPs in the future */
+ if (processorFQCN.equals(TOKENIZER_DEFINITION)) {
+ removeEmptyProperties(processor);
+ extractSingleOneOfFromAnyOf(processor);
+ removeNotFromOneOf(processor);
+ }
+
+ processAndRemoveAnyOfForSubCatalogs(processorFQCN, processor);
+
var processorProperties = processor.withObject("/properties");
Set propToRemove = new HashSet<>();
var propertyBlockList = processorPropertyBlockList.get(processorFQCN);
@@ -316,10 +368,12 @@ private ObjectNode extractFromOneOf(String name, ObjectNode definition) throws E
for (var def : oneOf) {
if (def.get("type").asText().equals("object")) {
var objectDef = (ObjectNode) def;
- if (definition.has("title"))
+ if (definition.has("title")) {
objectDef.set("title", definition.get("title"));
- if (definition.has("description"))
+ }
+ if (definition.has("description")) {
objectDef.set("description", definition.get("description"));
+ }
return objectDef;
}
}
@@ -328,9 +382,19 @@ private ObjectNode extractFromOneOf(String name, ObjectNode definition) throws E
name));
}
- private ObjectNode extractFromAnyOfOneOf(String name, ObjectNode definition) throws Exception {
+ /**
+ * Process the "anyOf" definition for the sub-catalogs, such as expressions, languages,
+ * data formats, and errorHandler.
+ * It puts a "$comment" in the schema to indicate the type of the sub-catalog and then removes
+ * the "anyOf" definition.
+ *
+ * @param name the FQCN of the definition, for instance "org.apache.camel.model.LoadBalanceDefinition"
+ * @param definition the definition that potentially could have "anyOf" definition, referencing to another
+ * sub-catalogs
+ */
+ private void processAndRemoveAnyOfForSubCatalogs(String name, ObjectNode definition) {
if (!definition.has("anyOf")) {
- return definition;
+ return;
}
var anyOfOneOf = definition.withArray("/anyOf").get(0).withArray("/oneOf");
for (var def : anyOfOneOf) {
@@ -355,7 +419,6 @@ private ObjectNode extractFromAnyOfOneOf(String name, ObjectNode definition) thr
}
}
definition.remove("anyOf");
- return definition;
}
private void sanitizeDefinitions(String processorFQCN, ObjectNode processor) throws Exception {
@@ -373,7 +436,7 @@ private void sanitizeDefinitions(String processorFQCN, ObjectNode processor) thr
var definition = (ObjectNode) entry.getValue();
definition = extractFromOneOf(definitionName, definition);
- definition = extractFromAnyOfOneOf(definitionName, definition);
+ processAndRemoveAnyOfForSubCatalogs(definitionName, definition);
var definitionProperties = definition.withObject("/properties");
var propToRemove = new HashSet();
for (var property : definitionProperties.properties()) {
@@ -529,7 +592,7 @@ public Map getLanguages() throws Exception {
* If the processor property is expression aware, it puts "expression" as a
* "$comment" in the property schema
*
- * @return
+ * @return A map of the entity name and the schema
*/
public Map getEntities() throws Exception {
var definitions = yamlDslSchema
@@ -548,8 +611,7 @@ public Map getEntities() throws Exception {
var yamlInDefinition = relocatedDefinitions.withObject("/" + yamlInFQCN);
yamlInDefinition = extractFromOneOf(yamlInFQCN, yamlInDefinition);
yamlInDefinition.remove("oneOf");
- yamlInDefinition = extractFromAnyOfOneOf(yamlInFQCN, yamlInDefinition);
- yamlInDefinition.remove("anyOf");
+ processAndRemoveAnyOfForSubCatalogs(yamlInFQCN, yamlInDefinition);
Set propToRemove = new HashSet<>();
var yamlInProperties = yamlInDefinition.withObject("/properties");
for (var yamlInPropertyEntry : yamlInProperties.properties()) {
diff --git a/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/Util.java b/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/Util.java
index 378a4d2f..0ae89cfd 100644
--- a/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/Util.java
+++ b/packages/catalog-generator/src/main/java/io/kaoto/camelcatalog/generator/Util.java
@@ -19,8 +19,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
-import org.apache.commons.io.FilenameUtils;
-
public class Util {
public static String generateHash(byte[] content) throws Exception {
if (content == null)
@@ -44,8 +42,7 @@ public static String getNormalizedFolder(String folder) {
// Resolve the relative path
Path absolutePath = currentDirectory.resolve(folder);
- String normalizedfolder = FilenameUtils.separatorsToUnix(absolutePath.toString());
- return normalizedfolder;
+ return absolutePath.toString();
}
}
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandOptionsTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandOptionsTest.java
index 3db10ba2..421cdec1 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandOptionsTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandOptionsTest.java
@@ -41,7 +41,7 @@ public void testConfigureWithAllRequiredOptions() throws ParseException {
generateCommandOptions.configure(args);
String outputDir = Util.getNormalizedFolder("outputDir");
- assertEquals(outputDir, configBean.getOutputFolder().toString());
+ assertEquals(outputDir, configBean.getOutputFolder().toPath().toString());
assertEquals("catalogName", configBean.getCatalogsName());
assertEquals("kameletsVersion", configBean.getKameletsVersion());
}
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandTest.java
index d569d79c..f0b365e4 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/commands/GenerateCommandTest.java
@@ -24,6 +24,7 @@
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
+import java.nio.file.Path;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -84,7 +85,8 @@ void testGeneratorCalledWithCorrectParameters() {
File expectedFolder = new File(tempDir, "camel-main/4.8.0");
verify(builder, times(1)).withOutputDirectory(expectedFolder);
- assertEquals(catalogDefinition.getFileName(), "camel-main/4.8.0/index.json");
+ String expectedFile = Path.of("camel-main", "4.8.0", "index.json").toString();
+ assertEquals(catalogDefinition.getFileName(), expectedFile);
}
}
@@ -123,7 +125,9 @@ void testCatalogLibraryOutput() {
assertEquals(catalogLibraryEntry.name(), "test-camel-catalog");
assertEquals(catalogLibraryEntry.version(), "4.8.0");
assertEquals(catalogLibraryEntry.runtime(), "Main");
- assertEquals(catalogLibraryEntry.fileName(), "camel-main/4.8.0/index.json");
+
+ String expectedFile = Path.of("camel-main", "4.8.0", "index.json").toString();
+ assertEquals(catalogLibraryEntry.fileName(), expectedFile);
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessorTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessorTest.java
index 91af4940..2d8d6810 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessorTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelCatalogProcessorTest.java
@@ -30,7 +30,7 @@
import static org.junit.jupiter.api.Assertions.*;
-public class CamelCatalogProcessorTest {
+class CamelCatalogProcessorTest {
private static final List ALLOWED_ENUM_TYPES = List.of("integer", "number", "string");
private final CamelCatalogProcessor processor;
private final ObjectNode componentCatalog;
@@ -41,7 +41,7 @@ public class CamelCatalogProcessorTest {
private final ObjectNode entityCatalog;
private final ObjectNode loadBalancerCatalog;
- public CamelCatalogProcessorTest() throws Exception {
+ CamelCatalogProcessorTest() throws Exception {
CamelCatalog catalog = new DefaultCamelCatalog();
ObjectMapper jsonMapper = new ObjectMapper();
var is = YamlRoutesBuilderLoader.class.getClassLoader().getResourceAsStream("schema/camelYamlDsl.json");
@@ -60,7 +60,7 @@ public CamelCatalogProcessorTest() throws Exception {
}
@Test
- public void testProcessCatalog() throws Exception {
+ void testProcessCatalog() throws Exception {
var catalogMap = processor.processCatalog();
assertEquals(processor.getComponentCatalog(), catalogMap.get("components"));
assertEquals(processor.getDataFormatCatalog(), catalogMap.get("dataformats"));
@@ -72,7 +72,7 @@ public void testProcessCatalog() throws Exception {
}
@Test
- public void testGetComponentCatalog() throws Exception {
+ void testGetComponentCatalog() throws Exception {
assertTrue(componentCatalog.size() > 300);
var directModel = componentCatalog
.withObject("/direct")
@@ -123,7 +123,7 @@ public void testGetComponentCatalog() throws Exception {
}
@Test
- public void testComponentEnumParameter() throws Exception {
+ void testComponentEnumParameter() throws Exception {
checkEnumParameters(componentCatalog);
}
@@ -154,7 +154,7 @@ private void checkEnumDuplicate(String entityName, String propertyName, ArrayNod
}
@Test
- public void testGetDataFormatCatalog() throws Exception {
+ void testGetDataFormatCatalog() throws Exception {
var customModel = dataFormatCatalog
.withObject("/custom")
.withObject("/model");
@@ -170,40 +170,48 @@ public void testGetDataFormatCatalog() throws Exception {
assertEquals("Custom", customPropertiesSchema.get("title").asText());
var refProperty = customPropertiesSchema.withObject("/properties").withObject("/ref");
assertEquals("Ref", refProperty.get("title").asText());
+ var customPropertiesSchemaRequiredFields = customPropertiesSchema.withArray("/required");
+ assertFalse(customPropertiesSchemaRequiredFields.isEmpty());
+ assertEquals(1, customPropertiesSchemaRequiredFields.size(), "Size should be 1");
}
@Test
- public void testDataFormatEnumParameter() throws Exception {
+ void testDataFormatEnumParameter() throws Exception {
checkEnumParameters(dataFormatCatalog);
}
@Test
- public void testGetLanguageCatalog() throws Exception {
+ void testGetLanguageCatalog() throws Exception {
assertFalse(languageCatalog.has("file"));
- var customModel = languageCatalog
+ var languageModel = languageCatalog
.withObject("/language")
.withObject("/model");
- assertEquals("model", customModel.get("kind").asText());
- assertEquals("Language", customModel.get("title").asText());
- var customProperties = languageCatalog
+ assertEquals("model", languageModel.get("kind").asText());
+ assertEquals("Language", languageModel.get("title").asText());
+ var languageProperties = languageCatalog
.withObject("/language")
.withObject("/properties");
- assertEquals("Language", customProperties.withObject("/language").get("displayName").asText());
- var customPropertiesSchema = languageCatalog
+ assertEquals("Language", languageProperties.withObject("/language").get("displayName").asText());
+ var languagePropertiesSchema = languageCatalog
.withObject("/language")
.withObject("/propertiesSchema");
- assertEquals("Language", customPropertiesSchema.get("title").asText());
- var languageProperty = customPropertiesSchema.withObject("/properties").withObject("/language");
+ assertEquals("Language", languagePropertiesSchema.get("title").asText());
+ var languageProperty = languagePropertiesSchema.withObject("/properties").withObject("/language");
assertEquals("Language", languageProperty.get("title").asText());
+ var languagePropertiesSchemaRequiredFields = languagePropertiesSchema.withArray("/required");
+ assertFalse(languagePropertiesSchemaRequiredFields.isEmpty());
+ assertEquals(2, languagePropertiesSchemaRequiredFields.size(), "Size should be 2");
+ assertEquals("expression", languagePropertiesSchemaRequiredFields.get(0).asText());
+ assertEquals("language", languagePropertiesSchemaRequiredFields.get(1).asText());
}
@Test
- public void testLanguageEnumParameter() throws Exception {
+ void testLanguageEnumParameter() throws Exception {
checkEnumParameters(languageCatalog);
}
@Test
- public void testGetModelCatalog() throws Exception {
+ void testGetModelCatalog() throws Exception {
assertTrue(modelCatalog.size() > 200);
var aggregateModel = modelCatalog
.withObject("/aggregate")
@@ -213,12 +221,12 @@ public void testGetModelCatalog() throws Exception {
}
@Test
- public void testModelEnumParameter() throws Exception {
+ void testModelEnumParameter() throws Exception {
checkEnumParameters(modelCatalog);
}
@Test
- public void testGetPatternCatalog() throws Exception {
+ void testGetPatternCatalog() throws Exception {
assertTrue(processorCatalog.size() > 65 && processorCatalog.size() < 80);
var choiceModel = processorCatalog.withObject("/choice").withObject("/model");
assertEquals("choice", choiceModel.get("name").asText());
@@ -235,17 +243,18 @@ public void testGetPatternCatalog() throws Exception {
}
@Test
- public void testRouteConfigurationCatalog() throws Exception {
- List.of("intercept", "interceptFrom", "interceptSendToEndpoint", "onCompletion", "onException").forEach(name -> assertTrue(entityCatalog.has(name), name));
+ void testRouteConfigurationCatalog() throws Exception {
+ List.of("intercept", "interceptFrom", "interceptSendToEndpoint", "onCompletion", "onException")
+ .forEach(name -> assertTrue(entityCatalog.has(name), name));
}
@Test
- public void testPatternEnumParameter() throws Exception {
+ void testPatternEnumParameter() throws Exception {
checkEnumParameters(processorCatalog);
}
@Test
- public void testGetEntityCatalog() throws Exception {
+ void testGetEntityCatalog() throws Exception {
List.of(
"bean",
"beans",
@@ -262,8 +271,7 @@ public void testGetEntityCatalog() throws Exception {
"templatedRoute",
"restConfiguration",
"rest",
- "routeTemplateBean"
- ).forEach(name -> assertTrue(entityCatalog.has(name), name));
+ "routeTemplateBean").forEach(name -> assertTrue(entityCatalog.has(name), name));
var bean = entityCatalog.withObject("/bean");
var beanScriptLanguage = bean.withObject("/propertiesSchema")
.withObject("/properties")
@@ -284,32 +292,42 @@ public void testGetEntityCatalog() throws Exception {
}
@Test
- public void testEntityEnumParameter() throws Exception {
+ void testEntityEnumParameter() throws Exception {
checkEnumParameters(entityCatalog);
}
@Test
- public void testGetLoadBalancerCatalog() throws Exception {
+ void testGetLoadBalancerCatalog() throws Exception {
assertFalse(loadBalancerCatalog.isEmpty());
var failoverModel = loadBalancerCatalog.withObject("/failoverLoadBalancer/model");
assertEquals("failoverLoadBalancer", failoverModel.get("name").asText());
var failoverSchema = loadBalancerCatalog.withObject("/failoverLoadBalancer/propertiesSchema");
+ var failoverSchemaRequiredFields = failoverSchema.withArray("/required");
+ assertTrue(failoverSchemaRequiredFields.isEmpty());
var maximumFailoverAttempts = failoverSchema.withObject("/properties/maximumFailoverAttempts");
assertEquals("string", maximumFailoverAttempts.get("type").asText());
assertEquals("-1", maximumFailoverAttempts.get("default").asText());
+
var roundRobinSchema = loadBalancerCatalog.withObject("/roundRobinLoadBalancer/propertiesSchema");
+ var roundRobinSchemaRequiredFields = roundRobinSchema.withArray("/required");
+ assertTrue(roundRobinSchemaRequiredFields.isEmpty());
var roundRobinId = roundRobinSchema.withObject("/properties/id");
assertEquals("string", roundRobinId.get("type").asText());
+
var customModel = loadBalancerCatalog.withObject("/customLoadBalancer/model");
assertEquals("Custom Load Balancer", customModel.get("title").asText());
var customSchema = loadBalancerCatalog.withObject("/customLoadBalancer/propertiesSchema");
+ var customSchemaRequiredFields = customSchema.withArray("/required");
+ assertFalse(customSchemaRequiredFields.isEmpty());
+ assertEquals(1, customSchemaRequiredFields.size(), "Size should be 1");
+ assertEquals("ref", customSchemaRequiredFields.get(0).asText());
assertEquals("Custom Load Balancer", customSchema.get("title").asText());
var customRef = customSchema.withObject("/properties/ref");
assertEquals("Ref", customRef.get("title").asText());
}
@Test
- public void testLoadBalancerEnumParameter() throws Exception {
+ void testLoadBalancerEnumParameter() throws Exception {
checkEnumParameters(loadBalancerCatalog);
}
}
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDSLKeysComparatorTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDSLKeysComparatorTest.java
index 1a8d1192..b8cffab3 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDSLKeysComparatorTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDSLKeysComparatorTest.java
@@ -26,16 +26,16 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-public class CamelYamlDSLKeysComparatorTest {
+class CamelYamlDSLKeysComparatorTest {
private DefaultCamelCatalog api;
@BeforeEach
- public void setUp() {
+ void setUp() {
this.api = new DefaultCamelCatalog();
}
@Test
- public void sort_keys_using_the_catalog_index() throws Exception {
+ void sort_keys_using_the_catalog_index() throws Exception {
var aggregateCatalogModel = (EipModel) api.model(Kind.eip, "aggregate");
List aggregateKeysFromCamelYAMLDsl = List.of("aggregateController", "aggregationRepository",
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessorTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessorTest.java
index 0d6f07a3..2681264e 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessorTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/CamelYamlDslSchemaProcessorTest.java
@@ -18,26 +18,28 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
-public class CamelYamlDslSchemaProcessorTest {
- private final ObjectMapper jsonMapper;
- private final ObjectNode yamlDslSchema;
- private final CamelYamlDslSchemaProcessor processor;
+class CamelYamlDslSchemaProcessorTest {
+ private ObjectMapper jsonMapper;
+ private CamelYamlDslSchemaProcessor processor;
- public CamelYamlDslSchemaProcessorTest() throws Exception {
+ @BeforeEach
+ void setUp() throws Exception {
jsonMapper = new ObjectMapper();
var is = YamlRoutesBuilderLoader.class.getClassLoader().getResourceAsStream("schema/camelYamlDsl.json");
- yamlDslSchema = (ObjectNode) jsonMapper.readTree(is);
+ ObjectNode yamlDslSchema = (ObjectNode) jsonMapper.readTree(is);
+
processor = new CamelYamlDslSchemaProcessor(jsonMapper, yamlDslSchema);
}
@Test
- public void testProcessSubSchema() throws Exception {
+ void testProcessSubSchema() throws Exception {
var subSchemaMap = processor.processSubSchema();
assertTrue(subSchemaMap.size() > 10 && subSchemaMap.size() < 20);
var beansSchema = jsonMapper.readTree(subSchemaMap.get("beans"));
@@ -52,7 +54,7 @@ public void testProcessSubSchema() throws Exception {
}
@Test
- public void testExtractSingleOneOfFromAnyOf() throws Exception {
+ void testExtractSingleOneOfFromAnyOf() throws Exception {
var subSchemaMap = processor.processSubSchema();
var errorHandlerSchema = jsonMapper.readTree(subSchemaMap.get("errorHandler"));
@@ -60,11 +62,45 @@ public void testExtractSingleOneOfFromAnyOf() throws Exception {
assertTrue(errorHandlerSchema.has("oneOf"));
assertTrue(errorHandlerSchema.get("oneOf").isArray());
- assertEquals(7, errorHandlerSchema.get("oneOf").size());
+ assertEquals(6, errorHandlerSchema.get("oneOf").size());
+ }
+
+ @Test
+ void testRemoveEmptyProperties() throws Exception {
+ var tokenizerRootSchema = (ObjectNode) jsonMapper.readTree(
+ getClass().getClassLoader().getResourceAsStream("camel-4.9.0-tokenizer-schema.json"));
+ processor = new CamelYamlDslSchemaProcessor(jsonMapper, tokenizerRootSchema);
+
+ var subSchemaMap = processor.processSubSchema();
+ var tokenizerSchema = jsonMapper.readTree(subSchemaMap.get("tokenizer"));
+ var properties = tokenizerSchema.withObject("/properties");
+
+ assertFalse(properties.has("langChain4jCharacterTokenizer"), "The langChain4jCharacterTokenizer empty property should not exist, since is empty");
+ assertFalse(properties.has("langChain4jLineTokenizer"), "The langChain4jLineTokenizer empty property should not exist, since is empty");
+ assertFalse(properties.has("langChain4jParagraphTokenizer"), "The langChain4jParagraphTokenizer empty property should not exist, since is empty");
+ assertFalse(properties.has("langChain4jSentenceTokenizer"), "The langChain4jSentenceTokenizer empty property should not exist, since is empty");
+ assertFalse(properties.has("langChain4jWordTokenizer"), "The langChain4jWordTokenizer empty property should not exist, since is empty");
+ }
+
+ @Test
+ void testRemoveNotFromOneOf() throws Exception {
+ var tokenizerRootSchema = (ObjectNode) jsonMapper.readTree(
+ getClass().getClassLoader().getResourceAsStream("camel-4.9.0-tokenizer-schema.json"));
+ processor = new CamelYamlDslSchemaProcessor(jsonMapper, tokenizerRootSchema);
+
+ var subSchemaMap = processor.processSubSchema();
+ var tokenizerSchema = jsonMapper.readTree(subSchemaMap.get("tokenizer"));
+ var oneOfArray = tokenizerSchema.withArray("/oneOf");
+
+ assertEquals(5, oneOfArray.size());
+ oneOfArray.forEach(oneOf -> {
+ assertTrue(oneOf.has("properties"));
+ assertFalse(oneOf.has("not"));
+ });
}
@Test
- public void testGetDataFormats() throws Exception {
+ void testGetDataFormats() throws Exception {
var dataFormatMap = processor.getDataFormats();
assertTrue(dataFormatMap.size() > 30 && dataFormatMap.size() < 50);
var customDataFormat = dataFormatMap.get("custom");
@@ -78,10 +114,11 @@ public void testGetDataFormats() throws Exception {
}
@Test
- public void testGetDataFormatYaml() throws Exception {
+ void testGetDataFormatYaml() throws Exception {
var dataFormatMap = processor.getDataFormats();
var yamlDataFormat = dataFormatMap.get("yaml");
- var typeFilterDefinition = yamlDataFormat.withObject("/definitions").withObject("org.apache.camel.model.dataformat.YAMLTypeFilterDefinition");
+ var typeFilterDefinition = yamlDataFormat.withObject("/definitions")
+ .withObject("org.apache.camel.model.dataformat.YAMLTypeFilterDefinition");
assertEquals("object", typeFilterDefinition.get("type").asText());
var propType = typeFilterDefinition.withObject("/properties").withObject("/type");
assertEquals("string", propType.get("type").asText());
@@ -89,7 +126,7 @@ public void testGetDataFormatYaml() throws Exception {
}
@Test
- public void testGetLanguages() throws Exception {
+ void testGetLanguages() throws Exception {
var languageMap = processor.getLanguages();
assertTrue(languageMap.size() > 20 && languageMap.size() < 30);
var customLanguage = languageMap.get("language");
@@ -103,7 +140,7 @@ public void testGetLanguages() throws Exception {
}
@Test
- public void testGetProcessors() throws Exception {
+ void testGetProcessors() throws Exception {
var processorMap = processor.getProcessors();
assertTrue(processorMap.size() > 50 && processorMap.size() < 100);
var aggregate = processorMap.get("org.apache.camel.model.AggregateDefinition");
@@ -141,13 +178,15 @@ public void testGetProcessors() throws Exception {
var actionDef = saga.withObject("/definitions").withObject("/org.apache.camel.model.SagaActionUriDefinition");
assertFalse(actionDef.has("oneOf"));
assertEquals("object", actionDef.withObject("/properties").withObject("/parameters").get("type").asText());
- var propExpDef = saga.withObject("/definitions").withObject("/org.apache.camel.model.PropertyExpressionDefinition");
+ var propExpDef =
+ saga.withObject("/definitions").withObject("/org.apache.camel.model.PropertyExpressionDefinition");
assertEquals("object", propExpDef.withObject("/properties").withObject("/expression").get("type").asText());
- assertEquals("expression", propExpDef.withObject("/properties").withObject("/expression").get("$comment").asText());
+ assertEquals("expression",
+ propExpDef.withObject("/properties").withObject("/expression").get("$comment").asText());
}
@Test
- public void testGetEntities() throws Exception {
+ void testGetEntities() throws Exception {
var entityMap = processor.getEntities();
List.of(
"beans",
@@ -163,12 +202,11 @@ public void testGetEntities() throws Exception {
"routeTemplate",
"templatedRoute",
"restConfiguration",
- "rest"
- ).forEach(name -> assertTrue(entityMap.containsKey(name), name));
+ "rest").forEach(name -> assertTrue(entityMap.containsKey(name), name));
}
@Test
- public void testGetLoadBalancers() throws Exception {
+ void testGetLoadBalancers() throws Exception {
var lbMap = processor.getLoadBalancers();
assertTrue(lbMap.containsKey("customLoadBalancer"));
var customLb = lbMap.get("customLoadBalancer");
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/K8sSchemaProcessorTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/K8sSchemaProcessorTest.java
index 3b2cc857..cf43f2d2 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/K8sSchemaProcessorTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/K8sSchemaProcessorTest.java
@@ -23,7 +23,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
-public class K8sSchemaProcessorTest {
+class K8sSchemaProcessorTest {
private static final String[] K8S_DEFINITIONS =
new String[] {
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
@@ -32,14 +32,14 @@ public class K8sSchemaProcessorTest {
private final ObjectMapper jsonMapper;
private final K8sSchemaProcessor processor;
- public K8sSchemaProcessorTest() throws Exception {
+ K8sSchemaProcessorTest() throws Exception {
jsonMapper = new ObjectMapper();
var openapiSpec = (ObjectNode) jsonMapper.readTree(getClass().getClassLoader().getResourceAsStream("kubernetes-api-v1-openapi.json"));
processor = new K8sSchemaProcessor(jsonMapper, openapiSpec);
}
@Test
- public void test() throws Exception {
+ void test() throws Exception {
var schemaMap = processor.processK8sDefinitions(List.of(K8S_DEFINITIONS));
var objectMeta = (ObjectNode) jsonMapper.readTree(schemaMap.get("ObjectMeta"));
assertTrue(objectMeta.withObject("/properties").has("annotations"));
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/KameletProcessorTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/KameletProcessorTest.java
index 7700b8d3..9ca62069 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/KameletProcessorTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/KameletProcessorTest.java
@@ -29,8 +29,8 @@
import static org.junit.jupiter.api.Assertions.*;
-public class KameletProcessorTest {
- private static final List ALLOWED_ENUM_TYPES = List.of("integer", "number", "string" );
+class KameletProcessorTest {
+ private static final List ALLOWED_ENUM_TYPES = List.of("integer", "number", "string");
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());;
private ObjectNode processKamelet(String name) throws Exception {
@@ -59,7 +59,7 @@ private Map getAllKameletFiles() throws Exception {
}
@Test
- public void test() throws Exception {
+ void test() throws Exception {
var beerSource = processKamelet("beer-source");
assertTrue(beerSource.has("propertiesSchema"));
var periodProp = beerSource.withObject("/propertiesSchema")
@@ -92,7 +92,7 @@ public void test() throws Exception {
}
@Test
- public void testEnumParameters() throws Exception {
+ void testEnumParameters() throws Exception {
for (var kamelet : getAllKameletFiles().values()) {
var schema = kamelet.withObject("/propertiesSchema");
var title = schema.get("title");
diff --git a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/UtilTest.java b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/UtilTest.java
index 3cc3e4b0..7b3ff2d8 100644
--- a/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/UtilTest.java
+++ b/packages/catalog-generator/src/test/java/io/kaoto/camelcatalog/generator/UtilTest.java
@@ -25,11 +25,11 @@
import static org.junit.jupiter.api.Assertions.*;
-public class UtilTest {
+class UtilTest {
private final List testFiles = List.of("testfile1.txt", "testfile2.txt");
@Test
- public void testGenerateHash() throws Exception {
+ void testGenerateHash() throws Exception {
var fileHashMap = new HashMap();
for (var file : testFiles) {
var is = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);
@@ -51,7 +51,7 @@ public void testGenerateHash() throws Exception {
}
@Test
- public void testGenerateHashFromPath() throws Exception {
+ void testGenerateHashFromPath() throws Exception {
var url = Thread.currentThread().getContextClassLoader().getResource(testFiles.get(0));
if (url == null) throw new Exception("no test file available");
var testFilePath = Path.of(url.toURI());
@@ -65,7 +65,7 @@ public void testGenerateHashFromPath() throws Exception {
}
@Test
- public void testGenerateHashFromString() throws Exception {
+ void testGenerateHashFromString() throws Exception {
var is = Thread.currentThread().getContextClassLoader().getResourceAsStream(testFiles.get(0));
if (is == null) throw new Exception("no test file available");
var testFileString = new String(is.readAllBytes());
@@ -79,7 +79,7 @@ public void testGenerateHashFromString() throws Exception {
}
@Test
- public void testMessageDigestHash() throws Exception {
+ void testMessageDigestHash() throws Exception {
try (var is = Thread.currentThread().getContextClassLoader().getResourceAsStream(testFiles.get(0))) {
if (is == null) throw new Exception("no test file available");
var digest = MessageDigest.getInstance("MD5");
diff --git a/packages/catalog-generator/src/test/resources/camel-4.9.0-tokenizer-schema.json b/packages/catalog-generator/src/test/resources/camel-4.9.0-tokenizer-schema.json
new file mode 100644
index 00000000..b2f7f459
--- /dev/null
+++ b/packages/catalog-generator/src/test/resources/camel-4.9.0-tokenizer-schema.json
@@ -0,0 +1,337 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "array",
+ "items": {
+ "properties": {
+ "tokenizer": {
+ "title": "Specialized tokenizer for AI applications",
+ "description": "Represents a Camel tokenizer for AI.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "description": {
+ "type": "string",
+ "title": "Description",
+ "description": "Sets the description of this node"
+ },
+ "disabled": {
+ "type": "boolean",
+ "title": "Disabled",
+ "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime."
+ },
+ "id": {
+ "type": "string",
+ "title": "Id",
+ "description": "Sets the id of this node"
+ },
+ "langChain4jCharacterTokenizer": {},
+ "langChain4jLineTokenizer": {},
+ "langChain4jParagraphTokenizer": {},
+ "langChain4jSentenceTokenizer": {},
+ "langChain4jWordTokenizer": {}
+ },
+ "anyOf": [
+ {
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "langChain4jCharacterTokenizer"
+ ],
+ "properties": {
+ "langChain4jCharacterTokenizer": {
+ "$ref": "#/items/definitions/org.apache.camel.model.tokenizer.LangChain4jCharacterTokenizerDefinition"
+ }
+ }
+ },
+ {
+ "not": {
+ "anyOf": [
+ {
+ "required": [
+ "langChain4jCharacterTokenizer"
+ ]
+ },
+ {
+ "required": [
+ "langChain4jLineTokenizer"
+ ]
+ },
+ {
+ "required": [
+ "langChain4jParagraphTokenizer"
+ ]
+ },
+ {
+ "required": [
+ "langChain4jSentenceTokenizer"
+ ]
+ },
+ {
+ "required": [
+ "langChain4jWordTokenizer"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "langChain4jLineTokenizer"
+ ],
+ "properties": {
+ "langChain4jLineTokenizer": {
+ "$ref": "#/items/definitions/org.apache.camel.model.tokenizer.LangChain4jTokenizerDefinition"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "langChain4jParagraphTokenizer"
+ ],
+ "properties": {
+ "langChain4jParagraphTokenizer": {
+ "$ref": "#/items/definitions/org.apache.camel.model.tokenizer.LangChain4jParagraphTokenizerDefinition"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "langChain4jSentenceTokenizer"
+ ],
+ "properties": {
+ "langChain4jSentenceTokenizer": {
+ "$ref": "#/items/definitions/org.apache.camel.model.tokenizer.LangChain4jSentenceTokenizerDefinition"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "langChain4jWordTokenizer"
+ ],
+ "properties": {
+ "langChain4jWordTokenizer": {
+ "$ref": "#/items/definitions/org.apache.camel.model.tokenizer.LangChain4jWordTokenizerDefinition"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "definitions": {
+ "org.apache.camel.model.tokenizer.LangChain4jCharacterTokenizerDefinition": {
+ "title": "LangChain4J Tokenizer with character splitter",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id",
+ "description": "The id of this node"
+ },
+ "maxOverlap": {
+ "type": "number",
+ "title": "Max Overlap",
+ "description": "Sets the maximum number of tokens that can overlap in each segment"
+ },
+ "maxTokens": {
+ "type": "number",
+ "title": "Max Tokens",
+ "description": "Sets the maximum number of tokens on each segment"
+ },
+ "tokenizerType": {
+ "type": "string",
+ "title": "Tokenizer Type",
+ "description": "Sets the tokenizer type",
+ "enum": [
+ "OPEN_AI",
+ "AZURE",
+ "QWEN"
+ ]
+ }
+ },
+ "required": [
+ "maxOverlap",
+ "maxTokens"
+ ]
+ },
+ "org.apache.camel.model.tokenizer.LangChain4jLineTokenizerDefinition": {
+ "title": "LangChain4J Tokenizer with line splitter",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id",
+ "description": "The id of this node"
+ },
+ "maxOverlap": {
+ "type": "number",
+ "title": "Max Overlap",
+ "description": "Sets the maximum number of tokens that can overlap in each segment"
+ },
+ "maxTokens": {
+ "type": "number",
+ "title": "Max Tokens",
+ "description": "Sets the maximum number of tokens on each segment"
+ },
+ "tokenizerType": {
+ "type": "string",
+ "title": "Tokenizer Type",
+ "description": "Sets the tokenizer type",
+ "enum": [
+ "OPEN_AI",
+ "AZURE",
+ "QWEN"
+ ]
+ }
+ },
+ "required": [
+ "maxOverlap",
+ "maxTokens"
+ ]
+ },
+ "org.apache.camel.model.tokenizer.LangChain4jParagraphTokenizerDefinition": {
+ "title": "LangChain4J Tokenizer with paragraph splitter",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id",
+ "description": "The id of this node"
+ },
+ "maxOverlap": {
+ "type": "number",
+ "title": "Max Overlap",
+ "description": "Sets the maximum number of tokens that can overlap in each segment"
+ },
+ "maxTokens": {
+ "type": "number",
+ "title": "Max Tokens",
+ "description": "Sets the maximum number of tokens on each segment"
+ },
+ "tokenizerType": {
+ "type": "string",
+ "title": "Tokenizer Type",
+ "description": "Sets the tokenizer type",
+ "enum": [
+ "OPEN_AI",
+ "AZURE",
+ "QWEN"
+ ]
+ }
+ },
+ "required": [
+ "maxOverlap",
+ "maxTokens"
+ ]
+ },
+ "org.apache.camel.model.tokenizer.LangChain4jSentenceTokenizerDefinition": {
+ "title": "LangChain4J Tokenizer with sentence splitter",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id",
+ "description": "The id of this node"
+ },
+ "maxOverlap": {
+ "type": "number",
+ "title": "Max Overlap",
+ "description": "Sets the maximum number of tokens that can overlap in each segment"
+ },
+ "maxTokens": {
+ "type": "number",
+ "title": "Max Tokens",
+ "description": "Sets the maximum number of tokens on each segment"
+ },
+ "tokenizerType": {
+ "type": "string",
+ "title": "Tokenizer Type",
+ "description": "Sets the tokenizer type",
+ "enum": [
+ "OPEN_AI",
+ "AZURE",
+ "QWEN"
+ ]
+ }
+ },
+ "required": [
+ "maxOverlap",
+ "maxTokens"
+ ]
+ },
+ "org.apache.camel.model.tokenizer.LangChain4jTokenizerDefinition": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "maxOverlap": {
+ "type": "number"
+ },
+ "maxTokens": {
+ "type": "number"
+ },
+ "tokenizerType": {
+ "type": "string",
+ "enum": [
+ "OPEN_AI",
+ "AZURE",
+ "QWEN"
+ ]
+ }
+ },
+ "required": [
+ "maxOverlap",
+ "maxTokens"
+ ]
+ },
+ "org.apache.camel.model.tokenizer.LangChain4jWordTokenizerDefinition": {
+ "title": "LangChain4J Tokenizer with word splitter",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "Id",
+ "description": "The id of this node"
+ },
+ "maxOverlap": {
+ "type": "number",
+ "title": "Max Overlap",
+ "description": "Sets the maximum number of tokens that can overlap in each segment"
+ },
+ "maxTokens": {
+ "type": "number",
+ "title": "Max Tokens",
+ "description": "Sets the maximum number of tokens on each segment"
+ },
+ "tokenizerType": {
+ "type": "string",
+ "title": "Tokenizer Type",
+ "description": "Sets the tokenizer type",
+ "enum": [
+ "OPEN_AI",
+ "AZURE",
+ "QWEN"
+ ]
+ }
+ },
+ "required": [
+ "maxOverlap",
+ "maxTokens"
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/producerConsumerConf.cy.ts b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/producerConsumerConf.cy.ts
new file mode 100644
index 00000000..c6da5283
--- /dev/null
+++ b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/producerConsumerConf.cy.ts
@@ -0,0 +1,20 @@
+describe('Tests for producer/consumer sidebar config', () => {
+ beforeEach(() => {
+ cy.openHomePage();
+ });
+
+ it('Check if producer/consumer properties are allowed or forbidden on route nodes', () => {
+ cy.uploadFixture('flows/camelRoute/consumerProducer.yaml');
+ cy.openDesignPage();
+
+ cy.openStepConfigurationTab('amqp');
+ cy.selectFormTab('All');
+ cy.get('.pf-v5-c-expandable-section__toggle-text').contains('Consumer (advanced) properties').should('exist');
+ cy.get('.pf-v5-c-expandable-section__toggle-text').contains('Producer (advanced) properties').should('not.exist');
+
+ cy.openStepConfigurationTab('activemq6');
+ cy.selectFormTab('All');
+ cy.get('.pf-v5-c-expandable-section__toggle-text').contains('Producer (advanced) properties').should('exist');
+ cy.get('.pf-v5-c-expandable-section__toggle-text').contains('Consumer (advanced) properties').should('not.exist');
+ });
+});
diff --git a/packages/ui-tests/cypress/fixtures/flows/camelRoute/consumerProducer.yaml b/packages/ui-tests/cypress/fixtures/flows/camelRoute/consumerProducer.yaml
new file mode 100644
index 00000000..3b62a6e4
--- /dev/null
+++ b/packages/ui-tests/cypress/fixtures/flows/camelRoute/consumerProducer.yaml
@@ -0,0 +1,11 @@
+- route:
+ id: route-1921
+ from:
+ id: from-2265
+ uri: amqp
+ parameters: {}
+ steps:
+ - to:
+ id: to-9644
+ uri: activemq6
+ parameters: {}
diff --git a/packages/ui-tests/stories/modal/ActionConfirmationModal.stories.tsx b/packages/ui-tests/stories/modal/ActionConfirmationModal.stories.tsx
new file mode 100644
index 00000000..8fa88270
--- /dev/null
+++ b/packages/ui-tests/stories/modal/ActionConfirmationModal.stories.tsx
@@ -0,0 +1,66 @@
+import {
+ ActionConfirmationModalContextProvider,
+ ActionConfirmationModalContext,
+ ActionConfirmationButtonOption,
+} from '@kaoto/kaoto/testing';
+import { Meta, StoryFn } from '@storybook/react';
+import { FunctionComponent, useContext, useState } from 'react';
+import { ButtonVariant } from '@patternfly/react-core';
+
+export default {
+ title: 'Modal/ActionConfirmationModal',
+ component: ActionConfirmationModalContextProvider,
+} as Meta;
+
+type TestComponentProps = {
+ title: string;
+ btnTitle?: string;
+ text?: string;
+ additionalModalText?: string;
+ buttonOptions?: Record;
+};
+
+const TestComponent: FunctionComponent = (props) => {
+ const [confirmationResult, setConfirmationResult] = useState('');
+ const { actionConfirmation: deleteConfirmation } = useContext(ActionConfirmationModalContext)!;
+ const handleDelete = async () => {
+ const res = await deleteConfirmation(props);
+ setConfirmationResult(res);
+ };
+
+ return (
+
+
+
+
{confirmationResult}
+
+ );
+};
+
+const Template: StoryFn = (_args) => {
+ return (
+
+
+
+
+ );
+};
+
+export const ActionConfirmationModal = Template.bind({});
+ActionConfirmationModal.args = {};
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 3b3b0e8f..85463ef4 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -52,7 +52,7 @@
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
- "@kaoto-next/uniforms-patternfly": "^0.7.1",
+ "@kaoto-next/uniforms-patternfly": "^0.7.10",
"@kaoto/xml-schema-ts": "workspace:*",
"@kie-tools-core/editor": "0.32.0",
"@kie-tools-core/notifications": "0.32.0",
diff --git a/packages/ui/src/components/Catalog/Catalog.tsx b/packages/ui/src/components/Catalog/Catalog.tsx
index 245c152e..a132f7a0 100644
--- a/packages/ui/src/components/Catalog/Catalog.tsx
+++ b/packages/ui/src/components/Catalog/Catalog.tsx
@@ -42,26 +42,27 @@ export const Catalog: FunctionComponent> = (prop
/** Selected Providers */
const [selectedProviders, setSelectedProviders] = useState(providers);
- /** Filter by selected group */
- const filteredTilesByGroup = useMemo(() => {
+ const filteredTiles = useMemo(() => {
return filterTiles(props.tiles, { searchTerm, searchTags: filterTags, selectedProviders });
}, [filterTags, props.tiles, searchTerm, selectedProviders]);
/** Set the tiles groups */
const tilesGroups = useMemo(() => {
- return Object.entries(filteredTilesByGroup).map(([group, tiles]) => ({ name: group, count: tiles.length }));
- }, [filteredTilesByGroup]);
+ const groups: Record = {};
+ filteredTiles.forEach((tile) => {
+ if (!groups[tile.type]) {
+ groups[tile.type] = [];
+ }
+ groups[tile.type].push(tile);
+ });
+ return Object.entries(groups).map(([group, tiles]) => ({ name: group, count: tiles.length }));
+ }, [filteredTiles]);
const [activeGroups, setActiveGroups] = useState(tilesGroups.map((g) => g.name));
- const filteredTiles = useMemo(() => {
- return Object.entries(filteredTilesByGroup).reduce((acc, [group, tiles]) => {
- if (activeGroups.includes(group)) {
- acc.push(...tiles);
- }
- return acc;
- }, [] as ITile[]);
- }, [activeGroups, filteredTilesByGroup]);
+ const filteredTilesByGroup = useMemo(() => {
+ return filteredTiles.filter((tile) => activeGroups.includes(tile.type));
+ }, [activeGroups, filteredTiles]);
const onFilterChange = useCallback(
(_event: unknown, value = '') => {
@@ -116,7 +117,7 @@ export const Catalog: FunctionComponent> = (prop
/>
{
const options = { searchTerm: 'message' };
const result = filterTiles(tiles, options);
- expect(result).toEqual({
- [CatalogKind.Component]: [tilesMap.activemq],
- [CatalogKind.Pattern]: [tilesMap.setBody, tilesMap.split],
- [CatalogKind.Kamelet]: [tilesMap.slackSource],
- });
+ expect(result).toEqual([tilesMap.activemq, tilesMap.setBody, tilesMap.split, tilesMap.slackSource]);
});
it('should filter tiles by provider', () => {
const options = { selectedProviders: ['Red Hat'] };
const result = filterTiles(tiles, options);
- expect(result).toEqual({
- [CatalogKind.Component]: [tilesMap.cron],
- [CatalogKind.Pattern]: [],
- [CatalogKind.Kamelet]: [],
- });
+ expect(result).toEqual([tilesMap.cron]);
});
it('should return tiles without provider when community is selected', () => {
const options = { selectedProviders: ['Community'] };
const result = filterTiles(tiles, options);
- expect(result).toEqual({
- [CatalogKind.Component]: [tilesMap.activemq, tilesMap.cometd, tilesMap.hazelcast],
- [CatalogKind.Pattern]: [tilesMap.setBody, tilesMap.split],
- [CatalogKind.Kamelet]: [tilesMap.beerSource, tilesMap.slackSource],
- });
+ expect(result).toEqual([
+ tilesMap.activemq,
+ tilesMap.cometd,
+ tilesMap.hazelcast,
+ tilesMap.setBody,
+ tilesMap.split,
+ tilesMap.beerSource,
+ tilesMap.slackSource,
+ ]);
});
it('should filter tiles by a single tag', () => {
const options = { searchTags: ['messaging'] };
const result = filterTiles(tiles, options);
- expect(result).toEqual({
- [CatalogKind.Component]: [tilesMap.activemq, tilesMap.cometd, tilesMap.hazelcast],
- [CatalogKind.Pattern]: [],
- [CatalogKind.Kamelet]: [],
- });
+ expect(result).toEqual([tilesMap.activemq, tilesMap.cometd, tilesMap.hazelcast]);
});
it('should filter tiles by multiple tags', () => {
const options = { searchTags: ['messaging', 'clustering'] };
const result = filterTiles(tiles, options);
- expect(result).toEqual({
- [CatalogKind.Component]: [tilesMap.hazelcast],
- [CatalogKind.Pattern]: [],
- [CatalogKind.Kamelet]: [],
- });
+ expect(result).toEqual([tilesMap.hazelcast]);
});
it('should filter tiles by search term and tags', () => {
const options = { searchTerm: 'cr', searchTags: ['scheduling'] };
const result = filterTiles(tiles, options);
- expect(result).toEqual({
- [CatalogKind.Component]: [tilesMap.cron],
- [CatalogKind.Pattern]: [],
- [CatalogKind.Kamelet]: [],
- });
+ expect(result).toEqual([tilesMap.cron]);
});
});
diff --git a/packages/ui/src/components/Catalog/filter-tiles.ts b/packages/ui/src/components/Catalog/filter-tiles.ts
index f7c10d64..ee155f29 100644
--- a/packages/ui/src/components/Catalog/filter-tiles.ts
+++ b/packages/ui/src/components/Catalog/filter-tiles.ts
@@ -6,41 +6,57 @@ const checkThatArrayContainsAllTags = (tileTags: string[], searchTags: string[])
export const filterTiles = (
tiles: ITile[],
options?: { searchTerm?: string; searchTags?: string[]; selectedProviders?: string[] },
-): Record => {
+): ITile[] => {
const { searchTerm = '', searchTags = [], selectedProviders = [] } = options ?? {};
const searchTermLowercase = searchTerm.toLowerCase();
- return tiles.reduce(
- (acc, tile) => {
- /** Filter by selected tags */
- const doesTagsMatches = searchTags.length ? checkThatArrayContainsAllTags(tile.tags, searchTags) : true;
-
- /** Filter by providers */
- let doesProviderMatch = true;
- if (selectedProviders.length) {
- doesProviderMatch =
- tile.provider === undefined
- ? selectedProviders.includes('Community')
- : selectedProviders.includes(tile.provider);
- }
-
- /** Determine whether the tile should be included in the filtered list */
- const shouldInclude =
- doesTagsMatches &&
- doesProviderMatch &&
- (!searchTermLowercase ||
- tile.name.toLowerCase().includes(searchTermLowercase) ||
- tile.title.toLowerCase().includes(searchTermLowercase) ||
- tile.description?.toLowerCase().includes(searchTermLowercase) ||
- tile.tags.some((tag) => tag.toLowerCase().includes(searchTermLowercase)));
-
- acc[tile.type] = acc[tile.type] ?? [];
- if (shouldInclude) {
- acc[tile.type].push(tile);
- }
-
- return acc;
- },
- {} as Record,
- );
+ // Step 1: Score each tile based on how well it matches the search term
+ const scoredTiles = tiles.map((tile) => {
+ let score = 0;
+
+ // Score based on name
+ const nameLower = tile.name.toLowerCase();
+ if (nameLower.startsWith(searchTermLowercase)) {
+ score += 100;
+ } else if (nameLower.includes(searchTermLowercase)) {
+ score += 40;
+ }
+
+ // Score based on title
+ if (tile.title?.toLowerCase().includes(searchTermLowercase)) {
+ score += 40;
+ }
+
+ // Score based on description
+ if (tile.description?.toLowerCase().includes(searchTermLowercase)) {
+ score += 10;
+ }
+
+ return { tile, score };
+ });
+
+ // Step 2: Filter tiles based on score, tags, and providers
+ const filteredTiles = scoredTiles.filter(({ tile, score }) => {
+ // Exclude tiles with no match
+ if (score <= 0) return false;
+
+ // Filter by selected tags
+ const doesTagsMatch = searchTags.length ? checkThatArrayContainsAllTags(tile.tags, searchTags) : true;
+
+ // Filter by selected providers
+ let doesProviderMatch = true;
+ if (selectedProviders.length) {
+ doesProviderMatch =
+ tile.provider === undefined
+ ? selectedProviders.includes('Community')
+ : selectedProviders.includes(tile.provider);
+ }
+
+ return doesTagsMatch && doesProviderMatch;
+ });
+
+ // Step 3: Sort the filtered tiles by score in descending order
+ const tilesResult: ITile[] = filteredTiles.sort((a, b) => b.score - a.score).map(({ tile }) => tile);
+
+ return tilesResult;
};
diff --git a/packages/ui/src/components/Form/CustomAutoField.test.tsx b/packages/ui/src/components/Form/CustomAutoField.test.tsx
index 2e150780..4667754c 100644
--- a/packages/ui/src/components/Form/CustomAutoField.test.tsx
+++ b/packages/ui/src/components/Form/CustomAutoField.test.tsx
@@ -11,45 +11,11 @@ jest.mock('uniforms', () => {
import { BoolField, DateField, ListField, RadioField, TextField } from '@kaoto-next/uniforms-patternfly';
import { AutoFieldProps } from 'uniforms';
import { CustomAutoField } from './CustomAutoField';
-import { OneOfField } from './OneOf/OneOfField';
import { CustomNestField } from './customField/CustomNestField';
import { DisabledField } from './customField/DisabledField';
import { TypeaheadField } from './customField/TypeaheadField';
describe('CustomAutoField', () => {
- it('should return `OneOfField` if `props.oneOf` is an array with a length > 0', () => {
- const props: AutoFieldProps = {
- oneOf: [{ type: 'string' }],
- name: 'test',
- };
-
- const result = CustomAutoField(props);
-
- expect(result).toBe(OneOfField);
- });
-
- it('should NOT return `OneOfField` if `props.oneOf` is an empty array', () => {
- const props: AutoFieldProps = {
- oneOf: [],
- name: 'test',
- };
-
- const result = CustomAutoField(props);
-
- expect(result).not.toBe(OneOfField);
- });
-
- it('should NOT return `OneOfField` if `props.oneOf` is not an array', () => {
- const props: AutoFieldProps = {
- oneOf: undefined,
- name: 'test',
- };
-
- const result = CustomAutoField(props);
-
- expect(result).not.toBe(OneOfField);
- });
-
it('should return `RadioField` if `props.options` & `props.checkboxes` are defined and `props.fieldType` is not `Array`', () => {
const props: AutoFieldProps = {
options: [],
diff --git a/packages/ui/src/components/Form/CustomAutoField.tsx b/packages/ui/src/components/Form/CustomAutoField.tsx
index 67ca24a6..2e33e55c 100644
--- a/packages/ui/src/components/Form/CustomAutoField.tsx
+++ b/packages/ui/src/components/Form/CustomAutoField.tsx
@@ -1,16 +1,15 @@
import { BoolField, DateField, ListField, RadioField, TextField } from '@kaoto-next/uniforms-patternfly';
import { createAutoField } from 'uniforms';
import { getValue } from '../../utils';
-import { OneOfField } from './OneOf/OneOfField';
import { BeanReferenceField } from './bean/BeanReferenceField';
+import { CustomLongTextField } from './customField/CustomLongTextField';
import { CustomNestField } from './customField/CustomNestField';
import { DisabledField } from './customField/DisabledField';
+import { PasswordField } from './customField/PasswordField';
import { TypeaheadField } from './customField/TypeaheadField';
import { ExpressionAwareNestField } from './expression/ExpressionAwareNestField';
import { ExpressionField } from './expression/ExpressionField';
import { PropertiesField } from './properties/PropertiesField';
-import { CustomLongTextField } from './customField/CustomLongTextField';
-import { PasswordField } from './customField/PasswordField';
// Name of the properties that should load CustomLongTextField
const CustomLongTextProps = ['Expression', 'Description', 'Query'];
@@ -20,10 +19,6 @@ const CustomLongTextProps = ['Expression', 'Description', 'Query'];
* In case a field is not supported, it will render a DisabledField
*/
export const CustomAutoField = createAutoField((props) => {
- if (Array.isArray(props.oneOf) && props.oneOf.length > 0) {
- return OneOfField;
- }
-
if (props.options) {
return props.checkboxes && props.fieldType !== Array ? RadioField : TypeaheadField;
}
diff --git a/packages/ui/src/components/Form/CustomAutoFields.test.tsx b/packages/ui/src/components/Form/CustomAutoFields.test.tsx
index 0aa5f113..3627527a 100644
--- a/packages/ui/src/components/Form/CustomAutoFields.test.tsx
+++ b/packages/ui/src/components/Form/CustomAutoFields.test.tsx
@@ -37,4 +37,54 @@ describe('CustomAutoFields', () => {
expect(wrapper?.asFragment()).toMatchSnapshot();
});
+
+ it('should render the "oneOf" selector when needed', () => {
+ const mockSchema: KaotoSchemaDefinition['schema'] = {
+ title: 'Test',
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ id: {
+ title: 'ID',
+ type: 'string',
+ },
+ },
+ oneOf: [
+ {
+ title: 'One',
+ type: 'object',
+ properties: {
+ timerName: {
+ title: 'Timer Name',
+ description: 'The name of the timer',
+ type: 'string',
+ },
+ },
+ },
+ {
+ title: 'Two',
+ type: 'object',
+ properties: {
+ pattern: {
+ title: 'Pattern',
+ description:
+ 'Allows you to specify a custom Date pattern to use for setting the time option using URI syntax.',
+ type: 'string',
+ },
+ },
+ },
+ ],
+ };
+
+ const wrapper = render(
+
+
+
+
+ ,
+ );
+
+ const oneOfToggle = wrapper.queryByTestId('-oneof-toggle');
+ expect(oneOfToggle).toBeInTheDocument();
+ });
});
diff --git a/packages/ui/src/components/Form/CustomAutoFields.tsx b/packages/ui/src/components/Form/CustomAutoFields.tsx
index 3626033e..1d25b645 100644
--- a/packages/ui/src/components/Form/CustomAutoFields.tsx
+++ b/packages/ui/src/components/Form/CustomAutoFields.tsx
@@ -1,14 +1,14 @@
import { AutoField } from '@kaoto-next/uniforms-patternfly';
+import { Card, CardBody } from '@patternfly/react-core';
import { ComponentType, createElement, useContext } from 'react';
import { useForm } from 'uniforms';
-import { KaotoSchemaDefinition } from '../../models';
-import { Card, CardBody } from '@patternfly/react-core';
-import { getFieldGroups } from '../../utils';
-import { CatalogKind } from '../../models';
+import { CatalogKind, KaotoSchemaDefinition } from '../../models';
import { CanvasFormTabsContext, FilteredFieldContext } from '../../providers';
+import { getFieldGroups } from '../../utils';
import './CustomAutoFields.scss';
import { CustomExpandableSection } from './customField/CustomExpandableSection';
import { NoFieldFound } from './NoFieldFound';
+import { OneOfField } from './OneOf/OneOfField';
export type AutoFieldsProps = {
autoField?: ComponentType<{ name: string }>;
@@ -28,11 +28,7 @@ export function CustomAutoFields({
const rootField = schema.getField('');
const { filteredFieldText, isGroupExpanded } = useContext(FilteredFieldContext);
const canvasFormTabsContext = useContext(CanvasFormTabsContext);
-
- /** Special handling for oneOf schemas */
- if (Array.isArray((rootField as KaotoSchemaDefinition['schema']).oneOf)) {
- return createElement(element, props, [createElement(autoField!, { key: '', name: '' })]);
- }
+ const oneOf = (rootField as KaotoSchemaDefinition['schema']).oneOf;
const cleanQueryTerm = filteredFieldText.replace(/\s/g, '').toLowerCase();
const actualFields = (fields ?? schema.getSubfields()).filter(
@@ -78,6 +74,9 @@ export function CustomAutoFields({
))}
+
+ {/* Special handling for oneOf schemas */}
+ {Array.isArray(oneOf) && }
>,
);
}
diff --git a/packages/ui/src/components/Form/OneOf/OneOfField.test.tsx b/packages/ui/src/components/Form/OneOf/OneOfField.test.tsx
index 8333936e..8f8addb9 100644
--- a/packages/ui/src/components/Form/OneOf/OneOfField.test.tsx
+++ b/packages/ui/src/components/Form/OneOf/OneOfField.test.tsx
@@ -20,7 +20,7 @@ describe('OneOfField', () => {
};
it('should render', () => {
- const wrapper = render(, {
+ const wrapper = render(, {
wrapper: (props) => (
{props.children}
@@ -32,7 +32,7 @@ describe('OneOfField', () => {
});
it('should render correctly', () => {
- const wrapper = render(, {
+ const wrapper = render(, {
wrapper: (props) => (
{props.children}
@@ -45,7 +45,7 @@ describe('OneOfField', () => {
});
it('should render the appropriate schema when given a matching model', () => {
- const wrapper = render(, {
+ const wrapper = render(, {
wrapper: (props) => (
{props.children}
@@ -60,7 +60,7 @@ describe('OneOfField', () => {
});
it('should render a new selected schema', async () => {
- const wrapper = render(, {
+ const wrapper = render(, {
wrapper: (props) => (
{props.children}
diff --git a/packages/ui/src/components/Form/OneOf/OneOfField.tsx b/packages/ui/src/components/Form/OneOf/OneOfField.tsx
index 95c0a6b1..d84b06c4 100644
--- a/packages/ui/src/components/Form/OneOf/OneOfField.tsx
+++ b/packages/ui/src/components/Form/OneOf/OneOfField.tsx
@@ -1,6 +1,6 @@
-import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
-import { GuaranteedProps, connectField } from 'uniforms';
+import { HTMLFieldProps, connectField } from 'uniforms';
import { useAppliedSchema, useSchemaBridgeContext } from '../../../hooks';
import { KaotoSchemaDefinition } from '../../../models';
import { SchemaBridgeProvider } from '../../../providers/schema-bridge.provider';
@@ -10,9 +10,16 @@ import { CustomAutoForm, CustomAutoFormRef } from '../CustomAutoForm';
import { SchemaService } from '../schema.service';
import { OneOfSchemaList } from './OneOfSchemaList';
-interface OneOfComponentProps extends GuaranteedProps {
- oneOf: KaotoSchemaDefinition['schema'][];
-}
+type OneOfComponentProps = HTMLFieldProps<
+ object,
+ HTMLDivElement,
+ {
+ properties?: Record;
+ helperText?: string;
+ itemProps?: object;
+ oneOf: KaotoSchemaDefinition['schema'][];
+ }
+>;
const applyDefinitionsToSchema = (
schema?: KaotoSchemaDefinition['schema'],
@@ -27,7 +34,7 @@ const applyDefinitionsToSchema = (
});
};
-const OneOfComponent: FunctionComponent = ({ name: propsName, oneOf, onChange }) => {
+export const OneOfField = connectField(({ name: propsName, oneOf, onChange }: OneOfComponentProps) => {
const formRef = useRef(null);
const divRef = useRef(null);
@@ -106,6 +113,4 @@ const OneOfComponent: FunctionComponent = ({ name: propsNam
)}
);
-};
-
-export const OneOfField = connectField(OneOfComponent as unknown as Parameters[0]);
+});
diff --git a/packages/ui/src/components/Form/OneOf/OneOfSchemaList.scss b/packages/ui/src/components/Form/OneOf/OneOfSchemaList.scss
new file mode 100644
index 00000000..dc4022f1
--- /dev/null
+++ b/packages/ui/src/components/Form/OneOf/OneOfSchemaList.scss
@@ -0,0 +1,3 @@
+.oneof-toggle {
+ margin-bottom: 1rem;
+}
diff --git a/packages/ui/src/components/Form/OneOf/OneOfSchemaList.tsx b/packages/ui/src/components/Form/OneOf/OneOfSchemaList.tsx
index e0dd6ecd..f95e1f53 100644
--- a/packages/ui/src/components/Form/OneOf/OneOfSchemaList.tsx
+++ b/packages/ui/src/components/Form/OneOf/OneOfSchemaList.tsx
@@ -1,7 +1,4 @@
import {
- Card,
- CardBody,
- CardTitle,
Dropdown,
DropdownItem,
DropdownList,
@@ -15,6 +12,7 @@ import { FunctionComponent, PropsWithChildren, Ref, useCallback, useEffect, useS
import { OneOfSchemas } from '../../../utils/get-oneof-schema-list';
import { isDefined } from '../../../utils/is-defined';
import { SchemaService } from '../schema.service';
+import './OneOfSchemaList.scss';
interface OneOfComponentProps extends PropsWithChildren {
name: string;
@@ -50,6 +48,7 @@ export const OneOfSchemaList: FunctionComponent = ({
= ({
}
return (
-
-
-
-
- {oneOfSchemas.map((schemaDef) => {
- return (
-
- {schemaDef.name}
-
- );
- })}
-
-
-
+ <>
+
+
+ {oneOfSchemas.map((schemaDef) => {
+ return (
+
+ {schemaDef.name}
+
+ );
+ })}
+
+
- {children}
-
+ {children}
+ >
);
};
diff --git a/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfField.test.tsx.snap b/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfField.test.tsx.snap
index 8ddab0b7..556199ff 100644
--- a/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfField.test.tsx.snap
+++ b/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfField.test.tsx.snap
@@ -7,74 +7,55 @@ exports[`OneOfField should render 1`] = `
data-testid="base-form"
novalidate=""
>
-
-
-
+ Select an option...
+
-
-
-
+
+
+
diff --git a/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfSchemaList.test.tsx.snap b/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfSchemaList.test.tsx.snap
index 6d11024c..acdaec14 100644
--- a/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfSchemaList.test.tsx.snap
+++ b/packages/ui/src/components/Form/OneOf/__snapshots__/OneOfSchemaList.test.tsx.snap
@@ -2,73 +2,54 @@
exports[`OneOfSchemaList should render 1`] = `
-
-
-
+ Select an option...
+
-
-
-
+
+
+
`;
diff --git a/packages/ui/src/components/Form/__snapshots__/CustomAutoFields.test.tsx.snap b/packages/ui/src/components/Form/__snapshots__/CustomAutoFields.test.tsx.snap
index 0af9aade..818637c8 100644
--- a/packages/ui/src/components/Form/__snapshots__/CustomAutoFields.test.tsx.snap
+++ b/packages/ui/src/components/Form/__snapshots__/CustomAutoFields.test.tsx.snap
@@ -583,6 +583,21 @@ exports[`CustomAutoFields renders \`AutoFields\` for common fields 1`] = `
value=""
/>
+
{
.filter((textbox) => textbox.getAttribute('label') === 'Pattern');
expect(inputPatternElement).toHaveLength(1);
});
-});
-describe('CustomNestField', () => {
- const mockSchema = {
- title: 'Test',
- type: 'object',
- additionalProperties: false,
- properties: {
- parameters: {
- type: 'object',
- title: 'Endpoint Properties',
- description: 'Endpoint properties description',
- properties: {
- timerName: {
- title: 'Timer Name',
- description: 'The name of the timer',
- type: 'string',
+ it('should not render the advanced properties button if no advanced properties are provided', () => {
+ const mockSchema = {
+ title: 'Test',
+ type: 'object',
+ additionalProperties: false,
+ properties: {
+ parameters: {
+ type: 'object',
+ title: 'Endpoint Properties',
+ description: 'Endpoint properties description',
+ properties: {
+ timerName: {
+ title: 'Timer Name',
+ description: 'The name of the timer',
+ type: 'string',
+ },
},
},
},
- },
- };
+ };
- const schemaService = new SchemaService();
- const schemaBridge = schemaService.getSchemaBridge(mockSchema);
+ const schemaService = new SchemaService();
+ const schemaBridge = schemaService.getSchemaBridge(mockSchema);
- it('should not render the advanced properties button if no advanced properties are provided', () => {
render(
diff --git a/packages/ui/src/components/Form/customField/CustomNestField.tsx b/packages/ui/src/components/Form/customField/CustomNestField.tsx
index 54e1faaa..3e59dfb7 100644
--- a/packages/ui/src/components/Form/customField/CustomNestField.tsx
+++ b/packages/ui/src/components/Form/customField/CustomNestField.tsx
@@ -20,16 +20,20 @@
import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core';
import { useContext } from 'react';
import { HTMLFieldProps, connectField, filterDOMProps } from 'uniforms';
+import { FilteredFieldContext } from '../../../providers';
import { getFieldGroups } from '../../../utils';
import { CustomAutoField } from '../CustomAutoField';
-import './CustomNestField.scss';
-import { FilteredFieldContext } from '../../../providers';
import { CustomExpandableSection } from './CustomExpandableSection';
+import './CustomNestField.scss';
export type CustomNestFieldProps = HTMLFieldProps<
object,
HTMLDivElement,
- { properties?: Record; helperText?: string; itemProps?: object }
+ {
+ properties?: Record;
+ helperText?: string;
+ itemProps?: object;
+ }
>;
export const CustomNestField = connectField(
@@ -56,7 +60,7 @@ export const CustomNestField = connectField(
if (propertiesArray.common.length === 0 && Object.keys(propertiesArray.groups).length === 0) return null;
return (
-
+
{label}
diff --git a/packages/ui/src/components/Visualization/Canvas/Canvas.tsx b/packages/ui/src/components/Visualization/Canvas/Canvas.tsx
index 5dc6ccdf..11089705 100644
--- a/packages/ui/src/components/Visualization/Canvas/Canvas.tsx
+++ b/packages/ui/src/components/Visualization/Canvas/Canvas.tsx
@@ -190,6 +190,16 @@ export const Canvas: FunctionComponent> = ({ enti
setSelectedNode(undefined);
}, []);
+ const handleCanvasClick = useCallback(
+ (event: React.MouseEvent) => {
+ const target = event.target as HTMLElement;
+ if (target.tagName === 'rect') {
+ handleCloseSideBar();
+ }
+ },
+ [handleCloseSideBar],
+ );
+
const isSidebarOpen = useMemo(() => selectedNode !== undefined, [selectedNode]);
return (
@@ -203,6 +213,7 @@ export const Canvas: FunctionComponent> = ({ enti
sideBar={}
contextToolbar={contextToolbar}
controlBar={}
+ onClick={handleCanvasClick}
>
diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx
index e2d08cdc..f0b30d05 100644
--- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx
+++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.test.tsx
@@ -1,7 +1,7 @@
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { createVisualizationNode, IVisualizationNode } from '../../../../models';
import {
- ACTION_INDEX_CONFIRM,
+ ACTION_ID_CONFIRM,
ActionConfirmationModalContext,
} from '../../../../providers/action-confirmation-modal.provider';
import { ItemDeleteGroup } from './ItemDeleteGroup';
@@ -42,7 +42,7 @@ describe('ItemDeleteGroup', () => {
it('should process addon when deleting', async () => {
const mockDeleteModalContext = {
- actionConfirmation: () => Promise.resolve(ACTION_INDEX_CONFIRM),
+ actionConfirmation: () => Promise.resolve(ACTION_ID_CONFIRM),
};
const mockAddon = jest.fn();
const mockNodeInteractionAddonContext = {
diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.tsx
index fd24454c..c82f99de 100644
--- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.tsx
+++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteGroup.tsx
@@ -3,7 +3,10 @@ import { ContextMenuItem } from '@patternfly/react-topology';
import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'react';
import { IDataTestID } from '../../../../models';
import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
-import { ActionConfirmationModalContext } from '../../../../providers/action-confirmation-modal.provider';
+import {
+ ACTION_ID_CONFIRM,
+ ActionConfirmationModalContext,
+} from '../../../../providers/action-confirmation-modal.provider';
import { EntitiesContext } from '../../../../providers/entities.provider';
import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider';
import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model';
@@ -27,7 +30,7 @@ export const ItemDeleteGroup: FunctionComponent = (props)
text: 'All steps will be lost.',
});
- if (!isDeleteConfirmed) return;
+ if (isDeleteConfirmed !== ACTION_ID_CONFIRM) return;
processNodeInteractionAddonRecursively(props.vizNode, (vn) =>
getRegisteredInteractionAddons(IInteractionAddonType.ON_DELETE, vn),
diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx
index 651c58fa..a32804fc 100644
--- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx
+++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.test.tsx
@@ -2,7 +2,7 @@ import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { createVisualizationNode, IVisualizationNode } from '../../../../models';
import { ItemDeleteStep } from './ItemDeleteStep';
import {
- ACTION_INDEX_CONFIRM,
+ ACTION_ID_CONFIRM,
ActionConfirmationModalContext,
} from '../../../../providers/action-confirmation-modal.provider';
import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model';
@@ -45,7 +45,7 @@ describe('ItemDeleteStep', () => {
});
it('should call removechild if deletion is confirmed', async () => {
- mockDeleteModalContext.actionConfirmation.mockResolvedValueOnce(true);
+ mockDeleteModalContext.actionConfirmation.mockResolvedValueOnce(ACTION_ID_CONFIRM);
const wrapper = render(
@@ -60,7 +60,7 @@ describe('ItemDeleteStep', () => {
it('should process addon when deleting', async () => {
const mockDeleteModalContext = {
- actionConfirmation: () => Promise.resolve(ACTION_INDEX_CONFIRM),
+ actionConfirmation: () => Promise.resolve(ACTION_ID_CONFIRM),
};
const mockAddon = jest.fn();
const mockNodeInteractionAddonContext = {
diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.tsx
index 393d33a0..56d84997 100644
--- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.tsx
+++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemDeleteStep.tsx
@@ -4,7 +4,10 @@ import { FunctionComponent, PropsWithChildren, useCallback, useContext } from 'r
import { IDataTestID } from '../../../../models';
import { IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { EntitiesContext } from '../../../../providers/entities.provider';
-import { ActionConfirmationModalContext } from '../../../../providers/action-confirmation-modal.provider';
+import {
+ ACTION_ID_CONFIRM,
+ ActionConfirmationModalContext,
+} from '../../../../providers/action-confirmation-modal.provider';
import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider';
import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model';
import { processNodeInteractionAddonRecursively } from './item-delete-helper';
@@ -27,7 +30,7 @@ export const ItemDeleteStep: FunctionComponent = (props) =>
text: 'Step and its children will be lost.',
});
- if (!isDeleteConfirmed) return;
+ if (isDeleteConfirmed !== ACTION_ID_CONFIRM) return;
}
processNodeInteractionAddonRecursively(props.vizNode, (vn) =>
diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx
index 9915945f..72cdc5e4 100644
--- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx
+++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.test.tsx
@@ -1,7 +1,7 @@
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { createVisualizationNode, DefinedComponent, IVisualizationNode } from '../../../../models';
import {
- ACTION_INDEX_CONFIRM,
+ ACTION_ID_CONFIRM,
ActionConfirmationModalContext,
} from '../../../../providers/action-confirmation-modal.provider';
import { ItemReplaceStep } from './ItemReplaceStep';
@@ -62,7 +62,7 @@ describe('ItemReplaceStep', () => {
getNewComponent: () => Promise.resolve({} as DefinedComponent),
};
const mockReplaceModalContext = {
- actionConfirmation: () => Promise.resolve(ACTION_INDEX_CONFIRM),
+ actionConfirmation: () => Promise.resolve(ACTION_ID_CONFIRM),
};
const mockAddon = jest.fn();
const mockNodeInteractionAddonContext = {
diff --git a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.tsx b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.tsx
index dbc140c3..e0bbc6d9 100644
--- a/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.tsx
+++ b/packages/ui/src/components/Visualization/Custom/ContextMenu/ItemReplaceStep.tsx
@@ -5,7 +5,10 @@ import { IDataTestID } from '../../../../models';
import { AddStepMode, IVisualizationNode } from '../../../../models/visualization/base-visual-entity';
import { CatalogModalContext } from '../../../../providers/catalog-modal.provider';
import { EntitiesContext } from '../../../../providers/entities.provider';
-import { ActionConfirmationModalContext } from '../../../../providers/action-confirmation-modal.provider';
+import {
+ ACTION_ID_CONFIRM,
+ ActionConfirmationModalContext,
+} from '../../../../providers/action-confirmation-modal.provider';
import { NodeInteractionAddonContext } from '../../../registers/interactions/node-interaction-addon.provider';
import { IInteractionAddonType } from '../../../registers/interactions/node-interaction-addon.model';
import { processNodeInteractionAddonRecursively } from './item-delete-helper';
@@ -31,7 +34,7 @@ export const ItemReplaceStep: FunctionComponent = (props)
text: 'Step and its children will be lost.',
});
- if (!isReplaceConfirmed) return;
+ if (isReplaceConfirmed !== ACTION_ID_CONFIRM) return;
}
/** Find compatible components */
diff --git a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.scss b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.scss
index 8554b7a9..ebd99f66 100644
--- a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.scss
+++ b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.scss
@@ -1,3 +1,5 @@
+/* stylelint-disable */
+
.custom-node {
&__image {
display: flex;
@@ -21,8 +23,23 @@
}
}
-/* stylelint-disable-next-line selector-class-pattern */
.custom-node__label--disabled {
font-style: italic;
text-decoration: line-through;
}
+
+.custom-node__label {
+ z-index: 20;
+}
+
+/* Ensure selected state has higher specificity */
+.custom-node__label--selected, .custom-node__label--selected:hover {
+ --pf-topology__node__label__background--Stroke: var(--pf-topology__node--m-selected--node__label__background--Stroke);
+ --pf-topology__node__label__background--Fill: var(--pf-topology__node--m-selected--node__label__background--Fill);
+ --pf-topology__node__separator--Stroke: var(--pf-topology__node--m-selected--node__label__text--m-secondary--Fill);
+ --pf-topology__node__action-icon__icon--Color: var(--pf-topology__node--m-selected--action-icon__icon--Color);
+ --pf-topology__node__label__text--Fill: var(--pf-topology__node--m-selected--node__label__text--Fill);
+ --pf-topology__node__label__text--m-secondary--Fill: var(--pf-topology__node--m-selected--node__label__text--m-secondary--Fill);
+
+ z-index: 50;
+}
\ No newline at end of file
diff --git a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx
index 7b4aeb4a..61183000 100644
--- a/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx
+++ b/packages/ui/src/components/Visualization/Custom/Node/CustomNode.tsx
@@ -5,8 +5,10 @@ import {
DefaultNode,
Node,
NodeStatus,
+ ScaleDetailsLevel,
WithSelectionProps,
observer,
+ useSelection,
withContextMenu,
withSelection,
} from '@patternfly/react-topology';
@@ -32,13 +34,19 @@ const CustomNode: FunctionComponent = observer(({ element, ...r
const tooltipContent = vizNode?.getTooltipContent();
const statusDecoratorTooltip = vizNode?.getNodeValidationText();
const nodeStatus = !statusDecoratorTooltip || isDisabled ? NodeStatus.default : NodeStatus.warning;
+ const detailsLevel = element.getGraph().getDetailsLevel();
+ const [selected] = useSelection();
return (
`;
diff --git a/packages/ui/src/providers/action-confirmation-modal.provider.tsx b/packages/ui/src/providers/action-confirmation-modal.provider.tsx
index 598a2b7f..2dcd5ad2 100644
--- a/packages/ui/src/providers/action-confirmation-modal.provider.tsx
+++ b/packages/ui/src/providers/action-confirmation-modal.provider.tsx
@@ -1,10 +1,9 @@
-import { Button, ButtonVariant, Modal, ModalVariant } from '@patternfly/react-core';
+import { Button, ButtonVariant, Modal, ModalVariant, Split, SplitItem } from '@patternfly/react-core';
import { FunctionComponent, PropsWithChildren, createContext, useCallback, useMemo, useRef, useState } from 'react';
-export const ACTION_INDEX_CANCEL = 0;
-export const ACTION_INDEX_CONFIRM = 1;
+export const ACTION_ID_CANCEL = 'cancel';
+export const ACTION_ID_CONFIRM = 'confirm';
export interface ActionConfirmationButtonOption {
- index: number;
buttonText: string;
variant: ButtonVariant;
isDanger?: boolean;
@@ -14,9 +13,9 @@ interface ActionConfirmationModalContextValue {
actionConfirmation: (options: {
title?: string;
text?: string;
- buttonOptions?: ActionConfirmationButtonOption[];
+ buttonOptions?: Record;
additionalModalText?: string;
- }) => Promise;
+ }) => Promise;
}
export const ActionConfirmationModalContext = createContext(undefined);
@@ -29,20 +28,20 @@ export const ActionConfirmationModalContextProvider: FunctionComponent([]);
- const [buttonOptions, setButtonOptions] = useState([]);
+ const [buttonOptions, setButtonOptions] = useState>({});
const actionConfirmationRef = useRef<{
- resolve: (index: number) => void;
+ resolve: (actionId: string) => void;
reject: (error: unknown) => unknown;
}>();
const handleCloseModal = useCallback(() => {
setIsModalOpen(false);
- actionConfirmationRef.current?.resolve(ACTION_INDEX_CANCEL);
+ actionConfirmationRef.current?.resolve(ACTION_ID_CANCEL);
}, []);
- const handleAction = useCallback((index: number) => {
+ const handleAction = useCallback((actionId: string) => {
setIsModalOpen(false);
- actionConfirmationRef.current?.resolve(index);
+ actionConfirmationRef.current?.resolve(actionId);
}, []);
const actionConfirmation = useCallback(
@@ -51,10 +50,10 @@ export const ActionConfirmationModalContextProvider: FunctionComponent;
} = {},
) => {
- const actionConfirmationPromise = new Promise((resolve, reject) => {
+ const actionConfirmationPromise = new Promise((resolve, reject) => {
/** Set both resolve and reject functions to be used once the user choose an action */
actionConfirmationRef.current = { resolve, reject };
});
@@ -67,7 +66,7 @@ export const ActionConfirmationModalContextProvider: FunctionComponent
+ {...Object.entries(buttonOptions).map(([actionId, option]) => (
+
+
+
+ ))}
+
+
+
+
+ );
+
return (
{props.children}
@@ -94,27 +121,7 @@ export const ActionConfirmationModalContextProvider: FunctionComponent (
-
- )),
- ,
- ]}
+ footer={footer}
>
{textParagraphs.length === 1
? textParagraphs[0]
diff --git a/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx b/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx
index b2b4ed64..34974d2c 100644
--- a/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx
+++ b/packages/ui/src/providers/action-confirmaton-modal.provider.test.tsx
@@ -1,13 +1,15 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { FunctionComponent, useContext } from 'react';
import {
+ ACTION_ID_CANCEL,
+ ACTION_ID_CONFIRM,
ActionConfirmationButtonOption,
ActionConfirmationModalContext,
ActionConfirmationModalContextProvider,
} from './action-confirmation-modal.provider';
import { ButtonVariant } from '@patternfly/react-core';
-let actionConfirmationResult: number | undefined;
+let actionConfirmationResult: string | undefined;
describe('ActionConfirmationModalProvider', () => {
beforeEach(() => {
@@ -28,7 +30,7 @@ describe('ActionConfirmationModalProvider', () => {
fireEvent.click(confirmButton);
// Wait for actionConfirmation promise to resolve
- await waitFor(() => expect(actionConfirmationResult).toEqual(1));
+ await waitFor(() => expect(actionConfirmationResult).toEqual(ACTION_ID_CONFIRM));
});
it('calls actionConfirmation with false when Cancel button is clicked', async () => {
@@ -45,7 +47,7 @@ describe('ActionConfirmationModalProvider', () => {
fireEvent.click(cancelButton);
// Wait for actionConfirmation promise to resolve
- await waitFor(() => expect(actionConfirmationResult).toEqual(0));
+ await waitFor(() => expect(actionConfirmationResult).toEqual(ACTION_ID_CANCEL));
});
it('should allow consumers to update the modal title and text', () => {
@@ -74,19 +76,17 @@ describe('ActionConfirmationModalProvider', () => {
title="Custom title"
text="Custom text"
additionalModalText="Additional text is added in the modal description"
- buttonOptions={[
- {
- index: 1,
+ buttonOptions={{
+ 'del-step-and-file': {
buttonText: 'Delete the step, and delete the file(s)',
variant: ButtonVariant.danger,
},
- {
- index: 2,
+ 'del-step-only': {
buttonText: 'Delete the step, but keep the file(s)',
variant: ButtonVariant.secondary,
isDanger: true,
},
- ]}
+ }}
/>
,
);
@@ -98,12 +98,12 @@ describe('ActionConfirmationModalProvider', () => {
const modalDialog = wrapper.getByRole('dialog');
expect(modalDialog.textContent).toContain('Additional text is added in the modal description');
act(() => {
- const cancelButton = wrapper.getByTestId('action-confirmation-modal-btn-0');
+ const cancelButton = wrapper.getByTestId('action-confirmation-modal-btn-cancel');
expect(cancelButton.textContent).toEqual('Cancel');
fireEvent.click(cancelButton);
});
await waitFor(() => {
- expect(actionConfirmationResult).toEqual(0);
+ expect(actionConfirmationResult).toEqual(ACTION_ID_CANCEL);
});
act(() => {
@@ -111,12 +111,12 @@ describe('ActionConfirmationModalProvider', () => {
fireEvent.click(deleteButton);
});
act(() => {
- const deleteStepAndFileButton = wrapper.getByTestId('action-confirmation-modal-btn-1');
+ const deleteStepAndFileButton = wrapper.getByTestId('action-confirmation-modal-btn-del-step-and-file');
expect(deleteStepAndFileButton.textContent).toEqual('Delete the step, and delete the file(s)');
fireEvent.click(deleteStepAndFileButton);
});
await waitFor(() => {
- expect(actionConfirmationResult).toEqual(1);
+ expect(actionConfirmationResult).toEqual('del-step-and-file');
});
act(() => {
@@ -124,12 +124,12 @@ describe('ActionConfirmationModalProvider', () => {
fireEvent.click(deleteButton);
});
act(() => {
- const deleteStepOnlyButton = wrapper.getByTestId('action-confirmation-modal-btn-2');
+ const deleteStepOnlyButton = wrapper.getByTestId('action-confirmation-modal-btn-del-step-only');
expect(deleteStepOnlyButton.textContent).toEqual('Delete the step, but keep the file(s)');
fireEvent.click(deleteStepOnlyButton);
});
await waitFor(() => {
- expect(actionConfirmationResult).toEqual(2);
+ expect(actionConfirmationResult).toEqual('del-step-only');
});
});
});
@@ -138,7 +138,7 @@ interface TestComponentProps {
title: string;
text: string;
additionalModalText?: string;
- buttonOptions?: ActionConfirmationButtonOption[];
+ buttonOptions?: Record;
}
const TestComponent: FunctionComponent = (props) => {
diff --git a/yarn.lock b/yarn.lock
index 15e80c41..0cf8ad54 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2770,19 +2770,19 @@ __metadata:
languageName: node
linkType: hard
-"@kaoto-next/uniforms-patternfly@npm:^0.7.1":
- version: 0.7.1
- resolution: "@kaoto-next/uniforms-patternfly@npm:0.7.1"
+"@kaoto-next/uniforms-patternfly@npm:^0.7.10":
+ version: 0.7.10
+ resolution: "@kaoto-next/uniforms-patternfly@npm:0.7.10"
dependencies:
invariant: "npm:^2.2.4"
lodash.clonedeep: "npm:^4.5.0"
- uniforms: "npm:4.0.0-alpha.5"
peerDependencies:
"@patternfly/react-core": ^5.0.0
"@patternfly/react-icons": ^5.0.0
react: ^18.2.0
react-dom: ^18.2.0
- checksum: 10/7b3fb24da2b383dcbd7c5fcff96570f213a1e380018d297b655a9ad4853cecfe9db6d6e16c333b6b0a6b4d76dd59deb1c76108a6e7264eaba1a4a9c3610d9550
+ uniforms: 4.0.0-alpha.5
+ checksum: 10/b6035b21974272c9d207475224326a839fe6aeec44a6f31eacbeb3728f986a2e0e2a1e968703da30d3da075b25503c77e1b381567595b76db82e00bee9fc8b69
languageName: node
linkType: hard
@@ -2859,7 +2859,7 @@ __metadata:
"@babel/preset-typescript": "npm:^7.21.5"
"@dnd-kit/core": "npm:^6.1.0"
"@eslint/js": "npm:^9.10.0"
- "@kaoto-next/uniforms-patternfly": "npm:^0.7.1"
+ "@kaoto-next/uniforms-patternfly": "npm:^0.7.10"
"@kaoto/camel-catalog": "workspace:*"
"@kaoto/xml-schema-ts": "workspace:*"
"@kie-tools-core/editor": "npm:0.32.0"