Skip to content

Commit

Permalink
[NU-1324] ComponentService uses domain objects instead of ComponentNo…
Browse files Browse the repository at this point in the history
…deTemplate (#5329)
  • Loading branch information
arkadius committed Jan 12, 2024
1 parent ad289d4 commit 51407ab
Show file tree
Hide file tree
Showing 64 changed files with 916 additions and 1,217 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class InterpreterSetup[T: ClassTag] {

val definitions = ModelDefinition(
ComponentDefinitionWithImplementation.forList(components, ComponentsUiConfig.Empty),
ModelDefinitionBuilder.toDefinitionWithImpl(ModelDefinitionBuilder.emptyExpressionConfig),
ModelDefinitionBuilder.emptyExpressionConfig,
ClassExtractionSettings.Default
)
val definitionsWithTypes = ModelDefinitionWithClasses(definitions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class ExpressionSuggesterBenchmarkSetup() {
)

private val expressionSuggester = new ExpressionSuggester(
ModelDefinitionBuilder.toDefinitionWithImpl(ModelDefinitionBuilder.emptyExpressionConfig),
ModelDefinitionBuilder.emptyExpressionConfig,
clazzDefinitions,
dictServices,
getClass.getClassLoader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import pl.touk.nussknacker.engine.api.parameter.{ParameterValueCompileTimeValida
*
* TODO: Currently the value of 'valueCompileTimeValidation' has no effect, it'll be supported in the future but is included now to keep the API stable.
* Validating and extracting a parameter's validators requires access to expressionCompiler and validationContext,
* this is currently hard to achieve where AdditionalUIConfigProvider is used (UIProcessObjectsFactory),
* this is currently hard to achieve where AdditionalUIConfigProvider is used (DefinitionsService and ComponentService),
* after refactoring it to be used at the level of ModelData or so it should be easy, and support for 'valueCompileTimeValidation' will be possible
*/
trait AdditionalUIConfigProvider extends Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import pl.touk.nussknacker.engine.api.definition.{ParameterEditor, ParameterVali
disabled: Boolean = false
) {
def paramConfig(name: String): ParameterConfig = params.flatMap(_.get(name)).getOrElse(ParameterConfig.empty)

def componentGroupUnsafe: ComponentGroupName =
componentGroup.getOrElse(throw new IllegalStateException(s"Component group not defined in $this"))
}

object SingleComponentConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ package object definition {

import pl.touk.nussknacker.engine.api.CirceUtil._

@JsonCodec(encodeOnly = true) final case class UIProcessObjects(
componentGroups: List[ComponentGroup],
// This class contains various views on definitions, used in a different FE contexts
@JsonCodec(encodeOnly = true) final case class UIDefinitions(
// This is dedicated view for the components toolbox panel
componentGroups: List[UIComponentGroup],
components: Map[ComponentInfo, UIComponentDefinition],
classes: List[TypingResult],
// TODO: remove it, use components field on the FE side instead
componentsConfig: Map[String, SingleComponentConfig],
scenarioPropertiesConfig: Map[String, UiScenarioPropertyConfig],
edgesForNodes: List[NodeEdges],
edgesForNodes: List[UINodeEdges],
customActions: List[UICustomAction]
)

Expand Down Expand Up @@ -73,56 +75,49 @@ package object definition {

@JsonCodec(encodeOnly = true) final case class UISourceParameters(sourceId: String, parameters: List[UIParameter])

final case class NodeEdges(
final case class UINodeEdges(
componentId: ComponentInfo,
edges: List[EdgeType],
canChooseNodes: Boolean,
isForInputDefinition: Boolean
)

object NodeEdges {
object UINodeEdges {
implicit val componentIdEncoder: Encoder[ComponentInfo] = Encoder.encodeString.contramap(_.toString)

implicit val encoder: Encoder[NodeEdges] = deriveConfiguredEncoder
implicit val encoder: Encoder[UINodeEdges] = deriveConfiguredEncoder
}

object ComponentNodeTemplate {
object UIComponentNodeTemplate {

def create(
componentInfo: ComponentInfo,
nodeTemplate: NodeData,
categories: List[String],
branchParametersTemplate: List[NodeParameter]
): ComponentNodeTemplate =
ComponentNodeTemplate(
): UIComponentNodeTemplate =
UIComponentNodeTemplate(
componentInfo.`type`,
componentInfo.name,
nodeTemplate,
categories,
branchParametersTemplate
)

}

@JsonCodec(encodeOnly = true) final case class ComponentNodeTemplate(
@JsonCodec(encodeOnly = true) final case class UIComponentNodeTemplate(
// This field is used to generate unique key in DOM model on FE side (the label isn't unique)
`type`: ComponentType,
label: String,
node: NodeData,
// TODO: Remove it - it is not used on the FE code, only ComponentService use it and we can take it from
// the processingType property there
categories: List[String],
branchParametersTemplate: List[NodeParameter] = List.empty,
// TODO: This field is added temporary to pick correct icon - we shouldn't use this class for other purposes than encoding to json
isEnricher: Option[Boolean] = None
branchParametersTemplate: List[NodeParameter] = List.empty
) {
// TODO: This is temporary - we shouldn't use ComponentNodeTemplate class for other purposes than encoding to json
def componentInfo: ComponentInfo = ComponentInfo(`type`, label)
}

@JsonCodec(encodeOnly = true) final case class ComponentGroup(
@JsonCodec(encodeOnly = true) final case class UIComponentGroup(
name: ComponentGroupName,
components: List[ComponentNodeTemplate]
components: List[UIComponentNodeTemplate]
)

@JsonCodec final case class UiScenarioPropertyConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import akka.http.scaladsl.model._
import akka.http.scaladsl.server.{Directives, Route}
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport
import pl.touk.nussknacker.engine.ProcessingTypeData
import pl.touk.nussknacker.ui.definition.{AdditionalUIConfigFinalizer, ModelDefinitionEnricher, UIProcessObjectsFactory}
import pl.touk.nussknacker.ui.process.ProcessCategoryService
import pl.touk.nussknacker.ui.definition.{AdditionalUIConfigFinalizer, DefinitionsService, ModelDefinitionEnricher}
import pl.touk.nussknacker.ui.process.fragment.FragmentRepository
import pl.touk.nussknacker.ui.process.processingtypedata.ProcessingTypeDataProvider
import pl.touk.nussknacker.ui.security.api.LoggedUser
Expand All @@ -14,46 +13,21 @@ import pl.touk.nussknacker.ui.util.NuPathMatchers
import scala.concurrent.ExecutionContext

class DefinitionResources(
processingTypeDataProvider: ProcessingTypeDataProvider[
(ProcessingTypeData, ModelDefinitionEnricher, AdditionalUIConfigFinalizer),
_
],
fragmentRepository: FragmentRepository,
getProcessCategoryService: () => ProcessCategoryService,
)(implicit ec: ExecutionContext)
extends Directives
serviceProvider: ProcessingTypeDataProvider[DefinitionsService, _],
) extends Directives
with FailFastCirceSupport
with NuPathMatchers
with RouteWithUser {

def securedRoute(implicit user: LoggedUser): Route = encodeResponse {
pathPrefix("processDefinitionData" / Segment) { processingType =>
processingTypeDataProvider
serviceProvider
.forType(processingType)
.map { case (processingTypeData, modelDefinitionEnricher, additionalUIConfigFinalizer) =>
.map { service =>
pathEndOrSingleSlash {
get {
parameter(Symbol("isFragment").as[Boolean]) { isFragment =>
complete(
fragmentRepository.fetchLatestFragments(processingType).map { fragments =>
// TODO: Extract DefinitionsService, move factory logic there, provide a method with a few arguments instead of 8 arguments
val enrichedModelDefinition =
modelDefinitionEnricher
.modelDefinitionWithBuiltInComponentsAndFragments(isFragment, fragments, processingType)
val finalizedScenarioPropertiesConfig = additionalUIConfigFinalizer
.finalizeScenarioProperties(processingTypeData.scenarioPropertiesConfig, processingType)
UIProcessObjectsFactory.prepareUIProcessObjects(
enrichedModelDefinition,
processingTypeData.modelData,
processingTypeData.deploymentManager,
user,
isFragment,
getProcessCategoryService(),
finalizedScenarioPropertiesConfig,
processingType
)
}
)
complete(service.prepareUIDefinitions(processingType, isFragment))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,67 @@
package pl.touk.nussknacker.ui.component

import pl.touk.nussknacker.engine.api.component.ComponentGroupName
import pl.touk.nussknacker.engine.api.process.ProcessingType
import pl.touk.nussknacker.engine.definition.component._
import pl.touk.nussknacker.engine.definition.component.defaultconfig.DefaultsComponentGroupName
import pl.touk.nussknacker.engine.definition.model.ModelDefinition
import pl.touk.nussknacker.engine.util.Implicits.RichTupleList
import pl.touk.nussknacker.restmodel.definition._
import pl.touk.nussknacker.ui.process.ProcessCategoryService
import pl.touk.nussknacker.ui.security.api.LoggedUser

import scala.collection.immutable.ListMap

class ComponentGroupsPreparer(componentsGroupMapping: Map[ComponentGroupName, Option[ComponentGroupName]]) {
object ComponentGroupsPreparer {

def prepareComponentGroups(
user: LoggedUser,
definitions: ModelDefinition[ComponentStaticDefinition],
processCategoryService: ProcessCategoryService,
processingType: ProcessingType
): List[ComponentGroup] = {
): List[UIComponentGroup] = {
ComponentNodeTemplatePreparer
.componentNodeTemplatesWithGroupNames(user, definitions, processCategoryService, processingType)
.componentNodeTemplatesWithGroupNames(definitions)
.map(templateWithGroups =>
(templateWithGroups.originalGroupName, templateWithGroups.mappedGroupName) -> templateWithGroups.nodeTemplate
)
.toGroupedMap
.toList
.map { case (originalGroupName, nodeTemplates) =>
val nonHiddenMappedGroupName = componentsGroupMapping.getOrElse(originalGroupName, Some(originalGroupName))
(originalGroupName, nonHiddenMappedGroupName, nodeTemplates)
}
.flatMap { case (originalGroupName, nonHiddenMappedGroupName, nodeTemplates) =>
nonHiddenMappedGroupName.map { mappedGroupName =>
(originalGroupName, mappedGroupName, nodeTemplates)
}
}
// We sort based on the original group name. It might be tricky when someone instead of using component group
// mapping feature, would override component group for component which is source or ending component.
// Maybe instead of sorting based on the original group name we should sort based on rules likes:
// group contains only (or some?) components that are sources. The same for ending components.
// What about base components in this case?
.sortBy {
case (
DefaultsComponentGroupName.SourcesGroupName | DefaultsComponentGroupName.FragmentsDefinitionGroupName,
mappedGroupName,
(
DefaultsComponentGroupName.SourcesGroupName | DefaultsComponentGroupName.FragmentsDefinitionGroupName,
mappedGroupName
),
_
) =>
(0, mappedGroupName.toLowerCase)
case (DefaultsComponentGroupName.BaseGroupName, mappedGroupName, _) =>
case ((DefaultsComponentGroupName.BaseGroupName, mappedGroupName), _) =>
(1, mappedGroupName.toLowerCase)
case (
DefaultsComponentGroupName.ServicesGroupName | DefaultsComponentGroupName.OptionalEndingCustomGroupName |
DefaultsComponentGroupName.SinksGroupName,
mappedGroupName,
(
DefaultsComponentGroupName.ServicesGroupName |
DefaultsComponentGroupName.OptionalEndingCustomGroupName | DefaultsComponentGroupName.SinksGroupName,
mappedGroupName
),
_
) =>
(3, mappedGroupName.toLowerCase)
case (_, mappedGroupName, _) =>
case ((_, mappedGroupName), _) =>
// We put everything else in the middle
(2, mappedGroupName.toLowerCase)
}
.map { case (_, mappedGroupName, nodeTemplates) =>
.map { case ((_, mappedGroupName), nodeTemplates) =>
(mappedGroupName, nodeTemplates)
}
// We need to merge node templates that originally where in the other group but after mapping are in the same group
.foldLeft(ListMap.empty[ComponentGroupName, List[ComponentNodeTemplate]]) {
.foldLeft(ListMap.empty[ComponentGroupName, List[UIComponentNodeTemplate]]) {
case (acc, (groupName, nodeTemplates)) =>
val mergedNodeTemplates = acc.getOrElse(groupName, List.empty) ++ nodeTemplates
acc + (groupName -> mergedNodeTemplates)
}
.toList
.map { case (groupName, nodeTemplates) =>
ComponentGroup(groupName, nodeTemplates.sortBy(_.label.toLowerCase))
UIComponentGroup(groupName, nodeTemplates.sortBy(_.label.toLowerCase))
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package pl.touk.nussknacker.ui.component

import pl.touk.nussknacker.engine.api.component.{BuiltInComponentInfo, ComponentGroupName, ComponentInfo}
import pl.touk.nussknacker.engine.api.process.ProcessingType
import pl.touk.nussknacker.engine.definition.component._
import pl.touk.nussknacker.engine.definition.model.ModelDefinition
import pl.touk.nussknacker.engine.graph.evaluatedparam.{Parameter => NodeParameter}
Expand All @@ -12,28 +11,13 @@ 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.restmodel.definition.ComponentNodeTemplate
import pl.touk.nussknacker.ui.process.{ProcessCategoryService, UserCategoryService}
import pl.touk.nussknacker.ui.security.api.LoggedUser
import pl.touk.nussknacker.restmodel.definition.UIComponentNodeTemplate

private[component] object ComponentNodeTemplatePreparer {

def componentNodeTemplatesWithGroupNames(
user: LoggedUser,
definitions: ModelDefinition[ComponentStaticDefinition],
processCategoryService: ProcessCategoryService,
processingType: ProcessingType
): List[(ComponentGroupName, ComponentNodeTemplate)] = {
val userCategoryService = new UserCategoryService(processCategoryService)
val userCategories = userCategoryService.getUserCategories(user)
val processingTypeCategories = List(processCategoryService.getProcessingTypeCategoryUnsafe(processingType))
val userProcessingTypeCategories = userCategories.intersect(processingTypeCategories)

def filterCategories(componentDefinition: ComponentStaticDefinition): List[String] =
userProcessingTypeCategories.intersect(
componentDefinition.categories.getOrElse(processCategoryService.getAllCategories)
)

definitions: ModelDefinition[ComponentStaticDefinition]
): List[ComponentNodeTemplateWithGroupNames] = {
def parameterTemplates(componentDefinition: ComponentStaticDefinition): List[NodeParameter] =
NodeParameterTemplatesPreparer.prepareNodeParameterTemplates(componentDefinition.parameters)

Expand Down Expand Up @@ -87,17 +71,25 @@ private[component] object ComponentNodeTemplatePreparer {
}
val branchParametersTemplate =
NodeParameterTemplatesPreparer.prepareNodeBranchParameterTemplates(componentDefinition.parameters)
val componentNodeTemplate = ComponentNodeTemplate.create(
val componentNodeTemplate = UIComponentNodeTemplate.create(
info,
nodeTemplate,
filterCategories(componentDefinition),
branchParametersTemplate
)
val componentGroup = componentDefinition.componentGroupUnsafe
(componentGroup, componentNodeTemplate)
ComponentNodeTemplateWithGroupNames(
componentNodeTemplate,
componentDefinition.originalGroupName,
componentDefinition.componentGroupUnsafe
)
}

definitions.components.toList.map(prepareComponentNodeTemplateWithGroup _ tupled)
}

}

private[component] case class ComponentNodeTemplateWithGroupNames(
nodeTemplate: UIComponentNodeTemplate,
originalGroupName: ComponentGroupName,
mappedGroupName: ComponentGroupName
)

0 comments on commit 51407ab

Please sign in to comment.