All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.touk.nussknacker.ui.process.ProcessStateDefinitionService.scala Maven / Gradle / Ivy

There is a newer version: 1.18.1
Show newest version
package pl.touk.nussknacker.ui.process

import cats.data.{NonEmptyList, Validated}
import io.circe.generic.JsonCodec
import pl.touk.nussknacker.engine.api.deployment.StateDefinitionDetails
import pl.touk.nussknacker.engine.api.deployment.StateStatus.StatusName
import pl.touk.nussknacker.engine.api.process.ProcessingType
import pl.touk.nussknacker.engine.util.Implicits.RichTupleList
import pl.touk.nussknacker.ui.process.ProcessStateDefinitionService.StateDefinitionDeduplicationResult
import pl.touk.nussknacker.ui.process.processingtype.ProcessingTypeData
import pl.touk.nussknacker.ui.process.processingtype.provider.ProcessingTypeDataProvider
import pl.touk.nussknacker.ui.security.api.LoggedUser

import java.net.URI

class ProcessStateDefinitionService(
    processingTypeDataProvider: ProcessingTypeDataProvider[
      String,
      Map[StatusName, StateDefinitionDeduplicationResult]
    ],
) {

  def fetchStateDefinitions(implicit user: LoggedUser): List[UIStateDefinition] = {
    val processingTypeToCategoryMap = processingTypeDataProvider.all
    val stateDefinitionsMapping     = processingTypeDataProvider.combined
    stateDefinitionsMapping.toList
      .map { case (statusName, StateDefinitionDeduplicationResult(stateDefinition, processingTypes)) =>
        // processingTypeDataProvider already check read permission
        val userCategoriesWhereStateAppears = processingTypes.flatMap(processingTypeToCategoryMap.get)
        // TODO: Here we switch icon to non-animated version, in rather not sophisticated manner. We should be able to handle
        //  both animated (in scenario list, scenario details) and non-animated (filter options) versions.
        UIStateDefinition(
          statusName,
          stateDefinition.displayableName,
          URI.create(stateDefinition.icon.toString.replace("-animated", "")),
          stateDefinition.tooltip,
          userCategoriesWhereStateAppears.toList
        )
      }
      .filter(_.categories.nonEmpty)
  }

}

object ProcessStateDefinitionService {

  final case class StateDefinitionDeduplicationResult(
      definition: StateDefinitionDetails,
      processingTypes: Set[ProcessingType]
  )

  /**
    * Each processing type define its own state definitions. Technically it is possible that two processing types provide
    * states with the same StatusName and different UI configurations (name and icon). Here is an assertion
    * that this does not happen and each state has the same definition across all processingTypes.
    */
  def createDefinitionsMappingUnsafe(
      processingTypes: Map[ProcessingType, ProcessingTypeData]
  ): Map[StatusName, StateDefinitionDeduplicationResult] = {
    import cats.instances.list._
    import cats.syntax.alternative._

    val (namesWithNonUniqueDefinitions, validDefinitions) = processingTypeStateDefinitions(processingTypes)
      .map { case (processingType, statusName, stateDefinitionDetails) =>
        statusName -> (processingType, stateDefinitionDetails)
      }
      .toGroupedMapSafe
      .toList
      .map { case (statusName, stateDefinitionsForOneStatusName) =>
        deduplicateStateDefinitions(stateDefinitionsForOneStatusName)
          .map(statusName -> _)
          .leftMap(_ => statusName)
      }
      .separate
    if (namesWithNonUniqueDefinitions.nonEmpty) {
      throw new IllegalStateException(
        s"State definitions are not unique for states: ${namesWithNonUniqueDefinitions.mkString(", ")}"
      )
    }
    validDefinitions.toMap
  }

  private def processingTypeStateDefinitions(
      processingTypes: Map[ProcessingType, ProcessingTypeData]
  ): List[(ProcessingType, StatusName, StateDefinitionDetails)] = {
    processingTypes.toList.flatMap { case (processingType, processingTypeData) =>
      processingTypeData.deploymentData.validDeploymentManagerOrStub.processStateDefinitionManager.stateDefinitions
        .map { case (name, sd) => (processingType, name, sd) }
    }
  }

  private def deduplicateStateDefinitions(
      stateDefinitionsForOneStatusName: NonEmptyList[(ProcessingType, StateDefinitionDetails)]
  ): Validated[Unit, StateDefinitionDeduplicationResult] = {
    val uniqueDefinitionsForName = stateDefinitionsForOneStatusName.toList
      // TODO: For some reasons we don't check other properties (tooltip and description). This code would be
      //       a little bit easier if we do - figure out why and write a comment
      .groupBy { case (_, sd) => (sd.displayableName, sd.icon) }
    lazy val deduplicatedResult = StateDefinitionDeduplicationResult(
      stateDefinitionsForOneStatusName.head._2,
      stateDefinitionsForOneStatusName.map(_._1).toList.toSet
    )
    Validated.cond(uniqueDefinitionsForName.size == 1, deduplicatedResult, ())
  }

}

@JsonCodec final case class UIStateDefinition(
    name: StatusName,
    displayableName: String,
    icon: URI,
    tooltip: String,
    categories: List[String]
)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy