Skip to content

Commit

Permalink
Tapir endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
gskrobisz committed Jan 16, 2025
1 parent 8e164b2 commit cf58d86
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.circe.generic.JsonCodec
import io.circe.generic.extras.semiauto.{deriveConfiguredDecoder, deriveConfiguredEncoder}
import io.circe.{Decoder, Encoder}
import pl.touk.nussknacker.engine.api.component.{ComponentGroupName, ComponentId}
import pl.touk.nussknacker.engine.api.definition.ParameterEditor
import pl.touk.nussknacker.engine.api.definition.{ParameterEditor, RawParameterEditor}
import pl.touk.nussknacker.engine.api.typed.typing.TypingResult
import pl.touk.nussknacker.engine.graph.EdgeType
import pl.touk.nussknacker.engine.graph.evaluatedparam.{Parameter => NodeParameter}
Expand Down Expand Up @@ -140,6 +140,10 @@ package object definition {
hintText: Option[String]
)

object UiActionParameterConfig {
def empty: UiActionParameterConfig = UiActionParameterConfig(None, RawParameterEditor, None, None)
}

object UIParameter {
implicit def decoder(implicit typing: Decoder[TypingResult]): Decoder[UIParameter] =
deriveConfiguredDecoder[UIParameter]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package pl.touk.nussknacker.ui.api

import com.typesafe.scalalogging.LazyLogging
import pl.touk.nussknacker.engine.api.process.ProcessName
import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions
import pl.touk.nussknacker.ui.api.ActionInfoHttpService.ActionInfoError
import pl.touk.nussknacker.ui.api.ActionInfoHttpService.ActionInfoError.{NoPermission, NoScenario}
import pl.touk.nussknacker.ui.api.BaseHttpService.CustomAuthorizationError
import pl.touk.nussknacker.ui.api.description.ActionInfoEndpoints
import pl.touk.nussknacker.ui.api.utils.ScenarioHttpServiceExtensions
import pl.touk.nussknacker.ui.process.ProcessService
import pl.touk.nussknacker.ui.process.newactivity.ActionInfoService
import pl.touk.nussknacker.ui.process.processingtype.provider.ProcessingTypeDataProvider
import pl.touk.nussknacker.ui.security.api.AuthManager
import sttp.tapir.{Codec, CodecFormat}

import scala.concurrent.ExecutionContext

class ActionInfoHttpService(
authManager: AuthManager,
processingTypeToActionInfoService: ProcessingTypeDataProvider[ActionInfoService, _],
protected override val scenarioService: ProcessService
)(implicit val executionContext: ExecutionContext)
extends BaseHttpService(authManager)
with ScenarioHttpServiceExtensions
with LazyLogging {

override protected type BusinessErrorType = ActionInfoError
override protected def noScenarioError(scenarioName: ProcessName): ActionInfoError = NoScenario(scenarioName)
override protected def noPermissionError: ActionInfoError with CustomAuthorizationError = NoPermission

private val securityInput = authManager.authenticationEndpointInput()

private val endpoints = new ActionInfoEndpoints(securityInput)

expose {
endpoints.actionParametersEndpoint
.serverSecurityLogic(authorizeKnownUser[ActionInfoError])
.serverLogicEitherT { implicit loggedUser => actionParametersInput =>
val (scenarioName, scenarioGraph) = actionParametersInput
for {
scenarioWithDetails <- getScenarioWithDetailsByName(scenarioName)
actionInfoService = processingTypeToActionInfoService.forProcessingTypeUnsafe(
scenarioWithDetails.processingType
)
actionParameters = actionInfoService.getActionParameters(
scenarioGraph,
scenarioWithDetails.processVersionUnsafe,
scenarioWithDetails.isFragment
)
} yield actionParameters
}
}

}

object ActionInfoHttpService {

sealed trait ActionInfoError

object ActionInfoError {
final case class NoScenario(scenarioName: ProcessName) extends ActionInfoError
final case object NoPermission extends ActionInfoError with CustomAuthorizationError

implicit val noScenarioCodec: Codec[String, NoScenario, CodecFormat.TextPlain] = {
BaseEndpointDefinitions.toTextPlainCodecSerializationOnly[NoScenario](e => s"No scenario ${e.scenarioName} found")
}

}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package pl.touk.nussknacker.ui.api.description

import pl.touk.nussknacker.engine.api.deployment.ScenarioActionName
import pl.touk.nussknacker.engine.api.{NodeId, StreamMetaData}
import pl.touk.nussknacker.engine.api.graph.{ProcessProperties, ScenarioGraph}
import pl.touk.nussknacker.engine.api.process.ProcessName
import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions
import pl.touk.nussknacker.restmodel.BaseEndpointDefinitions.SecuredEndpoint
import pl.touk.nussknacker.restmodel.definition.UiActionParameterConfig
import pl.touk.nussknacker.security.AuthCredentials
import pl.touk.nussknacker.ui.api.ActionInfoHttpService.ActionInfoError
import pl.touk.nussknacker.ui.api.ActionInfoHttpService.ActionInfoError.NoScenario
import pl.touk.nussknacker.ui.api.TapirCodecs.ScenarioNameCodec._
import pl.touk.nussknacker.ui.api.TapirCodecs.ScenarioGraphCodec._
import pl.touk.nussknacker.ui.api.description.ActionInfoEndpoints.Examples.noScenarioExample
import pl.touk.nussknacker.ui.api.description.ActionInfoEndpoints._
import pl.touk.nussknacker.ui.process.newactivity.ActionInfoService.{UiActionNodeParameters, UiActionParameters}
import sttp.model.StatusCode.{NotFound, Ok}
import sttp.tapir.EndpointIO.Example
import sttp.tapir._
import sttp.tapir.json.circe.jsonBody

class ActionInfoEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseEndpointDefinitions {

lazy val actionParametersEndpoint
: SecuredEndpoint[(ProcessName, ScenarioGraph), ActionInfoError, UiActionParameters, Any] =
baseNuApiEndpoint
.summary("Get action parameters")
.tag("Deployments")
.post
.in("actionInfo" / path[ProcessName]("scenarioName") / "actionParameters")
.in(
jsonBody[ScenarioGraph]
.example(simpleGraphExample)
)
.out(
statusCode(Ok).and(
jsonBody[UiActionParameters]
.examples(
List(
Example.of(
summary = Some("Valid action parameters for given scenario"),
value = Map(
ScenarioActionName.Deploy -> List(
UiActionNodeParameters(
NodeId("sample node id"),
Map("param name" -> UiActionParameterConfig.empty)
)
)
)
)
)
)
)
)
.errorOut(
oneOf[ActionInfoError](
noScenarioExample
)
)
.withSecurity(auth)

private val simpleGraphExample: Example[ScenarioGraph] = Example.of(
ScenarioGraph(
ProcessProperties(StreamMetaData()),
List(),
List(),
)
)

}

object ActionInfoEndpoints {

implicit val uiActionParametersSchema: Schema[UiActionParameters] = Schema.anyObject

object Examples {

val noScenarioExample: EndpointOutput.OneOfVariant[NoScenario] =
oneOfVariantFromMatchType(
NotFound,
plainBody[NoScenario]
.example(
Example.of(
summary = Some("No scenario {scenarioName} found"),
value = NoScenario(ProcessName("'example scenario'"))
)
)
)

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import pl.touk.nussknacker.engine.api.graph.ScenarioGraph
import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess
import pl.touk.nussknacker.engine.definition.activity.ActionInfoProvider
import pl.touk.nussknacker.restmodel.definition.UiActionParameterConfig
import pl.touk.nussknacker.ui.process.newactivity.ActionInfoService.UiActionNodeParameters
import pl.touk.nussknacker.ui.process.newactivity.ActionInfoService.{UiActionNodeParameters, UiActionParameters}
import pl.touk.nussknacker.ui.security.api.LoggedUser
import pl.touk.nussknacker.ui.uiresolving.UIProcessResolver

Expand All @@ -21,12 +21,12 @@ class ActionInfoService(activityInfoProvider: ActionInfoProvider, processResolve
isFragment: Boolean
)(
implicit user: LoggedUser
): Map[String, List[UiActionNodeParameters]] = {
): UiActionParameters = {
val canonical = toCanonicalProcess(scenarioGraph, processVersion, isFragment)
activityInfoProvider
.getActionParameters(processVersion, canonical)
.map { case (scenarioActionName, nodeParamsMap) =>
scenarioActionName.value -> nodeParamsMap.map { case (nodeId, params) =>
scenarioActionName -> nodeParamsMap.map { case (nodeId, params) =>
UiActionNodeParameters(
nodeId,
params.map { case (name, value) =>
Expand Down Expand Up @@ -55,4 +55,5 @@ class ActionInfoService(activityInfoProvider: ActionInfoProvider, processResolve

object ActionInfoService {
@JsonCodec case class UiActionNodeParameters(nodeId: NodeId, parameters: Map[String, UiActionParameterConfig])
type UiActionParameters = Map[ScenarioActionName, List[UiActionNodeParameters]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ class AkkaHttpBasedRouteProvider(
new ScenarioTestExecutorServiceImpl(scenarioResolver, deploymentManager)
)
}
val scenarioActivityService = scenarioTestServiceDeps.mapValues { case (_, processResolver, _, modelData, _) =>
val actionInfoService = scenarioTestServiceDeps.mapValues { case (_, processResolver, _, modelData, _) =>
new ActionInfoService(
new ModelDataActionInfoProvider(modelData),
processResolver
Expand Down Expand Up @@ -418,6 +418,12 @@ class AkkaHttpBasedRouteProvider(
scenarioService = processService,
)

val actionInfoHttpService = new ActionInfoHttpService(
authManager = authManager,
processingTypeToActionInfoService = actionInfoService,
scenarioService = processService,
)

val stickyNotesApiHttpService = new StickyNotesApiHttpService(
authManager = authManager,
stickyNotesRepository = stickyNotesRepository,
Expand Down Expand Up @@ -528,7 +534,6 @@ class AkkaHttpBasedRouteProvider(
)
}
),
new ActionInfoResources(processService, scenarioActivityService),
new StatusResources(stateDefinitionService),
)

Expand Down Expand Up @@ -616,6 +621,7 @@ class AkkaHttpBasedRouteProvider(
migrationApiHttpService,
nodesApiHttpService,
testingApiHttpService,
actionInfoHttpService,
notificationApiHttpService,
scenarioActivityApiHttpService,
scenarioLabelsApiHttpService,
Expand Down
Loading

0 comments on commit cf58d86

Please sign in to comment.