diff --git a/build.sbt b/build.sbt index ade9dbece4f..f7b8aca6623 100644 --- a/build.sbt +++ b/build.sbt @@ -349,6 +349,7 @@ val monocleV = "2.1.0" val jmxPrometheusJavaagentV = "0.18.0" val wireMockV = "2.35.0" val findBugsV = "3.0.2" +val enumeratumV = "1.7.3" // depending on scala version one of this jar lays in Flink lib dir def flinkLibScalaDeps(scalaVersion: String, configurations: Option[String] = None) = forScalaVersion( @@ -742,7 +743,12 @@ lazy val defaultModel = (project in (file("defaultModel"))) .settings(assemblyNoScala("defaultModel.jar"): _*) .settings(publishAssemblySettings: _*) .settings( - name := "nussknacker-default-model" + name := "nussknacker-default-model", + libraryDependencies ++= { + Seq( + "org.scalatest" %% "scalatest" % scalaTestV % Test + ) + } ) .dependsOn(defaultHelpers, extensionsApi % Provided) @@ -1095,6 +1101,10 @@ lazy val testUtils = (project in utils("test-utils")) "io.circe" %% "circe-parser" % circeV, "org.testcontainers" % "testcontainers" % testContainersJavaV, "com.lihaoyi" %% "ujson" % "3.1.2", + "com.github.erosb" % "everit-json-schema" % everitSchemaV exclude ("commons-logging", "commons-logging"), + "com.softwaremill.sttp.tapir" %% "tapir-core" % tapirV, + "com.softwaremill.sttp.tapir" %% "tapir-apispec-docs" % tapirV, + "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "0.7.4", ) ++ forScalaVersion( scalaVersion.value, Seq(), @@ -1479,6 +1489,7 @@ lazy val componentsApi = (project in file("components-api")) 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, "com.vdurmont" % "semver4j" % "3.1.0", @@ -1911,7 +1922,7 @@ lazy val designer = (project in file("designer/server")) "org.postgresql" % "postgresql" % postgresV, "org.flywaydb" % "flyway-core" % flywayV, "org.apache.xmlgraphics" % "fop" % "2.8" exclude ("commons-logging", "commons-logging"), - "com.beachape" %% "enumeratum-circe" % "1.7.3", + "com.beachape" %% "enumeratum-circe" % enumeratumV, "tf.tofu" %% "derevo-circe" % "0.13.0", "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "0.7.4", "com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % tapirV, @@ -1930,6 +1941,8 @@ lazy val designer = (project in file("designer/server")) "io.dropwizard.metrics5" % "metrics-core" % dropWizardV, "io.dropwizard.metrics5" % "metrics-jmx" % dropWizardV, "fr.davit" %% "akka-http-metrics-dropwizard-v5" % "1.7.1", + "org.scalacheck" %% "scalacheck" % scalaCheckV % Test, + "com.github.erosb" % "everit-json-schema" % everitSchemaV exclude ("commons-logging", "commons-logging"), "org.apache.flink" % "flink-metrics-dropwizard" % flinkV % Test, "com.github.tomakehurst" % "wiremock-jre8" % wireMockV % Test, ) ++ forScalaVersion( @@ -1951,6 +1964,7 @@ lazy val designer = (project in file("designer/server")) listenerApi, testUtils % Test, flinkTestUtils % Test, + componentsApi % "test->test", // All DeploymentManager dependencies are added because they are needed to run NussknackerApp* with // dev-application.conf. Currently, we doesn't have a separate classpath for DMs like we have for components. // schemedKafkaComponentsUtils is added because loading the provided liteEmbeddedDeploymentManager causes diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ComponentProvider.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ComponentProvider.scala index 84463d91de7..987c56918c5 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ComponentProvider.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ComponentProvider.scala @@ -1,8 +1,10 @@ package pl.touk.nussknacker.engine.api.component +import cats.data.NonEmptySet import com.typesafe.config.{Config, ConfigFactory} import com.vdurmont.semver4j.Semver import net.ceedubs.ficus.readers.{ArbitraryTypeReader, ValueReader} +import pl.touk.nussknacker.engine.api.component.Component._ import pl.touk.nussknacker.engine.api.process.ProcessObjectDependencies import pl.touk.nussknacker.engine.version.BuildInfo @@ -18,23 +20,54 @@ trait Component extends Serializable { // like in Service case, but in other cases, Component class is only a factory that returns some other class // and we don't know if this class allow given processing mode or not so the developer have to specify this // by his/her own - def allowedProcessingModes: Option[Set[ProcessingMode]] + def allowedProcessingModes: AllowedProcessingModes +} + +object Component { + sealed trait AllowedProcessingModes + + object AllowedProcessingModes { + case object All extends AllowedProcessingModes + final case class SetOf(allowedProcessingModes: NonEmptySet[ProcessingMode]) extends AllowedProcessingModes + + object SetOf { + + def apply(processingMode: ProcessingMode, processingModes: ProcessingMode*): SetOf = { + new SetOf(NonEmptySet.of(processingMode, processingModes: _*)) + } + + } + + } + + implicit class ToProcessingModes(val allowedProcessingModes: AllowedProcessingModes) extends AnyVal { + + def toProcessingModes: Set[ProcessingMode] = allowedProcessingModes match { + case AllowedProcessingModes.All => ProcessingMode.values.toSet + case AllowedProcessingModes.SetOf(allowedProcessingModesSet) => allowedProcessingModesSet.toSortedSet + } + + } + } trait UnboundedStreamComponent { self: Component => - override def allowedProcessingModes: Option[Set[ProcessingMode]] = Some(Set(ProcessingMode.UnboundedStream)) + override def allowedProcessingModes: AllowedProcessingModes = + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) } trait BoundedStreamComponent { self: Component => - override def allowedProcessingModes: Option[Set[ProcessingMode]] = Some(Set(ProcessingMode.BoundedStream)) + override def allowedProcessingModes: AllowedProcessingModes = + AllowedProcessingModes.SetOf(ProcessingMode.BoundedStream) } trait RequestResponseComponent { self: Component => - override def allowedProcessingModes: Option[Set[ProcessingMode]] = Some(Set(ProcessingMode.RequestResponse)) + override def allowedProcessingModes: AllowedProcessingModes = + AllowedProcessingModes.SetOf(ProcessingMode.RequestResponse) } trait AllProcessingModesComponent { self: Component => - override def allowedProcessingModes: Option[Set[ProcessingMode]] = None + override def allowedProcessingModes: AllowedProcessingModes = AllowedProcessingModes.All } object ComponentProviderConfig { diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ProcessingMode.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ProcessingMode.scala index f737f2c0417..a9cc99a5281 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ProcessingMode.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/component/ProcessingMode.scala @@ -1,35 +1,43 @@ package pl.touk.nussknacker.engine.api.component +import cats.Order +import enumeratum._ import io.circe.{Decoder, Encoder} -sealed trait ProcessingMode { - def value: String - override def toString: String = value -} +import scala.collection.immutable -case object RequestResponseProcessingMode extends ProcessingMode { - override def value: String = "Request-Response" -} +sealed trait ProcessingMode extends EnumEntry -case class StreamProcessingMode(bounded: Boolean) extends ProcessingMode { - override def value: String = s"${if (bounded) "Bounded" else "Unbounded"}-Stream" -} +object ProcessingMode extends Enum[ProcessingMode] { + case object RequestResponse extends ProcessingMode + case object BoundedStream extends ProcessingMode + case object UnboundedStream extends ProcessingMode -object ProcessingMode { + override def values: immutable.IndexedSeq[ProcessingMode] = findValues - val RequestResponse: ProcessingMode = RequestResponseProcessingMode - val UnboundedStream: ProcessingMode = StreamProcessingMode(bounded = false) - val BoundedStream: ProcessingMode = StreamProcessingMode(bounded = true) + private val RequestResponseJsonValue = "Request-Response" + private val BoundedStreamJsonValue = "Bounded-Stream" + private val UnboundedStreamJsonValue = "Unbounded-Stream" - val all: Set[ProcessingMode] = Set(UnboundedStream, BoundedStream, RequestResponse) + implicit class ProcessingModeOps(processingMode: ProcessingMode) { + + def toJsonString: String = processingMode match { + case ProcessingMode.RequestResponse => RequestResponseJsonValue + case ProcessingMode.BoundedStream => BoundedStreamJsonValue + case ProcessingMode.UnboundedStream => UnboundedStreamJsonValue + } + + } - implicit val encoder: Encoder[ProcessingMode] = Encoder.encodeString.contramap(_.value) + implicit val processingModeEncoder: Encoder[ProcessingMode] = Encoder.encodeString.contramap(_.toJsonString) - implicit val decoder: Decoder[ProcessingMode] = Decoder.decodeString.map { - case str if str == RequestResponse.value => RequestResponseProcessingMode - case str if str == UnboundedStream.value => UnboundedStream - case str if str == BoundedStream.value => BoundedStream - case other => throw new IllegalArgumentException(s"Not known processing mode: $other") + implicit val processingModeDecoder: Decoder[ProcessingMode] = Decoder.decodeString.map { + case RequestResponseJsonValue => ProcessingMode.RequestResponse + case BoundedStreamJsonValue => ProcessingMode.BoundedStream + case UnboundedStreamJsonValue => ProcessingMode.UnboundedStream + case other => throw new IllegalArgumentException(s"Unknown processing mode: $other") } + implicit val processingModeOrdering: Ordering[ProcessingMode] = Ordering.by(_.toJsonString) + implicit val processingModeOrder: Order[ProcessingMode] = Order.fromOrdering } diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/process/Source.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/process/Source.scala index d6b5b81f2b2..29bbd4ee54a 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/process/Source.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/process/Source.scala @@ -1,5 +1,7 @@ package pl.touk.nussknacker.engine.api.process +import cats.data.NonEmptySet +import pl.touk.nussknacker.engine.api.component.Component._ import pl.touk.nussknacker.engine.api.component.{Component, ProcessingMode} import pl.touk.nussknacker.engine.api.context.ContextTransformation import pl.touk.nussknacker.engine.api.definition.{Parameter, WithExplicitTypesToExtract} @@ -61,23 +63,39 @@ object SourceFactory { // source is called by for making SourceFactory serialization easier def noParamUnboundedStreamFactory(source: => Source, inputType: TypingResult): SourceFactory = - NoParamSourceFactory(_ => source, inputType, Some(Set(ProcessingMode.UnboundedStream))) + NoParamSourceFactory( + _ => source, + inputType, + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) + ) def noParamUnboundedStreamFactory[T: TypeTag](source: => Source)(implicit ev: T =:!= Nothing): SourceFactory = - NoParamSourceFactory(_ => source, Typed.fromDetailedType[T], Some(Set(ProcessingMode.UnboundedStream))) + NoParamSourceFactory( + _ => source, + Typed.fromDetailedType[T], + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) + ) def noParamUnboundedStreamFactory[T: TypeTag](createSource: NodeId => Source)( implicit ev: T =:!= Nothing ): SourceFactory = - NoParamSourceFactory(createSource, Typed.fromDetailedType[T], Some(Set(ProcessingMode.UnboundedStream))) + NoParamSourceFactory( + createSource, + Typed.fromDetailedType[T], + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) + ) def noParamUnboundedStreamFromClassTag[T: ClassTag](source: => Source)(implicit ev: T =:!= Nothing): SourceFactory = - NoParamSourceFactory(_ => source, Typed.apply[T], Some(Set(ProcessingMode.UnboundedStream))) + NoParamSourceFactory( + _ => source, + Typed.apply[T], + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) + ) case class NoParamSourceFactory( createSource: NodeId => Source, inputType: TypingResult, - override val allowedProcessingModes: Option[Set[ProcessingMode]] + override val allowedProcessingModes: AllowedProcessingModes ) extends SourceFactory with WithExplicitTypesToExtract { 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 index 9a16a22e8c4..141e310c7f7 100644 --- 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 @@ -1,7 +1,7 @@ package pl.touk.nussknacker.engine.api.typed import cats.data.Validated._ -import cats.data.{NonEmptyList, NonEmptySet, ValidatedNel} +import cats.data.{NonEmptyList, ValidatedNel} import cats.implicits.{catsSyntaxValidatedId, _} import org.apache.commons.lang3.ClassUtils import pl.touk.nussknacker.engine.api.typed.typing._ diff --git a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/supertype/CommonSupertypeFinder.scala b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/supertype/CommonSupertypeFinder.scala index 22121a27425..8e5e5c93732 100644 --- a/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/supertype/CommonSupertypeFinder.scala +++ b/components-api/src/main/scala/pl/touk/nussknacker/engine/api/typed/supertype/CommonSupertypeFinder.scala @@ -3,7 +3,6 @@ package pl.touk.nussknacker.engine.api.typed.supertype import cats.data.NonEmptyList import cats.implicits.toTraverseOps import pl.touk.nussknacker.engine.api.typed.supertype.CommonSupertypeFinder.{ - Default, SupertypeClassResolutionStrategy, looseFinder } 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 7ad523e4fc2..08917cb0332 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 @@ -23,7 +23,7 @@ object typing { implicit val encoder: Encoder[TypingResult] = TypeEncoders.typingResultEncoder } - // TODO: Rename to Typed + // TODO: Rename to Typed, maybe NuType? sealed trait TypingResult { // TODO: We should split this method into two or three methods: @@ -133,7 +133,8 @@ object typing { // Thanks to that we avoid nasty types like String | null (String type is nullable as well) // We can avoid this case by changing this folding logic - see the comment there case object TypedNull extends TypingResult { - override def withoutValue: TypedNull.type = TypedNull + + override val withoutValue: TypingResult = this // this value is intentionally `Some(null)` (and not `None`), as TypedNull represents null value override val valueOpt: Some[Null] = Some(null) diff --git a/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/DefaultModelMigrations.scala b/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/DefaultModelMigrations.scala index 49c484eaaa3..185724580b1 100644 --- a/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/DefaultModelMigrations.scala +++ b/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/DefaultModelMigrations.scala @@ -1,10 +1,6 @@ package pl.touk.nussknacker.defaultmodel -import pl.touk.nussknacker.defaultmodel.migrations.{ - GroupByMigration, - RequestResponseSinkValidationModeMigration, - SinkExpressionMigration -} +import pl.touk.nussknacker.defaultmodel.migrations._ import pl.touk.nussknacker.engine.migration.{ProcessMigration, ProcessMigrations} class DefaultModelMigrations extends ProcessMigrations { @@ -13,7 +9,8 @@ class DefaultModelMigrations extends ProcessMigrations { .listOf( GroupByMigration, SinkExpressionMigration, - RequestResponseSinkValidationModeMigration + RequestResponseSinkValidationModeMigration, + DecisionTableParameterNamesMigration ) .processMigrations diff --git a/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/migrations/DecisionTableParameterNamesMigration.scala b/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/migrations/DecisionTableParameterNamesMigration.scala new file mode 100644 index 00000000000..4add44d17d4 --- /dev/null +++ b/defaultModel/src/main/scala/pl/touk/nussknacker/defaultmodel/migrations/DecisionTableParameterNamesMigration.scala @@ -0,0 +1,30 @@ +package pl.touk.nussknacker.defaultmodel.migrations + +import pl.touk.nussknacker.engine.api.MetaData +import pl.touk.nussknacker.engine.api.parameter.ParameterName +import pl.touk.nussknacker.engine.graph.evaluatedparam.Parameter +import pl.touk.nussknacker.engine.graph.node +import pl.touk.nussknacker.engine.graph.node.Enricher +import pl.touk.nussknacker.engine.graph.service.ServiceRef +import pl.touk.nussknacker.engine.migration.NodeMigration + +object DecisionTableParameterNamesMigration extends NodeMigration { + + override val description: String = "Change Decision Table component parameter names" + + override def migrateNode(metaData: MetaData): PartialFunction[node.NodeData, node.NodeData] = { + case enricher @ Enricher(_, service @ ServiceRef(_, params), _, _) => + enricher.copy(service = service.copy(parameters = renameParams(params))) + } + + private def renameParams(params: List[Parameter]) = { + params.map { param => + param.name.value match { + case "Basic Decision Table" => param.copy(name = ParameterName("Decision Table")) + case "Expression" => param.copy(name = ParameterName("Filtering expression")) + case _ => param + } + } + } + +} diff --git a/defaultModel/src/test/scala/pl/touk/nussknacker/defaultModel/migrations/DecisionTableParameterNamesMigrationSpec.scala b/defaultModel/src/test/scala/pl/touk/nussknacker/defaultModel/migrations/DecisionTableParameterNamesMigrationSpec.scala new file mode 100644 index 00000000000..81e16674b39 --- /dev/null +++ b/defaultModel/src/test/scala/pl/touk/nussknacker/defaultModel/migrations/DecisionTableParameterNamesMigrationSpec.scala @@ -0,0 +1,61 @@ +package pl.touk.nussknacker.defaultModel.migrations + +import org.scalatest.freespec.AnyFreeSpecLike +import org.scalatest.matchers.should.Matchers +import pl.touk.nussknacker.defaultmodel.migrations.DecisionTableParameterNamesMigration +import pl.touk.nussknacker.engine.api.{MetaData, StreamMetaData} +import pl.touk.nussknacker.engine.api.parameter.ParameterName +import pl.touk.nussknacker.engine.graph.node.Enricher +import pl.touk.nussknacker.engine.graph.service.ServiceRef +import pl.touk.nussknacker.engine.graph.evaluatedparam.Parameter +import pl.touk.nussknacker.engine.graph.expression.Expression +import pl.touk.nussknacker.engine.spel.Implicits.asSpelExpression + +class DecisionTableParameterNamesMigrationSpec extends AnyFreeSpecLike with Matchers { + + "DecisionTableParameterNamesMigration should be applied" in { + val metaData = MetaData("test", StreamMetaData(Some(1))) + val beforeMigration = Enricher( + id = "decision-table", + service = ServiceRef( + id = "decision-table-service", + parameters = List( + Parameter(ParameterName("Basic Decision Table"), exampleDecisionTableJson), + Parameter(ParameterName("Expression"), "#ROW['age'] > #input.minAge && #ROW['DoB'] != null"), + ) + ), + output = "Output", + ) + val expectedAfterMigration = Enricher( + id = "decision-table", + service = ServiceRef( + id = "decision-table-service", + parameters = List( + Parameter(ParameterName("Decision Table"), exampleDecisionTableJson), + Parameter(ParameterName("Filtering expression"), "#ROW['age'] > #input.minAge && #ROW['DoB'] != null"), + ) + ), + output = "Output", + ) + + val migrated = DecisionTableParameterNamesMigration.migrateNode(metaData)(beforeMigration) + + migrated shouldBe expectedAfterMigration + } + + private lazy val exampleDecisionTableJson = Expression.tabularDataDefinition { + s"""{ + | "columns": [ + | { "name": "name", "type": "java.lang.String" }, + | { "name": "age", "type": "java.lang.Integer" }, + | { "name": "DoB", "type": "java.time.LocalDate" } + | ], + | "rows": [ + | [ "John", "39", null ], + | [ "Lisa", "21", "2003-01-13" ], + | [ "Mark", "54", "1970-12-30" ] + | ] + |}""".stripMargin + } + +} diff --git a/designer/client/cypress/e2e/fragment.cy.ts b/designer/client/cypress/e2e/fragment.cy.ts index 8b92b8813ea..ca3fc3f9dc8 100644 --- a/designer/client/cypress/e2e/fragment.cy.ts +++ b/designer/client/cypress/e2e/fragment.cy.ts @@ -38,6 +38,7 @@ describe("Fragment", () => { cy.get("[id$='option-1']").click({ force: true }); // Display Add list item errors when blank value + cy.get("[data-testid='settings:4']").contains("User defined list").click(); cy.get("[data-testid='settings:4']").find("[id='ace-editor']").type("{enter}"); cy.get("[data-testid='settings:4']").find("[data-testid='form-helper-text']").should("be.visible"); cy.get("[data-testid='settings:4']").contains("This field is mandatory and can not be empty"); @@ -78,6 +79,7 @@ describe("Fragment", () => { toggleSettings(5); cy.get("[data-testid='settings:5']").contains("Any value").click(); cy.get("[id$='option-0']").click({ force: true }); + cy.get("[data-testid='settings:5']").contains("User defined list").click(); cy.get("[data-testid='settings:5']").find("[id='ace-editor']").type("#meta.processName"); cy.get("[data-testid='settings:5']").contains("Typing...").should("not.exist"); cy.get("[data-testid='settings:5']").find("[id='ace-editor']").type("{enter}"); @@ -119,6 +121,44 @@ describe("Fragment", () => { cy.get("@window").find("[data-testid='settings:6']").matchImage(); + // Provide String Fixed value inputMode + cy.get("@window").contains("+").click(); + cy.get("[data-testid='fieldsRow:7']").find("[placeholder='Field name']").type("any_value_with_suggestions_preset"); + toggleSettings(7); + + // Select any value with suggestions Input mode + cy.get("[data-testid='settings:7']").contains("Any value").click(); + cy.get("[id$='option-1']").click({ force: true }); + + // Activate preset mode + cy.get("[data-testid='settings:7']").contains("Preset").click(); + + // Initial value should be disabled when there is no preset selected + cy.get("[data-testid='settings:7']") + .find("label") + .contains(/initial value/i) + .siblings() + .find("input") + .should("be.disabled"); + + // Select available values from Preset + cy.get("[data-testid='settings:7']") + .find("label") + .contains(/preset selection/i) + .siblings() + .eq(0) + .click(); + cy.get("[id$='option-1']").click({ force: true }); + + // Select Initial value + cy.get("[data-testid='settings:7']") + .find("label") + .contains(/initial value/i) + .siblings() + .eq(0) + .click(); + cy.get("[id$='option-1']").click({ force: true }); + cy.get("@window") .contains(/^apply$/i) .click(); @@ -143,7 +183,7 @@ describe("Fragment", () => { cy.intercept("POST", "/api/nodes/*/validation").as("fragmentInputValidation"); cy.get("[model-id^=e2e][model-id$=fragment-test-process]").should("be.visible").trigger("dblclick"); - cy.get("#nk-graph-fragment [model-id='input']").should("be.visible"); + cy.get("#nk-graph-fragment [model-id='input']").scrollIntoView().should("be.visible"); cy.wait("@fragmentInputValidation"); cy.get("[data-testid=window]").matchImage(); @@ -153,6 +193,24 @@ describe("Fragment", () => { cy.get('[title="name_string_fixed"]').find('[title="Hint text test"]').should("be.visible"); cy.get('[title="non_boolean_or_string"]').siblings().eq(0).contains("1"); + // any value with suggestions preset verification + cy.get('[title="any_value_with_suggestions_preset"]').siblings().eq(0).as("anyValueWithSuggestionField"); + + cy.get("@anyValueWithSuggestionField").find("input").should("have.value", "Email Marketing 12.2019"); + cy.get("@anyValueWithSuggestionField").clear().type("Campaign 2020"); + cy.get("[id$='option-0']").click({ force: true }); + cy.get("@anyValueWithSuggestionField").find("input").should("have.value", "Campaign 2020 News"); + cy.get("@anyValueWithSuggestionField").find('[title="Switch to expression mode"]').click(); + cy.get("@anyValueWithSuggestionField").contains('{"key":"9d6d4e3e-0ba6-43bb-8696-58432e8f6bd8","label":"Campaign '); + cy.get("@anyValueWithSuggestionField").find("#ace-editor").type("{selectall}t"); + cy.get("@anyValueWithSuggestionField") + .find('[title="Expression must be valid JSON to switch to basic mode"]') + .should("be.disabled"); + cy.get("@anyValueWithSuggestionField") + .find("#ace-editor") + .type("{selectall}{backspace}") + .type('{"key": "9d6d4e3e-0ba6-43bb-8696-58432e8f6bd8", "label": "Campaign 2020 News"}', { parseSpecialCharSequences: false }); + cy.get("[data-testid=window]").find("input[value=testOutput]").type("{selectall}fragmentResult"); cy.contains(/^apply/i) .should("be.enabled") @@ -198,6 +256,13 @@ describe("Fragment", () => { cy.contains(/^save\*$/i).click(); cy.contains(/^ok$/i).click(); + // Verify if Frontend received correct data after save + cy.get("[model-id^=e2e][model-id$=fragment-test-process]").trigger("dblclick"); + cy.get('[title="any_value_with_suggestions_preset"]').siblings().eq(0).find("input").should("have.value", "Campaign 2020 News"); + cy.contains(/^apply/i) + .should("be.enabled") + .click(); + // Go back to the fragment cy.go(-1); @@ -205,7 +270,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:7']").find("[placeholder='Field name']").type("test5"); + cy.get("[data-testid='fieldsRow:8']").find("[placeholder='Field name']").type("test5"); cy.get("@window") .contains(/^apply$/i) .click(); 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 cdc9a0e41be..c05ec8e7e6f 100644 --- a/designer/client/src/components/graph/node-modal/editors/EditableEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/EditableEditor.tsx @@ -8,7 +8,7 @@ import { ExpressionLang, ExpressionObj } from "./expression/types"; import { ParamType } from "./types"; import { FieldError, PossibleValue } from "./Validators"; import { cx } from "@emotion/css"; -import { FormControl } from "@mui/material"; +import { FormControl, FormLabel } from "@mui/material"; import ErrorBoundary from "../../../common/ErrorBoundary"; interface Props { @@ -69,7 +69,7 @@ function EditableEditorRow({ return ( <> - {fieldLabel && renderFieldLabel?.(fieldLabel)} + {fieldLabel ? renderFieldLabel?.(fieldLabel) : } diff --git a/designer/client/src/components/graph/node-modal/editors/expression/DictParameterEditor/DictParameterEditor.tsx b/designer/client/src/components/graph/node-modal/editors/expression/DictParameterEditor/DictParameterEditor.tsx index 1aef7b6ac08..b9ca3e6165f 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/DictParameterEditor/DictParameterEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/DictParameterEditor/DictParameterEditor.tsx @@ -4,7 +4,7 @@ import HttpService, { ProcessDefinitionDataDictOption } from "../../../../../../ import { getScenario } from "../../../../../../reducers/selectors/graph"; import { useSelector } from "react-redux"; import { debounce } from "@mui/material/utils"; -import { SimpleEditor } from "../Editor"; +import { ExtendedEditor } from "../Editor"; import { ExpressionObj } from "../types"; import { FieldError } from "../../Validators"; import { ParamType } from "../../types"; @@ -14,6 +14,7 @@ import i18next from "i18next"; import ValidationLabels from "../../../../../modals/ValidationLabels"; import { cx } from "@emotion/css"; import { isEmpty } from "lodash"; +import { tryParseOrNull } from "../../../../../../common/JsonUtils"; interface Props { expressionObj: ExpressionObj; @@ -24,7 +25,7 @@ interface Props { readOnly: boolean; } -export const DictParameterEditor: SimpleEditor = ({ +export const DictParameterEditor: ExtendedEditor = ({ fieldErrors, expressionObj, param, @@ -47,16 +48,18 @@ export const DictParameterEditor: SimpleEditor = ({ const [inputValue, setInputValue] = useState(""); const [isFetching, setIsFetching] = useState(false); + const dictId = param.editor.dictId || param.editor?.simpleEditor?.dictId; + const fetchProcessDefinitionDataDict = useCallback( async (inputValue: string) => { setIsFetching(true); - const { data } = await HttpService.fetchProcessDefinitionDataDict(scenario.processingType, param.editor.dictId, inputValue); + const { data } = await HttpService.fetchProcessDefinitionDataDict(scenario.processingType, dictId, inputValue); setIsFetching(false); return data; }, - [param.editor.dictId, scenario.processingType], + [dictId, scenario.processingType], ); const debouncedUpdateOptions = useMemo(() => { @@ -71,6 +74,7 @@ export const DictParameterEditor: SimpleEditor = ({ return ( (
= ({ options={options} filterOptions={(x) => x} onChange={(_, value) => { - onValueChange(JSON.stringify(value) || ""); + onValueChange(value ? JSON.stringify(value) : ""); setValue(value); setOpen(false); }} @@ -122,3 +126,12 @@ export const DictParameterEditor: SimpleEditor = ({ ); }; + +const isParseable = (expression: ExpressionObj): boolean => { + return tryParseOrNull(expression.expression); +}; + +DictParameterEditor.switchableToHint = () => i18next.t("editors.dictParameter.switchableToHint", "Switch to basic mode"); +DictParameterEditor.notSwitchableToHint = () => + i18next.t("editors.dictParameter.notSwitchableToHint", "Expression must be valid JSON to switch to basic mode"); +DictParameterEditor.isSwitchableTo = (expressionObj: ExpressionObj) => isParseable(expressionObj) || isEmpty(expressionObj.expression); diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/FieldsSelect.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/FieldsSelect.tsx index aaeab394896..82ff4e58a4c 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/FieldsSelect.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/FieldsSelect.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from "react"; -import { FixedValuesPresets, NodeValidationError, Parameter, VariableTypes } from "../../../../types"; +import { NodeValidationError, Parameter, VariableTypes } from "../../../../types"; import { DndItems } from "../../../common/dndItems/DndItems"; import { NodeRowFieldsProvider } from "../node-row-fields-provider"; import { Item, onChangeType, FragmentInputParameter } from "./item"; @@ -20,25 +20,11 @@ interface FieldsSelectProps { readOnly?: boolean; showValidation?: boolean; variableTypes: VariableTypes; - fixedValuesPresets: FixedValuesPresets; errors: NodeValidationError[]; } export function FieldsSelect(props: FieldsSelectProps): JSX.Element { - const { - fields, - label, - namespace, - options, - onChange, - variableTypes, - removeField, - addField, - readOnly, - showValidation, - fixedValuesPresets, - errors, - } = props; + const { fields, label, namespace, options, onChange, variableTypes, removeField, addField, readOnly, showValidation, errors } = props; const ItemElement = useCallback( ({ index, item, errors }: { index: number; item: FragmentInputParameter; errors: NodeValidationError[] }) => { @@ -52,12 +38,11 @@ export function FieldsSelect(props: FieldsSelectProps): JSX.Element { readOnly={readOnly} variableTypes={variableTypes} showValidation={showValidation} - fixedValuesPresets={fixedValuesPresets} errors={errors} /> ); }, - [namespace, onChange, options, readOnly, variableTypes, showValidation, fixedValuesPresets], + [namespace, onChange, options, readOnly, variableTypes, showValidation], ); const changeOrder = useCallback((value) => onChange(namespace, value), [namespace, onChange]); diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx index abc8d7cfdce..35bf942f884 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx @@ -40,7 +40,6 @@ export default function FragmentInputDefinition(props: Props): JSX.Element { const readOnly = !isEditMode; const { orderedTypeOptions, defaultTypeOption } = useTypeOptions(); - const definitionData = useSelector(getProcessDefinitionData); const addField = useCallback(() => { addElement("parameters", getDefaultFields(defaultTypeOption.value)); @@ -61,7 +60,6 @@ export default function FragmentInputDefinition(props: Props): JSX.Element { showValidation={showValidation} readOnly={readOnly} variableTypes={variableTypes} - fixedValuesPresets={definitionData.fixedValuesPresets} errors={passProps.errors} /> diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/item/Item.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/item/Item.tsx index 72801b3522e..a8a44f0a66d 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/item/Item.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/item/Item.tsx @@ -3,7 +3,7 @@ import { isEqual } from "lodash"; import { TypeSelect } from "../TypeSelect"; import { getValidationErrorsForField } from "../../editors/Validators"; import { Option } from "../FieldsSelect"; -import { FixedValuesPresets, NodeValidationError, ReturnedType, VariableTypes } from "../../../../../types"; +import { NodeValidationError, ReturnedType, VariableTypes } from "../../../../../types"; import SettingsButton from "../buttons/SettingsButton"; import { FieldsRow } from "../FieldsRow"; import { Settings } from "../settings/Settings"; @@ -23,12 +23,11 @@ interface ItemProps { variableTypes: VariableTypes; onChange: (path: string, value: onChangeType) => void; options: Option[]; - fixedValuesPresets: FixedValuesPresets; errors: NodeValidationError[]; } export function Item(props: ItemProps): JSX.Element { - const { index, item, namespace, variableTypes, readOnly, showValidation, onChange, options, fixedValuesPresets, errors } = props; + const { index, item, namespace, variableTypes, readOnly, showValidation, onChange, options, errors } = props; const { getIsOpen, toggleIsOpen } = useFieldsContext(); const isOpen = getIsOpen(item.uuid); @@ -85,7 +84,6 @@ export function Item(props: ItemProps): JSX.Element { item={item} onChange={onChange} variableTypes={variableTypes} - fixedValuesPresets={fixedValuesPresets} readOnly={readOnly} errors={errors} data-testid={`settings:${index}`} diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/item/types.ts b/designer/client/src/components/graph/node-modal/fragment-input-definition/item/types.ts index 13a8b38be72..a656d1bb61a 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/item/types.ts +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/item/types.ts @@ -1,6 +1,6 @@ import { Expression, ReturnedType } from "../../../../../types"; -export type onChangeType = string | number | boolean | FixedValuesOption | FixedValuesOption[] | ValueCompileTimeValidation; +export type onChangeType = string | number | boolean | FixedValuesOption | FixedValuesOption[] | ValueCompileTimeValidation | ValueEditor; export interface ValueCompileTimeValidation { validationExpression: Expression; @@ -13,7 +13,7 @@ export interface FragmentValidation { export enum FixedValuesType { "ValueInputWithFixedValuesProvided" = "ValueInputWithFixedValuesProvided", - "ValueInputWithFixedValuesPreset" = "ValueInputWithFixedValuesPreset", + "ValueInputWithDictEditor" = "ValueInputWithDictEditor", } export enum InputMode { @@ -38,11 +38,11 @@ export interface GenericParameterVariant { expression?: Expression; } -interface ValueEditor { +export interface ValueEditor { type: FixedValuesType; fixedValuesList: FixedValuesOption[] | null; allowOtherValue: boolean | null; - fixedValuesPresetId: string | null; + dictId: string; } export interface DefaultParameterVariant extends GenericParameterVariant, FragmentValidation { @@ -52,12 +52,10 @@ export interface DefaultParameterVariant extends GenericParameterVariant, Fragme export interface FixedListParameterVariant extends GenericParameterVariant, FragmentValidation { valueEditor: ValueEditor; - fixedValuesListPresetId: string; presetSelection?: string; } export interface AnyValueWithSuggestionsParameterVariant extends GenericParameterVariant, FragmentValidation { valueEditor: ValueEditor; - fixedValuesListPresetId: string; presetSelection?: string; } export interface AnyValueParameterVariant extends GenericParameterVariant, FragmentValidation { diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/item/utils.ts b/designer/client/src/components/graph/node-modal/fragment-input-definition/item/utils.ts index e74aa2080e9..ecbfe41d75c 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/item/utils.ts +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/item/utils.ts @@ -9,10 +9,7 @@ export const getDefaultFields = (refClazzName: string): FragmentInputParameter = required: false, hintText: "", initialValue: null, - // fixedValuesType: FixedValuesType.UserDefinedList, valueEditor: null, - // fixedValuesListPresetId: "", - // presetSelection: "", valueCompileTimeValidation: null, typ: { refClazzName } as ReturnedType, }; diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/Settings.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/Settings.tsx index 954c989a6ed..d94d88e5d9f 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/Settings.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/Settings.tsx @@ -1,6 +1,6 @@ import React from "react"; import { onChangeType, FragmentInputParameter, isStringOrBooleanVariant } from "../item"; -import { FixedValuesPresets, NodeValidationError, VariableTypes } from "../../../../../types"; +import { NodeValidationError, VariableTypes } from "../../../../../types"; import { DefaultVariant, StringBooleanVariant } from "./variants"; interface Settings { @@ -8,7 +8,6 @@ interface Settings { path: string; variableTypes: VariableTypes; onChange: (path: string, value: onChangeType) => void; - fixedValuesPresets: FixedValuesPresets; readOnly: boolean; errors: NodeValidationError[]; } diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariant.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariant.tsx index c98e5bf909b..6093f8a928d 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariant.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariant.tsx @@ -10,7 +10,7 @@ import { onChangeType, StringOrBooleanParameterVariant, } from "../../item"; -import { FixedValuesPresets, NodeValidationError, VariableTypes } from "../../../../../../types"; +import { NodeValidationError, VariableTypes } from "../../../../../../types"; import { useTranslation } from "react-i18next"; import { AnyValueVariant, AnyValueWithSuggestionVariant, FixedListVariant } from "./StringBooleanVariants"; @@ -19,12 +19,11 @@ interface Props { onChange: (path: string, value: onChangeType) => void; path: string; variableTypes: VariableTypes; - fixedValuesPresets: FixedValuesPresets; readOnly: boolean; errors: NodeValidationError[]; } -export const StringBooleanVariant = ({ item, path, variableTypes, onChange, fixedValuesPresets, readOnly, errors, ...props }: Props) => { +export const StringBooleanVariant = ({ item, path, variableTypes, onChange, readOnly, errors, ...props }: Props) => { const inputModeOptions = [ { label: "Fixed list", value: InputMode.FixedList }, { label: "Any value with suggestions", value: InputMode.AnyValueWithSuggestions }, @@ -54,7 +53,6 @@ export const StringBooleanVariant = ({ item, path, variableTypes, onChange, fixe item={item} inputModeOptions={inputModeOptions} readOnly={readOnly} - fixedValuesPresets={fixedValuesPresets} errors={errors} /> {isAnyValueParameter(item) && ( @@ -72,7 +70,6 @@ export const StringBooleanVariant = ({ item, path, variableTypes, onChange, fixe item={item} onChange={onChange} path={path} - fixedValuesPresets={fixedValuesPresets} readOnly={readOnly} variableTypes={variableTypes} errors={errors} @@ -84,7 +81,6 @@ export const StringBooleanVariant = ({ item, path, variableTypes, onChange, fixe onChange={onChange} path={path} variableTypes={variableTypes} - fixedValuesPresets={fixedValuesPresets} readOnly={readOnly} errors={errors} /> diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx index 8a9ef0a51e4..3b6a136d6d4 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/AnyValueWithSuggestionVariant.tsx @@ -2,44 +2,41 @@ import React from "react"; import InitialValue from "../fields/InitialValue"; import { SettingLabelStyled } from "../fields/StyledSettingsComponnets"; import { TextAreaNode } from "../../../../../../FormElements"; -import { AnyValueWithSuggestionsParameterVariant, FixedValuesType, onChangeType } from "../../../item"; +import { AnyValueWithSuggestionsParameterVariant, onChangeType } from "../../../item"; import { useTranslation } from "react-i18next"; import { FixedValuesSetting } from "../fields/FixedValuesSetting"; -import { FixedValuesPresets, NodeValidationError, VariableTypes } from "../../../../../../../types"; +import { NodeValidationError, VariableTypes } from "../../../../../../../types"; import { ValidationsFields } from "../fields/validation"; import { getValidationErrorsForField } from "../../../../editors/Validators"; import { FormControl } from "@mui/material"; +import { FixedValuesGroup } from "../fields/FixedValuesGroup"; interface Props { item: AnyValueWithSuggestionsParameterVariant; onChange: (path: string, value: onChangeType) => void; path: string; variableTypes: VariableTypes; - fixedValuesPresets: FixedValuesPresets; readOnly: boolean; errors: NodeValidationError[]; } -export const AnyValueWithSuggestionVariant = ({ item, path, onChange, variableTypes, fixedValuesPresets, readOnly, errors }: Props) => { +export const AnyValueWithSuggestionVariant = ({ item, path, onChange, variableTypes, readOnly, errors }: Props) => { const { t } = useTranslation(); - const presetListItemOptions = fixedValuesPresets?.[item.fixedValuesListPresetId] ?? []; - const fixedValuesList = item.valueEditor.fixedValuesList ?? []; const fixedValuesType = item.valueEditor.type; return ( <> - {/**/} + diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/FixedListVariant.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/FixedListVariant.tsx index edf12dea824..226fffbf62e 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/FixedListVariant.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/StringBooleanVariants/FixedListVariant.tsx @@ -1,44 +1,41 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { FixedListParameterVariant, FixedValuesType, onChangeType } from "../../../item"; +import { FixedListParameterVariant, onChangeType } from "../../../item"; import InitialValue from "../fields/InitialValue"; import { FixedValuesSetting } from "../fields/FixedValuesSetting"; import { SettingLabelStyled } from "../fields/StyledSettingsComponnets"; import { TextAreaNode } from "../../../../../../FormElements"; -import { FixedValuesPresets, NodeValidationError, VariableTypes } from "../../../../../../../types"; +import { NodeValidationError, VariableTypes } from "../../../../../../../types"; import { getValidationErrorsForField } from "../../../../editors/Validators"; import { FormControl } from "@mui/material"; +import { FixedValuesGroup } from "../fields/FixedValuesGroup"; interface Props { item: FixedListParameterVariant; onChange: (path: string, value: onChangeType) => void; path: string; - fixedValuesPresets: FixedValuesPresets; readOnly: boolean; variableTypes: VariableTypes; errors: NodeValidationError[]; } -export const FixedListVariant = ({ item, path, onChange, fixedValuesPresets, readOnly, variableTypes, errors }: Props) => { +export const FixedListVariant = ({ item, path, onChange, readOnly, variableTypes, errors }: Props) => { const { t } = useTranslation(); - const presetListItemOptions = fixedValuesPresets?.[item.fixedValuesListPresetId] ?? []; - const fixedValuesList = item.valueEditor.fixedValuesList ?? []; const fixedValuesType = item.valueEditor.type; return ( <> - {/**/} + { + const [processDefinitionDicts, setProcessDefinitionDicts] = useState([]); + const processingType = useSelector(getProcessingType); + + useEffect(() => { + httpService.fetchAllProcessDefinitionDataDicts(processingType, typ).then((response) => { + setProcessDefinitionDicts(response.data.map(({ id, label }) => ({ label, value: id }))); + }); + }, [processingType, typ]); + + return { processDefinitionDicts }; +}; diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesGroup.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesGroup.tsx index 3b091bc1528..911e45e7809 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesGroup.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesGroup.tsx @@ -1,17 +1,18 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { SettingLabelStyled, StyledFormControlLabel } from "./StyledSettingsComponnets"; -import { FormControl, FormControlLabel, Radio, RadioGroup } from "@mui/material"; -import { FixedValuesType, onChangeType } from "../../../item"; +import { FormControl, FormControlLabel, Radio, RadioGroup, Typography, useTheme } from "@mui/material"; +import { AnyValueWithSuggestionsParameterVariant, FixedListParameterVariant, FixedValuesType, onChangeType } from "../../../item"; interface FixedValuesGroup { + item: AnyValueWithSuggestionsParameterVariant | FixedListParameterVariant; onChange: (path: string, value: onChangeType) => void; path: string; fixedValuesType: FixedValuesType; readOnly: boolean; } -export function FixedValuesGroup({ onChange, path, fixedValuesType, readOnly }: FixedValuesGroup) { +export function FixedValuesGroup({ item, onChange, path, fixedValuesType, readOnly }: FixedValuesGroup) { const { t } = useTranslation(); return ( @@ -22,10 +23,16 @@ export function FixedValuesGroup({ onChange, path, fixedValuesType, readOnly }: onChange={(event) => { onChange(`${path}.initialValue`, null); onChange(`${path}.valueEditor.type`, event.target.value); + + if (event.target.value === FixedValuesType.ValueInputWithFixedValuesProvided) { + onChange(`${path}.valueEditor.fixedValuesList`, item?.valueEditor?.fixedValuesList || []); + } else { + onChange(`${path}.valueEditor.dictId`, item.valueEditor.dictId || ""); + } }} > } label={{t("fragment.settings.preset", "Preset")}} /> diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesSetting.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesSetting.tsx index dd22638ff51..8845ce13681 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesSetting.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesSetting.tsx @@ -1,20 +1,15 @@ import React from "react"; -import { SettingLabelStyled } from "./StyledSettingsComponnets"; -import { useTranslation } from "react-i18next"; import { FixedValuesType, onChangeType, FixedValuesOption, FixedListParameterVariant } from "../../../item"; -import { ListItems } from "./ListItems"; -import { Option, TypeSelect } from "../../../TypeSelect"; -import { FixedValuesPresets, NodeValidationError, ReturnedType, VariableTypes } from "../../../../../../../types"; +import { NodeValidationError, ReturnedType, VariableTypes } from "../../../../../../../types"; import { UserDefinedListInput } from "./UserDefinedListInput"; -import { FieldsControl } from "../../../../node-row-fields-provider/FieldsControl"; +import { DictSelect } from "./dictSelect"; interface FixedValuesSetting extends Pick { onChange: (path: string, value: onChangeType) => void; path: string; fixedValuesType: FixedValuesType; fixedValuesList: FixedValuesOption[]; - fixedValuesPresets: FixedValuesPresets; - fixedValuesListPresetId: string; + dictId: string; readOnly: boolean; variableTypes: VariableTypes; errors: NodeValidationError[]; @@ -27,8 +22,7 @@ export function FixedValuesSetting({ path, fixedValuesType, onChange, - fixedValuesListPresetId, - fixedValuesPresets, + dictId, fixedValuesList, readOnly, variableTypes, @@ -37,37 +31,10 @@ export function FixedValuesSetting({ name, initialValue, }: FixedValuesSetting) { - const { t } = useTranslation(); - - const presetListOptions: Option[] = Object.keys(fixedValuesPresets ?? {}).map((key) => ({ label: key, value: key })); - - const selectedPresetValueExpressions: Option[] = (fixedValuesPresets?.[fixedValuesListPresetId] ?? []).map( - (selectedPresetValueExpression) => ({ label: selectedPresetValueExpression.label, value: selectedPresetValueExpression.label }), - ); - return ( <> - {fixedValuesType === FixedValuesType.ValueInputWithFixedValuesPreset && ( - - {t("fragment.presetSelection", "Preset selection:")} - { - onChange(`${path}.fixedValuesListPresetId`, value); - onChange(`${path}.initialValue`, null); - }} - value={presetListOptions.find((presetListOption) => presetListOption.value === fixedValuesListPresetId)} - options={presetListOptions} - fieldErrors={undefined} - /> - {selectedPresetValueExpressions?.length > 0 && ( - - )} - + {fixedValuesType === FixedValuesType.ValueInputWithDictEditor && ( + )} {fixedValuesType === FixedValuesType.ValueInputWithFixedValuesProvided && ( {t("fragment.initialValue", "Initial value:")} - {options ? ( + {item?.valueEditor?.type === FixedValuesType.ValueInputWithFixedValuesProvided ? ( { const selectedOption = options.find((option) => option.label === value); @@ -41,6 +42,16 @@ export default function InitialValue({ onChange, item, path, options, readOnly, placeholder={""} fieldErrors={fieldErrors} /> + ) : item?.valueEditor?.type === FixedValuesType.ValueInputWithDictEditor ? ( + onChange(`${path}.initialValue`, { label: item.valueEditor.dictId, expression: value })} + param={{ editor: { dictId: item.valueEditor.dictId } }} + readOnly={!item.valueEditor.dictId} + /> ) : ( 0 ? item.valueEditor.fixedValuesList : temporaryUserDefinedList; - const setInitialValue = () => { + const setFixedValuesTypeInitialValue = () => { if (item?.valueEditor?.type === FixedValuesType.ValueInputWithFixedValuesProvided) { return onChange( `${path}.initialValue`, - fixedValuesList.find((fixedValuesList) => fixedValuesList.label === item.initialValue.label) + fixedValuesList.find((fixedValuesList) => fixedValuesList.label === item?.initialValue?.label) ? item.initialValue : null, ); } - if (item?.valueEditor?.type === FixedValuesType.ValueInputWithFixedValuesPreset) { - const presetListOptions: Option[] = Object.keys(fixedValuesPresets ?? {}).map((key) => ({ - label: key, - value: key, - })); - - return onChange( - `${path}.initialValue`, - presetListOptions.find((presetListOption) => presetListOption.label === item.initialValue.label) - ? item.initialValue - : null, - ); + if (item?.valueEditor?.type === FixedValuesType.ValueInputWithDictEditor) { + return onChange(`${path}.initialValue`, item.initialValue); } + }; - return onChange(`${path}.initialValue`, null); + const setValueEditor = ({ allowOtherValue }: { allowOtherValue: boolean }): ValueEditor => { + return { + allowOtherValue, + fixedValuesList, + dictId: item?.valueEditor?.dictId || "", + type: item?.valueEditor?.type || FixedValuesType.ValueInputWithDictEditor, + }; }; - if (value === InputMode.AnyValue) { - onChange(`${path}.valueEditor`, null); - } else if (value === InputMode.AnyValueWithSuggestions) { - onChange(`${path}.valueEditor.allowOtherValue`, true); - onChange(`${path}.valueEditor.fixedValuesList`, fixedValuesList); - onChange(`${path}.valueEditor.fixedValuesPresetId`, null); - onChange(`${path}.valueEditor.type`, FixedValuesType.ValueInputWithFixedValuesProvided); - setInitialValue(); - } else { - onChange(`${path}.valueEditor.allowOtherValue`, false); - onChange(`${path}.valueEditor.fixedValuesList`, fixedValuesList); - onChange(`${path}.valueEditor.fixedValuesPresetId`, null); - onChange(`${path}.valueEditor.type`, FixedValuesType.ValueInputWithFixedValuesProvided); - setInitialValue(); + switch (value) { + case InputMode.AnyValue: { + onChange(`${path}.valueEditor`, null); + onChange(`${path}.initialValue`, null); + break; + } + case InputMode.AnyValueWithSuggestions: { + onChange(`${path}.valueEditor`, setValueEditor({ allowOtherValue: true })); + setFixedValuesTypeInitialValue(); + break; + } + + case InputMode.FixedList: { + onChange(`${path}.valueEditor`, setValueEditor({ allowOtherValue: false })); + setFixedValuesTypeInitialValue(); + } } }} value={inputModeOptions.find((inputModeOption) => inputModeOption.value === value)} diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/StyledSettingsComponnets.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/StyledSettingsComponnets.tsx index c1f1ea32818..ead7dd1c786 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/StyledSettingsComponnets.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/StyledSettingsComponnets.tsx @@ -65,11 +65,6 @@ export const CustomSwitch = styled(Switch)` } `; -export const StyledFormControlLabel = styled(FormLabel)` - font-size: 12px; - font-weight: 400; -`; - export const fieldLabel = ({ label, required = false, hintText }: { label: string; required?: boolean; hintText?: string }) => ( {label} diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/dictSelect.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/dictSelect.tsx new file mode 100644 index 00000000000..2867e727419 --- /dev/null +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/dictSelect.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { useGetAllDicts } from "../StringBooleanVariants/useGetAllDicts"; +import { NodeValidationError, ReturnedType } from "../../../../../../../types"; +import { FormControl } from "@mui/material"; +import { SettingLabelStyled } from "./StyledSettingsComponnets"; +import { TypeSelect } from "../../../TypeSelect"; +import { getValidationErrorsForField } from "../../../../editors/Validators"; +import { useTranslation } from "react-i18next"; +import { onChangeType } from "../../../item"; + +interface Props { + typ: ReturnedType; + readOnly: boolean; + dictId: string; + path: string; + onChange: (path: string, value: onChangeType) => void; + errors: NodeValidationError[]; + name: string; +} +export const DictSelect = ({ typ, readOnly, dictId, onChange, path, errors, name }: Props) => { + const { processDefinitionDicts } = useGetAllDicts({ typ }); + const { t } = useTranslation(); + + return ( + + {t("fragment.presetSelection", "Preset selection:")} + { + onChange(`${path}.valueEditor.dictId`, value); + onChange(`${path}.initialValue`, null); + }} + value={processDefinitionDicts.find((presetListOption) => presetListOption.value === dictId) ?? null} + options={processDefinitionDicts} + fieldErrors={getValidationErrorsForField(errors, `$param.${name}.$dictId`)} + /> + + ); +}; diff --git a/designer/client/src/components/graph/node-modal/node/NodeDetails.tsx b/designer/client/src/components/graph/node-modal/node/NodeDetails.tsx index c7d781e6a45..198c14b8369 100644 --- a/designer/client/src/components/graph/node-modal/node/NodeDetails.tsx +++ b/designer/client/src/components/graph/node-modal/node/NodeDetails.tsx @@ -47,12 +47,15 @@ export function NodeDetails(props: NodeDetailsProps): JSX.Element { const dispatch = useDispatch(); const performNodeEdit = useCallback(async () => { - await dispatch(editNode(scenario, node, applyIdFromFakeName(editedNode), outputEdges)); - - //TODO: without removing nodeId query param, the dialog after close, is opening again. It looks like props.close doesn't unmount component. - mergeQuery(parseWindowsQueryParams({}, { nodeId: node.id })); - - props.close(); + try { + //TODO: without removing nodeId query param, the dialog after close, is opening again. It looks like useModalDetailsIfNeeded is fired after edit, because nodeId is still in the query string params, after scenario changes. + mergeQuery(parseWindowsQueryParams({}, { nodeId: node.id })); + await dispatch(editNode(scenario, node, applyIdFromFakeName(editedNode), outputEdges)); + props.close(); + } catch (e) { + //TODO: It's a workaround and continuation of above TODO, let's revert query param deletion, if dialog is still open because of server error + mergeQuery(parseWindowsQueryParams({ nodeId: node.id }, {})); + } }, [scenario, node, editedNode, outputEdges, dispatch, props]); const { t } = useTranslation(); diff --git a/designer/client/src/http/HttpService.ts b/designer/client/src/http/HttpService.ts index 45d37b86e3d..460fd77a6d5 100644 --- a/designer/client/src/http/HttpService.ts +++ b/designer/client/src/http/HttpService.ts @@ -18,7 +18,7 @@ import { } from "../components/Process/types"; import { ToolbarsConfig } from "../components/toolbarSettings/types"; import { AuthenticationSettings } from "../reducers/settings"; -import { Expression, NodeType, ProcessAdditionalFields, ProcessDefinitionData, ScenarioGraph, VariableTypes } from "../types"; +import { Expression, NodeType, ProcessAdditionalFields, ProcessDefinitionData, ReturnedType, ScenarioGraph, VariableTypes } from "../types"; import { Instant, WithId } from "../types/common"; import { BackendNotification } from "../containers/Notifications"; import { ProcessCounts } from "../reducers/graph"; @@ -150,6 +150,7 @@ export interface ScenarioParametersCombinations { } export type ProcessDefinitionDataDictOption = { key: string; label: string }; +type DictOption = { id: string; label: string }; class HttpService { //TODO: Move show information about error to another place. HttpService should avoid only action (get / post / etc..) - handling errors should be in another place. @@ -715,6 +716,23 @@ class HttpService { ); } + fetchAllProcessDefinitionDataDicts(processingType: ProcessingType, { refClazzName }: ReturnedType) { + return api + .post(`/processDefinitionData/${processingType}/dicts`, { + expectedType: { + value: { type: "TypedClass", refClazzName, params: [] }, + }, + }) + .catch((error) => + Promise.reject( + this.#addError( + i18next.t("notification.error.failedToFetchProcessDefinitionDataDict", "Failed to fetch presets"), + error, + ), + ), + ); + } + #addInfo(message: string) { if (this.#notificationActions) { this.#notificationActions.success(message); diff --git a/designer/client/src/types/scenarioGraph.ts b/designer/client/src/types/scenarioGraph.ts index 619c5a3147a..f9710967445 100644 --- a/designer/client/src/types/scenarioGraph.ts +++ b/designer/client/src/types/scenarioGraph.ts @@ -56,8 +56,6 @@ export interface ComponentDefinition { outputParameters?: string[] | null; } -export type FixedValuesPresets = Record; - export interface ProcessDefinitionData { components?: Record; classes?: TypingResult[]; @@ -65,7 +63,6 @@ export interface ProcessDefinitionData { scenarioPropertiesConfig?: ScenarioPropertiesConfig; edgesForNodes?: EdgesForNode[]; customActions?: Array; - fixedValuesPresets?: FixedValuesPresets; } export type EdgesForNode = { diff --git a/designer/client/test/Process/FrgamentInputDefinition/FixedValuesGroup-test.tsx b/designer/client/test/Process/FrgamentInputDefinition/FixedValuesGroup-test.tsx index b6644e8f3b5..df1d642f818 100644 --- a/designer/client/test/Process/FrgamentInputDefinition/FixedValuesGroup-test.tsx +++ b/designer/client/test/Process/FrgamentInputDefinition/FixedValuesGroup-test.tsx @@ -5,6 +5,7 @@ import { describe, expect, it, jest } from "@jest/globals"; import { NuThemeProvider } from "../../../src/containers/theme/nuThemeProvider"; import { FixedValuesGroup } from "../../../src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/FixedValuesGroup"; import { FixedValuesType } from "../../../src/components/graph/node-modal/fragment-input-definition/item"; +import { ReturnedType } from "../../../src/types"; jest.mock("../../../src/brace/theme/nussknacker.js", () => ({})); @@ -28,8 +29,20 @@ describe(FixedValuesGroup.name, () => { , ); diff --git a/designer/client/test/Process/FrgamentInputDefinition/Item-test.tsx b/designer/client/test/Process/FrgamentInputDefinition/Item-test.tsx index 7d8d9552294..eeca72398b5 100644 --- a/designer/client/test/Process/FrgamentInputDefinition/Item-test.tsx +++ b/designer/client/test/Process/FrgamentInputDefinition/Item-test.tsx @@ -44,7 +44,6 @@ describe(Item.name, () => { errors={[]} variableTypes={{}} onChange={mockOnChange} - fixedValuesPresets={{}} options={[ { value: "java.lang.Boolean", diff --git a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/ComponentApiEndpoints.scala b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/ComponentApiEndpoints.scala index 04d588c19fa..c2326e35a6a 100644 --- a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/ComponentApiEndpoints.scala +++ b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/ComponentApiEndpoints.scala @@ -1,6 +1,12 @@ package pl.touk.nussknacker.restmodel.component -import pl.touk.nussknacker.engine.api.component.{ComponentGroupName, ComponentType, DesignerWideComponentId} +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes +import pl.touk.nussknacker.engine.api.component.{ + ComponentGroupName, + ComponentType, + DesignerWideComponentId, + ProcessingMode +} import pl.touk.nussknacker.engine.api.deployment.{ ProcessAction, ProcessActionId, @@ -58,7 +64,8 @@ class ComponentApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEn ) ) ), - usageCount = 2 + usageCount = 2, + allowedProcessingModes = AllowedProcessingModes.SetOf(ProcessingMode.RequestResponse) ) ) ) diff --git a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/package.scala b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/package.scala index 337c5f88ddd..48d044ef1cd 100644 --- a/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/package.scala +++ b/designer/restmodel/src/main/scala/pl/touk/nussknacker/restmodel/component/package.scala @@ -1,15 +1,25 @@ package pl.touk.nussknacker.restmodel +import cats.data.NonEmptySet import io.circe.generic.JsonCodec import io.circe.generic.extras.ConfiguredJsonCodec +import io.circe.syntax.EncoderOps +import io.circe.{Decoder, DecodingFailure, Encoder} +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.ComponentType.ComponentType -import pl.touk.nussknacker.engine.api.component.{ComponentGroupName, ComponentId, DesignerWideComponentId} +import pl.touk.nussknacker.engine.api.component.{ + ComponentGroupName, + ComponentId, + DesignerWideComponentId, + ProcessingMode +} import pl.touk.nussknacker.engine.api.deployment.ProcessAction -import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessName} -import sttp.tapir.Schema +import pl.touk.nussknacker.engine.api.process.ProcessName +import sttp.tapir.{Codec, CodecFormat, Schema, SchemaType} import java.net.URI import java.time.Instant +import scala.collection.immutable.SortedSet package object component { @@ -45,6 +55,30 @@ package object component { def sortMethod(component: ComponentListElement): (String, String) = (component.name, component.id.value) } + implicit val allowedProcessingModesEncoder: Encoder[AllowedProcessingModes] = Encoder.instance { + case AllowedProcessingModes.All => ProcessingMode.values.asJson + case AllowedProcessingModes.SetOf(allowedProcessingModes) => allowedProcessingModes.asJson + } + + implicit val allowedProcessingModesDecoder: Decoder[AllowedProcessingModes] = Decoder.instance { c => + import ProcessingMode.processingModeOrdering + c.as[SortedSet[ProcessingMode]] + .map(NonEmptySet.fromSet) + .flatMap { + case None => Left(DecodingFailure("Set of allowed ProcessingModes cannot be empty", Nil)) + case Some(nonEmptySetOfAllowedProcessingModes) => + if (nonEmptySetOfAllowedProcessingModes.toSortedSet == ProcessingMode.values.toSet) { + Right(AllowedProcessingModes.All) + } else { + Right(AllowedProcessingModes.SetOf(nonEmptySetOfAllowedProcessingModes)) + } + } + } + + implicit val allowedProcessingModesSchema: Schema[AllowedProcessingModes] = Schema( + SchemaType.SArray(Schema.schemaForString)(_.toProcessingModes.map(_.toJsonString)) + ) + @JsonCodec final case class ComponentListElement( id: DesignerWideComponentId, @@ -54,7 +88,8 @@ package object component { componentGroupName: ComponentGroupName, categories: List[String], links: List[ComponentLink], - usageCount: Long + usageCount: Long, + allowedProcessingModes: AllowedProcessingModes ) { def componentId: ComponentId = ComponentId(componentType, name) } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/additionalInfo/AdditionalInfoProviders.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/additionalInfo/AdditionalInfoProviders.scala index 38453c3624a..d837b0bb395 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/additionalInfo/AdditionalInfoProviders.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/additionalInfo/AdditionalInfoProviders.scala @@ -39,7 +39,7 @@ class AdditionalInfoProviders(typeToConfig: ProcessingTypeDataProvider[ModelData user: LoggedUser ): Future[Option[AdditionalInfo]] = { (for { - provider <- OptionT.fromOption[Future](nodeProviders.forType(processingType).flatten) + provider <- OptionT.fromOption[Future](nodeProviders.forProcessingType(processingType).flatten) data <- OptionT(provider(nodeData)) } yield data).value } @@ -49,7 +49,7 @@ class AdditionalInfoProviders(typeToConfig: ProcessingTypeDataProvider[ModelData user: LoggedUser ): Future[Option[AdditionalInfo]] = { (for { - provider <- OptionT.fromOption[Future](propertiesProviders.forType(processingType).flatten) + provider <- OptionT.fromOption[Future](propertiesProviders.forProcessingType(processingType).flatten) data <- OptionT(provider(metaData)) } yield data).value } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DefinitionResources.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DefinitionResources.scala index 0423bf11d06..7f8b4845975 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DefinitionResources.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DefinitionResources.scala @@ -21,7 +21,7 @@ class DefinitionResources( def securedRoute(implicit user: LoggedUser): Route = encodeResponse { pathPrefix("processDefinitionData" / Segment) { processingType => definitionsServices - .forType(processingType) + .forProcessingType(processingType) .map { case (definitionsService) => pathEndOrSingleSlash { get { diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala index cc1daf408c5..59e254fe327 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DictApiHttpService.scala @@ -34,7 +34,7 @@ class DictApiHttpService( .serverLogic { implicit loggedUser: LoggedUser => queryParams => val (processingType, dictId, labelPattern) = queryParams - processingTypeData.forType(processingType) match { + processingTypeData.forProcessingType(processingType) match { case Some((dictQueryService, _, _)) => dictQueryService.queryEntriesByLabel(dictId, labelPattern) match { case Valid(dictEntries) => dictEntries.map(success) @@ -52,7 +52,7 @@ class DictApiHttpService( .serverLogic { implicit loggedUser: LoggedUser => queryParams => val (processingType, dictListRequestDto) = queryParams - processingTypeData.forType(processingType) match { + processingTypeData.forProcessingType(processingType) match { case Some((_, dictionaries, classLoader)) => val decoder = new TypingResultDecoder(ClassUtils.forName(_, classLoader)).decodeTypingResults diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ManagementResources.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ManagementResources.scala index 6421c8e9d0a..8cf39a237ce 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ManagementResources.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ManagementResources.scala @@ -164,7 +164,7 @@ class ManagementResources( parser.parse(scenarioGraphJson).flatMap(Decoder[ScenarioGraph].decodeJson) match { case Right(scenarioGraph) => scenarioTestServices - .forTypeUnsafe(details.processingType) + .forProcessingTypeUnsafe(details.processingType) .performTest( details.idWithNameUnsafe, scenarioGraph, @@ -189,7 +189,7 @@ class ManagementResources( canDeploy(details.idWithNameUnsafe) { complete { measureTime("generateAndTest", metricRegistry) { - val scenarioTestService = scenarioTestServices.forTypeUnsafe(details.processingType) + val scenarioTestService = scenarioTestServices.forProcessingTypeUnsafe(details.processingType) scenarioTestService.generateData( scenarioGraph, processName, @@ -218,7 +218,7 @@ class ManagementResources( path("testWithParameters" / ProcessNameSegment) { processName => { (post & processDetailsForName(processName)) { process => - val modelData = typeToConfig.forTypeUnsafe(process.processingType) + val modelData = typeToConfig.forProcessingTypeUnsafe(process.processingType) implicit val requestDecoder: Decoder[TestFromParametersRequest] = prepareTestFromParametersDecoder(modelData) (post & entity(as[TestFromParametersRequest])) { testParametersRequest => @@ -226,7 +226,7 @@ class ManagementResources( canDeploy(process.idWithNameUnsafe) { complete { scenarioTestServices - .forTypeUnsafe(process.processingType) + .forProcessingTypeUnsafe(process.processingType) .performTest( process.idWithNameUnsafe, testParametersRequest.scenarioGraph, diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/NodesApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/NodesApiHttpService.scala index d462c7e7c11..0c40cc769c8 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/NodesApiHttpService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/NodesApiHttpService.scala @@ -45,11 +45,11 @@ import scala.concurrent.{ExecutionContext, Future} class NodesApiHttpService( authenticator: AuthenticationResources, - typeToConfig: ProcessingTypeDataProvider[ModelData, _], - typeToProcessValidator: ProcessingTypeDataProvider[UIProcessValidator, _], - typeToNodeValidator: ProcessingTypeDataProvider[NodeValidator, _], - typeToExpressionSuggester: ProcessingTypeDataProvider[ExpressionSuggester, _], - typeToParametersValidator: ProcessingTypeDataProvider[ParametersValidator, _], + processingTypeToConfig: ProcessingTypeDataProvider[ModelData, _], + processingTypeToProcessValidator: ProcessingTypeDataProvider[UIProcessValidator, _], + processingTypeToNodeValidator: ProcessingTypeDataProvider[NodeValidator, _], + processingTypeToExpressionSuggester: ProcessingTypeDataProvider[ExpressionSuggester, _], + processingTypeToParametersValidator: ProcessingTypeDataProvider[ParametersValidator, _], protected override val scenarioService: ProcessService )(override protected implicit val executionContext: ExecutionContext) extends BaseHttpService(authenticator) @@ -62,7 +62,7 @@ class NodesApiHttpService( private val nodesApiEndpoints = new NodesApiEndpoints(authenticator.authenticationMethod()) - private val additionalInfoProviders = new AdditionalInfoProviders(typeToConfig) + private val additionalInfoProviders = new AdditionalInfoProviders(processingTypeToConfig) expose { nodesApiEndpoints.nodesAdditionalInfoEndpoint @@ -87,7 +87,7 @@ class NodesApiHttpService( for { scenario <- getScenarioWithDetailsByName(scenarioName) modelData <- getModelData(scenario.processingType) - nodeValidator = typeToNodeValidator.forTypeUnsafe(scenario.processingType) + nodeValidator = processingTypeToNodeValidator.forProcessingTypeUnsafe(scenario.processingType) nodeData <- dtoToNodeRequest(nodeValidationRequestDto, modelData) validation <- getNodeValidation(nodeValidator, scenarioName, nodeData) validationDto = NodeValidationResultDto.apply(validation) @@ -123,8 +123,8 @@ class NodesApiHttpService( for { scenarioWithDetails <- getScenarioWithDetailsByName(scenarioName) scenario = ScenarioGraph(ProcessProperties(request.additionalFields), Nil, Nil) - result = typeToProcessValidator - .forTypeUnsafe(scenarioWithDetails.processingType) + result = processingTypeToProcessValidator + .forProcessingTypeUnsafe(scenarioWithDetails.processingType) .validate(scenario, request.name, scenarioWithDetails.isFragment) validation = NodeValidationResultDto( parameters = None, @@ -144,7 +144,7 @@ class NodesApiHttpService( { case (processingType, request) => for { modelData <- getModelData(processingType) - validator = typeToParametersValidator.forTypeUnsafe(processingType) + validator = processingTypeToParametersValidator.forProcessingTypeUnsafe(processingType) requestWithTypingResult <- dtoToParameterRequest(request, modelData) validationResults = validator.validate(requestWithTypingResult) } yield ParametersValidationResultDto(validationResults, validationPerformed = true) @@ -159,7 +159,7 @@ class NodesApiHttpService( { case (processingType, request) => for { modelData <- getModelData(processingType) - expressionSuggester = typeToExpressionSuggester.forTypeUnsafe(processingType) + expressionSuggester = processingTypeToExpressionSuggester.forProcessingTypeUnsafe(processingType) suggestions <- getSuggestions(expressionSuggester, request, modelData) suggestionDto = suggestions.map(expression => ExpressionSuggestionDto(expression)) } yield suggestionDto @@ -180,8 +180,8 @@ class NodesApiHttpService( processingType: ProcessingType )(implicit user: LoggedUser): EitherT[Future, NodesError, ModelData] = { EitherT.fromEither( - typeToConfig - .forTypeE(processingType) + processingTypeToConfig + .forProcessingTypeE(processingType) .left .map(_ => NoPermission) .flatMap(_.toRight(NoProcessingType(processingType))) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ProcessesExportResources.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ProcessesExportResources.scala index f1d00661161..a0945b557c2 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ProcessesExportResources.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ProcessesExportResources.scala @@ -94,7 +94,7 @@ class ProcessesExportResources( processName: ProcessName, isFragment: Boolean )(implicit user: LoggedUser): HttpResponse = { - val processResolver = processResolvers.forTypeUnsafe(processingType) + val processResolver = processResolvers.forProcessingTypeUnsafe(processingType) val resolvedProcess = processResolver.validateAndResolve(processWithDictLabels, processName, isFragment) fileResponse(resolvedProcess) } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpService.scala index ad2a892919e..983f9be20d4 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioParametersApiHttpService.scala @@ -26,7 +26,7 @@ class ScenarioParametersApiHttpService( val parametersCombination = service.scenarioParametersCombinationsWithWritePermission .sortBy(parameters => - (parameters.processingMode.value, parameters.category, parameters.engineSetupName.value) + (parameters.processingMode.toJsonString, parameters.category, parameters.engineSetupName.value) ) val engineSetupErrors = service.engineSetupErrorsWithWritePermission.filterNot(_._2.isEmpty) ScenarioParametersCombinationWithEngineErrors( diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/TestInfoResources.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/TestInfoResources.scala index 70fcb578f49..feb276c5a29 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/TestInfoResources.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/TestInfoResources.scala @@ -33,7 +33,7 @@ class TestInfoResources( (post & processDetailsForName(processName)) { processDetails => entity(as[ScenarioGraph]) { scenarioGraph => canDeploy(processDetails.idWithNameUnsafe) { - val scenarioTestService = scenarioTestServices.forTypeUnsafe(processDetails.processingType) + val scenarioTestService = scenarioTestServices.forProcessingTypeUnsafe(processDetails.processingType) path("capabilities") { complete { scenarioTestService.getTestingCapabilities(scenarioGraph, processName, processDetails.isFragment) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ValidationResources.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ValidationResources.scala index 778a2f798b8..d7bd0671d25 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ValidationResources.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ValidationResources.scala @@ -31,7 +31,7 @@ class ValidationResources( NuDesignerErrorToHttp.toResponseEither( FatalValidationError.renderNotAllowedAsError( processResolver - .forTypeUnsafe(details.processingType) + .forProcessingTypeUnsafe(details.processingType) .validateBeforeUiResolving(request.scenarioGraph, request.processName, details.isFragment) ) ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/NodesApiEndpoints.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/NodesApiEndpoints.scala index 6bf46adc197..a6661939f27 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/NodesApiEndpoints.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/NodesApiEndpoints.scala @@ -1,6 +1,7 @@ package pl.touk.nussknacker.ui.api.description import cats.implicits.toTraverseOps +import cats.data.NonEmptyList import derevo.circe.{decoder, encoder} import derevo.derive import io.circe.generic.JsonCodec @@ -8,49 +9,77 @@ import io.circe.generic.extras.semiauto.deriveConfiguredDecoder import io.circe.{Decoder, Encoder, Json, KeyDecoder, KeyEncoder} import org.springframework.util.ClassUtils import pl.touk.nussknacker.engine.ModelData -import pl.touk.nussknacker.engine.additionalInfo.AdditionalInfo -import pl.touk.nussknacker.engine.api.ProcessAdditionalFields -import pl.touk.nussknacker.engine.api.definition.ParameterEditor +import pl.touk.nussknacker.engine.additionalInfo.{AdditionalInfo, MarkdownAdditionalInfo} +import pl.touk.nussknacker.engine.api.CirceUtil._ +import pl.touk.nussknacker.engine.api.{LayoutData, ProcessAdditionalFields} +import pl.touk.nussknacker.engine.api.definition.{FixedExpressionValue, ParameterEditor, SimpleParameterEditor} import pl.touk.nussknacker.engine.api.editor.DualEditorMode +import pl.touk.nussknacker.engine.api.generics.ExpressionParseError.ErrorDetails +import pl.touk.nussknacker.engine.api.generics.ExpressionParseError.TabularDataDefinitionParserErrorDetails.CellError import pl.touk.nussknacker.engine.api.graph.{Edge, ProcessProperties, ScenarioGraph} -import pl.touk.nussknacker.engine.api.parameter.ParameterName +import pl.touk.nussknacker.engine.api.parameter.{ + ParameterName, + ParameterValueCompileTimeValidation, + ParameterValueInput +} import pl.touk.nussknacker.engine.api.process.{ProcessName, ProcessingType} import pl.touk.nussknacker.engine.api.typed.TypingResultDecoder import pl.touk.nussknacker.engine.api.typed.typing._ +import pl.touk.nussknacker.engine.graph.EdgeType +import pl.touk.nussknacker.engine.graph.evaluatedparam.{Parameter => EvaluatedParameter} +import pl.touk.nussknacker.engine.graph.evaluatedparam.BranchParameters import pl.touk.nussknacker.engine.graph.expression.Expression +import pl.touk.nussknacker.engine.graph.fragment.FragmentRef import pl.touk.nussknacker.engine.graph.expression.Expression.Language import pl.touk.nussknacker.engine.graph.node.NodeData +import pl.touk.nussknacker.engine.graph.node.{Enricher, Filter} +import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter} import pl.touk.nussknacker.engine.graph.node.NodeData.nodeDataEncoder +import pl.touk.nussknacker.engine.graph.node.{BranchEndDefinition, FragmentInput} +import pl.touk.nussknacker.engine.graph.node._ +import pl.touk.nussknacker.engine.graph.service.ServiceRef +import pl.touk.nussknacker.engine.graph.sink.SinkRef +import pl.touk.nussknacker.engine.graph.source.SourceRef +import pl.touk.nussknacker.engine.graph.variable.Field import pl.touk.nussknacker.engine.spel.ExpressionSuggestion import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions.SecuredEndpoint import pl.touk.nussknacker.restmodel.definition.{UIParameter, UIValueParameter} -import pl.touk.nussknacker.restmodel.validation.ValidationResults.NodeValidationError +import pl.touk.nussknacker.restmodel.validation.ValidationResults.{NodeValidationError, NodeValidationErrorType} import pl.touk.nussknacker.security.AuthCredentials +import pl.touk.nussknacker.ui.suggester.CaretPosition2d +import pl.touk.nussknacker.ui.api.TapirCodecs.ScenarioNameCodec._ import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.NodesError.{ MalformedTypingResult, NoProcessingType, NoScenario } import pl.touk.nussknacker.ui.api.BaseHttpService.CustomAuthorizationError -import pl.touk.nussknacker.ui.suggester.CaretPosition2d +import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.NodeDataSchemas.nodeDataSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas._ +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedClassSchemaHelper.typedClassTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedDictSchemaHelper.typedDictTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedNullSchemaHelper.typedNullTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedObjectSchemaHelper.typedObjectTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedObjectTypingResultSchemaHelper.typedObjectTypingResultTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedTaggedSchemaHelper.typedTaggedTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.TypedUnionSchemaHelper.typedUnionTypeSchema +import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas.UnknownSchemaHelper.unknownTypeSchema import sttp.model.StatusCode.{BadRequest, NotFound, Ok} import sttp.tapir.EndpointIO.Example -import sttp.tapir.SchemaType.SString -import pl.touk.nussknacker.engine.api.CirceUtil._ -import pl.touk.nussknacker.ui.api.description.NodesApiEndpoints.Dtos.{decodeVariableTypes, prepareTypingResultDecoder} -import pl.touk.nussknacker.ui.api.description.TypingDtoSchemas._ +import sttp.tapir.Schema.{SName, Typeclass} +import sttp.tapir.SchemaType.{SProduct, SProductField, SString, SchemaWithValue} import sttp.tapir._ -import pl.touk.nussknacker.ui.api.TapirCodecs.ScenarioNameCodec._ import sttp.tapir.derevo.schema -import sttp.tapir.generic.auto._ import sttp.tapir.json.circe.jsonBody +import java.time.Duration import scala.language.implicitConversions class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpointDefinitions { import NodesApiEndpoints.Dtos._ + lazy val encoder: Encoder[TypingResult] = TypingResult.encoder lazy val nodesAdditionalInfoEndpoint : SecuredEndpoint[(ProcessName, NodeData), NodesError, Option[AdditionalInfo], Any] = { @@ -59,10 +88,38 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi .tag("Nodes") .post .in("nodes" / path[ProcessName]("scenarioName") / "additionalInfo") - .in(jsonBody[NodeData]) + .in( + jsonBody[NodeData] + .example( + Example.of( + summary = Some("Basic node request"), + value = Enricher( + "enricher", + ServiceRef( + "paramService", + List( + EvaluatedParameter(ParameterName("id"), Expression(Language.Spel, "'a'")) + ) + ), + "out", + additionalFields = None + ) + ) + ) + ) .out( statusCode(Ok).and( jsonBody[Option[AdditionalInfo]] + .example( + Example.of( + summary = Some("Additional info for node"), + value = Some( + MarkdownAdditionalInfo( + "\nSamples:\n\n| id | value |\n| --- | ----- |\n| a | generated |\n| b | not existent |\n\nResults for a can be found [here](http://touk.pl?id=a)\n" + ) + ) + ) + ) ) ) .errorOut(scenarioNotFoundErrorOutput) @@ -76,28 +133,92 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi .tag("Nodes") .post .in("nodes" / path[ProcessName]("scenarioName") / "validation") - .in(jsonBody[NodeValidationRequestDto]) + .in( + jsonBody[NodeValidationRequestDto] + .examples( + List( + Example.of( + value = NodeValidationRequestDto( + Filter("id", Expression(Language.Spel, "#longValue > 1"), isDisabled = None, additionalFields = None), + ProcessProperties.apply( + ProcessAdditionalFields(description = None, properties = Map.empty, metaDataType = "") + ), + Map( + "existButString" -> TypingResultInJson( + encoder.apply(Typed[java.lang.String]) + ), + "longValue" -> TypingResultInJson(encoder.apply(Typed[java.lang.Long])) + ), + branchVariableTypes = None, + outgoingEdges = None + ), + summary = Some("Validate correct Filter node") + ), + Example.of( + summary = Some("Validate incorrect Filter node - wrong expression type"), + value = NodeValidationRequestDto( + Filter( + "id", + Expression(Language.Spel, "#existButString"), + isDisabled = None, + additionalFields = None + ), + ProcessProperties.apply( + ProcessAdditionalFields(description = None, properties = Map.empty, metaDataType = "") + ), + Map( + "existButString" -> TypingResultInJson( + encoder.apply(Typed[java.lang.String]) + ), + "longValue" -> TypingResultInJson(encoder.apply(Typed[java.lang.Long])) + ), + branchVariableTypes = None, + outgoingEdges = None + ), + ) + ) + ) + ) .out( statusCode(Ok).and( jsonBody[NodeValidationResultDto] + .examples( + List( + Example.of( + summary = Some("Node validation without errors"), + value = NodeValidationResultDto( + parameters = None, + Some(Typed[java.lang.Boolean]), + validationErrors = List.empty, + validationPerformed = true + ) + ), + Example.of( + summary = Some("Wrong parameter type"), + value = NodeValidationResultDto( + parameters = None, + Some(Unknown), + List( + NodeValidationError( + "ExpressionParserCompilationError", + "Failed to parse expression: Bad expression type, expected: Boolean, found: String", + "There is problem with expression in field Some($expression) - it could not be parsed.", + Some("$expression"), + NodeValidationErrorType.SaveAllowed, + details = None + ) + ), + validationPerformed = true + ) + ) + ) + ) ) ) .errorOut( oneOf[NodesError]( - oneOfVariantFromMatchType( - NotFound, - plainBody[NoScenario] - .example( - Example.of( - summary = Some("No scenario {scenarioName} found"), - value = NoScenario(ProcessName("'example scenario'")) - ) - ) - ), - oneOfVariantFromMatchType( - BadRequest, - plainBody[MalformedTypingResult] - ) + noScenarioExample, + malformedTypingResultExample ) ) .withSecurity(auth) @@ -110,10 +231,26 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi .tag("Nodes") .post .in("properties" / path[ProcessName]("scenarioName") / "additionalInfo") - .in(jsonBody[ProcessProperties]) + .in( + jsonBody[ProcessProperties] + .example( + Example.of( + summary = Some("Proper process properties"), + value = ProcessProperties.apply( + validPropertiesAdditionalFields + ), + ) + ) + ) .out( statusCode(Ok).and( jsonBody[Option[AdditionalInfo]] + .example( + Example.of( + summary = Some("Some additional info for parameters"), + value = Some(MarkdownAdditionalInfo("2 threads will be used on environment '{scenarioName}'")) + ) + ) ) ) .errorOut(scenarioNotFoundErrorOutput) @@ -127,10 +264,80 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi .tag("Nodes") .post .in("properties" / path[ProcessName]("scenarioName") / "validation") - .in(jsonBody[PropertiesValidationRequestDto]) + .in( + jsonBody[PropertiesValidationRequestDto] + .examples( + List( + Example.of( + summary = Some("Validate proper properties"), + value = PropertiesValidationRequestDto( + validPropertiesAdditionalFields, + ProcessName("test") + ) + ), + Example.of( + summary = Some("Validate wrong 'number of threads' property"), + value = PropertiesValidationRequestDto( + ProcessAdditionalFields( + description = None, + Map( + "parallelism" -> "", + "checkpointIntervalInSeconds" -> "", + "numberOfThreads" -> "a", + "spillStateToDisk" -> "true", + "environment" -> "test", + "useAsyncInterpretation" -> "" + ), + "StreamMetaData" + ), + ProcessName("test") + ) + ) + ) + ) + ) .out( statusCode(Ok).and( jsonBody[NodeValidationResultDto] + .examples( + List( + Example.of( + summary = Some("Validation for proper node"), + value = NodeValidationResultDto( + parameters = None, + expressionType = None, + validationErrors = List.empty, + validationPerformed = true + ) + ), + Example.of( + summary = Some("Validation for properties with errors"), + value = NodeValidationResultDto( + parameters = None, + expressionType = None, + List( + NodeValidationError( + "InvalidPropertyFixedValue", + "Property numberOfThreads (Number of threads) has invalid value", + "Expected one of 1, 2, got: a.", + Some("numberOfThreads"), + NodeValidationErrorType.SaveAllowed, + details = None + ), + NodeValidationError( + "UnknownProperty", + "Unknown property parallelism", + "Property parallelism is not known", + Some("parallelism"), + NodeValidationErrorType.SaveAllowed, + details = None + ) + ), + validationPerformed = true + ) + ) + ) + ) ) ) .errorOut(scenarioNotFoundErrorOutput) @@ -144,26 +351,79 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi Any ] = { baseNuApiEndpoint - .summary("Validate node parameters") + .summary("Validate given parameters") .tag("Nodes") .post .in("parameters" / path[ProcessingType]("processingType") / "validate") - .in(jsonBody[ParametersValidationRequestDto]) + .in( + jsonBody[ParametersValidationRequestDto] + .example( + Example.of( + summary = Some("Parameters validation"), + value = ParametersValidationRequestDto( + List( + UIValueParameterDto( + "condition", + TypingResultInJson(encoder.apply(Typed[java.lang.Boolean])), + Expression(Language.Spel, "#input.amount > 2") + ) + ), + Map( + "input" -> + TypingResultInJson( + encoder.apply( + Typed.record( + Map( + "amount" -> + TypedObjectWithValue.apply( + Typed[java.lang.Long].asInstanceOf[TypedClass], + 5L + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) .out( statusCode(Ok).and( jsonBody[ParametersValidationResultDto] + .examples( + List( + Example.of( + summary = Some("Validate correct parameters"), + value = ParametersValidationResultDto( + validationErrors = List.empty, + validationPerformed = true + ) + ), + Example.of( + summary = Some("Validate incorrect parameters"), + value = ParametersValidationResultDto( + List( + NodeValidationError( + "ExpressionParserCompilationError", + "Failed to parse expression: Bad expression type, expected: Boolean, found: Long(5)", + "There is problem with expression in field Some(condition) - it could not be parsed.", + Some("condition"), + NodeValidationErrorType.SaveAllowed, + details = None + ) + ), + validationPerformed = true + ), + ) + ) + ) ) ) .errorOut( oneOf[NodesError]( - oneOfVariantFromMatchType( - NotFound, - plainBody[NoProcessingType] - ), - oneOfVariantFromMatchType( - BadRequest, - plainBody[MalformedTypingResult] - ) + noProcessingTypeExample, + malformedTypingResultExample ) ) .withSecurity(auth) @@ -180,22 +440,71 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi .tag("Nodes") .post .in("parameters" / path[ProcessingType]("processingType") / "suggestions") - .in(jsonBody[ExpressionSuggestionRequestDto]) + .in( + jsonBody[ExpressionSuggestionRequestDto] + .example( + Example.of( + summary = Some("Get suggestions for given expression"), + value = ExpressionSuggestionRequestDto( + Expression(Language.Spel, "#inpu"), + CaretPosition2d(0, 5), + Map( + "input" -> + TypingResultInJson( + encoder.apply( + Typed.record( + Map( + "amount" -> + TypedObjectWithValue.apply( + Typed[java.lang.Long].asInstanceOf[TypedClass], + 5L + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) .out( statusCode(Ok).and( jsonBody[List[ExpressionSuggestionDto]] + .examples( + List( + Example.of( + summary = Some("Found a suggestion for currently given expression"), + value = List( + ExpressionSuggestionDto( + "input", + Typed.record( + Map( + "amount" -> + TypedObjectWithValue.apply( + Typed[java.lang.Long].asInstanceOf[TypedClass], + 5L + ) + ) + ), + fromClass = false, + description = None, + parameters = List.empty + ) + ) + ), + Example.of( + summary = Some("No suggestions found for given expression"), + value = List.empty + ) + ) + ) ) ) .errorOut( oneOf[NodesError]( - oneOfVariantFromMatchType( - NotFound, - plainBody[NoProcessingType] - ), - oneOfVariantFromMatchType( - BadRequest, - plainBody[MalformedTypingResult] - ) + noProcessingTypeExample, + malformedTypingResultExample ) ) .withSecurity(auth) @@ -215,6 +524,58 @@ class NodesApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpoi ) ) + private val validPropertiesAdditionalFields = + ProcessAdditionalFields( + description = None, + Map( + "parallelism" -> "", + "checkpointIntervalInSeconds" -> "", + "numberOfThreads" -> "2", + "spillStateToDisk" -> "true", + "environment" -> "test", + "useAsyncInterpretation" -> "" + ), + "StreamMetaData" + ) + + private val noScenarioExample: EndpointOutput.OneOfVariant[NoScenario] = + oneOfVariantFromMatchType( + NotFound, + plainBody[NoScenario] + .example( + Example.of( + summary = Some("No scenario {scenarioName} found"), + value = NoScenario(ProcessName("'example scenario'")) + ) + ) + ) + + private val malformedTypingResultExample: EndpointOutput.OneOfVariant[MalformedTypingResult] = + oneOfVariantFromMatchType( + BadRequest, + plainBody[MalformedTypingResult] + .example( + Example.of( + summary = Some("Malformed TypingResult sent in request"), + value = MalformedTypingResult( + "Couldn't decode value 'WrongType'. Allowed values: 'TypedUnion,TypedDict,TypedObjectTypingResult,TypedTaggedValue,TypedClass,TypedObjectWithValue,TypedNull,Unknown" + ) + ) + ) + ) + + private val noProcessingTypeExample: EndpointOutput.OneOfVariant[NoProcessingType] = + oneOfVariantFromMatchType( + NotFound, + plainBody[NoProcessingType] + .example( + Example.of( + summary = Some("ProcessingType type: {processingType} not found"), + value = NoProcessingType("'processingType'") + ) + ) + ) + } object NodesApiEndpoints { @@ -237,9 +598,677 @@ object NodesApiEndpoints { implicit lazy val scenarioNameSchema: Schema[ProcessName] = Schema.derived implicit lazy val additionalInfoSchema: Schema[AdditionalInfo] = Schema.derived implicit lazy val scenarioAdditionalFieldsSchema: Schema[ProcessAdditionalFields] = Schema.derived + implicit lazy val scenarioPropertiesSchema: Schema[ProcessProperties] = Schema.derived.hidden(true) + + implicit lazy val parameterSchema: Schema[EvaluatedParameter] = Schema.derived + implicit lazy val edgeTypeSchema: Schema[EdgeType] = Schema.derived + implicit lazy val edgeSchema: Schema[Edge] = Schema.derived + implicit lazy val cellErrorSchema: Schema[CellError] = Schema.derived + implicit lazy val errorDetailsSchema: Schema[ErrorDetails] = Schema.derived + implicit lazy val nodeValidationErrorSchema: Schema[NodeValidationError] = Schema.derived + implicit lazy val fixedExpressionValueSchema: Schema[FixedExpressionValue] = Schema.derived + + implicit lazy val expressionSchema: Schema[Expression] = { + implicit val languageSchema: Schema[Language] = Schema.string[Language] + Schema.derived + } + + implicit lazy val caretPosition2dSchema: Schema[CaretPosition2d] = Schema.derived + + object NodeDataSchemas { + implicit lazy val fragmentRefSchema: Schema[FragmentRef] = Schema.derived + implicit lazy val fragmentClazzRefSchema: Schema[FragmentClazzRef] = Schema.derived + implicit lazy val parameterValueCompileTimeValidationSchema: Schema[ParameterValueCompileTimeValidation] = + Schema.derived + implicit lazy val parameterValueInputSchema: Schema[ParameterValueInput] = Schema.derived + implicit lazy val fragmentParameterSchema: Schema[FragmentParameter] = Schema.derived + implicit lazy val serviceRefSchema: Schema[ServiceRef] = Schema.derived + implicit lazy val branchEndDefinitionSchema: Schema[BranchEndDefinition] = Schema.derived + implicit lazy val userDefinedAdditionalNodeFieldsSchema: Schema[UserDefinedAdditionalNodeFields] = Schema.derived + implicit lazy val layoutDataSchema: Schema[LayoutData] = Schema.derived + implicit lazy val branchParametersSchema: Schema[BranchParameters] = Schema.derived + implicit lazy val fieldSchema: Schema[Field] = Schema.derived + implicit lazy val fragmentOutputVarDefinitionSchema: Schema[FragmentOutputVarDefinition] = Schema.derived + + // Tapir currently supports only json schema v4 which has no way to declare discriminator + // We declare that each type of NodeData belongs to an enum with only one value as a workaround for this problem + private object BranchEndDataSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object BranchEndData extends NodeTypes + } + + implicit lazy val branchEndDataTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val branchEndDataSchema: Schema[BranchEndData] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("definition"), + branchEndDefinitionSchema, + branchEndData => Some(branchEndData.definition) + ), + SProductField( + FieldName("type"), + BranchEndDataSchemaHelper.branchEndDataTypeSchema, + _ => Some(BranchEndDataSchemaHelper.NodeTypes.BranchEndData) + ) + ) + ) + ) + + private object CustomNodeSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object CustomNode extends NodeTypes + } + + implicit lazy val customNodeTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val customNodeSchema: Schema[CustomNode] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + customNode => Some(customNode.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("id"), Schema.string, customNode => Some(customNode.id)), + SProductField(FieldName("nodeType"), Schema.string, customNode => Some(customNode.nodeType)), + SProductField( + FieldName("outputVar"), + Schema.schemaForOption[String], + customNode => Some(customNode.outputVar) + ), + SProductField( + FieldName("parameters"), + Schema.schemaForIterable[EvaluatedParameter, List], + customNode => Some(customNode.parameters) + ), + SProductField( + FieldName("type"), + CustomNodeSchemaHelper.customNodeTypeSchema, + _ => Some(CustomNodeSchemaHelper.NodeTypes.CustomNode) + ), + ) + ) + ) + + private object EnricherSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Enricher extends NodeTypes + } + + implicit lazy val enricherTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val enricherSchema: Schema[Enricher] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + enricher => Some(enricher.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("id"), Schema.string, enricher => Some(enricher.id)), + SProductField(FieldName("output"), Schema.string, enricher => Some(enricher.output)), + SProductField(FieldName("service"), serviceRefSchema, enricher => Some(enricher.service)), + SProductField( + FieldName("type"), + EnricherSchemaHelper.enricherTypeSchema, + _ => Some(EnricherSchemaHelper.NodeTypes.Enricher) + ), + ) + ) + ) + + private object FilterSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Filter extends NodeTypes + } + + implicit lazy val filterTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val filterSchema: Schema[Filter] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + filter => Some(filter.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("expression"), expressionSchema, filter => Some(filter.expression)), + SProductField(FieldName("id"), Schema.string, filter => Some(filter.id)), + SProductField( + FieldName("isDisabled"), + Schema.schemaForOption[Boolean], + filter => Some(filter.isDisabled) + ), + SProductField( + FieldName("type"), + FilterSchemaHelper.filterTypeSchema, + _ => Some(FilterSchemaHelper.NodeTypes.Filter) + ), + ) + ) + ) + + private object FragmentInputSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object FragmentInput extends NodeTypes + } + + implicit val fragmentInputTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val fragmentInputSchema: Schema[FragmentInput] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + fragmentInput => Some(fragmentInput.additionalFields) + ), + SProductField(FieldName("id"), Schema.string, fragmentInput => Some(fragmentInput.id)), + SProductField( + FieldName("isDisabled"), + Schema.schemaForOption[Boolean], + fragmentInput => Some(fragmentInput.isDisabled) + ), + SProductField( + FieldName("fragmentParams"), + Schema.schemaForOption[List[FragmentParameter]], + fragmentInput => Some(fragmentInput.fragmentParams) + ), + SProductField(FieldName("ref"), fragmentRefSchema, fragmentInput => Some(fragmentInput.ref)), + SProductField( + FieldName("type"), + FragmentInputSchemaHelper.fragmentInputTypeSchema, + _ => Some(FragmentInputSchemaHelper.NodeTypes.FragmentInput) + ), + ) + ) + ) + + private object FragmentInputDefinitionSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object FragmentInputDefinition extends NodeTypes + } + + implicit lazy val fragmentInputDefinitionTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val fragmentInputDefinitionSchema: Schema[FragmentInputDefinition] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + fragmentInputDefinition => Some(fragmentInputDefinition.additionalFields) + ), + SProductField( + FieldName("id"), + Schema.string, + fragmentInputDefinition => Some(fragmentInputDefinition.id) + ), + SProductField( + FieldName("parameters"), + Schema.derived[List[FragmentParameter]], + fragmentInputDefinition => Some(fragmentInputDefinition.parameters) + ), + SProductField( + FieldName("type"), + FragmentInputDefinitionSchemaHelper.fragmentInputDefinitionTypeSchema, + _ => Some(FragmentInputDefinitionSchemaHelper.NodeTypes.FragmentInputDefinition) + ), + ) + ) + ) + + private object FragmentOutputDefinitionSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object FragmentOutputDefinition extends NodeTypes + } + + implicit lazy val fragmentOutputDefinitionTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val fragmentOutputDefinitionSchema: Schema[FragmentOutputDefinition] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + fragmentOutputDefinition => Some(fragmentOutputDefinition.additionalFields) + ), + SProductField( + FieldName("id"), + Schema.string, + fragmentOutputDefinition => Some(fragmentOutputDefinition.id) + ), + SProductField( + FieldName("fields"), + Schema.derived[List[Field]], + fragmentInputDefinition => Some(fragmentInputDefinition.fields) + ), + SProductField( + FieldName("outputName"), + Schema.string, + fragmentOutputDefinition => Some(fragmentOutputDefinition.outputName) + ), + SProductField( + FieldName("type"), + FragmentOutputDefinitionSchemaHelper.fragmentOutputDefinitionTypeSchema, + _ => Some(FragmentOutputDefinitionSchemaHelper.NodeTypes.FragmentOutputDefinition) + ), + ) + ) + ) + + private object JoinSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Join extends NodeTypes + } + + implicit lazy val joinTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val joinSchema: Schema[Join] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + join => Some(join.additionalFields) + ), + SProductField( + FieldName("branchParameters"), + Schema.schemaForIterable[BranchParameters, List], + join => Some(join.branchParameters) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("id"), Schema.string, join => Some(join.id)), + SProductField(FieldName("nodeType"), Schema.string, join => Some(join.nodeType)), + SProductField(FieldName("outputVar"), Schema.schemaForOption[String], join => Some(join.outputVar)), + SProductField( + FieldName("parameters"), + Schema.schemaForIterable[EvaluatedParameter, List], + join => Some(join.parameters) + ), + SProductField( + FieldName("type"), + JoinSchemaHelper.joinTypeSchema, + _ => Some(JoinSchemaHelper.NodeTypes.Join) + ), + ) + ) + ) + + private object ProcessorSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Processor extends NodeTypes + } + + implicit lazy val processorTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val processorSchema: Schema[Processor] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + processor => Some(processor.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("id"), Schema.string, processor => Some(processor.id)), + SProductField( + FieldName("isDisabled"), + Schema.schemaForOption[Boolean], + processor => Some(processor.isDisabled) + ), + SProductField(FieldName("service"), serviceRefSchema, processor => Some(processor.service)), + SProductField( + FieldName("type"), + ProcessorSchemaHelper.processorTypeSchema, + _ => Some(ProcessorSchemaHelper.NodeTypes.Processor) + ), + ) + ) + ) - // Request doesn't need valid encoder - @derive(decoder, schema) + private object SinkSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Sink extends NodeTypes + } + + implicit val sinkTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val sinkSchema: Schema[Sink] = { + implicit lazy val sinkRefSchema: Schema[SinkRef] = Schema.derived + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + sink => Some(sink.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("id"), Schema.string, sink => Some(sink.id)), + SProductField(FieldName("isDisabled"), Schema.schemaForOption[Boolean], sink => Some(sink.isDisabled)), + SProductField(FieldName("ref"), sinkRefSchema, sink => Some(sink.ref)), + SProductField( + FieldName("type"), + SinkSchemaHelper.sinkTypeSchema, + _ => Some(SinkSchemaHelper.NodeTypes.Sink) + ), + ) + ) + ) + } + + private object SourceSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Source extends NodeTypes + } + + implicit val sourceTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val sourceSchema: Schema[Source] = { + implicit lazy val sourceRefSchema: Schema[SourceRef] = Schema.derived + Schema( + SchemaType.SProduct( + List( + SProductField(FieldName("id"), Schema.string, source => Some(source.id)), + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + source => Some(source.additionalFields) + ), + SProductField( + FieldName("type"), + SourceSchemaHelper.sourceTypeSchema, + _ => Some(SourceSchemaHelper.NodeTypes.Source) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("ref"), sourceRefSchema, source => Some(source.ref)) + ) + ) + ) + } + + private object SplitSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Split extends NodeTypes + } + + implicit lazy val splitTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val splitSchema: Schema[Split] = + Schema( + SchemaType.SProduct( + List( + SProductField(FieldName("id"), Schema.string, split => Some(split.id)), + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + split => Some(split.additionalFields) + ), + SProductField( + FieldName("type"), + SplitSchemaHelper.splitTypeSchema, + _ => Some(SplitSchemaHelper.NodeTypes.Split) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + ) + ) + ) + + private object SwitchSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Switch extends NodeTypes + } + + implicit lazy val switchTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val switchSchema: Schema[Switch] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + switch => Some(switch.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField( + FieldName("expression"), + Schema.schemaForOption[Expression], + switch => Some(switch.expression) + ), + SProductField(FieldName("exprVal"), Schema.schemaForOption[String], switch => Some(switch.exprVal)), + SProductField(FieldName("id"), Schema.string, switch => Some(switch.id)), + SProductField( + FieldName("type"), + SwitchSchemaHelper.switchTypeSchema, + _ => Some(SwitchSchemaHelper.NodeTypes.Switch) + ), + ) + ) + ) + + private object VariableSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object Variable extends NodeTypes + } + + implicit val variableTypeSchema: Schema[NodeTypes] = Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val variableSchema: Schema[Variable] = { + Schema( + SchemaType.SProduct( + List( + SProductField(FieldName("id"), Schema.string, variable => Some(variable.id)), + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + variable => Some(variable.additionalFields) + ), + SProductField( + FieldName("type"), + VariableSchemaHelper.variableTypeSchema, + _ => Some(VariableSchemaHelper.NodeTypes.Variable) + ), + SProductField(FieldName("varName"), Schema.string, variable => Some(variable.varName)), + SProductField(FieldName("value"), expressionSchema, variable => Some(variable.value)) + ) + ), + Some(SName("Variable")) + ) + } + + private object VariableBuilderSchemaHelper { + sealed trait NodeTypes + + object NodeTypes { + case object VariableBuilder extends NodeTypes + } + + implicit lazy val variableBuilderTypeSchema: Schema[NodeTypes] = + Schema.derivedEnumeration[NodeTypes].defaultStringBased + } + + implicit lazy val variableBuilderSchema: Schema[VariableBuilder] = + Schema( + SchemaType.SProduct( + List( + SProductField( + FieldName("additionalFields"), + Schema.schemaForOption(userDefinedAdditionalNodeFieldsSchema), + variableBuilder => Some(variableBuilder.additionalFields) + ), + SProductField( + FieldName("branchParametersTemplate"), + Schema.schemaForIterable[EvaluatedParameter, List], + _ => None + ), + SProductField(FieldName("id"), Schema.string, variableBuilder => Some(variableBuilder.id)), + SProductField( + FieldName("fields"), + Schema.schemaForIterable[Field, List], + variableBuilder => Some(variableBuilder.fields) + ), + SProductField( + FieldName("type"), + VariableBuilderSchemaHelper.variableBuilderTypeSchema, + _ => Some(VariableBuilderSchemaHelper.NodeTypes.VariableBuilder) + ), + SProductField(FieldName("varName"), Schema.string, variableBuilder => Some(variableBuilder.varName)), + ) + ), + Some(SName("VariableBuilder")) + ) + + implicit lazy val nodeDataSchema: Schema[NodeData] = { + Schema( + SchemaType.SCoproduct( + List( + branchEndDataSchema.title("BranchEndData"), + customNodeSchema.title("CustomNode"), + enricherSchema.title("Enricher"), + filterSchema.title("Filter"), + fragmentInputSchema.title("FragmentInput"), + fragmentInputDefinitionSchema.title("FragmentInputDefinition"), + fragmentOutputDefinitionSchema.title("FragmentOutputDefinition"), + joinSchema.title("Join"), + processorSchema.title("Processor"), + sinkSchema.title("Sink"), + sourceSchema.title("Source"), + splitSchema.title("Split"), + switchSchema.title("Switch"), + variableSchema.title("Variable"), + variableBuilderSchema.title("VariableBuilder") + ), + None + ) { + case branchEnd: BranchEndData => Some(SchemaWithValue(branchEndDataSchema, branchEnd)) + case customNode: CustomNode => Some(SchemaWithValue(customNodeSchema, customNode)) + case enricher: Enricher => Some(SchemaWithValue(enricherSchema, enricher)) + case filter: Filter => Some(SchemaWithValue(filterSchema, filter)) + case fragmentInput: FragmentInput => Some(SchemaWithValue(fragmentInputSchema, fragmentInput)) + case fragmentInputDefinition: FragmentInputDefinition => + Some(SchemaWithValue(fragmentInputDefinitionSchema, fragmentInputDefinition)) + case fragmentOutputDefinition: FragmentOutputDefinition => + Some(SchemaWithValue(fragmentOutputDefinitionSchema, fragmentOutputDefinition)) + case join: Join => Some(SchemaWithValue(joinSchema, join)) + case processor: Processor => Some(SchemaWithValue(processorSchema, processor)) + case sink: Sink => Some(SchemaWithValue(sinkSchema, sink)) + case source: Source => Some(SchemaWithValue(sourceSchema, source)) + case split: Split => Some(SchemaWithValue(splitSchema, split)) + case switch: Switch => Some(SchemaWithValue(switchSchema, switch)) + case variable: Variable => Some(SchemaWithValue(variableSchema, variable)) + case variableBuilder: VariableBuilder => Some(SchemaWithValue(variableBuilderSchema, variableBuilder)) +// This one is more of internal so we don't provide schema for it for outside world + case _: FragmentUsageOutput => None + }, + Some(SName("NodeData")) + ) + } + + } + + // Request doesn't need valid encoder, apart from examples + @derive(encoder, decoder, schema) final case class NodeValidationRequestDto( nodeData: NodeData, processProperties: ProcessProperties, @@ -248,17 +1277,10 @@ object NodesApiEndpoints { outgoingEdges: Option[List[Edge]] ) - object NodeValidationRequestDto { - implicit lazy val nodeDataSchema: Schema[NodeData] = Schema.anyObject - implicit lazy val scenarioPropertiesSchema: Schema[ProcessProperties] = Schema.derived.hidden(true) - implicit val nodeValidationRequestDtoEmptyEncoder: Encoder[NodeValidationRequestDto] = - Encoder.encodeJson.contramap[NodeValidationRequestDto](_ => throw new IllegalStateException) - } - // Response doesn't need valid decoder @derive(encoder, schema) final case class NodeValidationResultDto( - parameters: Option[List[UIParameterDto]], + parameters: Option[List[UIParameter]], expressionType: Option[TypingResult], validationErrors: List[NodeValidationError], validationPerformed: Boolean @@ -268,12 +1290,24 @@ object NodesApiEndpoints { Decoder.instance[NodeValidationResultDto](_ => throw new IllegalStateException) object NodeValidationResultDto { + implicit lazy val simpleParameterEditorSchema: Schema[SimpleParameterEditor] = Schema.derived + implicit lazy val parameterEditorSchema: Schema[ParameterEditor] = Schema.derived + implicit lazy val dualEditorSchema: Schema[DualEditorMode] = Schema.string + implicit lazy val durationSchema: Schema[Duration] = Schema.schemaForJavaDuration + implicit lazy val uiParameterSchema: Schema[UIParameter] = Schema.derived + + implicit lazy val timeSchema: Schema[java.time.temporal.ChronoUnit] = Schema( + SProduct( + List( + SProductField(FieldName("name"), Schema.schemaForString, chronoUnit => Some(chronoUnit.name())), + SProductField(FieldName("duration"), durationSchema, chronoUnit => Some(chronoUnit.getDuration)) + ) + ) + ) def apply(node: NodeValidationResult): NodeValidationResultDto = { new NodeValidationResultDto( - parameters = node.parameters.map { list => - list.map(param => UIParameterDto(param)) - }, + parameters = node.parameters, expressionType = node.expressionType, validationErrors = node.validationErrors, validationPerformed = node.validationPerformed @@ -282,61 +1316,19 @@ object NodesApiEndpoints { } - // Only used in response, no need for valid decoder - @derive(encoder, schema) - final case class UIParameterDto( - name: String, - typ: TypingResult, - editor: ParameterEditor, - defaultValue: Expression, - additionalVariables: Map[String, TypingResult], - variablesToHide: Set[String], - branchParam: Boolean, - hintText: Option[String], - label: String - ) - - private object UIParameterDto { - implicit lazy val parameterEditorSchema: Schema[ParameterEditor] = Schema.derived - implicit lazy val dualEditorSchema: Schema[DualEditorMode] = Schema.string - - implicit lazy val expressionSchema: Schema[Expression] = { - implicit val languageSchema: Schema[Language] = Schema.string[Language] - Schema.derived - } - - implicit lazy val timeSchema: Schema[java.time.temporal.ChronoUnit] = Schema.anyObject - - def apply(param: UIParameter): UIParameterDto = new UIParameterDto( - param.name, - param.typ, - param.editor, - param.defaultValue, - param.additionalVariables, - param.variablesToHide, - param.branchParam, - param.hintText, - param.label - ) - - } - @derive(schema, encoder, decoder) final case class PropertiesValidationRequestDto( additionalFields: ProcessAdditionalFields, name: ProcessName ) - // Request doesn't need valid encoder - @derive(schema, decoder) + // Request doesn't need valid encoder, apart from examples + @derive(schema, encoder, decoder) final case class ParametersValidationRequestDto( parameters: List[UIValueParameterDto], variableTypes: Map[String, TypingResultInJson] ) - implicit val parametersValidationRequestDtoEncoder: Encoder[ParametersValidationRequestDto] = - Encoder.encodeJson.contramap[ParametersValidationRequestDto](_ => throw new IllegalStateException) - // for a sake of generation Open API using Scala 2.12, we have to define it explicitly private implicit def listSchema[T: Schema]: Typeclass[List[T]] = Schema.schemaForIterable[T, List] @@ -346,32 +1338,22 @@ object NodesApiEndpoints { validationPerformed: Boolean ) - // Request doesn't need valid encoder - @derive(schema, decoder) + // Request doesn't need valid encoder, apart from examples + @derive(schema, encoder, decoder) final case class UIValueParameterDto( name: String, typ: TypingResultInJson, expression: Expression ) - implicit lazy val expressionSchema: Schema[Expression] = { - implicit val languageSchema: Schema[Language] = Schema.string[Language] - Schema.derived - } - - implicit lazy val caretPosition2dSchema: Schema[CaretPosition2d] = Schema.derived - - // Request doesn't need valid encoder - @derive(schema, decoder) + // Request doesn't need valid encoder, apart from examples + @derive(schema, encoder, decoder) final case class ExpressionSuggestionRequestDto( expression: Expression, caretPosition2d: CaretPosition2d, variableTypes: Map[String, TypingResultInJson] ) - implicit val expressionSuggestionRequestDtoEncoder: Encoder[ExpressionSuggestionRequestDto] = - Encoder.encodeJson.contramap[ExpressionSuggestionRequestDto](_ => throw new IllegalStateException) - // Response doesn't need valid decoder @derive(schema, encoder) final case class ExpressionSuggestionDto( @@ -519,31 +1501,69 @@ object NodesApiEndpoints { object TypingDtoSchemas { - import pl.touk.nussknacker.engine.api.typed.TypingType - import pl.touk.nussknacker.engine.api.typed.TypingType.TypingType import pl.touk.nussknacker.engine.api.typed.typing._ import sttp.tapir.Schema.SName import sttp.tapir.SchemaType.SProductField import sttp.tapir.{FieldName, Schema, SchemaType} - implicit lazy val typingResult: Schema[TypingResult] = Schema.derived - - implicit lazy val singleTypingResultSchema: Schema[SingleTypingResult] = - Schema.derived.hidden(true) + implicit lazy val typingResult: Schema[TypingResult] = { + Schema( + SchemaType.SCoproduct( + List( + unknownSchema, + typedNullSchema, + typedObjectTypingResultSchema, + typedDictSchema, + typedObjectSchema, + typedClassSchema, + typedUnionSchema, + typedTaggedSchema + ), + None + ) { + case Unknown => Some(SchemaWithValue(unknownSchema, Unknown)) + case TypedNull => Some(SchemaWithValue(typedNullSchema, TypedNull)) + case typedObject: TypedObjectTypingResult => Some(SchemaWithValue(typedObjectTypingResultSchema, typedObject)) + case typedDict: TypedDict => Some(SchemaWithValue(typedDictSchema, typedDict)) + case typedWithValue: TypedObjectWithValue => Some(SchemaWithValue(typedObjectSchema, typedWithValue)) + case typedClass: TypedClass => Some(SchemaWithValue(typedClassSchema, typedClass)) + case union: TypedUnion => Some(SchemaWithValue(typedUnionSchema, union)) + case tagged: TypedTaggedValue => Some(SchemaWithValue(typedTaggedSchema, tagged)) + } + ) + } + implicit lazy val singleTypingResultSchema: Schema[SingleTypingResult] = Schema.derived implicit lazy val additionalDataValueSchema: Schema[AdditionalDataValue] = Schema.derived +// Tapir currently supports only json schema v4 which has no way to declare discriminator +// We declare that each type of TypingResult belongs to an enum with only one value as a workaround for this problem + object TypedObjectTypingResultSchemaHelper { + sealed trait Types + + object Types { + case object TypedObjectTypingResult extends Types + } + + implicit val typedObjectTypingResultTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + } + implicit lazy val typedObjectTypingResultSchema: Schema[TypedObjectTypingResult] = { Schema( SchemaType.SProduct( - sProductFieldForDisplayAndType ::: - List( - SProductField[String, Map[String, TypingResult]]( - FieldName("fields"), - Schema.schemaForMap[TypingResult], - _ => None - ) - ) ::: + List( + sProductFieldForDisplay, + SProductField[TypingResult, TypedObjectTypingResultSchemaHelper.Types]( + FieldName("type"), + typedObjectTypingResultTypeSchema, + _ => Some(TypedObjectTypingResultSchemaHelper.Types.TypedObjectTypingResult) + ), + SProductField[TypingResult, Map[String, TypingResult]]( + FieldName("fields"), + Schema.schemaForMap[TypingResult](Schema.derived[TypingResult]), + _ => None + ) + ) ::: sProductFieldForKlassAndParams ), Some(SName("TypedObjectTypingResult")) @@ -552,13 +1572,31 @@ object TypingDtoSchemas { .as } + object TypedDictSchemaHelper { + sealed trait Types + + object Types { + case object TypedDict extends Types + } + + implicit val typedDictTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + + } + implicit lazy val typedDictSchema: Schema[TypedDict] = { - final case class Dict(id: String, valueType: TypedTaggedValue) + final case class Dict(id: String, valueType: SingleTypingResult) lazy val dictSchema: Schema[Dict] = Schema.derived Schema( SchemaType.SProduct( - sProductFieldForDisplayAndType ::: - List(SProductField[String, Dict](FieldName("dict"), dictSchema, _ => None)) + List( + sProductFieldForDisplay, + SProductField[TypingResult, TypedDictSchemaHelper.Types]( + FieldName("type"), + typedDictTypeSchema, + _ => Some(TypedDictSchemaHelper.Types.TypedDict) + ), + ) ::: + List(SProductField[TypingResult, Dict](FieldName("dict"), dictSchema, _ => None)) ), Some(SName("TypedDict")) ) @@ -569,12 +1607,28 @@ object TypingDtoSchemas { implicit lazy val typedObjectWithDataSchema: Schema[TypedObjectWithData] = Schema.derived.hidden(true) + object TypedTaggedSchemaHelper { + sealed trait Types + + object Types { + case object TypedTaggedValue extends Types + } + + implicit val typedTaggedTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + } + implicit lazy val typedTaggedSchema: Schema[TypedTaggedValue] = { Schema( SchemaType.SProduct( - List(SProductField[String, String](FieldName("tag"), Schema.string, tag => Some(tag))) ::: - sProductFieldForDisplayAndType ::: - sProductFieldForKlassAndParams + List( + SProductField[TypingResult, String](FieldName("tag"), Schema.string, _ => None), + sProductFieldForDisplay, + SProductField[TypingResult, TypedTaggedSchemaHelper.Types]( + FieldName("type"), + typedTaggedTypeSchema, + _ => Some(TypedTaggedSchemaHelper.Types.TypedTaggedValue) + ), + ) ), Some(SName("TypedTaggedValue")) ) @@ -582,11 +1636,29 @@ object TypingDtoSchemas { .as } + object TypedObjectSchemaHelper { + sealed trait Types + + object Types { + case object TypedObjectWithValue extends Types + } + + implicit val typedObjectTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + + } + implicit lazy val typedObjectSchema: Schema[TypedObjectWithValue] = { Schema( SchemaType.SProduct( - List(SProductField[String, Any](FieldName("value"), Schema.any, value => Some(value))) ::: - sProductFieldForDisplayAndType ::: + List( + SProductField[TypingResult, Any](FieldName("value"), Schema.any, value => Some(value)), + sProductFieldForDisplay, + SProductField[TypingResult, TypedObjectSchemaHelper.Types]( + FieldName("type"), + typedObjectTypeSchema, + _ => Some(TypedObjectSchemaHelper.Types.TypedObjectWithValue) + ), + ) ::: sProductFieldForKlassAndParams ), Some(SName("TypedObjectWithValue")) @@ -595,25 +1667,116 @@ object TypingDtoSchemas { .as } + object TypedNullSchemaHelper { + sealed trait Types + + object Types { + case object TypedNull extends Types + } + + implicit val typedNullTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + + } + implicit lazy val typedNullSchema: Schema[TypedNull.type] = - Schema.derived.name(Schema.SName("TypedNull")).title("TypedNull") + Schema( + SchemaType.SProduct( + List( + sProductFieldForDisplay, + SProductField[TypingResult, TypedNullSchemaHelper.Types]( + FieldName("type"), + typedNullTypeSchema, + _ => Some(TypedNullSchemaHelper.Types.TypedNull) + ), + SProductField[TypingResult, String]( + FieldName("refClazzName"), + Schema(SString(), isOptional = true), + _ => None + ), + SProductField[TypingResult, List[TypingResult]]( + FieldName("params"), + Schema.schemaForIterable[TypingResult, List]( + Schema.derived[TypingResult] + ), + _ => Some(List(Unknown)) + ) + ) + ), + Some(SName("TypedNull")), + ) + .title("TypedNull") + .as + + object UnknownSchemaHelper { + sealed trait Types + + object Types { + case object Unknown extends Types + } + + implicit val unknownTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + + } implicit lazy val unknownSchema: Schema[Unknown.type] = - Schema.derived - .name(Schema.SName("Unknown")) + Schema( + SchemaType.SProduct( + List( + sProductFieldForDisplay, + SProductField[TypingResult, UnknownSchemaHelper.Types]( + FieldName("type"), + unknownTypeSchema, + _ => Some(UnknownSchemaHelper.Types.Unknown) + ), + SProductField[TypingResult, String]( + FieldName("refClazzName"), + Schema(SString(), isOptional = true), + _ => None + ), + SProductField[TypingResult, List[TypingResult]]( + FieldName("params"), + Schema.schemaForIterable[TypingResult, List]( + Schema.derived[TypingResult] + ), + _ => Some(List(Unknown)) + ) + ) + ), + Some(SName("Unknown")), + ) .title("Unknown") + .as + + object TypedUnionSchemaHelper { + sealed trait Types + + object Types { + case object TypedUnion extends Types + } + + implicit val typedUnionTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + + } implicit lazy val typedUnionSchema: Schema[TypedUnion] = { Schema( SchemaType.SProduct( - sProductFieldForDisplayAndType ::: - List( - SProductField[String, List[TypingResult]]( - FieldName("union"), - Schema.schemaForArray[TypingResult].as, - _ => Some(List(Unknown)) - ) + List( + sProductFieldForDisplay, + SProductField[TypingResult, TypedUnionSchemaHelper.Types]( + FieldName("type"), + typedUnionTypeSchema, + _ => Some(TypedUnionSchemaHelper.Types.TypedUnion) + ), + SProductField[TypingResult, NonEmptyList[TypingResult]]( + FieldName("union"), + Schema + .schemaForArray[TypingResult](Schema.derived[TypingResult]) + .copy(isOptional = false) + .as, + _ => Some(NonEmptyList(Unknown, List.empty)) ) + ) ), Some(Schema.SName("TypedUnion")) ) @@ -621,40 +1784,52 @@ object TypingDtoSchemas { .as } + object TypedClassSchemaHelper { + sealed trait Types + + object Types { + case object TypedClass extends Types + } + + implicit val typedClassTypeSchema: Schema[Types] = Schema.derivedEnumeration[Types].defaultStringBased + + } + implicit lazy val typedClassSchema: Schema[TypedClass] = { Schema( SchemaType.SProduct( - sProductFieldForDisplayAndType ::: + List( + sProductFieldForDisplay, + SProductField[TypingResult, TypedClassSchemaHelper.Types]( + FieldName("type"), + typedClassTypeSchema, + _ => Some(TypedClassSchemaHelper.Types.TypedClass) + ), + ) ::: sProductFieldForKlassAndParams ), - Some(SName("TypedClass")) + Some(SName("TypedClass")), ) .title("TypedClass") .as } - private lazy val sProductFieldForDisplayAndType: List[SProductField[String]] = { - List( - SProductField[String, String]( - FieldName("display"), - Schema(SString(), isOptional = true), - display => Some(display) - ), - SProductField[String, TypingType]( - FieldName("type"), - Schema.derivedEnumerationValue, - _ => Some(TypingType.Unknown) - ) + private lazy val sProductFieldForDisplay: SProductField[TypingResult] = + SProductField[TypingResult, String]( + FieldName("display"), + Schema(SString(), isOptional = true), + typingResult => Some(typingResult.display) ) - } - private lazy val sProductFieldForKlassAndParams: List[SProductField[String]] = { - lazy val typingResultSchema: Schema[TypingResult] = Schema.derived + private lazy val sProductFieldForKlassAndParams: List[SProductField[TypingResult]] = { + List( - SProductField[String, String](FieldName("refClazzName"), Schema.string, refClazzName => Some(refClazzName)), - SProductField[String, List[TypingResult]]( + SProductField[TypingResult, String](FieldName("refClazzName"), Schema.string, _ => None), + SProductField[TypingResult, List[TypingResult]]( FieldName("params"), - Schema.schemaForIterable[TypingResult, List](typingResultSchema), + Schema.schemaForIterable[TypingResult, List]( + Schema.derived[TypingResult] + ), _ => Some(List(Unknown)) ) ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala index 330a38f1aee..358a36bd96c 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala @@ -1,17 +1,21 @@ package pl.touk.nussknacker.ui.definition +import cats.data.NonEmptySet import pl.touk.nussknacker.engine.ModelData -import pl.touk.nussknacker.engine.api.component.ComponentType +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes +import pl.touk.nussknacker.engine.api.component.{ComponentType, ProcessingMode} import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.definition.component.ComponentDefinitionWithImplementation import pl.touk.nussknacker.engine.definition.component.bultin.BuiltInComponentsDefinitionsPreparer import pl.touk.nussknacker.engine.definition.fragment.FragmentComponentDefinitionExtractor import pl.touk.nussknacker.engine.definition.model.ModelDefinition +import pl.touk.nussknacker.ui.process.processingtype.DesignerModelData class AlignedComponentsDefinitionProvider( builtInComponentsDefinitionsPreparer: BuiltInComponentsDefinitionsPreparer, fragmentComponentDefinitionExtractor: FragmentComponentDefinitionExtractor, - modelDefinition: ModelDefinition + modelDefinition: ModelDefinition, + processingMode: ProcessingMode ) { def getAlignedComponentsWithBuiltInComponentsAndFragments( @@ -42,23 +46,26 @@ class AlignedComponentsDefinitionProvider( fragmentsScenarios: List[CanonicalProcess], ): List[ComponentDefinitionWithImplementation] = for { - scenario <- fragmentsScenarios - definition <- fragmentComponentDefinitionExtractor.extractFragmentComponentDefinition(scenario).toOption + scenario <- fragmentsScenarios + definition <- fragmentComponentDefinitionExtractor + .extractFragmentComponentDefinition(scenario, AllowedProcessingModes.SetOf(processingMode)) + .toOption } yield definition } object AlignedComponentsDefinitionProvider { - def apply(modelData: ModelData): AlignedComponentsDefinitionProvider = { + def apply(designerModelData: DesignerModelData): AlignedComponentsDefinitionProvider = { new AlignedComponentsDefinitionProvider( - new BuiltInComponentsDefinitionsPreparer(modelData.componentsUiConfig), + new BuiltInComponentsDefinitionsPreparer(designerModelData.modelData.componentsUiConfig), new FragmentComponentDefinitionExtractor( - modelData.modelClassLoader.classLoader, - modelData.componentsUiConfig.groupName, - modelData.determineDesignerWideId + designerModelData.modelData.modelClassLoader.classLoader, + designerModelData.modelData.componentsUiConfig.groupName, + designerModelData.modelData.determineDesignerWideId ), - modelData.modelDefinition + designerModelData.modelData.modelDefinition, + designerModelData.processingMode ) } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/component/ComponentService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/component/ComponentService.scala index b61ca8d6e92..3f2fcef26d2 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/component/ComponentService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/component/ComponentService.scala @@ -1,7 +1,7 @@ package pl.touk.nussknacker.ui.definition.component import cats.data.Validated.Valid -import pl.touk.nussknacker.engine.api.component.{ComponentId, DesignerWideComponentId} +import pl.touk.nussknacker.engine.api.component.DesignerWideComponentId import pl.touk.nussknacker.engine.api.process.ProcessingType import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.definition.component.ComponentDefinitionWithImplementation @@ -126,7 +126,6 @@ class DefaultComponentService( .map { definition => val designerWideId = definition.designerWideId val links = createComponentLinks(designerWideId, definition) - ComponentListElement( id = designerWideId, name = definition.name, @@ -135,7 +134,8 @@ class DefaultComponentService( componentGroupName = definition.componentGroup, categories = List(category), links = links, - usageCount = -1 // It will be enriched in the next step, after merge of components definitions + usageCount = -1, // It will be enriched in the next step, after merge of components definitions + allowedProcessingModes = definition.allowedProcessingModes ) } } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/initialization/Initialization.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/initialization/Initialization.scala index 38c75b708d1..eb52b0f62cf 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/initialization/Initialization.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/initialization/Initialization.scala @@ -104,7 +104,7 @@ class AutomaticMigration( )(implicit ec: ExecutionContext, lu: LoggedUser): DB[Unit] = { DBIOAction .sequenceOption(for { - migrator <- migrators.forType(processDetails.processingType) + migrator <- migrators.forProcessingType(processDetails.processingType) migrationResult <- migrator.migrateProcess(processDetails, skipEmptyMigrations = true) updateAction = migrationResult.toUpdateAction(processDetails.processId) } yield { diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/migrations/MigrationService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/migrations/MigrationService.scala index 7af4cce24d6..647c4f1aa28 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/migrations/MigrationService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/migrations/MigrationService.scala @@ -75,7 +75,7 @@ class MigrationService( validation <- processResolver - .forTypeE(processingType) match { + .forProcessingTypeE(processingType) match { case Left(e) => Future.successful[Either[NuDesignerError, ValidationResults.ValidationResult]](Left(e)) case Right(uiProcessResolverO) => uiProcessResolverO match { diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala index 2f8110e89ec..df30a10989b 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala @@ -296,7 +296,7 @@ class DBProcessService( scenarioParametersServiceProvider.combined.getParametersWithReadPermissionUnsafe(entity.processingType) ScenarioWithDetailsConversions.fromEntity( entity.mapScenario { canonical: CanonicalProcess => - val processResolver = processResolverByProcessingType.forTypeUnsafe(entity.processingType) + val processResolver = processResolverByProcessingType.forProcessingTypeUnsafe(entity.processingType) processResolver.validateAndReverseResolve(canonical, entity.name, entity.isFragment) }, parameters @@ -357,7 +357,7 @@ class DBProcessService( scenarioParametersService .queryProcessingTypeWithWritePermission(command.category, command.processingMode, command.engineSetupName) .map { processingType => - val newProcessPreparer = newProcessPreparers.forTypeUnsafe(processingType) + val newProcessPreparer = newProcessPreparers.forProcessingTypeUnsafe(processingType) val emptyCanonicalProcess = newProcessPreparer.prepareEmptyProcess(command.name, command.isFragment) val action = CreateProcessAction( @@ -392,7 +392,7 @@ class DBProcessService( implicit user: LoggedUser ): Future[UpdateProcessResponse] = withNotArchivedProcess(processIdWithName, "Can't update graph archived scenario.") { details => - val processResolver = processResolverByProcessingType.forTypeUnsafe(details.processingType) + val processResolver = processResolverByProcessingType.forProcessingTypeUnsafe(details.processingType) val validation = FatalValidationError.saveNotAllowedAsError( processResolver.validateBeforeUiResolving(action.scenarioGraph, details.name, details.isFragment) @@ -428,7 +428,7 @@ class DBProcessService( val canonical = jsonCanonicalProcess.withProcessName(processId.name) val scenarioGraph = CanonicalProcessConverter.toScenarioGraph(canonical) val validationResult = processResolverByProcessingType - .forTypeUnsafe(process.processingType) + .forProcessingTypeUnsafe(process.processingType) .validateBeforeUiReverseResolving(canonical, process.isFragment) Future.successful(ScenarioGraphWithValidationResult(scenarioGraph, validationResult)) } @@ -441,7 +441,7 @@ class DBProcessService( )(implicit user: LoggedUser) = { val validationResult = processResolverByProcessingType - .forTypeUnsafe(processingType) + .forProcessingTypeUnsafe(processingType) .validateBeforeUiReverseResolving(canonicalProcess, isFragment) validationResult.errors.processPropertiesErrors } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentManagerDispatcher.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentManagerDispatcher.scala index 6ea43ba4b5b..55ca15fe161 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentManagerDispatcher.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentManagerDispatcher.scala @@ -20,11 +20,11 @@ class DeploymentManagerDispatcher( } def deploymentManager(processingType: ProcessingType)(implicit user: LoggedUser): Option[DeploymentManager] = { - managers.forType(processingType) + managers.forProcessingType(processingType) } def deploymentManagerUnsafe(processingType: ProcessingType)(implicit user: LoggedUser): DeploymentManager = { - managers.forTypeUnsafe(processingType) + managers.forProcessingTypeUnsafe(processingType) } } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceImpl.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceImpl.scala index 9810eb6df4b..942e9143667 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceImpl.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceImpl.scala @@ -213,7 +213,7 @@ class DeploymentServiceImpl( // 1. check scenario has no errors _ <- Future { processValidator - .forTypeUnsafe(processDetails.processingType) + .forProcessingTypeUnsafe(processDetails.processingType) .validateCanonicalProcess(processDetails.json, processDetails.isFragment) }.flatMap { case validationResult if validationResult.hasErrors => @@ -279,7 +279,7 @@ class DeploymentServiceImpl( )(implicit user: LoggedUser, ec: ExecutionContext): Future[DeployedScenarioData] = { for { resolvedCanonicalProcess <- Future.fromTry( - scenarioResolver.forTypeUnsafe(processDetails.processingType).resolveScenario(processDetails.json) + scenarioResolver.forProcessingTypeUnsafe(processDetails.processingType).resolveScenario(processDetails.json) ) deploymentData = prepareDeploymentData( user.toManagerUser, diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/ProcessModelMigrator.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/ProcessModelMigrator.scala index 3d4c107c377..9c6fac24904 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/ProcessModelMigrator.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/ProcessModelMigrator.scala @@ -4,7 +4,6 @@ import pl.touk.nussknacker.engine.api.graph.ScenarioGraph import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessName} import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.migration.{ProcessMigration, ProcessMigrations} -import pl.touk.nussknacker.ui.process.ScenarioWithDetailsConversions import pl.touk.nussknacker.ui.process.marshall.CanonicalProcessConverter import pl.touk.nussknacker.ui.process.repository.ProcessRepository.UpdateProcessAction import pl.touk.nussknacker.ui.process.repository.{MigrationComment, ScenarioWithDetailsEntity} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/TestModelMigrations.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/TestModelMigrations.scala index e6a139fbe0d..ed03e18fa2a 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/TestModelMigrations.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/migrate/TestModelMigrations.scala @@ -47,7 +47,7 @@ class TestModelMigrations( processInParallel(migratedFragments ++ migratedProcesses, batchingExecutionContext) { migrationDetails => val validationResult = validator - .forTypeUnsafe(migrationDetails.processingType) + .forProcessingTypeUnsafe(migrationDetails.processingType) .validate(migrationDetails.newScenarioGraph, migrationDetails.processName, migrationDetails.isFragment) val newErrors = extractNewErrors(migrationDetails.oldProcessErrors, validationResult) TestMigrationResult( @@ -61,7 +61,7 @@ class TestModelMigrations( scenarioWithDetails: ScenarioWithDetailsForMigrations )(implicit user: LoggedUser): Option[MigratedProcessDetails] = { for { - migrator <- migrators.forType(scenarioWithDetails.processingType) + migrator <- migrators.forProcessingType(scenarioWithDetails.processingType) MigrationResult(newProcess, _) <- migrator.migrateProcess( scenarioWithDetails.name, diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/DesignerModelData.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/DesignerModelData.scala index 572038d3e09..f64a689495b 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/DesignerModelData.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/DesignerModelData.scala @@ -8,7 +8,7 @@ final case class DesignerModelData( modelData: ModelData, // We hold this map as a cache - computing it is a quite costly operation (it invokes external services) staticDefinitionForDynamicComponents: Map[ComponentId, ComponentStaticDefinition], - singleProcessingMode: ProcessingMode + processingMode: ProcessingMode ) { def close(): Unit = { diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeData.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeData.scala index 40a7990ca4a..06e5a57ee6e 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeData.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeData.scala @@ -12,7 +12,7 @@ import pl.touk.nussknacker.restmodel.scenariodetails.ScenarioParameters import scala.util.control.NonFatal final case class ProcessingTypeData private ( - processingType: ProcessingType, + name: ProcessingType, designerModelData: DesignerModelData, deploymentData: DeploymentData, category: String, @@ -24,7 +24,7 @@ final case class ProcessingTypeData private ( def scenarioParameters: ScenarioParametersWithEngineSetupErrors = ScenarioParametersWithEngineSetupErrors( ScenarioParameters( - designerModelData.singleProcessingMode, + designerModelData.processingMode, category, deploymentData.engineSetupName ), @@ -44,7 +44,7 @@ object ProcessingTypeData { import pl.touk.nussknacker.engine.util.config.FicusReaders._ def createProcessingTypeData( - processingType: ProcessingType, + name: ProcessingType, modelData: ModelData, deploymentManagerProvider: DeploymentManagerProvider, deploymentManagerDependencies: DeploymentManagerDependencies, @@ -64,9 +64,9 @@ object ProcessingTypeData { metaDataInitializer ) - val designerModelData = createDesignerModelData(modelData, metaDataInitializer, processingType) + val designerModelData = createDesignerModelData(modelData, metaDataInitializer, name) ProcessingTypeData( - processingType, + name, designerModelData, deploymentData, category @@ -74,7 +74,7 @@ object ProcessingTypeData { } catch { case NonFatal(ex) => throw new IllegalArgumentException( - s"Error during creation of processing type data for processing type [$processingType]", + s"Error during creation of processing type data for processing type [$name]", ex ) } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataProvider.scala index 46bc8dd78f6..58dfe73be3b 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataProvider.scala @@ -31,20 +31,21 @@ import java.util.concurrent.atomic.AtomicReference trait ProcessingTypeDataProvider[+Data, +CombinedData] { // TODO: replace with proper forType handling - final def forTypeUnsafe(processingType: ProcessingType)(implicit user: LoggedUser): Data = forType(processingType) - .getOrElse( - throw new IllegalArgumentException( - s"Unknown ProcessingType: $processingType, known ProcessingTypes are: ${all.keys.mkString(", ")}" + final def forProcessingTypeUnsafe(processingType: ProcessingType)(implicit user: LoggedUser): Data = + forProcessingType(processingType) + .getOrElse( + throw new IllegalArgumentException( + s"Unknown ProcessingType: $processingType, known ProcessingTypes are: ${all.keys.mkString(", ")}" + ) ) - ) - final def forType(processingType: ProcessingType)(implicit user: LoggedUser): Option[Data] = { + final def forProcessingType(processingType: ProcessingType)(implicit user: LoggedUser): Option[Data] = { allAuthorized .get(processingType) .map(_.getOrElse(throw new UnauthorizedError(user))) } - final def forTypeE( + final def forProcessingTypeE( processingType: ProcessingType )(implicit user: LoggedUser): Either[UnauthorizedError, Option[Data]] = { allAuthorized diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ScenarioParametersDeterminer.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ScenarioParametersDeterminer.scala index 2ce8c49c784..028fe0e12ec 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ScenarioParametersDeterminer.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/processingtype/ScenarioParametersDeterminer.scala @@ -1,5 +1,6 @@ package pl.touk.nussknacker.ui.process.processingtype +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.ProcessingMode import pl.touk.nussknacker.engine.api.process.ProcessingType import pl.touk.nussknacker.engine.definition.component.ComponentDefinitionWithImplementation @@ -15,7 +16,7 @@ object ScenarioParametersDeterminer { processingType: ProcessingType, ): ProcessingMode = { val componentsToProcessingMode = components.map { component => - val allowedProcessingModes = component.implementation.allowedProcessingModes + val allowedProcessingModes = component.allowedProcessingModes component.id -> allowedProcessingModes }.toMap @@ -23,16 +24,17 @@ object ScenarioParametersDeterminer { // FIXME: some proper errors and tests for that case Nil => throw new IllegalStateException(s"Empty list of components for processing type: $processingType") case nonEmptyList => - val intersection = nonEmptyList.foldLeft(ProcessingMode.all) { - case (acc, Some(componentProcessingModes)) => acc.intersect(componentProcessingModes) - case (acc, None) => acc + val intersection = nonEmptyList.foldLeft(ProcessingMode.values.toSet) { case (acc, allowedProcessingModes) => + acc.intersect(allowedProcessingModes.toProcessingModes) } intersection.toList match { case oneMode :: Nil => oneMode // FIXME: some proper errors and tests for that case Nil => - val componentsWithDefinedAllowedProcessingModes = componentsToProcessingMode.collect { - case (id, Some(modes)) => id -> modes + val componentsWithDefinedAllowedProcessingModes = componentsToProcessingMode.flatMap { + case (id, AllowedProcessingModes.SetOf(modes)) => id -> modes :: Nil + case (_, AllowedProcessingModes.All) => + Nil // Components allowed in all modes don't "collide" with any component } throw new IllegalStateException( s"Detected collision of allowed processing modes for processing type: $processingType among components: $componentsWithDefinedAllowedProcessingModes" diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessActionRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessActionRepository.scala index d8899f70684..6566f986709 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessActionRepository.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessActionRepository.scala @@ -207,7 +207,7 @@ class DbProcessActionRepository( buildInfoProcessingType: Option[ProcessingType] )(implicit user: LoggedUser): DB[ProcessActionEntityData] = { val actionId = actionIdOpt.getOrElse(ProcessActionId(UUID.randomUUID())) - val buildInfoJsonOpt = buildInfoProcessingType.flatMap(buildInfos.forType).map(BuildInfo.writeAsJson) + val buildInfoJsonOpt = buildInfoProcessingType.flatMap(buildInfos.forProcessingType).map(BuildInfo.writeAsJson) val processActionData = ProcessActionEntityData( id = actionId, processId = processId, diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessRepository.scala index 05b36d3be0b..6de71146f11 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessRepository.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ProcessRepository.scala @@ -175,7 +175,7 @@ class DBProcessRepository(protected val dbRef: DbRef, modelVersion: ProcessingTy json = Some(canonicalProcess), createDate = Timestamp.from(now), user = userName, - modelVersion = modelVersion.forType(processingType), + modelVersion = modelVersion.forProcessingType(processingType), componentsUsages = Some(ScenarioComponentsUsagesHelper.compute(canonicalProcess)), ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala index 9fbb2a228a6..5264cc3cef9 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala @@ -91,7 +91,7 @@ class AkkaHttpBasedRouteProvider( countsReporter <- createCountsReporter(featureTogglesConfig, environment, sttpBackend) deploymentServiceSupplier = new DelayedInitDeploymentServiceSupplier additionalUIConfigProvider = createAdditionalUIConfigProvider(resolvedConfig, sttpBackend) - typeToConfig <- prepareProcessingTypeData( + processingTypeDataProvider <- prepareProcessingTypeDataReload( config, processingTypeDataStateFactory, additionalUIConfigProvider, @@ -102,8 +102,8 @@ class AkkaHttpBasedRouteProvider( } yield { val analyticsConfig = AnalyticsConfig(resolvedConfig) - val migrations = typeToConfig.mapValues(_.designerModelData.modelData.migrations) - val modelBuildInfo = typeToConfig.mapValues(_.designerModelData.modelData.buildInfo) + val migrations = processingTypeDataProvider.mapValues(_.designerModelData.modelData.migrations) + val modelBuildInfo = processingTypeDataProvider.mapValues(_.designerModelData.modelData.buildInfo) val dbioRunner = DBIOActionRunner(dbRef) val actionRepository = new DbProcessActionRepository(dbRef, modelBuildInfo) @@ -115,9 +115,9 @@ class AkkaHttpBasedRouteProvider( val fragmentRepository = new DefaultFragmentRepository(futureProcessRepository) val fragmentResolver = new FragmentResolver(fragmentRepository) - val scenarioTestServiceDeps = typeToConfig.mapValues { processingTypeData => + val scenarioTestServiceDeps = processingTypeDataProvider.mapValues { processingTypeData => val validator = new UIProcessValidator( - processingTypeData.processingType, + processingTypeData.name, ProcessValidator.default(processingTypeData.designerModelData.modelData), processingTypeData.deploymentData.scenarioPropertiesConfig, processingTypeData.deploymentData.additionalValidators, @@ -126,7 +126,7 @@ class AkkaHttpBasedRouteProvider( val substitutor = ProcessDictSubstitutor(processingTypeData.designerModelData.modelData.designerDictServices.dictRegistry) val resolver = new UIProcessResolver(validator, substitutor) - val scenarioResolver = new ScenarioResolver(fragmentResolver, processingTypeData.processingType) + val scenarioResolver = new ScenarioResolver(fragmentResolver, processingTypeData.name) ( validator, resolver, @@ -163,7 +163,7 @@ class AkkaHttpBasedRouteProvider( val dmDispatcher = new DeploymentManagerDispatcher( - typeToConfig.mapValues(_.deploymentData.validDeploymentManagerOrStub), + processingTypeDataProvider.mapValues(_.deploymentData.validDeploymentManagerOrStub), futureProcessRepository ) @@ -184,14 +184,14 @@ class AkkaHttpBasedRouteProvider( // we need to reload processing type data after deployment service creation to make sure that it will be done using // correct classloader and that won't cause further delays during handling requests - typeToConfig.reloadAll() + processingTypeDataProvider.reloadAll() val processActivityRepository = new DbProcessActivityRepository(dbRef) val authenticationResources = AuthenticationResources(resolvedConfig, getClass.getClassLoader, sttpBackend) Initialization.init(migrations, dbRef, processRepository, environment) - val newProcessPreparer = typeToConfig.mapValues { processingTypeData => + val newProcessPreparer = processingTypeDataProvider.mapValues { processingTypeData => new NewProcessPreparer( processingTypeData.deploymentData.metaDataInitializer, processingTypeData.deploymentData.scenarioPropertiesConfig @@ -199,7 +199,7 @@ class AkkaHttpBasedRouteProvider( } val stateDefinitionService = new ProcessStateDefinitionService( - typeToConfig + processingTypeDataProvider .mapValues(_.category) .mapCombined(_.statusNameToStateDefinitionsMapping) ) @@ -207,7 +207,7 @@ class AkkaHttpBasedRouteProvider( val processService = new DBProcessService( deploymentService, newProcessPreparer, - typeToConfig.mapCombined(_.parametersService), + processingTypeDataProvider.mapCombined(_.parametersService), processResolver, dbioRunner, futureProcessRepository, @@ -222,11 +222,11 @@ class AkkaHttpBasedRouteProvider( def prepareAlignedComponentsDefinitionProvider( processingTypeData: ProcessingTypeData ): AlignedComponentsDefinitionProvider = - AlignedComponentsDefinitionProvider(processingTypeData.designerModelData.modelData) + AlignedComponentsDefinitionProvider(processingTypeData.designerModelData) val componentService = new DefaultComponentService( ComponentLinksConfigExtractor.extract(resolvedConfig), - typeToConfig + processingTypeDataProvider .mapValues { processingTypeData => val alignedModelDefinitionProvider = prepareAlignedComponentsDefinitionProvider(processingTypeData) ComponentServiceProcessingTypeData(alignedModelDefinitionProvider, processingTypeData.category) @@ -239,9 +239,9 @@ class AkkaHttpBasedRouteProvider( val appApiHttpService = new AppApiHttpService( config = resolvedConfig, authenticator = authenticationResources, - processingTypeDataReloader = typeToConfig, + processingTypeDataReloader = processingTypeDataProvider, modelBuildInfos = modelBuildInfo, - categories = typeToConfig.mapValues(_.category), + categories = processingTypeDataProvider.mapValues(_.category), processService = processService, shouldExposeConfig = featureTogglesConfig.enableConfigEndpoint, ) @@ -252,7 +252,7 @@ class AkkaHttpBasedRouteProvider( processResolver = processResolver, processAuthorizer = processAuthorizer, processChangeListener = processChangeListener, - scenarioParametersService = typeToConfig.mapCombined(_.parametersService), + scenarioParametersService = processingTypeDataProvider.mapCombined(_.parametersService), useLegacyCreateScenarioApi = true ) @@ -266,7 +266,7 @@ class AkkaHttpBasedRouteProvider( ) val userApiHttpService = new UserApiHttpService( authenticator = authenticationResources, - categories = typeToConfig.mapValues(_.category) + categories = processingTypeDataProvider.mapValues(_.category) ) val notificationApiHttpService = new NotificationApiHttpService( authenticator = authenticationResources, @@ -275,14 +275,15 @@ class AkkaHttpBasedRouteProvider( val nodesApiHttpService = new NodesApiHttpService( authenticator = authenticationResources, - typeToConfig = typeToConfig.mapValues(_.designerModelData.modelData), - typeToProcessValidator = processValidator, - typeToNodeValidator = - typeToConfig.mapValues(v => new NodeValidator(v.designerModelData.modelData, fragmentRepository)), - typeToExpressionSuggester = typeToConfig.mapValues(v => + processingTypeToConfig = processingTypeDataProvider.mapValues(_.designerModelData.modelData), + processingTypeToProcessValidator = processValidator, + processingTypeToNodeValidator = processingTypeDataProvider.mapValues(v => + new NodeValidator(v.designerModelData.modelData, fragmentRepository) + ), + processingTypeToExpressionSuggester = processingTypeDataProvider.mapValues(v => ExpressionSuggester(v.designerModelData.modelData, v.deploymentData.scenarioPropertiesConfig.keys) ), - typeToParametersValidator = typeToConfig.mapValues(v => + processingTypeToParametersValidator = processingTypeDataProvider.mapValues(v => new ParametersValidator(v.designerModelData.modelData, v.deploymentData.scenarioPropertiesConfig.keys) ), scenarioService = processService @@ -301,11 +302,11 @@ class AkkaHttpBasedRouteProvider( ) val scenarioParametersHttpService = new ScenarioParametersApiHttpService( authenticator = authenticationResources, - scenarioParametersService = typeToConfig.mapCombined(_.parametersService) + scenarioParametersService = processingTypeDataProvider.mapCombined(_.parametersService) ) val dictApiHttpService = new DictApiHttpService( authenticator = authenticationResources, - processingTypeData = typeToConfig.mapValues { processingTypeData => + processingTypeData = processingTypeDataProvider.mapValues { processingTypeData => ( processingTypeData.designerModelData.modelData.designerDictServices.dictQueryService, processingTypeData.designerModelData.modelData.modelDefinition.expressionConfig.dictionaries, @@ -340,16 +341,16 @@ class AkkaHttpBasedRouteProvider( dmDispatcher, metricsRegistry, scenarioTestService, - typeToConfig.mapValues(_.designerModelData.modelData) + processingTypeDataProvider.mapValues(_.designerModelData.modelData) ), new ValidationResources(processService, processResolver), new DefinitionResources( - typeToConfig.mapValues { processingTypeData => + processingTypeDataProvider.mapValues { processingTypeData => ( DefinitionsService( processingTypeData, prepareAlignedComponentsDefinitionProvider(processingTypeData), - new ScenarioPropertiesConfigFinalizer(additionalUIConfigProvider, processingTypeData.processingType), + new ScenarioPropertiesConfigFinalizer(additionalUIConfigProvider, processingTypeData.name), fragmentRepository ) ) @@ -389,7 +390,7 @@ class AkkaHttpBasedRouteProvider( val usageStatisticsReportsSettingsDeterminer = UsageStatisticsReportsSettingsDeterminer( usageStatisticsReportsConfig, processService, - typeToConfig.mapValues(_.deploymentData.deploymentManagerType) + processingTypeDataProvider.mapValues(_.deploymentData.deploymentManagerType) ) // TODO: WARNING now all settings are available for not sign in user. In future we should show only basic settings @@ -525,7 +526,7 @@ class AkkaHttpBasedRouteProvider( ) } - private def prepareProcessingTypeData( + private def prepareProcessingTypeDataReload( designerConfig: ConfigWithUnresolvedVersion, processingTypeDataStateFactory: ProcessingTypeDataStateFactory, additionalUIConfigProvider: AdditionalUIConfigProvider, diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/generators/NodeDataGen.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/generators/NodeDataGen.scala new file mode 100644 index 00000000000..13635def869 --- /dev/null +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/generators/NodeDataGen.scala @@ -0,0 +1,215 @@ +package pl.touk.nussknacker.test.utils.generators + +import org.scalacheck.{Arbitrary, Gen} +import pl.touk.nussknacker.engine.api.LayoutData +import pl.touk.nussknacker.engine.api.parameter.ParameterName +import pl.touk.nussknacker.engine.graph.evaluatedparam.{BranchParameters, Parameter} +import pl.touk.nussknacker.engine.graph.expression.Expression +import pl.touk.nussknacker.engine.graph.expression.Expression.Language +import pl.touk.nussknacker.engine.graph.fragment.FragmentRef +import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter} +import pl.touk.nussknacker.engine.graph.node._ +import pl.touk.nussknacker.engine.graph.service.ServiceRef +import pl.touk.nussknacker.engine.graph.source.SourceRef +import pl.touk.nussknacker.engine.graph.variable.Field + +class NodeDataGen private { + private lazy val booleanGen = Arbitrary.arbitrary[Boolean] + + private lazy val stringGen = Gen.oneOf("foo", "bar", "cat", "dog", "cactus") + + private lazy val mapGen = for { + key <- stringGen + value <- stringGen + } yield (key, value) + + lazy val expressionGen: Gen[Expression] = for { + language <- Gen.oneOf(Language.Spel, Language.SpelTemplate) + expression <- stringGen + } yield Expression(language, expression) + + lazy val layoutDataGen: Gen[LayoutData] = for { + x <- Gen.long + y <- Gen.long + } yield LayoutData(x, y) + + lazy val userDefinedNodeFieldsGen: Gen[UserDefinedAdditionalNodeFields] = for { + description <- Gen.option(stringGen) + layoutData <- Gen.option(layoutDataGen) + } yield UserDefinedAdditionalNodeFields(description, layoutData) + + lazy val fieldGen: Gen[Field] = for { + name <- stringGen + expression <- expressionGen + } yield Field(name, expression) + + lazy val fieldListGen: Gen[List[Field]] = Gen.listOfN(5, fieldGen) + + lazy val branchEndDefinitionGen: Gen[BranchEndDefinition] = for { + id <- stringGen + joinId <- stringGen + } yield BranchEndDefinition(id, joinId) + + lazy val parameterGen: Gen[Parameter] = for { + parameterName <- stringGen + expression <- expressionGen + } yield Parameter(ParameterName(parameterName), expression) + + lazy val parametersListGen: Gen[List[Parameter]] = Gen.listOfN(5, parameterGen) + + lazy val serviceRefGen: Gen[ServiceRef] = for { + id <- stringGen + parameters <- parametersListGen + } yield ServiceRef(id, parameters) + + lazy val fragmentRefGen: Gen[FragmentRef] = for { + id <- stringGen + parameters <- parametersListGen + outputVariableNames <- Gen.mapOfN(5, mapGen) + } yield FragmentRef(id, parameters, outputVariableNames) + + lazy val fragmentParamGen: Gen[FragmentParameter] = for { + name <- stringGen + typ <- stringGen + } yield FragmentParameter(ParameterName(name), FragmentClazzRef(typ)) + + lazy val fragmentParametersGen: Gen[List[FragmentParameter]] = Gen.listOfN(5, fragmentParamGen) + + lazy val branchParamGen: Gen[BranchParameters] = for { + branchId <- stringGen + parameters <- parametersListGen + } yield BranchParameters(branchId, parameters) + + lazy val branchParametersGen: Gen[List[BranchParameters]] = Gen.listOfN(5, branchParamGen) + + lazy val sourceRefGen: Gen[SourceRef] = for { + typ <- stringGen + parameters <- parametersListGen + } yield SourceRef(typ, parameters) + + private lazy val variableGen: Gen[Variable] = for { + id <- stringGen + varName <- stringGen + expression <- expressionGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Variable(id, varName, expression, additionalFields) + + private lazy val variableBuilderGen: Gen[VariableBuilder] = for { + id <- stringGen + varName <- stringGen + fields <- fieldListGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield VariableBuilder(id, varName, fields, additionalFields) + + private lazy val branchEndDataGen: Gen[BranchEndData] = for { + definition <- branchEndDefinitionGen + } yield BranchEndData(definition) + + private lazy val customNodeGen: Gen[CustomNode] = for { + id <- stringGen + outputVar <- Gen.option(stringGen) + nodeType <- stringGen + parameters <- parametersListGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield CustomNode(id, outputVar, nodeType, parameters, additionalFields) + + private lazy val enricherGen: Gen[Enricher] = for { + id <- stringGen + service <- serviceRefGen + output <- stringGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Enricher(id, service, output, additionalFields) + + private lazy val filterGen: Gen[Filter] = for { + id <- stringGen + expression <- expressionGen + isDisabled <- Gen.option(booleanGen) + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Filter(id, expression, isDisabled, additionalFields) + + private lazy val fragmentInputGen: Gen[FragmentInput] = for { + id <- stringGen + ref <- fragmentRefGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + isDisabled <- Gen.option(booleanGen) + fragmentParams <- Gen.option(fragmentParametersGen) + } yield FragmentInput(id, ref, additionalFields, isDisabled, fragmentParams) + + private lazy val fragmentInputDefinitionGen: Gen[FragmentInputDefinition] = for { + id <- stringGen + parameters <- fragmentParametersGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield FragmentInputDefinition(id, parameters, additionalFields) + + private lazy val fragmentOutputDefinitionGen: Gen[FragmentOutputDefinition] = for { + id <- stringGen + outputName <- stringGen + fields <- fieldListGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield FragmentOutputDefinition(id, outputName, fields, additionalFields) + + private lazy val joinGen: Gen[Join] = for { + id <- stringGen + outputVar <- Gen.option(stringGen) + nodeType <- stringGen + parameters <- parametersListGen + branchParameters <- branchParametersGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Join(id, outputVar, nodeType, parameters, branchParameters, additionalFields) + + private lazy val processorGen: Gen[Processor] = for { + id <- stringGen + service <- serviceRefGen + isDisabled <- Gen.option(booleanGen) + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Processor(id, service, isDisabled, additionalFields) + + private lazy val sourceGen: Gen[Source] = for { + id <- stringGen + ref <- sourceRefGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Source(id, ref, additionalFields) + + private lazy val splitGen: Gen[Split] = for { + id <- stringGen + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Split(id, additionalFields) + + private lazy val switchGen: Gen[Switch] = for { + id <- stringGen + expression <- Gen.option(expressionGen) + exprVal <- Gen.option(stringGen) + additionalFields <- Gen.option(userDefinedNodeFieldsGen) + } yield Switch(id, expression, exprVal, additionalFields) + + private def genFromListOfGens[T](list: List[Gen[T]]): Gen[T] = { + Gen.choose(0, list.size - 1).flatMap(list(_)) + } + + lazy val nodeDataGen: Gen[NodeData] = + Gen.lzy { + genFromListOfGens( + List( + variableGen, + variableBuilderGen, + branchEndDataGen, + customNodeGen, + enricherGen, + filterGen, + fragmentInputGen, + fragmentInputDefinitionGen, + fragmentOutputDefinitionGen, + joinGen, + processorGen, + sourceGen, + splitGen, + switchGen + ) + ) + } + +} + +object NodeDataGen { + def nodeDataGen: Gen[NodeData] = new NodeDataGen().nodeDataGen +} diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DefinitionResourcesSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DefinitionResourcesSpec.scala index 399d61e2ef0..8d5fa35b313 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DefinitionResourcesSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/DefinitionResourcesSpec.scala @@ -42,13 +42,13 @@ class DefinitionResourcesSpec private val definitionResources = new DefinitionResources( definitionsServices = testProcessingTypeDataProvider.mapValues { processingTypeData => - val modelDefinitionEnricher = AlignedComponentsDefinitionProvider(processingTypeData.designerModelData.modelData) + val modelDefinitionEnricher = AlignedComponentsDefinitionProvider(processingTypeData.designerModelData) ( DefinitionsService( processingTypeData, modelDefinitionEnricher, - new ScenarioPropertiesConfigFinalizer(TestAdditionalUIConfigProvider, processingTypeData.processingType), + new ScenarioPropertiesConfigFinalizer(TestAdditionalUIConfigProvider, processingTypeData.name), fragmentRepository ) ) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiEndpointsSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiEndpointsSpec.scala new file mode 100644 index 00000000000..ecd8f3b135b --- /dev/null +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiEndpointsSpec.scala @@ -0,0 +1,51 @@ +package pl.touk.nussknacker.ui.api + +import org.scalatest.freespec.AnyFreeSpecLike +import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks +import pl.touk.nussknacker.engine.api.typed.{EnabledTypedFeatures, TypingResultGen} +import pl.touk.nussknacker.test.{NuScalaTestAssertions, NuTapirSchemaTestHelpers} +import pl.touk.nussknacker.test.ProcessUtils.convertToAnyShouldWrapper +import pl.touk.nussknacker.test.utils.generators.NodeDataGen +import pl.touk.nussknacker.ui.api.description.{NodesApiEndpoints, TypingDtoSchemas} + +import scala.util.control.Breaks.{break, breakable} + +class NodesApiEndpointsSpec + extends AnyFreeSpecLike + with ScalaCheckDrivenPropertyChecks + with NuScalaTestAssertions + with NuTapirSchemaTestHelpers { + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfiguration(minSuccessful = 1000, minSize = 0) + + "TypingResult schemas shouldn't go out of sync with Codecs" in { + val schema = prepareJsonSchemaFromTapirSchema(TypingDtoSchemas.typingResult) + + forAll(TypingResultGen.typingResultGen(EnabledTypedFeatures.All)) { typingResult => + val json = createJsonObjectFrom(typingResult) + breakable { +// This test gets stuck when validating schema of a too big size +// There is no problem with creating json schema, just with validating it, so introduced a size limit +// This also allows us to test 1000 examples instead of 5 and don't worry for the test to take too long + if (json.toString.length() > 750) { + break() + } else { + schema should validateJson(json) + } + } + + } + } + + "Node data check up" in { + val schema = prepareJsonSchemaFromTapirSchema(NodesApiEndpoints.Dtos.NodeDataSchemas.nodeDataSchema) + + forAll(NodeDataGen.nodeDataGen) { nodeData => + val json = createJsonObjectFrom(nodeData) + + schema should validateJson(json) + } + } + +} diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala index ec6a07d3c34..bfb81bf543d 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/NodesApiHttpServiceBusinessSpec.scala @@ -5,13 +5,13 @@ import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse import org.hamcrest.Matchers.equalTo import org.scalatest.freespec.AnyFreeSpecLike import pl.touk.nussknacker.engine.build.ScenarioBuilder -import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} import pl.touk.nussknacker.test.base.it.{NuItTest, WithSimplifiedConfigScenarioHelper} import pl.touk.nussknacker.test.config.{ WithBusinessCaseRestAssuredUsersExtensions, WithMockableDeploymentManager, WithSimplifiedDesignerConfig } +import pl.touk.nussknacker.test.{NuRestAssureMatchers, PatientScalaFutures, RestAssuredVerboseLogging} class NodesApiHttpServiceBusinessSpec extends AnyFreeSpecLike diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala index 34ecee2dc39..cc46bfcb81f 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala @@ -273,7 +273,8 @@ class DefinitionsServiceSpec extends AnyFunSuite with Matchers with PatientScala Some(_), DesignerWideComponentId.default(processingType.stringify, _) ), - model.modelDefinition + model.modelDefinition, + ProcessingMode.UnboundedStream ) new DefinitionsService( diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala index d68fc171c9b..94a370fde8e 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala @@ -2,6 +2,7 @@ package pl.touk.nussknacker.ui.definition import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.{ BuiltInComponentId, ComponentId, @@ -24,7 +25,7 @@ class EdgeTypesPreparerTest extends AnyFunSuite with Matchers with ValidatedValu Some(_), DesignerWideComponentId.default(Streaming.stringify, _) ) - .extractFragmentComponentDefinition(ProcessTestData.sampleFragment) + .extractFragmentComponentDefinition(ProcessTestData.sampleFragment, AllowedProcessingModes.All) .validValue val definitionsWithFragments = ProcessTestData .modelDefinition() diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala index 56d19a0dc5e..bf8d8a72bed 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala @@ -193,6 +193,7 @@ class ComponentGroupsPreparerSpec DesignerWideComponentId.default("Streaming", _) ), modelDefinition, + ProcessingMode.UnboundedStream ) withStaticDefinition( diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala index 6d41b50d344..2b26f56d0b5 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala @@ -177,7 +177,8 @@ class ComponentsUsageHelperTest extends AnyFunSuite with Matchers with TableDriv val alignedComponentsDefinitionProvider = new AlignedComponentsDefinitionProvider( new BuiltInComponentsDefinitionsPreparer(new ComponentsUiConfig(Map.empty, Map.empty)), new FragmentComponentDefinitionExtractor(getClass.getClassLoader, Some(_), determineDesignerWideId), - modelDefinition + modelDefinition, + ProcessingMode.UnboundedStream ) alignedComponentsDefinitionProvider diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/DefaultComponentServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/DefaultComponentServiceSpec.scala index 48affa840dd..6f0b700055f 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/DefaultComponentServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/DefaultComponentServiceSpec.scala @@ -1,5 +1,6 @@ package pl.touk.nussknacker.ui.definition.component +import cats.data.NonEmptySet import com.typesafe.config.{Config, ConfigFactory} import org.scalatest.Inside.inside import org.scalatest.OptionValues @@ -7,6 +8,7 @@ import org.scalatest.exceptions.TestFailedException import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import pl.touk.nussknacker.engine.ModelData +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.ComponentType._ import pl.touk.nussknacker.engine.api.component._ import pl.touk.nussknacker.engine.api.graph.ScenarioGraph @@ -233,6 +235,7 @@ class DefaultComponentServiceSpec ComponentId(Source, SharedSourceName), SourceIcon, SourcesGroupName, + nonDefaultAllowedProcessingModes = Some(AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream)) ), sharedComponent( ComponentId(Sink, SharedSinkName), @@ -253,7 +256,8 @@ class DefaultComponentServiceSpec ComponentId(Source, SourceSinkSameNameComponentName), SourceIcon, SourcesGroupName, - designerWideComponentId = Some(overrideSourceComponentId) + designerWideComponentId = Some(overrideSourceComponentId), + nonDefaultAllowedProcessingModes = Some(AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream)) ), sharedComponent( ComponentId(Sink, SourceSinkSameNameComponentName), @@ -287,8 +291,18 @@ class DefaultComponentServiceSpec CustomComponentIcon, OptionalEndingCustomGroupName ), - marketingComponent(ComponentId(Source, SuperMarketingSourceName), SourceIcon, SourcesGroupName), - marketingComponent(ComponentId(Source, NotSharedSourceName), SourceIcon, SourcesGroupName), + marketingComponent( + ComponentId(Source, SuperMarketingSourceName), + SourceIcon, + SourcesGroupName, + nonDefaultAllowedProcessingModes = Some(AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream)) + ), + marketingComponent( + ComponentId(Source, NotSharedSourceName), + SourceIcon, + SourcesGroupName, + nonDefaultAllowedProcessingModes = Some(AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream)) + ), marketingComponent( ComponentId(Service, SingleProvidedComponentName), ServiceIcon, @@ -307,7 +321,12 @@ class DefaultComponentServiceSpec ), fraudComponent(ComponentId(Sink, SecondMonitorName), SinkIcon, executionGroupName), fraudComponent(ComponentId(Service, SingleProvidedComponentName), ServiceIcon, executionGroupName), - fraudComponent(ComponentId(Source, NotSharedSourceName), SourceIcon, SourcesGroupName), + fraudComponent( + ComponentId(Source, NotSharedSourceName), + SourceIcon, + SourcesGroupName, + nonDefaultAllowedProcessingModes = Some(AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream)) + ), fraudComponent(ComponentId(Sink, FraudSinkName), SinkIcon, executionGroupName), ) @@ -315,7 +334,8 @@ class DefaultComponentServiceSpec componentId: ComponentId, icon: String, componentGroupName: ComponentGroupName, - designerWideComponentId: Option[DesignerWideComponentId] = None + designerWideComponentId: Option[DesignerWideComponentId] = None, + nonDefaultAllowedProcessingModes: Option[AllowedProcessingModes] = None )(implicit user: LoggedUser) = { val id = designerWideComponentId.getOrElse(DesignerWideComponentId(componentId.name)) val links = createLinks(id, componentId) @@ -331,7 +351,8 @@ class DefaultComponentServiceSpec componentGroupName, availableCategories, links, - usageCount + usageCount, + nonDefaultAllowedProcessingModes.getOrElse(AllowedProcessingModes.All) ) } @@ -341,7 +362,19 @@ class DefaultComponentServiceSpec val designerWideComponentId = cid(ProcessingTypeStreaming, componentId) val icon = DefaultsComponentIcon.fromComponentId(componentId, None) val links = createLinks(designerWideComponentId, componentId) - List(ComponentListElement(designerWideComponentId, cat, icon, Fragment, FragmentsGroupName, List(cat), links, 0)) + List( + ComponentListElement( + designerWideComponentId, + cat, + icon, + Fragment, + FragmentsGroupName, + List(cat), + links, + 0, + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) + ) + ) } private val fragmentFraudComponents: List[ComponentListElement] = { @@ -358,11 +391,13 @@ class DefaultComponentServiceSpec FragmentsGroupName, List(cat), links, - 0 + 0, + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) ) ) } + // It would be good to rewrite these tests to make expected component list static which would also make expected values more readable private def prepareComponents(implicit user: LoggedUser): List[ComponentListElement] = baseComponents ++ prepareSharedComponents ++ prepareMarketingComponents ++ prepareFraudComponents ++ fragmentMarketingComponents ++ fragmentFraudComponents @@ -370,7 +405,8 @@ class DefaultComponentServiceSpec componentId: ComponentId, icon: String, componentGroupName: ComponentGroupName, - designerWideComponentId: Option[DesignerWideComponentId] = None + designerWideComponentId: Option[DesignerWideComponentId] = None, + nonDefaultAllowedProcessingModes: Option[AllowedProcessingModes] = None )(implicit user: LoggedUser) = createComponent( ProcessingTypeStreaming, @@ -378,14 +414,16 @@ class DefaultComponentServiceSpec icon, componentGroupName, List(CategoryMarketing), - designerWideComponentId + designerWideComponentId, + nonDefaultAllowedProcessingModes ) private def fraudComponent( componentId: ComponentId, icon: String, componentGroupName: ComponentGroupName, - designerWideComponentId: Option[DesignerWideComponentId] = None + designerWideComponentId: Option[DesignerWideComponentId] = None, + nonDefaultAllowedProcessingModes: Option[AllowedProcessingModes] = None )(implicit user: LoggedUser) = createComponent( ProcessingTypeFraud, @@ -393,7 +431,8 @@ class DefaultComponentServiceSpec icon, componentGroupName, List(CategoryFraud), - designerWideComponentId + designerWideComponentId, + nonDefaultAllowedProcessingModes ) private def createComponent( @@ -402,7 +441,8 @@ class DefaultComponentServiceSpec icon: String, componentGroupName: ComponentGroupName, categories: List[String], - designerWideComponentId: Option[DesignerWideComponentId] = None + designerWideComponentId: Option[DesignerWideComponentId] = None, + nonDefaultAllowedProcessingModes: Option[AllowedProcessingModes] = None )(implicit user: LoggedUser) = { val compId = designerWideComponentId.getOrElse(cid(processingType, componentId)) val links = createLinks(compId, componentId) @@ -415,7 +455,8 @@ class DefaultComponentServiceSpec componentGroupName, categories, links, - usageCount + usageCount, + nonDefaultAllowedProcessingModes.getOrElse(AllowedProcessingModes.All) ) } @@ -436,7 +477,8 @@ class DefaultComponentServiceSpec componentGroupName, categories, links, - 0 + 0, + AllowedProcessingModes.All ) } @@ -776,7 +818,7 @@ class DefaultComponentServiceSpec ScenarioParametersService.createUnsafe(processingTypeDataMap.mapValuesNow(_.scenarioParameters)) ).mapValues { processingTypeData => val modelDefinitionEnricher = AlignedComponentsDefinitionProvider( - processingTypeData.designerModelData.modelData + processingTypeData.designerModelData ) ComponentServiceProcessingTypeData(modelDefinitionEnricher, processingTypeData.category) } diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataReaderSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataReaderSpec.scala index 45e4a1cd64e..7ae9065502b 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataReaderSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/processingtype/ProcessingTypeDataReaderSpec.scala @@ -53,16 +53,16 @@ class ProcessingTypeDataReaderSpec extends AnyFunSuite with Matchers { val fooCategoryUser = LoggedUser("fooCategoryUser", "fooCategoryUser", Map("foo" -> Set(Permission.Read))) - provider.forType("foo")(fooCategoryUser) + provider.forProcessingType("foo")(fooCategoryUser) an[UnauthorizedError] shouldBe thrownBy { - provider.forType("bar")(fooCategoryUser) + provider.forProcessingType("bar")(fooCategoryUser) } provider.all(fooCategoryUser).keys should contain theSameElementsAs List("foo") val mappedProvider = provider.mapValues(_ => ()) - mappedProvider.forType("foo")(fooCategoryUser) + mappedProvider.forProcessingType("foo")(fooCategoryUser) an[UnauthorizedError] shouldBe thrownBy { - mappedProvider.forType("bar")(fooCategoryUser) + mappedProvider.forProcessingType("bar")(fooCategoryUser) } mappedProvider.all(fooCategoryUser).keys should contain theSameElementsAs List("foo") } @@ -93,7 +93,7 @@ class ProcessingTypeDataReaderSpec extends AnyFunSuite with Matchers { ) ) val fooCategoryUser = LoggedUser("fooCategoryUser", "fooCategoryUser", Map("foo" -> Set(Permission.Read))) - val processingTypeData = provider.forTypeUnsafe("foo")(fooCategoryUser) + val processingTypeData = provider.forProcessingTypeUnsafe("foo")(fooCategoryUser) processingTypeData.deploymentData.engineSetupName shouldEqual EngineSetupName("Overriden Engine Setup") } diff --git a/docs-internal/api/nu-designer-openapi.yaml b/docs-internal/api/nu-designer-openapi.yaml index 32b05a7aaaa..b0a443a9724 100644 --- a/docs-internal/api/nu-designer-openapi.yaml +++ b/docs-internal/api/nu-designer-openapi.yaml @@ -126,6 +126,8 @@ paths: icon: /assets/icons/documentation.svg url: https://nussknacker.io/documentation/docs/scenarios_authoring/RRDataSourcesAndSinks/#collect usageCount: 2 + allowedProcessingModes: + - Request-Response '401': description: '' content: @@ -735,6 +737,20 @@ paths: application/json: schema: $ref: '#/components/schemas/NodeData' + examples: + Example: + summary: Basic node request + value: + id: enricher + service: + id: paramService + parameters: + - name: id + expression: + language: spel + expression: '''a''' + output: out + type: Enricher required: true responses: '200': @@ -743,6 +759,21 @@ paths: application/json: schema: $ref: '#/components/schemas/AdditionalInfo' + examples: + Example: + summary: Additional info for node + value: + content: |2 + + Samples: + + | id | value | + | --- | ----- | + | a | generated | + | b | not existent | + + Results for a can be found [here](http://touk.pl?id=a) + type: MarkdownAdditionalInfo '400': description: 'Invalid value for: body' content: @@ -800,6 +831,55 @@ paths: application/json: schema: $ref: '#/components/schemas/NodeValidationRequestDto' + examples: + Example0: + summary: Validate correct Filter node + value: + nodeData: + id: id + expression: + language: spel + expression: '#longValue > 1' + type: Filter + processProperties: + additionalFields: + properties: {} + metaDataType: '' + variableTypes: + existButString: + display: String + type: TypedClass + refClazzName: java.lang.String + params: [] + longValue: + display: Long + type: TypedClass + refClazzName: java.lang.Long + params: [] + Example1: + summary: Validate incorrect Filter node - wrong expression type + value: + nodeData: + id: id + expression: + language: spel + expression: '#existButString' + type: Filter + processProperties: + additionalFields: + properties: {} + metaDataType: '' + variableTypes: + existButString: + display: String + type: TypedClass + refClazzName: java.lang.String + params: [] + longValue: + display: Long + type: TypedClass + refClazzName: java.lang.Long + params: [] required: true responses: '200': @@ -808,12 +888,46 @@ paths: application/json: schema: $ref: '#/components/schemas/NodeValidationResultDto' + examples: + Example0: + summary: Node validation without errors + value: + expressionType: + display: Boolean + type: TypedClass + refClazzName: java.lang.Boolean + params: [] + validationErrors: [] + validationPerformed: true + Example1: + summary: Wrong parameter type + value: + expressionType: + display: Unknown + type: Unknown + refClazzName: java.lang.Object + params: [] + validationErrors: + - typ: ExpressionParserCompilationError + message: 'Failed to parse expression: Bad expression type, expected: + Boolean, found: String' + description: There is problem with expression in field Some($expression) + - it could not be parsed. + fieldName: $expression + errorType: SaveAllowed + validationPerformed: true '400': description: '' content: text/plain: schema: type: string + examples: + Example: + summary: Malformed TypingResult sent in request + value: |- + The request content was malformed: + Couldn't decode value 'WrongType'. Allowed values: 'TypedUnion,TypedDict,TypedObjectTypingResult,TypedTaggedValue,TypedClass,TypedObjectWithValue,TypedNull,Unknown '401': description: '' content: @@ -865,6 +979,38 @@ paths: application/json: schema: $ref: '#/components/schemas/ExpressionSuggestionRequestDto' + examples: + Example: + summary: Get suggestions for given expression + value: + expression: + language: spel + expression: '#inpu' + caretPosition2d: + row: 0 + column: 5 + variableTypes: + input: + display: 'Record{amount: Long(5)}' + type: TypedObjectTypingResult + fields: + amount: + value: 5 + display: Long(5) + type: TypedObjectWithValue + refClazzName: java.lang.Long + params: [] + refClazzName: java.util.Map + params: + - display: String + type: TypedClass + refClazzName: java.lang.String + params: [] + - value: 5 + display: Long(5) + type: TypedObjectWithValue + refClazzName: java.lang.Long + params: [] required: true responses: '200': @@ -875,12 +1021,49 @@ paths: type: array items: $ref: '#/components/schemas/ExpressionSuggestionDto' + examples: + Example0: + summary: Found a suggestion for currently given expression + value: + - methodName: input + refClazz: + display: 'Record{amount: Long(5)}' + type: TypedObjectTypingResult + fields: + amount: + value: 5 + display: Long(5) + type: TypedObjectWithValue + refClazzName: java.lang.Long + params: [] + refClazzName: java.util.Map + params: + - display: String + type: TypedClass + refClazzName: java.lang.String + params: [] + - value: 5 + display: Long(5) + type: TypedObjectWithValue + refClazzName: java.lang.Long + params: [] + fromClass: false + parameters: [] + Example1: + summary: No suggestions found for given expression + value: [] '400': description: '' content: text/plain: schema: type: string + examples: + Example: + summary: Malformed TypingResult sent in request + value: |- + The request content was malformed: + Couldn't decode value 'WrongType'. Allowed values: 'TypedUnion,TypedDict,TypedObjectTypingResult,TypedTaggedValue,TypedClass,TypedObjectWithValue,TypedNull,Unknown '401': description: '' content: @@ -908,6 +1091,10 @@ paths: text/plain: schema: type: string + examples: + Example: + summary: 'ProcessingType type: {processingType} not found' + value: 'ProcessingType type: ''processingType'' not found' security: - {} - httpAuth: [] @@ -915,7 +1102,7 @@ paths: post: tags: - Nodes - summary: Validate node parameters + summary: Validate given parameters operationId: postApiParametersProcessingtypeValidate parameters: - name: processingType @@ -928,6 +1115,42 @@ paths: application/json: schema: $ref: '#/components/schemas/ParametersValidationRequestDto' + examples: + Example: + summary: Parameters validation + value: + parameters: + - name: condition + typ: + display: Boolean + type: TypedClass + refClazzName: java.lang.Boolean + params: [] + expression: + language: spel + expression: '#input.amount > 2' + variableTypes: + input: + display: 'Record{amount: Long(5)}' + type: TypedObjectTypingResult + fields: + amount: + value: 5 + display: Long(5) + type: TypedObjectWithValue + refClazzName: java.lang.Long + params: [] + refClazzName: java.util.Map + params: + - display: String + type: TypedClass + refClazzName: java.lang.String + params: [] + - value: 5 + display: Long(5) + type: TypedObjectWithValue + refClazzName: java.lang.Long + params: [] required: true responses: '200': @@ -936,12 +1159,36 @@ paths: application/json: schema: $ref: '#/components/schemas/ParametersValidationResultDto' + examples: + Example0: + summary: Validate correct parameters + value: + validationErrors: [] + validationPerformed: true + Example1: + summary: Validate incorrect parameters + value: + validationErrors: + - typ: ExpressionParserCompilationError + message: 'Failed to parse expression: Bad expression type, expected: + Boolean, found: Long(5)' + description: There is problem with expression in field Some(condition) + - it could not be parsed. + fieldName: condition + errorType: SaveAllowed + validationPerformed: true '400': description: '' content: text/plain: schema: type: string + examples: + Example: + summary: Malformed TypingResult sent in request + value: |- + The request content was malformed: + Couldn't decode value 'WrongType'. Allowed values: 'TypedUnion,TypedDict,TypedObjectTypingResult,TypedTaggedValue,TypedClass,TypedObjectWithValue,TypedNull,Unknown '401': description: '' content: @@ -969,6 +1216,10 @@ paths: text/plain: schema: type: string + examples: + Example: + summary: 'ProcessingType type: {processingType} not found' + value: 'ProcessingType type: ''processingType'' not found' security: - {} - httpAuth: [] @@ -989,6 +1240,19 @@ paths: application/json: schema: $ref: '#/components/schemas/ProcessProperties' + examples: + Example: + summary: Proper process properties + value: + additionalFields: + properties: + parallelism: '' + checkpointIntervalInSeconds: '' + numberOfThreads: '2' + spillStateToDisk: 'true' + environment: test + useAsyncInterpretation: '' + metaDataType: StreamMetaData required: true responses: '200': @@ -997,6 +1261,12 @@ paths: application/json: schema: $ref: '#/components/schemas/AdditionalInfo' + examples: + Example: + summary: Some additional info for parameters + value: + content: 2 threads will be used on environment '{scenarioName}' + type: MarkdownAdditionalInfo '400': description: 'Invalid value for: body' content: @@ -1054,6 +1324,33 @@ paths: application/json: schema: $ref: '#/components/schemas/PropertiesValidationRequestDto' + examples: + Example0: + summary: Validate proper properties + value: + additionalFields: + properties: + parallelism: '' + checkpointIntervalInSeconds: '' + numberOfThreads: '2' + spillStateToDisk: 'true' + environment: test + useAsyncInterpretation: '' + metaDataType: StreamMetaData + name: test + Example1: + summary: Validate wrong 'number of threads' property + value: + additionalFields: + properties: + parallelism: '' + checkpointIntervalInSeconds: '' + numberOfThreads: a + spillStateToDisk: 'true' + environment: test + useAsyncInterpretation: '' + metaDataType: StreamMetaData + name: test required: true responses: '200': @@ -1062,6 +1359,28 @@ paths: application/json: schema: $ref: '#/components/schemas/NodeValidationResultDto' + examples: + Example0: + summary: Validation for proper node + value: + validationErrors: [] + validationPerformed: true + Example1: + summary: Validation for properties with errors + value: + validationErrors: + - typ: InvalidPropertyFixedValue + message: Property numberOfThreads (Number of threads) has invalid + value + description: 'Expected one of 1, 2, got: a.' + fieldName: numberOfThreads + errorType: SaveAllowed + - typ: UnknownProperty + message: Unknown property parallelism + description: Property parallelism is not known + fieldName: parallelism + errorType: SaveAllowed + validationPerformed: true '400': description: 'Invalid value for: body' content: @@ -1504,13 +1823,6 @@ components: format: date-time BoolParameterEditor: type: object - BranchEndData: - required: - - definition - type: object - properties: - definition: - $ref: '#/components/schemas/BranchEndDefinition' BranchEndDefinition: required: - id @@ -1634,6 +1946,7 @@ components: - componentType - componentGroupName - usageCount + - allowedProcessingModes type: object properties: id: @@ -1657,6 +1970,10 @@ components: usageCount: type: integer format: int64 + allowedProcessingModes: + type: array + items: + type: string ComponentType: type: string enum: @@ -1707,28 +2024,6 @@ components: - type: 'null' CronParameterEditor: type: object - CustomNode: - required: - - id - - nodeType - type: object - properties: - id: - type: string - outputVar: - type: - - string - - 'null' - nodeType: - type: string - parameters: - type: array - items: - $ref: '#/components/schemas/Parameter' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' DataToRefresh: type: string enum: @@ -1750,7 +2045,7 @@ components: id: type: string valueType: - $ref: '#/components/schemas/TypedTaggedValue' + $ref: '#/components/schemas/SingleTypingResult' DictDto: required: - id @@ -1777,7 +2072,15 @@ components: type: object properties: expectedType: - $ref: '#/components/schemas/TypingResult' + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' DictParameterEditor: required: - dictId @@ -1825,7 +2128,15 @@ components: timeRangeComponents: type: array items: + required: + - name + - duration type: object + properties: + name: + type: string + duration: + type: string Edge: required: - from @@ -1847,23 +2158,6 @@ components: - $ref: '#/components/schemas/FragmentOutput' - $ref: '#/components/schemas/NextSwitch' - $ref: '#/components/schemas/SwitchDefault' - Enricher: - required: - - id - - service - - output - type: object - properties: - id: - type: string - service: - $ref: '#/components/schemas/ServiceRef' - output: - type: string - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' ErrorDetails: oneOf: - $ref: '#/components/schemas/TabularDataDefinitionParserErrorDetails' @@ -1887,7 +2181,15 @@ components: methodName: type: string refClazz: - $ref: '#/components/schemas/TypingResult' + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' fromClass: type: boolean description: @@ -1921,24 +2223,6 @@ components: type: string expression: $ref: '#/components/schemas/Expression' - Filter: - required: - - id - - expression - type: object - properties: - id: - type: string - expression: - $ref: '#/components/schemas/Expression' - isDisabled: - type: - - boolean - - 'null' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' FilterFalse: type: object FilterTrue: @@ -1967,45 +2251,6 @@ components: properties: refClazzName: type: string - FragmentInput: - required: - - id - - ref - type: object - properties: - id: - type: string - ref: - $ref: '#/components/schemas/FragmentRef' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' - isDisabled: - type: - - boolean - - 'null' - fragmentParams: - type: - - array - - 'null' - items: - $ref: '#/components/schemas/FragmentParameter' - FragmentInputDefinition: - required: - - id - type: object - properties: - id: - type: string - parameters: - type: array - items: - $ref: '#/components/schemas/FragmentParameter' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' FragmentOutput: required: - name @@ -2013,35 +2258,6 @@ components: properties: name: type: string - FragmentOutputDefinition: - required: - - id - - outputName - type: object - properties: - id: - type: string - outputName: - type: string - fields: - type: array - items: - $ref: '#/components/schemas/Field' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' - FragmentOutputVarDefinition: - required: - - name - type: object - properties: - name: - type: string - fields: - type: array - items: - $ref: '#/components/schemas/Field' FragmentParameter: required: - name @@ -2095,24 +2311,6 @@ components: type: string nodeId: type: string - FragmentUsageOutput: - required: - - id - - outputName - type: object - properties: - id: - type: string - outputName: - type: string - outputVar: - oneOf: - - $ref: '#/components/schemas/FragmentOutputVarDefinition' - - type: 'null' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' HealthCheckProcessErrorResponseDto: required: - status @@ -2147,32 +2345,6 @@ components: - 'null' items: type: string - Join: - required: - - id - - nodeType - type: object - properties: - id: - type: string - outputVar: - type: - - string - - 'null' - nodeType: - type: string - parameters: - type: array - items: - $ref: '#/components/schemas/Parameter' - branchParameters: - type: array - items: - $ref: '#/components/schemas/BranchParameters' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' JsonParameterEditor: type: object LayoutData: @@ -2187,6 +2359,18 @@ components: y: type: integer format: int64 + List_Field: + oneOf: + - $ref: '#/components/schemas/Nil' + - type: array + items: + $ref: '#/components/schemas/Field' + List_FragmentParameter: + oneOf: + - $ref: '#/components/schemas/Nil' + - type: array + items: + $ref: '#/components/schemas/FragmentParameter' Map_EngineSetupName_List_String: type: object additionalProperties: @@ -2218,7 +2402,15 @@ components: Map_TypingResultInJson: type: object additionalProperties: - $ref: '#/components/schemas/TypingResult' + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' MarkdownAdditionalInfo: required: - content @@ -2233,24 +2425,372 @@ components: properties: condition: $ref: '#/components/schemas/Expression' + Nil: + type: object NodeData: oneOf: - - $ref: '#/components/schemas/BranchEndData' - - $ref: '#/components/schemas/CustomNode' - - $ref: '#/components/schemas/Enricher' - - $ref: '#/components/schemas/Filter' - - $ref: '#/components/schemas/FragmentInput' - - $ref: '#/components/schemas/FragmentInputDefinition' - - $ref: '#/components/schemas/FragmentOutputDefinition' - - $ref: '#/components/schemas/FragmentUsageOutput' - - $ref: '#/components/schemas/Join' - - $ref: '#/components/schemas/Processor' - - $ref: '#/components/schemas/Sink' - - $ref: '#/components/schemas/Source' - - $ref: '#/components/schemas/Split' - - $ref: '#/components/schemas/Switch' - $ref: '#/components/schemas/Variable' - $ref: '#/components/schemas/VariableBuilder' + - title: BranchEndData + required: + - definition + - type + type: object + properties: + definition: + $ref: '#/components/schemas/BranchEndDefinition' + type: + $ref: '#/components/schemas/NodeTypes' + - title: CustomNode + required: + - id + - nodeType + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + id: + type: string + nodeType: + type: string + outputVar: + type: + - string + - 'null' + parameters: + type: array + items: + $ref: '#/components/schemas/Parameter' + type: + $ref: '#/components/schemas/NodeTypes1' + - title: Enricher + required: + - id + - output + - service + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + id: + type: string + output: + type: string + service: + $ref: '#/components/schemas/ServiceRef' + type: + $ref: '#/components/schemas/NodeTypes2' + - title: Filter + required: + - expression + - id + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + expression: + $ref: '#/components/schemas/Expression' + id: + type: string + isDisabled: + type: + - boolean + - 'null' + type: + $ref: '#/components/schemas/NodeTypes3' + - title: FragmentInput + required: + - id + - ref + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + id: + type: string + isDisabled: + type: + - boolean + - 'null' + fragmentParams: + type: + - array + - 'null' + items: + $ref: '#/components/schemas/FragmentParameter' + ref: + $ref: '#/components/schemas/FragmentRef' + type: + $ref: '#/components/schemas/NodeTypes4' + - title: FragmentInputDefinition + required: + - id + - parameters + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + id: + type: string + parameters: + $ref: '#/components/schemas/List_FragmentParameter' + type: + $ref: '#/components/schemas/NodeTypes5' + - title: FragmentOutputDefinition + required: + - id + - fields + - outputName + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + id: + type: string + fields: + $ref: '#/components/schemas/List_Field' + outputName: + type: string + type: + $ref: '#/components/schemas/NodeTypes6' + - title: Join + required: + - id + - nodeType + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParameters: + type: array + items: + $ref: '#/components/schemas/BranchParameters' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + id: + type: string + nodeType: + type: string + outputVar: + type: + - string + - 'null' + parameters: + type: array + items: + $ref: '#/components/schemas/Parameter' + type: + $ref: '#/components/schemas/NodeTypes7' + - title: Processor + required: + - id + - service + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + id: + type: string + isDisabled: + type: + - boolean + - 'null' + service: + $ref: '#/components/schemas/ServiceRef' + type: + $ref: '#/components/schemas/NodeTypes8' + - title: Sink + required: + - id + - ref + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + id: + type: string + isDisabled: + type: + - boolean + - 'null' + ref: + $ref: '#/components/schemas/SinkRef' + type: + $ref: '#/components/schemas/NodeTypes9' + - title: Source + required: + - id + - type + - ref + type: object + properties: + id: + type: string + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + type: + $ref: '#/components/schemas/NodeTypes10' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + ref: + $ref: '#/components/schemas/SourceRef' + - title: Split + required: + - id + - type + type: object + properties: + id: + type: string + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + type: + $ref: '#/components/schemas/NodeTypes11' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + - title: Switch + required: + - id + - type + type: object + properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' + expression: + oneOf: + - $ref: '#/components/schemas/Expression' + - type: 'null' + exprVal: + type: + - string + - 'null' + id: + type: string + type: + $ref: '#/components/schemas/NodeTypes12' + NodeTypes: + type: string + enum: + - BranchEndData + NodeTypes1: + type: string + enum: + - CustomNode + NodeTypes10: + type: string + enum: + - Source + NodeTypes11: + type: string + enum: + - Split + NodeTypes12: + type: string + enum: + - Switch + NodeTypes13: + type: string + enum: + - Variable + NodeTypes14: + type: string + enum: + - VariableBuilder + NodeTypes2: + type: string + enum: + - Enricher + NodeTypes3: + type: string + enum: + - Filter + NodeTypes4: + type: string + enum: + - FragmentInput + NodeTypes5: + type: string + enum: + - FragmentInputDefinition + NodeTypes6: + type: string + enum: + - FragmentOutputDefinition + NodeTypes7: + type: string + enum: + - Join + NodeTypes8: + type: string + enum: + - Processor + NodeTypes9: + type: string + enum: + - Sink NodeUsageData: oneOf: - $ref: '#/components/schemas/FragmentUsageData' @@ -2293,7 +2833,7 @@ components: type: object properties: nodeData: - type: object + $ref: '#/components/schemas/NodeData' variableTypes: $ref: '#/components/schemas/Map_TypingResultInJson' branchVariableTypes: @@ -2316,11 +2856,17 @@ components: - array - 'null' items: - $ref: '#/components/schemas/UIParameterDto' + $ref: '#/components/schemas/UIParameter' expressionType: oneOf: - - $ref: '#/components/schemas/TypingResult' - - type: 'null' + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' validationErrors: type: array items: @@ -2374,7 +2920,15 @@ components: name: type: string refClazz: - $ref: '#/components/schemas/TypingResult' + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' ParameterEditor: oneOf: - $ref: '#/components/schemas/BoolParameterEditor' @@ -2437,7 +2991,15 @@ components: timeRangeComponents: type: array items: + required: + - name + - duration type: object + properties: + name: + type: string + duration: + type: string ProcessAction: required: - id @@ -2535,24 +3097,6 @@ components: properties: additionalFields: $ref: '#/components/schemas/ProcessAdditionalFields' - Processor: - required: - - id - - service - type: object - properties: - id: - type: string - service: - $ref: '#/components/schemas/ServiceRef' - isDisabled: - type: - - boolean - - 'null' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' PropertiesValidationRequestDto: required: - additionalFields @@ -2635,28 +3179,13 @@ components: - $ref: '#/components/schemas/TabularTypedDataEditor' - $ref: '#/components/schemas/TextareaParameterEditor' - $ref: '#/components/schemas/TimeParameterEditor' - Sink: - required: - - id - - ref - type: object - properties: - id: - type: string - ref: - $ref: '#/components/schemas/SinkRef' - legacyEndResultExpression: - oneOf: - - $ref: '#/components/schemas/Expression' - - type: 'null' - isDisabled: - type: - - boolean - - 'null' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' + SingleTypingResult: + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' SinkRef: required: - typ @@ -2668,20 +3197,6 @@ components: type: array items: $ref: '#/components/schemas/Parameter' - Source: - required: - - id - - ref - type: object - properties: - id: - type: string - ref: - $ref: '#/components/schemas/SourceRef' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' SourceRef: required: - typ @@ -2695,17 +3210,6 @@ components: $ref: '#/components/schemas/Parameter' SpelTemplateParameterEditor: type: object - Split: - required: - - id - type: object - properties: - id: - type: string - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' SqlParameterEditor: type: object Status: @@ -2718,25 +3222,6 @@ components: - ERROR StringParameterEditor: type: object - Switch: - required: - - id - type: object - properties: - id: - type: string - expression: - oneOf: - - $ref: '#/components/schemas/Expression' - - type: 'null' - exprVal: - type: - - string - - 'null' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' SwitchDefault: type: object TabularDataDefinitionParserErrorDetails: @@ -2762,7 +3247,7 @@ components: display: type: string type: - $ref: '#/components/schemas/TypingType' + $ref: '#/components/schemas/Types1' refClazzName: type: string params: @@ -2779,12 +3264,25 @@ components: display: type: string type: - $ref: '#/components/schemas/TypingType' + $ref: '#/components/schemas/Types2' dict: $ref: '#/components/schemas/Dict' TypedNull: title: TypedNull + required: + - type type: object + properties: + display: + type: string + type: + $ref: '#/components/schemas/Types6' + refClazzName: + type: string + params: + type: array + items: + $ref: '#/components/schemas/TypingResult' TypedObjectTypingResult: title: TypedObjectTypingResult required: @@ -2796,7 +3294,7 @@ components: display: type: string type: - $ref: '#/components/schemas/TypingType' + $ref: '#/components/schemas/Types3' fields: $ref: '#/components/schemas/Map_TypingResult' refClazzName: @@ -2817,7 +3315,7 @@ components: display: type: string type: - $ref: '#/components/schemas/TypingType' + $ref: '#/components/schemas/Types4' refClazzName: type: string params: @@ -2829,7 +3327,6 @@ components: required: - tag - type - - refClazzName type: object properties: tag: @@ -2837,27 +3334,54 @@ components: display: type: string type: - $ref: '#/components/schemas/TypingType' - refClazzName: - type: string - params: - type: array - items: - $ref: '#/components/schemas/TypingResult' + $ref: '#/components/schemas/Types5' TypedUnion: title: TypedUnion required: - type + - union type: object properties: display: type: string type: - $ref: '#/components/schemas/TypingType' + $ref: '#/components/schemas/Types7' union: type: array items: $ref: '#/components/schemas/TypingResult' + Types: + type: string + enum: + - Unknown + Types1: + type: string + enum: + - TypedClass + Types2: + type: string + enum: + - TypedDict + Types3: + type: string + enum: + - TypedObjectTypingResult + Types4: + type: string + enum: + - TypedObjectWithValue + Types5: + type: string + enum: + - TypedTaggedValue + Types6: + type: string + enum: + - TypedNull + Types7: + type: string + enum: + - TypedUnion TypingResult: oneOf: - $ref: '#/components/schemas/TypedClass' @@ -2868,18 +3392,7 @@ components: - $ref: '#/components/schemas/TypedTaggedValue' - $ref: '#/components/schemas/TypedUnion' - $ref: '#/components/schemas/Unknown' - TypingType: - type: string - enum: - - TypedUnion - - TypedDict - - TypedObjectTypingResult - - TypedTaggedValue - - TypedClass - - TypedObjectWithValue - - TypedNull - - Unknown - UIParameterDto: + UIParameter: required: - name - typ @@ -2893,7 +3406,15 @@ components: name: type: string typ: - $ref: '#/components/schemas/TypingResult' + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' editor: $ref: '#/components/schemas/ParameterEditor' defaultValue: @@ -2922,12 +3443,33 @@ components: name: type: string typ: - $ref: '#/components/schemas/TypingResult' + oneOf: + - $ref: '#/components/schemas/TypedClass' + - $ref: '#/components/schemas/TypedDict' + - $ref: '#/components/schemas/TypedNull' + - $ref: '#/components/schemas/TypedObjectTypingResult' + - $ref: '#/components/schemas/TypedObjectWithValue' + - $ref: '#/components/schemas/TypedTaggedValue' + - $ref: '#/components/schemas/TypedUnion' + - $ref: '#/components/schemas/Unknown' expression: $ref: '#/components/schemas/Expression' Unknown: title: Unknown + required: + - type type: object + properties: + display: + type: string + type: + $ref: '#/components/schemas/Types' + refClazzName: + type: string + params: + type: array + items: + $ref: '#/components/schemas/TypingResult' UserDefinedAdditionalNodeFields: type: object properties: @@ -2961,40 +3503,52 @@ components: allowOtherValue: type: boolean Variable: + title: Variable required: - id + - type - varName - value type: object properties: id: type: string - varName: - type: string - value: - $ref: '#/components/schemas/Expression' additionalFields: oneOf: - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - type: 'null' + type: + $ref: '#/components/schemas/NodeTypes13' + varName: + type: string + value: + $ref: '#/components/schemas/Expression' VariableBuilder: + title: VariableBuilder required: - id + - type - varName type: object properties: + additionalFields: + oneOf: + - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' + - type: 'null' + branchParametersTemplate: + type: array + items: + $ref: '#/components/schemas/Parameter' id: type: string - varName: - type: string fields: type: array items: $ref: '#/components/schemas/Field' - additionalFields: - oneOf: - - $ref: '#/components/schemas/UserDefinedAdditionalNodeFields' - - type: 'null' + type: + $ref: '#/components/schemas/NodeTypes14' + varName: + type: string VersionId: required: - value diff --git a/docs/Changelog.md b/docs/Changelog.md index fe6b9e8e6cd..2bd9304c798 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -2,12 +2,15 @@ 1.15.0 (Not released yet) ------------------------- +* [#5620](https://github.com/TouK/nussknacker/pull/5620) Nodes Api OpenApi-based documentation (e.g. `https://demo.nussknacker.io/api/docs`) * [#5760](https://github.com/TouK/nussknacker/pull/5760) [#5599](https://github.com/TouK/nussknacker/pull/5599) Libraries bump: * Flink: 1.17.2 -> 1.18.1 * Tapir: 1.7.4 -> 1.9.11 * openapi-circe-yaml: 0.6.0 -> 0.7.4 * [#5438](https://github.com/TouK/nussknacker/pull/5438) [#5495](https://github.com/TouK/nussknacker/pull/5495) Improvement in DeploymentManager API: * Alignment in the api of primary (deploy/cancel) actions and the experimental api of custom actions. +* [#5783](https://github.com/TouK/nussknacker/pull/5783) Added information about component's allowed processing modes to Component API +* [#5831](https://github.com/TouK/nussknacker/pull/5831) Fragment input parameters presets support * [#5780](https://github.com/TouK/nussknacker/pull/5780) Fixed Scala case classes serialization when a class has additional fields in its body 1.14.0 (21 Mar 2024) diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index c4acc8e3d45..c0f1dd9f9da 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -27,6 +27,9 @@ To see the biggest differences please consult the [changelog](Changelog.md). * in `ProcessAction` attribute `actionType` renamed to `actionName` * in table `process_actions` column `action_type` is renamed to `action_name` * [#5762](https://github.com/TouK/nussknacker/pull/5762) for the Flink-based TestRunner scenario builder you should replace the last component that was `testResultService` with `testResultSink` +* [#5783](https://github.com/TouK/nussknacker/pull/5783) Return type of `allowedProcessingMode` method in `Component` trait has been changed to `AllowedProcessingModes` type which is one of: + * `AllowedProcessingModes.All` in case of all processing modes allowed + * `AllowedProcessingModes.SetOf(nonEmptySetOfAllowedProcessingModes)` in case only set of processing modes is allowed ### Configuration changes @@ -48,6 +51,9 @@ To see the biggest differences please consult the [changelog](Changelog.md). * [#5724](https://github.com/TouK/nussknacker/pull/5724) Improvements: Run Designer locally * Introduce `JAVA_DEBUG_PORT` to run the Designer locally with remote debugging capability * Removed `SCALA_VERSION`, please use `NUSSKNACKER_SCALA_VERSION` instead of it +* [#5824](https://github.com/TouK/nussknacker/pull/5824) Decision Table parameters rename: + * "Basic Decision Table" -> "Decision Table" + * "Expression" -> "Filtering expression" ## In version 1.14.0 diff --git a/engine/common/components-tests/src/test/scala/pl/touk/nussknacker/engine/common/components/DecisionTableSpec.scala b/engine/common/components-tests/src/test/scala/pl/touk/nussknacker/engine/common/components/DecisionTableSpec.scala index 3670e4e2f02..0319414784c 100644 --- a/engine/common/components-tests/src/test/scala/pl/touk/nussknacker/engine/common/components/DecisionTableSpec.scala +++ b/engine/common/components-tests/src/test/scala/pl/touk/nussknacker/engine/common/components/DecisionTableSpec.scala @@ -100,7 +100,7 @@ trait DecisionTableSpec ExpressionParserCompilationError( message = "There is no property 'years' in type: Record{DoB: LocalDate, age: Integer, name: String}", nodeId = "decision-table", - paramName = Some(ParameterName("Expression")), + paramName = Some(ParameterName("Filtering expression")), originalExpr = "#ROW['years'] > #input.minAge", details = None ) @@ -124,7 +124,7 @@ trait DecisionTableSpec ExpressionParserCompilationError( message = "Wrong part types", nodeId = "decision-table", - paramName = Some(ParameterName("Expression")), + paramName = Some(ParameterName("Filtering expression")), originalExpr = "#ROW['name'] > #input.minAge", details = None ) @@ -151,7 +151,7 @@ trait DecisionTableSpec ExpressionParserCompilationError( message = "Typing error in some cells", nodeId = "decision-table", - paramName = Some(ParameterName("Basic Decision Table")), + paramName = Some(ParameterName("Decision Table")), originalExpr = invalidColumnTypeDecisionTableJson.expression, details = Some( TabularDataDefinitionParserErrorDetails( @@ -222,12 +222,10 @@ trait DecisionTableSpec ScenarioBuilder .requestResponse("test scenario") .source("request", TestScenarioRunner.testDataSource) - .enricher( - "decision-table", - "dtResult", - "decision-table", - "Basic Decision Table" -> basicDecisionTableDefinition, - "Expression" -> expression, + .decisionTable( + decisionTableParamValue = basicDecisionTableDefinition, + filterExpressionParamValue = expression, + output = "dtResult", ) .end("end", "value" -> sinkValueExpression) } diff --git a/engine/common/components/src/main/scala/pl/touk/nussknacker/engine/common/components/DecisionTable.scala b/engine/common/components/src/main/scala/pl/touk/nussknacker/engine/common/components/DecisionTable.scala index 564e9eed592..5fac818d3b4 100644 --- a/engine/common/components/src/main/scala/pl/touk/nussknacker/engine/common/components/DecisionTable.scala +++ b/engine/common/components/src/main/scala/pl/touk/nussknacker/engine/common/components/DecisionTable.scala @@ -26,7 +26,7 @@ object DecisionTable extends EagerService with SingleInputDynamicComponent[Servi private type Output = java.util.List[java.util.Map[String, Any]] private object BasicDecisionTableParameter { - val name: ParameterName = ParameterName("Basic Decision Table") + val name: ParameterName = ParameterName("Decision Table") val declaration: ParameterExtractor[TabularTypedData] with ParameterCreatorWithNoDependency = ParameterDeclaration @@ -36,7 +36,7 @@ object DecisionTable extends EagerService with SingleInputDynamicComponent[Servi } private object FilterDecisionTableExpressionParameter { - val name: ParameterName = ParameterName("Expression") + val name: ParameterName = ParameterName("Filtering expression") val declaration: ParameterCreator[TabularTypedData] with ParameterExtractor[LazyParameter[lang.Boolean]] = { ParameterDeclaration @@ -70,9 +70,9 @@ object DecisionTable extends EagerService with SingleInputDynamicComponent[Servi dependencies: List[NodeDependencyValue], finalState: Option[Unit] ): ServiceInvoker = { - val tabularTypedData = BasicDecisionTableParameter.declaration.extractValueUnsafe(params) - val filterExpression = FilterDecisionTableExpressionParameter.declaration.extractValueUnsafe(params) - new DecisionTableImplementation(tabularTypedData, filterExpression) + val tabularTypedData = BasicDecisionTableParameter.declaration.extractValueUnsafe(params) + val filteringExpression = FilterDecisionTableExpressionParameter.declaration.extractValueUnsafe(params) + new DecisionTableImplementation(tabularTypedData, filteringExpression) } private lazy val prepare: ContextTransformationDefinition = { case TransformationStep(Nil, _) => diff --git a/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFragmentSourceDefinitionPreparer.scala b/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFragmentSourceDefinitionPreparer.scala index 34f60f4ecf4..6a5ab379f00 100644 --- a/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFragmentSourceDefinitionPreparer.scala +++ b/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFragmentSourceDefinitionPreparer.scala @@ -3,6 +3,7 @@ package pl.touk.nussknacker.engine.process.compiler import cats.data.Validated.Valid import cats.data.ValidatedNel import org.apache.flink.api.common.typeinfo.TypeInformation +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.DesignerWideComponentId import pl.touk.nussknacker.engine.api.context.{ProcessCompilationError, ValidationContext} import pl.touk.nussknacker.engine.api.definition.Parameter @@ -43,6 +44,7 @@ class StubbedFragmentSourceDefinitionPreparer( docsUrl = None, translateGroupName = Some(_), designerWideId = DesignerWideComponentId("dumpId"), + allowedProcessingModes = AllowedProcessingModes.All ) } diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ComponentExecutorFactory.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ComponentExecutorFactory.scala index 0b88b9e0773..88c15f80672 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ComponentExecutorFactory.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ComponentExecutorFactory.scala @@ -49,7 +49,7 @@ class ComponentExecutorFactory(parameterEvaluator: ParameterEvaluator) extends L nodeId: NodeId ): ComponentExecutor = { implicit val lazyParameterCreationStrategy: LazyParameterCreationStrategy = - componentDefinition.implementation match { + componentDefinition.component match { // Services are created within Interpreter so for every engine, lazy parameters can be evaluable. Other component types // (Sources, Sinks and CustomComponent) have engine specific logic around lazy parameters. // For Flink, they need to be Serializable (PostponedEvaluatorLazyParameterStrategy) diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala index 4971fba0478..1daa05cac88 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala @@ -76,7 +76,7 @@ object ProcessCompilerData { expressionEvaluator, interpreter, listeners, - servicesDefs.map(service => service.name -> service.implementation.asInstanceOf[Lifecycle]).toMap + servicesDefs.map(service => service.name -> service.component.asInstanceOf[Lifecycle]).toMap ) } diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala index 311ed363b78..244cb2d7842 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala @@ -352,7 +352,7 @@ class NodeCompiler( ): NodeCompilationResult[compiledgraph.service.ServiceRef] = { definitions.getComponent(ComponentType.Service, n.id) match { - case Some(componentDefinition) if componentDefinition.implementation.isInstanceOf[EagerService] => + case Some(componentDefinition) if componentDefinition.component.isInstanceOf[EagerService] => compileEagerService(n, componentDefinition, validationContext, outputVar) case Some(static: MethodBasedComponentDefinitionWithImplementation) => ServiceCompiler.compile(n, outputVar, static, validationContext) @@ -617,7 +617,7 @@ class NodeCompiler( outputVar: Option[String], dynamicDefinition: DynamicComponentDefinitionWithImplementation )(implicit metaData: MetaData, nodeId: NodeId): ValidatedNel[ProcessCompilationError, TransformationResult] = - (dynamicDefinition.implementation, eitherSingleOrJoin) match { + (dynamicDefinition.component, eitherSingleOrJoin) match { case (single: SingleInputDynamicComponent[_], Left(singleCtx)) => dynamicNodeValidator.validateNode( single, diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionExtractor.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionExtractor.scala index f35ce44d59d..fa47341ab30 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionExtractor.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionExtractor.scala @@ -103,7 +103,7 @@ object ComponentDefinitionExtractor { DynamicComponentDefinitionWithImplementation( name = componentName, implementationInvoker = invoker, - implementation = e, + component = e, componentTypeSpecificData = componentTypeSpecificData, uiDefinition = uiDefinition, parametersConfig = parametersConfig @@ -135,7 +135,7 @@ object ComponentDefinitionExtractor { MethodBasedComponentDefinitionWithImplementation( name = componentName, implementationInvoker = invoker, - implementation = component, + component = component, componentTypeSpecificData = componentTypeSpecificData, staticDefinition = staticDefinition, uiDefinition = uiDefinition diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionWithImplementation.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionWithImplementation.scala index 0635cedfe98..e76f068c232 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionWithImplementation.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/ComponentDefinitionWithImplementation.scala @@ -1,5 +1,6 @@ package pl.touk.nussknacker.engine.definition.component +import pl.touk.nussknacker.engine.api.component.Component._ import pl.touk.nussknacker.engine.api.component.ComponentType.ComponentType import pl.touk.nussknacker.engine.api.component._ import pl.touk.nussknacker.engine.api.definition.WithExplicitTypesToExtract @@ -18,7 +19,7 @@ trait ComponentDefinitionWithImplementation extends ObjectOperatingOnTypes { // For purpose of transforming (e.g.) stubbing of the implementation def withImplementationInvoker(invoker: ComponentImplementationInvoker): ComponentDefinitionWithImplementation - def implementation: Component + def component: Component def componentTypeSpecificData: ComponentTypeSpecificData @@ -44,7 +45,7 @@ trait ComponentDefinitionWithImplementation extends ObjectOperatingOnTypes { final def docsUrl: Option[String] = uiDefinition.docsUrl override final def definedTypes: List[TypingResult] = { - val fromExplicitTypes = implementation match { + val fromExplicitTypes = component match { case explicit: WithExplicitTypesToExtract => explicit.typesToExtract case _ => Nil } @@ -53,6 +54,8 @@ trait ComponentDefinitionWithImplementation extends ObjectOperatingOnTypes { protected def typesFromStaticDefinition: List[TypingResult] + def allowedProcessingModes: AllowedProcessingModes = component.allowedProcessingModes + } trait ObjectOperatingOnTypes { diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/DynamicComponentStaticDefinitionDeterminer.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/DynamicComponentStaticDefinitionDeterminer.scala index 772cd076390..c5096848947 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/DynamicComponentStaticDefinitionDeterminer.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/DynamicComponentStaticDefinitionDeterminer.scala @@ -35,7 +35,7 @@ class DynamicComponentStaticDefinitionDeterminer( val parameters = determineInitialParameters(dynamic) ComponentStaticDefinition( parameters, - staticReturnType(dynamic.implementation) + staticReturnType(dynamic.component) ) } @@ -51,7 +51,7 @@ class DynamicComponentStaticDefinitionDeterminer( transformer, Nil, Nil, - if (dynamic.implementation.nodeDependencies.contains(OutputVariableNameDependency)) Some("fakeOutputVariable") + if (dynamic.component.nodeDependencies.contains(OutputVariableNameDependency)) Some("fakeOutputVariable") else None, dynamic.parametersConfig )(inputContext) @@ -66,7 +66,7 @@ class DynamicComponentStaticDefinitionDeterminer( } } - dynamic.implementation match { + dynamic.component match { case withStatic: WithStaticParameters => StandardParameterEnrichment.enrichParameterDefinitions(withStatic.staticParameters, dynamic.parametersConfig) case single: SingleInputDynamicComponent[_] => diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/bultin/BuiltInComponentsDefinitionsPreparer.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/bultin/BuiltInComponentsDefinitionsPreparer.scala index 1cf2f8a9f47..bc6eb8b9311 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/bultin/BuiltInComponentsDefinitionsPreparer.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/bultin/BuiltInComponentsDefinitionsPreparer.scala @@ -2,6 +2,7 @@ package pl.touk.nussknacker.engine.definition.component.bultin import cats.implicits.catsSyntaxSemigroup import pl.touk.nussknacker.engine.api.component.BuiltInComponentId +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.definition.component.defaultconfig.DefaultComponentConfigDeterminer import pl.touk.nussknacker.engine.definition.component.methodbased.MethodBasedComponentDefinitionWithImplementation import pl.touk.nussknacker.engine.definition.component.{ @@ -32,7 +33,8 @@ class BuiltInComponentsDefinitionsPreparer(componentsUiConfig: ComponentsUiConfi BuiltInComponentSpecificData, ComponentStaticDefinition(List.empty, None), uiDefinition, - allowedProcessingModes = None // built-in components are available in every processing mode + allowedProcessingModes = + AllowedProcessingModes.All // built-in components are available in every processing mode ) } } diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/dynamic/DynamicComponentDefinitionWithImplementation.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/dynamic/DynamicComponentDefinitionWithImplementation.scala index 8d6d892eed3..c3fd1555acd 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/dynamic/DynamicComponentDefinitionWithImplementation.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/dynamic/DynamicComponentDefinitionWithImplementation.scala @@ -14,7 +14,7 @@ import pl.touk.nussknacker.engine.definition.component.{ final case class DynamicComponentDefinitionWithImplementation( override val name: String, override val implementationInvoker: ComponentImplementationInvoker, - override val implementation: DynamicComponent[_], + override val component: DynamicComponent[_], override val componentTypeSpecificData: ComponentTypeSpecificData, override protected val uiDefinition: ComponentUiDefinition, parametersConfig: Map[ParameterName, ParameterConfig] diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/methodbased/MethodBasedComponentDefinitionWithImplementation.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/methodbased/MethodBasedComponentDefinitionWithImplementation.scala index d1216d052a3..5900e056f62 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/methodbased/MethodBasedComponentDefinitionWithImplementation.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/component/methodbased/MethodBasedComponentDefinitionWithImplementation.scala @@ -1,6 +1,7 @@ package pl.touk.nussknacker.engine.definition.component.methodbased -import pl.touk.nussknacker.engine.api.component.{Component, ProcessingMode} +import pl.touk.nussknacker.engine.api.component.Component +import pl.touk.nussknacker.engine.api.component.Component._ import pl.touk.nussknacker.engine.api.definition.Parameter import pl.touk.nussknacker.engine.api.typed.typing.TypingResult import pl.touk.nussknacker.engine.definition.component._ @@ -8,7 +9,7 @@ import pl.touk.nussknacker.engine.definition.component._ final case class MethodBasedComponentDefinitionWithImplementation( override val name: String, override val implementationInvoker: ComponentImplementationInvoker, - override val implementation: Component, + override val component: Component, override val componentTypeSpecificData: ComponentTypeSpecificData, staticDefinition: ComponentStaticDefinition, override protected val uiDefinition: ComponentUiDefinition, @@ -40,18 +41,19 @@ object MethodBasedComponentDefinitionWithImplementation { componentTypeSpecificData: ComponentTypeSpecificData, staticDefinition: ComponentStaticDefinition, uiDefinition: ComponentUiDefinition, - allowedProcessingModes: Option[Set[ProcessingMode]], + allowedProcessingModes: AllowedProcessingModes, ): MethodBasedComponentDefinitionWithImplementation = { MethodBasedComponentDefinitionWithImplementation( name, ComponentImplementationInvoker.nullReturningComponentImplementationInvoker, - new NullComponent(allowedProcessingModes), + new FakeComponentWithAllowedProcessingModesSpecified(allowedProcessingModes), componentTypeSpecificData, staticDefinition, uiDefinition ) } - private class NullComponent(override val allowedProcessingModes: Option[Set[ProcessingMode]]) extends Component + class FakeComponentWithAllowedProcessingModesSpecified(override val allowedProcessingModes: AllowedProcessingModes) + extends Component } diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinition.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinition.scala index 3d3515f11d9..f5ae1784e54 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinition.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinition.scala @@ -1,10 +1,12 @@ package pl.touk.nussknacker.engine.definition.fragment +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.{ComponentGroupName, DesignerWideComponentId} import pl.touk.nussknacker.engine.api.definition.Parameter import pl.touk.nussknacker.engine.api.typed.typing.{Typed, Unknown} import pl.touk.nussknacker.engine.definition.component.defaultconfig.DefaultComponentConfigDeterminer import pl.touk.nussknacker.engine.definition.component.methodbased.MethodBasedComponentDefinitionWithImplementation +import pl.touk.nussknacker.engine.definition.component.methodbased.MethodBasedComponentDefinitionWithImplementation.FakeComponentWithAllowedProcessingModesSpecified import pl.touk.nussknacker.engine.definition.component.{ ComponentDefinitionWithImplementation, ComponentImplementationInvoker, @@ -22,6 +24,7 @@ object FragmentComponentDefinition { docsUrl: Option[String], translateGroupName: ComponentGroupName => Option[ComponentGroupName], designerWideId: DesignerWideComponentId, + allowedProcessingModes: AllowedProcessingModes, ): ComponentDefinitionWithImplementation = { val uiDefinition = DefaultComponentConfigDeterminer.forFragment(docsUrl, translateGroupName, designerWideId) @@ -29,7 +32,7 @@ object FragmentComponentDefinition { MethodBasedComponentDefinitionWithImplementation( name = name, implementationInvoker = implementationInvoker, - implementation = null, + component = new FakeComponentWithAllowedProcessingModesSpecified(allowedProcessingModes), componentTypeSpecificData = FragmentSpecificData(outputNames), staticDefinition = ComponentStaticDefinition( parameters, diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala index 503b1bfbc79..c0dbf5c29c6 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala @@ -1,6 +1,7 @@ package pl.touk.nussknacker.engine.definition.fragment import cats.data.Validated +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.{ ComponentGroupName, ComponentId, @@ -24,6 +25,7 @@ class FragmentComponentDefinitionExtractor( def extractFragmentComponentDefinition( fragment: CanonicalProcess, + allowedProcessingModes: AllowedProcessingModes ): Validated[FragmentDefinitionError, ComponentDefinitionWithImplementation] = { FragmentGraphDefinitionExtractor.extractFragmentGraph(fragment).map { case (input, _, outputs) => val parameters = @@ -40,6 +42,7 @@ class FragmentComponentDefinitionExtractor( docsUrl = docsUrl, translateGroupName = translateGroupName, designerWideId = componentId, + allowedProcessingModes = allowedProcessingModes, ) } } diff --git a/interpreter/src/main/scala/pl/touk/nussknacker/engine/testing/ModelDefinitionBuilder.scala b/interpreter/src/main/scala/pl/touk/nussknacker/engine/testing/ModelDefinitionBuilder.scala index b1b5b46690e..4199ef8b6eb 100644 --- a/interpreter/src/main/scala/pl/touk/nussknacker/engine/testing/ModelDefinitionBuilder.scala +++ b/interpreter/src/main/scala/pl/touk/nussknacker/engine/testing/ModelDefinitionBuilder.scala @@ -1,6 +1,8 @@ package pl.touk.nussknacker.engine.testing +import cats.data.NonEmptySet import pl.touk.nussknacker.engine.api.SpelExpressionExcludeList +import pl.touk.nussknacker.engine.api.component.Component.AllowedProcessingModes import pl.touk.nussknacker.engine.api.component.{ ComponentGroupName, ComponentId, @@ -52,7 +54,11 @@ final case class ModelDefinitionBuilder( returnType: Option[TypingResult], params: Parameter* ): ModelDefinitionBuilder = - withSource(name, ComponentStaticDefinition(params.toList, returnType), Some(Set(ProcessingMode.UnboundedStream))) + withSource( + name, + ComponentStaticDefinition(params.toList, returnType), + AllowedProcessingModes.SetOf(ProcessingMode.UnboundedStream) + ) def withSink(name: String, params: Parameter*): ModelDefinitionBuilder = withSink(name, ComponentStaticDefinition(params.toList, None)) @@ -95,7 +101,7 @@ final case class ModelDefinitionBuilder( private def withSource( name: String, staticDefinition: ComponentStaticDefinition, - allowedProcessingModes: Option[Set[ProcessingMode]] + allowedProcessingModes: AllowedProcessingModes ): ModelDefinitionBuilder = withComponent( name, @@ -116,7 +122,7 @@ final case class ModelDefinitionBuilder( SinkSpecificData, componentGroupName = None, designerWideComponentId = None, - allowedProcessingModes = None + allowedProcessingModes = AllowedProcessingModes.All ) private def wrapService( @@ -129,7 +135,7 @@ final case class ModelDefinitionBuilder( ServiceSpecificData, componentGroupName = None, designerWideComponentId = None, - allowedProcessingModes = None + allowedProcessingModes = AllowedProcessingModes.All ) private def wrapCustom( @@ -145,7 +151,7 @@ final case class ModelDefinitionBuilder( componentSpecificData, componentGroupName, designerWideComponentId, - allowedProcessingModes = None + allowedProcessingModes = AllowedProcessingModes.All ) private def withComponent( @@ -154,7 +160,7 @@ final case class ModelDefinitionBuilder( componentTypeSpecificData: ComponentTypeSpecificData, componentGroupName: Option[ComponentGroupName], designerWideComponentId: Option[DesignerWideComponentId], - allowedProcessingModes: Option[Set[ProcessingMode]] + allowedProcessingModes: AllowedProcessingModes ): ModelDefinitionBuilder = { val defaultConfig = DefaultComponentConfigDeterminer.forNotBuiltInComponentType( diff --git a/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/component/ComponentFromProvidersExtractorTest.scala b/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/component/ComponentFromProvidersExtractorTest.scala index 17f7d442880..ffde16812ef 100644 --- a/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/component/ComponentFromProvidersExtractorTest.scala +++ b/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/component/ComponentFromProvidersExtractorTest.scala @@ -37,7 +37,7 @@ class ComponentFromProvidersExtractorTest extends AnyFunSuite with Matchers { ) components should have size 7 components.map(_.name) should contain theSameElementsAs (1 to 7).map(i => s"component-v$i") - components.map(_.implementation) should contain theSameElementsAs (1 to 7).map(i => DynamicService(s"v$i")) + components.map(_.component) should contain theSameElementsAs (1 to 7).map(i => DynamicService(s"v$i")) } test("should handle multiple providers") { @@ -52,7 +52,7 @@ class ComponentFromProvidersExtractorTest extends AnyFunSuite with Matchers { val expectedNames = (1 to 2).map(i => s"component-v$i") ++ (1 to 3).map(i => s"t1-component-v$i") components.map(_.name) should contain theSameElementsAs expectedNames val expectedServices = (1 to 2).map(i => DynamicService(s"v$i")) ++ (1 to 3).map(i => DynamicService(s"v$i")) - components.map(_.implementation) should contain theSameElementsAs expectedServices + components.map(_.component) should contain theSameElementsAs expectedServices } test("should discover components with same name and different component type for same provider") { @@ -65,7 +65,7 @@ class ComponentFromProvidersExtractorTest extends AnyFunSuite with Matchers { components.size shouldBe 2 components.map(_.name) shouldBe List("component", "component") - val implementations = components.map(_.implementation) + val implementations = components.map(_.component) implementations.head shouldBe a[Service] implementations(1) shouldBe a[SinkFactory] } @@ -103,7 +103,7 @@ class ComponentFromProvidersExtractorTest extends AnyFunSuite with Matchers { components should have size 1 val component = components.head component.name shouldBe "auto-component" - component.implementation shouldBe AutoService + component.component shouldBe AutoService } test("should skip incompatible auto loadable providers") { diff --git a/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/model/ModelDefinitionFromConfigCreatorExtractorSpec.scala b/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/model/ModelDefinitionFromConfigCreatorExtractorSpec.scala index 9af8892c5ab..993e86745f2 100644 --- a/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/model/ModelDefinitionFromConfigCreatorExtractorSpec.scala +++ b/interpreter/src/test/scala/pl/touk/nussknacker/engine/definition/model/ModelDefinitionFromConfigCreatorExtractorSpec.scala @@ -72,7 +72,7 @@ class ModelDefinitionFromConfigCreatorExtractorSpec extends AnyFunSuite with Mat definition .asInstanceOf[DynamicComponentDefinitionWithImplementation] - .implementation + .component .asInstanceOf[EagerServiceWithStaticParametersAndReturnType] .returnType shouldBe Typed[String] } diff --git a/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala b/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala index 6bbda60196f..081eb6250fa 100644 --- a/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala +++ b/scenario-api/src/main/scala/pl/touk/nussknacker/engine/build/GraphBuilder.scala @@ -197,6 +197,20 @@ trait GraphBuilder[R] { new SimpleGraphBuilder(SourceNode(node.Join(id, output, typ, toNodeParameters(params), branchParameters), _)) } + def decisionTable( + decisionTableParamValue: Expression, + filterExpressionParamValue: Expression, + output: String, + ): GraphBuilder[R] = { + enricher( + id = "decision-table", + output, + svcId = "decision-table", + "Decision Table" -> decisionTableParamValue, + "Filtering expression" -> filterExpressionParamValue, + ) + } + private def toNodeParameters(params: Iterable[(String, Expression)]) = { params.map { case (name, expr) => NodeParameter(ParameterName(name), expr) }.toList } diff --git a/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala b/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala index ad5e6cf0bfa..fd978680f94 100644 --- a/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala +++ b/utils/schemed-kafka-components-utils/src/main/scala/pl/touk/nussknacker/engine/schemedkafka/encode/AvroSchemaOutputValidator.scala @@ -62,9 +62,9 @@ class AvroSchemaOutputValidator(validationMode: ValidationMode) extends LazyLogg validateMapSchema(typingResult, schema, path) case (tc @ TypedClass(cl, _), Type.ARRAY) if classOf[java.util.List[_]].isAssignableFrom(cl) => validateArraySchema(tc, schema, path) - case (_ @TypedNull, _) if !schema.isNullable => + case (TypedNull, _) if !schema.isNullable => invalid(typingResult, schema, path) - case (_ @TypedNull, _) if schema.isNullable => + case (TypedNull, _) if schema.isNullable => valid case (typingResult, Type.ENUM) => validateEnum(typingResult, schema, path) diff --git a/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuScalaTestAssertions.scala b/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuScalaTestAssertions.scala index 05ef5768503..e924a5e0db8 100644 --- a/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuScalaTestAssertions.scala +++ b/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuScalaTestAssertions.scala @@ -1,9 +1,13 @@ package pl.touk.nussknacker.test +import org.everit.json.schema.Schema +import org.json.JSONObject import org.scalactic.source +import org.scalatest.matchers.{MatchResult, Matcher} import org.scalatest.{Assertion, Assertions} import scala.reflect.ClassTag +import scala.util.{Success, Try} trait NuScalaTestAssertions extends Assertions { @@ -20,4 +24,17 @@ trait NuScalaTestAssertions extends Assertions { } } + class ValidateJsonMatcher(json: JSONObject) extends Matcher[Schema] { + + override def apply(left: Schema): MatchResult = + MatchResult( + Try(left.validate(json)) == Success(()), + s"JSON $json cannot be validated by schema $left", + s"JSON $json can be validated by schema $left" + ) + + } + + def validateJson(json: JSONObject): Matcher[Schema] = new ValidateJsonMatcher(json) + } diff --git a/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuTapirSchemaTestHelpers.scala b/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuTapirSchemaTestHelpers.scala new file mode 100644 index 00000000000..9ddcdfb1167 --- /dev/null +++ b/utils/test-utils/src/main/scala/pl/touk/nussknacker/test/NuTapirSchemaTestHelpers.scala @@ -0,0 +1,43 @@ +package pl.touk.nussknacker.test + +import io.circe.{Encoder, Printer} +import io.circe.syntax.EncoderOps +import org.everit.json.schema.Schema +import org.everit.json.schema.loader.SchemaLoader +import org.json.JSONObject +import sttp.tapir.docs.apispec.schema.TapirSchemaToJsonSchema +import sttp.apispec.circe._ + +trait NuTapirSchemaTestHelpers { + + protected def prepareJsonSchemaFromTapirSchema[T](schema: sttp.tapir.Schema[T]): org.everit.json.schema.Schema = { + val jsonSchema = TapirSchemaToJsonSchema( + schema, + markOptionsAsNullable = true + ).asJson + val schemaStr: String = Printer.spaces2.print(jsonSchema.deepDropNullValues) + val jsonObject = new JSONObject(schemaStr) + + val schemaLoader = SchemaLoader + .builder() + .schemaJson(jsonObject) + .build() + + schemaLoader + .load() + .build() + .asInstanceOf[Schema] + } + + protected def createJsonObjectFrom[T](value: T)(implicit encoder: Encoder[T]): JSONObject = { + val sampleJson = encoder.apply(value) + val sampleStr: String = Printer.spaces2.print(sampleJson.deepDropNullValues) + + new JSONObject(sampleStr) + } + + protected def createJsonFromString(json: String): JSONObject = { + new JSONObject(json) + } + +}