diff --git a/designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivityManager.scala b/designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivityManager.scala index 11a23a86823..206b424e18d 100644 --- a/designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivityManager.scala +++ b/designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivityManager.scala @@ -1,41 +1,19 @@ package pl.touk.nussknacker.engine.api.deployment -import pl.touk.nussknacker.engine.api.deployment.ScenarioActivityManager.ModificationResult - import scala.concurrent.Future trait ScenarioActivityManager { def saveActivity( - scenarioActivity: ScenarioActivity + activity: DeploymentRelatedActivity ): Future[Unit] - def modifyActivity( - scenarioActivityId: ScenarioActivityId, - modify: ScenarioActivity => ScenarioActivity, - ): Future[ModificationResult] - -} - -object ScenarioActivityManager { - sealed trait ModificationResult - - object ModificationResult { - case object Success extends ModificationResult - case object Failure extends ModificationResult - } - } object NoOpScenarioActivityManager extends ScenarioActivityManager { def saveActivity( - scenarioActivity: ScenarioActivity + activity: DeploymentRelatedActivity ): Future[Unit] = Future.unit - def modifyActivity( - scenarioActivityId: ScenarioActivityId, - modify: ScenarioActivity => ScenarioActivity, - ): Future[ModificationResult] = Future.successful(ModificationResult.Success) - } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpService.scala index a5160056ca5..19dcb5f6260 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpService.scala @@ -6,12 +6,7 @@ import pl.touk.nussknacker.engine.api.deployment.ScenarioActivityHandling.{ AllScenarioActivitiesStoredByNussknacker, ManagerSpecificScenarioActivitiesStoredByManager } -import pl.touk.nussknacker.engine.api.deployment.{ - ScenarioActivity, - ScenarioActivityId, - ScenarioAttachment, - ScenarioComment -} +import pl.touk.nussknacker.engine.api.deployment.{ScenarioActivity, _} import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessIdWithName, ProcessName} import pl.touk.nussknacker.security.Permission import pl.touk.nussknacker.security.Permission.Permission @@ -233,9 +228,22 @@ class ScenarioActivityApiHttpService( case None => Future.successful(List.empty) } - combinedActivities = (generalActivities ++ deploymentManagerSpecificActivities).map(toDto) - sortedCombinedActivities = combinedActivities.toList.sortBy(_.date) - } yield sortedCombinedActivities + combinedActivities = generalActivities ++ deploymentManagerSpecificActivities + // The API endpoint returning scenario activities does not yet have support for filtering. We made a decision to: + // - for activities not related to deployments: always display them on FE + // - for activities related to batch deployments: always display them on FE + // - for activities related to non-batch deployments: display on FE only those, that represent successful operations + combinedSuccessfulActivities = combinedActivities.filter { + case _: BatchDeploymentRelatedActivity => true + case activity: DeploymentRelatedActivity => + activity.result match { + case _: DeploymentResult.Success => true + case _: DeploymentResult.Failure => false + } + case _ => true + } + sortedResult = combinedSuccessfulActivities.map(toDto).toList.sortBy(_.date) + } yield sortedResult } private def toDto(scenarioComment: ScenarioComment): Dtos.ScenarioActivityComment = { @@ -297,7 +305,7 @@ class ScenarioActivityApiHttpService( date = date, scenarioVersionId = scenarioVersionId.map(_.value) ) - case ScenarioActivity.ScenarioDeployed(_, scenarioActivityId, user, date, scenarioVersionId, comment) => + case ScenarioActivity.ScenarioDeployed(_, scenarioActivityId, user, date, scenarioVersionId, comment, _) => Dtos.ScenarioActivity.forScenarioDeployed( id = scenarioActivityId.value, user = user.name.value, @@ -305,7 +313,7 @@ class ScenarioActivityApiHttpService( scenarioVersionId = scenarioVersionId.map(_.value), comment = toDto(comment), ) - case ScenarioActivity.ScenarioPaused(_, scenarioActivityId, user, date, scenarioVersionId, comment) => + case ScenarioActivity.ScenarioPaused(_, scenarioActivityId, user, date, scenarioVersionId, comment, _) => Dtos.ScenarioActivity.forScenarioPaused( id = scenarioActivityId.value, user = user.name.value, @@ -313,7 +321,7 @@ class ScenarioActivityApiHttpService( scenarioVersionId = scenarioVersionId.map(_.value), comment = toDto(comment), ) - case ScenarioActivity.ScenarioCanceled(_, scenarioActivityId, user, date, scenarioVersionId, comment) => + case ScenarioActivity.ScenarioCanceled(_, scenarioActivityId, user, date, scenarioVersionId, comment, _) => Dtos.ScenarioActivity.forScenarioCanceled( id = scenarioActivityId.value, user = user.name.value, @@ -321,12 +329,13 @@ class ScenarioActivityApiHttpService( scenarioVersionId = scenarioVersionId.map(_.value), comment = toDto(comment), ) - case ScenarioActivity.ScenarioModified(_, scenarioActivityId, user, date, scenarioVersionId, comment) => + case ScenarioActivity.ScenarioModified(_, scenarioActivityId, user, date, oldVersionId, newVersionId, comment) => Dtos.ScenarioActivity.forScenarioModified( id = scenarioActivityId.value, user = user.name.value, date = date, - scenarioVersionId = scenarioVersionId.map(_.value), + previousScenarioVersionId = oldVersionId.map(_.value), + scenarioVersionId = newVersionId.map(_.value), comment = toDto(comment), ) case ScenarioActivity.ScenarioNameChanged(_, id, user, date, version, oldName, newName) => @@ -406,9 +415,7 @@ class ScenarioActivityApiHttpService( date, scenarioVersionId, comment, - dateFinished, - status, - errorMessage, + result, ) => Dtos.ScenarioActivity.forPerformedSingleExecution( id = scenarioActivityId.value, @@ -416,9 +423,11 @@ class ScenarioActivityApiHttpService( date = date, scenarioVersionId = scenarioVersionId.map(_.value), comment = toDto(comment), - dateFinished = dateFinished, - status = status, - errorMessage = errorMessage, + dateFinished = result.dateFinished, + errorMessage = result match { + case DeploymentResult.Success(_) => None + case DeploymentResult.Failure(_, errorMessage) => errorMessage + }, ) case ScenarioActivity.PerformedScheduledExecution( _, @@ -426,9 +435,9 @@ class ScenarioActivityApiHttpService( user, date, scenarioVersionId, + scheduledExecutionStatus, dateFinished, scheduleName, - status, createdAt, nextRetryAt, retriesLeft, @@ -440,7 +449,7 @@ class ScenarioActivityApiHttpService( scenarioVersionId = scenarioVersionId.map(_.value), dateFinished = dateFinished, scheduleName = scheduleName, - status = status, + scheduledExecutionStatus = scheduledExecutionStatus, createdAt = createdAt, retriesLeft = retriesLeft, nextRetryAt = nextRetryAt, @@ -452,7 +461,6 @@ class ScenarioActivityApiHttpService( date, scenarioVersionId, changes, - errorMessage, ) => Dtos.ScenarioActivity.forAutomaticUpdate( id = scenarioActivityId.value, @@ -460,9 +468,17 @@ class ScenarioActivityApiHttpService( date = date, scenarioVersionId = scenarioVersionId.map(_.value), changes = changes, - errorMessage = errorMessage, ) - case ScenarioActivity.CustomAction(_, scenarioActivityId, user, date, scenarioVersionId, actionName, comment) => + case ScenarioActivity.CustomAction( + _, + scenarioActivityId, + user, + date, + scenarioVersionId, + actionName, + comment, + result, + ) => Dtos.ScenarioActivity.forCustomAction( id = scenarioActivityId.value, user = user.name.value, @@ -471,6 +487,10 @@ class ScenarioActivityApiHttpService( actionName = actionName, comment = toDto(comment), customIcon = None, + errorMessage = result match { + case DeploymentResult.Success(_) => None + case DeploymentResult.Failure(_, errorMessage) => errorMessage + }, ) } } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Dtos.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Dtos.scala index 2e1c6165b8e..7f095c86675 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Dtos.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Dtos.scala @@ -143,7 +143,7 @@ object Dtos { } case object ScenarioModified extends ScenarioActivityType { - override def displayableName: String = "New version saved" + override def displayableName: String = "Scenario modified" override def icon: String = "/assets/activities/scenarioModified.svg" override def supportedActions: List[String] = commentRelatedActions ::: "compare" :: Nil } @@ -442,6 +442,7 @@ object Dtos { id: UUID, user: String, date: Instant, + previousScenarioVersionId: Option[Long], scenarioVersionId: Option[Long], comment: ScenarioActivityComment, ): ScenarioActivity = ScenarioActivity( @@ -453,9 +454,19 @@ object Dtos { comment = Some(comment), attachment = None, additionalFields = List.empty, - overrideDisplayableName = scenarioVersionId.map(version => s"Version $version saved"), + overrideDisplayableName = updatedVersionId(previousScenarioVersionId, scenarioVersionId).map(updatedVersion => + s"Version $updatedVersion saved" + ) ) + private def updatedVersionId(oldVersionIdOpt: Option[Long], newVersionIdOpt: Option[Long]) = { + for { + newVersionId <- newVersionIdOpt + oldVersionIdOrZero = oldVersionIdOpt.getOrElse(0L) + updatedVersionId <- if (newVersionId > oldVersionIdOrZero) Some(newVersionId) else None + } yield updatedVersionId + } + def forScenarioNameChanged( id: UUID, user: String, @@ -586,8 +597,7 @@ object Dtos { date: Instant, scenarioVersionId: Option[Long], comment: ScenarioActivityComment, - dateFinished: Option[Instant], - status: Option[String], + dateFinished: Instant, errorMessage: Option[String], ): ScenarioActivity = ScenarioActivity( id = id, @@ -598,8 +608,7 @@ object Dtos { comment = Some(comment), attachment = None, additionalFields = List( - status.map(AdditionalField("status", _)), - dateFinished.map(date => AdditionalField("dateFinished", date.toString)), + Some(AdditionalField("dateFinished", dateFinished.toString)), errorMessage.map(e => AdditionalField("errorMessage", e)), ).flatten ) @@ -609,16 +618,14 @@ object Dtos { user: String, date: Instant, scenarioVersionId: Option[Long], - dateFinished: Option[Instant], + dateFinished: Instant, scheduleName: String, - status: ScheduledExecutionStatus, + scheduledExecutionStatus: ScheduledExecutionStatus, createdAt: Instant, nextRetryAt: Option[Instant], retriesLeft: Option[Int], ): ScenarioActivity = { - val humanReadableStatus = status match { - case ScheduledExecutionStatus.Scheduled => "Scheduled" - case ScheduledExecutionStatus.Deployed => "Deployed" + val humanReadableStatus = scheduledExecutionStatus match { case ScheduledExecutionStatus.Finished => "Execution finished" case ScheduledExecutionStatus.Failed => "Execution failed" case ScheduledExecutionStatus.DeploymentWillBeRetried => "Deployment will be retried" @@ -635,7 +642,7 @@ object Dtos { additionalFields = List( Some(AdditionalField("status", humanReadableStatus)), Some(AdditionalField("createdAt", createdAt.toString)), - dateFinished.map(date => AdditionalField("dateFinished", date.toString)), + Some(AdditionalField("dateFinished", dateFinished.toString)), Some(AdditionalField("scheduleName", scheduleName)), Some(AdditionalField("retriesLeft", retriesLeft.toString)), nextRetryAt.map(nra => AdditionalField("nextRetryAt", nra.toString)), @@ -651,7 +658,6 @@ object Dtos { date: Instant, scenarioVersionId: Option[Long], changes: String, - errorMessage: Option[String], ): ScenarioActivity = ScenarioActivity( id = id, `type` = ScenarioActivityType.AutomaticUpdate, @@ -661,9 +667,8 @@ object Dtos { comment = None, attachment = None, additionalFields = List( - Some(AdditionalField("changes", changes)), - errorMessage.map(e => AdditionalField("errorMessage", e)), - ).flatten + AdditionalField("changes", changes), + ), ) def forCustomAction( @@ -674,6 +679,7 @@ object Dtos { comment: ScenarioActivityComment, actionName: String, customIcon: Option[String], + errorMessage: Option[String], ): ScenarioActivity = ScenarioActivity( id = id, `type` = ScenarioActivityType.CustomAction, @@ -683,8 +689,9 @@ object Dtos { comment = Some(comment), attachment = None, additionalFields = List( - AdditionalField("actionName", actionName), - ), + Some(AdditionalField("actionName", actionName)), + errorMessage.map(e => AdditionalField("errorMessage", e)), + ).flatten, overrideIcon = customIcon, ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Examples.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Examples.scala index 1fd356cf195..39c3b375620 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Examples.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/scenarioActivity/Examples.scala @@ -87,7 +87,8 @@ object Examples { id = UUID.fromString("07b04d45-c7c0-4980-a3bc-3c7f66410f68"), user = "some user", date = Instant.parse("2024-01-17T14:21:17Z"), - scenarioVersionId = Some(1), + previousScenarioVersionId = Some(1), + scenarioVersionId = Some(2), comment = ScenarioActivityComment( content = ScenarioActivityCommentContent.Available("Added new processing step"), lastModifiedBy = "some user", @@ -183,8 +184,7 @@ object Examples { lastModifiedBy = "some user", lastModifiedAt = Instant.parse("2024-01-17T14:21:17Z") ), - dateFinished = Some(Instant.parse("2024-01-17T14:21:17Z")), - status = Some("IN_PROGRESS"), + dateFinished = Instant.parse("2024-01-17T14:21:17Z"), errorMessage = Some("Execution error occurred"), ), ScenarioActivity.forPerformedSingleExecution( @@ -197,8 +197,7 @@ object Examples { lastModifiedBy = "some user", lastModifiedAt = Instant.parse("2024-01-17T14:21:17Z") ), - dateFinished = Some(Instant.parse("2024-01-17T14:21:17Z")), - status = Some("FAILED"), + dateFinished = Instant.parse("2024-01-17T14:21:17Z"), errorMessage = None, ), ScenarioActivity.forPerformedScheduledExecution( @@ -206,9 +205,9 @@ object Examples { user = "some user", date = Instant.parse("2024-01-17T14:21:17Z"), scenarioVersionId = Some(1), - dateFinished = Some(Instant.parse("2024-01-17T14:21:17Z")), + dateFinished = Instant.parse("2024-01-17T14:21:17Z"), scheduleName = "main-schedule", - status = ScheduledExecutionStatus.Finished, + scheduledExecutionStatus = ScheduledExecutionStatus.Finished, createdAt = Instant.parse("2024-01-17T13:21:17Z"), retriesLeft = None, nextRetryAt = None, @@ -218,9 +217,9 @@ object Examples { user = "some user", date = Instant.parse("2024-01-17T14:21:17Z"), scenarioVersionId = Some(1), - dateFinished = Some(Instant.parse("2024-01-17T14:21:17Z")), + dateFinished = Instant.parse("2024-01-17T14:21:17Z"), scheduleName = "main-schedule", - status = ScheduledExecutionStatus.DeploymentWillBeRetried, + scheduledExecutionStatus = ScheduledExecutionStatus.DeploymentWillBeRetried, createdAt = Instant.parse("2024-01-17T13:21:17Z"), retriesLeft = Some(1), nextRetryAt = Some(Instant.parse("2024-01-17T15:21:17Z")), @@ -231,7 +230,6 @@ object Examples { date = Instant.parse("2024-01-17T14:21:17Z"), scenarioVersionId = Some(1), changes = "JIRA-12345, JIRA-32146", - errorMessage = None, ), ScenarioActivity.forCustomAction( id = UUID.fromString("33509d37-7657-4229-940f-b5736c82fb13"), @@ -244,7 +242,8 @@ object Examples { lastModifiedAt = Instant.parse("2024-01-17T14:21:17Z") ), actionName = "special_execution", - customIcon = Some("/assets/states/deploy-running-animated.svg") + customIcon = Some("/assets/states/deploy-running-animated.svg"), + errorMessage = None, ) ), ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentService.scala index 9da52a5d985..3dc8509c41a 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentService.scala @@ -348,6 +348,7 @@ class DeploymentService( actionName, ctx.versionOnWhichActionIsDone, performedAt, + deploymentComment, exception.getMessage, ctx.buildInfoProcessingType ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/RepositoryBasedScenarioActivityManager.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/RepositoryBasedScenarioActivityManager.scala index 3abf514edb0..18d633b3e9e 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/RepositoryBasedScenarioActivityManager.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/RepositoryBasedScenarioActivityManager.scala @@ -1,10 +1,12 @@ package pl.touk.nussknacker.ui.process.deployment -import pl.touk.nussknacker.engine.api.deployment.ScenarioActivityManager.ModificationResult -import pl.touk.nussknacker.engine.api.deployment.{ScenarioActivity, ScenarioActivityId, ScenarioActivityManager} +import pl.touk.nussknacker.engine.api.deployment.{ + DeploymentRelatedActivity, + ScenarioActivityId, + ScenarioActivityManager +} import pl.touk.nussknacker.ui.process.repository.DBIOActionRunner import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepository -import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepository.ModifyActivityError import scala.concurrent.{ExecutionContext, Future} @@ -15,23 +17,11 @@ class RepositoryBasedScenarioActivityManager( extends ScenarioActivityManager { override def saveActivity( - scenarioActivity: ScenarioActivity + activity: DeploymentRelatedActivity ): Future[Unit] = { dbioActionRunner - .run(repository.addActivity(scenarioActivity)) + .run(repository.addActivity(activity)) .map((_: ScenarioActivityId) => ()) } - override def modifyActivity( - scenarioActivityId: ScenarioActivityId, - modification: ScenarioActivity => ScenarioActivity - ): Future[ModificationResult] = dbioActionRunner.run( - repository - .modifyActivity(scenarioActivityId, modification) - .map { - case Right(_: Unit) => ModificationResult.Success - case Left(_: ModifyActivityError) => ModificationResult.Failure - } - ) - } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/newactivity/ActivityService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/newactivity/ActivityService.scala index 22292bcf222..87631d4bcca 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/newactivity/ActivityService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/newactivity/ActivityService.scala @@ -34,7 +34,7 @@ class ActivityService( (for { validatedCommentOpt <- validateDeploymentCommentWhenPassed(comment) keys <- runDeployment(command) - _ <- saveCommentWhenPassed[RunDeploymentError]( + _ <- saveActivityWhenFinished[RunDeploymentError]( validatedCommentOpt, keys.scenarioId, keys.scenarioGraphVersionId, @@ -58,7 +58,7 @@ class ActivityService( EitherT(deploymentService.runDeployment(command)) .leftMap[ActivityError[RunDeploymentError]](UnderlyingServiceError(_)) - private def saveCommentWhenPassed[ErrorType]( + private def saveActivityWhenFinished[ErrorType]( commentOpt: Option[Comment], scenarioId: ProcessId, scenarioGraphVersionId: VersionId, @@ -79,6 +79,7 @@ class ActivityService( case Some(comment) => ScenarioComment.Available(comment.content, UserName(loggedUser.username), now) case None => ScenarioComment.Deleted(UserName(loggedUser.username), now) }, + result = DeploymentResult.Success(clock.instant()), ) ) ) 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 b9678cb7b1f..64a1b592403 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 @@ -202,21 +202,22 @@ class DBProcessRepository( )(implicit loggedUser: LoggedUser): DB[ProcessUpdated] = { editProcess( updateProcessAction, - (processId, versionId, _) => + (processId, oldVersionId, versionId, _) => ScenarioActivity.ScenarioModified( scenarioId = ScenarioId(processId.value), scenarioActivityId = ScenarioActivityId.random, user = loggedUser.scenarioUser, date = Instant.now(), - scenarioVersionId = Some(ScenarioVersionId.from(versionId)), - comment = updateProcessAction.comment match { - case Some(comment) => + previousScenarioVersionId = oldVersionId.map(ScenarioVersionId.from), + scenarioVersionId = versionId.map(ScenarioVersionId.from), + comment = updateProcessAction.comment.map(_.content) match { + case Some(content) if content.nonEmpty => ScenarioComment.Available( - comment = comment.content, + comment = content, lastModifiedByUserName = UserName(loggedUser.username), lastModifiedAt = clock.instant(), ) - case None => + case Some(_) | None => ScenarioComment.Deleted( deletedByUserName = UserName(loggedUser.username), deletedAt = clock.instant(), @@ -231,13 +232,13 @@ class DBProcessRepository( )(implicit loggedUser: LoggedUser): DB[ProcessUpdated] = { editProcess( migrateProcessAction, - (processId, versionId, user) => + (processId, _, versionId, user) => ScenarioActivity.IncomingMigration( scenarioId = ScenarioId(processId.value), scenarioActivityId = ScenarioActivityId.random, user = loggedUser.scenarioUser, date = clock.instant(), - scenarioVersionId = Some(ScenarioVersionId.from(versionId)), + scenarioVersionId = versionId.map(ScenarioVersionId.from), sourceEnvironment = Environment(migrateProcessAction.sourceEnvironment), sourceUser = UserName(user), sourceScenarioVersionId = migrateProcessAction.sourceScenarioVersionId.map(ScenarioVersionId.from), @@ -251,27 +252,30 @@ class DBProcessRepository( )(implicit loggedUser: LoggedUser): DB[ProcessUpdated] = { editProcess( automaticProcessUpdateAction, - (processId, versionId, _) => + (processId, _, versionId, _) => ScenarioActivity.AutomaticUpdate( scenarioId = ScenarioId(processId.value), scenarioActivityId = ScenarioActivityId.random, user = loggedUser.scenarioUser, date = Instant.now(), - scenarioVersionId = Some(ScenarioVersionId.from(versionId)), + scenarioVersionId = versionId.map(ScenarioVersionId.from), changes = automaticProcessUpdateAction.migrationsApplies.map(_.description).mkString(", "), - errorMessage = None, ) ) } def editProcess[ACTION <: ModifyProcessAction]( action: ACTION, - activityCreator: (ProcessId, VersionId, String) => ScenarioActivity, + activityCreator: (ProcessId, Option[VersionId], Option[VersionId], String) => ScenarioActivity, )(implicit loggedUser: LoggedUser): DB[ProcessUpdated] = { val userName = action.forwardedUserName.map(_.name).getOrElse(loggedUser.username) - def addScenarioModifiedActivity(scenarioId: ProcessId, scenarioGraphVersionId: VersionId) = { - run(scenarioActivityRepository.addActivity(activityCreator(scenarioId, scenarioGraphVersionId, userName))) + def addScenarioModifiedActivity( + scenarioId: ProcessId, + oldVersionId: Option[VersionId], + newVersionId: Option[VersionId] + ) = { + run(scenarioActivityRepository.addActivity(activityCreator(scenarioId, oldVersionId, newVersionId, userName))) } for { @@ -283,10 +287,10 @@ class DBProcessRepository( ) _ <- updateProcessRes match { // Comment should be added via ProcessService not to mix this repository responsibility. - case updateProcessRes @ ProcessUpdated(processId, _, Some(newVersion)) => - addScenarioModifiedActivity(processId, newVersion).map(_ => updateProcessRes) - case updateProcessRes @ ProcessUpdated(processId, Some(oldVersion), _) => - addScenarioModifiedActivity(processId, oldVersion).map(_ => updateProcessRes) + case updateProcessRes @ ProcessUpdated(processId, oldVersion, Some(newVersion)) => + addScenarioModifiedActivity(processId, oldVersion, Some(newVersion)).map(_ => updateProcessRes) + case updateProcessRes @ ProcessUpdated(processId, Some(theSameVersion), _) => + addScenarioModifiedActivity(processId, Some(theSameVersion), Some(theSameVersion)).map(_ => updateProcessRes) case _ => dbMonad.unit } _ <- scenarioLabelsRepository.overwriteLabels( diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepository.scala index c5da583ad46..cf6430044cf 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepository.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepository.scala @@ -132,6 +132,7 @@ class DbScenarioActionRepository( actionName: ScenarioActionName, processVersion: Option[VersionId], performedAt: Instant, + comment: Option[Comment], failureMessage: String, buildInfoProcessingType: Option[ProcessingType] )(implicit user: LoggedUser): DB[Unit] = { @@ -152,7 +153,7 @@ class DbScenarioActionRepository( performedAt, Some(performedAt), failureMessageOpt, - None, + comment, buildInfoProcessingType ) } @@ -203,12 +204,10 @@ class DbScenarioActionRepository( None, comment, buildInfoProcessingType - ).map(toFinishedProcessAction).map { - case Right(processAction) => - processAction - case Left(error) => - throw new IllegalArgumentException(s"Could not insert ProcessAction as ScenarioActivity [$error") - } + ).map( + toFinishedProcessAction(_) + .getOrElse(throw new IllegalArgumentException(s"Could not insert ProcessAction as ScenarioActivity")) + ) ) } @@ -341,7 +340,7 @@ class DbScenarioActionRepository( .sortBy(_._1.performedAt) .result .map(_.flatMap { case (data, name) => - toFinishedProcessAction(data).map((_, name)).toOption + toFinishedProcessAction(data).map((_, name)) }.toList) ) } @@ -375,7 +374,7 @@ class DbScenarioActionRepository( run( finalQuery.result.map(_.flatMap { case (scenarioId, action) => - toFinishedProcessAction(action).map((ProcessId(scenarioId.value), _)).toOption + toFinishedProcessAction(action).map((ProcessId(scenarioId.value), _)) }.toMap) ) } @@ -392,7 +391,7 @@ class DbScenarioActionRepository( ) .result .headOption - .map(_.flatMap(toFinishedProcessAction(_).toOption)) + .map(_.flatMap(toFinishedProcessAction)) ) override def getFinishedProcessActions( @@ -407,12 +406,14 @@ class DbScenarioActionRepository( .map(actionNames => query.filter { entity => entity.activityType.inSet(activityTypes(actionNames)) }) .getOrElse(query) .result - .map(_.toList.flatMap(toFinishedProcessAction(_).toOption)) + .map(_.toList.flatMap(toFinishedProcessAction)) ) } - private def toFinishedProcessAction(activityEntity: ScenarioActivityEntityData): Either[String, ProcessAction] = { - for { + private def toFinishedProcessAction( + activityEntity: ScenarioActivityEntityData + ): Option[ProcessAction] = actionName(activityEntity.activityType).flatMap { actionName => + (for { processVersionId <- activityEntity.scenarioVersion .map(_.value) .map(VersionId.apply) @@ -420,8 +421,6 @@ class DbScenarioActionRepository( performedAt <- activityEntity.finishedAt .map(_.toInstant) .toRight(s"PerformedAt not available for finished action: $activityEntity") - actionName <- actionName(activityEntity.activityType) - .toRight(s"ActionName not available for finished action: $activityEntity") state <- activityEntity.state .toRight(s"State not available for finished action: $activityEntity") } yield ProcessAction( @@ -437,10 +436,10 @@ class DbScenarioActionRepository( commentId = activityEntity.comment.map(_ => activityEntity.id), comment = activityEntity.comment.map(_.value), buildInfo = activityEntity.buildInfo.flatMap(BuildInfo.parseJson).getOrElse(BuildInfo.empty) - ) - }.left.map { error => - logger.error(s"Could not interpret ScenarioActivity stored in the db as ProcessAction: [$error]") - error + )).left.map { error => + logger.error(s"Could not interpret ScenarioActivity entity [$activityEntity] as ProcessAction: [$error]") + error + }.toOption } private def activityId(actionId: ProcessActionId) = diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/DbScenarioActivityRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/DbScenarioActivityRepository.scala index d7b69e16b79..723adb24acd 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/DbScenarioActivityRepository.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/DbScenarioActivityRepository.scala @@ -4,7 +4,6 @@ import cats.implicits.catsSyntaxEitherId import com.typesafe.scalalogging.LazyLogging import db.util.DBIOActionInstances.DB import pl.touk.nussknacker.engine.api.component.ProcessingMode -import pl.touk.nussknacker.engine.api.deployment.ProcessActionState.ProcessActionState import pl.touk.nussknacker.engine.api.deployment.ScenarioAttachment.{AttachmentFilename, AttachmentId} import pl.touk.nussknacker.engine.api.deployment._ import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId} @@ -25,6 +24,7 @@ import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepo import pl.touk.nussknacker.ui.security.api.LoggedUser import pl.touk.nussknacker.ui.statistics.{AttachmentsTotal, CommentsTotal} import pl.touk.nussknacker.ui.util.LoggedUserUtils.Ops +import pl.touk.nussknacker.ui.util.ScenarioActivityUtils.ScenarioActivityOps import java.sql.Timestamp import java.time.{Clock, Instant} @@ -225,13 +225,18 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C ): DB[Seq[(Long, ScenarioActivity)]] = { scenarioActivityTable .filter(_.scenarioId === scenarioId) + // ScenarioActivity in domain represents a single, immutable event, so we interpret only finished operations as ScenarioActivities + .filter { entity => + entity.state.isEmpty || + entity.state === ProcessActionState.Finished || + entity.state === ProcessActionState.ExecutionFinished + } .sortBy(_.createdAt) .result - .map(_.map(fromEntity)) - .map { - _.flatMap { + .map { entity => + entity.map(fromEntity).flatMap { case Left(error) => - logger.warn(s"Ignoring invalid scenario activity: [$error]") + logger.warn(s"Ignoring invalid scenario activity: [$error] for $entity") None case Right(activity) => Some(activity) @@ -456,35 +461,13 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C attachmentId: Option[Long] = None, comment: Option[String] = None, lastModifiedByUserName: Option[String] = None, - finishedAt: Option[Timestamp] = None, - state: Option[ProcessActionState] = None, - errorMessage: Option[String] = None, buildInfo: Option[String] = None, additionalProperties: AdditionalProperties = AdditionalProperties.empty, ): ScenarioActivityEntityData = { val now = Timestamp.from(clock.instant()) - val activityType = scenarioActivity match { - case _: ScenarioActivity.ScenarioCreated => ScenarioActivityType.ScenarioCreated - case _: ScenarioActivity.ScenarioArchived => ScenarioActivityType.ScenarioArchived - case _: ScenarioActivity.ScenarioUnarchived => ScenarioActivityType.ScenarioUnarchived - case _: ScenarioActivity.ScenarioDeployed => ScenarioActivityType.ScenarioDeployed - case _: ScenarioActivity.ScenarioPaused => ScenarioActivityType.ScenarioPaused - case _: ScenarioActivity.ScenarioCanceled => ScenarioActivityType.ScenarioCanceled - case _: ScenarioActivity.ScenarioModified => ScenarioActivityType.ScenarioModified - case _: ScenarioActivity.ScenarioNameChanged => ScenarioActivityType.ScenarioNameChanged - case _: ScenarioActivity.CommentAdded => ScenarioActivityType.CommentAdded - case _: ScenarioActivity.AttachmentAdded => ScenarioActivityType.AttachmentAdded - case _: ScenarioActivity.ChangedProcessingMode => ScenarioActivityType.ChangedProcessingMode - case _: ScenarioActivity.IncomingMigration => ScenarioActivityType.IncomingMigration - case _: ScenarioActivity.OutgoingMigration => ScenarioActivityType.OutgoingMigration - case _: ScenarioActivity.PerformedSingleExecution => ScenarioActivityType.PerformedSingleExecution - case _: ScenarioActivity.PerformedScheduledExecution => ScenarioActivityType.PerformedScheduledExecution - case _: ScenarioActivity.AutomaticUpdate => ScenarioActivityType.AutomaticUpdate - case activity: ScenarioActivity.CustomAction => ScenarioActivityType.CustomAction(activity.actionName) - } ScenarioActivityEntityData( id = -1, - activityType = activityType, + activityType = scenarioActivity.activityType, scenarioId = ProcessId(scenarioActivity.scenarioId.value), activityId = scenarioActivity.scenarioActivityId, userId = scenarioActivity.user.id.map(_.value), @@ -497,9 +480,16 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C scenarioVersion = scenarioActivity.scenarioVersionId, comment = comment, attachmentId = attachmentId, - finishedAt = finishedAt, - state = state, - errorMessage = errorMessage, + finishedAt = scenarioActivity.dateFinishedOpt.map(Timestamp.from), + state = Some(ProcessActionState.Finished), + errorMessage = scenarioActivity match { + case activity: DeploymentRelatedActivity => + activity.result match { + case DeploymentResult.Success(_) => None + case DeploymentResult.Failure(_, errorMessage) => errorMessage + } + case _ => None + }, buildInfo = buildInfo, additionalProperties = additionalProperties, ) @@ -557,6 +547,11 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C createEntity(scenarioActivity)( comment = comment(activity.comment), lastModifiedByUserName = lastModifiedByUserName(activity.comment), + additionalProperties = AdditionalProperties( + List( + activity.previousScenarioVersionId.map(_.value.toString).map("prevVersionId" -> _), + ).flatten.toMap + ) ) case activity: ScenarioActivity.ScenarioNameChanged => createEntity(scenarioActivity)( @@ -614,17 +609,13 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C ) ) case activity: ScenarioActivity.PerformedSingleExecution => - createEntity(scenarioActivity)( - finishedAt = activity.dateFinished.map(Timestamp.from), - errorMessage = activity.errorMessage, - ) + createEntity(scenarioActivity)() case activity: ScenarioActivity.PerformedScheduledExecution => createEntity(scenarioActivity)( - finishedAt = activity.dateFinished.map(Timestamp.from), additionalProperties = AdditionalProperties( List( Some("scheduleName" -> activity.scheduleName), - Some("status" -> activity.status.entryName), + Some("status" -> activity.scheduledExecutionStatus.entryName), Some("createdAt" -> activity.createdAt.toString), activity.nextRetryAt.map(nra => "nextRetryAt" -> nra.toString), activity.retriesLeft.map(rl => "retriesLeft" -> rl.toString) @@ -633,7 +624,6 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C ) case activity: ScenarioActivity.AutomaticUpdate => createEntity(scenarioActivity)( - errorMessage = activity.errorMessage, additionalProperties = AdditionalProperties( Map( "description" -> activity.changes.mkString(",\n"), @@ -661,6 +651,19 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C ScenarioId(entity.scenarioId.value) } + private def resultFromEntity(entity: ScenarioActivityEntityData): Either[String, DeploymentResult] = { + for { + state <- entity.state.toRight("Missing state field") + dateFinished <- entity.finishedAt.toRight("Missing finishedAt field").map(_.toInstant) + result <- state match { + case ProcessActionState.InProgress => Left("InProgress is not a terminal state") + case ProcessActionState.Finished => Right(DeploymentResult.Success(dateFinished)) + case ProcessActionState.Failed => Right(DeploymentResult.Failure(dateFinished, entity.errorMessage)) + case ProcessActionState.ExecutionFinished => Right(DeploymentResult.Success(dateFinished)) + } + } yield result + } + private def commentFromEntity(entity: ScenarioActivityEntityData): Either[String, ScenarioComment] = { for { lastModifiedByUserName <- entity.lastModifiedByUserName.toRight("Missing lastModifiedByUserName field") @@ -750,56 +753,59 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C .asRight .map((entity.id, _)) case ScenarioActivityType.ScenarioDeployed => - commentFromEntity(entity) - .map { comment => - ScenarioActivity.ScenarioDeployed( - scenarioId = scenarioIdFromEntity(entity), - scenarioActivityId = entity.activityId, - user = userFromEntity(entity), - date = entity.createdAt.toInstant, - scenarioVersionId = entity.scenarioVersion, - comment = comment, - ) - } - .map((entity.id, _)) + (for { + comment <- commentFromEntity(entity) + result <- resultFromEntity(entity) + } yield ScenarioActivity.ScenarioDeployed( + scenarioId = scenarioIdFromEntity(entity), + scenarioActivityId = entity.activityId, + user = userFromEntity(entity), + date = entity.createdAt.toInstant, + scenarioVersionId = entity.scenarioVersion, + comment = comment, + result = result, + )).map((entity.id, _)) case ScenarioActivityType.ScenarioPaused => - commentFromEntity(entity) - .map { comment => - ScenarioActivity.ScenarioPaused( - scenarioId = scenarioIdFromEntity(entity), - scenarioActivityId = entity.activityId, - user = userFromEntity(entity), - date = entity.createdAt.toInstant, - scenarioVersionId = entity.scenarioVersion, - comment = comment, - ) - } - .map((entity.id, _)) + (for { + comment <- commentFromEntity(entity) + result <- resultFromEntity(entity) + } yield ScenarioActivity.ScenarioPaused( + scenarioId = scenarioIdFromEntity(entity), + scenarioActivityId = entity.activityId, + user = userFromEntity(entity), + date = entity.createdAt.toInstant, + scenarioVersionId = entity.scenarioVersion, + comment = comment, + result = result, + )).map((entity.id, _)) case ScenarioActivityType.ScenarioCanceled => - commentFromEntity(entity) - .map { comment => - ScenarioActivity.ScenarioCanceled( - scenarioId = scenarioIdFromEntity(entity), - scenarioActivityId = entity.activityId, - user = userFromEntity(entity), - date = entity.createdAt.toInstant, - scenarioVersionId = entity.scenarioVersion, - comment = comment, - ) - } - .map((entity.id, _)) + (for { + comment <- commentFromEntity(entity) + result <- resultFromEntity(entity) + } yield ScenarioActivity.ScenarioCanceled( + scenarioId = scenarioIdFromEntity(entity), + scenarioActivityId = entity.activityId, + user = userFromEntity(entity), + date = entity.createdAt.toInstant, + scenarioVersionId = entity.scenarioVersion, + comment = comment, + result = result, + )).map((entity.id, _)) case ScenarioActivityType.ScenarioModified => - commentFromEntity(entity) - .map { comment => - ScenarioActivity.ScenarioModified( - scenarioId = scenarioIdFromEntity(entity), - scenarioActivityId = entity.activityId, - user = userFromEntity(entity), - date = entity.createdAt.toInstant, - scenarioVersionId = entity.scenarioVersion, - comment = comment, - ) - } + (for { + comment <- commentFromEntity(entity) + previousScenarioVersionId = optionalAdditionalPropertyFromEntity(entity, "prevVersionId") + .flatMap(toLongOption) + .map(ScenarioVersionId.apply) + } yield ScenarioActivity.ScenarioModified( + scenarioId = scenarioIdFromEntity(entity), + scenarioActivityId = entity.activityId, + user = userFromEntity(entity), + date = entity.createdAt.toInstant, + previousScenarioVersionId = previousScenarioVersionId, + scenarioVersionId = entity.scenarioVersion, + comment = comment, + )) .map((entity.id, _)) case ScenarioActivityType.ScenarioNameChanged => (for { @@ -886,6 +892,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C case ScenarioActivityType.PerformedSingleExecution => (for { comment <- commentFromEntity(entity) + result <- resultFromEntity(entity) } yield ScenarioActivity.PerformedSingleExecution( scenarioId = scenarioIdFromEntity(entity), scenarioActivityId = entity.activityId, @@ -893,17 +900,16 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C date = entity.createdAt.toInstant, scenarioVersionId = entity.scenarioVersion, comment = comment, - dateFinished = entity.finishedAt.map(_.toInstant), - status = entity.state.map(_.toString), - errorMessage = entity.errorMessage, + result = result, )).map((entity.id, _)) case ScenarioActivityType.PerformedScheduledExecution => (for { scheduleName <- additionalPropertyFromEntity(entity, "scheduleName") - status <- additionalPropertyFromEntity(entity, "status").flatMap( + scheduledExecutionStatus <- additionalPropertyFromEntity(entity, "status").flatMap( ScheduledExecutionStatus.withNameEither(_).left.map(_.getMessage) ) - createdAt <- additionalPropertyFromEntity(entity, "createdAt").map(Instant.parse) + dateFinished <- entity.finishedAt.toRight("Missing finishedAt field").map(_.toInstant) + createdAt <- additionalPropertyFromEntity(entity, "createdAt").map(Instant.parse) nextRetryAt = optionalAdditionalPropertyFromEntity(entity, "nextRetryAt").map(Instant.parse) retriesLeft = optionalAdditionalPropertyFromEntity(entity, "retriesLeft").flatMap(toIntOption) } yield ScenarioActivity.PerformedScheduledExecution( @@ -912,9 +918,9 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C user = userFromEntity(entity), date = entity.createdAt.toInstant, scenarioVersionId = entity.scenarioVersion, - dateFinished = entity.finishedAt.map(_.toInstant), + dateFinished = dateFinished, scheduleName = scheduleName, - status = status, + scheduledExecutionStatus = scheduledExecutionStatus, createdAt = createdAt, nextRetryAt = nextRetryAt, retriesLeft = retriesLeft, @@ -928,13 +934,13 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C user = userFromEntity(entity), date = entity.createdAt.toInstant, scenarioVersionId = entity.scenarioVersion, - errorMessage = entity.errorMessage, changes = description, )).map((entity.id, _)) case ScenarioActivityType.CustomAction(actionName) => (for { comment <- commentFromEntity(entity) + result <- resultFromEntity(entity) } yield ScenarioActivity .CustomAction( scenarioId = scenarioIdFromEntity(entity), @@ -944,6 +950,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C scenarioVersionId = entity.scenarioVersion, actionName = actionName, comment = comment, + result = result, )).map((entity.id, _)) } } diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/util/ScenarioActivityUtils.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/util/ScenarioActivityUtils.scala new file mode 100644 index 00000000000..8de25560374 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/util/ScenarioActivityUtils.scala @@ -0,0 +1,43 @@ +package pl.touk.nussknacker.ui.util + +import pl.touk.nussknacker.engine.api.deployment.{DeploymentRelatedActivity, ScenarioActivity} +import pl.touk.nussknacker.ui.db.entity.ScenarioActivityType + +import java.time.Instant + +object ScenarioActivityUtils { + + implicit class ScenarioActivityOps(scenarioActivity: ScenarioActivity) { + + def activityType: ScenarioActivityType = { + scenarioActivity match { + case _: ScenarioActivity.ScenarioCreated => ScenarioActivityType.ScenarioCreated + case _: ScenarioActivity.ScenarioArchived => ScenarioActivityType.ScenarioArchived + case _: ScenarioActivity.ScenarioUnarchived => ScenarioActivityType.ScenarioUnarchived + case _: ScenarioActivity.ScenarioDeployed => ScenarioActivityType.ScenarioDeployed + case _: ScenarioActivity.ScenarioPaused => ScenarioActivityType.ScenarioPaused + case _: ScenarioActivity.ScenarioCanceled => ScenarioActivityType.ScenarioCanceled + case _: ScenarioActivity.ScenarioModified => ScenarioActivityType.ScenarioModified + case _: ScenarioActivity.ScenarioNameChanged => ScenarioActivityType.ScenarioNameChanged + case _: ScenarioActivity.CommentAdded => ScenarioActivityType.CommentAdded + case _: ScenarioActivity.AttachmentAdded => ScenarioActivityType.AttachmentAdded + case _: ScenarioActivity.ChangedProcessingMode => ScenarioActivityType.ChangedProcessingMode + case _: ScenarioActivity.IncomingMigration => ScenarioActivityType.IncomingMigration + case _: ScenarioActivity.OutgoingMigration => ScenarioActivityType.OutgoingMigration + case _: ScenarioActivity.PerformedSingleExecution => ScenarioActivityType.PerformedSingleExecution + case _: ScenarioActivity.PerformedScheduledExecution => ScenarioActivityType.PerformedScheduledExecution + case _: ScenarioActivity.AutomaticUpdate => ScenarioActivityType.AutomaticUpdate + case activity: ScenarioActivity.CustomAction => ScenarioActivityType.CustomAction(activity.actionName) + } + } + + def dateFinishedOpt: Option[Instant] = { + scenarioActivity match { + case activity: DeploymentRelatedActivity => Some(activity.result.dateFinished) + case _ => None + } + } + + } + +} diff --git a/designer/server/src/test/scala/db/migration/V1_057__MigrateActionsAndCommentsToScenarioActivities.scala b/designer/server/src/test/scala/db/migration/V1_057__MigrateActionsAndCommentsToScenarioActivities.scala index f9270c86538..cfe7f87b1a8 100644 --- a/designer/server/src/test/scala/db/migration/V1_057__MigrateActionsAndCommentsToScenarioActivities.scala +++ b/designer/server/src/test/scala/db/migration/V1_057__MigrateActionsAndCommentsToScenarioActivities.scala @@ -105,8 +105,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities scenarioVersion = Some(processVersionId), comment = Some(s"Very important change $expectedOldCommentIdForHeadActivity"), attachmentId = None, - finishedAt = None, - state = Some("IN_PROGRESS"), + finishedAt = Some(now), + state = Some("FINISHED"), errorMessage = None, buildInfo = None, additionalProperties = AdditionalProperties.empty.properties.asJson.noSpaces, @@ -123,7 +123,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities user = user, date = date, scenarioVersionId = sv, - comment = Available("Deployment with scenario fix", user.name, date) + comment = Available("Deployment with scenario fix", user.name, date), + result = DeploymentResult.Success(date), ) ) } @@ -138,7 +139,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities user = user, date = date, scenarioVersionId = sv, - comment = Available("I'm canceling this scenario, it causes problems", user.name, date) + comment = Available("I'm canceling this scenario, it causes problems", user.name, date), + result = DeploymentResult.Success(date), ) ) } @@ -181,7 +183,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities user = user, date = date, scenarioVersionId = sv, - comment = Available("Paused because marketing campaign is paused for now", user.name, date) + comment = Available("Paused because marketing campaign is paused for now", user.name, date), + result = DeploymentResult.Success(date), ) ) } @@ -217,8 +220,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities scenarioVersion = Some(5), comment = Some("Rename: [marketing-campaign] -> [marketing-campaign-plus]"), attachmentId = None, - finishedAt = None, - state = Some("IN_PROGRESS"), + finishedAt = Some(now), + state = Some("FINISHED"), errorMessage = None, buildInfo = None, additionalProperties = "{}" @@ -236,10 +239,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities user = user, date = date, scenarioVersionId = sv, - dateFinished = None, - status = Some("IN_PROGRESS"), - errorMessage = None, - comment = Available("Deployed at the request of business", user.name, date) + comment = Available("Deployed at the request of business", user.name, date), + result = DeploymentResult.Success(date), ) ) } @@ -255,7 +256,8 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities date = date, scenarioVersionId = sv, actionName = "special action", - comment = Available("Special action needed to be executed", user.name, date) + comment = Available("Special action needed to be executed", user.name, date), + result = DeploymentResult.Success(date), ) ) } @@ -401,9 +403,9 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities impersonatedByIdentity = None, impersonatedByUsername = None, createdAt = now, - performedAt = None, + performedAt = Some(now), actionName = scenarioActionName.value, - state = "IN_PROGRESS", + state = "FINISHED", failureMessage = None, commentId = commentId, buildInfo = None diff --git a/designer/server/src/test/scala/db/migration/V1_058__UpdateAndAddMissingScenarioActivitiesSpec.scala b/designer/server/src/test/scala/db/migration/V1_058__UpdateAndAddMissingScenarioActivitiesSpec.scala index 4ebab69cac4..13b48020c19 100644 --- a/designer/server/src/test/scala/db/migration/V1_058__UpdateAndAddMissingScenarioActivitiesSpec.scala +++ b/designer/server/src/test/scala/db/migration/V1_058__UpdateAndAddMissingScenarioActivitiesSpec.scala @@ -85,6 +85,7 @@ class V1_058__UpdateAndAddMissingScenarioActivitiesSpec scenarioActivityId = activities(0).scenarioActivityId, user = ScenarioUser(None, UserName("Test User"), None, None), date = activities(0).date, + previousScenarioVersionId = None, scenarioVersionId = Some(ScenarioVersionId(2)), comment = ScenarioComment.Deleted(UserName("Test User"), activities(0).date) ), @@ -125,6 +126,7 @@ class V1_058__UpdateAndAddMissingScenarioActivitiesSpec impersonatedByUserName = None ), date = Instant.now, + previousScenarioVersionId = None, scenarioVersionId = None, comment = ScenarioComment.Available( comment = "Scenario migrated from TEST_ENV by test env user", @@ -202,7 +204,6 @@ class V1_058__UpdateAndAddMissingScenarioActivitiesSpec changes = """feature A |feature B |feature C""".stripMargin, - errorMessage = None, ) ) } @@ -223,6 +224,7 @@ class V1_058__UpdateAndAddMissingScenarioActivitiesSpec impersonatedByUserName = None ), date = Instant.now, + previousScenarioVersionId = None, scenarioVersionId = None, comment = ScenarioComment.Available( comment = "Migrations applied: feature A\\nfeature B\\nfeature C", @@ -251,7 +253,6 @@ class V1_058__UpdateAndAddMissingScenarioActivitiesSpec changes = """feature A |feature B |feature C""".stripMargin, - errorMessage = None, ) ) } diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/mock/MockDeploymentManager.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/mock/MockDeploymentManager.scala index f98cb0c8532..6e722229cd8 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/mock/MockDeploymentManager.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/mock/MockDeploymentManager.scala @@ -101,12 +101,13 @@ class MockDeploymentManager( user = ScenarioUser.internalNuUser, date = Instant.now(), scenarioVersionId = Some(ScenarioVersionId.from(processVersion.versionId)), - actionName = "Custom action of MockDeploymentManager", + actionName = "Custom action of MockDeploymentManager just before deployment", comment = ScenarioComment.Available( - comment = "???", + comment = "With comment from DeploymentManager", lastModifiedByUserName = ScenarioUser.internalNuUser.name, lastModifiedAt = Instant.now() - ) + ), + result = DeploymentResult.Success(Instant.now()), ) ) externalDeploymentId <- this.synchronized { @@ -116,20 +117,6 @@ class MockDeploymentManager( .lastOption .getOrElse(Future.successful(None)) } - _ <- scenarioActivityManager.modifyActivity( - customActivityId, - { - case customActionActivity: ScenarioActivity.CustomAction => - customActionActivity.copy( - comment = ScenarioComment.Available( - comment = s"With successfully updated comment", - lastModifiedByUserName = ScenarioUser.internalNuUser.name, - lastModifiedAt = Instant.now() - ) - ) - case other => other - } - ) } yield externalDeploymentId } diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala index 9698926f6ad..6b6519a501b 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ProcessesResourcesSpec.scala @@ -759,7 +759,7 @@ class ProcessesResourcesSpec attachment = None, additionalFields = Nil, overrideIcon = None, - overrideDisplayableName = Some("Version 2 saved"), + overrideDisplayableName = None, overrideSupportedActions = None, `type` = ScenarioActivityType.ScenarioModified ) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala index 207619041d2..d9d286f3935 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/api/ScenarioActivityApiHttpServiceBusinessSpec.scala @@ -19,6 +19,7 @@ import pl.touk.nussknacker.test.{ RestAssuredVerboseLoggingIfValidationFails } +import java.time.Instant import java.util.UUID class ScenarioActivityApiHttpServiceBusinessSpec @@ -328,7 +329,8 @@ class ScenarioActivityApiHttpServiceBusinessSpec ) ) } - "return SCENARIO_CREATED activity and activities returned by deployment manager" in { + + "return SCENARIO_CREATED activity and activities returned by deployment manager, without failed non-batch activity" in { given() .applicationState { createSavedScenario(exampleScenario) @@ -345,7 +347,22 @@ class ScenarioActivityApiHttpServiceBusinessSpec comment = "Executed on custom deployment manager", lastModifiedByUserName = UserName("custom-user"), lastModifiedAt = clock.instant() - ) + ), + result = DeploymentResult.Success(clock.instant()) + ), + ScenarioActivity.CustomAction( + scenarioId = ScenarioId(123), + scenarioActivityId = ScenarioActivityId.random, + user = ScenarioUser(None, UserName("custom-user"), None, None), + date = clock.instant(), + scenarioVersionId = None, + actionName = "Custom action handled by deployment manager", + comment = ScenarioComment.Available( + comment = "Executed on custom deployment manager", + lastModifiedByUserName = UserName("custom-user"), + lastModifiedAt = clock.instant() + ), + result = DeploymentResult.Failure(clock.instant(), None) ) ) ) @@ -392,6 +409,73 @@ class ScenarioActivityApiHttpServiceBusinessSpec ) } + "return SCENARIO_CREATED activity and activities returned by deployment manager, with failed batch activity" in { + given() + .applicationState { + createSavedScenario(exampleScenario) + MockableDeploymentManager.configureManagerSpecificScenarioActivities( + List( + ScenarioActivity.PerformedSingleExecution( + scenarioId = ScenarioId(123), + scenarioActivityId = ScenarioActivityId.random, + user = ScenarioUser(None, UserName("custom-user"), None, None), + date = clock.instant(), + scenarioVersionId = None, + comment = ScenarioComment.Available( + comment = "Immediate execution", + lastModifiedByUserName = UserName("custom-user"), + lastModifiedAt = clock.instant() + ), + result = DeploymentResult.Success(clock.instant()) + ) + ) + ) + } + .when() + .basicAuthAllPermUser() + .get(s"$nuDesignerHttpAddress/api/processes/$exampleScenarioName/activity/activities") + .Then() + .statusCode(200) + .body( + matchJsonWithRegexValues( + s""" + |{ + | "activities": [ + | { + | "id": "${regexes.looseUuidRegex}", + | "user": "admin", + | "date": "${regexes.zuluDateRegex}", + | "scenarioVersionId": 1, + | "additionalFields": [], + | "type": "SCENARIO_CREATED" + | }, + | { + | "id": "${regexes.looseUuidRegex}", + | "user": "custom-user", + | "date": "${regexes.zuluDateRegex}", + | "comment": { + | "content": { + | "value": "Immediate execution", + | "status": "AVAILABLE" + | }, + | "lastModifiedBy": "custom-user", + | "lastModifiedAt": "${regexes.zuluDateRegex}" + | }, + | "additionalFields": [ + | { + | "name": "dateFinished", + | "value": "${regexes.zuluDateRegex}" + | } + | ], + | "type": "PERFORMED_SINGLE_EXECUTION" + | } + | ] + |} + |""".stripMargin + ) + ) + } + "return 404 for no existing scenario" in { given() .when() diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceSpec.scala index 1faf9551618..997c000f646 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/deployment/DeploymentServiceSpec.scala @@ -395,9 +395,19 @@ class DeploymentServiceSpec case _ => fail("Second activity should be ScenarioDeployed") } activities(2) match { - case ScenarioActivity.CustomAction(_, _, _, _, _, actionName, ScenarioComment.Available(content, _, _)) => - actionName shouldBe "Custom action of MockDeploymentManager" - content shouldBe "With successfully updated comment" + case ScenarioActivity.CustomAction( + _, + _, + _, + _, + _, + actionName, + ScenarioComment.Available(content, _, _), + result, + ) => + actionName shouldBe "Custom action of MockDeploymentManager just before deployment" + content shouldBe "With comment from DeploymentManager" + result shouldBe DeploymentResult.Success(result.dateFinished) case _ => fail("Third activity should be CustomAction with comment") } } diff --git a/docs-internal/api/nu-designer-openapi.yaml b/docs-internal/api/nu-designer-openapi.yaml index 5456908924a..2f8bdc6dc0d 100644 --- a/docs-internal/api/nu-designer-openapi.yaml +++ b/docs-internal/api/nu-designer-openapi.yaml @@ -4124,7 +4124,7 @@ paths: - id: 07b04d45-c7c0-4980-a3bc-3c7f66410f68 user: some user date: '2024-01-17T14:21:17Z' - scenarioVersionId: 1 + scenarioVersionId: 2 comment: content: value: Added new processing step @@ -4132,7 +4132,7 @@ paths: lastModifiedBy: some user lastModifiedAt: '2024-01-17T14:21:17Z' additionalFields: [] - overrideDisplayableName: Version 1 saved + overrideDisplayableName: Version 2 saved type: SCENARIO_MODIFIED - id: da3d1f78-7d73-4ed9-b0e5-95538e150d0d user: some user @@ -4235,8 +4235,6 @@ paths: lastModifiedBy: some user lastModifiedAt: '2024-01-17T14:21:17Z' additionalFields: - - name: status - value: IN_PROGRESS - name: dateFinished value: '2024-01-17T14:21:17Z' - name: errorMessage @@ -4252,8 +4250,6 @@ paths: lastModifiedBy: some user lastModifiedAt: '2024-01-17T14:21:17Z' additionalFields: - - name: status - value: FAILED - name: dateFinished value: '2024-01-17T14:21:17Z' type: PERFORMED_SINGLE_EXECUTION @@ -4425,7 +4421,7 @@ paths: - delete_comment - edit_comment - type: SCENARIO_MODIFIED - displayableName: New version saved + displayableName: Scenario modified icon: /assets/activities/scenarioModified.svg supportedActions: - delete_comment diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index 5e53cd9cc94..78cec421552 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -12,6 +12,10 @@ To see the biggest differences please consult the [changelog](Changelog.md). ### Code API changes +* [#6971](https://github.com/TouK/nussknacker/pull/6971) `DeploymentManagerDependencies` API changes: + * Added field `scenarioActivityManager: ScenarioActivityManager` + * `scenarioActivityManager` can be used by any `DeploymentManager` to save scenario activities in the Nu database + * there is `NoOpScenarioActivityManager` implementation available (if needed for tests etc.) * [#6695](https://github.com/TouK/nussknacker/pull/6695) `SingleTypingResult` API changes: * Added `typeHintsObjType` which is used as a type for a type hints, suggester and validation. * Renamed `objType` to `runtimeObjType` which indicates a current object in a runtime. diff --git a/engine/flink/management/periodic/src/main/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicProcessService.scala b/engine/flink/management/periodic/src/main/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicProcessService.scala index c37199f9fe9..000aa300e58 100644 --- a/engine/flink/management/periodic/src/main/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicProcessService.scala +++ b/engine/flink/management/periodic/src/main/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicProcessService.scala @@ -60,27 +60,27 @@ class PeriodicProcessService( def getScenarioActivitiesSpecificToPeriodicProcess( processIdWithName: ProcessIdWithName - ): Future[List[ScenarioActivity]] = - scheduledProcessesRepository - .getSchedulesState(processIdWithName.name) - .run - .map(_.groupedByPeriodicProcess) - .map(_.flatMap(_.deployments)) - .map(_.map { deployment => - ScenarioActivity.PerformedScheduledExecution( - scenarioId = ScenarioId(processIdWithName.id.value), - scenarioActivityId = ScenarioActivityId.random, - user = ScenarioUser.internalNuUser, - date = instantAtUTC(deployment.runAt), - scenarioVersionId = Some(ScenarioVersionId.from(deployment.periodicProcess.processVersion.versionId)), - dateFinished = deployment.state.completedAt.map(instantAtUTC), - scheduleName = deployment.scheduleName.display, - status = scheduledExecutionStatus(deployment.state.status), - createdAt = instantAtUTC(deployment.createdAt), - nextRetryAt = deployment.nextRetryAt.map(instantAtUTC), - retriesLeft = deployment.nextRetryAt.map(_ => deployment.retriesLeft), - ) - }.toList.sortBy(_.date)) + ): Future[List[ScenarioActivity]] = for { + schedulesState <- scheduledProcessesRepository.getSchedulesState(processIdWithName.name).run + groupedByProcess = schedulesState.groupedByPeriodicProcess + deployments = groupedByProcess.flatMap(_.deployments) + deploymentsWithStatuses = deployments.flatMap(d => scheduledExecutionStatusAndDateFinished(d).map((d, _))) + activities = deploymentsWithStatuses.map { case (deployment, (status, dateFinished)) => + ScenarioActivity.PerformedScheduledExecution( + scenarioId = ScenarioId(processIdWithName.id.value), + scenarioActivityId = ScenarioActivityId.random, + user = ScenarioUser.internalNuUser, + date = instantAtUTC(deployment.runAt), + scenarioVersionId = Some(ScenarioVersionId.from(deployment.periodicProcess.processVersion.versionId)), + scheduledExecutionStatus = status, + dateFinished = dateFinished, + scheduleName = deployment.scheduleName.display, + createdAt = instantAtUTC(deployment.createdAt), + nextRetryAt = deployment.nextRetryAt.map(instantAtUTC), + retriesLeft = deployment.nextRetryAt.map(_ => deployment.retriesLeft), + ) + } + } yield activities def schedule( schedule: ScheduleProperty, @@ -520,21 +520,26 @@ class PeriodicProcessService( } - private def scheduledExecutionStatus(status: PeriodicProcessDeploymentStatus): ScheduledExecutionStatus = { - status match { - case PeriodicProcessDeploymentStatus.Scheduled => - ScheduledExecutionStatus.Scheduled - case PeriodicProcessDeploymentStatus.Deployed => - ScheduledExecutionStatus.Deployed - case PeriodicProcessDeploymentStatus.Finished => - ScheduledExecutionStatus.Finished - case PeriodicProcessDeploymentStatus.Failed => - ScheduledExecutionStatus.Failed - case PeriodicProcessDeploymentStatus.RetryingDeploy => - ScheduledExecutionStatus.DeploymentWillBeRetried - case PeriodicProcessDeploymentStatus.FailedOnDeploy => - ScheduledExecutionStatus.DeploymentFailed - } + private def scheduledExecutionStatusAndDateFinished( + entity: PeriodicProcessDeployment[Unit], + ): Option[(ScheduledExecutionStatus, Instant)] = { + for { + dateFinished <- entity.state.completedAt.map(instantAtUTC) + status <- entity.state.status match { + case PeriodicProcessDeploymentStatus.Scheduled => + None + case PeriodicProcessDeploymentStatus.Deployed => + None + case PeriodicProcessDeploymentStatus.Finished => + Some(ScheduledExecutionStatus.Finished) + case PeriodicProcessDeploymentStatus.Failed => + Some(ScheduledExecutionStatus.Failed) + case PeriodicProcessDeploymentStatus.RetryingDeploy => + Some(ScheduledExecutionStatus.DeploymentWillBeRetried) + case PeriodicProcessDeploymentStatus.FailedOnDeploy => + Some(ScheduledExecutionStatus.DeploymentFailed) + } + } yield (status, dateFinished) } private def instantAtUTC(localDateTime: LocalDateTime): Instant = diff --git a/engine/flink/management/periodic/src/test/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicDeploymentManagerTest.scala b/engine/flink/management/periodic/src/test/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicDeploymentManagerTest.scala index e9d08636226..bd4e22b7b81 100644 --- a/engine/flink/management/periodic/src/test/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicDeploymentManagerTest.scala +++ b/engine/flink/management/periodic/src/test/scala/pl/touk/nussknacker/engine/management/periodic/PeriodicDeploymentManagerTest.scala @@ -381,8 +381,7 @@ class PeriodicDeploymentManagerTest val activities = f.periodicDeploymentManager.scenarioActivityHandling.managerSpecificScenarioActivities(idWithName).futureValue - val firstActivity = activities.head.asInstanceOf[ScenarioActivity.PerformedScheduledExecution] - val secondActivity = activities(1).asInstanceOf[ScenarioActivity.PerformedScheduledExecution] + val firstActivity = activities.head.asInstanceOf[ScenarioActivity.PerformedScheduledExecution] activities shouldBe List( ScenarioActivity.PerformedScheduledExecution( scenarioId = ScenarioId(1), @@ -392,24 +391,11 @@ class PeriodicDeploymentManagerTest scenarioVersionId = Some(ScenarioVersionId(1)), dateFinished = firstActivity.dateFinished, scheduleName = "[default]", - status = ScheduledExecutionStatus.Finished, + scheduledExecutionStatus = ScheduledExecutionStatus.Finished, createdAt = firstActivity.createdAt, retriesLeft = None, nextRetryAt = None ), - ScenarioActivity.PerformedScheduledExecution( - scenarioId = ScenarioId(1), - scenarioActivityId = secondActivity.scenarioActivityId, - user = ScenarioUser(None, UserName("Nussknacker"), None, None), - date = secondActivity.date, - scenarioVersionId = Some(ScenarioVersionId(42)), - dateFinished = secondActivity.dateFinished, - scheduleName = "[default]", - status = ScheduledExecutionStatus.Scheduled, - createdAt = secondActivity.createdAt, - retriesLeft = None, - nextRetryAt = None - ) ) } @@ -444,7 +430,7 @@ class PeriodicDeploymentManagerTest scenarioVersionId = Some(ScenarioVersionId(1)), dateFinished = headActivity.dateFinished, scheduleName = "[default]", - status = ScheduledExecutionStatus.Failed, + scheduledExecutionStatus = ScheduledExecutionStatus.Failed, createdAt = headActivity.createdAt, retriesLeft = None, nextRetryAt = None diff --git a/extensions-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivity.scala b/extensions-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivity.scala index 19df447e84a..ac132479585 100644 --- a/extensions-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivity.scala +++ b/extensions-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/ScenarioActivity.scala @@ -80,10 +80,6 @@ final case class Environment(name: String) extends AnyVal sealed trait ScheduledExecutionStatus extends EnumEntry with UpperSnakecase object ScheduledExecutionStatus extends Enum[ScheduledExecutionStatus] { - case object Scheduled extends ScheduledExecutionStatus - - case object Deployed extends ScheduledExecutionStatus - case object Finished extends ScheduledExecutionStatus case object Failed extends ScheduledExecutionStatus @@ -103,6 +99,29 @@ sealed trait ScenarioActivity { def scenarioVersionId: Option[ScenarioVersionId] } +sealed trait DeploymentRelatedActivity extends ScenarioActivity { + def result: DeploymentResult +} + +sealed trait BatchDeploymentRelatedActivity extends DeploymentRelatedActivity + +sealed trait DeploymentResult { + def dateFinished: Instant +} + +object DeploymentResult { + + final case class Success( + dateFinished: Instant, + ) extends DeploymentResult + + final case class Failure( + dateFinished: Instant, + errorMessage: Option[String], + ) extends DeploymentResult + +} + object ScenarioActivity { final case class ScenarioCreated( @@ -138,7 +157,8 @@ object ScenarioActivity { date: Instant, scenarioVersionId: Option[ScenarioVersionId], comment: ScenarioComment, - ) extends ScenarioActivity + result: DeploymentResult, + ) extends DeploymentRelatedActivity final case class ScenarioPaused( scenarioId: ScenarioId, @@ -147,7 +167,8 @@ object ScenarioActivity { date: Instant, scenarioVersionId: Option[ScenarioVersionId], comment: ScenarioComment, - ) extends ScenarioActivity + result: DeploymentResult, + ) extends DeploymentRelatedActivity final case class ScenarioCanceled( scenarioId: ScenarioId, @@ -156,7 +177,8 @@ object ScenarioActivity { date: Instant, scenarioVersionId: Option[ScenarioVersionId], comment: ScenarioComment, - ) extends ScenarioActivity + result: DeploymentResult, + ) extends DeploymentRelatedActivity // Scenario modifications @@ -165,6 +187,7 @@ object ScenarioActivity { scenarioActivityId: ScenarioActivityId, user: ScenarioUser, date: Instant, + previousScenarioVersionId: Option[ScenarioVersionId], scenarioVersionId: Option[ScenarioVersionId], comment: ScenarioComment, ) extends ScenarioActivity @@ -239,10 +262,8 @@ object ScenarioActivity { date: Instant, scenarioVersionId: Option[ScenarioVersionId], comment: ScenarioComment, - dateFinished: Option[Instant], - status: Option[String], - errorMessage: Option[String], - ) extends ScenarioActivity + result: DeploymentResult, + ) extends BatchDeploymentRelatedActivity final case class PerformedScheduledExecution( scenarioId: ScenarioId, @@ -250,15 +271,24 @@ object ScenarioActivity { user: ScenarioUser, date: Instant, scenarioVersionId: Option[ScenarioVersionId], - dateFinished: Option[Instant], + scheduledExecutionStatus: ScheduledExecutionStatus, + dateFinished: Instant, scheduleName: String, - status: ScheduledExecutionStatus, createdAt: Instant, nextRetryAt: Option[Instant], retriesLeft: Option[Int], - ) extends ScenarioActivity + ) extends BatchDeploymentRelatedActivity { + + override def result: DeploymentResult = scheduledExecutionStatus match { + case ScheduledExecutionStatus.Finished => DeploymentResult.Success(dateFinished) + case ScheduledExecutionStatus.Failed => DeploymentResult.Failure(dateFinished, None) + case ScheduledExecutionStatus.DeploymentWillBeRetried => DeploymentResult.Failure(dateFinished, None) + case ScheduledExecutionStatus.DeploymentFailed => DeploymentResult.Failure(dateFinished, None) + } + + } - // Other/technical + // Technical final case class AutomaticUpdate( scenarioId: ScenarioId, @@ -267,9 +297,10 @@ object ScenarioActivity { date: Instant, scenarioVersionId: Option[ScenarioVersionId], changes: String, - errorMessage: Option[String], ) extends ScenarioActivity + // Other + final case class CustomAction( scenarioId: ScenarioId, scenarioActivityId: ScenarioActivityId, @@ -278,6 +309,7 @@ object ScenarioActivity { scenarioVersionId: Option[ScenarioVersionId], actionName: String, comment: ScenarioComment, - ) extends ScenarioActivity + result: DeploymentResult, + ) extends DeploymentRelatedActivity }