Skip to content

Commit

Permalink
separate presentation from business logic - part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
arkadius committed Feb 17, 2025
1 parent 064fa83 commit a6fc258
Show file tree
Hide file tree
Showing 28 changed files with 344 additions and 329 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import java.net.URI
*/
class OverridingProcessStateDefinitionManager(
delegate: ProcessStateDefinitionManager,
statusActionsPF: PartialFunction[ScenarioStatusWithScenarioContext, List[ScenarioActionName]] =
statusActionsPF: PartialFunction[ScenarioStatusWithScenarioContext, Set[ScenarioActionName]] =
PartialFunction.empty,
statusIconsPF: PartialFunction[StateStatus, URI] = PartialFunction.empty,
statusTooltipsPF: PartialFunction[StateStatus, String] = PartialFunction.empty,
Expand All @@ -34,7 +34,7 @@ class OverridingProcessStateDefinitionManager(
override def visibleActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] =
customVisibleActions.getOrElse(delegate.visibleActions(input))

override def statusActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] =
override def statusActions(input: ScenarioStatusWithScenarioContext): Set[ScenarioActionName] =
statusActionsPF.applyOrElse(input, delegate.statusActions)

override def actionTooltips(input: ScenarioStatusWithScenarioContext): Map[ScenarioActionName, String] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package pl.touk.nussknacker.engine.api.deployment
import io.circe.Json
import pl.touk.nussknacker.engine.api.ProcessVersion
import pl.touk.nussknacker.engine.api.deployment.ProcessStateDefinitionManager.{
DefaultVisibleActions,
ScenarioStatusPresentationDetails,
ScenarioStatusWithScenarioContext,
defaultVisibleActions
ScenarioStatusWithScenarioContext
}
import pl.touk.nussknacker.engine.api.deployment.StateStatus.StatusName
import pl.touk.nussknacker.engine.api.process.VersionId
Expand Down Expand Up @@ -51,7 +51,7 @@ trait ProcessStateDefinitionManager {
/**
* Actions that are applicable to scenario in general. They may be available only in particular states, as defined by `def statusActions`
*/
def visibleActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] = defaultVisibleActions
def visibleActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] = DefaultVisibleActions

/**
* Custom tooltips for actions
Expand All @@ -61,7 +61,7 @@ trait ProcessStateDefinitionManager {
/**
* Allowed transitions between states.
*/
def statusActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName]
def statusActions(input: ScenarioStatusWithScenarioContext): Set[ScenarioActionName]

/**
* Returns presentations details of status
Expand Down Expand Up @@ -109,8 +109,8 @@ object ProcessStateDefinitionManager {

final case class ScenarioStatusPresentationDetails(
visibleActions: List[ScenarioActionName],
// This one is not exactly a part of presentation but for now we keep in this class
allowedActions: List[ScenarioActionName],
// This one is not exactly a part of presentation, it is rather a thing related with scenario lifecycle but for now it is kept here
allowedActions: Set[ScenarioActionName],
actionTooltips: Map[ScenarioActionName, String],
icon: URI,
tooltip: String,
Expand All @@ -120,7 +120,7 @@ object ProcessStateDefinitionManager {
/**
* Actions, that are applicable in standard use-cases for most deployment managers.
*/
val defaultVisibleActions: List[ScenarioActionName] = List(
val DefaultVisibleActions: List[ScenarioActionName] = List(
ScenarioActionName.Cancel,
ScenarioActionName.Deploy,
ScenarioActionName.Pause,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import pl.touk.nussknacker.engine.api.deployment.{
*/
object SimpleProcessStateDefinitionManager extends ProcessStateDefinitionManager {

override def statusActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] =
statusActionsPF.lift(input.status).getOrElse(DefaultActions)
override def statusActions(input: ScenarioStatusWithScenarioContext): Set[ScenarioActionName] =
statusActionsPF.lift(input.status).getOrElse(DefaultActions.toSet)

override def statusDescription(input: ScenarioStatusWithScenarioContext): String = statusDescription(input.status)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object SimpleStateStatus {
}

// Represents general problem.
final case class ProblemStateStatus(description: String, allowedActions: List[ScenarioActionName] = defaultActions)
final case class ProblemStateStatus(description: String, allowedActions: Set[ScenarioActionName] = defaultActions)
extends StateStatus {
override def name: StatusName = ProblemStateStatus.name
}
Expand All @@ -32,15 +32,15 @@ object SimpleStateStatus {

val icon: URI = URI.create("/assets/states/error.svg")
val defaultDescription = "There are some problems with scenario."
val defaultActions: List[ScenarioActionName] =
List(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
val defaultActions: Set[ScenarioActionName] =
Set(ScenarioActionName.Deploy, ScenarioActionName.Cancel)

// Problem factory methods

val Failed: ProblemStateStatus = ProblemStateStatus(defaultDescription)

val ArchivedShouldBeCanceled: ProblemStateStatus =
ProblemStateStatus("Archived scenario should be canceled.", List(ScenarioActionName.Cancel))
ProblemStateStatus("Archived scenario should be canceled.", Set(ScenarioActionName.Cancel))

val FailedToGet: ProblemStateStatus =
ProblemStateStatus(s"Failed to get a state of the scenario.")
Expand Down Expand Up @@ -71,7 +71,7 @@ object SimpleStateStatus {
ProblemStateStatus("Scenario state error - no actions found.")

val MultipleJobsRunning: ProblemStateStatus =
ProblemStateStatus("More than one deployment is running.", List(ScenarioActionName.Cancel))
ProblemStateStatus("More than one deployment is running.", Set(ScenarioActionName.Cancel))

}

Expand All @@ -92,18 +92,21 @@ object SimpleStateStatus {
status
)

val statusActionsPF: PartialFunction[StateStatus, List[ScenarioActionName]] = {
val statusActionsPF: PartialFunction[StateStatus, Set[ScenarioActionName]] = {
case SimpleStateStatus.NotDeployed =>
List(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
case SimpleStateStatus.DuringDeploy => List(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
Set(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
case SimpleStateStatus.DuringDeploy =>
Set(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
case SimpleStateStatus.Running =>
List(ScenarioActionName.Cancel, ScenarioActionName.Pause, ScenarioActionName.Deploy)
Set(ScenarioActionName.Cancel, ScenarioActionName.Pause, ScenarioActionName.Deploy)
case SimpleStateStatus.Canceled =>
List(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
case SimpleStateStatus.Restarting => List(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
Set(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
case SimpleStateStatus.Restarting =>
Set(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
case SimpleStateStatus.Finished =>
List(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
case SimpleStateStatus.DuringCancel => List(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
Set(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
case SimpleStateStatus.DuringCancel =>
Set(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
// When Failed - process is in terminal state in Flink and it doesn't require any cleanup in Flink, but in NK it does
// - that's why Cancel action is available
case SimpleStateStatus.ProblemStateStatus(_, allowedActions) => allowedActions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class OverridingProcessStateDefinitionManagerTest extends AnyFunSuite with Match
)
)

override def statusActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] = Nil
override def statusActions(input: ScenarioStatusWithScenarioContext): Set[ScenarioActionName] = Set.empty
}

test("should combine delegate state definitions with custom overrides") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package pl.touk.nussknacker.engine.api.deployment
import org.scalatest.Inside
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import pl.touk.nussknacker.engine.api.deployment.ProcessStateDefinitionManager.{ScenarioStatusPresentationDetails, ScenarioStatusWithScenarioContext}
import pl.touk.nussknacker.engine.api.deployment.ProcessStateDefinitionManager.{
ScenarioStatusPresentationDetails,
ScenarioStatusWithScenarioContext
}
import pl.touk.nussknacker.engine.api.deployment.simple.{SimpleProcessStateDefinitionManager, SimpleStateStatus}
import pl.touk.nussknacker.engine.api.process.VersionId
import pl.touk.nussknacker.engine.deployment.ExternalDeploymentId
Expand All @@ -22,17 +25,17 @@ class SimpleScenarioStatusDtoSpec extends AnyFunSpec with Matchers with Inside {

it("scenario state should be during deploy") {
val state = createInput(SimpleStateStatus.DuringDeploy)
state.allowedActions shouldBe List(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
state.allowedActions shouldBe Set(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
}

it("scenario state should be running") {
val state = createInput(SimpleStateStatus.Running)
state.allowedActions shouldBe List(ScenarioActionName.Cancel, ScenarioActionName.Pause, ScenarioActionName.Deploy)
state.allowedActions shouldBe Set(ScenarioActionName.Cancel, ScenarioActionName.Pause, ScenarioActionName.Deploy)
}

it("scenario state should be finished") {
val state = createInput(SimpleStateStatus.Finished)
state.allowedActions shouldBe List(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
state.allowedActions shouldBe Set(ScenarioActionName.Deploy, ScenarioActionName.Archive, ScenarioActionName.Rename)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.circe.syntax.EncoderOps
import pl.touk.nussknacker.engine.api.deployment.DataFreshnessPolicy
import pl.touk.nussknacker.engine.api.process.{ProcessName, VersionId}
import pl.touk.nussknacker.engine.util.Implicits._
import pl.touk.nussknacker.restmodel.scenariodetails.ScenarioStatusDto
import pl.touk.nussknacker.ui._
import pl.touk.nussknacker.ui.listener.ProcessChangeEvent._
import pl.touk.nussknacker.ui.listener.{ProcessChangeEvent, ProcessChangeListener, User}
Expand All @@ -21,15 +22,16 @@ import pl.touk.nussknacker.ui.process.ProcessService.{
}
import pl.touk.nussknacker.ui.process.ScenarioWithDetailsConversions._
import pl.touk.nussknacker.ui.process._
import pl.touk.nussknacker.ui.process.deployment.ScenarioStateProvider
import pl.touk.nussknacker.ui.process.deployment.ScenarioStatusProvider
import pl.touk.nussknacker.ui.security.api.LoggedUser
import pl.touk.nussknacker.ui.util._

import scala.concurrent.{ExecutionContext, Future}

class ProcessesResources(
protected val processService: ProcessService,
scenarioStateProvider: ScenarioStateProvider,
scenarioStatusProvider: ScenarioStatusProvider,
scenarioStatusPresenter: ScenarioStatusPresenter,
processToolbarService: ScenarioToolbarService,
val processAuthorizer: AuthorizeProcess,
processChangeListener: ProcessChangeListener
Expand Down Expand Up @@ -212,9 +214,14 @@ class ProcessesResources(
currentlyPresentedVersionIdParameter { currentlyPresentedVersionId =>
complete {
implicit val freshnessPolicy: DataFreshnessPolicy = DataFreshnessPolicy.Fresh
scenarioStateProvider
.getProcessState(processId, currentlyPresentedVersionId)
.map(ToResponseMarshallable(_))
for {
scenarioDetails <- processService
.getLatestProcessWithDetails(processId, GetScenarioWithDetailsOptions.detailsOnly)
.map(_.toEntity)
statusDetails <- scenarioStatusProvider
.getScenarioStatus(processId, currentlyPresentedVersionId)
dto = scenarioStatusPresenter.toDto(statusDetails, scenarioDetails, currentlyPresentedVersionId)
} yield dto
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package pl.touk.nussknacker.ui.api

import pl.touk.nussknacker.engine.api.deployment.ProcessStateDefinitionManager.ScenarioStatusWithScenarioContext
import pl.touk.nussknacker.engine.api.deployment.StatusDetails
import pl.touk.nussknacker.engine.api.process.VersionId
import pl.touk.nussknacker.restmodel.scenariodetails.ScenarioStatusDto
import pl.touk.nussknacker.ui.process.deployment.DeploymentManagerDispatcher
import pl.touk.nussknacker.ui.process.repository.ScenarioWithDetailsEntity
import pl.touk.nussknacker.ui.security.api.LoggedUser

class ScenarioStatusPresenter(dispatcher: DeploymentManagerDispatcher) {

def toDto(
statusDetails: StatusDetails,
processDetails: ScenarioWithDetailsEntity[_],
currentlyPresentedVersionId: Option[VersionId]
)(implicit user: LoggedUser): ScenarioStatusDto = {
val presentation = dispatcher
.deploymentManagerUnsafe(processDetails.processingType)
.processStateDefinitionManager
.statusPresentation(
ScenarioStatusWithScenarioContext(
statusDetails = statusDetails,
latestVersionId = processDetails.processVersionId,
deployedVersionId = processDetails.lastDeployedAction.map(_.processVersionId),
currentlyPresentedVersionId = currentlyPresentedVersionId
)
)
ScenarioStatusDto(
externalDeploymentId = statusDetails.externalDeploymentId,
status = statusDetails.status,
version = statusDetails.version,
visibleActions = presentation.visibleActions,
allowedActions = presentation.allowedActions.toList.sortBy(_.value),
actionTooltips = presentation.actionTooltips,
icon = presentation.icon,
tooltip = presentation.tooltip,
description = presentation.description,
startTime = statusDetails.startTime,
attributes = statusDetails.attributes,
errors = statusDetails.errors,
)
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package pl.touk.nussknacker.ui.process

import cats._
import cats.data.Validated
import cats.implicits.toTraverseOps
import cats.data.Ior.Both
import cats.data.{Validated, ZipList}
import cats.implicits.{catsSyntaxFoldOps, catsSyntaxTuple2Semigroupal, toAlignOps, toTraverseOps}
import cats.syntax.functor._
import com.typesafe.scalalogging.LazyLogging
import db.util.DBIOActionInstances.DB
Expand All @@ -15,14 +16,16 @@ import pl.touk.nussknacker.engine.api.{Comment, ProcessVersion}
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.deployment.EngineSetupName
import pl.touk.nussknacker.engine.marshall.ProcessMarshaller
import pl.touk.nussknacker.engine.util.copySyntax.apply
import pl.touk.nussknacker.restmodel.process._
import pl.touk.nussknacker.restmodel.scenariodetails.{BaseCreateScenarioCommand, ScenarioWithDetails}
import pl.touk.nussknacker.restmodel.scenariodetails.{BaseCreateScenarioCommand, ScenarioStatusDto, ScenarioWithDetails}
import pl.touk.nussknacker.restmodel.validation.ScenarioGraphWithValidationResult
import pl.touk.nussknacker.ui.NuDesignerError
import pl.touk.nussknacker.ui.api.ProcessesResources.ProcessUnmarshallingError
import pl.touk.nussknacker.ui.api.ScenarioStatusPresenter
import pl.touk.nussknacker.ui.process.ProcessService._
import pl.touk.nussknacker.ui.process.ScenarioWithDetailsConversions._
import pl.touk.nussknacker.ui.process.deployment.ScenarioStateProvider
import pl.touk.nussknacker.ui.process.deployment.ScenarioStatusProvider
import pl.touk.nussknacker.ui.process.exception.{ProcessIllegalAction, ProcessValidationError}
import pl.touk.nussknacker.ui.process.label.ScenarioLabel
import pl.touk.nussknacker.ui.process.marshall.CanonicalProcessConverter
Expand Down Expand Up @@ -178,7 +181,8 @@ trait ProcessService {
* Each action includes verification based on actual process state and checking process is fragment / archived.
*/
class DBProcessService(
processStateProvider: ScenarioStateProvider,
scenarioStatusProvider: ScenarioStatusProvider,
scenarioStatusPresenter: ScenarioStatusPresenter,
newProcessPreparers: ProcessingTypeDataProvider[NewProcessPreparer, _],
scenarioParametersServiceProvider: ProcessingTypeDataProvider[_, ScenarioParametersService],
processResolverByProcessingType: ProcessingTypeDataProvider[UIProcessResolver, _],
Expand Down Expand Up @@ -254,7 +258,7 @@ class DBProcessService(
def apply[PS: ScenarioShapeFetchStrategy]: Future[F[ScenarioWithDetailsEntity[PS]]]
}

private def doGetProcessWithDetails[F[_]: Traverse](
private def doGetProcessWithDetails[F[_]: Traverse: Align](
fetchScenario: FetchScenarioFun[F],
options: GetScenarioWithDetailsOptions
)(
Expand All @@ -278,11 +282,24 @@ class DBProcessService(
case skipFieldsOption: SkipAdditionalFields =>
ScenarioWithDetailsConversions.skipAdditionalFields(details, skipFieldsOption)
}
}).flatMap { details =>
}).flatMap { scenarioDetailsTraverse =>
if (options.fetchState)
processStateProvider.enrichDetailsWithProcessState(details)
scenarioStatusProvider.getScenariosStatuses(scenarioDetailsTraverse.map(_.toEntity)).map {
statusesDetailsTraverse =>
scenarioDetailsTraverse.alignWith(statusesDetailsTraverse) {
case Both(scenarioDetails, statusDetailsOpt) =>
scenarioDetails.copy(state =
statusDetailsOpt
.map(scenarioStatusPresenter.toDto(_, scenarioDetails.toEntity, currentlyPresentedVersionId = None))
)
case other =>
throw new IllegalStateException(
s"Traverse with different sizes during scenario status enrichment: $other"
)
}
}
else
Future.successful(details)
Future.successful(scenarioDetailsTraverse)
}
}

Expand Down Expand Up @@ -538,15 +555,15 @@ class DBProcessService(
callback: => Future[T]
)(implicit user: LoggedUser): Future[T] = {
implicit val freshnessPolicy: DataFreshnessPolicy = DataFreshnessPolicy.Fresh
processStateProvider
.getProcessState(process.toEntity)
.flatMap(state => {
if (state.allowedActions.contains(actionToCheck)) {
scenarioStatusProvider
.getAllowedActionsForScenarioStatus(process.toEntity)
.flatMap { statusWithAllowedActions =>
if (statusWithAllowedActions.allowedActions.contains(actionToCheck)) {
callback
} else {
throw ProcessIllegalAction(actionToCheck, process.name, state)
throw ProcessIllegalAction(actionToCheck, process.name, statusWithAllowedActions)
}
})
}
}

private def doArchive(process: ScenarioWithDetails)(implicit user: LoggedUser): Future[Unit] =
Expand Down
Loading

0 comments on commit a6fc258

Please sign in to comment.