diff --git a/build.sbt b/build.sbt index fbaf1a8e2fa..e0ad0a586d5 100644 --- a/build.sbt +++ b/build.sbt @@ -811,7 +811,16 @@ lazy val flinkExecutor = (project in flink("executor")) ) }.toList, ) - .dependsOn(flinkComponentsUtils, scenarioCompiler, flinkExtensionsApi, flinkTestUtils % Test) + .dependsOn( + flinkComponentsUtils, + flinkExtensionsApi, + scenarioCompiler, + // Various components uses one of library in stack: sttp -> async-http-client -> netty + // Different versions of netty which is on the bottom of this stack causes NoClassDefFoundError. + // To overcome this problem and reduce size of model jar bundle, we add http utils as a compile time dependency. + httpUtils, + flinkTestUtils % Test + ) lazy val scenarioCompiler = (project in file("scenario-compiler")) .settings(commonSettings) @@ -1355,7 +1364,15 @@ lazy val liteEngineRuntime = (project in lite("runtime")) ) }, ) - .dependsOn(liteComponentsApi, scenarioCompiler, testUtils % Test) + .dependsOn( + liteComponentsApi, + scenarioCompiler, + // Various components uses one of library in stack: sttp -> async-http-client -> netty + // Different versions of netty which is on the bottom of this stack causes NoClassDefFoundError. + // To overcome this problem and reduce size of model jar bundle, we add http utils as a compile time dependency. + httpUtils, + testUtils % Test + ) lazy val liteEngineKafkaIntegrationTest: Project = (project in lite("integration-test")) .configs(IntegrationTest) @@ -1503,11 +1520,12 @@ lazy val liteK8sDeploymentManager = (project in lite("k8sDeploymentManager")) libraryDependencies ++= { Seq( // From version 4.0.0 onwards, skuber uses pekko instead of akka, so we need to migrate to pekko first - "io.github.hagay3" %% "skuber" % "3.2" exclude ("commons-logging", "commons-logging"), - "com.github.julien-truffaut" %% "monocle-core" % monocleV, - "com.github.julien-truffaut" %% "monocle-macro" % monocleV, - "com.typesafe.akka" %% "akka-slf4j" % akkaV % Test, - "org.wiremock" % "wiremock" % wireMockV % Test, + "io.github.hagay3" %% "skuber" % "3.2" exclude ("commons-logging", "commons-logging"), + "com.github.julien-truffaut" %% "monocle-core" % monocleV, + "com.github.julien-truffaut" %% "monocle-macro" % monocleV, + "com.typesafe.akka" %% "akka-slf4j" % akkaV % Test, + "org.wiremock" % "wiremock" % wireMockV % Test, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-future" % sttpV % Test, ) }, buildAndImportRuntimeImageToK3d := { @@ -1541,20 +1559,20 @@ lazy val componentsApi = (project in file("components-api")) name := "nussknacker-components-api", libraryDependencies ++= { Seq( - "org.apache.commons" % "commons-text" % flinkCommonsTextV, - "org.typelevel" %% "cats-core" % catsV, - "com.beachape" %% "enumeratum" % enumeratumV, - "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingV, - "com.typesafe" % "config" % configV, - "org.semver4j" % "semver4j" % "5.4.0", - "javax.validation" % "validation-api" % javaxValidationApiV, - "org.scala-lang.modules" %% "scala-collection-compat" % scalaCollectionsCompatV, - "com.iheart" %% "ficus" % ficusV, - "org.springframework" % "spring-core" % springV, - "org.springframework" % "spring-expression" % springV % Test, - "com.google.code.findbugs" % "jsr305" % findBugsV, - "com.softwaremill.sttp.client3" %% "async-http-client-backend-future" % sttpV, - "org.scalatestplus" %% s"scalacheck-$scalaCheckVshort" % scalaTestPlusV % Test + "org.apache.commons" % "commons-text" % flinkCommonsTextV, + "org.typelevel" %% "cats-core" % catsV, + "com.beachape" %% "enumeratum" % enumeratumV, + "com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingV, + "com.typesafe" % "config" % configV, + "org.semver4j" % "semver4j" % "5.4.0", + "javax.validation" % "validation-api" % javaxValidationApiV, + "org.scala-lang.modules" %% "scala-collection-compat" % scalaCollectionsCompatV, + "com.iheart" %% "ficus" % ficusV, + "org.springframework" % "spring-core" % springV, + "org.springframework" % "spring-expression" % springV % Test, + "com.google.code.findbugs" % "jsr305" % findBugsV, + "com.softwaremill.sttp.client3" %% "core" % sttpV, + "org.scalatestplus" %% s"scalacheck-$scalaCheckVshort" % scalaTestPlusV % Test ) } ) @@ -1680,15 +1698,20 @@ lazy val processReports = (project in file("designer/processReports")) ) .dependsOn(httpUtils, commonUtils, testUtils % "it,test") +// This dependency is delivered by flink-executor and lite-runtime to ensure the same version of libraries in stack: +// sttp -> async-http-client -> netty. Different versions of netty in model classpath causes NoClassDefFoundError. +// Also, thanks to this approach we reduce size of model jar bundle. lazy val httpUtils = (project in utils("http-utils")) .settings(commonSettings) .settings( name := "nussknacker-http-utils", libraryDependencies ++= { Seq( - "com.softwaremill.sttp.client3" %% "core" % sttpV, - "com.softwaremill.sttp.client3" %% "json-common" % sttpV, - "com.softwaremill.sttp.client3" %% "circe" % sttpV, + "com.softwaremill.sttp.client3" %% "core" % sttpV, + "com.softwaremill.sttp.client3" %% "json-common" % sttpV, + "com.softwaremill.sttp.client3" %% "circe" % sttpV, + "com.softwaremill.sttp.client3" %% "async-http-client-backend-future" % sttpV, + "io.netty" % "netty-transport-native-epoll" % nettyV, ) } ) @@ -1706,19 +1729,18 @@ lazy val openapiComponents = (project in component("openapi")) .settings( name := "nussknacker-openapi", libraryDependencies ++= Seq( - "io.swagger.core.v3" % "swagger-integration" % swaggerIntegrationV excludeAll ( + "io.swagger.core.v3" % "swagger-integration" % swaggerIntegrationV excludeAll ( ExclusionRule(organization = "jakarta.activation"), ExclusionRule(organization = "jakarta.validation") ), - "io.netty" % "netty-transport-native-epoll" % nettyV, - "org.apache.flink" % "flink-streaming-java" % flinkV % Provided, - "org.scalatest" %% "scalatest" % scalaTestV % "it,test" + "org.apache.flink" % "flink-streaming-java" % flinkV % Provided, + "org.scalatest" %% "scalatest" % scalaTestV % "it,test" ), ) .dependsOn( componentsUtils % Provided, jsonUtils % Provided, - httpUtils, + httpUtils % Provided, requestResponseComponentsUtils % "it,test", flinkComponentsTestkit % "it,test" ) 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/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/AssignabilityDeterminer.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/AssignabilityDeterminer.scala new file mode 100644 index 00000000000..8e4cec7ee42 --- /dev/null +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/AssignabilityDeterminer.scala @@ -0,0 +1,251 @@ +package pl.touk.nussknacker.engine.api.typed + +import cats.data.Validated._ +import cats.data.{NonEmptyList, Validated, ValidatedNel} +import cats.implicits.{catsSyntaxValidatedId, _} +import org.apache.commons.lang3.ClassUtils +import pl.touk.nussknacker.engine.api.typed.typing._ + +/** + * This class determine whether we can assign one type to another type - that is if its the same class, a subclass or can be converted to another type. We provide two modes of conversion - + * 1. Loose conversion is based on the fact that TypingResults are + * sets of possible supertypes with some additional restrictions (like TypedObjectTypingResult). It is basically how SpEL + * can convert things. Like CommonSupertypeFinder it's in the spirit of "Be type safe as much as possible, but also provide some helpful + * conversion for types not in the same jvm class hierarchy like boxed Integer to boxed Long and so on". + * 2. Strict conversion checks whether we can convert to a wider type. Eg only widening numerical types + * are allowed ( Int -> Long). For other types it should work the same as a loose conversion. + * + */ +object AssignabilityDeterminer { + + private val javaMapClass = classOf[java.util.Map[_, _]] + private val javaListClass = classOf[java.util.List[_]] + private val arrayOfAnyRefClass = classOf[Array[AnyRef]] + + /** + * This method checks if `givenType` can by subclass of `superclassCandidate` + * It will return true if `givenType` is equals to `superclassCandidate` or `givenType` "extends" `superclassCandidate` + */ + def isAssignableLoose(from: TypingResult, to: TypingResult): ValidatedNel[String, Unit] = + isAssignable(from, to, LooseConversionChecker) + + def isAssignableStrict(from: TypingResult, to: TypingResult): ValidatedNel[String, Unit] = + isAssignable(from, to, StrictConversionChecker) + + private def isAssignable(from: TypingResult, to: TypingResult, conversionChecker: ConversionChecker) = { + (from, to) match { + case (_, Unknown) => ().validNel + case (Unknown, _) => ().validNel + case (TypedNull, other) => isNullAsignableTo(other) + case (_, TypedNull) => s"No type can be subclass of ${TypedNull.display}".invalidNel + case (given: SingleTypingResult, superclass: TypedUnion) => + isAnyOfAssignableToAnyOf(NonEmptyList.one(given), superclass.possibleTypes, conversionChecker) + case (given: TypedUnion, superclass: SingleTypingResult) => + isAnyOfAssignableToAnyOf(given.possibleTypes, NonEmptyList.one(superclass), conversionChecker) + case (given: SingleTypingResult, superclass: SingleTypingResult) => + isSingleAssignableToSingle(given, superclass, conversionChecker) + case (given: TypedUnion, superclass: TypedUnion) => + isAnyOfAssignableToAnyOf(given.possibleTypes, superclass.possibleTypes, conversionChecker) + } + } + + private def isNullAsignableTo(to: TypingResult): ValidatedNel[String, Unit] = to match { + // TODO: Null should not be subclass of typed map that has all values assigned. + case TypedObjectWithValue(_, _) => s"${TypedNull.display} cannot be subclass of type with value".invalidNel + case _ => ().validNel + } + + private def isSingleAssignableToSingle( + from: SingleTypingResult, + to: SingleTypingResult, + conversionChecker: ConversionChecker + ): ValidatedNel[String, Unit] = { + val objTypeRestriction = isSingleAssignableToTypedClass(from, to.runtimeObjType, conversionChecker) + val typedObjectRestrictions = (_: Unit) => + to match { + case superclass: TypedObjectTypingResult => + val givenTypeFields = from match { + case given: TypedObjectTypingResult => given.fields + case _ => Map.empty[String, TypingResult] + } + + superclass.fields.toList + .map { case (name, typ) => + givenTypeFields.get(name) match { + case None => + s"Field '$name' is lacking".invalidNel + case Some(givenFieldType) => + condNel( + isAssignable(givenFieldType, typ, conversionChecker).isValid, + (), + s"Field '$name' is of the wrong type. Expected: ${givenFieldType.display}, actual: ${typ.display}" + ) + } + } + .foldLeft(().validNel[String])(_.combine(_)) + case _ => + ().validNel + } + val dictRestriction = (_: Unit) => { + (from, to) match { + case (given: TypedDict, superclass: TypedDict) => + condNel( + given.dictId == superclass.dictId, + (), + "The type and the superclass candidate are Dicts with unequal IDs" + ) + case (_: TypedDict, _) => + "The type is a Dict but the superclass candidate not".invalidNel + case (_, _: TypedDict) => + "The superclass candidate is a Dict but the type not".invalidNel + case _ => + ().validNel + } + } + val taggedValueRestriction = (_: Unit) => { + (from, to) match { + case (givenTaggedValue: TypedTaggedValue, superclassTaggedValue: TypedTaggedValue) => + condNel( + givenTaggedValue.tag == superclassTaggedValue.tag, + (), + s"Tagged values have unequal tags: ${givenTaggedValue.tag} and ${superclassTaggedValue.tag}" + ) + case (_: TypedTaggedValue, _) => ().validNel + case (_, _: TypedTaggedValue) => + s"The type is not a tagged value".invalidNel + case _ => ().validNel + } + } + // Type like Integer can be subclass of Integer{5}, because Integer could + // possibly have value of 5, that would make it subclass of Integer{5}. + // This allows us to supply unknown Integer to function that requires + // Integer{5}. + val dataValueRestriction = (_: Unit) => { + (from, to) match { + case (TypedObjectWithValue(_, givenValue), TypedObjectWithValue(_, candidateValue)) + if givenValue == candidateValue => + ().validNel + case (TypedObjectWithValue(_, givenValue), TypedObjectWithValue(_, candidateValue)) => + s"Types with value have different values: $givenValue and $candidateValue".invalidNel + case _ => ().validNel + } + } + objTypeRestriction andThen + (typedObjectRestrictions combine dictRestriction combine taggedValueRestriction combine dataValueRestriction) + } + + private def isSingleAssignableToTypedClass( + from: SingleTypingResult, + to: TypedClass, + conversionChecker: ConversionChecker + ): ValidatedNel[String, Unit] = { + def typeParametersMatches(givenClass: TypedClass, superclassCandidate: TypedClass) = { + def canBeSubOrSuperclass(givenClassParam: TypingResult, superclassParam: TypingResult) = + condNel( + isAssignable(givenClassParam, superclassParam, conversionChecker).isValid || + isAssignable(superclassParam, givenClassParam, conversionChecker).isValid, + (), + f"None of ${givenClassParam.display} and ${superclassParam.display} is a subclass of another" + ) + + (givenClass, superclassCandidate) match { + case (TypedClass(_, givenElementParam :: Nil), TypedClass(superclass, superclassParam :: Nil)) + // Array are invariant but we have built-in conversion between array types - this check should be moved outside this class when we move away canBeConvertedTo as well + if javaListClass.isAssignableFrom(superclass) || arrayOfAnyRefClass.isAssignableFrom(superclass) => + isAssignable(givenElementParam, superclassParam, conversionChecker) + case ( + TypedClass(_, givenKeyParam :: givenValueParam :: Nil), + TypedClass(superclass, superclassKeyParam :: superclassValueParam :: Nil) + ) if javaMapClass.isAssignableFrom(superclass) => + // Map's key generic param is invariant. We can't just check givenKeyParam == superclassKeyParam because of Unknown type which is a kind of wildcard + condNel( + isAssignable(givenKeyParam, superclassKeyParam, conversionChecker).isValid && + isAssignable(superclassKeyParam, givenKeyParam, conversionChecker).isValid, + (), + s"Key types of Maps ${givenKeyParam.display} and ${superclassKeyParam.display} are not equals" + ) andThen (_ => isAssignable(givenValueParam, superclassValueParam, conversionChecker)) + case _ => + // for unknown types we are lax - the generic type may be co- contra- or in-variant - and we don't want to + // return validation errors in this case. It's better to accept to much than too little + condNel( + superclassCandidate.params.zip(givenClass.params).forall { case (superclassParam, givenClassParam) => + canBeSubOrSuperclass(givenClassParam, superclassParam).isValid + }, + (), + s"Wrong type parameters" + ) + } + } + val givenClass = from.runtimeObjType + + val equalClassesOrCanAssign = + condNel( + givenClass == to, + (), + f"${givenClass.display} and ${to.display} are not the same" + ) orElse + isAssignable(givenClass.klass, to.klass) + + val canBeSubclass = equalClassesOrCanAssign andThen (_ => typeParametersMatches(givenClass, to)) + canBeSubclass orElse conversionChecker.isConvertable(from, to) + } + + private def isAnyOfAssignableToAnyOf( + from: NonEmptyList[SingleTypingResult], + to: NonEmptyList[SingleTypingResult], + conversionChecker: ConversionChecker + ): ValidatedNel[String, Unit] = { + // Would be more safety to do givenTypes.forAll(... superclassCandidates.exists ...) - we wil protect against + // e.g. (String | Int).isAnyOfAssignableToAnyOf(String) which can fail in runtime for Int, but on the other hand we can't block user's intended action. + // He/she could be sure that in this type, only String will appear. He/she also can't easily downcast (String | Int) to String so leaving here + // "double exists" looks like a good tradeoff + condNel( + from.exists(given => to.exists(isSingleAssignableToSingle(given, _, conversionChecker).isValid)), + (), + s"""None of the following types: + |${from.map(" - " + _.display).toList.mkString(",\n")} + |can be a subclass of any of: + |${to.map(" - " + _.display).toList.mkString(",\n")}""".stripMargin + ) + } + + // we use explicit autoboxing = true flag, as ClassUtils in commons-lang3:3.3 (used in Flink) cannot handle JDK 11... + private def isAssignable(from: Class[_], to: Class[_]): ValidatedNel[String, Unit] = + condNel(ClassUtils.isAssignable(from, to, true), (), s"$to is not assignable from $from") + + // TODO: Conversions should be checked during typing, not during generic usage of TypingResult.canBeSubclassOf(...) + private sealed trait ConversionChecker { + + def isConvertable( + from: SingleTypingResult, + to: TypedClass + ): ValidatedNel[String, Unit] + + } + + private object StrictConversionChecker extends ConversionChecker { + + override def isConvertable( + from: SingleTypingResult, + to: TypedClass + ): ValidatedNel[String, Unit] = { + val errMsgPrefix = + s"${from.runtimeObjType.display} cannot be strictly converted to ${to.display}" + condNel(TypeConversionHandler.canBeStrictlyConvertedTo(from, to), (), errMsgPrefix) + } + + } + + private object LooseConversionChecker extends ConversionChecker { + + override def isConvertable( + from: SingleTypingResult, + to: TypedClass + ): ValidatedNel[String, Unit] = { + val errMsgPrefix = s"${from.runtimeObjType.display} cannot be converted to ${to.display}" + condNel(TypeConversionHandler.canBeLooselyConvertedTo(from, to), (), errMsgPrefix) + } + + } + +} diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/CanBeSubclassDeterminer.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/CanBeSubclassDeterminer.scala deleted file mode 100644 index 9545ab30dd6..00000000000 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/CanBeSubclassDeterminer.scala +++ /dev/null @@ -1,214 +0,0 @@ -package pl.touk.nussknacker.engine.api.typed - -import cats.data.Validated._ -import cats.data.{NonEmptyList, ValidatedNel} -import cats.implicits.{catsSyntaxValidatedId, _} -import org.apache.commons.lang3.ClassUtils -import pl.touk.nussknacker.engine.api.typed.typing._ - -/** - * This class determine if type can be subclass of other type. It basically based on fact that TypingResults are - * sets of possible supertypes with some additional restrictions (like TypedObjectTypingResult). - * - * This class, like CommonSupertypeFinder is in spirit of "Be type safety as much as possible, but also provide some helpful - * conversion for types not in the same jvm class hierarchy like boxed Integer to boxed Long and so on". - * WARNING: Evaluation of SpEL expressions fit into this spirit, for other language evaluation engines you need to provide such a compatibility. - */ -trait CanBeSubclassDeterminer { - - private val javaMapClass = classOf[java.util.Map[_, _]] - private val javaListClass = classOf[java.util.List[_]] - private val arrayOfAnyRefClass = classOf[Array[AnyRef]] - - /** - * This method checks if `givenType` can by subclass of `superclassCandidate` - * It will return true if `givenType` is equals to `superclassCandidate` or `givenType` "extends" `superclassCandidate` - */ - def canBeSubclassOf(givenType: TypingResult, superclassCandidate: TypingResult): ValidatedNel[String, Unit] = { - (givenType, superclassCandidate) match { - case (_, Unknown) => ().validNel - case (Unknown, _) => ().validNel - case (TypedNull, other) => canNullBeSubclassOf(other) - case (_, TypedNull) => s"No type can be subclass of ${TypedNull.display}".invalidNel - case (given: SingleTypingResult, superclass: TypedUnion) => - canBeSubclassOf(NonEmptyList.one(given), superclass.possibleTypes) - case (given: TypedUnion, superclass: SingleTypingResult) => - canBeSubclassOf(given.possibleTypes, NonEmptyList.one(superclass)) - case (given: SingleTypingResult, superclass: SingleTypingResult) => singleCanBeSubclassOf(given, superclass) - case (given: TypedUnion, superclass: TypedUnion) => canBeSubclassOf(given.possibleTypes, superclass.possibleTypes) - } - } - - private def canNullBeSubclassOf(result: TypingResult): ValidatedNel[String, Unit] = result match { - // TODO: Null should not be subclass of typed map that has all values assigned. - case TypedObjectWithValue(_, _) => s"${TypedNull.display} cannot be subclass of type with value".invalidNel - case _ => ().validNel - } - - protected def singleCanBeSubclassOf( - givenType: SingleTypingResult, - superclassCandidate: SingleTypingResult - ): ValidatedNel[String, Unit] = { - val objTypeRestriction = classCanBeSubclassOf(givenType, superclassCandidate.runtimeObjType) - val typedObjectRestrictions = (_: Unit) => - superclassCandidate match { - case superclass: TypedObjectTypingResult => - val givenTypeFields = givenType match { - case given: TypedObjectTypingResult => given.fields - case _ => Map.empty[String, TypingResult] - } - - superclass.fields.toList - .map { case (name, typ) => - givenTypeFields.get(name) match { - case None => - s"Field '$name' is lacking".invalidNel - case Some(givenFieldType) => - condNel( - canBeSubclassOf(givenFieldType, typ).isValid, - (), - s"Field '$name' is of the wrong type. Expected: ${givenFieldType.display}, actual: ${typ.display}" - ) - } - } - .foldLeft(().validNel[String])(_.combine(_)) - case _ => - ().validNel - } - val dictRestriction = (_: Unit) => { - (givenType, superclassCandidate) match { - case (given: TypedDict, superclass: TypedDict) => - condNel( - given.dictId == superclass.dictId, - (), - "The type and the superclass candidate are Dicts with unequal IDs" - ) - case (_: TypedDict, _) => - "The type is a Dict but the superclass candidate not".invalidNel - case (_, _: TypedDict) => - "The superclass candidate is a Dict but the type not".invalidNel - case _ => - ().validNel - } - } - val taggedValueRestriction = (_: Unit) => { - (givenType, superclassCandidate) match { - case (givenTaggedValue: TypedTaggedValue, superclassTaggedValue: TypedTaggedValue) => - condNel( - givenTaggedValue.tag == superclassTaggedValue.tag, - (), - s"Tagged values have unequal tags: ${givenTaggedValue.tag} and ${superclassTaggedValue.tag}" - ) - case (_: TypedTaggedValue, _) => ().validNel - case (_, _: TypedTaggedValue) => - s"The type is not a tagged value".invalidNel - case _ => ().validNel - } - } - // Type like Integer can be subclass of Integer{5}, because Integer could - // possibly have value of 5, that would make it subclass of Integer{5}. - // This allows us to supply unknown Integer to function that requires - // Integer{5}. - val dataValueRestriction = (_: Unit) => { - (givenType, superclassCandidate) match { - case (TypedObjectWithValue(_, givenValue), TypedObjectWithValue(_, candidateValue)) - if givenValue == candidateValue => - ().validNel - case (TypedObjectWithValue(_, givenValue), TypedObjectWithValue(_, candidateValue)) => - s"Types with value have different values: $givenValue and $candidateValue".invalidNel - case _ => ().validNel - } - } - objTypeRestriction andThen - (typedObjectRestrictions combine dictRestriction combine taggedValueRestriction combine dataValueRestriction) - } - - protected def classCanBeSubclassOf( - givenType: SingleTypingResult, - superclassCandidate: TypedClass - ): ValidatedNel[String, Unit] = { - val givenClass = givenType.runtimeObjType - - val equalClassesOrCanAssign = - condNel( - givenClass == superclassCandidate, - (), - f"${givenClass.display} and ${superclassCandidate.display} are not the same" - ) orElse - isAssignable(givenClass.klass, superclassCandidate.klass) - - val canBeSubclass = equalClassesOrCanAssign andThen (_ => typeParametersMatches(givenClass, superclassCandidate)) - canBeSubclass orElse canBeConvertedTo(givenType, superclassCandidate) - } - - private def typeParametersMatches(givenClass: TypedClass, superclassCandidate: TypedClass) = { - def canBeSubOrSuperclass(givenClassParam: TypingResult, superclassParam: TypingResult) = - condNel( - canBeSubclassOf(givenClassParam, superclassParam).isValid || - canBeSubclassOf(superclassParam, givenClassParam).isValid, - (), - f"None of ${givenClassParam.display} and ${superclassParam.display} is a subclass of another" - ) - - (givenClass, superclassCandidate) match { - case (TypedClass(_, givenElementParam :: Nil), TypedClass(superclass, superclassParam :: Nil)) - // Array are invariant but we have built-in conversion between array types - this check should be moved outside this class when we move away canBeConvertedTo as well - if javaListClass.isAssignableFrom(superclass) || arrayOfAnyRefClass.isAssignableFrom(superclass) => - canBeSubclassOf(givenElementParam, superclassParam) - case ( - TypedClass(_, givenKeyParam :: givenValueParam :: Nil), - TypedClass(superclass, superclassKeyParam :: superclassValueParam :: Nil) - ) if javaMapClass.isAssignableFrom(superclass) => - // Map's key generic param is invariant. We can't just check givenKeyParam == superclassKeyParam because of Unknown type which is a kind of wildcard - condNel( - canBeSubclassOf(givenKeyParam, superclassKeyParam).isValid && - canBeSubclassOf(superclassKeyParam, givenKeyParam).isValid, - (), - s"Key types of Maps ${givenKeyParam.display} and ${superclassKeyParam.display} are not equals" - ) andThen (_ => canBeSubclassOf(givenValueParam, superclassValueParam)) - case _ => - // for unknown types we are lax - the generic type may be co- contra- or in-variant - and we don't want to - // return validation errors in this case. It's better to accept to much than too little - condNel( - superclassCandidate.params.zip(givenClass.params).forall { case (superclassParam, givenClassParam) => - canBeSubOrSuperclass(givenClassParam, superclassParam).isValid - }, - (), - s"Wrong type parameters" - ) - } - } - - private def canBeSubclassOf( - givenTypes: NonEmptyList[SingleTypingResult], - superclassCandidates: NonEmptyList[SingleTypingResult] - ): ValidatedNel[String, Unit] = { - // Would be more safety to do givenTypes.forAll(... superclassCandidates.exists ...) - we wil protect against - // e.g. (String | Int).canBeSubclassOf(String) which can fail in runtime for Int, but on the other hand we can't block user's intended action. - // He/she could be sure that in this type, only String will appear. He/she also can't easily downcast (String | Int) to String so leaving here - // "double exists" looks like a good tradeoff - condNel( - givenTypes.exists(given => superclassCandidates.exists(singleCanBeSubclassOf(given, _).isValid)), - (), - s"""None of the following types: - |${givenTypes.map(" - " + _.display).toList.mkString(",\n")} - |can be a subclass of any of: - |${superclassCandidates.map(" - " + _.display).toList.mkString(",\n")}""".stripMargin - ) - } - - // TODO: Conversions should be checked during typing, not during generic usage of TypingResult.canBeSubclassOf(...) - private def canBeConvertedTo( - givenType: SingleTypingResult, - superclassCandidate: TypedClass - ): ValidatedNel[String, Unit] = { - val errMsgPrefix = s"${givenType.runtimeObjType.display} cannot be converted to ${superclassCandidate.display}" - condNel(TypeConversionHandler.canBeConvertedTo(givenType, superclassCandidate), (), errMsgPrefix) - } - - // we use explicit autoboxing = true flag, as ClassUtils in commons-lang3:3.3 (used in Flink) cannot handle JDK 11... - private def isAssignable(from: Class[_], to: Class[_]): ValidatedNel[String, Unit] = - condNel(ClassUtils.isAssignable(from, to, true), (), s"$to is not assignable from $from") -} - -object CanBeSubclassDeterminer extends CanBeSubclassDeterminer diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala index fa37bc54a40..647f169c393 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/NumberTypeUtils.scala @@ -15,9 +15,9 @@ object NumberTypeUtils { else if (typ == Typed[java.lang.Double]) java.lang.Double.valueOf(0) else if (typ == Typed[java.math.BigDecimal]) java.math.BigDecimal.ZERO // in case of some unions - else if (typ.canBeSubclassOf(Typed[java.lang.Integer])) java.lang.Integer.valueOf(0) + else if (typ.canBeConvertedTo(Typed[java.lang.Integer])) java.lang.Integer.valueOf(0) // double is quite safe - it can be converted to any Number - else if (typ.canBeSubclassOf(Typed[Number])) java.lang.Double.valueOf(0) + else if (typ.canBeConvertedTo(Typed[Number])) java.lang.Double.valueOf(0) else throw new IllegalArgumentException(s"Not expected type: ${typ.display}, should be Number") } diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala index cc91f255239..e5629241152 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/TypeConversionHandler.scala @@ -3,8 +3,10 @@ package pl.touk.nussknacker.engine.api.typed import org.apache.commons.lang3.{ClassUtils, LocaleUtils} import org.springframework.util.StringUtils import pl.touk.nussknacker.engine.api.typed.supertype.NumberTypesPromotionStrategy +import pl.touk.nussknacker.engine.api.typed.supertype.NumberTypesPromotionStrategy.AllNumbers import pl.touk.nussknacker.engine.api.typed.typing.{SingleTypingResult, TypedClass, TypedObjectWithValue} +import java.math.BigInteger import java.nio.charset.Charset import java.time._ import java.time.chrono.{ChronoLocalDate, ChronoLocalDateTime} @@ -20,8 +22,8 @@ object TypeConversionHandler { /** * java.math.BigDecimal is quite often returned as a wrapper for all kind of numbers (floating and without floating point). - * Given to this we cannot to be sure if conversion is safe or not based on type (without scale knowledge). - * So we have two options: enforce user to convert to some type without floating point (e.g. BigInteger) or be loose in this point. + * Given to this we cannot be sure if conversion is safe or not based on type (without scale knowledge). + * So we have two options: force user to convert to some type without floating point (e.g. BigInteger) or be loose in this point. * Be default we will be loose. */ // TODO: Add feature flag: strictBigDecimalChecking (default false?) @@ -35,8 +37,8 @@ object TypeConversionHandler { cl } - def canConvert(value: String, superclassCandidate: TypedClass): Boolean = { - ClassUtils.isAssignable(superclassCandidate.klass, klass, true) && Try( + def canConvert(value: String, to: TypedClass): Boolean = { + ClassUtils.isAssignable(to.klass, klass, true) && Try( convert(value) ).isSuccess } @@ -63,17 +65,33 @@ object TypeConversionHandler { StringConversion[ChronoLocalDateTime[_]](LocalDateTime.parse) ) - def canBeConvertedTo(givenType: SingleTypingResult, superclassCandidate: TypedClass): Boolean = { - handleNumberConversions(givenType.runtimeObjType, superclassCandidate) || - handleStringToValueClassConversions(givenType, superclassCandidate) + def canBeLooselyConvertedTo(from: SingleTypingResult, to: TypedClass): Boolean = + canBeConvertedToAux(from, to) + + def canBeStrictlyConvertedTo(from: SingleTypingResult, to: TypedClass): Boolean = + canBeConvertedToAux(from, to, strict = true) + + private def canBeConvertedToAux(from: SingleTypingResult, to: TypedClass, strict: Boolean = false) = { + handleStringToValueClassConversions(from, to) || + handleNumberConversion(from.runtimeObjType, to, strict) + } + + private def handleNumberConversion(from: SingleTypingResult, to: TypedClass, strict: Boolean) = { + val boxedGivenClass = ClassUtils.primitiveToWrapper(from.runtimeObjType.klass) + val boxedSuperclassCandidate = ClassUtils.primitiveToWrapper(to.klass) + + if (strict) + handleStrictNumberConversions(boxedGivenClass, boxedSuperclassCandidate) + else + handleLooseNumberConversion(boxedGivenClass, boxedSuperclassCandidate) } // See org.springframework.core.convert.support.NumberToNumberConverterFactory - private def handleNumberConversions(givenClass: TypedClass, superclassCandidate: TypedClass): Boolean = { - val boxedGivenClass = ClassUtils.primitiveToWrapper(givenClass.klass) - val boxedSuperclassCandidate = ClassUtils.primitiveToWrapper(superclassCandidate.klass) + private def handleLooseNumberConversion( + boxedGivenClass: Class[_], + boxedSuperclassCandidate: Class[_] + ): Boolean = { // We can't check precision here so we need to be loose here - // TODO: Add feature flag: strictNumberPrecisionChecking (default false?) if (NumberTypesPromotionStrategy .isFloatingNumber(boxedSuperclassCandidate) || boxedSuperclassCandidate == classOf[java.math.BigDecimal]) { ClassUtils.isAssignable(boxedGivenClass, classOf[Number], true) @@ -84,13 +102,25 @@ object TypeConversionHandler { } } + private def handleStrictNumberConversions(givenClass: Class[_], to: Class[_]): Boolean = { + (givenClass, to) match { + case (bigInteger, t) + if (bigInteger == classOf[BigInteger] && (t == classOf[BigDecimal] || t == classOf[BigInteger])) => + true + case (f, t) if (AllNumbers.contains(f) && AllNumbers.contains(t)) => + AllNumbers.indexOf(f) >= AllNumbers.indexOf(t) + case _ => false + + } + } + private def handleStringToValueClassConversions( - givenType: SingleTypingResult, - superclassCandidate: TypedClass + from: SingleTypingResult, + to: TypedClass ): Boolean = - givenType match { + from match { case TypedObjectWithValue(_, str: String) => - stringConversions.exists(_.canConvert(str, superclassCandidate)) + stringConversions.exists(_.canConvert(str, to)) case _ => false } 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/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala index ca5cbde9597..1aff3f9a890 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/typing.scala @@ -27,15 +27,18 @@ object typing { // TODO: Rename to Typed, maybe NuType? sealed trait TypingResult { - // TODO: We should split this method into two or three methods: - // - Simple, strictly checking subclassing similar to isAssignable, where we don't do heuristics like - // Any can be subclass of Int, or for Union of Int and String can be subclass of Int - // - The one with heuristics considering limitations of our tool like poor support for generics, lack - // of casting allowing things described above - // - The one that allow things above + SPeL conversions like any Number to any Number conversion, - // String to LocalDate etc. This one should be accessible only for context where SPeL is used - final def canBeSubclassOf(typingResult: TypingResult): Boolean = - CanBeSubclassDeterminer.canBeSubclassOf(this, typingResult).isValid + /** + * Checks if there exists a conversion to a given typingResult, with possible loss of precision, e.g. long to int. + * If you need to retain conversion precision, use canBeStrictlyConvertedTo + */ + final def canBeConvertedTo(typingResult: TypingResult): Boolean = + AssignabilityDeterminer.isAssignableLoose(this, typingResult).isValid + + /** + * Checks if the conversion to a given typingResult can be made without loss of precision + */ + final def canBeStrictlyConvertedTo(typingResult: TypingResult): Boolean = + AssignabilityDeterminer.isAssignableStrict(this, typingResult).isValid def valueOpt: Option[Any] @@ -466,7 +469,9 @@ object typing { case class CastTypedValue[T: TypeTag]() { def unapply(typingResult: TypingResult): Option[TypingResultTypedValue[T]] = { - Option(typingResult).filter(_.canBeSubclassOf(Typed.fromDetailedType[T])).map(new TypingResultTypedValue(_)) + Option(typingResult) + .filter(_.canBeConvertedTo(Typed.fromDetailedType[T])) + .map(new TypingResultTypedValue(_)) } } 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/AssignabilityDeterminerSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/AssignabilityDeterminerSpec.scala new file mode 100644 index 00000000000..82d90e679f2 --- /dev/null +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/AssignabilityDeterminerSpec.scala @@ -0,0 +1,67 @@ +package pl.touk.nussknacker.engine.api.typed + +import cats.data.NonEmptyList +import cats.data.Validated.{Invalid, Valid} +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import org.scalatest.prop.TableDrivenPropertyChecks.forAll +import org.scalatest.prop.Tables.Table +import pl.touk.nussknacker.engine.api.typed.typing.Typed + +class AssignabilityDeterminerSpec extends AnyFunSuite with Matchers { + + val wideningConversionCases = Table( + ("sourceType", "targetType", "expectedStrict", "expectedLoose"), + (Typed[Int], Typed[Int], Valid(()), Valid(())), + (Typed[Int], Typed[Double], Valid(()), Valid(())), + (Typed[List[Int]], Typed[List[Int]], Valid(()), Valid(())), + (Typed[List[Int]], Typed[List[Any]], Valid(()), Valid(())), + (Typed[Map[String, Int]], Typed[Map[String, Int]], Valid(()), Valid(())), + (Typed[Map[String, Int]], Typed[Map[Any, Any]], Valid(()), Valid(())) + ) + + test("isAssignableStrict should pass for widening cases") { + forAll(wideningConversionCases) { (sourceType, targetType, expectedStrict, _) => + val result = AssignabilityDeterminer.isAssignableStrict(sourceType, targetType) + result shouldBe expectedStrict + } + } + + test("isAssignableLoose should pass for widening cases") { + forAll(wideningConversionCases) { (sourceType, targetType, _, expectedLoose) => + val result = AssignabilityDeterminer.isAssignableLoose(sourceType, targetType) + result shouldBe expectedLoose + } + } + + val narrowingConversionCases = Table( + ("sourceType", "targetType", "expectedStrict", "expectedLoose"), + (Typed[Long], Typed[Int], Invalid(NonEmptyList.of("")), Valid(())), + (Typed[Long], Typed[Short], Invalid(NonEmptyList.of("")), Valid(())), + (Typed[Double], Typed[Float], Invalid(NonEmptyList.of("")), Valid(())), + (Typed[BigDecimal], Typed[Double], Invalid(NonEmptyList.of("")), Valid(())) + ) + + test("isAssignableStrict should fail for narrowing numerical cases") { + forAll(narrowingConversionCases) { (sourceType, targetType, expectedStrict, _) => + val result = AssignabilityDeterminer.isAssignableStrict(sourceType, targetType) + result match { + case Valid(_) if expectedStrict.isValid => succeed + case Invalid(_) if expectedStrict.isInvalid => succeed + case _ => fail(s"Unexpected result: $result for types $sourceType -> $targetType") + } + } + } + + test("isAssignableLoose should pass for narrowing cases") { + forAll(narrowingConversionCases) { (sourceType, targetType, _, expectedLoose) => + val result = AssignabilityDeterminer.isAssignableLoose(sourceType, targetType) + result match { + case Valid(_) if expectedLoose.isValid => succeed + case Invalid(_) if expectedLoose.isInvalid => succeed + case _ => fail(s"Unexpected result: $result for types $sourceType -> $targetType") + } + } + } + +} diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala index 6e8e4ce6708..b1d98cd537d 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypedFromInstanceTest.scala @@ -60,20 +60,20 @@ class TypedFromInstanceTest extends AnyFunSuite with Matchers with LoneElement w } test("should type empty list") { - Typed.fromInstance(Nil).canBeSubclassOf(Typed(classOf[List[_]])) shouldBe true - Typed.fromInstance(Nil.asJava).canBeSubclassOf(Typed(classOf[java.util.List[_]])) shouldBe true + Typed.fromInstance(Nil).canBeConvertedTo(Typed(classOf[List[_]])) shouldBe true + Typed.fromInstance(Nil.asJava).canBeConvertedTo(Typed(classOf[java.util.List[_]])) shouldBe true } test("should type lists and return union of types coming from all elements") { def checkTypingResult(obj: Any, klass: Class[_], paramTypingResult: TypingResult): Unit = { val typingResult = Typed.fromInstance(obj) - typingResult.canBeSubclassOf(Typed(klass)) shouldBe true + typingResult.canBeConvertedTo(Typed(klass)) shouldBe true typingResult.withoutValue .asInstanceOf[TypedClass] .params .loneElement - .canBeSubclassOf(paramTypingResult) shouldBe true + .canBeConvertedTo(paramTypingResult) shouldBe true } def checkNotASubclassOfOtherParamTypingResult(obj: Any, otherParamTypingResult: TypingResult): Unit = { @@ -82,7 +82,7 @@ class TypedFromInstanceTest extends AnyFunSuite with Matchers with LoneElement w .asInstanceOf[TypedClass] .params .loneElement - .canBeSubclassOf(otherParamTypingResult) shouldBe false + .canBeConvertedTo(otherParamTypingResult) shouldBe false } val listOfSimpleObjects = List[Any](1.1, 2) 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/TypingResultErrorMessagesSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala index 5483df1a9a8..22d0836fd1b 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultErrorMessagesSpec.scala @@ -5,6 +5,7 @@ import cats.data.NonEmptyList import org.scalatest.{Inside, OptionValues} import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import pl.touk.nussknacker.engine.api.typed.AssignabilityDeterminer.isAssignableLoose import pl.touk.nussknacker.engine.api.typed.typing._ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with OptionValues with Inside { @@ -13,11 +14,11 @@ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with Optio private def list(arg: TypingResult) = Typed.genericTypeClass[java.util.List[_]](List(arg)) - import CanBeSubclassDeterminer.canBeSubclassOf + import AssignabilityDeterminer.isAssignable - test("determine if can be subclass for typed object") { + test("determine if can be subclass for simple typed objects") { - canBeSubclassOf( + isAssignableLoose( typeMap( "field1" -> Typed[String], "field2" -> Typed[Int], @@ -37,8 +38,10 @@ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with Optio "Field 'field4' is lacking" ) .invalid + } - canBeSubclassOf( + test("determine if can be subclass for map of typed objects") { + isAssignableLoose( typeMap("field1" -> list(typeMap("field2a" -> Typed[String], "field3" -> Typed[Int]))), typeMap("field1" -> list(typeMap("field2" -> Typed[String]))) ) shouldBe NonEmptyList @@ -49,30 +52,30 @@ class TypingResultErrorMessagesSpec extends AnyFunSuite with Matchers with Optio } test("determine if can be subclass for class") { - canBeSubclassOf(Typed.fromDetailedType[Set[BigDecimal]], Typed.fromDetailedType[Set[String]]) shouldBe + isAssignableLoose(Typed.fromDetailedType[Set[BigDecimal]], Typed.fromDetailedType[Set[String]]) shouldBe "Set[BigDecimal] cannot be converted to Set[String]".invalidNel } test("determine if can be subclass for tagged value") { - canBeSubclassOf( + isAssignableLoose( Typed.tagged(Typed.typedClass[String], "tag1"), Typed.tagged(Typed.typedClass[String], "tag2") ) shouldBe "Tagged values have unequal tags: tag1 and tag2".invalidNel - canBeSubclassOf(Typed.typedClass[String], Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe + isAssignableLoose(Typed.typedClass[String], Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe "The type is not a tagged value".invalidNel } test("determine if can be subclass for object with value") { - canBeSubclassOf(Typed.fromInstance(2), Typed.fromInstance(3)) shouldBe + isAssignableLoose(Typed.fromInstance(2), Typed.fromInstance(3)) shouldBe "Types with value have different values: 2 and 3".invalidNel } test("determine if can be subclass for null") { - canBeSubclassOf(Typed[String], TypedNull) shouldBe + isAssignableLoose(Typed[String], TypedNull) shouldBe "No type can be subclass of Null".invalidNel - canBeSubclassOf(TypedNull, Typed.fromInstance(1)) shouldBe + isAssignableLoose(TypedNull, Typed.fromInstance(1)) shouldBe "Null cannot be subclass of type with value".invalidNel } diff --git a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala index ec38ad9cf2a..1581edf488c 100644 --- a/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala +++ b/components-api/src/test/scala/pl/touk/nussknacker/engine/api/typed/TypingResultSpec.scala @@ -32,33 +32,33 @@ class TypingResultSpec test("determine if can be subclass for typed object") { - typeMap("field1" -> Typed[String], "field2" -> Typed[Int]).canBeSubclassOf( + typeMap("field1" -> Typed[String], "field2" -> Typed[Int]).canBeConvertedTo( typeMap("field1" -> Typed[String]) ) shouldBe true - typeMap("field1" -> Typed[String]).canBeSubclassOf( + typeMap("field1" -> Typed[String]).canBeConvertedTo( typeMap("field1" -> Typed[String], "field2" -> Typed[Int]) ) shouldBe false - typeMap("field1" -> Typed[Int]).canBeSubclassOf( + typeMap("field1" -> Typed[Int]).canBeConvertedTo( typeMap("field1" -> Typed[String]) ) shouldBe false - typeMap("field1" -> Typed[Int]).canBeSubclassOf( + typeMap("field1" -> Typed[Int]).canBeConvertedTo( typeMap("field1" -> Typed[Number]) ) shouldBe true - typeMap("field1" -> list(typeMap("field2" -> Typed[String], "field3" -> Typed[Int]))).canBeSubclassOf( + typeMap("field1" -> list(typeMap("field2" -> Typed[String], "field3" -> Typed[Int]))).canBeConvertedTo( typeMap("field1" -> list(typeMap("field2" -> Typed[String]))) ) shouldBe true - typeMap("field1" -> list(typeMap("field2a" -> Typed[String], "field3" -> Typed[Int]))).canBeSubclassOf( + typeMap("field1" -> list(typeMap("field2a" -> Typed[String], "field3" -> Typed[Int]))).canBeConvertedTo( typeMap("field1" -> list(typeMap("field2" -> Typed[String]))) ) shouldBe false - typeMap("field1" -> Typed[String]).canBeSubclassOf(Typed[java.util.Map[_, _]]) shouldBe true + typeMap("field1" -> Typed[String]).canBeConvertedTo(Typed[java.util.Map[_, _]]) shouldBe true - Typed[java.util.Map[_, _]].canBeSubclassOf(typeMap("field1" -> Typed[String])) shouldBe false + Typed[java.util.Map[_, _]].canBeConvertedTo(typeMap("field1" -> Typed[String])) shouldBe false } test("extract Unknown value type when no super matching supertype found among all fields of Record") { @@ -76,72 +76,78 @@ class TypingResultSpec } test("determine if can be subclass for typed unions") { - Typed(Typed[String], Typed[Int]).canBeSubclassOf(Typed[Int]) shouldBe true - Typed[Int].canBeSubclassOf(Typed(Typed[String], Typed[Int])) shouldBe true + Typed(Typed[String], Typed[Int]).canBeConvertedTo(Typed[Int]) shouldBe true + Typed[Int].canBeConvertedTo(Typed(Typed[String], Typed[Int])) shouldBe true - Typed(Typed[String], Typed[Int]).canBeSubclassOf(Typed(Typed[Long], Typed[Int])) shouldBe true + Typed(Typed[String], Typed[Int]).canBeConvertedTo(Typed(Typed[Long], Typed[Int])) shouldBe true } test("determine if can be subclass for unknown") { - Unknown.canBeSubclassOf(Typed[Int]) shouldBe true - Typed[Int].canBeSubclassOf(Unknown) shouldBe true + Unknown.canBeConvertedTo(Typed[Int]) shouldBe true + Typed[Int].canBeConvertedTo(Unknown) shouldBe true - Unknown.canBeSubclassOf(Typed(Typed[String], Typed[Int])) shouldBe true - Typed(Typed[String], Typed[Int]).canBeSubclassOf(Unknown) shouldBe true + Unknown.canBeConvertedTo(Typed(Typed[String], Typed[Int])) shouldBe true + Typed(Typed[String], Typed[Int]).canBeConvertedTo(Unknown) shouldBe true - Unknown.canBeSubclassOf(typeMap("field1" -> Typed[String])) shouldBe true - typeMap("field1" -> Typed[String]).canBeSubclassOf(Unknown) shouldBe true + Unknown.canBeConvertedTo(typeMap("field1" -> Typed[String])) shouldBe true + typeMap("field1" -> Typed[String]).canBeConvertedTo(Unknown) shouldBe true } test("determine if can be subclass for class") { Typed .fromDetailedType[java.util.List[BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe true + .canBeConvertedTo(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe true Typed .fromDetailedType[java.util.List[BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.List[Number]]) shouldBe true + .canBeConvertedTo(Typed.fromDetailedType[java.util.List[Number]]) shouldBe true Typed .fromDetailedType[java.util.List[Number]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe false + .canBeConvertedTo(Typed.fromDetailedType[java.util.List[BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, Number]]) shouldBe true + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, Number]]) shouldBe true Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[Number, Number]]) shouldBe false + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[Number, Number]]) shouldBe false Typed .fromDetailedType[java.util.Map[Number, Number]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[Number, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[BigDecimal, Number]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe false Typed .fromDetailedType[java.util.Map[BigDecimal, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[_, BigDecimal]]) shouldBe true + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[_, BigDecimal]]) shouldBe true Typed .fromDetailedType[java.util.Map[_, BigDecimal]] - .canBeSubclassOf(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true + .canBeConvertedTo(Typed.fromDetailedType[java.util.Map[BigDecimal, BigDecimal]]) shouldBe true // For arrays it might be tricky - Typed.fromDetailedType[Array[BigDecimal]].canBeSubclassOf(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe true - Typed.fromDetailedType[Array[BigDecimal]].canBeSubclassOf(Typed.fromDetailedType[Array[Number]]) shouldBe true - Typed.fromDetailedType[Array[Number]].canBeSubclassOf(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe false + Typed + .fromDetailedType[Array[BigDecimal]] + .canBeConvertedTo(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe true + Typed + .fromDetailedType[Array[BigDecimal]] + .canBeConvertedTo(Typed.fromDetailedType[Array[Number]]) shouldBe true + Typed + .fromDetailedType[Array[Number]] + .canBeConvertedTo(Typed.fromDetailedType[Array[BigDecimal]]) shouldBe false } test("determine if numbers can be converted") { - Typed[Int].canBeSubclassOf(Typed[Long]) shouldBe true - Typed[Long].canBeSubclassOf(Typed[Int]) shouldBe true - Typed[Long].canBeSubclassOf(Typed[Double]) shouldBe true - Typed[Double].canBeSubclassOf(Typed[Long]) shouldBe false - Typed[java.math.BigDecimal].canBeSubclassOf(Typed[Long]) shouldBe true - Typed[Long].canBeSubclassOf(Typed[java.math.BigDecimal]) shouldBe true + Typed[Int].canBeConvertedTo(Typed[Long]) shouldBe true + Typed[Long].canBeConvertedTo(Typed[Int]) shouldBe true + Typed[Long].canBeConvertedTo(Typed[Double]) shouldBe true + Typed[Double].canBeConvertedTo(Typed[Long]) shouldBe false + Typed[java.math.BigDecimal].canBeConvertedTo(Typed[Long]) shouldBe true + Typed[Long].canBeConvertedTo(Typed[java.math.BigDecimal]) shouldBe true } test("find common supertype for simple types") { @@ -297,22 +303,22 @@ class TypingResultSpec test("determine if can be subclass for tagged value") { Typed .tagged(Typed.typedClass[String], "tag1") - .canBeSubclassOf(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe true + .canBeConvertedTo(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe true Typed .tagged(Typed.typedClass[String], "tag1") - .canBeSubclassOf(Typed.tagged(Typed.typedClass[String], "tag2")) shouldBe false + .canBeConvertedTo(Typed.tagged(Typed.typedClass[String], "tag2")) shouldBe false Typed .tagged(Typed.typedClass[String], "tag1") - .canBeSubclassOf(Typed.tagged(Typed.typedClass[Integer], "tag1")) shouldBe false - Typed.tagged(Typed.typedClass[String], "tag1").canBeSubclassOf(Typed.typedClass[String]) shouldBe true - Typed.typedClass[String].canBeSubclassOf(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe false + .canBeConvertedTo(Typed.tagged(Typed.typedClass[Integer], "tag1")) shouldBe false + Typed.tagged(Typed.typedClass[String], "tag1").canBeConvertedTo(Typed.typedClass[String]) shouldBe true + Typed.typedClass[String].canBeConvertedTo(Typed.tagged(Typed.typedClass[String], "tag1")) shouldBe false } test("determine if can be subclass for null") { - TypedNull.canBeSubclassOf(Typed[Int]) shouldBe true - TypedNull.canBeSubclassOf(Typed.fromInstance(4)) shouldBe false - TypedNull.canBeSubclassOf(TypedNull) shouldBe true - Typed[String].canBeSubclassOf(TypedNull) shouldBe false + TypedNull.canBeConvertedTo(Typed[Int]) shouldBe true + TypedNull.canBeConvertedTo(Typed.fromInstance(4)) shouldBe false + TypedNull.canBeConvertedTo(TypedNull) shouldBe true + Typed[String].canBeConvertedTo(TypedNull) shouldBe false } test("should deeply extract typ parameters") { @@ -331,20 +337,20 @@ class TypingResultSpec } test("determine if can be subclass for object with value") { - Typed.fromInstance(45).canBeSubclassOf(Typed.typedClass[Long]) shouldBe true - Typed.fromInstance(29).canBeSubclassOf(Typed.typedClass[String]) shouldBe false - Typed.fromInstance(78).canBeSubclassOf(Typed.fromInstance(78)) shouldBe true - Typed.fromInstance(12).canBeSubclassOf(Typed.fromInstance(15)) shouldBe false - Typed.fromInstance(41).canBeSubclassOf(Typed.fromInstance("t")) shouldBe false - Typed.typedClass[String].canBeSubclassOf(Typed.fromInstance("t")) shouldBe true + Typed.fromInstance(45).canBeConvertedTo(Typed.typedClass[Long]) shouldBe true + Typed.fromInstance(29).canBeConvertedTo(Typed.typedClass[String]) shouldBe false + Typed.fromInstance(78).canBeConvertedTo(Typed.fromInstance(78)) shouldBe true + Typed.fromInstance(12).canBeConvertedTo(Typed.fromInstance(15)) shouldBe false + Typed.fromInstance(41).canBeConvertedTo(Typed.fromInstance("t")) shouldBe false + Typed.typedClass[String].canBeConvertedTo(Typed.fromInstance("t")) shouldBe true } test("determine if can be subclass for object with value - use conversion") { - Typed.fromInstance("2007-12-03").canBeSubclassOf(Typed.typedClass[LocalDate]) shouldBe true - Typed.fromInstance("2007-12-03T10:15:30").canBeSubclassOf(Typed.typedClass[LocalDateTime]) shouldBe true + Typed.fromInstance("2007-12-03").canBeConvertedTo(Typed.typedClass[LocalDate]) shouldBe true + Typed.fromInstance("2007-12-03T10:15:30").canBeConvertedTo(Typed.typedClass[LocalDateTime]) shouldBe true - Typed.fromInstance("2007-12-03-qwerty").canBeSubclassOf(Typed.typedClass[LocalDate]) shouldBe false - Typed.fromInstance("2007-12-03").canBeSubclassOf(Typed.typedClass[Currency]) shouldBe false + Typed.fromInstance("2007-12-03-qwerty").canBeConvertedTo(Typed.typedClass[LocalDate]) shouldBe false + Typed.fromInstance("2007-12-03").canBeConvertedTo(Typed.typedClass[Currency]) shouldBe false } test("determinate if can be superclass for objects with value") { @@ -443,7 +449,7 @@ class TypingResultSpec logger.trace(s"Checking: ${input.display}") withClue(s"Input: ${input.display};") { - input.canBeSubclassOf(input) shouldBe true + input.canBeConvertedTo(input) shouldBe true val superType = CommonSupertypeFinder.Default.commonSupertype(input, input) withClue(s"Supertype: ${superType.display};") { superType shouldEqual input @@ -457,11 +463,11 @@ class TypingResultSpec logger.trace(s"Checking: ${input.display}") withClue(s"Input: ${input.display};") { - input.canBeSubclassOf(input) shouldBe true + input.canBeConvertedTo(input) shouldBe true val superType = CommonSupertypeFinder.Default.commonSupertype(input, input) withClue(s"Supertype: ${superType.display};") { // We generate combinations of types co we can only check if input type is a subclass of super type - input.canBeSubclassOf(superType) + input.canBeConvertedTo(superType) } } } @@ -477,12 +483,12 @@ class TypingResultSpec logger.trace(s"Checking supertype of: ${first.display} and ${second.display}") withClue(s"Input: ${first.display}; ${second.display};") { - first.canBeSubclassOf(first) shouldBe true - second.canBeSubclassOf(second) shouldBe true + first.canBeConvertedTo(first) shouldBe true + second.canBeConvertedTo(second) shouldBe true val superType = CommonSupertypeFinder.Default.commonSupertype(first, second) withClue(s"Supertype: ${superType.display};") { - first.canBeSubclassOf(superType) - second.canBeSubclassOf(superType) + first.canBeConvertedTo(superType) + second.canBeConvertedTo(superType) } } } 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/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #0.png index 9b46d59eed0..92071466a54 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #1.png index d64ecdcdb68..cd47578d2ef 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #1.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #1.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #2.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #2.png index 66875cc0399..9a1f6d10481 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #2.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #2.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #3.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #3.png index 61edcac2df9..142e3b6736b 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #3.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Activities should display activities #3.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by name with multiple words #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by name with multiple words #0.png index 2faa0fce27b..4e0a37340f9 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by name with multiple words #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by name with multiple words #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by processing mode #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by processing mode #1.png index 9fbb630e47d..2369f45b904 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by processing mode #1.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by processing mode #1.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #0.png index 8f79298458d..6d203e6054a 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #1.png index a7f073a9601..6e56d9e4090 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #1.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should allow filtering by usage #1.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should apply filters from query #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should apply filters from query #0.png index 5862bd56258..31cf1a2867f 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should apply filters from query #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should apply filters from query #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display component #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display component #0.png index 70f95efa447..5bfe842d262 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display component #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display component #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display usages #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display usages #0.png index e5cc8a1b1b0..1b9e57a924c 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display usages #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should display usages #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #0.png index 821872447cf..06ac67c0a84 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #0.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #1.png index 79f4a5c188a..338d51142ac 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #1.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #1.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #2.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #2.png index 5584ba90ae7..dc24b2f8870 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #2.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usage types #2.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usages #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usages #0.png index 8bfa0387e8b..933137660d6 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usages #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Components list should filter usages #0.png differ 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 dc9a0bfbbee..f5f7253c8a9 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #0.png differ 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 061e72f6285..ff3f1d07d54 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #1.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Expression suggester should display colorfull and sorted completions #1.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #1.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #1.png index e32d162b820..fbec7074a9f 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #1.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #1.png differ diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #4.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #4.png index b6f7e64d25a..2e0af0a8267 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #4.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #4.png differ 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 7606c4988e8..0699447c130 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #7.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should allow adding input parameters and display used fragment graph in modal #7.png differ diff --git a/designer/client/cypress/e2e/activities.cy.ts b/designer/client/cypress/e2e/activities.cy.ts index 06b565a1a74..6228b1b2456 100644 --- a/designer/client/cypress/e2e/activities.cy.ts +++ b/designer/client/cypress/e2e/activities.cy.ts @@ -25,7 +25,7 @@ const makeScreenshot = () => { cy.get('[data-testid="activities-panel"]').matchImage({ maxDiffThreshold: 0.01, screenshotConfig: { - blackout: ["[data-testid='activity-date']"], + blackout: [":has(>[data-testid='activity-date'])"], }, }); }; diff --git a/designer/client/cypress/e2e/components.cy.ts b/designer/client/cypress/e2e/components.cy.ts index be96c802b1c..88587a19555 100644 --- a/designer/client/cypress/e2e/components.cy.ts +++ b/designer/client/cypress/e2e/components.cy.ts @@ -247,7 +247,7 @@ describe("Components list", () => { }); // Sort by processing mode - cy.get("[role='columnheader'][aria-label='Processing modes']").dblclick({ force: true }); + cy.get("[role='columnheader'][data-field='allowedProcessingModes']").dblclick({ force: true }); cy.get("#app-container>main").matchImage(); }); diff --git a/designer/client/cypress/e2e/connectionError.cy.ts b/designer/client/cypress/e2e/connectionError.cy.ts index 5e0aef6aca9..08f295fda04 100644 --- a/designer/client/cypress/e2e/connectionError.cy.ts +++ b/designer/client/cypress/e2e/connectionError.cy.ts @@ -59,13 +59,13 @@ describe("Connection error", () => { cy.get("[model-id='filter']").dblclick(); cy.wait("@validation"); - cy.intercept("/api/notifications", { statusCode: 502 }); + cy.intercept("/api/notifications?scenarioName=*", { statusCode: 502 }); cy.contains(/Backend connection issue/).should("be.visible"); cy.get("body").matchImage({ screenshotConfig, }); - cy.intercept("/api/notifications", (req) => { + cy.intercept("/api/notifications?scenarioName=*", (req) => { req.continue(); }); diff --git a/designer/client/cypress/e2e/fragment.cy.ts b/designer/client/cypress/e2e/fragment.cy.ts index c31cfcd6b48..5a9cf09f0e7 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']") @@ -158,6 +158,11 @@ describe("Fragment", () => { .click(); cy.get("[id$='option-1']").click({ force: true }); + // Provide String Fixed value inputMode + cy.get("@window").contains("+").click(); + cy.get("[data-testid='fieldsRow:8']").find("[placeholder='Field name']").type("generic_type"); + cy.get("[data-testid='fieldsRow:8']").contains("String").click().type("List[String]{enter}"); + cy.get("@window") .contains(/^apply$/i) .click(); @@ -281,7 +286,7 @@ describe("Fragment", () => { cy.get("[model-id=input]").should("be.visible").trigger("dblclick"); cy.get("[data-testid=window]").should("be.visible").as("window"); cy.get("@window").contains("+").click(); - cy.get("[data-testid='fieldsRow:8']").find("[placeholder='Field name']").type("test5"); + cy.get("[data-testid='fieldsRow:9']").find("[placeholder='Field name']").type("test5"); cy.get("@window") .contains(/^apply$/i) .click(); 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/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/client/package-lock.json b/designer/client/package-lock.json index 1b7539f899a..9c85baa895e 100644 --- a/designer/client/package-lock.json +++ b/designer/client/package-lock.json @@ -18,10 +18,11 @@ "@hello-pangea/dnd": "16.6.0", "@juggle/resize-observer": "3.3.1", "@loadable/component": "5.15.2", - "@mui/icons-material": "5.15.7", - "@mui/lab": "5.0.0-alpha.165", - "@mui/material": "5.15.7", - "@touk/federated-component": "1.0.0", + "@mui/icons-material": "5.16.7", + "@mui/lab": "5.0.0-alpha.173", + "@mui/material": "5.16.7", + "@mui/system": "5.16.7", + "@touk/federated-component": "1.1.0", "@touk/window-manager": "1.9.0", "ace-builds": "1.34.2", "axios": "1.7.5", @@ -2244,9 +2245,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2629,17 +2630,27 @@ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.5.tgz", + "integrity": "sha512-Z3xbtJ+UcK76eWkagZ1onvn/wAVb1GOMuR15s30Fm2wrMgC7jzpnO2JZXr4eujTTqoQFUrZIw/rT0c6Zzjca1g==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, + "node_modules/@emotion/cache/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/cache/node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@emotion/cache/node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -2668,9 +2679,9 @@ } }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.2", @@ -2734,21 +2745,26 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, + "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "node_modules/@emotion/styled": { "version": "11.11.5", @@ -2773,9 +2789,9 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", @@ -2786,9 +2802,9 @@ } }, "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "node_modules/@emotion/weak-memoize": { "version": "0.3.1", @@ -3252,28 +3268,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.8" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.2.tgz", - "integrity": "sha512-xymkSSowKdGqo0SRr2Mp4czH5A8o2Pum35PAD0ftb3gCcPacWzwhvtUeUqmVXm9EVtm2hThD/lRrFNcahMOaSQ==", + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.6.1" + "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -3281,9 +3300,10 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "license": "MIT" }, "node_modules/@fontsource/inter": { "version": "5.0.16", @@ -4464,14 +4484,15 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.34.tgz", - "integrity": "sha512-e2mbTGTtReD/y5RFwnhkl1Tgl3XwgJhY040IlfkTVaU9f5LWrVhEnpRsYXu3B1CtLrwiWs4cu7aMHV9yRd4jpw==", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.7", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", "clsx": "^2.1.0", "prop-types": "^15.8.1" @@ -4495,26 +4516,29 @@ } }, "node_modules/@mui/base/node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.9.tgz", - "integrity": "sha512-CSDpVevGaxsvMkiYBZ8ztki1z/eT0mM2MqUT21eCRiMz3DU4zQw5rXG5ML/yTuJF9Z2Wv9SliIeaRAuSR/9Nig==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.15.7", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.7.tgz", - "integrity": "sha512-EDAc8TVJGIA/imAvR3u4nANl2W5h3QeHieu2gK7Ypez/nIA55p08tHjf8UrMXEpxCAvfZO6piY9S9uaxETdicA==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", + "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9" }, @@ -4537,15 +4561,16 @@ } }, "node_modules/@mui/lab": { - "version": "5.0.0-alpha.165", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.165.tgz", - "integrity": "sha512-8/zJStT10nh9yrAzLOPTICGhpf5YiGp/JpM0bdTP7u5AE+YT+X2u6QwMxuCrVeW8/WVLAPFg0vtzyfgPcN5T7g==", + "version": "5.0.0-alpha.173", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.173.tgz", + "integrity": "sha512-Gt5zopIWwxDgGy/MXcp6GueD84xFFugFai4hYiXY0zowJpTVnIrTQCQXV004Q7rejJ7aaCntX9hpPJqCrioshA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.36", - "@mui/system": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/base": "5.0.0-beta.40", + "@mui/system": "^5.16.5", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.5", "clsx": "^2.1.0", "prop-types": "^15.8.1" }, @@ -4576,37 +4601,6 @@ } } }, - "node_modules/@mui/lab/node_modules/@mui/base": { - "version": "5.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz", - "integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/lab/node_modules/clsx": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", @@ -4616,21 +4610,22 @@ } }, "node_modules/@mui/material": { - "version": "5.15.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.7.tgz", - "integrity": "sha512-l6+AiKZH3iOJmZCnlpel8ghYQe9Lq0BEuKP8fGj3g5xz4arO9GydqYAtLPMvuHKtArj8lJGNuT2yHYxmejincA==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", + "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.34", - "@mui/core-downloads-tracker": "^5.15.7", - "@mui/system": "^5.15.7", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.7", + "@mui/core-downloads-tracker": "^5.16.7", + "@mui/system": "^5.16.7", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, "engines": { @@ -4676,17 +4671,19 @@ } }, "node_modules/@mui/material/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/@mui/private-theming": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.9.tgz", - "integrity": "sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.9", + "@mui/utils": "^5.16.6", "prop-types": "^15.8.1" }, "engines": { @@ -4707,9 +4704,10 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.9.tgz", - "integrity": "sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", @@ -4738,15 +4736,16 @@ } }, "node_modules/@mui/system": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.9.tgz", - "integrity": "sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.9", - "@mui/styled-engine": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -4777,19 +4776,20 @@ } }, "node_modules/@mui/system/node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@mui/types": { - "version": "7.2.13", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", - "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -4798,14 +4798,16 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.9.tgz", - "integrity": "sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", "dependencies": { "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^18.3.1" }, "engines": { "node": ">=12.0.0" @@ -4824,10 +4826,18 @@ } } }, + "node_modules/@mui/utils/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/utils/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.3", @@ -6109,10 +6119,9 @@ } }, "node_modules/@touk/federated-component": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@touk/federated-component/-/federated-component-1.0.0.tgz", - "integrity": "sha512-ibliSr5T1pbM8S8M0NqUGJlBQJSmBeDTuO1fcPRQ97XO+Ai+9tQ00qUaCr0tI43cdTXLlkunXW+K5IlM4Krx+Q==", - "hasInstallScript": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@touk/federated-component/-/federated-component-1.1.0.tgz", + "integrity": "sha512-pYHL8d4RWxONtW3+PJT/1AzaS9cmtyKLd9VXhEM+dIL0DMIe2IkvB12W/yZUBeB1mW8bWlG/vpthL0lD8yV44Q==", "peerDependencies": { "react": "^17 || ^18", "react-dom": "^17 || ^18" @@ -6711,9 +6720,9 @@ } }, "node_modules/@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/qs": { "version": "6.9.7", @@ -30044,9 +30053,9 @@ } }, "@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -30375,17 +30384,27 @@ } }, "@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "requires": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.5.tgz", + "integrity": "sha512-Z3xbtJ+UcK76eWkagZ1onvn/wAVb1GOMuR15s30Fm2wrMgC7jzpnO2JZXr4eujTTqoQFUrZIw/rT0c6Zzjca1g==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" }, "dependencies": { + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -30416,9 +30435,9 @@ } }, "@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "@emotion/is-prop-valid": { "version": "1.2.2", @@ -30462,21 +30481,28 @@ } }, "@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "requires": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + } } }, "@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" }, "@emotion/styled": { "version": "11.11.5", @@ -30492,9 +30518,9 @@ } }, "@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" }, "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", @@ -30502,9 +30528,9 @@ "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==" }, "@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" }, "@emotion/weak-memoize": { "version": "0.3.1", @@ -30738,34 +30764,34 @@ } }, "@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", "requires": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.8" } }, "@floating-ui/dom": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.2.tgz", - "integrity": "sha512-xymkSSowKdGqo0SRr2Mp4czH5A8o2Pum35PAD0ftb3gCcPacWzwhvtUeUqmVXm9EVtm2hThD/lRrFNcahMOaSQ==", + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", "requires": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" } }, "@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", "requires": { - "@floating-ui/dom": "^1.6.1" + "@floating-ui/dom": "^1.0.0" } }, "@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" }, "@fontsource/inter": { "version": "5.0.16", @@ -31664,67 +31690,53 @@ } }, "@mui/base": { - "version": "5.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.34.tgz", - "integrity": "sha512-e2mbTGTtReD/y5RFwnhkl1Tgl3XwgJhY040IlfkTVaU9f5LWrVhEnpRsYXu3B1CtLrwiWs4cu7aMHV9yRd4jpw==", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", "requires": { "@babel/runtime": "^7.23.9", "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.7", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", "clsx": "^2.1.0", "prop-types": "^15.8.1" }, "dependencies": { "clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" } } }, "@mui/core-downloads-tracker": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.9.tgz", - "integrity": "sha512-CSDpVevGaxsvMkiYBZ8ztki1z/eT0mM2MqUT21eCRiMz3DU4zQw5rXG5ML/yTuJF9Z2Wv9SliIeaRAuSR/9Nig==" + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==" }, "@mui/icons-material": { - "version": "5.15.7", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.7.tgz", - "integrity": "sha512-EDAc8TVJGIA/imAvR3u4nANl2W5h3QeHieu2gK7Ypez/nIA55p08tHjf8UrMXEpxCAvfZO6piY9S9uaxETdicA==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", + "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", "requires": { "@babel/runtime": "^7.23.9" } }, "@mui/lab": { - "version": "5.0.0-alpha.165", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.165.tgz", - "integrity": "sha512-8/zJStT10nh9yrAzLOPTICGhpf5YiGp/JpM0bdTP7u5AE+YT+X2u6QwMxuCrVeW8/WVLAPFg0vtzyfgPcN5T7g==", + "version": "5.0.0-alpha.173", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.173.tgz", + "integrity": "sha512-Gt5zopIWwxDgGy/MXcp6GueD84xFFugFai4hYiXY0zowJpTVnIrTQCQXV004Q7rejJ7aaCntX9hpPJqCrioshA==", "requires": { "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.36", - "@mui/system": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/base": "5.0.0-beta.40", + "@mui/system": "^5.16.5", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.5", "clsx": "^2.1.0", "prop-types": "^15.8.1" }, "dependencies": { - "@mui/base": { - "version": "5.0.0-beta.36", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.36.tgz", - "integrity": "sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==", - "requires": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - } - }, "clsx": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", @@ -31733,21 +31745,21 @@ } }, "@mui/material": { - "version": "5.15.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.7.tgz", - "integrity": "sha512-l6+AiKZH3iOJmZCnlpel8ghYQe9Lq0BEuKP8fGj3g5xz4arO9GydqYAtLPMvuHKtArj8lJGNuT2yHYxmejincA==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", + "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", "requires": { "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.34", - "@mui/core-downloads-tracker": "^5.15.7", - "@mui/system": "^5.15.7", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.7", + "@mui/core-downloads-tracker": "^5.16.7", + "@mui/system": "^5.16.7", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", + "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.2.0", + "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, "dependencies": { @@ -31765,26 +31777,26 @@ "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" }, "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" } } }, "@mui/private-theming": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.9.tgz", - "integrity": "sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", "requires": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.9", + "@mui/utils": "^5.16.6", "prop-types": "^15.8.1" } }, "@mui/styled-engine": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.9.tgz", - "integrity": "sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", "requires": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", @@ -31793,47 +31805,54 @@ } }, "@mui/system": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.9.tgz", - "integrity": "sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", "requires": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.9", - "@mui/styled-engine": "^5.15.9", - "@mui/types": "^7.2.13", - "@mui/utils": "^5.15.9", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "dependencies": { "clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" } } }, "@mui/types": { - "version": "7.2.13", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", - "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==" + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==" }, "@mui/utils": { - "version": "5.15.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.9.tgz", - "integrity": "sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", "requires": { "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^18.3.1" }, "dependencies": { + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" } } }, @@ -32692,9 +32711,9 @@ "dev": true }, "@touk/federated-component": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@touk/federated-component/-/federated-component-1.0.0.tgz", - "integrity": "sha512-ibliSr5T1pbM8S8M0NqUGJlBQJSmBeDTuO1fcPRQ97XO+Ai+9tQ00qUaCr0tI43cdTXLlkunXW+K5IlM4Krx+Q==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@touk/federated-component/-/federated-component-1.1.0.tgz", + "integrity": "sha512-pYHL8d4RWxONtW3+PJT/1AzaS9cmtyKLd9VXhEM+dIL0DMIe2IkvB12W/yZUBeB1mW8bWlG/vpthL0lD8yV44Q==" }, "@touk/federated-types": { "version": "1.1.0", @@ -33250,9 +33269,9 @@ } }, "@types/prop-types": { - "version": "15.7.11", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "@types/qs": { "version": "6.9.7", diff --git a/designer/client/package.json b/designer/client/package.json index 89dd719feb2..9ee99243032 100644 --- a/designer/client/package.json +++ b/designer/client/package.json @@ -11,10 +11,11 @@ "@hello-pangea/dnd": "16.6.0", "@juggle/resize-observer": "3.3.1", "@loadable/component": "5.15.2", - "@mui/icons-material": "5.15.7", - "@mui/lab": "5.0.0-alpha.165", - "@mui/material": "5.15.7", - "@touk/federated-component": "1.0.0", + "@mui/icons-material": "5.16.7", + "@mui/lab": "5.0.0-alpha.173", + "@mui/material": "5.16.7", + "@mui/system": "5.16.7", + "@touk/federated-component": "1.1.0", "@touk/window-manager": "1.9.0", "ace-builds": "1.34.2", "axios": "1.7.5", diff --git a/designer/client/progressBar.js b/designer/client/progressBar.js index b82cdcc3cab..36c04b89f62 100644 --- a/designer/client/progressBar.js +++ b/designer/client/progressBar.js @@ -19,7 +19,7 @@ function getElapsed() { function getBar(percentage) { const { barLength, almostDone } = options; - const bar = padEnd(repeat("◼︎", Math.ceil(percentage * barLength)), barLength, "□"); + const bar = padEnd(repeat("◼", Math.ceil(percentage * barLength)), barLength, "_"); const barColor = percentage > almostDone ? chalk.green : percentage > (almostDone * 2) / 3 ? chalk.yellow : chalk.red; return barColor(bar); } diff --git a/designer/client/src/actions/actionTypes.ts b/designer/client/src/actions/actionTypes.ts index 36388502dd2..306aaea65fc 100644 --- a/designer/client/src/actions/actionTypes.ts +++ b/designer/client/src/actions/actionTypes.ts @@ -10,8 +10,6 @@ export type ActionTypes = | "DELETE_NODES" | "NODES_CONNECTED" | "NODES_DISCONNECTED" - | "NODE_ADDED" - | "NODES_WITH_EDGES_ADDED" | "VALIDATION_RESULT" | "COPY_SELECTION" | "CUT_SELECTION" diff --git a/designer/client/src/actions/nk/node.ts b/designer/client/src/actions/nk/node.ts index a10ac7f5f23..96d875710f0 100644 --- a/designer/client/src/actions/nk/node.ts +++ b/designer/client/src/actions/nk/node.ts @@ -1,12 +1,14 @@ +import { Dictionary } from "lodash"; +import { flushSync } from "react-dom"; +import NodeUtils from "../../components/graph/NodeUtils"; +import { batchGroupBy } from "../../reducers/graph/batchGroupBy"; +import { prepareNewNodesWithLayout } from "../../reducers/graph/utils"; +import { getScenarioGraph } from "../../reducers/selectors/graph"; +import { getProcessDefinitionData } from "../../reducers/selectors/settings"; import { Edge, EdgeType, NodeId, NodeType, ProcessDefinitionData, ValidationResult } from "../../types"; import { ThunkAction } from "../reduxTypes"; -import { layoutChanged, Position } from "./ui/layout"; import { EditNodeAction, EditScenarioLabels } from "./editNode"; -import { getProcessDefinitionData } from "../../reducers/selectors/settings"; -import { batchGroupBy } from "../../reducers/graph/batchGroupBy"; -import NodeUtils from "../../components/graph/NodeUtils"; -import { getScenarioGraph } from "../../reducers/selectors/graph"; -import { flushSync } from "react-dom"; +import { layoutChanged, NodePosition, Position } from "./ui/layout"; export type NodesWithPositions = { node: NodeType; position: Position }[]; @@ -31,7 +33,9 @@ type NodesDisonnectedAction = { type NodesWithEdgesAddedAction = { type: "NODES_WITH_EDGES_ADDED"; - nodesWithPositions: NodesWithPositions; + nodes: NodeType[]; + layout: NodePosition[]; + idMapping: Dictionary; edges: Edge[]; processDefinitionData: ProcessDefinitionData; }; @@ -43,8 +47,8 @@ type ValidationResultAction = { type NodeAddedAction = { type: "NODE_ADDED"; - node: NodeType; - position: Position; + nodes: NodeType[]; + layout: NodePosition[]; }; export function deleteNodes(ids: NodeId[]): ThunkAction { @@ -118,13 +122,20 @@ export function injectNode(from: NodeType, middle: NodeType, to: NodeType, { edg } export function nodeAdded(node: NodeType, position: Position): ThunkAction { - return (dispatch) => { + return (dispatch, getState) => { batchGroupBy.startOrContinue(); // We need to disable automatic React batching https://react.dev/blog/2022/03/29/react-v18#new-feature-automatic-batching // since it breaks redux undo in this case flushSync(() => { - dispatch({ type: "NODE_ADDED", node, position }); + const scenarioGraph = getScenarioGraph(getState()); + const { nodes, layout } = prepareNewNodesWithLayout(scenarioGraph.nodes, [{ node, position }], false); + + dispatch({ + type: "NODE_ADDED", + nodes, + layout, + }); dispatch(layoutChanged()); }); batchGroupBy.end(); @@ -133,11 +144,17 @@ export function nodeAdded(node: NodeType, position: Position): ThunkAction { export function nodesWithEdgesAdded(nodesWithPositions: NodesWithPositions, edges: Edge[]): ThunkAction { return (dispatch, getState) => { - const processDefinitionData = getProcessDefinitionData(getState()); + const state = getState(); + const processDefinitionData = getProcessDefinitionData(state); + const scenarioGraph = getScenarioGraph(state); + const { nodes, layout, idMapping } = prepareNewNodesWithLayout(scenarioGraph.nodes, nodesWithPositions, true); + batchGroupBy.startOrContinue(); dispatch({ type: "NODES_WITH_EDGES_ADDED", - nodesWithPositions, + nodes, + layout, + idMapping, edges, processDefinitionData, }); diff --git a/engine/flink/management/periodic/src/main/resources/web/static/assets/custom-actions/batch-instant.svg b/designer/client/src/assets/img/toolbarButtons/perform-single-execution.svg similarity index 100% rename from engine/flink/management/periodic/src/main/resources/web/static/assets/custom-actions/batch-instant.svg rename to designer/client/src/assets/img/toolbarButtons/perform-single-execution.svg diff --git a/designer/client/src/common/ClipboardUtils.ts b/designer/client/src/common/ClipboardUtils.ts index e831406d535..b5206185feb 100644 --- a/designer/client/src/common/ClipboardUtils.ts +++ b/designer/client/src/common/ClipboardUtils.ts @@ -17,6 +17,10 @@ export async function readText(event?: Event): Promise { } } +interface WriteText { + (text: string): Promise; +} + // We could have used navigator.clipboard.writeText but it is not defined for // content delivered via HTTP. The workaround is to create a hidden element // and then write text into it. After that copy command is used to replace @@ -24,13 +28,15 @@ export async function readText(event?: Event): Promise { // assigned with given id to be able to differentiate between artificial // copy event and the real one triggered by user. // Based on https://techoverflow.net/2018/03/30/copying-strings-to-the-clipboard-using-pure-javascript/ - -export function writeText(text: string): Promise { +const fallbackWriteText: WriteText = (text) => { return new Promise((resolve) => { const el = document.createElement("textarea"); el.value = text; el.setAttribute("readonly", ""); - el.className = css({ position: "absolute", left: "-9999px" }); + el.className = css({ + position: "absolute", + left: "-9999px", + }); el.oncopy = (e) => { // Skip event triggered by writing selection to the clipboard. e.stopPropagation(); @@ -41,4 +47,11 @@ export function writeText(text: string): Promise { document.execCommand("copy"); document.body.removeChild(el); }); -} +}; + +export const writeText: WriteText = (text) => { + if (navigator.clipboard?.writeText) { + return navigator.clipboard.writeText(text).then(() => text); + } + return fallbackWriteText(text); +}; diff --git a/designer/client/src/components/AddProcessDialog.tsx b/designer/client/src/components/AddProcessDialog.tsx index 060ad6a7a51..f947318e4eb 100644 --- a/designer/client/src/components/AddProcessDialog.tsx +++ b/designer/client/src/components/AddProcessDialog.tsx @@ -1,9 +1,9 @@ import { WindowButtonProps, WindowContentProps } from "@touk/window-manager"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { visualizationUrl } from "../common/VisualizationUrl"; import { useProcessNameValidators } from "../containers/hooks/useProcessNameValidators"; -import HttpService, { ProcessingMode, ScenarioParametersCombination } from "../http/HttpService"; +import HttpService, { ProcessingMode } from "../http/HttpService"; import { WindowContent } from "../windowManager"; import { AddProcessForm, FormValue, TouchedValue } from "./AddProcessForm"; import { extendErrors, mandatoryValueValidator } from "./graph/node-modal/editors/Validators"; @@ -12,6 +12,7 @@ import { NodeValidationError } from "../types"; import { flow, isEmpty, transform } from "lodash"; import { useProcessFormDataOptions } from "./useProcessFormDataOptions"; import { LoadingButtonTypes } from "../windowManager/LoadingButton"; +import { useGetAllCombinations } from "./useGetAllCombinations"; interface AddProcessDialogProps extends WindowContentProps { isFragment?: boolean; @@ -22,7 +23,12 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element { const { t } = useTranslation(); const { isFragment = false, errors = [], ...passProps } = props; const nameValidators = useProcessNameValidators(); - const [value, setState] = useState({ processName: "", processCategory: "", processingMode: "", processEngine: "" }); + const [value, setState] = useState({ + processName: "", + processCategory: "", + processingMode: "" as ProcessingMode, + processEngine: "", + }); const [touched, setTouched] = useState({ processName: false, processCategory: false, @@ -30,8 +36,13 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element { processEngine: false, }); const [processNameFromBackend, setProcessNameFromBackendError] = useState([]); - const [engineSetupErrors, setEngineSetupErrors] = useState>({}); - const [allCombinations, setAllCombinations] = useState([]); + + const { engineSetupErrors, allCombinations } = useGetAllCombinations({ + processCategory: value.processCategory, + processingMode: value.processingMode, + processEngine: value.processEngine, + }); + const engineErrors: NodeValidationError[] = (engineSetupErrors[value.processEngine] ?? []).map((error) => ({ fieldName: "processEngine", errorType: "SaveNotAllowed", @@ -122,12 +133,6 @@ export function AddProcessDialog(props: AddProcessDialogProps): JSX.Element { setTouched(touched); }; - useEffect(() => { - HttpService.fetchScenarioParametersCombinations().then((response) => { - setAllCombinations(response.data.combinations); - setEngineSetupErrors(response.data.engineSetupErrors); - }); - }, []); return ( ; diff --git a/designer/client/src/components/Process/ProcessStateUtils.ts b/designer/client/src/components/Process/ProcessStateUtils.ts index 1fe025efb88..520b380fed2 100644 --- a/designer/client/src/components/Process/ProcessStateUtils.ts +++ b/designer/client/src/components/Process/ProcessStateUtils.ts @@ -1,4 +1,4 @@ -import { PredefinedActionName, ProcessStateType, Scenario } from "./types"; +import { ActionName, PredefinedActionName, ProcessStateType, Scenario } from "./types"; import { descriptionProcessArchived, descriptionFragment, @@ -18,6 +18,12 @@ class ProcessStateUtils { public canArchive = (state: ProcessStateType): boolean => state?.allowedActions.includes(PredefinedActionName.Archive); + public canSeePerformSingleExecution = (state: ProcessStateType): boolean => + state?.visibleActions.includes(PredefinedActionName.PerformSingleExecution); + + public canPerformSingleExecution = (state: ProcessStateType): boolean => + state?.allowedActions.includes(PredefinedActionName.PerformSingleExecution); + getStateDescription({ isArchived, isFragment }: Scenario, processState: ProcessStateType): string { if (isArchived) { return isFragment ? descriptionFragmentArchived() : descriptionProcessArchived(); @@ -60,6 +66,10 @@ class ProcessStateUtils { } return `${name}-${processState?.icon || state?.icon || unknownIcon}`; } + + getActionCustomTooltip(processState: ProcessStateType, actionName: ActionName): string | undefined { + return processState?.actionTooltips[actionName] || undefined; + } } export default new ProcessStateUtils(); diff --git a/designer/client/src/components/Process/types.ts b/designer/client/src/components/Process/types.ts index 36ddc9dd2e8..f5cbd62763a 100644 --- a/designer/client/src/components/Process/types.ts +++ b/designer/client/src/components/Process/types.ts @@ -9,6 +9,7 @@ export enum PredefinedActionName { Archive = "ARCHIVE", UnArchive = "UNARCHIVE", Pause = "PAUSE", + PerformSingleExecution = "PERFORM_SINGLE_EXECUTION", } export type ActionName = string; @@ -69,7 +70,9 @@ export type ProcessName = Scenario["name"]; export type ProcessStateType = { status: StatusType; externalDeploymentId?: string; + visibleActions: Array; allowedActions: Array; + actionTooltips: Record; icon: string; tooltip: string; description: string; diff --git a/designer/client/src/components/RemoteComponent.tsx b/designer/client/src/components/RemoteComponent.tsx index 6c28c975a2b..9f6c7a2b19a 100644 --- a/designer/client/src/components/RemoteComponent.tsx +++ b/designer/client/src/components/RemoteComponent.tsx @@ -1,8 +1,9 @@ -import React from "react"; -import LoaderSpinner from "./spinner/Spinner"; import { FederatedComponent, FederatedComponentProps, getFederatedComponentLoader } from "@touk/federated-component"; -import { NuThemeProvider } from "../containers/theme/nuThemeProvider"; +import React, { useMemo } from "react"; import SystemUtils from "../common/SystemUtils"; +import { NuThemeProvider } from "../containers/theme/nuThemeProvider"; +import { useWindows, WindowKind } from "../windowManager"; +import LoaderSpinner from "./spinner/Spinner"; export const loadExternalReactModule = getFederatedComponentLoader({ Wrapper: NuThemeProvider }); export const loadExternalReactModuleWithAuth = getFederatedComponentLoader({ @@ -13,6 +14,38 @@ export const loadExternalReactModuleWithAuth = getFederatedComponentLoader({ window["loadExternalReactModule"] = loadExternalReactModule; window["loadExternalReactModuleWithAuth"] = loadExternalReactModuleWithAuth; -export const RemoteComponent =

>(props: FederatedComponentProps

) => ( - {...props} fallback={} buildHash={__BUILD_HASH__} /> -); +function PlainRemoteComponentRender

, T = unknown>( + props: FederatedComponentProps

, + ref: React.ForwardedRef, +) { + return ref={ref} {...props} fallback={} buildHash={__BUILD_HASH__} />; +} + +export const PlainRemoteComponent = React.forwardRef(PlainRemoteComponentRender) as

, T = unknown>( + props: FederatedComponentProps

& React.RefAttributes, +) => React.ReactElement; + +export type RemoteToolbarContentProps = { + openRemoteModuleWindow:

>(props: P & { url?: string; title?: string }) => void; +}; + +function RemoteComponentRender

, T = unknown>(props: FederatedComponentProps

, ref: React.ForwardedRef) { + const { open } = useWindows(); + const sharedContext = useMemo( + () => ({ + openRemoteModuleWindow: ({ title, ...props }) => + open({ + kind: WindowKind.remote, + title, + meta: props, + }), + }), + [open], + ); + + return ref={ref} {...sharedContext} {...props} />; +} + +export const RemoteComponent = React.forwardRef(RemoteComponentRender) as

, T = unknown>( + props: FederatedComponentProps

& React.RefAttributes, +) => React.ReactElement; diff --git a/designer/client/src/components/RemoteModuleDialog.tsx b/designer/client/src/components/RemoteModuleDialog.tsx new file mode 100644 index 00000000000..4d44d822dab --- /dev/null +++ b/designer/client/src/components/RemoteModuleDialog.tsx @@ -0,0 +1,61 @@ +import { ModuleUrl } from "@touk/federated-component"; +import { WindowContentProps } from "@touk/window-manager"; +import type { FooterButtonProps } from "@touk/window-manager/cjs/components/window/footer"; +import React, { useCallback, useMemo, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import { WindowContent, WindowKind } from "../windowManager"; +import { LoadingButtonTypes } from "../windowManager/LoadingButton"; +import { RemoteComponent } from "./RemoteComponent"; + +export type RemoteModuleDialogProps = NonNullable; +export type RemoteModuleDialogRef = NonNullable<{ + closeAction?: () => Promise; + adjustButtons: (buttons: { closeButton: FooterButtonProps; confirmButton: FooterButtonProps }) => FooterButtonProps[]; +}>; + +export function RemoteModuleDialog

>({ + close, + ...props +}: WindowContentProps): JSX.Element { + const { + data: { meta: passProps }, + } = props; + + const ref = useRef(); + + const closeAction = useCallback(async () => { + await Promise.all([ref.current?.closeAction?.()]); + close(); + }, [close]); + + const { t } = useTranslation(); + + const closeButton = useMemo( + () => ({ + title: t("dialog.button.cancel", "cancel"), + action: closeAction, + className: LoadingButtonTypes.secondaryButton, + }), + [closeAction, t], + ); + + const confirmButton = useMemo( + () => ({ + title: t("dialog.button.ok", "OK"), + action: closeAction, + }), + [closeAction, t], + ); + + return ( + + ref={ref} {...passProps} /> + + ); +} + +export default RemoteModuleDialog; diff --git a/designer/client/src/components/graph/SelectionContextProvider.tsx b/designer/client/src/components/graph/SelectionContextProvider.tsx index 4f19a426c05..5e09087322f 100644 --- a/designer/client/src/components/graph/SelectionContextProvider.tsx +++ b/designer/client/src/components/graph/SelectionContextProvider.tsx @@ -1,3 +1,4 @@ +import { min } from "lodash"; import React, { createContext, PropsWithChildren, @@ -11,6 +12,7 @@ import React, { } from "react"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; +import { ActionCreators as UndoActionCreators } from "redux-undo"; import { useDebouncedCallback } from "use-debounce"; import { copySelection, @@ -23,17 +25,15 @@ import { selectAll, } from "../../actions/nk"; import { error, success } from "../../actions/notificationActions"; -import { ActionCreators as UndoActionCreators } from "redux-undo"; import * as ClipboardUtils from "../../common/ClipboardUtils"; import { tryParseOrNull } from "../../common/JsonUtils"; import { isInputEvent } from "../../containers/BindKeyboardShortcuts"; +import { useInterval } from "../../containers/Interval"; import { useDocumentListeners } from "../../containers/useDocumentListeners"; import { canModifySelectedNodes, getSelection, getSelectionState } from "../../reducers/selectors/graph"; import { getCapabilities } from "../../reducers/selectors/other"; import { getProcessDefinitionData } from "../../reducers/selectors/settings"; import NodeUtils from "./NodeUtils"; -import { min } from "lodash"; -import { useInterval } from "../../containers/Interval"; const hasTextSelection = () => !!window.getSelection().toString(); diff --git a/designer/client/src/components/graph/node-modal/ParametersUtils.ts b/designer/client/src/components/graph/node-modal/ParametersUtils.ts index 5748225133c..5acfa3dac5f 100644 --- a/designer/client/src/components/graph/node-modal/ParametersUtils.ts +++ b/designer/client/src/components/graph/node-modal/ParametersUtils.ts @@ -34,7 +34,7 @@ const parametersPath = (node) => { export function adjustParameters(node: NodeType, parameterDefinitions: UIParameter[]): AdjustReturn { const path = parametersPath(node); - if (!path) { + if (!path || !parameterDefinitions) { return { adjustedNode: node, unusedParameters: [] }; } @@ -42,7 +42,7 @@ export function adjustParameters(node: NodeType, parameterDefinitions: UIParamet const currentParameters = get(currentNode, path); //TODO: currently dynamic branch parameters are *not* supported... const adjustedParameters = parameterDefinitions - ?.filter((def) => !def.branchParam) + .filter((def) => !def.branchParam) .map((def) => { const currentParam = currentParameters.find((p) => p.name == def.name); const parameterFromDefinition = { diff --git a/designer/client/src/components/graph/node-modal/editors/EditableEditor.tsx b/designer/client/src/components/graph/node-modal/editors/EditableEditor.tsx index 52119f9d3ca..9ba341ffe5c 100644 --- a/designer/client/src/components/graph/node-modal/editors/EditableEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/EditableEditor.tsx @@ -1,5 +1,5 @@ import { isEmpty } from "lodash"; -import React, { forwardRef, useMemo } from "react"; +import React, { forwardRef, ReactNode, useMemo } from "react"; import { VariableTypes } from "../../../../types"; import { UnknownFunction } from "../../../../types/common"; import { editors, EditorType, ExtendedEditor, SimpleEditor } from "./expression/Editor"; @@ -24,7 +24,7 @@ interface Props { onValueChange: (value: string) => void; fieldErrors?: FieldError[]; variableTypes: VariableTypes; - validationLabelInfo?: string; + validationLabelInfo?: ReactNode; placeholder?: string; } diff --git a/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx b/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx index 374e97835df..51d7b19a96a 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx @@ -41,23 +41,19 @@ export default forwardRef(function AceWithSettings( const editor = editorRef.current?.editor; const selection = editor?.session.selection; - // before setting cursor position ensure all position calculations are actual - const prepare = () => editor?.renderer.updateFull(true); - const scrollToView = throttle( () => { - document.activeElement.scrollIntoView({ block: "nearest", inline: "nearest" }); + // before setting cursor position ensure all position calculations are actual + editor?.renderer.updateFull(true); + const activeElement = editor.container.querySelector(".ace_cursor") || document.activeElement; + activeElement.scrollIntoView({ block: "nearest", inline: "nearest" }); }, 150, { leading: false }, ); - editor?.addEventListener("mousedown", prepare); - editor?.addEventListener("mouseup", scrollToView); selection?.on("changeCursor", scrollToView); return () => { - editor?.removeEventListener("mousedown", prepare); - editor?.removeEventListener("mouseup", scrollToView); selection?.off("changeCursor", scrollToView); }; }, []); 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} /> + definitionData?.classes?.map((type) => ({ + value: type.refClazzName as SupportedType, + label: ProcessUtils.humanReadableType(type), + })), + [definitionData?.classes], + ); + + const orderedTypeOptions = useMemo(() => orderBy(typeOptions, (item) => [item.label, item.value], ["asc"]), [typeOptions]); + + const defaultTypeOption = useMemo(() => find(typeOptions, { label: "String" }) || head(typeOptions), [typeOptions]); + return { + orderedTypeOptions, + defaultTypeOption, + }; +} + export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: EditorProps) => { const tableDateContext = useTableState(expressionObj); const [{ rows, columns }, dispatch, rawExpression] = tableDateContext; @@ -97,7 +121,7 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: } }, [expressionObj.expression, onValueChange, rawExpression]); - const { defaultTypeOption, orderedTypeOptions } = useTypeOptions(); + const { defaultTypeOption, orderedTypeOptions } = useTableEditorTypeOptions(); const supportedTypes = useMemo(() => orderedTypeOptions.filter(({ value }) => SUPPORTED_TYPES.includes(value)), [orderedTypeOptions]); useEffect(() => { diff --git a/designer/client/src/components/graph/node-modal/editors/field/MarkdownFormControl.tsx b/designer/client/src/components/graph/node-modal/editors/field/MarkdownFormControl.tsx index 84e2bec9379..340d03ee21b 100644 --- a/designer/client/src/components/graph/node-modal/editors/field/MarkdownFormControl.tsx +++ b/designer/client/src/components/graph/node-modal/editors/field/MarkdownFormControl.tsx @@ -15,6 +15,7 @@ export const MarkdownFormControl = ({ value, onChange, className, children, read {children} , "readOnly isEditMode?: boolean; } -export function useTypeOptions() { +export function useFragmentInputDefinitionTypeOptions() { const definitionData = useSelector(getProcessDefinitionData); + const typeOptions = useMemo( () => definitionData?.classes?.map((type) => ({ - // TODO: Instead of using type assertion type, set refClazzName as a union of available clazzNames - value: type.refClazzName as Value, + value: type.display as string, label: ProcessUtils.humanReadableType(type), })), [definitionData?.classes], @@ -40,7 +40,7 @@ export default function FragmentInputDefinition(props: Props): JSX.Element { const { node, setProperty, isEditMode, showValidation } = passProps; const readOnly = !isEditMode; - const { orderedTypeOptions, defaultTypeOption } = useTypeOptions(); + const { orderedTypeOptions, defaultTypeOption } = useFragmentInputDefinitionTypeOptions(); const addField = useCallback(() => { addElement("parameters", getDefaultFields(defaultTypeOption.value)); diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/TypeSelect.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/TypeSelect.tsx index 0d2611674c7..45576a3c765 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/TypeSelect.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/TypeSelect.tsx @@ -1,5 +1,5 @@ import React, { HTMLProps, useCallback, useState } from "react"; -import Select from "react-select"; +import CreatableSelect from "react-select/creatable"; import { NodeValue } from "../node"; import { selectStyled } from "../../../../stylesheets/SelectStyled"; import { useTheme } from "@mui/material"; @@ -61,7 +61,7 @@ export function TypeSelect({ return ( -