From a4d84cc558f86b5b23dca41bea714a094ea38f6c Mon Sep 17 00:00:00 2001 From: mgoworko <37329559+mgoworko@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:42:41 +0200 Subject: [PATCH] [NU-1772] ScenarioActivity-based activity log (#7039) --- .../api/ScenarioActivityApiHttpService.scala | 6 +- .../ui/process/ProcessService.scala | 16 +- .../ui/process/ScenarioActivityAuditLog.scala | 207 ++++++++++++++++++ ...cessingTypeDeployedScenariosProvider.scala | 2 +- .../deployment/DeploymentService.scala | 29 ++- .../ui/process/repository/LockableTable.scala | 37 ++++ .../repository/ScenarioActionRepository.scala | 127 ++++++++--- ...rioActionRepositoryAuditLogDecorator.scala | 174 +++++++++++++++ .../DbScenarioActivityRepository.scala | 67 ++---- .../ScenarioActivityRepository.scala | 41 ++-- ...oActivityRepositoryAuditLogDecorator.scala | 106 +++++++++ .../server/AkkaHttpBasedRouteProvider.scala | 14 +- .../nussknacker/ui/util/FunctorUtils.scala | 23 ++ ...tionsAndCommentsToScenarioActivities.scala | 2 +- ...eAndAddMissingScenarioActivitiesSpec.scala | 2 +- .../test/base/it/NuResourcesTest.scala | 7 +- .../test/utils/domain/ScenarioHelper.scala | 10 +- .../test/utils/domain/TestFactory.scala | 10 +- .../NotificationServiceTest.scala | 2 +- .../ScenarioAttachmentServiceSpec.scala | 21 +- .../deployment/DeploymentServiceSpec.scala | 6 +- .../DBFetchingProcessRepositorySpec.scala | 4 +- docs/MigrationGuide.md | 16 ++ .../src/main/resources/logback-test.xml | 10 + 24 files changed, 791 insertions(+), 148 deletions(-) create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ScenarioActivityAuditLog.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/LockableTable.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepositoryAuditLogDecorator.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepositoryAuditLogDecorator.scala create mode 100644 designer/server/src/main/scala/pl/touk/nussknacker/ui/util/FunctorUtils.scala 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 60810490582..14b90437672 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 @@ -529,7 +529,7 @@ class ScenarioActivityApiHttpService( private def editComment(request: EditCommentRequest, scenarioId: ProcessId)( implicit loggedUser: LoggedUser - ): EitherT[Future, ScenarioActivityError, Unit] = + ): EitherT[Future, ScenarioActivityError, ScenarioActivityId] = EitherT( dbioActionRunner.run( scenarioActivityRepository.editComment( @@ -542,14 +542,14 @@ class ScenarioActivityApiHttpService( private def deleteComment(request: DeprecatedDeleteCommentRequest, scenarioId: ProcessId)( implicit loggedUser: LoggedUser - ): EitherT[Future, ScenarioActivityError, Unit] = + ): EitherT[Future, ScenarioActivityError, ScenarioActivityId] = EitherT( dbioActionRunner.run(scenarioActivityRepository.deleteComment(scenarioId, request.commentId)) ).leftMap(_ => NoComment(request.commentId)) private def deleteComment(request: DeleteCommentRequest, scenarioId: ProcessId)( implicit loggedUser: LoggedUser - ): EitherT[Future, ScenarioActivityError, Unit] = + ): EitherT[Future, ScenarioActivityError, ScenarioActivityId] = EitherT( dbioActionRunner.run( scenarioActivityRepository.deleteComment(scenarioId, ScenarioActivityId(request.scenarioActivityId)) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala index b61652cb428..8e5ae07a82d 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ProcessService.scala @@ -350,7 +350,13 @@ class DBProcessService( DBIOAction.seq( processRepository.archive(processId = process.idWithNameUnsafe, isArchived = false), scenarioActionRepository - .markProcessAsUnArchived(processId = process.processIdUnsafe, process.processVersionId) + .addInstantAction( + process.processIdUnsafe, + process.processVersionId, + ScenarioActionName.UnArchive, + None, + None + ) ) ) } else { @@ -527,7 +533,13 @@ class DBProcessService( .runInTransaction( DBIOAction.seq( processRepository.archive(processId = process.idWithNameUnsafe, isArchived = true), - scenarioActionRepository.markProcessAsArchived(processId = process.processIdUnsafe, process.processVersionId) + scenarioActionRepository.addInstantAction( + process.processIdUnsafe, + process.processVersionId, + ScenarioActionName.Archive, + None, + None + ) ) ) diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ScenarioActivityAuditLog.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ScenarioActivityAuditLog.scala new file mode 100644 index 00000000000..12c1dadd1f9 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/ScenarioActivityAuditLog.scala @@ -0,0 +1,207 @@ +package pl.touk.nussknacker.ui.process + +import cats.effect.IO +import com.typesafe.scalalogging.Logger +import org.slf4j.{LoggerFactory, MDC} +import pl.touk.nussknacker.engine.api.deployment._ +import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId} +import pl.touk.nussknacker.ui.process.ScenarioAttachmentService.AttachmentToAdd +import pl.touk.nussknacker.ui.security.api.LoggedUser + +object ScenarioActivityAuditLog { + + private val logger = Logger(LoggerFactory.getLogger(s"scenario-activity-audit")) + + def onCreateScenarioActivity( + scenarioActivity: ScenarioActivity + ): IO[Unit] = + logWithContext(scenarioActivity.scenarioId, scenarioActivity.scenarioVersionId, scenarioActivity.user.name.value)( + s"New activity: ${stringify(scenarioActivity)}" + ) + + def onEditComment( + processId: ProcessId, + user: LoggedUser, + scenarioActivityId: ScenarioActivityId, + comment: String + ): IO[Unit] = { + logWithContext(ScenarioId(processId.value), None, user.username)( + s"[commentId=${scenarioActivityId.value.toString}] Comment edited, new value: [$comment]" + ) + } + + def onDeleteComment( + processId: ProcessId, + rowId: Long, + user: LoggedUser, + ): IO[Unit] = + logWithContext(ScenarioId(processId.value), None, user.username)( + s"Comment with rowId=$rowId deleted" + ) + + def onDeleteComment( + processId: ProcessId, + activityId: ScenarioActivityId, + user: LoggedUser, + ): IO[Unit] = + logWithContext(ScenarioId(processId.value), None, user.username)( + s"Comment for activityId=${activityId.value} deleted" + ) + + def onAddAttachment( + attachmentToAdd: AttachmentToAdd, + user: LoggedUser, + ): IO[Unit] = + logWithContext( + ScenarioId(attachmentToAdd.scenarioId.value), + Some(ScenarioVersionId.from(attachmentToAdd.scenarioVersionId)), + user.username + )(s"Attachment added: [${attachmentToAdd.fileName}]") + + def onDeleteAttachment( + scenarioId: ProcessId, + attachmentId: Long, + user: LoggedUser, + ): IO[Unit] = + logWithContext( + ScenarioId(scenarioId.value), + None, + user.username + )(s"Attachment deleted: [attachmentId=$attachmentId]") + + def onScenarioImmediateAction( + processActionId: ProcessActionId, + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + user: LoggedUser + ): IO[Unit] = + logWithContext(ScenarioId(processId.value), processVersion.map(ScenarioVersionId.from), user.username)( + s"Immediate scenario action [actionName=${actionName.value},actionId=${processActionId.value}]" + ) + + def onScenarioActionStarted( + processActionId: ProcessActionId, + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + user: LoggedUser + ): IO[Unit] = + logWithContext(ScenarioId(processId.value), processVersion.map(ScenarioVersionId.from), user.username)( + s"Scenario action [actionName=${actionName.value},actionId=${processActionId.value}] started" + ) + + def onScenarioActionFinishedWithSuccess( + processActionId: ProcessActionId, + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + comment: Option[String], + user: LoggedUser + ): IO[Unit] = { + val commentValue = comment match { + case Some(content) => s"comment [$content]" + case None => "without comment" + } + logWithContext(ScenarioId(processId.value), processVersion.map(ScenarioVersionId.from), user.username)( + s"Scenario action [actionName=${actionName.value},actionId=${processActionId.value}] finished with success and $commentValue " + ) + } + + def onScenarioActionFinishedWithFailure( + processActionId: ProcessActionId, + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + comment: Option[String], + failureMessage: String, + user: LoggedUser + ): IO[Unit] = { + val commentValue = comment match { + case Some(content) => s"with comment [$content]" + case None => "without comment" + } + logWithContext(ScenarioId(processId.value), processVersion.map(ScenarioVersionId.from), user.username)( + s"Scenario action [actionName=${actionName.value},actionId=${processActionId.value}] finished with failure [$failureMessage] $commentValue" + ) + } + + def onScenarioActionRemoved( + processActionId: ProcessActionId, + processId: ProcessId, + processVersion: Option[VersionId], + user: LoggedUser + ): IO[Unit] = { + logWithContext(ScenarioId(processId.value), processVersion.map(ScenarioVersionId.from), user.username)( + s"Scenario action [actionId=${processActionId.value}] removed" + ) + } + + private def stringify(scenarioActivity: ScenarioActivity): String = scenarioActivity match { + case ScenarioActivity.ScenarioDeployed(_, _, _, _, _, comment, result) => + s"ScenarioDeployed(comment=${stringify(comment)},result=${stringify(result)})" + case ScenarioActivity.ScenarioPaused(_, _, _, _, _, comment, result) => + s"ScenarioPaused(comment=${stringify(comment)},result=${stringify(result)})" + case ScenarioActivity.ScenarioCanceled(_, _, _, _, _, comment, result) => + s"ScenarioCanceled(comment=${stringify(comment)},result=${stringify(result)})" + case ScenarioActivity.CustomAction(_, _, _, _, _, actionName, comment, result) => + s"CustomAction(action=$actionName,comment=${stringify(comment)},result=${stringify(result)})" + case ScenarioActivity.PerformedSingleExecution(_, _, _, _, _, comment, result) => + s"PerformedSingleExecution(comment=${stringify(comment)},result=${stringify(result)})" + case ScenarioActivity.PerformedScheduledExecution(_, _, _, _, _, status, _, scheduleName, _, _, _) => + s"PerformedScheduledExecution(scheduleName=$scheduleName,scheduledExecutionStatus=${status.entryName})" + case ScenarioActivity.ScenarioCreated(_, _, _, _, _) => + "ScenarioCreated" + case ScenarioActivity.ScenarioArchived(_, _, _, _, _) => + "ScenarioArchived" + case ScenarioActivity.ScenarioUnarchived(_, _, _, _, _) => + "ScenarioUnarchived" + case ScenarioActivity.ScenarioModified(_, _, _, _, _, _, comment) => + s"ScenarioModified(comment=${stringify(comment)})" + case ScenarioActivity.ScenarioNameChanged(_, _, _, _, _, oldName, newName) => + s"ScenarioNameChanged(oldName=$oldName,newName=$newName)" + case ScenarioActivity.CommentAdded(_, _, _, _, _, comment) => + s"CommentAdded(comment=${stringify(comment)})" + case ScenarioActivity.AttachmentAdded(_, _, _, _, _, attachment) => + s"AttachmentAdded(fileName=${stringify(attachment)})" + case ScenarioActivity.ChangedProcessingMode(_, _, _, _, _, from, to) => + s"ChangedProcessingMode(from=$from,to=$to)" + case ScenarioActivity.IncomingMigration(_, _, _, _, _, sourceEnvironment, sourceUser, sourceVersionId, _) => + s"IncomingMigration(sourceEnvironment=${sourceEnvironment.name},sourceUser=${sourceUser.value},sourceVersionId=${sourceVersionId + .map(_.value.toString) + .getOrElse("[none]")})" + case ScenarioActivity.OutgoingMigration(_, _, _, _, _, destinationEnvironment) => + s"OutgoingMigration(destinationEnvironment=${destinationEnvironment.name})" + case ScenarioActivity.AutomaticUpdate(_, _, _, _, _, changes) => + s"AutomaticUpdate(changes=$changes)" + } + + private def stringify(attachment: ScenarioAttachment): String = attachment match { + case ScenarioAttachment.Available(_, attachmentFilename, _, _) => s"Available(${attachmentFilename.value})" + case ScenarioAttachment.Deleted(attachmentFilename, _, _) => s"Deleted(${attachmentFilename.value})" + } + + private def stringify(comment: ScenarioComment): String = comment match { + case ScenarioComment.WithContent(comment, _, _) => comment + case ScenarioComment.WithoutContent(_, _) => "none" + } + + private def stringify(result: DeploymentResult): String = result match { + case DeploymentResult.Success(_) => "Success" + case DeploymentResult.Failure(_, errorMessage) => s"Failure($errorMessage)" + } + + private def logWithContext( + scenarioId: ScenarioId, + scenarioVersionId: Option[ScenarioVersionId], + username: String, + )(log: String): IO[Unit] = IO.delay { + MDC.clear() + MDC.put("scenarioId", scenarioId.value.toString) + MDC.put("scenarioVersionId", scenarioVersionId.map(_.value.toString).getOrElse("none")) + MDC.put("username", username) + logger.info(log) + MDC.clear() + } + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DefaultProcessingTypeDeployedScenariosProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DefaultProcessingTypeDeployedScenariosProvider.scala index 857acd16f33..838787de980 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DefaultProcessingTypeDeployedScenariosProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/deployment/DefaultProcessingTypeDeployedScenariosProvider.scala @@ -85,7 +85,7 @@ object DefaultProcessingTypeDeployedScenariosProvider { val dumbModelInfoProvier = ProcessingTypeDataProvider.withEmptyCombinedData( Map(processingType -> ValueWithRestriction.anyUser(Map.empty[String, String])) ) - val actionRepository = new DbScenarioActionRepository(dbRef, dumbModelInfoProvier) + val actionRepository = DbScenarioActionRepository.create(dbRef, dumbModelInfoProvier) val scenarioLabelsRepository = new ScenarioLabelsRepository(dbRef) val processRepository = DBFetchingProcessRepository.create(dbRef, actionRepository, scenarioLabelsRepository) val futureProcessRepository = 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 3dc8509c41a..5ae7f7bab7b 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 @@ -7,6 +7,7 @@ import cats.implicits.{toFoldableOps, toTraverseOps} import cats.syntax.functor._ import com.typesafe.scalalogging.LazyLogging import db.util.DBIOActionInstances._ +import pl.touk.nussknacker.engine.api.Comment import pl.touk.nussknacker.engine.api.component.NodesDeploymentData import pl.touk.nussknacker.engine.api.deployment.ScenarioActionName.{Cancel, Deploy} import pl.touk.nussknacker.engine.api.deployment._ @@ -19,7 +20,6 @@ import pl.touk.nussknacker.restmodel.scenariodetails.ScenarioWithDetails import pl.touk.nussknacker.ui.api.{DeploymentCommentSettings, ListenerApiUser} import pl.touk.nussknacker.ui.listener.ProcessChangeEvent.{OnActionExecutionFinished, OnActionFailed, OnActionSuccess} import pl.touk.nussknacker.ui.listener.{ProcessChangeListener, User => ListenerUser} -import pl.touk.nussknacker.engine.api.Comment import pl.touk.nussknacker.ui.process.ProcessStateProvider import pl.touk.nussknacker.ui.process.ScenarioWithDetailsConversions._ import pl.touk.nussknacker.ui.process.deployment.LoggedUserConversions.LoggedUserOps @@ -48,7 +48,7 @@ import scala.util.{Failure, Success} class DeploymentService( dispatcher: DeploymentManagerDispatcher, processRepository: FetchingProcessRepository[DB], - actionRepository: DbScenarioActionRepository, + actionRepository: ScenarioActionRepository, dbioRunner: DBIOActionRunner, processValidator: ProcessingTypeDataProvider[UIProcessValidator, _], scenarioResolver: ProcessingTypeDataProvider[ScenarioResolver, _], @@ -141,7 +141,7 @@ class DeploymentService( actionResult <- validateBeforeDeploy(ctx.latestScenarioDetails, deployedScenarioData, updateStrategy) .transformWith { case Failure(ex) => - removeInvalidAction(ctx.actionId).transform(_ => Failure(ex)) + removeInvalidAction(ctx).transform(_ => Failure(ex)) case Success(_) => // we notify of deployment finish/fail only if initial validation succeeded val deploymentFuture = runActionAndHandleResults( @@ -170,10 +170,9 @@ class DeploymentService( getBuildInfoProcessingType: ScenarioWithDetailsEntity[PS] => Option[ProcessingType] )(implicit user: LoggedUser): Future[CommandContext[PS]] = { implicit val freshnessPolicy: DataFreshnessPolicy = DataFreshnessPolicy.Fresh - dbioRunner.runInTransaction( + // 1.1 lock for critical section + transactionallyRunCriticalSection( for { - // 1.1 lock for critical section - _ <- actionRepository.lockActionsTable // 1.2. fetch scenario data processDetailsOpt <- processRepository.fetchLatestProcessDetailsForProcessId[PS](processId.id) processDetails <- existsOrFail(processDetailsOpt, ProcessNotFoundError(processId.name)) @@ -207,6 +206,10 @@ class DeploymentService( ) } + private def transactionallyRunCriticalSection[T](dbioAction: DB[T]) = { + dbioRunner.runInTransaction(actionRepository.withLockedTable(dbioAction)) + } + // TODO: Use buildInfo explicitly instead of ProcessingType-that-is-used-to-calculate-buildInfo private case class CommandContext[PS: ScenarioShapeFetchStrategy]( latestScenarioDetails: ScenarioWithDetailsEntity[PS], @@ -387,14 +390,22 @@ class DeploymentService( // Before we can do that we should check if we somewhere rely on fact that version is always defined - // see ProcessAction.processVersionId logger.info(s"Action $actionString finished for action without version id - skipping listener notification") - removeInvalidAction(ctx.actionId) + removeInvalidAction(ctx) } .map(_ => result) } } - private def removeInvalidAction(actionId: ProcessActionId): Future[Unit] = { - dbioRunner.runInTransaction(actionRepository.removeAction(actionId)) + private def removeInvalidAction[PS: ScenarioShapeFetchStrategy]( + context: CommandContext[PS] + )(implicit user: LoggedUser): Future[Unit] = { + dbioRunner.runInTransaction( + actionRepository.removeAction( + context.actionId, + context.latestScenarioDetails.processId, + context.versionOnWhichActionIsDone + ) + ) } // TODO: check deployment id to be sure that returned status is for given deployment diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/LockableTable.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/LockableTable.scala new file mode 100644 index 00000000000..80737daa299 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/LockableTable.scala @@ -0,0 +1,37 @@ +package pl.touk.nussknacker.ui.process.repository + +import db.util.DBIOActionInstances.DB +import slick.lifted.{AbstractTable, TableQuery => LTableQuery} + +import scala.concurrent.ExecutionContext + +trait LockableTable { + + def withLockedTable[T]( + dbioAction: DB[T] + ): DB[T] + +} + +trait DbLockableTable { this: DbioRepository => + + import profile.api._ + + type ENTITY <: AbstractTable[_] + + protected implicit def executionContext: ExecutionContext + + protected def table: LTableQuery[ENTITY] + + def withLockedTable[T]( + dbioAction: DB[T] + ): DB[T] = for { + _ <- lockTable + result <- dbioAction + } yield result + + private def lockTable: DB[Unit] = { + run(table.filter(_ => false).forUpdate.result.map(_ => ())) + } + +} 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 41947cf63da..287f44bceca 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 @@ -10,7 +10,12 @@ import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessName, Processin import pl.touk.nussknacker.engine.management.periodic.InstantBatchCustomAction import pl.touk.nussknacker.engine.util.Implicits.RichScalaMap import pl.touk.nussknacker.ui.app.BuildInfo -import pl.touk.nussknacker.ui.db.entity.{AdditionalProperties, ScenarioActivityEntityData, ScenarioActivityType} +import pl.touk.nussknacker.ui.db.entity.{ + AdditionalProperties, + ScenarioActivityEntityData, + ScenarioActivityEntityFactory, + ScenarioActivityType +} import pl.touk.nussknacker.ui.db.{DbRef, NuTables} import pl.touk.nussknacker.ui.process.processingtype.provider.ProcessingTypeDataProvider import pl.touk.nussknacker.ui.security.api.LoggedUser @@ -21,18 +26,66 @@ import java.time.Instant import java.util.UUID import scala.concurrent.ExecutionContext -//TODO: Add missing methods: markProcessAsDeployed and markProcessAsCancelled -trait ScenarioActionRepository { +// This repository should be fully replaced with ScenarioActivityRepository +// 1. At the moment, the new ScenarioActivityRepository: +// - is not aware that the underlying operation (Deployment, Cancel) may be long and may be in progress +// - it operates only on activities, that correspond to the finished, or immediate operations (finished deployments, renames, updates, migrations, etc.) +// 2. At the moment, the old ScenarioActionRepository +// - handles those activities, which underlying operations may be long and may be in progress +// 3. Eventually, the new ScenarioActivityRepository should be aware of the state of the underlying operation, and should replace this repository +trait ScenarioActionRepository extends LockableTable { + + def addInstantAction( + processId: ProcessId, + processVersion: VersionId, + actionName: ScenarioActionName, + comment: Option[Comment], + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[ProcessAction] - def markProcessAsArchived( + def addInProgressAction( + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[ProcessActionId] + + def markActionAsFinished( + actionId: ProcessActionId, processId: ProcessId, - processVersion: VersionId - )(implicit user: LoggedUser): DB[_] + actionName: ScenarioActionName, + processVersion: VersionId, + performedAt: Instant, + comment: Option[Comment], + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[Unit] - def markProcessAsUnArchived( + def markActionAsFailed( + actionId: ProcessActionId, processId: ProcessId, - processVersion: VersionId - )(implicit user: LoggedUser): DB[_] + actionName: ScenarioActionName, + processVersion: Option[VersionId], + performedAt: Instant, + comment: Option[Comment], + failureMessage: String, + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[Unit] + + def markFinishedActionAsExecutionFinished( + actionId: ProcessActionId + ): DB[Boolean] + + def removeAction(actionId: ProcessActionId, processId: ProcessId, processVersion: Option[VersionId])( + implicit user: LoggedUser + ): DB[Unit] + + def deleteInProgressActions(): DB[Unit] + + def getInProgressActionNames(processId: ProcessId): DB[Set[ScenarioActionName]] + + def getInProgressActionNames( + allowedActionNames: Set[ScenarioActionName] + ): DB[Map[ProcessId, Set[ScenarioActionName]]] def getFinishedProcessAction( actionId: ProcessActionId @@ -57,18 +110,23 @@ trait ScenarioActionRepository { } -class DbScenarioActionRepository( +class DbScenarioActionRepository private ( protected val dbRef: DbRef, buildInfos: ProcessingTypeDataProvider[Map[String, String], _] -)(implicit ec: ExecutionContext) +)(override implicit val executionContext: ExecutionContext) extends DbioRepository with NuTables + with DbLockableTable with ScenarioActionRepository with LazyLogging { import profile.api._ - def addInProgressAction( + override type ENTITY = ScenarioActivityEntityFactory#ScenarioActivityEntity + + override protected def table: TableQuery[ScenarioActivityEntityFactory#ScenarioActivityEntity] = scenarioActivityTable + + override def addInProgressAction( processId: ProcessId, actionName: ScenarioActionName, processVersion: Option[VersionId], @@ -126,7 +184,7 @@ class DbScenarioActionRepository( } // We pass all parameters here because in_progress action can be invalidated and we have to revert it back - def markActionAsFailed( + override def markActionAsFailed( actionId: ProcessActionId, processId: ProcessId, actionName: ScenarioActionName, @@ -160,7 +218,7 @@ class DbScenarioActionRepository( } yield ()) } - def markFinishedActionAsExecutionFinished(actionId: ProcessActionId): DB[Boolean] = { + override def markFinishedActionAsExecutionFinished(actionId: ProcessActionId): DB[Boolean] = { run( scenarioActivityTable .filter(a => a.activityId === activityId(actionId) && a.state === ProcessActionState.Finished) @@ -170,21 +228,15 @@ class DbScenarioActionRepository( ) } - def removeAction(actionId: ProcessActionId): DB[Unit] = { + override def removeAction( + actionId: ProcessActionId, + processId: ProcessId, + processVersion: Option[VersionId] + )(implicit user: LoggedUser): DB[Unit] = { run(scenarioActivityTable.filter(a => a.activityId === activityId(actionId)).delete.map(_ => ())) } - override def markProcessAsArchived(processId: ProcessId, processVersion: VersionId)( - implicit user: LoggedUser - ): DB[ProcessAction] = - addInstantAction(processId, processVersion, ScenarioActionName.Archive, None, None) - - override def markProcessAsUnArchived(processId: ProcessId, processVersion: VersionId)( - implicit user: LoggedUser - ): DB[ProcessAction] = - addInstantAction(processId, processVersion, ScenarioActionName.UnArchive, None, None) - - def addInstantAction( + override def addInstantAction( processId: ProcessId, processVersion: VersionId, actionName: ScenarioActionName, @@ -289,12 +341,7 @@ class DbScenarioActionRepository( } yield updateCount == 1 } - // we use "select for update where false" query syntax to lock the table - it is useful if you plan to insert something in a critical section - def lockActionsTable: DB[Unit] = { - run(scenarioActivityTable.filter(_ => false).forUpdate.result.map(_ => ())) - } - - def getInProgressActionNames(processId: ProcessId): DB[Set[ScenarioActionName]] = { + override def getInProgressActionNames(processId: ProcessId): DB[Set[ScenarioActionName]] = { val query = scenarioActivityTable .filter(action => action.scenarioId === processId && action.state === ProcessActionState.InProgress) .map(_.activityType) @@ -302,7 +349,7 @@ class DbScenarioActionRepository( run(query.result.map(_.toSet.flatMap(actionName))) } - def getInProgressActionNames( + override def getInProgressActionNames( allowedActionNames: Set[ScenarioActionName] ): DB[Map[ProcessId, Set[ScenarioActionName]]] = { val query = scenarioActivityTable @@ -345,7 +392,7 @@ class DbScenarioActionRepository( ) } - def deleteInProgressActions(): DB[Unit] = { + override def deleteInProgressActions(): DB[Unit] = { run(scenarioActivityTable.filter(_.state === ProcessActionState.InProgress).delete.map(_ => ())) } @@ -506,3 +553,15 @@ class DbScenarioActionRepository( } } + +object DbScenarioActionRepository { + + def create(dbRef: DbRef, buildInfos: ProcessingTypeDataProvider[Map[String, String], _])( + implicit executionContext: ExecutionContext, + ): ScenarioActionRepository = { + new ScenarioActionRepositoryAuditLogDecorator( + new DbScenarioActionRepository(dbRef, buildInfos) + ) + } + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepositoryAuditLogDecorator.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepositoryAuditLogDecorator.scala new file mode 100644 index 00000000000..6b9293e872f --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/ScenarioActionRepositoryAuditLogDecorator.scala @@ -0,0 +1,174 @@ +package pl.touk.nussknacker.ui.process.repository + +import db.util.DBIOActionInstances._ +import pl.touk.nussknacker.engine.api.Comment +import pl.touk.nussknacker.engine.api.deployment.ProcessActionState.ProcessActionState +import pl.touk.nussknacker.engine.api.deployment._ +import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessName, ProcessingType, VersionId} +import pl.touk.nussknacker.ui.process.ScenarioActivityAuditLog +import pl.touk.nussknacker.ui.security.api.LoggedUser +import pl.touk.nussknacker.ui.util.FunctorUtils.Ops + +import java.time.Instant +import scala.concurrent.ExecutionContext + +class ScenarioActionRepositoryAuditLogDecorator(underlying: ScenarioActionRepository)( + implicit executionContext: ExecutionContext +) extends ScenarioActionRepository { + + override def addInstantAction( + processId: ProcessId, + processVersion: VersionId, + actionName: ScenarioActionName, + comment: Option[Comment], + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[ProcessAction] = + underlying + .addInstantAction(processId, processVersion, actionName, comment, buildInfoProcessingType) + .onSuccessRunAsync(processAction => + ScenarioActivityAuditLog.onScenarioImmediateAction( + processAction.id, + processId, + actionName, + Some(processVersion), + user + ) + ) + + override def addInProgressAction( + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[ProcessActionId] = + underlying + .addInProgressAction(processId, actionName, processVersion, buildInfoProcessingType) + .onSuccessRunAsync(processActionId => + ScenarioActivityAuditLog.onScenarioActionStarted(processActionId, processId, actionName, processVersion, user) + ) + + override def markActionAsFinished( + actionId: ProcessActionId, + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: VersionId, + performedAt: Instant, + comment: Option[Comment], + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[Unit] = + underlying + .markActionAsFinished( + actionId, + processId, + actionName, + processVersion, + performedAt, + comment, + buildInfoProcessingType + ) + .onSuccessRunAsync(_ => + ScenarioActivityAuditLog + .onScenarioActionFinishedWithSuccess( + actionId, + processId, + actionName, + Some(processVersion), + comment.map(_.content), + user + ) + ) + + override def markActionAsFailed( + actionId: ProcessActionId, + processId: ProcessId, + actionName: ScenarioActionName, + processVersion: Option[VersionId], + performedAt: Instant, + comment: Option[Comment], + failureMessage: String, + buildInfoProcessingType: Option[ProcessingType] + )(implicit user: LoggedUser): DB[Unit] = + underlying + .markActionAsFailed( + actionId, + processId, + actionName, + processVersion, + performedAt, + comment, + failureMessage, + buildInfoProcessingType + ) + .onSuccessRunAsync(_ => + ScenarioActivityAuditLog + .onScenarioActionFinishedWithFailure( + actionId, + processId, + actionName, + processVersion, + comment.map(_.content), + failureMessage, + user + ) + ) + + override def removeAction(actionId: ProcessActionId, processId: ProcessId, processVersion: Option[VersionId])( + implicit user: LoggedUser + ): DB[Unit] = + underlying + .removeAction(actionId, processId, processVersion) + .onSuccessRunAsync(_ => + ScenarioActivityAuditLog + .onScenarioActionRemoved( + actionId, + processId, + processVersion, + user, + ) + ) + + override def deleteInProgressActions(): DB[Unit] = + underlying.deleteInProgressActions() + + override def markFinishedActionAsExecutionFinished( + actionId: ProcessActionId + ): DB[Boolean] = + underlying.markFinishedActionAsExecutionFinished(actionId) + + override def getInProgressActionNames(processId: ProcessId): DB[Set[ScenarioActionName]] = + underlying.getInProgressActionNames(processId) + + override def getInProgressActionNames( + allowedActionNames: Set[ScenarioActionName] + ): DB[Map[ProcessId, Set[ScenarioActionName]]] = + underlying.getInProgressActionNames(allowedActionNames) + + override def getFinishedProcessAction( + actionId: ProcessActionId + ): DB[Option[ProcessAction]] = + underlying.getFinishedProcessAction(actionId) + + override def getFinishedProcessActions( + processId: ProcessId, + actionNamesOpt: Option[Set[ScenarioActionName]] + ): DB[List[ProcessAction]] = + underlying.getFinishedProcessActions(processId, actionNamesOpt) + + override def getLastActionPerProcess( + actionState: Set[ProcessActionState], + actionNamesOpt: Option[Set[ScenarioActionName]] + ): DB[Map[ProcessId, ProcessAction]] = + underlying.getLastActionPerProcess(actionState, actionNamesOpt) + + override def getUserActionsAfter( + user: LoggedUser, + possibleActionNames: Set[ScenarioActionName], + possibleStates: Set[ProcessActionState], + limit: Instant + ): DB[List[(ProcessAction, ProcessName)]] = + underlying.getUserActionsAfter(user, possibleActionNames, possibleStates, limit) + + override def withLockedTable[T](dbioAction: DB[T]): DB[T] = + underlying.withLockedTable(dbioAction) + +} 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 84186c2b666..1f5eb84e5a3 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 @@ -6,7 +6,7 @@ import db.util.DBIOActionInstances.DB import pl.touk.nussknacker.engine.api.component.ProcessingMode 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} +import pl.touk.nussknacker.engine.api.process.ProcessId import pl.touk.nussknacker.ui.api.description.scenarioActivity.Dtos.Legacy import pl.touk.nussknacker.ui.db.entity.{ AdditionalProperties, @@ -32,7 +32,7 @@ import java.time.{Clock, Instant} import scala.concurrent.ExecutionContext import scala.util.Try -class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: Clock)( +class DbScenarioActivityRepository private (override protected val dbRef: DbRef, override val clock: Clock)( implicit executionContext: ExecutionContext, ) extends DbioRepository with NuTables @@ -53,46 +53,11 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C insertActivity(scenarioActivity).map(_.activityId) } - def modifyActivity( - activityId: ScenarioActivityId, - modification: ScenarioActivity => ScenarioActivity, - ): DB[Either[ModifyActivityError, Unit]] = { - modifyActivityByActivityId[ModifyActivityError, ScenarioActivity]( - activityId = activityId, - activityDoesNotExistError = ModifyActivityError.ActivityDoesNotExist, - validateCurrentValue = validateActivityExistsForScenario, - modify = originalActivity => toEntity(modification(originalActivity)), - couldNotModifyError = ModifyActivityError.CouldNotModifyActivity, - ) - } - - def addComment( - scenarioId: ProcessId, - processVersionId: VersionId, - comment: String, - )(implicit user: LoggedUser): DB[ScenarioActivityId] = { - val now = clock.instant() - insertActivity( - ScenarioActivity.CommentAdded( - scenarioId = ScenarioId(scenarioId.value), - scenarioActivityId = ScenarioActivityId.random, - user = user.scenarioUser, - date = now, - scenarioVersionId = Some(ScenarioVersionId.from(processVersionId)), - comment = ScenarioComment.WithContent( - comment = comment, - lastModifiedByUserName = UserName(user.username), - lastModifiedAt = now, - ) - ), - ).map(_.activityId) - } - def editComment( scenarioId: ProcessId, rowId: Long, comment: String - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] = { + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = { modifyActivityByRowId( rowId = rowId, activityDoesNotExistError = ModifyCommentError.ActivityDoesNotExist, @@ -106,7 +71,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C scenarioId: ProcessId, activityId: ScenarioActivityId, comment: String - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] = { + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = { modifyActivityByActivityId( activityId = activityId, activityDoesNotExistError = ModifyCommentError.ActivityDoesNotExist, @@ -119,7 +84,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C def deleteComment( scenarioId: ProcessId, rowId: Long, - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] = { + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = { modifyActivityByRowId( rowId = rowId, activityDoesNotExistError = ModifyCommentError.ActivityDoesNotExist, @@ -132,7 +97,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C def deleteComment( scenarioId: ProcessId, activityId: ScenarioActivityId, - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] = { + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = { modifyActivityByActivityId( activityId = activityId, activityDoesNotExistError = ModifyCommentError.ActivityDoesNotExist, @@ -386,7 +351,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C validateCurrentValue: ScenarioActivityEntityData => Either[ERROR, T], modify: T => ScenarioActivityEntityData, couldNotModifyError: ERROR, - ): DB[Either[ERROR, Unit]] = { + ): DB[Either[ERROR, ScenarioActivityId]] = { doModifyActivity[ScenarioActivityId, ERROR, T]( key = activityId, fetchActivity = activityByIdCompiled(_).result.headOption, @@ -404,7 +369,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C validateCurrentValue: ScenarioActivityEntityData => Either[ERROR, ScenarioActivityEntityData], modify: ScenarioActivityEntityData => ScenarioActivityEntityData, couldNotModifyError: ERROR, - ): DB[Either[ERROR, Unit]] = { + ): DB[Either[ERROR, ScenarioActivityId]] = { doModifyActivity[Long, ERROR, ScenarioActivityEntityData]( key = rowId, fetchActivity = activityByRowIdCompiled(_).result.headOption, @@ -424,7 +389,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C validateCurrentValue: ScenarioActivityEntityData => Either[ERROR, VALIDATED], modify: VALIDATED => ScenarioActivityEntityData, couldNotModifyError: ERROR, - ): DB[Either[ERROR, Unit]] = { + ): DB[Either[ERROR, ScenarioActivityId]] = { val action = for { fetchedActivity <- fetchActivity(key) result <- { @@ -440,7 +405,7 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C case Right(modifiedEntity) => for { rowsAffected <- updateRow(key, modifiedEntity) - res <- DBIO.successful(Either.cond(rowsAffected != 0, (), couldNotModifyError)) + res <- DBIO.successful(Either.cond(rowsAffected != 0, modifiedEntity.activityId, couldNotModifyError)) } yield res } } @@ -1000,3 +965,15 @@ class DbScenarioActivityRepository(override protected val dbRef: DbRef, clock: C private def toIntOption(str: String) = Try(str.toInt).toOption } + +object DbScenarioActivityRepository { + + def create(dbRef: DbRef, clock: Clock)( + implicit executionContext: ExecutionContext, + ): ScenarioActivityRepository = { + new ScenarioActivityRepositoryAuditLogDecorator( + new DbScenarioActivityRepository(dbRef, clock) + ) + } + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepository.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepository.scala index 1ee0534b7d7..457a1882144 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepository.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepository.scala @@ -1,20 +1,24 @@ package pl.touk.nussknacker.ui.process.repository.activities import db.util.DBIOActionInstances.DB -import pl.touk.nussknacker.engine.api.deployment.{ScenarioActivity, ScenarioActivityId} +import pl.touk.nussknacker.engine.api.deployment._ import pl.touk.nussknacker.engine.api.process.{ProcessId, VersionId} import pl.touk.nussknacker.ui.api.description.scenarioActivity.Dtos.Legacy import pl.touk.nussknacker.ui.db.entity.AttachmentEntityData import pl.touk.nussknacker.ui.process.ScenarioAttachmentService.AttachmentToAdd import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepository.{ DeleteAttachmentError, - ModifyActivityError, ModifyCommentError } import pl.touk.nussknacker.ui.security.api.LoggedUser +import pl.touk.nussknacker.ui.util.LoggedUserUtils.Ops + +import java.time.Clock trait ScenarioActivityRepository { + def clock: Clock + def findActivities( scenarioId: ProcessId, ): DB[Seq[ScenarioActivity]] @@ -23,38 +27,49 @@ trait ScenarioActivityRepository { scenarioActivity: ScenarioActivity, ): DB[ScenarioActivityId] - def modifyActivity( - activityId: ScenarioActivityId, - modification: ScenarioActivity => ScenarioActivity, - ): DB[Either[ModifyActivityError, Unit]] - def addComment( scenarioId: ProcessId, processVersionId: VersionId, - comment: String - )(implicit user: LoggedUser): DB[ScenarioActivityId] + comment: String, + )(implicit user: LoggedUser): DB[ScenarioActivityId] = { + val now = clock.instant() + addActivity( + ScenarioActivity.CommentAdded( + scenarioId = ScenarioId(scenarioId.value), + scenarioActivityId = ScenarioActivityId.random, + user = user.scenarioUser, + date = now, + scenarioVersionId = Some(ScenarioVersionId.from(processVersionId)), + comment = ScenarioComment.WithContent( + comment = comment, + lastModifiedByUserName = UserName(user.username), + lastModifiedAt = now, + ) + ), + ) + } def editComment( scenarioId: ProcessId, scenarioActivityId: ScenarioActivityId, comment: String, - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] def editComment( scenarioId: ProcessId, commentId: Long, comment: String, - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] def deleteComment( scenarioId: ProcessId, commentId: Long, - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] def deleteComment( scenarioId: ProcessId, scenarioActivityId: ScenarioActivityId - )(implicit user: LoggedUser): DB[Either[ModifyCommentError, Unit]] + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] def addAttachment( attachmentToAdd: AttachmentToAdd diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepositoryAuditLogDecorator.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepositoryAuditLogDecorator.scala new file mode 100644 index 00000000000..c1ae6ba4be1 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/process/repository/activities/ScenarioActivityRepositoryAuditLogDecorator.scala @@ -0,0 +1,106 @@ +package pl.touk.nussknacker.ui.process.repository.activities + +import cats.effect.IO +import db.util.DBIOActionInstances.{DB, dbMonad} +import pl.touk.nussknacker.engine.api.deployment._ +import pl.touk.nussknacker.engine.api.process.ProcessId +import pl.touk.nussknacker.ui.api.description.scenarioActivity.Dtos.Legacy +import pl.touk.nussknacker.ui.db.entity.AttachmentEntityData +import pl.touk.nussknacker.ui.process.ScenarioActivityAuditLog +import pl.touk.nussknacker.ui.process.ScenarioAttachmentService.AttachmentToAdd +import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepository.ModifyCommentError +import pl.touk.nussknacker.ui.security.api.LoggedUser +import pl.touk.nussknacker.ui.util.FunctorUtils._ + +import java.time.Clock +import scala.concurrent.ExecutionContext + +class ScenarioActivityRepositoryAuditLogDecorator( + underlying: ScenarioActivityRepository +)(implicit executionContext: ExecutionContext) + extends ScenarioActivityRepository { + + def clock: Clock = underlying.clock + + def addActivity( + scenarioActivity: ScenarioActivity, + ): DB[ScenarioActivityId] = + underlying + .addActivity(scenarioActivity) + .onSuccessRunAsync(_ => ScenarioActivityAuditLog.onCreateScenarioActivity(scenarioActivity)) + + def editComment( + scenarioId: ProcessId, + rowId: Long, + comment: String + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = + underlying + .editComment(scenarioId, rowId, comment) + .onSuccessRunAsync { + case Right(activityId) => ScenarioActivityAuditLog.onEditComment(scenarioId, user, activityId, comment) + case Left(_) => IO.unit + } + + def editComment( + scenarioId: ProcessId, + activityId: ScenarioActivityId, + comment: String + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = + underlying + .editComment(scenarioId, activityId, comment) + .onSuccessRunAsync { + case Right(activityId) => ScenarioActivityAuditLog.onEditComment(scenarioId, user, activityId, comment) + case Left(_) => IO.unit + } + + def deleteComment( + scenarioId: ProcessId, + rowId: Long, + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = + underlying + .deleteComment(scenarioId, rowId) + .onSuccessRunAsync(_ => ScenarioActivityAuditLog.onDeleteComment(scenarioId, rowId, user)) + + def deleteComment( + scenarioId: ProcessId, + activityId: ScenarioActivityId, + )(implicit user: LoggedUser): DB[Either[ModifyCommentError, ScenarioActivityId]] = + underlying + .deleteComment(scenarioId, activityId) + .onSuccessRunAsync(_ => ScenarioActivityAuditLog.onDeleteComment(scenarioId, activityId, user)) + + def addAttachment( + attachmentToAdd: AttachmentToAdd + )(implicit user: LoggedUser): DB[ScenarioActivityId] = + underlying + .addAttachment(attachmentToAdd) + .onSuccessRunAsync(_ => ScenarioActivityAuditLog.onAddAttachment(attachmentToAdd, user)) + + def markAttachmentAsDeleted( + scenarioId: ProcessId, + attachmentId: Long + )(implicit user: LoggedUser): DB[Either[ScenarioActivityRepository.DeleteAttachmentError, Unit]] = + underlying + .markAttachmentAsDeleted(scenarioId, attachmentId) + .onSuccessRunAsync(_ => ScenarioActivityAuditLog.onDeleteAttachment(scenarioId, attachmentId, user)) + + def findActivities( + scenarioId: ProcessId, + ): DB[Seq[ScenarioActivity]] = underlying.findActivities(scenarioId) + + def findAttachments( + scenarioId: ProcessId, + ): DB[Seq[AttachmentEntityData]] = underlying.findAttachments(scenarioId) + + def findAttachment( + scenarioId: ProcessId, + attachmentId: Long, + ): DB[Option[AttachmentEntityData]] = underlying.findAttachment(scenarioId, attachmentId) + + def findActivity( + processId: ProcessId + ): DB[Legacy.ProcessActivity] = underlying.findActivity(processId) + + def getActivityStats: DB[Map[String, Int]] = underlying.getActivityStats + +} diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala index a0c367f0e06..879c85850d0 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/server/AkkaHttpBasedRouteProvider.scala @@ -67,7 +67,7 @@ import pl.touk.nussknacker.ui.process.processingtype.ProcessingTypeData import pl.touk.nussknacker.ui.process.processingtype.loader.ProcessingTypeDataLoader import pl.touk.nussknacker.ui.process.processingtype.provider.ReloadableProcessingTypeDataProvider import pl.touk.nussknacker.ui.process.repository._ -import pl.touk.nussknacker.ui.process.repository.activities.DbScenarioActivityRepository +import pl.touk.nussknacker.ui.process.repository.activities.{DbScenarioActivityRepository, ScenarioActivityRepository} import pl.touk.nussknacker.ui.process.test.{PreliminaryScenarioTestDataSerDe, ScenarioTestService} import pl.touk.nussknacker.ui.process.version.{ScenarioGraphVersionRepository, ScenarioGraphVersionService} import pl.touk.nussknacker.ui.processreport.ProcessCounter @@ -125,7 +125,7 @@ class AkkaHttpBasedRouteProvider( actionServiceSupplier = new DelayedInitActionServiceSupplier additionalUIConfigProvider = createAdditionalUIConfigProvider(resolvedConfig, sttpBackend) deploymentRepository = new DeploymentRepository(dbRef, Clock.systemDefaultZone()) - scenarioActivityRepository = new DbScenarioActivityRepository(dbRef, designerClock) + scenarioActivityRepository = DbScenarioActivityRepository.create(dbRef, designerClock) dbioRunner = DBIOActionRunner(dbRef) processingTypeDataProvider <- prepareProcessingTypeDataReload( additionalUIConfigProvider, @@ -163,8 +163,8 @@ class AkkaHttpBasedRouteProvider( val modelBuildInfo = processingTypeDataProvider.mapValues(_.designerModelData.modelData.buildInfo) implicit val implicitDbioRunner: DBIOActionRunner = dbioRunner - val scenarioActivityRepository = new DbScenarioActivityRepository(dbRef, designerClock) - val actionRepository = new DbScenarioActionRepository(dbRef, modelBuildInfo) + val scenarioActivityRepository = DbScenarioActivityRepository.create(dbRef, designerClock) + val actionRepository = DbScenarioActionRepository.create(dbRef, modelBuildInfo) val scenarioLabelsRepository = new ScenarioLabelsRepository(dbRef) val processRepository = DBFetchingProcessRepository.create(dbRef, actionRepository, scenarioLabelsRepository) // TODO: get rid of Future based repositories - it is easier to use everywhere one implementation - DBIOAction based which allows transactions handling @@ -284,7 +284,7 @@ class AkkaHttpBasedRouteProvider( dbioRunner, futureProcessRepository, actionRepository, - writeProcessRepository + writeProcessRepository, ) val configProcessToolbarService = new ConfigScenarioToolbarService( @@ -688,7 +688,7 @@ class AkkaHttpBasedRouteProvider( private def prepareProcessingTypeDataReload( additionalUIConfigProvider: AdditionalUIConfigProvider, actionServiceProvider: Supplier[ActionService], - scenarioActivityRepository: DbScenarioActivityRepository, + scenarioActivityRepository: ScenarioActivityRepository, dbioActionRunner: DBIOActionRunner, sttpBackend: SttpBackend[Future, Any], )(implicit executionContext: ExecutionContext): Resource[IO, ReloadableProcessingTypeDataProvider] = { @@ -715,7 +715,7 @@ class AkkaHttpBasedRouteProvider( private def getDeploymentManagerDependencies( actionServiceProvider: Supplier[ActionService], - scenarioActivityRepository: DbScenarioActivityRepository, + scenarioActivityRepository: ScenarioActivityRepository, dbioActionRunner: DBIOActionRunner, sttpBackend: SttpBackend[Future, Any], processingType: ProcessingType diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/util/FunctorUtils.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/util/FunctorUtils.scala new file mode 100644 index 00000000000..716799cbef6 --- /dev/null +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/util/FunctorUtils.scala @@ -0,0 +1,23 @@ +package pl.touk.nussknacker.ui.util + +import cats.Functor +import cats.effect.IO +import cats.implicits.toFunctorOps +import pl.touk.nussknacker.engine.util.SynchronousExecutionContextAndIORuntime.syncIoRuntime + +import scala.language.higherKinds + +object FunctorUtils { + + implicit class Ops[M[_]: Functor, T](m: M[T]) { + + def onSuccessRunAsync(action: T => IO[Unit]): M[T] = { + m.map { result => + action(result).unsafeRunAndForget() + result + } + } + + } + +} 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 c9d46440ecc..7e6cfd9aab3 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 @@ -57,7 +57,7 @@ class V1_057__MigrateActionsAndCommentsToScenarioActivities private val actionInsertQuery = processActionsDefinitions.table returning processActionsDefinitions.table.map(_.id) into ((item, id) => item.copy(id = id)) - private val scenarioActivityRepository = new DbScenarioActivityRepository(testDbRef, clock) + private val scenarioActivityRepository = DbScenarioActivityRepository.create(testDbRef, clock) private val now: Timestamp = Timestamp.from(Instant.now) private val user = "John Doe" 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 d5cf3f487ce..b34270796f6 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 @@ -44,7 +44,7 @@ class V1_058__UpdateAndAddMissingScenarioActivitiesSpec private val processInsertQuery = processesTable returning processesTable.map(_.id) into ((item, id) => item.copy(id = id)) - private val scenarioActivityRepository = new DbScenarioActivityRepository(testDbRef, clock) + private val scenarioActivityRepository = DbScenarioActivityRepository.create(testDbRef, clock) private val now: Timestamp = Timestamp.from(Instant.now) private val user = "Test User" diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/NuResourcesTest.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/NuResourcesTest.scala index fbc0e211747..1858ee9265a 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/NuResourcesTest.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/base/it/NuResourcesTest.scala @@ -21,6 +21,7 @@ import pl.touk.nussknacker.engine.api.CirceUtil.humanReadablePrinter import pl.touk.nussknacker.engine.api.Comment import pl.touk.nussknacker.engine.api.deployment._ import pl.touk.nussknacker.engine.api.graph.ScenarioGraph +import pl.touk.nussknacker.engine.api.process.VersionId.initialVersionId import pl.touk.nussknacker.engine.api.process._ import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.definition.test.{ModelDataTestInfoProvider, TestInfoProvider} @@ -93,7 +94,7 @@ trait NuResourcesTest protected val fragmentRepository: DefaultFragmentRepository = newFragmentRepository(testDbRef) - protected val actionRepository: DbScenarioActionRepository = newActionProcessRepository(testDbRef) + protected val actionRepository: ScenarioActionRepository = newActionProcessRepository(testDbRef) protected val scenarioActivityRepository: ScenarioActivityRepository = newScenarioActivityRepository(testDbRef, clock) @@ -195,7 +196,7 @@ trait NuResourcesTest dbioRunner, futureFetchingScenarioRepository, actionRepository, - writeProcessRepository + writeProcessRepository, ) protected def createScenarioTestService(modelData: ModelData): ScenarioTestService = @@ -498,7 +499,7 @@ trait NuResourcesTest _ <- dbioRunner.runInTransaction( DBIOAction.seq( writeProcessRepository.archive(processId = ProcessIdWithName(id, processName), isArchived = true), - actionRepository.markProcessAsArchived(processId = id, VersionId(1)) + actionRepository.addInstantAction(id, initialVersionId, ScenarioActionName.Archive, None, None) ) ) } yield id).futureValue diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala index 18c8fbd0d19..585a8f53301 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/ScenarioHelper.scala @@ -2,9 +2,9 @@ package pl.touk.nussknacker.test.utils.domain import com.typesafe.config.{Config, ConfigObject, ConfigRenderOptions} import pl.touk.nussknacker.engine.MetaDataInitializer -import pl.touk.nussknacker.engine.api.{Comment, StreamMetaData} import pl.touk.nussknacker.engine.api.deployment.ScenarioActionName import pl.touk.nussknacker.engine.api.process.{ProcessId, ProcessIdWithName, ProcessName, VersionId} +import pl.touk.nussknacker.engine.api.{Comment, StreamMetaData} import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess import pl.touk.nussknacker.engine.management.FlinkStreamingPropertiesConfig import pl.touk.nussknacker.test.PatientScalaFutures @@ -33,17 +33,17 @@ private[test] class ScenarioHelper(dbRef: DbRef, clock: Clock, designerConfig: C private val dbioRunner: DBIOActionRunner = new DBIOActionRunner(dbRef) - private val actionRepository: DbScenarioActionRepository = new DbScenarioActionRepository( + private val actionRepository: ScenarioActionRepository = DbScenarioActionRepository.create( dbRef, mapProcessingTypeDataProvider(Map("engine-version" -> "0.1")) - ) with DbioRepository + ) private val scenarioLabelsRepository: ScenarioLabelsRepository = new ScenarioLabelsRepository(dbRef) private val writeScenarioRepository: DBProcessRepository = new DBProcessRepository( dbRef, clock, - new DbScenarioActivityRepository(dbRef, clock), + DbScenarioActivityRepository.create(dbRef, clock), scenarioLabelsRepository, mapProcessingTypeDataProvider(1) ) @@ -131,7 +131,7 @@ private[test] class ScenarioHelper(dbRef: DbRef, clock: Clock, designerConfig: C dbioRunner.runInTransaction( DBIOAction.seq( writeScenarioRepository.archive(processId = idWithName, isArchived = true), - actionRepository.markProcessAsArchived(processId = idWithName.id, version) + actionRepository.addInstantAction(idWithName.id, version, ScenarioActionName.Archive, None, None) ) ) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/TestFactory.scala b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/TestFactory.scala index 63783155046..b0c1f4c5c1c 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/TestFactory.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/test/utils/domain/TestFactory.scala @@ -37,7 +37,7 @@ import pl.touk.nussknacker.ui.process.processingtype.{ ValueWithRestriction } import pl.touk.nussknacker.ui.process.repository._ -import pl.touk.nussknacker.ui.process.repository.activities.DbScenarioActivityRepository +import pl.touk.nussknacker.ui.process.repository.activities.{DbScenarioActivityRepository, ScenarioActivityRepository} import pl.touk.nussknacker.ui.process.version.{ScenarioGraphVersionRepository, ScenarioGraphVersionService} import pl.touk.nussknacker.ui.security.api.{LoggedUser, RealLoggedUser} import pl.touk.nussknacker.ui.uiresolving.UIProcessResolver @@ -147,7 +147,7 @@ object TestFactory { def newDummyDBIOActionRunner(): DBIOActionRunner = newDBIOActionRunner(dummyDbRef) - def newScenarioActivityRepository(dbRef: DbRef, clock: Clock) = new DbScenarioActivityRepository(dbRef, clock) + def newScenarioActivityRepository(dbRef: DbRef, clock: Clock) = DbScenarioActivityRepository.create(dbRef, clock) def newScenarioLabelsRepository(dbRef: DbRef) = new ScenarioLabelsRepository(dbRef) @@ -187,12 +187,12 @@ object TestFactory { new DefaultFragmentRepository(newFutureFetchingScenarioRepository(dbRef)) def newActionProcessRepository(dbRef: DbRef) = - new DbScenarioActionRepository( + DbScenarioActionRepository.create( dbRef, mapProcessingTypeDataProvider(Streaming.stringify -> buildInfo) - ) with DbioRepository + ) - def newDummyActionRepository(): DbScenarioActionRepository = + def newDummyActionRepository(): ScenarioActionRepository = newActionProcessRepository(dummyDbRef) def newScenarioMetadataRepository(dbRef: DbRef) = new ScenarioMetadataRepository(dbRef) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/notifications/NotificationServiceTest.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/notifications/NotificationServiceTest.scala index d7238830075..931bcdf91c3 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/notifications/NotificationServiceTest.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/notifications/NotificationServiceTest.scala @@ -64,7 +64,7 @@ class NotificationServiceTest private val writeProcessRepository = TestFactory.newWriteProcessRepository(testDbRef, clock) private val actionRepository = - new DbScenarioActionRepository( + DbScenarioActionRepository.create( testDbRef, ProcessingTypeDataProvider.withEmptyCombinedData(Map.empty) ) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/ScenarioAttachmentServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/ScenarioAttachmentServiceSpec.scala index 18664e26f9f..be40651358f 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/ScenarioAttachmentServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/ScenarioAttachmentServiceSpec.scala @@ -11,11 +11,11 @@ import pl.touk.nussknacker.ui.api.description.scenarioActivity.Dtos.Legacy.Proce import pl.touk.nussknacker.ui.config.AttachmentsConfig import pl.touk.nussknacker.ui.db.entity.AttachmentEntityData import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepository -import pl.touk.nussknacker.ui.process.repository.activities.ScenarioActivityRepository.ModifyActivityError import pl.touk.nussknacker.ui.security.api.{LoggedUser, RealLoggedUser} import slick.dbio.DBIO import java.io.ByteArrayInputStream +import java.time.Clock import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} import scala.util.Random @@ -53,19 +53,12 @@ class ScenarioAttachmentServiceSpec extends AnyFunSuite with Matchers with Scala private object TestProcessActivityRepository extends ScenarioActivityRepository { + override def clock: Clock = Clock.systemUTC() + override def findActivities(scenarioId: ProcessId): DB[Seq[ScenarioActivity]] = notSupported("findActivities") override def addActivity(scenarioActivity: ScenarioActivity): DB[ScenarioActivityId] = notSupported("addActivity") - override def modifyActivity( - activityId: ScenarioActivityId, - modification: ScenarioActivity => ScenarioActivity, - ): DB[Either[ModifyActivityError, Unit]] = notSupported("modifyActivity") - - override def addComment(scenarioId: ProcessId, processVersionId: VersionId, comment: String)( - implicit user: LoggedUser - ): DB[ScenarioActivityId] = notSupported("addComment") - override def addAttachment(attachmentToAdd: ScenarioAttachmentService.AttachmentToAdd)( implicit user: LoggedUser ): DB[ScenarioActivityId] = @@ -88,19 +81,19 @@ private object TestProcessActivityRepository extends ScenarioActivityRepository override def editComment(scenarioId: ProcessId, scenarioActivityId: ScenarioActivityId, comment: String)( implicit user: LoggedUser - ): DB[Either[ScenarioActivityRepository.ModifyCommentError, Unit]] = notSupported("editComment") + ): DB[Either[ScenarioActivityRepository.ModifyCommentError, ScenarioActivityId]] = notSupported("editComment") override def editComment(scenarioId: ProcessId, commentId: Long, comment: String)( implicit user: LoggedUser - ): DB[Either[ScenarioActivityRepository.ModifyCommentError, Unit]] = notSupported("editComment") + ): DB[Either[ScenarioActivityRepository.ModifyCommentError, ScenarioActivityId]] = notSupported("editComment") override def deleteComment(scenarioId: ProcessId, commentId: Long)( implicit user: LoggedUser - ): DB[Either[ScenarioActivityRepository.ModifyCommentError, Unit]] = notSupported("deleteComment") + ): DB[Either[ScenarioActivityRepository.ModifyCommentError, ScenarioActivityId]] = notSupported("deleteComment") override def deleteComment(scenarioId: ProcessId, scenarioActivityId: ScenarioActivityId)( implicit user: LoggedUser - ): DB[Either[ScenarioActivityRepository.ModifyCommentError, Unit]] = notSupported("deleteComment") + ): DB[Either[ScenarioActivityRepository.ModifyCommentError, ScenarioActivityId]] = notSupported("deleteComment") private def notSupported(methodName: String): Nothing = throw new Exception( s"Method $methodName not supported by TestProcessActivityRepository test implementation" 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 979326d8e06..27e6eacf345 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 @@ -994,7 +994,7 @@ class DeploymentServiceSpec for { (processId, actionIdOpt) <- prepareArchivedProcess(processName, actionNameOpt) _ <- writeProcessRepository.archive(processId = processId, isArchived = false) - _ <- actionRepository.markProcessAsUnArchived(processId = processId.id, initialVersionId) + _ <- actionRepository.addInstantAction(processId.id, initialVersionId, ScenarioActionName.UnArchive, None, None) } yield (processId, actionIdOpt) private def prepareArchivedProcess( @@ -1010,7 +1010,9 @@ class DeploymentServiceSpec private def archiveProcess(processId: ProcessIdWithName): DB[_] = { writeProcessRepository .archive(processId = processId, isArchived = true) - .flatMap(_ => actionRepository.markProcessAsArchived(processId = processId.id, initialVersionId)) + .flatMap(_ => + actionRepository.addInstantAction(processId.id, initialVersionId, ScenarioActionName.Archive, None, None) + ) } private def prepareProcessesInProgress = { diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/repository/DBFetchingProcessRepositorySpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/repository/DBFetchingProcessRepositorySpec.scala index 9d6f059ef8a..09766a0410b 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/repository/DBFetchingProcessRepositorySpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/process/repository/DBFetchingProcessRepositorySpec.scala @@ -44,7 +44,7 @@ class DBFetchingProcessRepositorySpec private val dbioRunner = DBIOActionRunner(testDbRef) - private val activities = new DbScenarioActivityRepository(testDbRef, clock) + private val activities = DbScenarioActivityRepository.create(testDbRef, clock) private val scenarioLabelsRepository = new ScenarioLabelsRepository(testDbRef) @@ -62,7 +62,7 @@ class DBFetchingProcessRepositorySpec private var currentTime: Instant = Instant.now() private val actions = - new DbScenarioActionRepository( + DbScenarioActionRepository.create( testDbRef, ProcessingTypeDataProvider.withEmptyCombinedData(Map.empty) ) diff --git a/docs/MigrationGuide.md b/docs/MigrationGuide.md index 4d9e185ad5a..81fc0ddfaa5 100644 --- a/docs/MigrationGuide.md +++ b/docs/MigrationGuide.md @@ -10,6 +10,22 @@ To see the biggest differences please consult the [changelog](Changelog.md). * Button name for 'test adhoc' was renamed from `test-with-form` to `adhoc-testing` If you are using custom button config remember to update button type to `type: "adhoc-testing"` in `processToolbarConfig` +* [7039](https://github.com/TouK/nussknacker/pull/7039) + * Scenario Activity audit log is available + * logger name, `scenario-activity-audit`, it is optional, does not have to be configured + * it uses MDC context, example of configuration in `logback.xml`: + ``` + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [scenarioId=%X{scenarioId}][version=%X{scenarioVersionId}][user=%X{username}] %msg%n + + + ``` + ### Code API changes * [#6971](https://github.com/TouK/nussknacker/pull/6971) `DeploymentManagerDependencies` API changes: diff --git a/utils/test-utils/src/main/resources/logback-test.xml b/utils/test-utils/src/main/resources/logback-test.xml index 2407b450eb6..6211e9153da 100644 --- a/utils/test-utils/src/main/resources/logback-test.xml +++ b/utils/test-utils/src/main/resources/logback-test.xml @@ -7,6 +7,12 @@ + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [scenarioId=%X{scenarioId}][version=%X{scenarioVersionId}][user=%X{username}] %msg%n + + + @@ -65,4 +71,8 @@ + + + +