Skip to content

Commit

Permalink
[NU-1979] Scenario statuses refactor: scenario status and deployment …
Browse files Browse the repository at this point in the history
…statuses are decoupled (#7566)
  • Loading branch information
arkadius authored Feb 25, 2025
1 parent 16eaf0d commit 0ba764d
Show file tree
Hide file tree
Showing 132 changed files with 3,126 additions and 3,101 deletions.
22 changes: 0 additions & 22 deletions designer/client/src/components/Process/ProcessErrors.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions designer/client/src/components/Process/ProcessStateIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ProcessStateType, Scenario } from "./types";
import ProcessStateUtils from "./ProcessStateUtils";
import UrlIcon from "../UrlIcon";
import { Box, Divider, Popover, styled, Typography } from "@mui/material";
import { Errors } from "./ProcessErrors";

const StyledUrlIcon = styled(UrlIcon)(({ theme }) => ({
width: theme.spacing(2.5),
Expand Down Expand Up @@ -44,7 +43,6 @@ function ProcessStateIcon({ scenario, processState }: Props) {
<Typography variant="body2" style={{ whiteSpace: "pre-wrap" }}>
{tooltip}
</Typography>
<Errors state={processState} />
</Box>
</Popover>
</>
Expand Down
7 changes: 1 addition & 6 deletions designer/client/src/components/Process/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable i18next/no-literal-string */
import { UnknownRecord, Instant } from "../../types/common";
import { Instant } from "../../types/common";
import { ScenarioGraph, ValidationResult } from "../../types";
import { ProcessingMode } from "../../http/HttpService";

Expand Down Expand Up @@ -48,17 +48,12 @@ export type ProcessName = Scenario["name"];

export type ProcessStateType = {
status: StatusType;
externalDeploymentId?: string;
visibleActions: Array<ActionName>;
allowedActions: Array<ActionName>;
actionTooltips: Record<ActionName, string>;
icon: string;
tooltip: string;
description: string;
startTime?: Date;
attributes?: UnknownRecord;
errors?: Array<string>;
version?: number | null;
};

export type StatusType = {
Expand Down
5 changes: 0 additions & 5 deletions designer/client/src/reducers/graph/utils.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,20 +162,15 @@ export const state: GraphState = {
],
},
state: {
externalDeploymentId: null,
status: {
name: "NOT_DEPLOYED",
},
version: null,
visibleActions: ["DEPLOY", "ARCHIVE", "RENAME"],
allowedActions: ["DEPLOY", "ARCHIVE", "RENAME"],
actionTooltips: {},
icon: "/assets/states/not-deployed.svg",
tooltip: "The scenario is not deployed.",
description: "The scenario is not deployed.",
startTime: null,
attributes: null,
errors: [],
},
validationResult: {
errors: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,101 +1,31 @@
package pl.touk.nussknacker.engine.api.deployment

import com.typesafe.config.Config
import pl.touk.nussknacker.engine.api.deployment.inconsistency.InconsistentStateDetector
import pl.touk.nussknacker.engine.api.deployment.scheduler.services._
import pl.touk.nussknacker.engine.api.process.{ProcessIdWithName, ProcessName, VersionId}
import pl.touk.nussknacker.engine.api.process.{ProcessIdWithName, ProcessName}
import pl.touk.nussknacker.engine.newdeployment
import pl.touk.nussknacker.engine.util.WithDataFreshnessStatusUtils.WithDataFreshnessStatusOps

import java.time.Instant
import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.Future

trait DeploymentManagerInconsistentStateHandlerMixIn {
self: DeploymentManager =>

final override def resolve(
idWithName: ProcessIdWithName,
statusDetails: List[StatusDetails],
lastStateAction: Option[ProcessAction],
latestVersionId: VersionId,
deployedVersionId: Option[VersionId],
currentlyPresentedVersionId: Option[VersionId],
): Future[ProcessState] = {
val engineStateResolvedWithLastAction = flattenStatus(lastStateAction, statusDetails)
Future.successful(
processStateDefinitionManager.processState(
engineStateResolvedWithLastAction,
latestVersionId,
deployedVersionId,
currentlyPresentedVersionId
)
)
}

// This method is protected to make possible to override it with own logic handling different edge cases like
// other state on engine than based on lastStateAction
protected def flattenStatus(
lastStateAction: Option[ProcessAction],
statusDetails: List[StatusDetails]
): StatusDetails = {
InconsistentStateDetector.resolve(statusDetails, lastStateAction)
}

}

trait DeploymentManager extends AutoCloseable {

def deploymentSynchronisationSupport: DeploymentSynchronisationSupport

def stateQueryForAllScenariosSupport: StateQueryForAllScenariosSupport
def deploymentsStatusesQueryForAllScenariosSupport: DeploymentsStatusesQueryForAllScenariosSupport

def schedulingSupport: SchedulingSupport

def processCommand[Result](command: DMScenarioCommand[Result]): Future[Result]

final def getProcessState(
idWithName: ProcessIdWithName,
lastStateAction: Option[ProcessAction],
latestVersionId: VersionId,
deployedVersionId: Option[VersionId],
currentlyPresentedVersionId: Option[VersionId],
)(
implicit freshnessPolicy: DataFreshnessPolicy
): Future[WithDataFreshnessStatus[ProcessState]] = {
for {
statusDetailsWithFreshness <- getProcessStates(idWithName.name)
stateWithFreshness <- resolve(
idWithName,
statusDetailsWithFreshness.value,
lastStateAction,
latestVersionId,
deployedVersionId,
currentlyPresentedVersionId,
).map(statusDetailsWithFreshness.withValue)
} yield stateWithFreshness
}

/**
* We provide a special wrapper called WithDataFreshnessStatus to ensure that fetched data is restored
* from the cache or not. If you use any kind of cache in your DM implementation please wrap result data
* with WithDataFreshnessStatus.cached(data) in opposite situation use WithDataFreshnessStatus.fresh(data)
*/
def getProcessStates(name: ProcessName)(
def getScenarioDeploymentsStatuses(scenarioName: ProcessName)(
implicit freshnessPolicy: DataFreshnessPolicy
): Future[WithDataFreshnessStatus[List[StatusDetails]]]

/**
* Resolves possible inconsistency with lastAction and formats status using `ProcessStateDefinitionManager`
*/
def resolve(
idWithName: ProcessIdWithName,
statusDetails: List[StatusDetails],
lastStateAction: Option[ProcessAction],
latestVersionId: VersionId,
deployedVersionId: Option[VersionId],
currentlyPresentedVersionId: Option[VersionId],
): Future[ProcessState]
): Future[WithDataFreshnessStatus[List[DeploymentStatusDetails]]]

def processStateDefinitionManager: ProcessStateDefinitionManager

Expand All @@ -113,17 +43,17 @@ trait ManagerSpecificScenarioActivitiesStoredByManager { self: DeploymentManager

}

sealed trait StateQueryForAllScenariosSupport
sealed trait DeploymentsStatusesQueryForAllScenariosSupport

trait StateQueryForAllScenariosSupported extends StateQueryForAllScenariosSupport {
trait DeploymentsStatusesQueryForAllScenariosSupported extends DeploymentsStatusesQueryForAllScenariosSupport {

def getAllProcessesStates()(
def getAllScenariosDeploymentsStatuses()(
implicit freshnessPolicy: DataFreshnessPolicy
): Future[WithDataFreshnessStatus[Map[ProcessName, List[StatusDetails]]]]
): Future[WithDataFreshnessStatus[Map[ProcessName, List[DeploymentStatusDetails]]]]

}

case object NoStateQueryForAllScenariosSupport extends StateQueryForAllScenariosSupport
case object NoDeploymentsStatusesQueryForAllScenariosSupport extends DeploymentsStatusesQueryForAllScenariosSupport

sealed trait DeploymentSynchronisationSupport

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pl.touk.nussknacker.engine.api.deployment

import pl.touk.nussknacker.engine.api.process.VersionId
import pl.touk.nussknacker.engine.deployment.DeploymentId

// TODO replace by simple tuple DeploymentId -> DeploymentStatus after fixing TODOs
case class DeploymentStatusDetails(
status: StateStatus,
// deploymentId is optional because some deployment managers (k8s) don't support it
deploymentId: Option[DeploymentId],
// version might be unavailable in some failing cases. It is used during checking if deployed version is the same
// as expected by user - see InconsistentStateDetector.
// TODO it should be an attribute of "following deploy" StateStatuses: DuringDeploy, Running and Finished
version: Option[VersionId],
) {

def deploymentIdUnsafe: DeploymentId =
deploymentId.getOrElse(throw new IllegalStateException(s"deploymentId is missing"))

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package pl.touk.nussknacker.engine.api.deployment

import pl.touk.nussknacker.engine.api.deployment.ProcessStateDefinitionManager.ProcessStatus
import pl.touk.nussknacker.engine.api.deployment.ProcessStateDefinitionManager.ScenarioStatusWithScenarioContext
import pl.touk.nussknacker.engine.api.deployment.StateStatus.StatusName

import java.net.URI
Expand All @@ -21,32 +21,42 @@ import java.net.URI
*/
class OverridingProcessStateDefinitionManager(
delegate: ProcessStateDefinitionManager,
statusActionsPF: PartialFunction[ProcessStatus, List[ScenarioActionName]] = PartialFunction.empty,
statusActionsPF: PartialFunction[ScenarioStatusWithScenarioContext, Set[ScenarioActionName]] =
PartialFunction.empty,
statusIconsPF: PartialFunction[StateStatus, URI] = PartialFunction.empty,
statusTooltipsPF: PartialFunction[StateStatus, String] = PartialFunction.empty,
statusDescriptionsPF: PartialFunction[StateStatus, String] = PartialFunction.empty,
customStateDefinitions: Map[StatusName, StateDefinitionDetails] = Map.empty,
customVisibleActions: Option[List[ScenarioActionName]] = None,
customActionTooltips: Option[ProcessStatus => Map[ScenarioActionName, String]] = None,
customActionTooltips: Option[ScenarioStatusWithScenarioContext => Map[ScenarioActionName, String]] = None,
) extends ProcessStateDefinitionManager {

override def visibleActions: List[ScenarioActionName] =
customVisibleActions.getOrElse(delegate.visibleActions)
override def visibleActions(input: ScenarioStatusWithScenarioContext): List[ScenarioActionName] =
customVisibleActions.getOrElse(delegate.visibleActions(input))

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

override def actionTooltips(processStatus: ProcessStatus): Map[ScenarioActionName, String] =
customActionTooltips.map(_(processStatus)).getOrElse(delegate.actionTooltips(processStatus))
override def actionTooltips(input: ScenarioStatusWithScenarioContext): Map[ScenarioActionName, String] =
customActionTooltips.map(_(input)).getOrElse(delegate.actionTooltips(input))

override def statusIcon(stateStatus: StateStatus): URI =
statusIconsPF.orElse(stateDefinitionsPF(_.icon)).applyOrElse(stateStatus, delegate.statusIcon)
override def statusIcon(input: ScenarioStatusWithScenarioContext): URI =
statusIconsPF
.orElse(stateDefinitionsPF(_.icon))
.lift(input.scenarioStatus)
.getOrElse(delegate.statusIcon(input))

override def statusTooltip(stateStatus: StateStatus): String =
statusTooltipsPF.orElse(stateDefinitionsPF(_.tooltip)).applyOrElse(stateStatus, delegate.statusTooltip)
override def statusTooltip(input: ScenarioStatusWithScenarioContext): String =
statusTooltipsPF
.orElse(stateDefinitionsPF(_.tooltip))
.lift(input.scenarioStatus)
.getOrElse(delegate.statusTooltip(input))

override def statusDescription(stateStatus: StateStatus): String =
statusDescriptionsPF.orElse(stateDefinitionsPF(_.description)).applyOrElse(stateStatus, delegate.statusDescription)
override def statusDescription(input: ScenarioStatusWithScenarioContext): String =
statusDescriptionsPF
.orElse(stateDefinitionsPF(_.description))
.lift(input.scenarioStatus)
.getOrElse(delegate.statusDescription(input))

override def stateDefinitions: Map[StatusName, StateDefinitionDetails] =
delegate.stateDefinitions ++ customStateDefinitions
Expand Down
Loading

0 comments on commit 0ba764d

Please sign in to comment.