From d798a6ddbb63ae0dd5167023fb37e44cf2f1943e Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Mon, 18 Nov 2024 12:37:42 +0100 Subject: [PATCH 01/14] Fragment input fields: better error message for situation when required field has an empty list of possible values (#7160) --- .../engine/api/context/ProcessCompilationError.scala | 2 +- .../restmodel/validation/PrettyValidationErrors.scala | 6 +++--- .../compile/nodecompilation/ValueEditorValidator.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala index 95c0671c722..d4d544bd48b 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/context/ProcessCompilationError.scala @@ -316,7 +316,7 @@ object ProcessCompilationError { extends PartSubGraphCompilationError with InASingleNode - final case class RequireValueFromEmptyFixedList(paramName: ParameterName, nodeIds: Set[String]) + final case class EmptyFixedListForRequiredField(paramName: ParameterName, nodeIds: Set[String]) extends PartSubGraphCompilationError final case class InitialValueNotPresentInPossibleValues(paramName: ParameterName, nodeIds: Set[String]) diff --git a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala index 729dd2ccc4d..a2328fda58d 100644 --- a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala +++ b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/validation/PrettyValidationErrors.scala @@ -197,10 +197,10 @@ object PrettyValidationErrors { "Please check component definition", paramName = Some(qualifiedParamFieldName(paramName = paramName, subFieldName = Some(TypFieldName))) ) - case RequireValueFromEmptyFixedList(paramName, _) => + case EmptyFixedListForRequiredField(paramName, _) => node( - s"Required parameter '${paramName.value}' cannot be a member of an empty fixed list", - description = "Please check component definition", + s"Non-empty fixed list of values have to be declared for required parameter", + description = "Please add a value to the list of possible values", paramName = Some(qualifiedParamFieldName(paramName = paramName, subFieldName = Some(InputModeFieldName))) ) case ExpressionParserCompilationErrorInFragmentDefinition(message, _, paramName, subFieldName, originalExpr) => diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ValueEditorValidator.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ValueEditorValidator.scala index 309bc28005a..ab67ed5592b 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ValueEditorValidator.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/ValueEditorValidator.scala @@ -59,7 +59,7 @@ object ValueEditorValidator { if (!allowOtherValue) { List( if (fixedValuesList.isEmpty) - invalidNel(RequireValueFromEmptyFixedList(paramName, nodeIds)) + invalidNel(EmptyFixedListForRequiredField(paramName, nodeIds)) else Valid(()), if (initialValueNotPresentInPossibleValues(fixedValuesList, initialValue)) invalidNel(InitialValueNotPresentInPossibleValues(paramName, nodeIds)) From 469c3fb732d6491c8a95dccdf653433982228ce5 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Mon, 18 Nov 2024 12:38:16 +0100 Subject: [PATCH 02/14] Allow to convert Map toList in the same way as we convert List toMap (#7156) * Allow to convert Map toList in the same way as List is converted toMap * Updated snapshots * tests fix --------- Co-authored-by: arkadius <534341+arkadius@users.noreply.github.com> --- ...ay colorfull and sorted completions #0.png | Bin 22074 -> 22158 bytes ...ay colorfull and sorted completions #1.png | Bin 16426 -> 20694 bytes ...isplay used fragment graph in modal #7.png | Bin 40136 -> 39832 bytes .../resources/extractedTypes/devCreator.json | 100 +++++++++++++++++- .../extractedTypes/defaultModel.json | 100 +++++++++++++++++- .../engine/extension/Conversion.scala | 7 -- .../extension/ToListConversionExt.scala | 53 +++++++++- .../engine/extension/ToMapConversionExt.scala | 13 ++- .../engine/spel/SpelExpressionSpec.scala | 63 ++++++++++- 9 files changed, 316 insertions(+), 20 deletions(-) diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #0.png index dc9a0bfbbeee62db7f45ef38913dcc39b08fb131..f5f7253c8a9627d55f1b3e641876ab16132cf28e 100644 GIT binary patch literal 22158 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 A5&!@I diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #1.png index 061e72f62852fb8650d6a77d1221f2ab9d779084..ff3f1d07d54d61e185fcc68eb6dfa09b869dfaa1 100644 GIT binary patch literal 20694 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 literal 16426 zcma*O1z1$?+b(K?bT^Vi2uQbtG($HGEit6DbcmF62}p|wNDCuf(nFUBNJ)1i9sA|? z`|od`ea`n?*YQ$h!CLQn-_=@eC zQUm@Yc9YR_lhx2v)z*-bF_BW!(U3OLxyQrK&CUaU3;u`?L8T?MJnwI}qGlMLOZ+*X z_xW2?@1k3iIz(pA`RWzPuODmMd>JXG=0BV;?B0o+ z?r%?adAW?r=dxAlZXb%)85cOFEY2efD??lqCWqj~bqDbnJR1n#2+ ze&iEy+4edP4_pRM)6m~t)=0?Wf=h>>C~zmu3zQ=%4&`=19X{@VvXIX$g}I6F@J z*B1#74}YA_So_=89iE4OH{3gwRbgz88AWp&(fPVFN|P72&nX|fQ*CG=yJ@(gPpH{HZ=>leL7C_|=)4knvubkp^I*zU zfgGEESM!{E;!osxPZ5iD$jb60MhT^Z_S-Ar7jB+FjBz3GZH+z5Z2 zdDVaOw3ZDnn>=xTloHFc_xDJ}Dh`V`Wnz(?O(Fr}yKpbj?&u4*&F=RiIsq)p@r(@8 zKMtEEHjG&c342bb#um^;+pc6Qy_3=-9W-evd%N{N+dpYwe33LyCh`keLg_qq3KTLQ zLyXWJ<$9Dtb>*1OWp8mqba)aV2frtIͷ)^w>RyR#deq5{PDUP{5Zs71er?HN)s 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 diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #7.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #7.png index 7606c4988e8a085ff3d5fe730eeda78106c783ef..0699447c13099b26c0f03cb0b8dc26a4dbbfdc68 100644 GIT binary patch literal 39832 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 literal 40136 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%0AE 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 diff --git a/engine/flink/management/dev-model/src/test/resources/extractedTypes/devCreator.json b/engine/flink/management/dev-model/src/test/resources/extractedTypes/devCreator.json index bd5bc831a52..b9621179696 100644 --- a/engine/flink/management/dev-model/src/test/resources/extractedTypes/devCreator.json +++ b/engine/flink/management/dev-model/src/test/resources/extractedTypes/devCreator.json @@ -13613,6 +13613,32 @@ "refClazzName": "java.util.Map" }, "methods": { + "canBe": [ + { + "description": "Checks if a type can be converted to a given class", + "name": "canBe", + "signatures": [ + { + "noVarArgs": [ + {"name": "className", "refClazz": {"refClazzName": "java.lang.String"}} + ], + "result": {"refClazzName": "java.lang.Boolean"} + } + ] + } + ], + "canBeList": [ + { + "description": "Check whether can be convert to a list", + "name": "canBeList", + "signatures": [ + { + "noVarArgs": [], + "result": {"refClazzName": "java.lang.Boolean"} + } + ] + } + ], "containsKey": [ { "name": "containsKey", @@ -13710,6 +13736,78 @@ } } ], + "to": [ + { + "description": "Converts a type to a given class or throws exception if type cannot be converted.", + "name": "to", + "signatures": [ + { + "noVarArgs": [ + {"name": "className", "refClazz": {"refClazzName": "java.lang.String"}} + ], + "result": {"type": "Unknown"} + } + ] + } + ], + "toList": [ + { + "description": "Convert to a list or throw exception in case of failure", + "name": "toList", + "signatures": [ + { + "noVarArgs": [], + "result": { + "params": [ + { + "display": "Unknown", + "params": [], + "refClazzName": "java.lang.Object", + "type": "Unknown" + } + ], + "refClazzName": "java.util.List" + } + } + ] + } + ], + "toListOrNull": [ + { + "description": "Convert to a list or null in case of failure", + "name": "toListOrNull", + "signatures": [ + { + "noVarArgs": [], + "result": { + "params": [ + { + "display": "Unknown", + "params": [], + "refClazzName": "java.lang.Object", + "type": "Unknown" + } + ], + "refClazzName": "java.util.List" + } + } + ] + } + ], + "toOrNull": [ + { + "description": "Converts a type to a given class or return null if type cannot be converted.", + "name": "toOrNull", + "signatures": [ + { + "noVarArgs": [ + {"name": "className", "refClazz": {"refClazzName": "java.lang.String"}} + ], + "result": {"type": "Unknown"} + } + ] + } + ], "toString": [ { "name": "toString", @@ -15664,4 +15762,4 @@ ] } } -] +] \ No newline at end of file diff --git a/engine/flink/tests/src/test/resources/extractedTypes/defaultModel.json b/engine/flink/tests/src/test/resources/extractedTypes/defaultModel.json index 7e66bbf952c..03d4cc6889f 100644 --- a/engine/flink/tests/src/test/resources/extractedTypes/defaultModel.json +++ b/engine/flink/tests/src/test/resources/extractedTypes/defaultModel.json @@ -14005,6 +14005,32 @@ "refClazzName": "java.util.Map" }, "methods": { + "canBe": [ + { + "description": "Checks if a type can be converted to a given class", + "name": "canBe", + "signatures": [ + { + "noVarArgs": [ + {"name": "className", "refClazz": {"refClazzName": "java.lang.String"}} + ], + "result": {"refClazzName": "java.lang.Boolean"} + } + ] + } + ], + "canBeList": [ + { + "description": "Check whether can be convert to a list", + "name": "canBeList", + "signatures": [ + { + "noVarArgs": [], + "result": {"refClazzName": "java.lang.Boolean"} + } + ] + } + ], "containsKey": [ { "name": "containsKey", @@ -14102,6 +14128,78 @@ } } ], + "to": [ + { + "description": "Converts a type to a given class or throws exception if type cannot be converted.", + "name": "to", + "signatures": [ + { + "noVarArgs": [ + {"name": "className", "refClazz": {"refClazzName": "java.lang.String"}} + ], + "result": {"type": "Unknown"} + } + ] + } + ], + "toList": [ + { + "description": "Convert to a list or throw exception in case of failure", + "name": "toList", + "signatures": [ + { + "noVarArgs": [], + "result": { + "params": [ + { + "display": "Unknown", + "params": [], + "refClazzName": "java.lang.Object", + "type": "Unknown" + } + ], + "refClazzName": "java.util.List" + } + } + ] + } + ], + "toListOrNull": [ + { + "description": "Convert to a list or null in case of failure", + "name": "toListOrNull", + "signatures": [ + { + "noVarArgs": [], + "result": { + "params": [ + { + "display": "Unknown", + "params": [], + "refClazzName": "java.lang.Object", + "type": "Unknown" + } + ], + "refClazzName": "java.util.List" + } + } + ] + } + ], + "toOrNull": [ + { + "description": "Converts a type to a given class or return null if type cannot be converted.", + "name": "toOrNull", + "signatures": [ + { + "noVarArgs": [ + {"name": "className", "refClazz": {"refClazzName": "java.lang.String"}} + ], + "result": {"type": "Unknown"} + } + ] + } + ], "toString": [ { "name": "toString", @@ -17635,4 +17733,4 @@ ] } } -] +] \ No newline at end of file diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala index b5200171f46..5830d0b13ff 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/Conversion.scala @@ -130,13 +130,6 @@ object ToStringConversion extends Conversion[String] { override def convertEither(value: Any): Either[Throwable, String] = Right(value.toString) } -abstract class ToCollectionConversion[T >: Null <: AnyRef: ClassTag] extends Conversion[T] { - private val collectionClass = classOf[JCollection[_]] - - override def appliesToConversion(clazz: Class[_]): Boolean = - clazz != resultTypeClass && (clazz.isAOrChildOf(collectionClass) || clazz == unknownClass || clazz.isArray) -} - object ToByteConversion extends ToNumericConversion[JByte] { override def convertEither(value: Any): Either[Throwable, JByte] = value match { diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToListConversionExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToListConversionExt.scala index d77e2b1f371..ca38339826e 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToListConversionExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToListConversionExt.scala @@ -3,14 +3,14 @@ package pl.touk.nussknacker.engine.extension import cats.data.ValidatedNel import cats.implicits.catsSyntaxValidatedId import pl.touk.nussknacker.engine.api.generics.{GenericFunctionTypingError, MethodTypeInfo} -import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedClass, TypingResult, Unknown} +import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedClass, TypedObjectTypingResult, TypingResult, Unknown} import pl.touk.nussknacker.engine.definition.clazz.{FunctionalMethodDefinition, MethodDefinition} import pl.touk.nussknacker.engine.extension.CastOrConversionExt.{canBeMethodName, orNullSuffix, toMethodName} import pl.touk.nussknacker.engine.spel.internal.ConversionHandler import pl.touk.nussknacker.engine.util.classes.Extensions.ClassExtensions import java.lang.{Boolean => JBoolean} -import java.util.{ArrayList => JArrayList, Collection => JCollection, List => JList} +import java.util.{ArrayList => JArrayList, Collection => JCollection, HashMap => JHashMap, List => JList, Map => JMap} object ToListConversionExt extends ConversionExt(ToListConversion) { @@ -46,16 +46,26 @@ object ToListConversionExt extends ConversionExt(ToListConversion) { } -object ToListConversion extends ToCollectionConversion[JList[_]] { +object ToListConversion extends Conversion[JList[_]] { private val collectionClass = classOf[JCollection[_]] + private val mapClass = classOf[JMap[_, _]] override def convertEither(value: Any): Either[Throwable, JList[_]] = { value match { case l: JList[_] => Right(l) case c: JCollection[_] => Right(new JArrayList[Any](c)) case a: Array[_] => Right(ConversionHandler.convertArrayToList(a)) - case x => Left(new IllegalArgumentException(s"Cannot convert: $x to a List")) + case m: JMap[_, _] => + val l = new JArrayList[JMap[_, _]]() + m.entrySet().forEach { entry => + val entryRecord = new JHashMap[String, Any]() + entryRecord.put(ToMapConversion.keyName, entry.getKey) + entryRecord.put(ToMapConversion.valueName, entry.getValue) + l.add(entryRecord) + } + Right(l) + case x => Left(new IllegalArgumentException(s"Cannot convert: $x to a List")) } } @@ -66,11 +76,44 @@ object ToListConversion extends ToCollectionConversion[JList[_]] { invocationTarget.withoutValue match { case TypedClass(klass, params) if klass.isAOrChildOf(collectionClass) || klass.isArray => Typed.genericTypeClass[JList[_]](params).validNel + case TypedObjectTypingResult(_, TypedClass(klass, _ :: valuesSuperType :: Nil), _) + if klass.isAOrChildOf(mapClass) => + Typed + .genericTypeClass[JList[_]]( + List( + Typed.record( + List( + ToMapConversion.keyName -> Typed[String], + ToMapConversion.valueName -> valuesSuperType + ) + ) + ) + ) + .validNel + case TypedClass(klass, keysSuperType :: valuesSuperType :: Nil) if klass.isAOrChildOf(mapClass) => + Typed + .genericTypeClass[JList[_]]( + List( + Typed.record( + List( + ToMapConversion.keyName -> keysSuperType, + ToMapConversion.valueName -> valuesSuperType + ) + ) + ) + ) + .validNel case Unknown => Typed.genericTypeClass[JList[_]](List(Unknown)).validNel case _ => GenericFunctionTypingError.ArgumentTypeError.invalidNel } // We could leave underlying method using convertEither as well but this implementation is faster - override def canConvert(value: Any): JBoolean = value.getClass.isAOrChildOf(collectionClass) || value.getClass.isArray + override def canConvert(value: Any): JBoolean = + value.getClass.isAOrChildOf(collectionClass) || value.getClass.isAOrChildOf(mapClass) || value.getClass.isArray + + override def appliesToConversion(clazz: Class[_]): Boolean = + clazz != resultTypeClass && (clazz.isAOrChildOf(collectionClass) || clazz.isAOrChildOf( + mapClass + ) || clazz == unknownClass || clazz.isArray) } diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToMapConversionExt.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToMapConversionExt.scala index 5a97be28fdd..b3bd7abd35f 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToMapConversionExt.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/extension/ToMapConversionExt.scala @@ -46,13 +46,15 @@ object ToMapConversionExt extends ConversionExt(ToMapConversion) { } -object ToMapConversion extends ToCollectionConversion[JMap[_, _]] { +object ToMapConversion extends Conversion[JMap[_, _]] { private val mapClass = classOf[JMap[_, _]] - private val keyName = "key" - private val valueName = "value" - private val keyAndValueNames = JSet.of(keyName, valueName) + private val collectionClass = classOf[JCollection[_]] + + private[extension] val keyName = "key" + private[extension] val valueName = "value" + private val keyAndValueNames = JSet.of(keyName, valueName) override val typingResult: TypingResult = Typed.genericTypeClass(resultTypeClass, List(Unknown, Unknown)) @@ -99,4 +101,7 @@ object ToMapConversion extends ToCollectionConversion[JMap[_, _]] { case _ => false } + override def appliesToConversion(clazz: Class[_]): Boolean = + clazz != resultTypeClass && (clazz.isAOrChildOf(collectionClass) || clazz == unknownClass || clazz.isArray) + } diff --git a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala index 6e7331fda8f..03a1c429cb2 100644 --- a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala +++ b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/spel/SpelExpressionSpec.scala @@ -1921,7 +1921,7 @@ class SpelExpressionSpec extends AnyFunSuite with Matchers with ValidatedValuesD ("#unknownString.value.canBe('BigDecimal')", false), ("#unknownList.value.canBe('List')", true), ("#unknownList.value.canBe('Map')", false), - ("#unknownMap.value.canBe('List')", false), + ("#unknownMap.value.canBe('List')", true), ("#unknownMap.value.canBe('Map')", true), ("#unknownListOfTuples.value.canBe('List')", true), ("#unknownListOfTuples.value.canBe('Map')", true), @@ -1939,7 +1939,7 @@ class SpelExpressionSpec extends AnyFunSuite with Matchers with ValidatedValuesD ("#unknownString.value.canBeBigDecimal", false), ("#unknownList.value.canBeList", true), ("#unknownList.value.canBeMap", false), - ("#unknownMap.value.canBeList", false), + ("#unknownMap.value.canBeList", true), ("#unknownMap.value.canBeMap", true), ("#unknownListOfTuples.value.canBeList", true), ("#unknownListOfTuples.value.canBeMap", true), @@ -1986,6 +1986,65 @@ class SpelExpressionSpec extends AnyFunSuite with Matchers with ValidatedValuesD parsed.evaluateSync[Any](ctx) shouldBe List("a").asJava } + test("should convert a map to a list analogical as list can be converted to map") { + val listClass = classOf[JList[_]] + + val containerWithMapWithIntValues = Map("foo" -> 123, "bar" -> 234).asJava + val containerWithMapWithDifferentTypesValues = Map("foo" -> 123, "bar" -> "baz").asJava + val customCtx = Context("someContextId") + .withVariable("containerWithMapWithIntValues", ContainerOfGenericMap(containerWithMapWithIntValues)) + .withVariable( + "containerWithMapWithDifferentTypesValues", + ContainerOfGenericMap(containerWithMapWithDifferentTypesValues) + ) + + forAll( + Table( + ("mapExpression", "expectedKeyType", "expectedValueType", "expectedToListResult"), + ("{:}", Typed[String], Unknown, List.empty.asJava), + ("{foo: 123}", Typed[String], Typed[Int], List(Map("key" -> "foo", "value" -> 123).asJava).asJava), + ( + "#containerWithMapWithIntValues.value", + Unknown, + Unknown, + List( + Map("key" -> "foo", "value" -> 123).asJava, + Map("key" -> "bar", "value" -> 234).asJava, + ).asJava + ), + ( + "#containerWithMapWithDifferentTypesValues.value", + Unknown, + Unknown, + List( + Map("key" -> "foo", "value" -> 123).asJava, + Map("key" -> "bar", "value" -> "baz").asJava, + ).asJava + ), + ) + ) { (mapExpression, expectedKeyType, expectedValueType, expectedToListResult) => + val givenMapExpression = parse[Any](mapExpression, customCtx).validValue + val givenMap = givenMapExpression.evaluateSync[Any](customCtx) + + val parsedToListExpression = parse[Any](mapExpression + ".toList", customCtx).validValue + inside(parsedToListExpression.returnType) { + case TypedClass(`listClass`, (entryType: TypedObjectTypingResult) :: Nil) => + entryType.runtimeObjType.klass shouldBe classOf[JMap[_, _]] + entryType.fields.keySet shouldBe Set("key", "value") + entryType.fields("key") shouldBe expectedKeyType + entryType.fields("value") shouldBe expectedValueType + } + parsedToListExpression.evaluateSync[Any](customCtx) shouldBe expectedToListResult + + val parsedRoundTripExpression = parse[Any](mapExpression + ".toList.toMap", customCtx).validValue + parsedRoundTripExpression.evaluateSync[Any](customCtx) shouldBe givenMap + val roundTripTypeIsAGeneralizationOfGivenType = + givenMapExpression.returnType canBeSubclassOf parsedRoundTripExpression.returnType + roundTripTypeIsAGeneralizationOfGivenType shouldBe true + } + + } + test("should allow use no param method property accessor on unknown") { val customCtx = ctx.withVariable("unknownInt", ContainerOfUnknown("11")) val parsed = parse[Any]("#unknownInt.value.toLongOrNull", customCtx).validValue From 74022e2be42f1a216f901cf3213d661252175a3a Mon Sep 17 00:00:00 2001 From: Filip Michalski Date: Mon, 18 Nov 2024 22:09:48 +0100 Subject: [PATCH 03/14] Assign user friendly editor fix (#7166) * Fix assignUserFriendlyEditor casting parameters editor to StringEditor * Add tests * Add changelog entry * Simplify type check * Fix cypress test --- designer/client/cypress/e2e/process.cy.ts | 2 +- .../ui/process/test/ScenarioTestService.scala | 14 +- .../ui/api/TestingApiHttpServiceSpec.scala | 154 +++++++++++++++++- docs/Changelog.md | 3 +- .../engine/build/GraphBuilder.scala | 11 ++ .../engine/build/ScenarioBuilder.scala | 21 ++- 6 files changed, 188 insertions(+), 17 deletions(-) diff --git a/designer/client/cypress/e2e/process.cy.ts b/designer/client/cypress/e2e/process.cy.ts index afcd1748916..44f32c2a0ce 100644 --- a/designer/client/cypress/e2e/process.cy.ts +++ b/designer/client/cypress/e2e/process.cy.ts @@ -333,7 +333,7 @@ describe("Process", () => { cy.layoutScenario(); cy.contains("button", "ad hoc").should("be.enabled").click(); - cy.get("[data-testid=window]").should("be.visible").find("input").type("10"); //There should be only one input field + cy.get("[data-testid=window]").should("be.visible").find("#ace-editor").type("10"); cy.get("[data-testid=window]") .contains(/^test$/i) .should("be.enabled") diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala index 56f3a05611d..d5c1366fa17 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/test/ScenarioTestService.scala @@ -3,8 +3,8 @@ package pl.touk.nussknacker.ui.process.test import com.carrotsearch.sizeof.RamUsageEstimator import com.typesafe.scalalogging.LazyLogging import pl.touk.nussknacker.engine.api.ProcessVersion -import pl.touk.nussknacker.engine.api.definition.StringParameterEditor -import pl.touk.nussknacker.engine.api.definition.Parameter +import pl.touk.nussknacker.engine.api.definition.{DualParameterEditor, Parameter, StringParameterEditor} +import pl.touk.nussknacker.engine.api.editor.DualEditorMode import pl.touk.nussknacker.engine.api.graph.ScenarioGraph import pl.touk.nussknacker.engine.api.test.ScenarioTestData import pl.touk.nussknacker.engine.api.typed.CanBeSubclassDeterminer @@ -17,7 +17,6 @@ import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.TestSourceP import pl.touk.nussknacker.ui.api.TestDataSettings import pl.touk.nussknacker.ui.definition.DefinitionsService import pl.touk.nussknacker.ui.process.deployment.ScenarioTestExecutorService -import pl.touk.nussknacker.ui.process.label.ScenarioLabel import pl.touk.nussknacker.ui.processreport.{NodeCount, ProcessCounter, RawCount} import pl.touk.nussknacker.ui.security.api.LoggedUser import pl.touk.nussknacker.ui.uiresolving.UIProcessResolver @@ -133,10 +132,11 @@ class ScenarioTestService( private def assignUserFriendlyEditor(uiSourceParameter: UISourceParameters): UISourceParameters = { val adaptedParameters = uiSourceParameter.parameters.map { uiParameter => - if (CanBeSubclassDeterminer.canBeSubclassOf(uiParameter.typ, Typed.apply(classOf[String])).isValid) { - uiParameter.copy(editor = StringParameterEditor) - } else { - uiParameter + uiParameter.editor match { + case DualParameterEditor(StringParameterEditor, DualEditorMode.RAW) + if uiParameter.typ.canBeSubclassOf(Typed[String]) => + uiParameter.copy(editor = StringParameterEditor) + case _ => uiParameter } } uiSourceParameter.copy(parameters = adaptedParameters) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/TestingApiHttpServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/TestingApiHttpServiceSpec.scala index f233dbbdaf2..2c7d0e62824 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/TestingApiHttpServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/TestingApiHttpServiceSpec.scala @@ -6,10 +6,13 @@ import io.circe.syntax.EncoderOps import io.restassured.RestAssured.given import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.scalatest.freespec.AnyFreeSpecLike +import pl.touk.nussknacker.engine.api.definition.FixedExpressionValue import pl.touk.nussknacker.engine.api.graph.ScenarioGraph -import pl.touk.nussknacker.engine.api.parameter.ParameterName +import pl.touk.nussknacker.engine.api.parameter.{ParameterName, ValueInputWithFixedValuesProvided} import pl.touk.nussknacker.engine.build.ScenarioBuilder +import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.graph.expression.Expression +import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter} import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ WithBusinessCaseRestAssuredUsersExtensions, @@ -46,6 +49,36 @@ class TestingApiHttpServiceSpec "Value" -> Expression.spel("#input") ) + private val fragmentFixedParameter = FragmentParameter( + ParameterName("paramFixedString"), + FragmentClazzRef[java.lang.String], + initialValue = Some(FixedExpressionValue("'uno'", "uno")), + hintText = None, + valueEditor = Some( + ValueInputWithFixedValuesProvided( + fixedValuesList = List( + FixedExpressionValue("'uno'", "uno"), + FixedExpressionValue("'due'", "due"), + ), + allowOtherValue = false + ) + ), + valueCompileTimeValidation = None + ) + + private val fragmentRawStringParameter = FragmentParameter( + ParameterName("paramRawString"), + FragmentClazzRef[java.lang.String], + initialValue = None, + hintText = None, + valueEditor = None, + valueCompileTimeValidation = None + ) + + private def exampleFragment(parameter: FragmentParameter) = ScenarioBuilder + .fragmentWithRawParameters("fragment", parameter) + .fragmentOutput("fragmentEnd", "output", "out" -> "'hola'".spel) + "The endpoint for capabilities should" - { "return valid capabilities for scenario with all capabilities" in { given() @@ -172,6 +205,122 @@ class TestingApiHttpServiceSpec |""".stripMargin ) } + "generate parameters for fragment with fixed list parameter" in { + val fragment = exampleFragment(fragmentFixedParameter) + given() + .applicationState { + createSavedScenario(fragment) + } + .when() + .basicAuthAllPermUser() + .jsonBody(canonicalGraphStr(fragment)) + .post(s"$nuDesignerHttpAddress/api/scenarioTesting/${fragment.name}/parameters") + .Then() + .statusCode(200) + .equalsJsonBody( + s"""[ + | { + | "sourceId": "fragment", + | "parameters": [ + | { + | "name": "paramFixedString", + | "typ": { + | "display": "String", + | "type": "TypedClass", + | "refClazzName": "java.lang.String", + | "params": [ + | + | ] + | }, + | "editor": { + | "possibleValues": [ + | { + | "expression": "", + | "label": "" + | }, + | { + | "expression": "'uno'", + | "label": "uno" + | }, + | { + | "expression": "'due'", + | "label": "due" + | } + | ], + | "type": "FixedValuesParameterEditor" + | }, + | "defaultValue": { + | "language": "spel", + | "expression": "'uno'" + | }, + | "additionalVariables": { + | + | }, + | "variablesToHide": [ + | + | ], + | "branchParam": false, + | "hintText": null, + | "label": "paramFixedString", + | "requiredParam": false + | } + | ] + | } + |] + |""".stripMargin + ) + } + "Generate parameters with simplified (single) editor for fragment with raw string parameter" in { + val fragment = exampleFragment(fragmentRawStringParameter) + given() + .applicationState { + createSavedScenario(fragment) + } + .when() + .basicAuthAllPermUser() + .jsonBody(canonicalGraphStr(fragment)) + .post(s"$nuDesignerHttpAddress/api/scenarioTesting/${fragment.name}/parameters") + .Then() + .statusCode(200) + .equalsJsonBody( + s"""[ + | { + | "sourceId": "fragment", + | "parameters": [ + | { + | "name": "paramRawString", + | "typ": { + | "display": "String", + | "type": "TypedClass", + | "refClazzName": "java.lang.String", + | "params": [ + | + | ] + | }, + | "editor": { + | "type": "StringParameterEditor" + | }, + | "defaultValue": { + | "language": "spel", + | "expression": "" + | }, + | "additionalVariables": { + | + | }, + | "variablesToHide": [ + | + | ], + | "branchParam": false, + | "hintText": null, + | "label": "paramRawString", + | "requiredParam": false + | } + | ] + | } + |] + |""".stripMargin + ) + } "return error if scenario does not exists" in { val notExistingScenarioName = exampleScenario.name.value + "_2" given() @@ -249,4 +398,7 @@ class TestingApiHttpServiceSpec private val exampleScenarioGraph = CanonicalProcessConverter.toScenarioGraph(exampleScenario) private val exampleScenarioGraphStr = Encoder[ScenarioGraph].apply(exampleScenarioGraph).toString() + + private def canonicalGraphStr(canonical: CanonicalProcess) = + Encoder[ScenarioGraph].apply(CanonicalProcessConverter.toScenarioGraph(canonical)).toString() } diff --git a/docs/Changelog.md b/docs/Changelog.md index b7e9122af3d..a24027d7203 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -20,10 +20,11 @@ ### 1.18.0 (Not released yet) -* [6944](https://github.com/TouK/nussknacker/pull/6944) Changes around adhoc testing feature +* [6944](https://github.com/TouK/nussknacker/pull/6944) [7166](https://github.com/TouK/nussknacker/pull/7166) Changes around adhoc testing feature * `test-with-form` button was renamed to `adhoc-testing` * Improved form validators inside adhoc tests (validation was moved to backend) * Moved `testInfo/*` endpoints to `scenarioTesting/` path and rewrite then using Tapir + * Fix method `assignUserFriendlyEditor` not to change all String parameter editors to simple `StringParameterEditor` * Batch processing mode related improvements: * [#6692](https://github.com/TouK/nussknacker/pull/6692) Kryo serializers for `UnmodifiableCollection`, `scala.Product` etc. are registered based on class of Serializer instead of instance of Serializer. Thanks to this change, it is possible to use `RAW<>` diff --git a/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala b/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala index 296802e9348..8d7e71371f8 100644 --- a/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala +++ b/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala @@ -108,6 +108,17 @@ trait GraphBuilder[R] { ) ) + def fragmentInputWithRawParameters(id: String, params: FragmentParameter*): GraphBuilder[SourceNode] = + new SimpleGraphBuilder( + SourceNode( + FragmentInputDefinition( + id = id, + parameters = params.toList + ), + _ + ) + ) + def fragmentOutput(id: String, outputName: String, params: (String, Expression)*): R = creator(EndingNode(FragmentOutputDefinition(id, outputName, params.map(kv => Field(kv._1, kv._2)).toList))) diff --git a/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/ScenarioBuilder.scala b/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/ScenarioBuilder.scala index 06131fd3ce0..c30669e757e 100644 --- a/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/ScenarioBuilder.scala +++ b/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/ScenarioBuilder.scala @@ -7,7 +7,8 @@ import pl.touk.nussknacker.engine.build.GraphBuilder.Creator import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.graph.EspProcess import pl.touk.nussknacker.engine.graph.expression.Expression -import pl.touk.nussknacker.engine.graph.node.SourceNode +import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.FragmentParameter +import pl.touk.nussknacker.engine.graph.node.{SourceNode, SubsequentNode} class ProcessMetaDataBuilder private[build] (metaData: MetaData) { @@ -120,16 +121,22 @@ object ScenarioBuilder { new ProcessMetaDataBuilder(MetaData(id, RequestResponseMetaData(Some(slug)))) def fragmentWithInputNodeId(id: String, inputNodeId: String, params: (String, Class[_])*): ProcessGraphBuilder = { - new ProcessGraphBuilder( - GraphBuilder - .fragmentInput(inputNodeId, params: _*) - .creator - .andThen(r => EspProcess(MetaData(id, FragmentSpecificData()), NonEmptyList.of(r)).toCanonicalProcess) - ) + createFragment(id, GraphBuilder.fragmentInput(inputNodeId, params: _*)) + } + + def fragmentWithRawParameters(id: String, params: FragmentParameter*): ProcessGraphBuilder = { + createFragment(id, GraphBuilder.fragmentInputWithRawParameters(id, params: _*)) } def fragment(id: String, params: (String, Class[_])*): ProcessGraphBuilder = { fragmentWithInputNodeId(id, id, params: _*) } + private def createFragment(id: String, graphBuilder: GraphBuilder[SourceNode]): ProcessGraphBuilder = { + new ProcessGraphBuilder( + graphBuilder.creator + .andThen(r => EspProcess(MetaData(id, FragmentSpecificData()), NonEmptyList.of(r)).toCanonicalProcess) + ) + } + } From 628a6a81f52d23ce5d6c2b38d956fa31944bb972 Mon Sep 17 00:00:00 2001 From: Dawid Poliszak Date: Wed, 20 Nov 2024 08:06:35 +0100 Subject: [PATCH 04/14] [Nu-1882] change parameter parameter list item labels (#7171) * NU-1882 rename Add list item to "Suggested values" and "Possible values" --- designer/client/cypress/e2e/fragment.cy.ts | 2 +- .../StringBooleanVariants/AnyValueWithSuggestionVariant.tsx | 1 + .../variants/StringBooleanVariants/FixedListVariant.tsx | 1 + .../settings/variants/fields/FixedValuesSetting.tsx | 3 +++ .../settings/variants/fields/UserDefinedListInput.tsx | 4 +++- 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/designer/client/cypress/e2e/fragment.cy.ts b/designer/client/cypress/e2e/fragment.cy.ts index c31cfcd6b48..7545c06fcf9 100644 --- a/designer/client/cypress/e2e/fragment.cy.ts +++ b/designer/client/cypress/e2e/fragment.cy.ts @@ -66,7 +66,7 @@ describe("Fragment", () => { cy.get("[data-testid='settings:4']").contains("Typing...").should("not.exist"); cy.get("[data-testid='settings:4']").find("[id='ace-editor']").type("{enter}"); cy.get("[data-testid='settings:4']") - .contains(/Add list item/i) + .contains(/Suggested values/i) .siblings() .eq(0) .find("[data-testid='form-helper-text']") diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx index 9e44691e5bb..2139a7e6a8d 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx @@ -44,6 +44,7 @@ export const AnyValueWithSuggestionVariant = ({ item, path, onChange, variableTy typ={item.typ} name={item.name} initialValue={item.initialValue} + userDefinedListInputLabel={t("fragment.userDefinedList.label.suggestedValues", "Suggested values:")} /> @@ -47,6 +49,7 @@ export function FixedValuesSetting({ typ={typ} name={name} initialValue={initialValue} + inputLabel={userDefinedListInputLabel} /> )} diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/UserDefinedListInput.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/UserDefinedListInput.tsx index f2d5ffe005c..f6891664f31 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/UserDefinedListInput.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/UserDefinedListInput.tsx @@ -27,6 +27,7 @@ interface Props { typ: ReturnedType; name: string; initialValue: FixedValuesOption; + inputLabel: string; } export const UserDefinedListInput = ({ @@ -39,6 +40,7 @@ export const UserDefinedListInput = ({ typ, name, initialValue, + inputLabel, }: Props) => { const { t } = useTranslation(); const [temporaryListItem, setTemporaryListItem] = useState(""); @@ -166,7 +168,7 @@ export const UserDefinedListInput = ({ return ( - {t("fragment.addListItem", "Add list item:")} + {inputLabel} Date: Wed, 20 Nov 2024 08:28:19 +0100 Subject: [PATCH 05/14] [NU-1836] Add casting and conversions docs (#7124) --- docs/scenarios_authoring/Spel.md | 88 +++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/docs/scenarios_authoring/Spel.md b/docs/scenarios_authoring/Spel.md index 405d8dd33e7..f40fc5764bc 100644 --- a/docs/scenarios_authoring/Spel.md +++ b/docs/scenarios_authoring/Spel.md @@ -264,18 +264,88 @@ can be accessed in e.g. in following ways: * `#exampleObjects['someNestedObject']['someFieldInNestedObject']` * `#exampleObjects['someArrayWithObjects'][0]['someFieldInObjectInArray']` -Every unknown accessed field/element will produce `Unknown` data type which can be further navigated or [cast](#Casting) to a desired type. +Every unknown accessed field/element will produce `Unknown` data type, which can be further navigated or [converted](#type-conversions) to a desired type. ### Type conversions -It is possible to convert from a type to another type and this can be done by implicit and explicit conversion. +It is possible to cast or convert from a type to another type and this can be done by implicit and explicit conversion. + +#### Explicit conversions + +Explicit conversions/casts are available as built-in functions. +List of built-in functions: +- `canBe(className)`/`to(className)`/`toOrNull(className)` +- `canBeBoolean`/`toBoolean`/`toBooleanOrNull` +- `canBeLong`/`toLong`/`toLongOrNull` +- `canBeDouble`/`toDouble`/`toDoubleOrNull` +- `canBeBigDecimal`/`toBigDecimal`/`toBigDecimalOrNull` +- `canBeList`/`toList`/`toListOrNull` +- `canBeMap`/`toMap`/`toMapOrNull` + +The aforementioned functions first attempt to cast a value to the specified class. If the cast fails and there is a +defined conversion to that class, the conversion is applied. +The `canBe`, `to` and `toOrNull` functions take the name of target class as a parameter, in contrast to, for +example, `canBeLong` which has the name of target class in the function name and is the shortcut for: `canBe('Long')`. +We have added some functions with types in their names, for example: `canBeLong` to have shortcuts to the most common +types. + +Functions with the prefix `canBe` check whether a type can be cast or converted to the appropriate type. Functions with +the `to` prefix cast or convert a value to the desired type, and if the operation fails, an exception is propagated +further. Functions with the `to` prefix and `OrNull` suffix cast or convert a value to the desired type, +and if the operation fails, a null value is returned. + +Examples of conversions: + +| Expression | Result | Type | +|--------------------------------------------------------------------------|-----------------------------|-------------------| +| `'123'.canBeDouble` | true | Boolean | +| `'123'.toDouble` | 123.0 | Double | +| `'abc'.toDoubleOrNull` | null | Double | +| `'123'.canBe('Double')` | true | Boolean | +| `'123'.to('Double')` | 123.0 | Double | +| `'abc'.toOrNull('Double')` | null | Double | +| `'abc'.toLong` | exception thrown in runtime | Long | +| `{{name: 'John', age: 22}}.![{key: #this.name, value: #this.age}].toMap` | {John: 22} | Map[String, Long] | + +Conversions only make sense between specific types. We limit SpeL's suggestions to show only possible conversions. +Below is a matrix which shows which types can be converted with each other: + +| Source type :arrow_down: \ Target type :arrow_right: | BigDecimal | BigInteger | Boolean | Byte | Charset | ChronoLocalDate | ChronoLocalDateTime | Currency | Double | Float | Integer | List | Locale | LocalDate | LocalDateTime | LocalTime | Long | Map | Unknown | UUID | Short | String | ZoneId | ZoneOffset | All existing types | +|------------------------------------------------------|--------------------|--------------------|---------------|--------------------|---------------|--------------------|---------------------|---------------|--------------------|--------------------|--------------------|---------------|---------------|---------------|---------------|---------------|--------------------|---------------|---------------|---------------|--------------------|--------------------|---------------|---------------|--------------------| +| BigDecimal | :x: | :heavy_check_mark: | :x: | :exclamation: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :exclamation: | :exclamation: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | +| BigInteger | :heavy_check_mark: | :x: | :x: | :exclamation: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :exclamation: | :exclamation: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | +| Byte | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | +| Double | :heavy_check_mark: | :heavy_check_mark: | :x: | :exclamation: | :x: | :x: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | +| Float | :heavy_check_mark: | :heavy_check_mark: | :x: | :exclamation: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | +| Integer | :heavy_check_mark: | :heavy_check_mark: | :x: | :exclamation: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :exclamation: | :x: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | +| LocalDate | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | +| LocalDateTime | :x: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | +| Long | :heavy_check_mark: | :heavy_check_mark: | :x: | :exclamation: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :exclamation: | :exclamation: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :exclamation: | :heavy_check_mark: | :x: | :x: | :x: | +| Unknown | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | +| UUID | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | +| Short | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | +| String | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | :exclamation: | + +Where: +:heavy_check_mark: - conversion is possible +:x: - conversion is not possible +:exclamation: - conversion is potentially failing + +Examples of utility classes usage: + +| Expression | Result | Type | +|-----------------------------------------------------------------|----------------------------|-----------------| +| `#DATE_FORMAT.parseOffsetDateTime('2018-10-23T12:12:13+00:00')` | 1540296720000 | OffsetDateTime | +| `#DATE_FORMAT.parseLocalDateTime('2018-10-23T12:12:13')` | 2018-10-23T12:12:13+00:00 | LocalDateTime | #### Implicit conversion SpEL has many built-in implicit conversions that are available also in Nussknacker. Mostly conversions between various numeric types and between `String` and some useful logical value types. Implicit conversion means that when finding the "input value" of type "input type" (see the table below) in the context where "target type" is expected, Nussknacker -will try to convert the type of the "input value" to the "target type". Some examples: +will try to convert the type of the "input value" to the "target type". This behaviour can be encountered in particular +when passing certain values to method parameters (these values can be automatically converted to the desired type). +Some conversions example: | Input value | Input type | Conversion target type | |------------------------------------------|------------|------------------------| @@ -298,20 +368,10 @@ Usage example: | Expression | Input value | Input type | Target type | |-------------------------------------|-------------------|------------|-------------| | `#DATE.now.atZone('Europe/Warsaw')` | `'Europe/Warsaw'` | String | ZoneId | -| `'' + 42` | `42` | Integer | String | +| `'' + 42` | `'42'` | Integer | String | More usage examples can be found in [this section](#conversions-between-datetime-types). -#### Explicit conversions - -Explicit conversions are available in utility classes and build-in java conversion mechanisms: - -| Expression | Result | Type | -|-----------------------------------------------------------------|----------------------------|-----------------| -| `#DATE_FORMAT.parseOffsetDateTime('2018-10-23T12:12:13+00:00')` | 1540296720000 | OffsetDateTime | -| `#DATE_FORMAT.parseLocalDateTime('2018-10-23T12:12:13')` | 2018-10-23T12:12:13+00:00 | LocalDateTime | - - ## Built-in helpers Nussknacker comes with the following helpers: From de6af00bec0b22e7b470b41407b114875f965b37 Mon Sep 17 00:00:00 2001 From: Dawid Poliszak Date: Thu, 21 Nov 2024 08:04:52 +0100 Subject: [PATCH 06/14] [NU-1891] remove autocompletion from markdown editors (#7178) * NU-1891 remove autosuggestion from markdown field --- .../editors/expression/AceWrapper.tsx | 17 +++++++++++++---- .../expression/CustomCompleterAceEditor.tsx | 4 +++- .../editors/field/MarkdownFormControl.tsx | 1 + docs/Changelog.md | 1 + 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/designer/client/src/components/graph/node-modal/editors/expression/AceWrapper.tsx b/designer/client/src/components/graph/node-modal/editors/expression/AceWrapper.tsx index 965c87c9b86..037134a079a 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/AceWrapper.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/AceWrapper.tsx @@ -25,18 +25,18 @@ export interface AceWrapperProps extends Pick, ): JSX.Element { const { language, readOnly, rows = 1, editorMode } = inputProps; @@ -183,9 +191,10 @@ export default forwardRef(function AceWrapper( className={readOnly ? " read-only" : ""} wrapEnabled={!!wrapEnabled} showGutter={!!showLineNumbers} - editorProps={DEFAULF_EDITOR_PROPS} + editorProps={DEFAULT_EDITOR_PROPS} setOptions={{ ...DEFAULT_OPTIONS, + enableLiveAutocompletion, showLineNumbers, }} enableBasicAutocompletion={customAceEditorCompleter && [customAceEditorCompleter]} diff --git a/designer/client/src/components/graph/node-modal/editors/expression/CustomCompleterAceEditor.tsx b/designer/client/src/components/graph/node-modal/editors/expression/CustomCompleterAceEditor.tsx index 3b37693fa41..d6682b0427f 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/CustomCompleterAceEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/CustomCompleterAceEditor.tsx @@ -28,10 +28,11 @@ export type CustomCompleterAceEditorProps = { showValidation?: boolean; isMarked?: boolean; className?: string; + enableLiveAutocompletion?: boolean; }; export function CustomCompleterAceEditor(props: CustomCompleterAceEditorProps): JSX.Element { - const { className, isMarked, showValidation, fieldErrors, validationLabelInfo, completer, isLoading } = props; + const { className, isMarked, showValidation, fieldErrors, validationLabelInfo, completer, isLoading, enableLiveAutocompletion } = props; const { value, onValueChange, ref, ...inputProps } = props.inputProps; const [editorFocused, setEditorFocused] = useState(false); @@ -65,6 +66,7 @@ export function CustomCompleterAceEditor(props: CustomCompleterAceEditorProps): ...inputProps, }} customAceEditorCompleter={completer} + enableLiveAutocompletion={enableLiveAutocompletion} /> Date: Thu, 21 Nov 2024 09:17:10 +0100 Subject: [PATCH 07/14] Fix scenario tests with fragment input validation (#7159) --- docs/Changelog.md | 1 + .../process/runner/FlinkTestMainSpec.scala | 59 ++++++++++++++++++- .../engine/compile/ProcessCompilerData.scala | 10 ++-- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 1d2b8b56062..6b358bb54c6 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -101,6 +101,7 @@ * [#7102](https://github.com/TouK/nussknacker/pull/7102) Introduce a new UI to defining aggregations within nodes * [#7147](https://github.com/TouK/nussknacker/pull/7147) Fix redundant "ParameterName(...)" wrapper string in exported PDFs in nodes details * [#7178](https://github.com/TouK/nussknacker/pull/7178) Remove autocompletion from markdown editors +* [#7159](https://github.com/TouK/nussknacker/pull/7159) Fix running scenario tests with provided fragment input validation ## 1.17 diff --git a/engine/flink/executor/src/test/scala/pl/touk/nussknacker/engine/process/runner/FlinkTestMainSpec.scala b/engine/flink/executor/src/test/scala/pl/touk/nussknacker/engine/process/runner/FlinkTestMainSpec.scala index dea96b429cc..0a1892d3d0f 100644 --- a/engine/flink/executor/src/test/scala/pl/touk/nussknacker/engine/process/runner/FlinkTestMainSpec.scala +++ b/engine/flink/executor/src/test/scala/pl/touk/nussknacker/engine/process/runner/FlinkTestMainSpec.scala @@ -6,7 +6,7 @@ import org.apache.flink.runtime.client.JobExecutionException import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.{BeforeAndAfterEach, Inside, OptionValues} -import pl.touk.nussknacker.engine.api.{CirceUtil, DisplayJsonWithEncoder} +import pl.touk.nussknacker.engine.api.{CirceUtil, DisplayJsonWithEncoder, FragmentSpecificData, MetaData} import pl.touk.nussknacker.engine.api.process.ComponentUseCase import pl.touk.nussknacker.engine.api.test.{ScenarioTestData, ScenarioTestJsonRecord} import pl.touk.nussknacker.engine.build.{GraphBuilder, ScenarioBuilder} @@ -16,11 +16,21 @@ import pl.touk.nussknacker.engine.flink.test.{ RecordingExceptionConsumer, RecordingExceptionConsumerProvider } -import pl.touk.nussknacker.engine.graph.node.Case +import pl.touk.nussknacker.engine.graph.node.{Case, FragmentInputDefinition, FragmentOutputDefinition} import pl.touk.nussknacker.engine.process.helpers.SampleNodes._ import pl.touk.nussknacker.engine.testmode.TestProcess._ import pl.touk.nussknacker.engine.util.ThreadUtils import pl.touk.nussknacker.engine.ModelData +import pl.touk.nussknacker.engine.api.parameter.ParameterName +import pl.touk.nussknacker.engine.graph.expression.Expression +import pl.touk.nussknacker.engine.api.parameter.ParameterValueCompileTimeValidation +import pl.touk.nussknacker.engine.canonicalgraph.canonicalnode.FlatNode +import pl.touk.nussknacker.engine.compile.FragmentResolver +import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter} +import pl.touk.nussknacker.engine.process.runner.FlinkTestMainSpec.{ + fragmentWithValidationName, + processWithFragmentParameterValidation +} import java.util.{Date, UUID} import scala.concurrent.ExecutionContext.Implicits.global @@ -654,6 +664,23 @@ class FlinkTestMainSpec extends AnyWordSpec with Matchers with Inside with Befor variable(List(ComponentUseCase.TestRuntime, ComponentUseCase.TestRuntime)) ) } + + "should not throw exception when process fragment has parameter validation defined" in { + val scenario = ScenarioBuilder + .streaming("scenario1") + .source(sourceNodeId, "input") + .fragmentOneOut("sub", fragmentWithValidationName, "output", "fragmentResult", "param" -> "'asd'".spel) + .emptySink("out", "valueMonitor", "Value" -> "1".spel) + + val resolved = FragmentResolver(List(processWithFragmentParameterValidation)).resolve(scenario) + + val results = runFlinkTest( + resolved.valueOr { _ => throw new IllegalArgumentException("Won't happen") }, + ScenarioTestData(List(ScenarioTestJsonRecord(sourceNodeId, Json.fromString("0|1|2|3|4|5|6")))), + useIOMonadInInterpreter + ) + results.exceptions.length shouldBe 0 + } } private def createTestRecord( @@ -710,3 +737,31 @@ class FlinkTestMainSpec extends AnyWordSpec with Matchers with Inside with Befor } } + +object FlinkTestMainSpec { + private val fragmentWithValidationName = "fragmentWithValidation" + + private val processWithFragmentParameterValidation: CanonicalProcess = { + val fragmentParamName = ParameterName("param") + val fragmentParam = FragmentParameter(fragmentParamName, FragmentClazzRef[String]).copy( + valueCompileTimeValidation = Some( + ParameterValueCompileTimeValidation( + validationExpression = Expression.spel("true"), + validationFailedMessage = Some("param validation failed") + ) + ) + ) + + CanonicalProcess( + MetaData(fragmentWithValidationName, FragmentSpecificData()), + List( + FlatNode( + FragmentInputDefinition("start", List(fragmentParam)) + ), + FlatNode(FragmentOutputDefinition("out1", "output", List.empty)) + ), + List.empty + ) + } + +} diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala index f80282eba93..00e2c592c04 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala @@ -39,15 +39,15 @@ object ProcessCompilerData { .filter(_.componentType == ComponentType.Service) val globalVariablesPreparer = GlobalVariablesPreparer(definitionWithTypes.modelDefinition.expressionConfig) - val expressionEvaluator = - ExpressionEvaluator.optimizedEvaluator(globalVariablesPreparer, listeners) + // Here we pass unOptimizedEvaluator for ValidationExpressionParameterValidator + // as the optimized once could cause problems with serialization with its listeners during scenario testing val expressionCompiler = ExpressionCompiler.withOptimization( userCodeClassLoader, dictRegistry, definitionWithTypes.modelDefinition.expressionConfig, definitionWithTypes.classDefinitions, - expressionEvaluator + ExpressionEvaluator.unOptimizedEvaluator(globalVariablesPreparer) ) // for testing environment it's important to take classloader from user jar @@ -69,8 +69,8 @@ object ProcessCompilerData { nodeCompiler, customProcessValidator ) - - val interpreter = Interpreter(listeners, expressionEvaluator, componentUseCase) + val expressionEvaluator = ExpressionEvaluator.optimizedEvaluator(globalVariablesPreparer, listeners) + val interpreter = Interpreter(listeners, expressionEvaluator, componentUseCase) new ProcessCompilerData( processCompiler, From 2a4713b6d4583890f35cab5682af0df4fd9fd372 Mon Sep 17 00:00:00 2001 From: Dawid Poliszak Date: Thu, 21 Nov 2024 09:19:04 +0100 Subject: [PATCH 08/14] [Nu-1889] provide an unique validation message to the scenario labels (#7182) * NU-1889 provide an unique validation message to the scenario label --- designer/client/cypress/e2e/labels.cy.ts | 8 +++++++ .../scenarioDetails/ScenarioLabels.tsx | 22 +++++++++++++++---- docs/Changelog.md | 1 + 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/designer/client/cypress/e2e/labels.cy.ts b/designer/client/cypress/e2e/labels.cy.ts index d3e882c720c..a65a7db60cd 100644 --- a/designer/client/cypress/e2e/labels.cy.ts +++ b/designer/client/cypress/e2e/labels.cy.ts @@ -33,6 +33,14 @@ describe("Scenario labels", () => { cy.get("[data-testid=scenario-label-0]").should("be.visible").contains("tagX"); + cy.get("@labelInput").should("be.visible").click().type("tagX"); + + cy.wait("@labelvalidation"); + + cy.get("@labelInput").should("be.visible").contains("This label already exists. Please enter a unique value."); + + cy.get("@labelInput").find("input").clear(); + cy.get("@labelInput").should("be.visible").click().type("tag2"); cy.wait("@labelvalidation"); diff --git a/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx b/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx index 34235357cc6..06b5c7d5d50 100644 --- a/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx +++ b/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx @@ -26,6 +26,13 @@ interface AddLabelProps { onClick: () => void; } +const labelUniqueValidation = (label: string) => ({ + label, + messages: [ + i18next.t("panels.scenarioDetails.labels.validation.uniqueValue", "This label already exists. Please enter a unique value."), + ], +}); + const AddLabel = ({ onClick }: AddLabelProps) => { return ( { setIsEdited(true); }; - const isInputInSelectedOptions = (inputValue: string): boolean => { - return scenarioLabelOptions.some((option) => inputValue === toLabelValue(option)); - }; + const isInputInSelectedOptions = useCallback( + (inputValue: string): boolean => { + return scenarioLabelOptions.some((option) => inputValue === toLabelValue(option)); + }, + [scenarioLabelOptions], + ); const inputHelperText = useMemo(() => { if (inputErrors.length !== 0) { @@ -151,9 +161,13 @@ export const ScenarioLabels = ({ readOnly }: Props) => { } } + if (isInputInSelectedOptions(newInput)) { + setInputErrors((prevState) => [...prevState, labelUniqueValidation(newInput)]); + } + setInputTyping(false); }, 500); - }, []); + }, [isInputInSelectedOptions]); const validateSelectedOptions = useMemo(() => { return debounce(async (labels: LabelOption[]) => { diff --git a/docs/Changelog.md b/docs/Changelog.md index 6b358bb54c6..4bebce77ee8 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -100,6 +100,7 @@ * [#7099](https://github.com/TouK/nussknacker/pull/7099) Provide an option to embedded video to the markdown * [#7102](https://github.com/TouK/nussknacker/pull/7102) Introduce a new UI to defining aggregations within nodes * [#7147](https://github.com/TouK/nussknacker/pull/7147) Fix redundant "ParameterName(...)" wrapper string in exported PDFs in nodes details +* [#7182](https://github.com/TouK/nussknacker/pull/7182) Provide an unique validation message to the scenario labels * [#7178](https://github.com/TouK/nussknacker/pull/7178) Remove autocompletion from markdown editors * [#7159](https://github.com/TouK/nussknacker/pull/7159) Fix running scenario tests with provided fragment input validation From 59f3ac4532184775ccfd67d892e0fdf469bac718 Mon Sep 17 00:00:00 2001 From: Arek Burdach Date: Thu, 21 Nov 2024 12:47:36 +0100 Subject: [PATCH 09/14] [NU-1877] Fix "Failed to get node validation" when using literal lists mixing different types of elements (#7187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [NU-1877] Fix "Failed to get node validation" when using literal lists mixing different types of elements * test fixes + changelog/migration guide changes * Update docs/MigrationGuide.md Co-authored-by: Mateusz SÅ‚abek --------- Co-authored-by: Mateusz SÅ‚abek --- .../engine/api/json/FromJsonDecoder.scala | 25 +++++++++++++++++ .../engine/api/typed/ValueDecoder.scala | 19 ++++++------- .../engine/api/json/FromJsonDecoderTest.scala | 21 ++++++++++++++ .../api/typed/TypingResultDecoderSpec.scala | 15 +++++++++- .../engine/api/typed/ValueDecoderSpec.scala | 13 ++++----- .../openapi/extractor/HandleResponse.scala | 4 +-- docs/Changelog.md | 9 +++--- docs/MigrationGuide.md | 20 +++++++------ .../JsonSchemaRequestResponseSource.scala | 6 ++-- .../engine/util/functions/conversion.scala | 5 ++-- .../util/functions/ConversionUtilsSpec.scala | 7 +++-- .../json/serde/CirceJsonDeserializer.scala | 4 +-- .../engine/json/swagger/SwaggerTyped.scala | 2 +- .../FromJsonSchemaBasedDecoder.scala} | 22 ++++++++------- ...sonSchemaTypeDefinitionExtractorTest.scala | 4 +-- .../serde/CirceJsonDeserializerSpec.scala | 2 +- .../FromJsonSchemaBasedDecoderTest.scala} | 28 +++++++++---------- .../engine/util/json/JsonUtils.scala | 18 ------------ 18 files changed, 135 insertions(+), 89 deletions(-) create mode 100644 components-api/src/main/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoder.scala create mode 100644 components-api/src/test/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoderTest.scala rename utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/{extractor/FromJsonDecoder.scala => decode/FromJsonSchemaBasedDecoder.scala} (83%) rename utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/{extractor/FromJsonDecoderTest.scala => decode/FromJsonSchemaBasedDecoderTest.scala} (84%) delete mode 100644 utils/utils/src/main/scala/pl/touk/nussknacker/engine/util/json/JsonUtils.scala diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoder.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoder.scala new file mode 100644 index 00000000000..587b4db9614 --- /dev/null +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoder.scala @@ -0,0 +1,25 @@ +package pl.touk.nussknacker.engine.api.json + +import io.circe.Json +import pl.touk.nussknacker.engine.util.Implicits._ + +import scala.jdk.CollectionConverters._ + +object FromJsonDecoder { + + def jsonToAny(json: Json): Any = json.fold( + jsonNull = null, + jsonBoolean = identity[Boolean], + jsonNumber = jsonNumber => + // we pick the narrowest type as possible to reduce the amount of memory and computations overheads + jsonNumber.toInt orElse + jsonNumber.toLong orElse + // We prefer java big decimal over float/double + jsonNumber.toBigDecimal.map(_.bigDecimal) + getOrElse (throw new IllegalArgumentException(s"Not supported json number: $jsonNumber")), + jsonString = identity[String], + jsonArray = _.map(jsonToAny).asJava, + jsonObject = _.toMap.mapValuesNow(jsonToAny).asJava + ) + +} diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoder.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoder.scala index 54ff028d39d..045039ab206 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoder.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoder.scala @@ -2,6 +2,7 @@ package pl.touk.nussknacker.engine.api.typed import cats.implicits.toTraverseOps import io.circe.{ACursor, Decoder, DecodingFailure, Json} +import pl.touk.nussknacker.engine.api.json.FromJsonDecoder import pl.touk.nussknacker.engine.api.typed.typing._ import java.math.BigInteger @@ -58,19 +59,15 @@ object ValueDecoder { case record: TypedObjectTypingResult => for { fieldsJson <- obj.as[Map[String, Json]] - decodedFields <- record.fields.toList.traverse { case (fieldName, fieldType) => - fieldsJson.get(fieldName) match { - case Some(fieldJson) => decodeValue(fieldType, fieldJson.hcursor).map(fieldName -> _) - case None => - Left( - DecodingFailure( - s"Record field '$fieldName' isn't present in encoded Record fields: $fieldsJson", - List() - ) - ) + decodedFields <- + fieldsJson.toList.traverse { case (fieldName, fieldJson) => + val fieldType = record.fields.getOrElse(fieldName, Unknown) + decodeValue(fieldType, fieldJson.hcursor).map(fieldName -> _) } - } } yield decodedFields.toMap.asJava + case Unknown => + /// For Unknown we fallback to generic json to any conversion. It won't work for some types such as LocalDate but for others should work correctly + obj.as[Json].map(FromJsonDecoder.jsonToAny) case typ => Left(DecodingFailure(s"Decoding of type [$typ] is not supported.", List())) } diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoderTest.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoderTest.scala new file mode 100644 index 00000000000..c27a2baf320 --- /dev/null +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/json/FromJsonDecoderTest.scala @@ -0,0 +1,21 @@ +package pl.touk.nussknacker.engine.api.json + +import io.circe.Json +import org.scalatest.OptionValues +import org.scalatest.funsuite.AnyFunSuiteLike +import org.scalatest.matchers.should.Matchers + +class FromJsonDecoderTest extends AnyFunSuiteLike with Matchers with OptionValues { + + test("json number decoding pick the narrowest type") { + FromJsonDecoder.jsonToAny(Json.fromInt(1)) shouldBe 1 + FromJsonDecoder.jsonToAny(Json.fromInt(Integer.MAX_VALUE)) shouldBe Integer.MAX_VALUE + FromJsonDecoder.jsonToAny(Json.fromLong(Long.MaxValue)) shouldBe Long.MaxValue + FromJsonDecoder.jsonToAny( + Json.fromBigDecimal(java.math.BigDecimal.valueOf(Double.MaxValue)) + ) shouldBe java.math.BigDecimal.valueOf(Double.MaxValue) + val moreThanLongMaxValue = BigDecimal(Long.MaxValue) * 10 + FromJsonDecoder.jsonToAny(Json.fromBigDecimal(moreThanLongMaxValue)) shouldBe moreThanLongMaxValue.bigDecimal + } + +} diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultDecoderSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultDecoderSpec.scala index 2358f5d6df4..b9cd3c45767 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultDecoderSpec.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultDecoderSpec.scala @@ -56,7 +56,20 @@ class TypingResultDecoderSpec Map("a" -> TypedObjectWithValue(Typed.typedClass[Int], 1)) ), List(Map("a" -> 1).asJava).asJava - ) + ), + typedListWithElementValues( + Typed.record( + List( + "a" -> Typed.typedClass[Int], + "b" -> Typed.typedClass[Int] + ) + ), + List(Map("a" -> 1).asJava, Map("b" -> 2).asJava).asJava + ), + typedListWithElementValues( + Unknown, + List(Map("a" -> 1).asJava, 2).asJava + ), ).foreach { typing => val encoded = TypeEncoders.typingResultEncoder(typing) diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoderSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoderSpec.scala index 5c7a4205012..9cbb14ab760 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoderSpec.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/ValueDecoderSpec.scala @@ -33,7 +33,7 @@ class ValueDecoderSpec extends AnyFunSuite with EitherValuesDetailedMessage with ) } - test("decodeValue should fail when a required Record field is missing") { + test("decodeValue should ignore missing Record field") { val typedRecord = Typed.record( Map( "name" -> Typed.fromInstance("Alice"), @@ -45,12 +45,10 @@ class ValueDecoderSpec extends AnyFunSuite with EitherValuesDetailedMessage with "name" -> "Alice".asJson ) - ValueDecoder.decodeValue(typedRecord, json.hcursor).leftValue.message should include( - "Record field 'age' isn't present in encoded Record fields" - ) + ValueDecoder.decodeValue(typedRecord, json.hcursor).rightValue shouldBe Map("name" -> "Alice").asJava } - test("decodeValue should not include extra fields that aren't typed") { + test("decodeValue should decode extra fields using generic json decoding strategy") { val typedRecord = Typed.record( Map( "name" -> Typed.fromInstance("Alice"), @@ -66,8 +64,9 @@ class ValueDecoderSpec extends AnyFunSuite with EitherValuesDetailedMessage with ValueDecoder.decodeValue(typedRecord, json.hcursor) shouldEqual Right( Map( - "name" -> "Alice", - "age" -> 30 + "name" -> "Alice", + "age" -> 30, + "occupation" -> "nurse" ).asJava ) } diff --git a/components/openapi/src/main/scala/pl/touk/nussknacker/openapi/extractor/HandleResponse.scala b/components/openapi/src/main/scala/pl/touk/nussknacker/openapi/extractor/HandleResponse.scala index 0d95933449b..f0f0257c6df 100644 --- a/components/openapi/src/main/scala/pl/touk/nussknacker/openapi/extractor/HandleResponse.scala +++ b/components/openapi/src/main/scala/pl/touk/nussknacker/openapi/extractor/HandleResponse.scala @@ -2,14 +2,14 @@ package pl.touk.nussknacker.openapi.extractor import java.util.Collections import io.circe.Json -import pl.touk.nussknacker.engine.json.swagger.extractor.FromJsonDecoder +import pl.touk.nussknacker.engine.json.swagger.decode.FromJsonSchemaBasedDecoder import pl.touk.nussknacker.engine.json.swagger.{SwaggerArray, SwaggerTyped} object HandleResponse { def apply(res: Option[Json], responseType: SwaggerTyped): AnyRef = res match { case Some(json) => - FromJsonDecoder.decode(json, responseType) + FromJsonSchemaBasedDecoder.decode(json, responseType) case None => responseType match { case _: SwaggerArray => Collections.EMPTY_LIST diff --git a/docs/Changelog.md b/docs/Changelog.md index 4bebce77ee8..325d5e0141c 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -20,7 +20,7 @@ ### 1.18.0 (Not released yet) -* [6944](https://github.com/TouK/nussknacker/pull/6944) [7166](https://github.com/TouK/nussknacker/pull/7166) Changes around adhoc testing feature +* [#6944](https://github.com/TouK/nussknacker/pull/6944) [#7166](https://github.com/TouK/nussknacker/pull/7166) Changes around adhoc testing feature * `test-with-form` button was renamed to `adhoc-testing` * Improved form validators inside adhoc tests (validation was moved to backend) * Moved `testInfo/*` endpoints to `scenarioTesting/` path and rewrite then using Tapir @@ -40,7 +40,7 @@ in table source and sink components in `Table` parameter * [#6950](https://github.com/TouK/nussknacker/pull/6950) Fix for testing mechanism for table sources: using full, model classpath instead of only flinkTable.jar * [#6716](https://github.com/TouK/nussknacker/pull/6716) Fix type hints for #COLLECTION.merge function. -* [#6695](https://github.com/TouK/nussknacker/pull/6695) [7032](https://github.com/TouK/nussknacker/pull/7032) From now on, arrays on UI are visible as lists but on a background they are stored as it is. +* [#6695](https://github.com/TouK/nussknacker/pull/6695) [#7032](https://github.com/TouK/nussknacker/pull/7032) From now on, arrays on UI are visible as lists but on a background they are stored as it is. * [#6750](https://github.com/TouK/nussknacker/pull/6750) Add varargs to `#COLLECTION.concat` and `#COLLECTION.merge`. * [#6778](https://github.com/TouK/nussknacker/pull/6778) SpeL: check for methods if a property for a given name does not exist. * [#6769](https://github.com/TouK/nussknacker/pull/6769) Added possibility to choose presets and define lists for Long typed parameter inputs in fragments. @@ -103,6 +103,7 @@ * [#7182](https://github.com/TouK/nussknacker/pull/7182) Provide an unique validation message to the scenario labels * [#7178](https://github.com/TouK/nussknacker/pull/7178) Remove autocompletion from markdown editors * [#7159](https://github.com/TouK/nussknacker/pull/7159) Fix running scenario tests with provided fragment input validation +* [#7187](https://github.com/TouK/nussknacker/pull/7187) Fix "Failed to get node validation" when using literal lists that mixes different types of elements ## 1.17 @@ -645,7 +646,7 @@ * [#4254](https://github.com/TouK/nussknacker/pull/4254) Add simple spel expression suggestions endpoint to BE * [#4323](https://github.com/TouK/nussknacker/pull/4323) Improved code suggestions with Typer * [#4406](https://github.com/TouK/nussknacker/pull/4406) `backendCodeSuggestions` set to `true`, so by default Nussknacker will use new suggestion mechanism -* [#4299](https://github.com/TouK/nussknacker/pull/4299)[4322](https://github.com/TouK/nussknacker/pull/4322) `StateStatus` is identified by its name. +* [#4299](https://github.com/TouK/nussknacker/pull/4299)[#4322](https://github.com/TouK/nussknacker/pull/4322) `StateStatus` is identified by its name. `ProcessState` serialization uses this name as serialized state value. For compatibility reasons, it is still represented as a nested object with one `name` field. * [#4312](https://github.com/TouK/nussknacker/pull/4312) Fix for losing unsaved changes in designer after cancel/deploy * [#4332](https://github.com/TouK/nussknacker/pull/4332) Improvements: Don't fetch state for fragments at /api/processes/status @@ -901,7 +902,7 @@ * [#3264](https://github.com/TouK/nussknacker/pull/3264) Added support for generic functions * [#3253](https://github.com/TouK/nussknacker/pull/3253) Separate validation step during scenario deployment * [#3328](https://github.com/TouK/nussknacker/pull/3328) Schema type aware serialization of `NkSerializableParsedSchema` -* [#3071](https://github.com/TouK/nussknacker/pull/3071) [3379](https://github.com/TouK/nussknacker/pull/3379) More strict Avro schema validation: include optional fields validation, +* [#3071](https://github.com/TouK/nussknacker/pull/3071) [#3379](https://github.com/TouK/nussknacker/pull/3379) More strict Avro schema validation: include optional fields validation, handling some invalid cases like putting long to int field, strict union types validation, reduced number of validation modes to lax | strict. * [#3289](https://github.com/TouK/nussknacker/pull/3289) Handle asynchronous deployment and status checks better * [#3071](https://github.com/TouK/nussknacker/pull/3334) Improvements: Allow to import file with different id diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index 24ae0dbbc3d..305f7dabe8e 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -6,11 +6,11 @@ To see the biggest differences please consult the [changelog](Changelog.md). ### Configuration changes -* [6944](https://github.com/TouK/nussknacker/pull/6944) +* [#6944](https://github.com/TouK/nussknacker/pull/6944) * Button name for 'test adhoc' was renamed from `test-with-form` to `adhoc-testing` If you are using custom button config remember to update button type to `type: "adhoc-testing"` in `processToolbarConfig` -* [7039](https://github.com/TouK/nussknacker/pull/7039) +* [#7039](https://github.com/TouK/nussknacker/pull/7039) * Scenario Activity audit log is available * logger name, `scenario-activity-audit`, it is optional, does not have to be configured * it uses MDC context, example of configuration in `logback.xml`: @@ -26,6 +26,8 @@ To see the biggest differences please consult the [changelog](Changelog.md). ``` +* [#6979](https://github.com/TouK/nussknacker/pull/6979) Add `type: "activities-panel"` to the `processToolbarConfig` which replaces removed `{ type: "versions-panel" }` `{ type: "comments-panel" }` and `{ type: "attachments-panel" }` + ### Code API changes * [#6971](https://github.com/TouK/nussknacker/pull/6971) `DeploymentManagerDependencies` API changes: @@ -47,7 +49,7 @@ To see the biggest differences please consult the [changelog](Changelog.md). ### REST API changes -* [6944](https://github.com/TouK/nussknacker/pull/6944) +* [#6944](https://github.com/TouK/nussknacker/pull/6944) * New endpoint `/api/scenarioTesting/{scenarioName}/adhoc/validate` * [#6766](https://github.com/TouK/nussknacker/pull/6766) * Process API changes: @@ -93,8 +95,10 @@ To see the biggest differences please consult the [changelog](Changelog.md). * [#7113](https://github.com/TouK/nussknacker/pull/7113) Scala 2.13 was updated to 2.13.15, you should update your `flink-scala-2.13` to 1.1.2 -### Configuration changes -* [#6979](https://github.com/TouK/nussknacker/pull/6979) Add `type: "activities-panel"` to the `processToolbarConfig` which replaces removed `{ type: "versions-panel" }` `{ type: "comments-panel" }` and `{ type: "attachments-panel" }` +* [#7187](https://github.com/TouK/nussknacker/pull/7187) JSON decoding in `request` source (request-response processing mode) + and in `kafka` source (streaming processing mode): For small decimal numbers is used either `Integer` or `Long` (depending on number size) + instead of `BigDecimal`. This change should be transparent in most cases as this value was mostly used after `#CONV.toNumber()` invocation + which still will return a `Number`. ## In version 1.17.0 @@ -103,7 +107,7 @@ To see the biggest differences please consult the [changelog](Changelog.md). * [#6248](https://github.com/TouK/nussknacker/pull/6248) Removed implicit conversion from string to SpeL expression (`pl.touk.nussknacker.engine.spel.Implicits`). The conversion should be replaced by `pl.touk.nussknacker.engine.spel.SpelExtension.SpelExpresion.spel`. -* [6282](https://github.com/TouK/nussknacker/pull/6184) If you relied on the default value of the `topicsExistenceValidationConfig.enabled` +* [#6282](https://github.com/TouK/nussknacker/pull/6184) If you relied on the default value of the `topicsExistenceValidationConfig.enabled` setting, you must now be aware that topics will be validated by default (Kafka's `auto.create.topics.enable` setting is only considered in case of Sinks). Create proper topics manually if needed. * Component's API changes @@ -1088,7 +1092,7 @@ To see the biggest differences please consult the [changelog](Changelog.md). * Some methods from API classes (e.g. `Parameter.validate`) and classes (`InterpretationResult`) moved to interpreter * `DeploymentManagerProvider.createDeploymentManager` takes now `BaseModelData` as an argument instead of `ModelData`. If you want to use this data to invoke scenario, you should cast it to invokable representation via: `import ModelData._; modelData.asInvokableModelData` -* [#2878](https://github.com/TouK/nussknacker/pull/2878) [2898](https://github.com/TouK/nussknacker/pull/2898) [#2924](https://github.com/TouK/nussknacker/pull/2924) Cleaning up of `-utils` modules +* [#2878](https://github.com/TouK/nussknacker/pull/2878) [#2898](https://github.com/TouK/nussknacker/pull/2898) [#2924](https://github.com/TouK/nussknacker/pull/2924) Cleaning up of `-utils` modules * Extracted internal classes, not intended to be used in extensions to nussknacker-internal-utils module * Extracted component classes, not used directly by runtime/designer to nussknacker-components-utils module * Extracted kafka component classes, not used directly by lite-kafka-runtime/kafka-test-utils to nussknacker-kafka-components-utils @@ -1307,7 +1311,7 @@ may cause __runtime__ consequences - make sure your custom services/listeners in * [#2501](https://github.com/TouK/nussknacker/pull/2501) `nussknacker-baseengine-components` module renamed to `nussknacker-lite-base-components` * [#2221](https://github.com/TouK/nussknacker/pull/2221) ReflectUtils `fixedClassSimpleNameWithoutParentModule` renamed to `simpleNameWithoutSuffix` * [#2495](https://github.com/TouK/nussknacker/pull/2495) TypeSpecificDataInitializer trait change to TypeSpecificDataInitializ -* [2245](https://github.com/TouK/nussknacker/pull/2245) `FailedEvent` has been specified in `FailedOnDeployEvent` and `FailedOnRunEvent` +* [#2245](https://github.com/TouK/nussknacker/pull/2245) `FailedEvent` has been specified in `FailedOnDeployEvent` and `FailedOnRunEvent` ## In version 1.0.0 * [#1439](https://github.com/TouK/nussknacker/pull/1439) [#2090](https://github.com/TouK/nussknacker/pull/2090) Upgrade do Flink 1.13. diff --git a/engine/lite/components/request-response/src/main/scala/pl/touk/nussknacker/engine/lite/components/requestresponse/jsonschema/sources/JsonSchemaRequestResponseSource.scala b/engine/lite/components/request-response/src/main/scala/pl/touk/nussknacker/engine/lite/components/requestresponse/jsonschema/sources/JsonSchemaRequestResponseSource.scala index c937e3c0a42..c12d347e4f6 100644 --- a/engine/lite/components/request-response/src/main/scala/pl/touk/nussknacker/engine/lite/components/requestresponse/jsonschema/sources/JsonSchemaRequestResponseSource.scala +++ b/engine/lite/components/request-response/src/main/scala/pl/touk/nussknacker/engine/lite/components/requestresponse/jsonschema/sources/JsonSchemaRequestResponseSource.scala @@ -13,7 +13,7 @@ import pl.touk.nussknacker.engine.api.{CirceUtil, MetaData, NodeId} import pl.touk.nussknacker.engine.json.{JsonSchemaBasedParameter, SwaggerBasedJsonSchemaTypeDefinitionExtractor} import pl.touk.nussknacker.engine.json.serde.CirceJsonDeserializer import pl.touk.nussknacker.engine.json.swagger.SwaggerTyped -import pl.touk.nussknacker.engine.json.swagger.extractor.FromJsonDecoder +import pl.touk.nussknacker.engine.json.swagger.decode.FromJsonSchemaBasedDecoder import pl.touk.nussknacker.engine.lite.components.requestresponse.jsonschema.sinks.JsonRequestResponseSink.SinkRawValueParamName import pl.touk.nussknacker.engine.requestresponse.api.openapi.OpenApiSourceDefinition import pl.touk.nussknacker.engine.requestresponse.api.{RequestResponsePostSource, ResponseEncoder} @@ -94,7 +94,7 @@ class JsonSchemaRequestResponseSource( val json = ToJsonEncoder.defaultForTests.encode(paramValue) val schema = getFirstMatchingSchemaForJson(cs, json) val swaggerTyped: SwaggerTyped = SwaggerBasedJsonSchemaTypeDefinitionExtractor.swaggerType(schema) - FromJsonDecoder.decode(json, swaggerTyped) + FromJsonSchemaBasedDecoder.decode(json, swaggerTyped) } .getOrElse { throw new IllegalArgumentException( // Should never happen since CombinedSchema is created using SinkRawValueParamName but still... @@ -105,7 +105,7 @@ class JsonSchemaRequestResponseSource( case _ => val json = ToJsonEncoder.defaultForTests.encode(TestingParametersSupport.unflattenParameters(params)) val swaggerTyped: SwaggerTyped = SwaggerBasedJsonSchemaTypeDefinitionExtractor.swaggerType(inputSchema) - FromJsonDecoder.decode(json, swaggerTyped) + FromJsonSchemaBasedDecoder.decode(json, swaggerTyped) } } diff --git a/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/conversion.scala b/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/conversion.scala index ca259e6e15e..de8998f4cc9 100644 --- a/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/conversion.scala +++ b/utils/default-helpers/src/main/scala/pl/touk/nussknacker/engine/util/functions/conversion.scala @@ -2,9 +2,10 @@ package pl.touk.nussknacker.engine.util.functions import com.github.ghik.silencer.silent import pl.touk.nussknacker.engine.api.generics.GenericType +import pl.touk.nussknacker.engine.api.json.FromJsonDecoder import pl.touk.nussknacker.engine.api.{Documentation, HideToString, ParamName} import pl.touk.nussknacker.engine.util.functions.NumericUtils.ToNumberTypingFunction -import pl.touk.nussknacker.engine.util.json.{JsonUtils, ToJsonEncoder} +import pl.touk.nussknacker.engine.util.json.ToJsonEncoder object conversion extends ConversionUtils @@ -41,7 +42,7 @@ trait ConversionUtils extends HideToString { private def toJsonEither(value: String): Either[Throwable, Any] = { io.circe.parser.parse(value) match { - case Right(json) => Right(JsonUtils.jsonToAny(json)) + case Right(json) => Right(FromJsonDecoder.jsonToAny(json)) case Left(ex) => Left(new IllegalArgumentException(s"Cannot convert [$value] to JSON", ex)) } } diff --git a/utils/default-helpers/src/test/scala/pl/touk/nussknacker/engine/util/functions/ConversionUtilsSpec.scala b/utils/default-helpers/src/test/scala/pl/touk/nussknacker/engine/util/functions/ConversionUtilsSpec.scala index 56dc8e237ce..b4c9f4ce5ff 100644 --- a/utils/default-helpers/src/test/scala/pl/touk/nussknacker/engine/util/functions/ConversionUtilsSpec.scala +++ b/utils/default-helpers/src/test/scala/pl/touk/nussknacker/engine/util/functions/ConversionUtilsSpec.scala @@ -16,17 +16,18 @@ class ConversionUtilsSpec extends AnyFunSuite with BaseSpelSpec with Matchers { ("expression", "expected"), ("#CONV.toJson('null')", null), ("#CONV.toJson('\"str\"')", "str"), - ("#CONV.toJson('1')", JBigDecimal.valueOf(1)), + ("#CONV.toJson('1')", 1), + ("#CONV.toJson('1.234')", JBigDecimal.valueOf(1.234)), ("#CONV.toJson('true')", true), ("#CONV.toJson('[]')", JList.of()), ("#CONV.toJson('[{}]')", JList.of[JMap[Nothing, Nothing]](JMap.of())), - ("#CONV.toJson('[1, \"str\", true]')", JList.of(JBigDecimal.valueOf(1), "str", true)), + ("#CONV.toJson('[1, \"str\", true]')", JList.of(1, "str", true)), ("#CONV.toJson('{}')", JMap.of()), ( "#CONV.toJson('{ \"a\": 1, \"b\": true, \"c\": \"str\", \"d\": [], \"e\": {} }')", JMap.of( "a", - JBigDecimal.valueOf(1), + 1, "b", true, "c", diff --git a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializer.scala b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializer.scala index 182ad9f2ddf..940cca1dcf3 100644 --- a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializer.scala +++ b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializer.scala @@ -5,7 +5,7 @@ import org.json.JSONTokener import pl.touk.nussknacker.engine.api.typed.CustomNodeValidationException import pl.touk.nussknacker.engine.json.SwaggerBasedJsonSchemaTypeDefinitionExtractor import pl.touk.nussknacker.engine.json.swagger.SwaggerTyped -import pl.touk.nussknacker.engine.json.swagger.extractor.FromJsonDecoder +import pl.touk.nussknacker.engine.json.swagger.decode.FromJsonSchemaBasedDecoder import pl.touk.nussknacker.engine.util.json.JsonSchemaUtils import java.nio.charset.StandardCharsets @@ -36,7 +36,7 @@ class CirceJsonDeserializer(jsonSchema: Schema) { .valueOr(errorMsg => throw CustomNodeValidationException(errorMsg, None)) val circeJson = JsonSchemaUtils.jsonToCirce(validatedJson) - val struct = FromJsonDecoder.decode(circeJson, swaggerTyped) + val struct = FromJsonSchemaBasedDecoder.decode(circeJson, swaggerTyped) struct } diff --git a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/SwaggerTyped.scala b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/SwaggerTyped.scala index cc876093962..8c8803d4a46 100644 --- a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/SwaggerTyped.scala +++ b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/SwaggerTyped.scala @@ -10,7 +10,7 @@ import io.swagger.v3.oas.models.media.{ArraySchema, MapSchema, ObjectSchema, Sch import pl.touk.nussknacker.engine.api.typed.typing._ import pl.touk.nussknacker.engine.json.swagger.parser.{PropertyName, SwaggerRefSchemas} import pl.touk.nussknacker.engine.util.Implicits.RichScalaMap -import pl.touk.nussknacker.engine.util.json.JsonUtils.jsonToAny +import pl.touk.nussknacker.engine.api.json.FromJsonDecoder.jsonToAny import pl.touk.nussknacker.engine.util.json.ToJsonEncoder import java.time.{LocalDate, LocalTime, ZonedDateTime} diff --git a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/extractor/FromJsonDecoder.scala b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/decode/FromJsonSchemaBasedDecoder.scala similarity index 83% rename from utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/extractor/FromJsonDecoder.scala rename to utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/decode/FromJsonSchemaBasedDecoder.scala index 38eff47a380..0b241b40564 100644 --- a/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/extractor/FromJsonDecoder.scala +++ b/utils/json-utils/src/main/scala/pl/touk/nussknacker/engine/json/swagger/decode/FromJsonSchemaBasedDecoder.scala @@ -1,16 +1,16 @@ -package pl.touk.nussknacker.engine.json.swagger.extractor +package pl.touk.nussknacker.engine.json.swagger.decode import io.circe.{Json, JsonNumber, JsonObject} +import pl.touk.nussknacker.engine.api.json.FromJsonDecoder import pl.touk.nussknacker.engine.api.typed.TypedMap import pl.touk.nussknacker.engine.json.swagger._ -import pl.touk.nussknacker.engine.util.json.JsonUtils.jsonToAny import java.time.format.DateTimeFormatter import java.time.{LocalDate, OffsetTime, ZonedDateTime} import scala.util.Try // TODO: Validated -object FromJsonDecoder { +object FromJsonSchemaBasedDecoder { import scala.jdk.CollectionConverters._ @@ -41,16 +41,16 @@ object FromJsonDecoder { TypedMap( jo.toMap.collect { case (key, value) if obj.elementType.contains(key) => - key -> FromJsonDecoder.decode(value, obj.elementType(key), addPath(key)) + key -> FromJsonSchemaBasedDecoder.decode(value, obj.elementType(key), addPath(key)) case keyValue @ KeyMatchingPatternSchema(patternPropertySchema) => val (key, value) = keyValue - key -> FromJsonDecoder.decode(value, patternPropertySchema, addPath(key)) + key -> FromJsonSchemaBasedDecoder.decode(value, patternPropertySchema, addPath(key)) case (key, value) if obj.additionalProperties != AdditionalPropertiesDisabled => obj.additionalProperties match { case add: AdditionalPropertiesEnabled => - key -> FromJsonDecoder.decode(value, add.value, addPath(key)) + key -> FromJsonSchemaBasedDecoder.decode(value, add.value, addPath(key)) case _ => - key -> jsonToAny(value) + key -> FromJsonDecoder.jsonToAny(value) } } ) @@ -65,7 +65,7 @@ object FromJsonDecoder { case SwaggerString => extract(_.asString) case SwaggerEnum(_) => - extract[AnyRef](j => Option(jsonToAny(j).asInstanceOf[AnyRef])) + extract[AnyRef](j => Option(FromJsonDecoder.jsonToAny(j).asInstanceOf[AnyRef])) case SwaggerBool => extract(_.asBoolean, boolean2Boolean) case SwaggerInteger => @@ -88,7 +88,9 @@ object FromJsonDecoder { case SwaggerArray(elementType) => extract[Vector[Json]]( _.asArray, - _.zipWithIndex.map { case (el, idx) => FromJsonDecoder.decode(el, elementType, s"$path[$idx]") }.asJava + _.zipWithIndex + .map { case (el, idx) => FromJsonSchemaBasedDecoder.decode(el, elementType, s"$path[$idx]") } + .asJava ) case obj: SwaggerObject => extractObject(obj) case u @ SwaggerUnion(types) => @@ -96,7 +98,7 @@ object FromJsonDecoder { .flatMap(aType => Try(decode(json, aType)).toOption) .headOption .getOrElse(throw JsonToObjectError(json, u, path)) - case SwaggerAny => extract[AnyRef](j => Option(jsonToAny(j).asInstanceOf[AnyRef])) + case SwaggerAny => extract[AnyRef](j => Option(FromJsonDecoder.jsonToAny(j).asInstanceOf[AnyRef])) // should not happen as we handle null above case SwaggerNull => throw JsonToObjectError(json, definition, path) } diff --git a/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/SwaggerBasedJsonSchemaTypeDefinitionExtractorTest.scala b/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/SwaggerBasedJsonSchemaTypeDefinitionExtractorTest.scala index 09acfb8b62a..a9aaa197edf 100644 --- a/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/SwaggerBasedJsonSchemaTypeDefinitionExtractorTest.scala +++ b/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/SwaggerBasedJsonSchemaTypeDefinitionExtractorTest.scala @@ -7,7 +7,7 @@ import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper import org.scalatest.prop.TableDrivenPropertyChecks import pl.touk.nussknacker.engine.api.typed.TypedMap import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypedObjectTypingResult, TypingResult, Unknown} -import pl.touk.nussknacker.engine.json.swagger.extractor.FromJsonDecoder +import pl.touk.nussknacker.engine.json.swagger.decode.FromJsonSchemaBasedDecoder import pl.touk.nussknacker.engine.json.swagger._ class SwaggerBasedJsonSchemaTypeDefinitionExtractorTest extends AnyFunSuite with TableDrivenPropertyChecks { @@ -304,7 +304,7 @@ class SwaggerBasedJsonSchemaTypeDefinitionExtractorTest extends AnyFunSuite with val jsonObject = Json.obj("time" -> fromString("2022-07-11T18:12:27+02:00")) val swaggerObject = new SwaggerObject(elementType = Map("time" -> SwaggerDateTime), AdditionalPropertiesDisabled) - val jsonToObjectExtracted = FromJsonDecoder.decode(jsonObject, swaggerObject) + val jsonToObjectExtracted = FromJsonSchemaBasedDecoder.decode(jsonObject, swaggerObject) swaggerTypeExtracted.asInstanceOf[TypedObjectTypingResult].fields("time") shouldBe Typed.fromInstance(jsonToObjectExtracted.asInstanceOf[TypedMap].get("time")) diff --git a/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializerSpec.scala b/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializerSpec.scala index 6374f1383af..8cf95710325 100644 --- a/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializerSpec.scala +++ b/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/serde/CirceJsonDeserializerSpec.scala @@ -284,7 +284,7 @@ class CirceJsonDeserializerSpec extends AnyFunSuite with ValidatedValuesDetailed |}""".stripMargin) result shouldEqual Map( - "additionalInt" -> java.math.BigDecimal.valueOf(1234), + "additionalInt" -> 1234, "additionalString" -> "foo", "additionalObject" -> Map("foo" -> "bar").asJava ).asJava diff --git a/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/extractor/FromJsonDecoderTest.scala b/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/decode/FromJsonSchemaBasedDecoderTest.scala similarity index 84% rename from utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/extractor/FromJsonDecoderTest.scala rename to utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/decode/FromJsonSchemaBasedDecoderTest.scala index ba8a497bb43..6f7ae4301f0 100644 --- a/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/extractor/FromJsonDecoderTest.scala +++ b/utils/json-utils/src/test/scala/pl/touk/nussknacker/engine/json/swagger/decode/FromJsonSchemaBasedDecoderTest.scala @@ -1,4 +1,4 @@ -package pl.touk.nussknacker.engine.json.swagger.extractor +package pl.touk.nussknacker.engine.json.swagger.decode import io.circe.Json import io.circe.Json.{fromBoolean, fromInt, fromLong, fromString, fromValues} @@ -6,12 +6,12 @@ import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import pl.touk.nussknacker.engine.api.typed.TypedMap import pl.touk.nussknacker.engine.json.swagger._ -import pl.touk.nussknacker.engine.json.swagger.extractor.FromJsonDecoder.JsonToObjectError +import pl.touk.nussknacker.engine.json.swagger.decode.FromJsonSchemaBasedDecoder.JsonToObjectError import java.time.format.DateTimeFormatter import java.time.{LocalDate, OffsetTime, ZoneOffset, ZonedDateTime} -class FromJsonDecoderTest extends AnyFunSuite with Matchers { +class FromJsonSchemaBasedDecoderTest extends AnyFunSuite with Matchers { private val json = Json.obj( "field1" -> fromString("value"), @@ -46,7 +46,7 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { AdditionalPropertiesDisabled ) - val value = FromJsonDecoder.decode(json, definition) + val value = FromJsonSchemaBasedDecoder.decode(json, definition) value shouldBe a[TypedMap] val fields = value.asInstanceOf[TypedMap] @@ -62,7 +62,7 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { fields.get("nullField").asInstanceOf[AnyRef] shouldBe null val mapField = fields.get("mapField").asInstanceOf[TypedMap] mapField.get("a") shouldBe "1" - mapField.get("b") shouldBe java.math.BigDecimal.valueOf(2) + mapField.get("b") shouldBe 2 mapField.get("c") shouldBe a[java.util.List[_]] fields.get("mapOfStringsField") shouldBe a[TypedMap] } @@ -73,7 +73,7 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { AdditionalPropertiesDisabled ) - val ex = intercept[JsonToObjectError](FromJsonDecoder.decode(json, definition)) + val ex = intercept[JsonToObjectError](FromJsonSchemaBasedDecoder.decode(json, definition)) ex.getMessage shouldBe "JSON returned by service has invalid type at mapField.b. Expected: SwaggerString. Returned json: 2" ex.path shouldBe "mapField.b" @@ -82,27 +82,27 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { test("should skip additionalFields when schema/SwaggerObject does not allow them") { val definitionWithoutFields = SwaggerObject(elementType = Map("field3" -> SwaggerLong), AdditionalPropertiesDisabled) - extractor.FromJsonDecoder.decode(json, definitionWithoutFields) shouldBe TypedMap(Map.empty) + decode.FromJsonSchemaBasedDecoder.decode(json, definitionWithoutFields) shouldBe TypedMap(Map.empty) val definitionWithOneField = SwaggerObject(elementType = Map("field2" -> SwaggerLong), AdditionalPropertiesDisabled) - extractor.FromJsonDecoder.decode(json, definitionWithOneField) shouldBe TypedMap(Map("field2" -> 1L)) + decode.FromJsonSchemaBasedDecoder.decode(json, definitionWithOneField) shouldBe TypedMap(Map("field2" -> 1L)) } test("should not trim additional fields fields when additionalPropertiesOn") { val json = Json.obj("field1" -> fromString("value"), "field2" -> Json.fromInt(1)) val definition = SwaggerObject(elementType = Map("field3" -> SwaggerLong)) - extractor.FromJsonDecoder.decode(json, definition) shouldBe TypedMap( + decode.FromJsonSchemaBasedDecoder.decode(json, definition) shouldBe TypedMap( Map( "field1" -> "value", - "field2" -> java.math.BigDecimal.valueOf(1) + "field2" -> 1 ) ) val jsonIntegers = Json.obj("field1" -> fromInt(2), "field2" -> fromInt(1)) val definition2 = SwaggerObject(elementType = Map("field3" -> SwaggerLong), AdditionalPropertiesEnabled(SwaggerLong)) - extractor.FromJsonDecoder.decode(jsonIntegers, definition2) shouldBe TypedMap( + decode.FromJsonSchemaBasedDecoder.decode(jsonIntegers, definition2) shouldBe TypedMap( Map( "field1" -> 2L, "field2" -> 1L @@ -115,7 +115,7 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { val definition = SwaggerObject(elementType = Map("field3" -> SwaggerLong), AdditionalPropertiesEnabled(SwaggerLong)) val ex = intercept[JsonToObjectError] { - extractor.FromJsonDecoder.decode(json, definition) + decode.FromJsonSchemaBasedDecoder.decode(json, definition) } ex.getMessage shouldBe """JSON returned by service has invalid type at field1. Expected: SwaggerLong. Returned json: "value"""" @@ -125,7 +125,7 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { val definition = SwaggerObject(elementType = Map("field2" -> SwaggerUnion(List(SwaggerString, SwaggerLong)))) - val value = FromJsonDecoder.decode(json, definition) + val value = FromJsonSchemaBasedDecoder.decode(json, definition) value shouldBe a[TypedMap] val fields = value.asInstanceOf[TypedMap] @@ -143,7 +143,7 @@ class FromJsonDecoderTest extends AnyFunSuite with Matchers { ) def assertPath(json: Json, path: String) = - intercept[JsonToObjectError](FromJsonDecoder.decode(json, definition)).path shouldBe path + intercept[JsonToObjectError](FromJsonSchemaBasedDecoder.decode(json, definition)).path shouldBe path assertPath(Json.obj("string" -> fromLong(1)), "string") assertPath( diff --git a/utils/utils/src/main/scala/pl/touk/nussknacker/engine/util/json/JsonUtils.scala b/utils/utils/src/main/scala/pl/touk/nussknacker/engine/util/json/JsonUtils.scala deleted file mode 100644 index bbf9f401084..00000000000 --- a/utils/utils/src/main/scala/pl/touk/nussknacker/engine/util/json/JsonUtils.scala +++ /dev/null @@ -1,18 +0,0 @@ -package pl.touk.nussknacker.engine.util.json - -import io.circe.Json -import pl.touk.nussknacker.engine.util.Implicits._ -import scala.jdk.CollectionConverters._ - -object JsonUtils { - - def jsonToAny(json: Json): Any = json.fold( - jsonNull = null, - jsonBoolean = identity[Boolean], - jsonNumber = _.toBigDecimal.map(_.bigDecimal).orNull, // we need here java BigDecimal type - jsonString = identity[String], - jsonArray = _.map(jsonToAny).asJava, - jsonObject = _.toMap.mapValuesNow(jsonToAny).asJava - ) - -} From 7aa80a89afa9f88e3dd28fe96e5d5dfc22ac2d8e Mon Sep 17 00:00:00 2001 From: Dawid Poliszak Date: Thu, 21 Nov 2024 14:03:59 +0100 Subject: [PATCH 10/14] [Nu-1892] add missing tooltips (#7193) * NU-1892 add missing tooltips --- .../toolbars/scenarioDetails/CategoryDetails.tsx | 8 +++++++- .../scenarioDetails/ScenarioDetailsComponents.tsx | 5 +++++ .../toolbars/scenarioDetails/ScenarioLabels.tsx | 5 +++++ .../packages/components/src/common/categoryChip.tsx | 3 +++ .../packages/components/src/common/labelChip.tsx | 3 +++ .../components/src/scenarios/list/processingMode.tsx | 3 +++ .../components/src/scenarios/list/scenarioAvatar.tsx | 7 ++++++- .../components/src/scenarios/list/scenarioStatus.tsx | 1 + .../components/src/scenarios/list/tableCellAvatar.tsx | 3 ++- docs/Changelog.md | 1 + 10 files changed, 36 insertions(+), 3 deletions(-) diff --git a/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx b/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx index 60b2d1b3b3b..07be4f6473d 100644 --- a/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx +++ b/designer/client/src/components/toolbars/scenarioDetails/CategoryDetails.tsx @@ -3,8 +3,10 @@ import { useProcessFormDataOptions } from "../../useProcessFormDataOptions"; import HttpService, { ScenarioParametersCombination } from "../../../http/HttpService"; import { Skeleton, Typography } from "@mui/material"; import { Scenario } from "../../Process/types"; +import { useTranslation } from "react-i18next"; export const CategoryDetails = ({ scenario }: { scenario: Scenario }) => { + const { t } = useTranslation(); const [allCombinations, setAllCombinations] = useState([]); const [isAllCombinationsLoading, setIsAllCombinationsLoading] = useState(false); @@ -33,7 +35,11 @@ export const CategoryDetails = ({ scenario }: { scenario: Scenario }) => { {isAllCombinationsLoading ? ( ) : ( - isCategoryFieldVisible && {scenario.processCategory} / + isCategoryFieldVisible && ( + + {scenario.processCategory} / + + ) )} ); diff --git a/designer/client/src/components/toolbars/scenarioDetails/ScenarioDetailsComponents.tsx b/designer/client/src/components/toolbars/scenarioDetails/ScenarioDetailsComponents.tsx index 21810a99560..1693c49d114 100644 --- a/designer/client/src/components/toolbars/scenarioDetails/ScenarioDetailsComponents.tsx +++ b/designer/client/src/components/toolbars/scenarioDetails/ScenarioDetailsComponents.tsx @@ -1,4 +1,5 @@ import { css, styled, Typography } from "@mui/material"; +import i18next from "i18next"; export const PanelScenarioDetails = styled("div")( ({ theme }) => css` @@ -26,6 +27,10 @@ export const ScenarioDetailsItemWrapper = styled("div")( export const ProcessName = styled(Typography)``; +ProcessName.defaultProps = { + title: i18next.t("panels.scenarioDetails.tooltip.name", "Name"), +}; + export const ProcessRename = styled(ProcessName)(({ theme }) => ({ color: theme.palette.warning.main, })); diff --git a/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx b/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx index 06b5c7d5d50..6126f30b30c 100644 --- a/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx +++ b/designer/client/src/components/toolbars/scenarioDetails/ScenarioLabels.tsx @@ -21,6 +21,7 @@ import i18next from "i18next"; import { editScenarioLabels } from "../../../actions/nk"; import { debounce } from "lodash"; import { ScenarioLabelValidationError } from "../../Labels/types"; +import { useTranslation } from "react-i18next"; interface AddLabelProps { onClick: () => void; @@ -107,6 +108,7 @@ interface Props { } export const ScenarioLabels = ({ readOnly }: Props) => { + const { t } = useTranslation(); const scenarioLabels = useSelector(getScenarioLabels); const scenarioLabelOptions: LabelOption[] = useMemo(() => scenarioLabels.map(toLabelOption), [scenarioLabels]); const initialScenarioLabelOptionsErrors = useSelector(getScenarioLabelsErrors).filter((error) => @@ -352,6 +354,9 @@ export const ScenarioLabels = ({ readOnly }: Props) => { const labelError = labelOptionsErrors.find((error) => error.label === toLabelValue(option)); return ( filterValues.includes(category), [filterValues, category]); const onClick = useCallback( @@ -36,6 +38,7 @@ export function CategoryButton({ category, filterValues, setFilter }: Props): JS return ( filterValue.includes(value), [filterValue, value]); const onClick = useCallback( @@ -26,6 +28,7 @@ export function LabelChip({ id, value, filterValue, setFilter }: Props): JSX.Ele return ( ; } export const ProcessingModeItem = ({ processingMode, filtersContext }: Props) => { + const { t } = useTranslation(); const { setFilter, getFilter } = filtersContext; const filterValue = useMemo(() => getFilter("PROCESSING_MODE", true), [getFilter]); @@ -58,6 +60,7 @@ export const ProcessingModeItem = ({ processingMode, filtersContext }: Props) => return (