From 736683da24a782f4c426543a3221047724e7c0ed Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Mon, 5 Oct 2026 15:23:35 +0200 Subject: [PATCH 1/3] Add persistent workflow history console and simplify fulfillment flow --- order-fulfillment-workflow/.gitignore | 1 + order-fulfillment-workflow/README.md | 313 ++- .../docker-compose.yaml | 7 +- order-fulfillment-workflow/pom.xml | 32 +- .../InitiatingPaymentForCustomerStarted.java | 2 +- .../config/WorkflowHistoryConfig.java | 52 + .../controller/WorkflowHistoryController.java | 63 + .../JdbcWorkflowHistoryRepository.java | 301 +++ .../history/WorkflowHistoryUpdates.java | 74 + .../service/FailureRecorder.java | 29 - .../service/ShippingService.java | 3 +- .../simulator/AutoPaymentRobot.java | 10 +- .../workflow/OrderFulfillmentWorkflow.java | 184 +- .../src/main/resources/application.yaml | 9 +- .../src/main/resources/static/index.html | 18 +- .../resources/static/order-fulfillment.bpmn | 235 ++ .../resources/static/vendor/bpmn-js/LICENSE | 23 + .../resources/static/vendor/bpmn-js/README.md | 12 + .../static/vendor/bpmn-js/bpmn-js.css | 151 ++ .../bpmn-navigated-viewer.production.min.js | 22 + .../static/vendor/bpmn-js/diagram-js.css | 1325 +++++++++++ .../main/resources/static/workflow-bpmn.mjs | 160 ++ .../main/resources/static/workflow-trace.mjs | 165 ++ .../src/main/resources/static/workflows.css | 2090 +++++++++++++++++ .../src/main/resources/static/workflows.html | 312 +++ .../src/main/resources/static/workflows.js | 924 ++++++++ .../AbstractOrderFulfillmentTest.java | 106 + .../JdbcWorkflowHistoryRepositoryTest.java | 188 ++ .../OrderFulfillmentApplicationTest.java | 39 + .../orderfulfillment/OrderFulfillmentIT.java | 33 +- .../OrderFulfillmentWorkflowTest.java | 175 ++ .../src/test/js/workflow-trace.test.mjs | 239 ++ 32 files changed, 7083 insertions(+), 214 deletions(-) create mode 100644 order-fulfillment-workflow/.gitignore create mode 100644 order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java create mode 100644 order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/controller/WorkflowHistoryController.java create mode 100644 order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java create mode 100644 order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/WorkflowHistoryUpdates.java delete mode 100644 order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/FailureRecorder.java create mode 100644 order-fulfillment-workflow/src/main/resources/static/order-fulfillment.bpmn create mode 100644 order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/LICENSE create mode 100644 order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/README.md create mode 100644 order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-js.css create mode 100644 order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-navigated-viewer.production.min.js create mode 100644 order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/diagram-js.css create mode 100644 order-fulfillment-workflow/src/main/resources/static/workflow-bpmn.mjs create mode 100644 order-fulfillment-workflow/src/main/resources/static/workflow-trace.mjs create mode 100644 order-fulfillment-workflow/src/main/resources/static/workflows.css create mode 100644 order-fulfillment-workflow/src/main/resources/static/workflows.html create mode 100644 order-fulfillment-workflow/src/main/resources/static/workflows.js create mode 100644 order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java create mode 100644 order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/JdbcWorkflowHistoryRepositoryTest.java create mode 100644 order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentApplicationTest.java create mode 100644 order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentWorkflowTest.java create mode 100644 order-fulfillment-workflow/src/test/js/workflow-trace.test.mjs diff --git a/order-fulfillment-workflow/.gitignore b/order-fulfillment-workflow/.gitignore new file mode 100644 index 00000000..d0718296 --- /dev/null +++ b/order-fulfillment-workflow/.gitignore @@ -0,0 +1 @@ +.data/ diff --git a/order-fulfillment-workflow/README.md b/order-fulfillment-workflow/README.md index d7619a84..b43a3188 100644 --- a/order-fulfillment-workflow/README.md +++ b/order-fulfillment-workflow/README.md @@ -1,95 +1,298 @@ # Order Fulfillment Workflow -Sample project showing how to model an order fulfillment process using the -**Axoniq Workflow Engine**. The workflow is written as plain imperative Java — -the engine handles event sourcing, crash recovery, and audit trails. +An order fulfillment demo using **Axoniq Workflows** and Spring Boot 4. +The workflow reserves stock, initiates payment, waits for confirmation, ships the +order, and notifies the customer. The live map shows shipments moving between +cities over server-sent events. A second UI at `/workflows.html` inspects persisted +workflow executions, step state, and the complete workflow event timeline. + +## Dependencies + +This sample uses Axoniq Framework artifacts from Maven Central: + +- `io.axoniq.framework:axoniq-workflow-dsl` provides the Java DSL and workflow engine. +- `io.axoniq.framework:axoniq-spring-boot-starter` provides workflow infrastructure. + This sample registers its `WorkflowModule` explicitly to configure history storage. + The former workflow-specific Spring Boot artifact is no longer used. +- `io.axoniq.framework:axoniq-workflow-test` provides the workflow test fixture. +- Axon Framework and Axoniq Framework versions are pinned in [`pom.xml`](pom.xml). + +Java 21+, Maven 3.9+, and Docker are required to run the complete demo. + +## Workflow API + +The workflow uses imports under `io.axoniq.framework.workflow`. Its entry point +reads as the order process, with step implementation in private methods below it: ```java -@Workflow(idProperty = "orderId", startOnEvent = "io.axoniq.demo.orderfulfillment.api.OrderPlaced") +@Workflow(idProperty = "orderId", + startOnEventClass = OrderPlaced.class, + workflowName = "OrderFulfillmentWorkflow") public void execute(SimpleWorkflowContext ctx) { - // Register the wait FIRST, so the workflow is subscribed before any payment-confirmation - // event has a chance to land. - var paymentConfirmation = ctx.waitForEvent("awaitPayment", PaymentConfirmed.class, - associate(payloadProperty("orderId"), equalsTo(orderId)), Duration.ofMinutes(15)); - - var reserved = ctx.awaitExecute("reserveStock", payload, Boolean.class, inventory::reserveStock); - if (!reserved) { - paymentConfirmation.cancel("Stock unavailable"); - ctx.fail(new RuntimeException("Stock unavailable")); + var paymentConfirmation = registerPaymentWait(ctx); + + if (!reserveStock(ctx)) { + rejectOutOfStock(ctx, paymentConfirmation); return; } - ctx.awaitExecute("initiatePayment", payload, payment::initiatePayment, - Duration.ofSeconds(30), - baseName("InitiatingPaymentForCustomer").namespace("io.axoniq.demo.orderfulfillment.api")); - - paymentConfirmation.await(); - // The Completed event of `shipOrder` (`ShipOrderCompleted`, in the api namespace) is what the - // projection subscribes to — no eventGateway.publish anywhere inside the workflow. - var shipResult = ctx.awaitExecute("shipOrder", payload, shipping::shipOrder, - Duration.ofSeconds(30), namespace("io.axoniq.demo.orderfulfillment.api")); - ctx.awaitExecute("notifyCustomer", Boolean.class, () -> { notifications.sendConfirmation(email); return true; }); + awaitPayment(ctx, paymentConfirmation); + var trackingNumber = shipOrder(ctx); + notifyCustomer(ctx, trackingNumber); } ``` -## Prerequisites +[`OrderFulfillmentWorkflow`](src/main/java/io/axoniq/demo/orderfulfillment/workflow/OrderFulfillmentWorkflow.java) +keeps payload mapping, timeouts, and failure handling in those methods. Payment +and shipping share a `serviceStep` helper for their timeout and event namespace. +Their event names and payloads remain stable for the payment robot, shipment +listeners, and UI. All step IDs remain stable for history and replay. + +The payment wait is registered without blocking, before any action that could +trigger a response. The `awaitPayment` helper initiates payment with nonblocking +`execute`, joins both results with `allMatch(WorkflowStepResult::success, ...)`, +and returns only after both succeed. Either can finish first. A failure or timeout +ends the join, cancels any unfinished companion step, and records the failure. +Its association helpers come from +`runtime.association.Associations.associate` and +`dsl.api.EventAssociationsUtils.equalsTo` / `payloadProperty` within the workflow +package. + +`awaitExecute` with an input payload returns the action's result map. The +three-argument `awaitExecute(name, resultType, supplier)` convenience is used for +customer notification. + +The engine's `InitiatingPaymentForCustomerStarted` and `ShipOrderCompleted` +events drive the projection, SSE stream, and simulators. Payment's Started event +includes the scenario, so the robot can skip forced timeouts without depending on +projection timing. Shipping generates its tracking number inside the execute +action and returns it in the recorded result; replay and customer notification +therefore use the same number. -- Java 21+ -- Maven 3.9+ -- Docker (for Axon Server) -- The Axoniq Workflow Engine (`io.axoniq.framework.workflow:*:0.1.0`), available on Maven Central +Failures use `awaitPublish` to publish `OrderFailed` as a durable workflow step, +then `ctx.fail(...)` to terminate the workflow. A completed publish is not +repeated during replay. -## Running the application +## Run + +From this directory: ```bash docker compose up -d mvn spring-boot:run ``` -The application starts on port **9090**. +Open [the demo UI](http://localhost:9090). Axon Server is available at +[localhost:8024](http://localhost:8024). +Compose uses a dedicated project name to keep its data +separate from older versions of this demo. It persists Axon Server's data, events, +and replication log so container recreation retains all three. +Open the [workflow console](http://localhost:9090/workflows.html) directly or follow +the link in the map header. Its waterfall shows actual step intervals, overlapping +event waits, and failures. Select a span for its inputs, output, and lifecycle +events; open the event journal for the full payload and metadata. -### REST endpoints +For a live walkthrough, choose **Interactive payment** and **Run workflow**. Watch +the waiting span grow, then click **Confirm payment** to see shipping and +notification complete. The wait has a 45-second deadline. **Automatic payment**, +**Payment timeout**, and **Out of stock** show the other paths. -| Method | Path | Description | -|--------|-------------------------------|---------------------------------------------| -| POST | `/orders?customerId=&email=&amount=` | Place a new order. Returns the order id. | -| POST | `/orders/{orderId}/payment` | Confirm payment and resume the workflow. | -| GET | `/orders/{orderId}` | Read the order's projected status. | +Switch between **Waterfall**, **BPMN**, and **Event journal**. Use **+ / − / Fit** +to zoom the waterfall or BPMN diagram. **Replay trace** reconstructs state one +recorded event at a time, with enough playback time to see even short actions. +The slider and **Previous / Next event** controls move through the same event +cursor. Every view, including the inspector, shows only the state known at that +cursor: future steps are pending, results appear upon completion, and rewinding +removes them. Source timestamps still determine actual durations. Replay executes +no workflow actions. The +**Live** button pauses or resumes incoming updates. At narrower widths, the step +inspector opens as a drawer; close it with **×** or **Escape**. -### Quick demo +The BPMN view uses the locally bundled [bpmn-js viewer](https://bpmn.io/toolkit/bpmn-js/walkthrough/) +and [`order-fulfillment.bpmn`](src/main/resources/static/order-fulfillment.bpmn). +This illustrative BPMN 2.0 model is authored alongside the Java workflow; it is +not generated by the framework or used to execute the workflow. It includes the +parallel payment wait, stock decision, deadline, and failure paths. State colors +come from the same history cursor as the waterfall. Unvisited branches become +**Not taken** once the execution finishes. The XML can be downloaded from the view. -```bash -# 1. place an order -ORDER=$(curl -s -X POST 'http://localhost:9090/orders?customerId=alice&email=alice@example.com&amount=99.95') +The delivery map uses standard OpenStreetMap tiles without an API key and keeps +visible attribution and normal browser caching. Only the basemap is tinted for +the dark theme; route and truck colors are unchanged. See the +[tile usage policy](https://operations.osmfoundation.org/policies/tiles/) for hosting details. + +Use a fresh demo event store when switching from the old `0.1.0` sample. This +sample changes the recorded workflow steps and does not migrate existing workflow +histories. The map's order projection and its processor tokens remain in memory; +the truck and payment simulators are demo components. Workflow history and its +processor token are stored in embedded, file-backed H2. + +## Extending the framework history component + +Start with [`WorkflowHistoryConfig`](src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java). +The sample keeps the framework's standard `WorkflowHistoryProjector` and configures +its storage and processor through two module extension points: + +```java +WorkflowModule.defaults("OrderFulfillment", SimpleWorkflowContext.class) + .definition(definitions -> definitions.autodetected(configuration -> workflow)) + .withHistory(configuration -> { + history.initialize(configuration.getComponent(UnitOfWorkFactory.class)); + return history; + }) + .historyProcessorConfiguration(processor -> processor + .tokenStore(historyTokens) + .initialSegmentCount(1) + .batchSize(1) + .withInterceptor(history)); +``` + +`axon.workflow.enabled=false` disables automatic workflow module registration; +Spring registers the explicit module bean instead. It still autodetects the +workflow definition's `@Workflow` method. + +[`JdbcWorkflowHistoryRepository`](src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java) +implements `MutableWorkflowHistoryRepository`. Its processor interceptor provides +the current event to `save(WorkflowHistory)`, because that repository method only +receives the resulting state. It stores the event and a UI document in one H2 +transaction. It uses no business `@EventHandler` methods and does not subclass the +projector. -# 2. confirm payment -curl -X POST "http://localhost:9090/orders/$ORDER/payment" +The event journal has two purposes: it supplies the inspection timeline, and it +restores the framework's actual state for further projection after a restart. The +projector requires `EventSourcedWorkflowState`; it cannot continue +from an arbitrary UI DTO. The repository reconstructs that state by replaying its +stored events through a fresh standard projector and a temporary in-memory repository. +This applies recorded state changes only; it does not execute workflow actions. -# 3. observe the projected status +The durable data consists of: + +| H2 table | Contents | +|---|---| +| `workflow_history` | Latest workflow status, payload, version, and step state for the UI. | +| `workflow_history_event` | Workflow event IDs, type/version, time, payload, and metadata, in recorded order. | +| `TokenEntry` | The history processor's durable progress and segment claim. | + +The demo processes history one event per batch. Event and state writes commit +before the processor advances its token. They are separate transactions, so an +interrupted batch may be delivered again. Unique +event IDs and duplicate checks make that redelivery a no-op. A failed history +write rolls back both the event and UI state. The original workflow events and +execution recovery remain in Axon Server; H2 is the inspection read model. + +### Live history updates + +`WorkflowHistoryConfig` supplies `updates::publish` to the repository. An +`afterCommit` callback sends the workflow ID to +[`WorkflowHistoryUpdates`](src/main/java/io/axoniq/demo/orderfulfillment/history/WorkflowHistoryUpdates.java). +This component coalesces IDs and broadcasts SSE invalidations at +`/api/workflows/stream`. It observes committed repository writes, using no additional +business event handlers. Rolled-back writes and duplicate deliveries send nothing. +Network sends run off the projection thread. + +The browser reloads the persisted state and incrementally reads new journal events +after an invalidation. On reconnect it catches up from the same journal; SSE is +only a notification channel, not a second history store. Heartbeats maintain the +connection, with a 30-second reconciliation fetch as a fallback. Running spans +animate against the server clock between updates; completed spans use their +recorded end time. Events without a recorded start appear as diamonds, not invented +durations. Waiting-time totals count overlapping intervals once. + +**Extension scope:** the mutable history repository and projector are marked `@Internal` +in the released framework. This is a version-specific extension example, not a +framework-provided JDBC implementation. The interceptor relies on the synchronous +history projector and checks that assumption. Reconstructing state reads all recorded +events for one workflow on each update; this is suitable for short demo workflows. +The framework query API uses its query matcher over reconstructed states, while +the UI reads persisted documents directly with SQL filtering and pagination. + +## Persistence and restart demo + +The default database is `.data/workflow-history.mv.db`, relative to the process's +working directory. Start the sample from this directory to keep its location +consistent. Override `spring.datasource.url` to select another file. No separate +database container is required. + +1. Run a few scenarios at `/workflows.html`, including a failure. +2. Stop the application, keeping Axon Server and `.data` intact. +3. Start the application from the same directory and revisit `/workflows.html`. +4. The execution list and timelines are still available; the history processor + resumes from its stored token. New events continue updating the same history. + +The timeline shows workflow lifecycle and step events. The independent truck +location feed remains on the live map; workflow completion precedes truck delivery. +Keep Axon Server's data and the H2 file together. When resetting this demo to an +empty event store, also remove `.data` while the application is stopped. + +## REST endpoints + +| Method | Path | Description | +|---|---|---| +| POST | `/orders?customerId=&email=&amount=&scenario=` | Place an order; returns its ID. | +| POST | `/orders/{orderId}/payment` | Manually confirm payment. | +| GET | `/orders/{orderId}` | Read the projected order status. | +| GET | `/orders/stream` | Subscribe to the initial snapshot and live SSE updates. | +| GET | `/api/workflows?search=&status=&limit=50&offset=0` | Page through persisted executions; search matches workflow ID. | +| GET | `/api/workflows/stream` | SSE notifications after history commits; reconnect and read the journal to catch up. | +| GET | `/api/workflows/{workflowId}` | Read persisted workflow and step state. | +| GET | `/api/workflows/{workflowId}/events?after=0&limit=200` | Read the workflow timeline; `after` is the last returned sequence. | +| POST | `/simulate/single?scenario=` | Place a random order. | +| POST | `/simulate/burst?count=10&scenario=` | Place a burst of random orders. | +| POST | `/simulate/scenario?type=` | Run a selected scenario. | + +The default `happy` scenario automatically confirms payment and progresses +through `PLACED`, `AWAITING_PAYMENT`, `IN_TRANSIT`, and `DELIVERED`. +`out-of-stock` fails before payment initiation. `payment-timeout` disables the +payment robot for that order and fails after four seconds; other orders have a +45-second payment deadline. `manual-payment` also disables the robot and waits for +an explicit payment confirmation from the console or REST endpoint. + +```bash +# Place an order; the payment robot confirms it automatically. +ORDER=$(curl -s -X POST 'http://localhost:9090/orders?customerId=alice&email=alice@example.com&amount=99.95') curl "http://localhost:9090/orders/$ORDER" + +# Exercise the two failure paths. +curl -X POST 'http://localhost:9090/simulate/scenario?type=out-of-stock' +curl -X POST 'http://localhost:9090/simulate/scenario?type=payment-timeout' ``` -## Integration test +The workflow completes after shipping and notification. Truck movement and final +delivery are simulated separately, so `DELIVERED` appears a few seconds later. + +## Tests ```bash +# Workflow fixture and Spring application tests; no Docker required. +mvn test + +# Also run the application against an Axon Server Testcontainer. mvn verify -``` -`OrderFulfillmentIT` boots the application against a real Axon Server (started -via Testcontainers), places an order, confirms payment, and asserts that the -projection reaches the `SHIPPED` state. +# Waterfall timing, state replay, and BPMN branch mapping (Node.js 20+). +node --test src/test/js/workflow-trace.test.mjs +``` -## What this sample demonstrates +`OrderFulfillmentWorkflowTest` uses the released `WorkflowTestFixture` and a +manual clock to verify normal fulfillment, payment arriving before initiation, +correlation to the correct order, timeout, and stock failure. +`OrderFulfillmentApplicationTest` checks automatic delivery and the projected +reasons for both failure scenarios through the HTTP API with an in-memory event +store, including the history endpoints, scenario timelines, and an interactive +payment that remains suspended until explicitly confirmed. Tests use isolated +databases and never touch the demo's `.data` directory. `OrderFulfillmentIT` runs +the same HTTP scenarios against Axon Server. -* `@Workflow` with `idProperty` and `startOnEvent` -* `awaitExecute` with payload and typed return value -* `awaitExecute` with timeout and event-name customization (`baseName(...)`) -* `awaitEvent` correlated to the workflow instance via `associate(payloadProperty(...), equalsTo(...))` -* `ctx.fail(...)` to terminate a workflow with an error -* A standard `@EventHandler` projection consuming events emitted by the workflow's actions +`JdbcWorkflowHistoryRepositoryTest` closes and reopens a real H2 file, continues an +unfinished projection, checks failure/retry/version state, verifies duplicate +delivery is harmless, and verifies rollback and retry after a failed projection. +It also verifies that live notifications happen only after successful commits. ## Cleanup ```bash +# Removes this demo's containers and stored events. docker compose down -v +# With the application stopped, also remove its history and processor token. +rm -rf .data ``` diff --git a/order-fulfillment-workflow/docker-compose.yaml b/order-fulfillment-workflow/docker-compose.yaml index 9b4ed015..b288ac11 100644 --- a/order-fulfillment-workflow/docker-compose.yaml +++ b/order-fulfillment-workflow/docker-compose.yaml @@ -1,7 +1,9 @@ +name: order-fulfillment-workflow-rc1 + services: axon-server: image: docker.axoniq.io/axoniq/axonserver:2026.0.0 - container_name: order-fulfillment-axon-server + container_name: order-fulfillment-rc1-axon-server ports: - "8024:8024" - "8124:8124" @@ -12,9 +14,12 @@ services: volumes: - data:/axonserver/data - events:/axonserver/events + - log:/axonserver/log volumes: data: driver: local events: driver: local + log: + driver: local diff --git a/order-fulfillment-workflow/pom.xml b/order-fulfillment-workflow/pom.xml index ce4ed52e..6782b4f6 100644 --- a/order-fulfillment-workflow/pom.xml +++ b/order-fulfillment-workflow/pom.xml @@ -12,13 +12,11 @@ 21 - 21 - 21 + ${java.version} UTF-8 - 5.3.2 - 5.3.2 - 0.2.0 + 5.4.0-RC1 + 5.4.0-RC1 4.1.1 2.0.5 @@ -57,11 +55,15 @@ - + io.axoniq.framework - axoniq-workflow-spring-boot - ${axon-workflow.version} + axoniq-workflow-dsl + ${axoniq-framework.version} + + + io.axoniq.framework + axoniq-spring-boot-starter @@ -83,12 +85,21 @@ com.fasterxml.jackson.core jackson-databind + + org.springframework.boot + spring-boot-starter-jdbc + + + com.h2database + h2 + runtime + io.axoniq.framework axoniq-workflow-test - ${axon-workflow.version} + ${axoniq-framework.version} test @@ -140,8 +151,7 @@ maven-compiler-plugin 3.16.0 - ${java.version} - ${java.version} + ${java.version} true diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/api/InitiatingPaymentForCustomerStarted.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/api/InitiatingPaymentForCustomerStarted.java index 3c6fc569..c2b77035 100644 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/api/InitiatingPaymentForCustomerStarted.java +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/api/InitiatingPaymentForCustomerStarted.java @@ -6,5 +6,5 @@ * already registered its {@code awaitPayment} wait (the wait is registered at the very top * of the workflow, before any {@code awaitExecute} step runs). */ -public record InitiatingPaymentForCustomerStarted(String orderId) { +public record InitiatingPaymentForCustomerStarted(String orderId, String scenario) { } diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java new file mode 100644 index 00000000..e9cb5af3 --- /dev/null +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java @@ -0,0 +1,52 @@ +package io.axoniq.demo.orderfulfillment.config; + +import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository; +import io.axoniq.demo.orderfulfillment.history.WorkflowHistoryUpdates; +import io.axoniq.demo.orderfulfillment.workflow.OrderFulfillmentWorkflow; +import io.axoniq.framework.workflow.configuration.WorkflowModule; +import io.axoniq.framework.workflow.dsl.simple.SimpleWorkflowContext; +import org.axonframework.conversion.GeneralConverter; +import org.axonframework.messaging.core.unitofwork.UnitOfWorkFactory; +import org.axonframework.messaging.core.unitofwork.transaction.jdbc.JdbcTransactionalExecutorProvider; +import org.axonframework.messaging.eventhandling.conversion.EventConverter; +import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.GenericTokenTableFactory; +import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.JdbcTokenStore; +import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.JdbcTokenStoreConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +/** The two framework extension points demonstrated by this sample. */ +@Configuration(proxyBeanMethods = false) +public class WorkflowHistoryConfig { + + @Bean + JdbcWorkflowHistoryRepository workflowHistoryRepository(DataSource dataSource, EventConverter converter, + WorkflowHistoryUpdates updates) { + return new JdbcWorkflowHistoryRepository(dataSource, converter, updates::publish); + } + + @Bean + WorkflowModule orderFulfillmentModule( + OrderFulfillmentWorkflow workflow, JdbcWorkflowHistoryRepository history, + DataSource dataSource, GeneralConverter converter) { + // Only the history processor uses durable tokens. The map projection and demo + // simulators retain their existing in-memory token store. + var historyTokens = new JdbcTokenStore(new JdbcTransactionalExecutorProvider(dataSource), + converter, JdbcTokenStoreConfiguration.DEFAULT); + historyTokens.createSchema(GenericTokenTableFactory.INSTANCE); + + return WorkflowModule.defaults("OrderFulfillment", SimpleWorkflowContext.class) + .definition(definitions -> definitions.autodetected(configuration -> workflow)) + .withHistory(configuration -> { + history.initialize(configuration.getComponent(UnitOfWorkFactory.class)); + return history; + }) + .historyProcessorConfiguration(processor -> processor + .tokenStore(historyTokens) + .initialSegmentCount(1) + .batchSize(1) + .withInterceptor(history)); + } +} diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/controller/WorkflowHistoryController.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/controller/WorkflowHistoryController.java new file mode 100644 index 00000000..f50a9b96 --- /dev/null +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/controller/WorkflowHistoryController.java @@ -0,0 +1,63 @@ +package io.axoniq.demo.orderfulfillment.controller; + +import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository; +import io.axoniq.demo.orderfulfillment.history.WorkflowHistoryUpdates; +import io.axoniq.framework.workflow.dsl.api.WorkflowStatus; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/workflows") +public class WorkflowHistoryController { + + private final JdbcWorkflowHistoryRepository history; + private final WorkflowHistoryUpdates updates; + + public WorkflowHistoryController(JdbcWorkflowHistoryRepository history, WorkflowHistoryUpdates updates) { + this.history = history; + this.updates = updates; + } + + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter stream() { + return updates.subscribe(); + } + + @GetMapping + public Map list(@RequestParam(defaultValue = "") String search, + @RequestParam(required = false) WorkflowStatus status, + @RequestParam(defaultValue = "50") int limit, + @RequestParam(defaultValue = "0") int offset) { + if (limit < 1 || limit > 200 || offset < 0) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Use limit 1–200 and offset >= 0"); + } + return history.list(search, status == null ? "" : status.name(), limit, offset); + } + + @GetMapping("/{workflowId}") + public Object detail(@PathVariable String workflowId) { + return history.detail(workflowId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Workflow not found")); + } + + @GetMapping("/{workflowId}/events") + public List> events(@PathVariable String workflowId, + @RequestParam(defaultValue = "0") long after, + @RequestParam(defaultValue = "200") int limit) { + if (after < 0 || limit < 1 || limit > 500) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Use limit 1–500 and after >= 0"); + } + detail(workflowId); + return history.timeline(workflowId, after, limit); + } +} diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java new file mode 100644 index 00000000..851a5273 --- /dev/null +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java @@ -0,0 +1,301 @@ +package io.axoniq.demo.orderfulfillment.history; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import io.axoniq.framework.workflow.dsl.api.WorkflowState; +import io.axoniq.framework.workflow.history.api.WorkflowHistory; +import io.axoniq.framework.workflow.history.inmemory.InMemoryWorkflowHistoryRepository; +import io.axoniq.framework.workflow.history.inmemory.MutableWorkflowHistoryRepository; +import io.axoniq.framework.workflow.history.inmemory.WorkflowHistoryProjector; +import io.axoniq.framework.workflow.query.api.WorkflowStateQuery; +import io.axoniq.framework.workflow.query.utils.WorkflowStateQueryMatcher; +import io.axoniq.framework.workflow.runtime.util.MetadataUtils; +import org.axonframework.messaging.core.MessageHandlerInterceptor; +import org.axonframework.messaging.core.MessageHandlerInterceptorChain; +import org.axonframework.messaging.core.MessageStream; +import org.axonframework.messaging.core.Metadata; +import org.axonframework.messaging.core.MessageType; +import org.axonframework.messaging.core.unitofwork.ProcessingContext; +import org.axonframework.messaging.core.unitofwork.UnitOfWorkFactory; +import org.axonframework.messaging.eventhandling.EventMessage; +import org.axonframework.messaging.eventhandling.GenericEventMessage; +import org.axonframework.messaging.eventhandling.conversion.EventConverter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Consumer; + +/** + * Persists workflow events and the latest workflow state for the history console. + * + * Each event and its resulting state are stored in a single database transaction. + * The console reads the stored state and event journal to display workflow progress. + * Stored events also reconstruct the framework's workflow state so history + * projection can continue after an application restart. + */ +public class JdbcWorkflowHistoryRepository + implements MutableWorkflowHistoryRepository, MessageHandlerInterceptor { + + private final JdbcTemplate jdbc; + private final TransactionTemplate transactions; + private final EventConverter converter; + private final Consumer onCommitted; + private final ObjectMapper json = JsonMapper.builder().build(); + private final ThreadLocal projection = new ThreadLocal<>(); + private UnitOfWorkFactory unitOfWorkFactory; + + public JdbcWorkflowHistoryRepository(DataSource dataSource, EventConverter converter) { + this(dataSource, converter, id -> { }); + } + + public JdbcWorkflowHistoryRepository(DataSource dataSource, EventConverter converter, + Consumer onCommitted) { + this.jdbc = new JdbcTemplate(dataSource); + this.transactions = new TransactionTemplate(new DataSourceTransactionManager(dataSource)); + this.converter = converter; + this.onCommitted = onCommitted; + jdbc.execute(""" + CREATE TABLE IF NOT EXISTS workflow_history ( + workflow_id VARCHAR(255) PRIMARY KEY, + status VARCHAR(40) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + document CLOB NOT NULL + ) + """); + jdbc.execute(""" + CREATE TABLE IF NOT EXISTS workflow_history_event ( + sequence BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + event_id VARCHAR(255) NOT NULL UNIQUE, + workflow_id VARCHAR(255) NOT NULL, + event_name VARCHAR(1024) NOT NULL, + event_version VARCHAR(255) NOT NULL, + occurred_at VARCHAR(40) NOT NULL, + payload BLOB NOT NULL, + metadata CLOB NOT NULL + ) + """); + jdbc.execute("CREATE INDEX IF NOT EXISTS history_event_workflow ON workflow_history_event(workflow_id, sequence)"); + } + + public void initialize(UnitOfWorkFactory unitOfWorkFactory) { + this.unitOfWorkFactory = unitOfWorkFactory; + } + + @Override + public MessageStream interceptOnHandle(EventMessage event, ProcessingContext context, + MessageHandlerInterceptorChain chain) { + if (!MetadataUtils.hasWorkflowId().test(event.metadata())) { + return chain.proceed(event, context); + } + return transactions.execute(transaction -> { + // History can commit before the processor's token. Redelivery must be a no-op. + if (Boolean.TRUE.equals(jdbc.queryForObject( + "SELECT EXISTS(SELECT 1 FROM workflow_history_event WHERE event_id = ?)", + Boolean.class, event.identifier()))) { + return MessageStream.empty(); + } + projection.set(new Projection(event, context)); + try { + var result = chain.proceed(event, context); + // The standard projector performs findById/evolve/save synchronously. + // Fail explicitly if a future framework version changes that contract. + if (!result.isCompleted()) { + throw new IllegalStateException("History adapter requires a synchronous history projector"); + } + if (result.error().isPresent()) { + throw new IllegalStateException("History projection failed", result.error().get()); + } + return result; + } finally { + projection.remove(); + } + }); + } + + @Override + public void save(WorkflowHistory history) { + var active = projection.get(); + if (active == null) { + throw new IllegalStateException("Configure this repository as the history processor interceptor too"); + } + var event = active.event(); + if (!history.workflowId().equals(MetadataUtils.getWorkflowId(event.metadata()))) { + throw new IllegalArgumentException("History and event workflow IDs differ"); + } + jdbc.update(""" + INSERT INTO workflow_history_event + (event_id, workflow_id, event_name, event_version, occurred_at, payload, metadata) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, event.identifier(), history.workflowId(), event.type().qualifiedName().toString(), + event.type().version(), event.timestamp().toString(), event.payloadAs(byte[].class), + write(event.metadata())); + jdbc.update(""" + MERGE INTO workflow_history (workflow_id, status, updated_at, document) + KEY (workflow_id) VALUES (?, ?, ?, ?) + """, history.workflowId(), history.state().workflowStatus().name(), + OffsetDateTime.ofInstant(event.timestamp(), ZoneOffset.UTC), write(document(history.state(), event))); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + onCommitted.accept(history.workflowId()); + } + }); + } + + @Override + public CompletableFuture> findById(String workflowId) { + return withContext(context -> restore(workflowId, context)); + } + + @Override + public CompletableFuture> findAll(WorkflowStateQuery query) { + // For this small demo, evaluate the framework's complete query semantics in Java. + // The UI uses the indexed SQL list below and does not replay every workflow. + return withContext(context -> jdbc.queryForList("SELECT workflow_id FROM workflow_history", String.class) + .stream().map(id -> restore(id, context)).flatMap(Optional::stream) + .filter(history -> WorkflowStateQueryMatcher.matches(query, history.state())).toList()); + } + + private Optional restore(String workflowId, ProcessingContext context) { + var memory = new InMemoryWorkflowHistoryRepository(); + var projector = new WorkflowHistoryProjector(memory); + jdbc.query("SELECT * FROM workflow_history_event WHERE workflow_id = ? ORDER BY sequence", + (org.springframework.jdbc.core.RowCallbackHandler) row -> projector.handle(readEvent(row), context), + workflowId); + return memory.findById(workflowId).join(); + } + + private CompletableFuture withContext(Function operation) { + var active = projection.get(); + if (active != null) { + return CompletableFuture.completedFuture(operation.apply(active.context())); + } + if (unitOfWorkFactory == null) { + return CompletableFuture.failedFuture(new IllegalStateException("History repository is not initialized")); + } + return unitOfWorkFactory.create().executeWithResult( + context -> CompletableFuture.completedFuture(operation.apply(context))); + } + + @Override + public void clear() { + transactions.executeWithoutResult(transaction -> { + jdbc.update("DELETE FROM workflow_history_event"); + jdbc.update("DELETE FROM workflow_history"); + }); + } + + public Map list(String search, String status, int limit, int offset) { + var pattern = "%" + search.toLowerCase(java.util.Locale.ROOT) + .replace("!", "!!").replace("%", "!%").replace("_", "!_") + "%"; + var where = " WHERE LOWER(workflow_id) LIKE ? ESCAPE '!' AND (? = '' OR status = ?)"; + var items = jdbc.query("SELECT document FROM workflow_history" + where + + " ORDER BY updated_at DESC, workflow_id LIMIT ? OFFSET ?", + (row, index) -> read(row.getString(1)), pattern, status, status, limit, offset); + var total = jdbc.queryForObject("SELECT COUNT(*) FROM workflow_history" + where, + Long.class, pattern, status, status); + return Map.of("items", items, "total", total, "limit", limit, "offset", offset, "storage", "H2 file"); + } + + public Optional detail(String workflowId) { + return jdbc.query("SELECT document FROM workflow_history WHERE workflow_id = ?", + (row, index) -> read(row.getString(1)), workflowId).stream().findFirst(); + } + + public List> timeline(String workflowId, long after, int limit) { + return jdbc.query(""" + SELECT * FROM workflow_history_event + WHERE workflow_id = ? AND sequence > ? ORDER BY sequence LIMIT ? + """, (row, index) -> { + var event = readEvent(row); + Map item = new LinkedHashMap<>(); + item.put("sequence", row.getLong("sequence")); + item.put("eventId", event.identifier()); + item.put("type", event.type().qualifiedName().toString()); + item.put("version", event.type().version()); + item.put("timestamp", event.timestamp().toString()); + item.put("step", MetadataUtils.getStepStatus(event.metadata()).isPresent() + ? MetadataUtils.getStepName(event.metadata()) : null); + item.put("status", MetadataUtils.getStepStatus(event.metadata()).map(Enum::name) + .orElseGet(() -> MetadataUtils.getWorkflowStatus(event.metadata()).map(Enum::name).orElse("EVENT"))); + item.put("payload", read(new String(row.getBytes("payload"), java.nio.charset.StandardCharsets.UTF_8))); + item.put("metadata", event.metadata()); + return item; + }, workflowId, after, limit); + } + + private Map document(WorkflowState state, EventMessage event) { + Map document = new LinkedHashMap<>(); + document.put("workflowId", state.workflowId()); + document.put("definition", state.workflowDefinitionId().qualifiedName().toString()); + document.put("version", state.workflowDefinitionId().version()); + document.put("status", state.workflowStatus().name()); + document.put("updatedAt", event.timestamp().toString()); + document.put("startedAt", jdbc.queryForObject(""" + SELECT occurred_at FROM workflow_history_event WHERE workflow_id = ? ORDER BY sequence LIMIT 1 + """, String.class, state.workflowId())); + document.put("payload", state.payload()); + document.put("versionMigrations", state.versionMigrations()); + document.put("steps", state.workflowStepNames().stream().map(name -> { + var step = state.getStep(name); + Map entry = new LinkedHashMap<>(); + entry.put("name", name); + entry.put("status", step.status().name()); + entry.put("timestamp", step.timestamp().toString()); + entry.put("result", step.result()); + entry.put("error", step.error() == null ? null : step.error().toString()); + return entry; + }).toList()); + return document; + } + + private EventMessage readEvent(ResultSet row) throws SQLException { + try { + Map metadata = json.readValue(row.getString("metadata"), new TypeReference<>() { }); + return new GenericEventMessage(row.getString("event_id"), + new MessageType(row.getString("event_name"), row.getString("event_version")), + // Materialize JSON first: payloadAs(Object.class) otherwise returns the + // byte[] unchanged, leaving reconstructed step results as binary data. + read(new String(row.getBytes("payload"), java.nio.charset.StandardCharsets.UTF_8)), + Metadata.from(metadata), Instant.parse(row.getString("occurred_at"))) + .withConverter(converter); + } catch (JacksonException e) { + throw new IllegalStateException("Cannot read stored event metadata", e); + } + } + + private String write(Object value) { + try { + return json.writeValueAsString(value); + } catch (JacksonException e) { + throw new IllegalStateException("Cannot encode workflow history", e); + } + } + + private Object read(String value) { + try { + return json.readValue(value, Object.class); + } catch (JacksonException e) { + throw new IllegalStateException("Cannot decode workflow history", e); + } + } + + private record Projection(EventMessage event, ProcessingContext context) { } +} diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/WorkflowHistoryUpdates.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/WorkflowHistoryUpdates.java new file mode 100644 index 00000000..74f190d0 --- /dev/null +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/WorkflowHistoryUpdates.java @@ -0,0 +1,74 @@ +package io.axoniq.demo.orderfulfillment.history; + +import jakarta.annotation.PreDestroy; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** Notifications invalidate browser caches; the database remains the source of truth. */ +@Component +public class WorkflowHistoryUpdates { + private final Set clients = ConcurrentHashMap.newKeySet(); + private final Set pending = ConcurrentHashMap.newKeySet(); + private final ScheduledExecutorService sender = Executors.newSingleThreadScheduledExecutor(task -> { + var thread = new Thread(task, "workflow-history-stream"); + thread.setDaemon(true); + return thread; + }); + + public WorkflowHistoryUpdates() { + sender.scheduleWithFixedDelay(this::flush, 100, 100, TimeUnit.MILLISECONDS); + sender.scheduleWithFixedDelay(() -> clients.forEach(client -> send(client, "heartbeat", clock())), + 15, 15, TimeUnit.SECONDS); + } + + public SseEmitter subscribe() { + var client = new SseEmitter(0L); + clients.add(client); + client.onCompletion(() -> clients.remove(client)); + client.onTimeout(() -> clients.remove(client)); + client.onError(error -> clients.remove(client)); + send(client, "ready", clock()); + return client; + } + + /** Called after commit. Coalescing avoids blocking the projector on slow browsers. */ + public void publish(String workflowId) { + pending.add(workflowId); + } + + private void flush() { + for (String id : pending) { + if (pending.remove(id)) { + clients.forEach(client -> send(client, "workflow", Map.of("workflowId", id))); + } + } + } + + private Map clock() { + return Map.of("serverTime", System.currentTimeMillis()); + } + + private void send(SseEmitter client, String event, Object data) { + try { + client.send(SseEmitter.event().name(event).data(data)); + } catch (IOException | IllegalStateException error) { + clients.remove(client); + client.complete(); + } + } + + @PreDestroy + void close() { + sender.shutdownNow(); + clients.forEach(SseEmitter::complete); + clients.clear(); + } +} diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/FailureRecorder.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/FailureRecorder.java deleted file mode 100644 index fe0a74fb..00000000 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/FailureRecorder.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.axoniq.demo.orderfulfillment.service; - -import io.axoniq.demo.orderfulfillment.api.OrderFailed; -import org.axonframework.messaging.eventhandling.gateway.EventGateway; -import org.springframework.stereotype.Component; - -import java.util.Map; - -/** - * Lets the workflow publish an {@link OrderFailed} event from inside an {@code awaitExecute} - * step, so failure broadcasts go through the regular event pipeline instead of being side-effects - * of the workflow body. - */ -@Component -public class FailureRecorder { - - private final EventGateway eventGateway; - - public FailureRecorder(EventGateway eventGateway) { - this.eventGateway = eventGateway; - } - - public Boolean record(Map payload) { - var orderId = (String) payload.get("orderId"); - var reason = (String) payload.get("reason"); - eventGateway.publish(null, new OrderFailed(orderId, reason)); - return true; - } -} diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/ShippingService.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/ShippingService.java index 4fdfc15f..02fe619a 100644 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/ShippingService.java +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/service/ShippingService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Component; import java.util.Map; +import java.util.UUID; @Component public class ShippingService { @@ -13,7 +14,7 @@ public class ShippingService { public Map shipOrder(Map payload) { var orderId = (String) payload.get("orderId"); - var trackingNumber = (String) payload.get("trackingNumber"); + var trackingNumber = "TRK-" + UUID.randomUUID(); logger.info("Shipping order {} with tracking number {}.", orderId, trackingNumber); return Map.of( "orderId", orderId, diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/simulator/AutoPaymentRobot.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/simulator/AutoPaymentRobot.java index bbf7696a..a4d1ebdd 100644 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/simulator/AutoPaymentRobot.java +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/simulator/AutoPaymentRobot.java @@ -19,8 +19,8 @@ /** * Auto-confirms payments after a randomized delay so the demo runs end-to-end without manual - * clicks. Skips orders flagged with the {@code payment-timeout} scenario, letting the workflow's - * waitForEvent expire on purpose. + * clicks. Skips payment-timeout and manual-payment scenarios so the wait can expire + * or be completed explicitly from the execution console. */ @Component public class AutoPaymentRobot { @@ -45,10 +45,8 @@ public AutoPaymentRobot(EventGateway eventGateway, OrderStatusProjection project @EventHandler public void on(InitiatingPaymentForCustomerStarted event) { var orderId = event.orderId(); - var status = projection.findById(orderId).orElse(null); - var scenario = status == null ? null : status.scenario(); - if ("payment-timeout".equals(scenario)) { - logger.info("Skipping auto-payment for {} — scenario forces timeout.", orderId); + if ("payment-timeout".equals(event.scenario()) || "manual-payment".equals(event.scenario())) { + logger.info("Skipping auto-payment for {} — scenario {}.", orderId, event.scenario()); return; } var delayMs = ThreadLocalRandom.current().nextInt(800, 3500); diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/workflow/OrderFulfillmentWorkflow.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/workflow/OrderFulfillmentWorkflow.java index fff9ac24..f849fb0b 100644 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/workflow/OrderFulfillmentWorkflow.java +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/workflow/OrderFulfillmentWorkflow.java @@ -1,28 +1,28 @@ package io.axoniq.demo.orderfulfillment.workflow; +import io.axoniq.demo.orderfulfillment.api.OrderFailed; import io.axoniq.demo.orderfulfillment.api.OrderPlaced; import io.axoniq.demo.orderfulfillment.api.PaymentConfirmed; -import io.axoniq.demo.orderfulfillment.service.FailureRecorder; import io.axoniq.demo.orderfulfillment.service.InventoryService; import io.axoniq.demo.orderfulfillment.service.NotificationService; import io.axoniq.demo.orderfulfillment.service.PaymentService; import io.axoniq.demo.orderfulfillment.service.ShippingService; -import io.axoniq.workflow.dsl.simple.SimpleWorkflowContext; -import io.axoniq.workflow.runtime.api.annotation.Workflow; +import io.axoniq.framework.workflow.annotation.Workflow; +import io.axoniq.framework.workflow.dsl.api.CombinatorWorkflowStepResult; +import io.axoniq.framework.workflow.dsl.api.ExecuteStepDefinition; +import io.axoniq.framework.workflow.dsl.api.WorkflowStepResult; +import io.axoniq.framework.workflow.dsl.simple.SimpleWorkflowContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.time.Duration; -import java.util.HashMap; import java.util.Map; -import java.util.UUID; -import static io.axoniq.workflow.dsl.api.EventAssociationsUtils.equalsTo; -import static io.axoniq.workflow.dsl.api.EventAssociationsUtils.payloadProperty; -import static io.axoniq.workflow.runtime.association.Associations.associate; -import static io.axoniq.workflow.runtime.execution.DefaultEventNameCustomizer.Builder.baseName; -import static io.axoniq.workflow.runtime.execution.DefaultEventNameCustomizer.Builder.namespace; +import static io.axoniq.framework.workflow.dsl.api.EventAssociationsUtils.equalsTo; +import static io.axoniq.framework.workflow.dsl.api.EventAssociationsUtils.payloadProperty; +import static io.axoniq.framework.workflow.runtime.association.Associations.associate; +import static io.axoniq.framework.workflow.runtime.execution.DefaultEventNameCustomizer.Builder.baseName; @Component public class OrderFulfillmentWorkflow { @@ -33,18 +33,15 @@ public class OrderFulfillmentWorkflow { private final PaymentService payment; private final ShippingService shipping; private final NotificationService notifications; - private final FailureRecorder failures; public OrderFulfillmentWorkflow(InventoryService inventory, - PaymentService payment, - ShippingService shipping, - NotificationService notifications, - FailureRecorder failures) { + PaymentService payment, + ShippingService shipping, + NotificationService notifications) { this.inventory = inventory; this.payment = payment; this.shipping = shipping; this.notifications = notifications; - this.failures = failures; } @Workflow( @@ -53,96 +50,131 @@ public OrderFulfillmentWorkflow(InventoryService inventory, workflowName = "OrderFulfillmentWorkflow" ) public void execute(SimpleWorkflowContext ctx) { - var orderId = (String) ctx.workflowPayload().get("orderId"); - var customerId = (String) ctx.workflowPayload().get("customerId"); - var email = (String) ctx.workflowPayload().get("email"); - var amount = ctx.workflowPayload().get("amount"); - var scenario = (String) ctx.workflowPayload().get("scenario"); - var originLat = ((Number) ctx.workflowPayload().get("originLat")).doubleValue(); - var originLng = ((Number) ctx.workflowPayload().get("originLng")).doubleValue(); - var destLat = ((Number) ctx.workflowPayload().get("destinationLat")).doubleValue(); - var destLng = ((Number) ctx.workflowPayload().get("destinationLng")).doubleValue(); + var paymentConfirmation = registerPaymentWait(ctx); + + if (!reserveStock(ctx)) { + rejectOutOfStock(ctx, paymentConfirmation); + return; + } + + awaitPayment(ctx, paymentConfirmation); + var trackingNumber = shipOrder(ctx); + notifyCustomer(ctx, trackingNumber); + } + private WorkflowStepResult registerPaymentWait(SimpleWorkflowContext ctx) { + var order = ctx.workflowPayload(); logger.info("Order {} workflow started for customer {} (amount {}, scenario {}).", - orderId, customerId, amount, scenario); + order.get("orderId"), order.get("customerId"), order.get("amount"), order.get("scenario")); - var paymentTimeout = "payment-timeout".equals(scenario) + var paymentTimeout = "payment-timeout".equals(order.get("scenario")) ? Duration.ofSeconds(4) : Duration.ofSeconds(45); - var paymentConfirmation = ctx.waitForEvent( + // Register before any action that could trigger a payment response. + return ctx.waitForEvent( "awaitPayment", PaymentConfirmed.class, - associate(payloadProperty("orderId"), equalsTo(orderId)), + associate(payloadProperty("orderId"), equalsTo(order.get("orderId"))), step -> step.timeout(paymentTimeout) ); + } - var reserved = (Boolean) ctx.awaitExecute( + private boolean reserveStock(SimpleWorkflowContext ctx) { + var order = ctx.workflowPayload(); + var reservation = ctx.awaitExecute( "reserveStock", - Map.of("customerId", customerId, "amount", amount, "scenario", scenario), - (pc, p) -> Map.of("result", inventory.reserveStock(p)) - ).get("result"); - if (!reserved) { - paymentConfirmation.cancel("Stock unavailable"); - ctx.awaitExecute( - "recordOutOfStock", - Map.of("orderId", orderId, "reason", "Out of stock"), - (pc, p) -> Map.of("result", failures.record(p)) - ); - ctx.fail(new RuntimeException("Stock unavailable for order " + orderId)); - return; + Map.of("customerId", order.get("customerId"), + "amount", order.get("amount"), "scenario", order.get("scenario")), + (pc, input) -> Map.of("reserved", inventory.reserveStock(input)) + ); + return (boolean) reservation.get("reserved"); + } + + private void awaitPayment(SimpleWorkflowContext ctx, WorkflowStepResult paymentConfirmation) { + var paymentInitiation = initiatePayment(ctx); + var payment = ctx.allMatch(WorkflowStepResult::success, paymentConfirmation, paymentInitiation); + payment.await(); + if (!payment.success()) { + rejectPayment(ctx, payment); } + } - ctx.awaitExecute( + private WorkflowStepResult initiatePayment(SimpleWorkflowContext ctx) { + var order = ctx.workflowPayload(); + return ctx.execute( "initiatePayment", - Map.of("orderId", orderId, "customerId", customerId, "amount", amount), + Map.of("orderId", order.get("orderId"), "customerId", order.get("customerId"), + "amount", order.get("amount"), "scenario", order.get("scenario")), (pc, p) -> { payment.initiatePayment(p); return Map.of(); }, - def -> def.timeout(Duration.ofSeconds(30)) - .eventNameCustomizer(baseName("InitiatingPaymentForCustomer") - .namespace("io.axoniq.demo.orderfulfillment.api")) + step -> serviceStep(step, "InitiatingPaymentForCustomer") ); + } - paymentConfirmation.await(); - var confirmation = paymentConfirmation.result(); - if (confirmation.isEmpty()) { - ctx.awaitExecute( - "recordPaymentTimeout", - Map.of("orderId", orderId, "reason", "Payment timed out"), - (pc, p) -> Map.of("result", failures.record(p)) - ); - ctx.fail(new RuntimeException("Payment timed out for order " + orderId)); - return; + private void rejectPayment(SimpleWorkflowContext ctx, CombinatorWorkflowStepResult payment) { + var orderId = (String) ctx.workflowPayload().get("orderId"); + var unsuccessful = payment.unmatched().getFirst(); + var reason = paymentFailureReason(unsuccessful); + payment.unmatched().stream() + .filter(step -> !step.isCompleted()) + .forEach(step -> step.cancel(reason)); + + var failureStep = "awaitPayment".equals(unsuccessful.getStepName()) && unsuccessful.timeout() + ? "recordPaymentTimeout" : "recordPaymentFailure"; + ctx.awaitPublish(failureStep, new OrderFailed(orderId, reason)); + ctx.fail(new RuntimeException(reason + " for order " + orderId)); + } + + private String paymentFailureReason(WorkflowStepResult step) { + if ("initiatePayment".equals(step.getStepName())) { + if (step.timeout()) { + return "Payment initiation timed out"; + } + return step.canceled() ? "Payment initiation cancelled" : "Payment initiation failed"; } - var transactionId = (String) confirmation.get().get("transactionId"); - logger.info("Order {} payment confirmed (transaction {}).", orderId, transactionId); - - // The Completed event of this step is `ShipOrderCompleted` — that is the durable signal - // the truck-movement simulator subscribes to. The trackingNumber is generated up front so - // both the Started and Completed events expose it. - var trackingNumber = "TRK-" + UUID.randomUUID(); - var shipPayload = new HashMap(); - shipPayload.put("orderId", orderId); - shipPayload.put("trackingNumber", trackingNumber); - shipPayload.put("originLat", originLat); - shipPayload.put("originLng", originLng); - shipPayload.put("destinationLat", destLat); - shipPayload.put("destinationLng", destLng); - ctx.awaitExecute( + if (step.timeout()) { + return "Payment timed out"; + } + return step.canceled() ? "Payment wait cancelled" : "Payment confirmation failed"; + } + + private String shipOrder(SimpleWorkflowContext ctx) { + var order = ctx.workflowPayload(); + // Create the tracking number inside the action so replay uses its recorded result. + var shipment = ctx.awaitExecute( "shipOrder", - shipPayload, + Map.of("orderId", order.get("orderId"), + "originLat", ((Number) order.get("originLat")).doubleValue(), + "originLng", ((Number) order.get("originLng")).doubleValue(), + "destinationLat", ((Number) order.get("destinationLat")).doubleValue(), + "destinationLng", ((Number) order.get("destinationLng")).doubleValue()), (pc, p) -> shipping.shipOrder(p), - def -> def.timeout(Duration.ofSeconds(30)) - .eventNameCustomizer(namespace("io.axoniq.demo.orderfulfillment.api")) + step -> serviceStep(step, "ShipOrder") ); + return (String) shipment.get("trackingNumber"); + } + private void notifyCustomer(SimpleWorkflowContext ctx, String trackingNumber) { + var email = (String) ctx.workflowPayload().get("email"); ctx.awaitExecute("notifyCustomer", Boolean.class, () -> { notifications.sendConfirmation(email, trackingNumber); return true; }); + logger.info("Order {} workflow completed (tracking {}).", ctx.workflowPayload().get("orderId"), trackingNumber); + } + + private void rejectOutOfStock(SimpleWorkflowContext ctx, WorkflowStepResult paymentConfirmation) { + var orderId = (String) ctx.workflowPayload().get("orderId"); + paymentConfirmation.cancel("Stock unavailable"); + ctx.awaitPublish("recordOutOfStock", new OrderFailed(orderId, "Out of stock")); + ctx.fail(new RuntimeException("Stock unavailable for order " + orderId)); + } - logger.info("Order {} workflow completed (tracking {}).", orderId, trackingNumber); + private static ExecuteStepDefinition serviceStep(ExecuteStepDefinition step, String eventBaseName) { + return step.timeout(Duration.ofSeconds(30)) + .eventNameCustomizer(baseName(eventBaseName).namespace(OrderPlaced.class.getPackageName())); } } diff --git a/order-fulfillment-workflow/src/main/resources/application.yaml b/order-fulfillment-workflow/src/main/resources/application.yaml index d51d395f..bffda2e3 100644 --- a/order-fulfillment-workflow/src/main/resources/application.yaml +++ b/order-fulfillment-workflow/src/main/resources/application.yaml @@ -4,7 +4,14 @@ server: spring: application: name: order-fulfillment-workflow + datasource: + url: jdbc:h2:file:./.data/workflow-history;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: axon: - axon-server: + workflow: + # WorkflowHistoryConfig declares the module and its history storage explicitly. + enabled: false + axonserver: enabled: true diff --git a/order-fulfillment-workflow/src/main/resources/static/index.html b/order-fulfillment-workflow/src/main/resources/static/index.html index 28f7b3b3..d218f9d7 100644 --- a/order-fulfillment-workflow/src/main/resources/static/index.html +++ b/order-fulfillment-workflow/src/main/resources/static/index.html @@ -91,6 +91,8 @@ .stat.total .value { color: var(--fg); } #map { width: 100%; height: 100%; background: #0a1020; } + /* Keep the demo's dark map without tinting routes, markers, or attribution. */ + #map .leaflet-tile-pane { filter: grayscale(1) invert(1) brightness(.75) contrast(.9); } .activity { display: flex; flex-direction: column; gap: 0.4rem; } .activity .row { @@ -123,11 +125,20 @@ .leaflet-container { background: #0a1020; } .leaflet-control-attribution { background: rgba(13,17,23,0.8) !important; color: var(--muted) !important; } .leaflet-control-attribution a { color: var(--awaiting) !important; } + @media (max-width: 1200px) { + body { grid-template-columns: 240px minmax(0, 1fr) 260px; } + } + @media (max-width: 1100px) { + body { grid-template-columns: 220px minmax(0, 1fr); } + aside.right { display: none; } + header h1 { font-size: .85rem; } + }

Order Fulfillment — Live Simulator

+ Workflow history → connecting…
@@ -182,9 +193,10 @@

Activity

const map = L.map('map', { zoomControl: true, preferCanvas: true }) .setView([39.8283, -98.5795], 4); -L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { - attribution: '© OpenStreetMap © CARTO', - subdomains: 'abcd', maxZoom: 19 +// Standard OSM tiles need no API key. Browser caching and visible attribution are retained. +L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors', + maxZoom: 19 }).addTo(map); const orders = new Map(); // orderId -> { status, marker, line, originMarker, destMarker, data } diff --git a/order-fulfillment-workflow/src/main/resources/static/order-fulfillment.bpmn b/order-fulfillment-workflow/src/main/resources/static/order-fulfillment.bpmn new file mode 100644 index 00000000..d8a90012 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/order-fulfillment.bpmn @@ -0,0 +1,235 @@ + + + + + + f_start + + + f_start + f_wait + f_stock + + + f_wait + f_paid + + + f_timeout + ${paymentTimeout} + + + f_error + + + + f_stock + f_stock_result + + + f_stock_result + f_stock_yes + f_stock_no + + + f_stock_yes + f_initiated + + + f_initiation_error + + + + f_initiated + f_paid + f_ship + + + f_ship + f_notify + + + f_notify + f_done + + + f_done + + + f_stock_no + f_stock_failed + + + f_stock_failed + + + + f_error + f_initiation_error + f_error_end + + + f_error_end + + + + f_timeout + f_timeout_end + + + f_timeout_end + + + + + + + reserved == true + + + + + + reserved == false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/LICENSE b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/LICENSE new file mode 100644 index 00000000..b20e936d --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014-present Camunda Services GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The source code responsible for displaying the bpmn.io project watermark that +links back to https://bpmn.io as part of rendered diagrams MUST NOT be +removed or changed. When this software is being used in a website or application, +the watermark must stay fully visible and not visually overlapped by other elements. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/README.md b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/README.md new file mode 100644 index 00000000..84b288f1 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/README.md @@ -0,0 +1,12 @@ +# bpmn-js viewer + +Vendored from the official npm package `bpmn-js@18.31.0`: +https://www.npmjs.com/package/bpmn-js/v/18.31.0 + +Included: navigated viewer production bundle, diagram-js.css, bpmn-js.css, and LICENSE. +The bundle retains its dependency notices. The required bpmn.io watermark remains visible. + +Upstream documentation: https://bpmn.io/toolkit/bpmn-js/walkthrough/ + +To update, use `npm pack bpmn-js@` and copy these files from `package/dist` +and `package/LICENSE`. This demo does not need a frontend build or CDN for BPMN rendering. diff --git a/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-js.css b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-js.css new file mode 100644 index 00000000..8acf9c75 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-js.css @@ -0,0 +1,151 @@ +/** + * Semantic tokens, copied from `@bpmn-io/theme` to style the components + * according to this theme. + * + * Names and values of the tokens should stay intact with the `@bpmn-io/theme` + * source of truth, to ensure matching styles and allow overrides with an + * alternative theme. + */ +.bio-theme-parent { + --bio-surface-subtle: hsl(225, 10%, 97%); + --bio-text-on-primary: hsl(0, 0%, 100%); + --bio-border: hsl(225, 10%, 75%); + --bio-primary: hsl(205, 100%, 40%); + --bio-focus: hsl(205, 100%, 40%); + --bio-radius-md: 3px; + --bio-radius-lg: 4px; +} + +.bjs-container { + --bjs-font-family: Arial, sans-serif; + + --accent-color: var(--bio-primary); + + --breadcrumbs-font-family: var(--bjs-font-family); + --breadcrumbs-item-color: var(--accent-color); + --breadcrumbs-background-color: var(--bio-surface-subtle); + --breadcrumbs-border-color: var(--bio-border); + --breadcrumbs-focus-outline-color: var(--bio-focus); + --drilldown-fill-color: var(--bio-text-on-primary); + --drilldown-background-color: var(--accent-color); + --drilldown-focus-outline-color: var(--bio-focus); + + --button-border-radius: var(--bio-radius-md); + --surface-border-radius: var(--bio-radius-lg); +} + +.bjs-breadcrumbs { + position: absolute; + display: none; + flex-wrap: wrap; + align-items: center; + top: 20px; + left: 20px; + padding: 5px 10px; + margin: 0px; + font-family: var(--breadcrumbs-font-family); + font-size: 16px; + line-height: normal; + background-color: var(--breadcrumbs-background-color); + border: solid 1px var(--breadcrumbs-border-color); + border-radius: var(--surface-border-radius); + row-gap: 5px; +} + +.bjs-breadcrumbs-shown .bjs-breadcrumbs { + display: flex; +} + +.djs-palette-shown .bjs-breadcrumbs { + left: 90px; +} + +.djs-palette-shown.djs-palette-two-column .bjs-breadcrumbs { + left: 140px; +} + +.bjs-breadcrumbs li { + display: inline-flex; + align-items: center; +} + +.bjs-breadcrumbs a.bjs-crumb { + cursor: pointer; + color: var(--breadcrumbs-item-color); + text-decoration: none; +} + +.bjs-breadcrumbs a.bjs-crumb:focus-visible { + outline: solid 1px var(--breadcrumbs-focus-outline-color); + outline-offset: 1px; +} + +.bjs-breadcrumbs li:not(:first-child)::before { + content: url('data:image/svg+xml;utf8,'); + padding: 0 8px; + height: 1em; +} + +.bjs-breadcrumbs .bjs-crumb { + display: inline-block; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bjs-drilldown { + width: 20px; + height: 20px; + + padding: 0px; + margin-left: -20px; + + cursor: pointer; + border: none; + border-radius: var(--button-border-radius); + + fill: var(--drilldown-fill-color); + background-color: var(--drilldown-background-color); +} + +.bjs-drilldown:focus-visible { + outline: solid 1px var(--drilldown-focus-outline-color); + outline-offset: 1px; +} + +.bjs-drilldown-empty { + display: none; +} + +.selected .bjs-drilldown-empty { + display: inherit; +} + +[data-popup="align-elements"] .djs-popup-results { + display: flex; +} + +[data-popup="align-elements"] .djs-popup-body [data-group] + [data-group] { + border-left: 1px solid var(--popup-border-color); +} + +[data-popup="align-elements"] [data-group="align"] { + display: grid; + grid-template-columns: repeat(3, 1fr); +} + +[data-popup="align-elements"] .djs-popup-body .entry { + padding: 6px 8px; +} + +[data-popup="align-elements"] .djs-popup-body .entry:not(:first-child) { + margin-top: 0; +} + +[data-popup="align-elements"] .djs-popup-entry-icon { + display: block; + margin: 0; + height: 20px; + width: 20px; +} diff --git a/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-navigated-viewer.production.min.js b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-navigated-viewer.production.min.js new file mode 100644 index 00000000..99ab66b3 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/bpmn-navigated-viewer.production.min.js @@ -0,0 +1,22 @@ +/*! bpmn-js - bpmn-navigated-viewer v18.31.0 | Copyright (c) 2014-present, camunda Services GmbH | bpmn.io/license */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).BpmnJS=t()}(this,function(){"use strict";function e(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}const t=Object.prototype.toString,n=Object.prototype.hasOwnProperty;function i(e){return void 0===e}function r(e){return void 0!==e}function o(e){return null==e}function a(e){return"[object Array]"===t.call(e)}function s(e){return"[object Object]"===t.call(e)}function l(e){return"[object Number]"===t.call(e)}function c(e){const n=t.call(e);return"[object Function]"===n||"[object AsyncFunction]"===n||"[object GeneratorFunction]"===n||"[object AsyncGeneratorFunction]"===n||"[object Proxy]"===n}function p(e){return"[object String]"===t.call(e)}function u(e,t){return!o(e)&&n.call(e,t)}function h(e,t){const n=x(t);let i;return m(e,function(e,t){if(n(e,t))return i=e,!1}),i}function f(e,t){const n=x(t);let i=[];return m(e,function(e,t){n(e,t)&&i.push(e)}),i}function m(e,t){let n,r;if(i(e))return;const o=a(e)?w:b;for(let i in e)if(u(e,i)&&(n=e[i],r=t(n,o(i)),!1===r))return n}function d(e,t,n){return m(e,function(e,i){n=t(n,e,i)}),n}function y(e,t){return!!d(e,function(e,n,i){return e&&t(n,i)},!0)}function g(e,t){return!!h(e,t)}function v(e){return function(t){return y(e,function(e,n){return t[n]===e})}}function x(e){return c(e)?e:t=>t===e}function b(e){return e}function w(e){return Number(e)}function E(e,t){let n,i,r,o;function a(n){let a=Date.now(),c=n?0:o+t-a;if(c>0)return s(c);e.apply(r,i),l()}function s(e){n=setTimeout(a,e)}function l(){n&&clearTimeout(n),n=o=i=r=void 0}function c(...e){o=Date.now(),i=e,r=this,n||s(t)}return c.flush=function(){n&&a(!0),l()},c.cancel=l,c}function _(e,t){return e.bind(t)}function k(e,...t){return Object.assign(e,...t)}function A(e,t){var n=this;t=t||1e3,e.on(["render.shape","render.connection"],t,function(e,t){var i=e.type,r=t.element,o=t.gfx,a=t.attrs;if(n.canRender(r))return"render.shape"===i?n.drawShape(o,r,a):n.drawConnection(o,r,a)}),e.on(["render.getShapePath","render.getConnectionPath"],t,function(e,t){if(n.canRender(t))return"render.getShapePath"===e.type?n.getShapePath(t):n.getConnectionPath(t)})}function S(e,t){var n=R(e);return n&&"function"==typeof n.$instanceOf&&n.$instanceOf(t)}function R(e){return e&&e.businessObject||e}function C(e){return e&&e.di}function M(e,t){return!S(e,"bpmn:CallActivity")&&(S(e,"bpmn:SubProcess")?!(!(t=t||C(e))||!S(t,"bpmndi:BPMNPlane"))||t&&!!t.isExpanded:!S(e,"bpmn:Participant")||!!R(e).processRef)}function P(e){if(S(e,"bpmn:Participant")||S(e,"bpmn:Lane")){var t=C(e).isHorizontal;return void 0===t||t}}A.prototype.canRender=function(e){},A.prototype.drawShape=function(e,t){},A.prototype.drawConnection=function(e,t){},A.prototype.getShapePath=function(e){},A.prototype.getConnectionPath=function(e){};var N={width:90,height:20};function B(e){var t=e.length/2-1,n=e[Math.floor(t)],i=e[Math.ceil(t+.01)],r=function(e){var t=e.length/2-1,n=e[Math.floor(t)],i=e[Math.ceil(t+.01)];return{x:n.x+(i.x-n.x)/2,y:n.y+(i.y-n.y)/2}}(e),o=Math.atan((i.y-n.y)/(i.x-n.x)),a=r.x,s=r.y;return Math.abs(o)r||void 0===r)&&(r=s+p),(l+c>o||void 0===o)&&(o=l+c)}),{x:n,y:i,height:o-i,width:r-n}}function L(e){return"waypoints"in e?"connection":"x"in e?"shape":"root"}function I(e){return!(!e||!e.isFrame)}function j(e,t){return function(e,t){t.appendChild(function(e,t){if(e.ownerDocument!==t.ownerDocument)try{return t.ownerDocument.importNode(e,!0)}catch(e){}return e}(e,t))}(t,e),e}var F={"alignment-baseline":1,"baseline-shift":1,clip:1,"clip-path":1,"clip-rule":1,color:1,"color-interpolation":1,"color-interpolation-filters":1,"color-profile":1,"color-rendering":1,cursor:1,direction:1,display:1,"dominant-baseline":1,"enable-background":1,fill:1,"fill-opacity":1,"fill-rule":1,filter:1,"flood-color":1,"flood-opacity":1,font:1,"font-family":1,"font-size":2,"font-size-adjust":1,"font-stretch":1,"font-style":1,"font-variant":1,"font-weight":1,"glyph-orientation-horizontal":1,"glyph-orientation-vertical":1,"image-rendering":1,kerning:1,"letter-spacing":1,"lighting-color":1,marker:1,"marker-end":1,"marker-mid":1,"marker-start":1,mask:1,opacity:1,overflow:1,"pointer-events":1,"shape-rendering":1,"stop-color":1,"stop-opacity":1,stroke:1,"stroke-dasharray":1,"stroke-dashoffset":1,"stroke-linecap":1,"stroke-linejoin":1,"stroke-miterlimit":1,"stroke-opacity":1,"stroke-width":2,"text-anchor":1,"text-decoration":1,"text-rendering":1,"unicode-bidi":1,visibility:1,"word-spacing":1,"writing-mode":1};function V(e,t,n){var i=t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),r=F[i];r?(2===r&&"number"==typeof n&&(n=String(n)+"px"),e.style[i]=n):e.setAttributeNS(null,t,n)}function z(e,t,n){if("string"==typeof t){if(void 0===n)return function(e,t){return F[t]?e.style[t]:e.getAttributeNS(null,t)}(e,t);V(e,t,n)}else!function(e,t){var n,i,r=Object.keys(t);for(n=0;i=r[n];n++)V(e,i,t[i])}(e,t);return e}const W=Object.prototype.toString;function $(e){return new G(e)}function G(e){if(!e||!e.nodeType)throw new Error("A DOM element reference is required");this.el=e,this.list=e.classList}G.prototype.add=function(e){return this.list.add(e),this},G.prototype.remove=function(e){return"[object RegExp]"==W.call(e)?this.removeMatching(e):(this.list.remove(e),this)},G.prototype.removeMatching=function(e){const t=this.array();for(let n=0;n"+e+"",t=!0);var n=function(e){var t;return(t=new DOMParser).async=!1,t.parseFromString(e,"text/xml")}(e);if(!t)return n;for(var i=document.createDocumentFragment(),r=n.firstChild;r.firstChild;)i.appendChild(r.firstChild);return i}function q(e,t){var n;return"<"===(e=e.trim()).charAt(0)?(n=U(e).firstChild,n=document.importNode(n,!0)):n=document.createElementNS(H,e),t&&z(n,t),n}var Y=null;function X(){return null===Y&&(Y=q("svg")),Y}function Z(e,t){var n,i,r=Object.keys(t);for(n=0;i=r[n];n++)e[i]=t[i];return e}function J(e){return X().createSVGTransform()}var Q=/([&<>]{1})/g,ee=/([&<>\n\r"]{1})/g,te={"&":"&","<":"<",">":">",'"':"'"};function ne(e,t){return e.replace(t,function(e,t){return te[t]||t})}function ie(e,t){var n,i,r,o,a;switch(e.nodeType){case 3:t.push(ne(e.textContent,Q));break;case 1:if(t.push("<",e.tagName),e.hasAttributes())for(n=0,i=(r=e.attributes).length;n"),n=0,i=(a=e.childNodes).length;n")}else t.push("/>");break;case 8:t.push("\x3c!--",ne(e.nodeValue,Q),"--\x3e");break;case 4:t.push("");break;default:throw new Error("unable to handle node "+e.nodeType)}return t}function re(e,t){return function(e){for(var t=e.firstChild,n=[];t;)ie(t,n),t=t.nextSibling;return n.join("")}(e)}function oe(e){var t=e.parentNode;return t&&t.removeChild(e),e}function ae(e,t){return t instanceof SVGMatrix?e.createSVGTransformFromMatrix(t):t}function se(e,t){var n=e.transform.baseVal;return t&&(Array.isArray(t)||(t=[t]),function(e,t){var n,i;for(e.clear(),n=0;i=t[n];n++)e.appendItem(ae(e,i))}(n,t)),n.consolidate()}function le(e){return e.flat().join(",").replace(/,?([A-Za-z]),?/g,"$1")}function ce(e){return["L",e.x,e.y]}function pe(e,t,n){return["C",e.x,e.y,t.x,t.y,n.x,n.y]}function ue(e,t){const n=e.length,i=[(r=e[0],["M",r.x,r.y])];var r;for(let r=1;r",""],tr:[2,"","
"],col:[2,"","
"],_default:[0,"",""]};function Se(e,t=globalThis.document){if("string"!=typeof e)throw new TypeError("String expected");const n=/^$/s.exec(e);if(n)return t.createComment(n[1]);const i=/<([\w:]+)/.exec(e)?.[1];if(!i)return t.createTextNode(e);if(e=e.trim(),"body"===i){const n=t.createElement("html");n.innerHTML=e;const{lastChild:i}=n;return i.remove(),i}let[r,o,a]=Object.hasOwn(Ae,i)?Ae[i]:Ae._default,s=t.createElement("div");for(s.innerHTML=o+e+a;r--;)s=s.lastChild;if(s.firstChild===s.lastChild){const{firstChild:e}=s;return e.remove(),e}const l=t.createDocumentFragment();return l.append(...s.childNodes),l}function Re(e,t){return t.forEach(function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach(function(n){if("default"!==n&&!(n in e)){var i=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,i.get?i:{enumerable:!0,get:function(){return t[n]}})}})}),Object.freeze(e)}function Ce(e,...t){const n=e.style;return m(t,function(e){e&&m(e,function(e,t){n[t]=e})}),e}function Me(e,t,n){return 2==arguments.length?e.getAttribute(t):null===n?e.removeAttribute(t):(e.setAttribute(t,n),e)}Ae.td=Ae.th=[3,"","
"],Ae.option=Ae.optgroup=[1,'"],Ae.thead=Ae.tbody=Ae.colgroup=Ae.caption=Ae.tfoot=[1,"","
"],Ae.polyline=Ae.ellipse=Ae.polygon=Ae.circle=Ae.text=Ae.line=Ae.path=Ae.rect=Ae.g=[1,'',""];const Pe=Object.prototype.toString;function Ne(e){return new Be(e)}function Be(e){if(!e||!e.nodeType)throw new Error("A DOM element reference is required");this.el=e,this.list=e.classList}function De(e){for(var t;t=e.firstChild;)e.removeChild(t);return e}function Te(e,t,n){var i=n?e:e.parentNode;return i&&"function"==typeof i.closest&&i.closest(t)||null}function Oe(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}Be.prototype.add=function(e){return this.list.add(e),this},Be.prototype.remove=function(e){return"[object RegExp]"==Pe.call(e)?this.removeMatching(e):(this.list.remove(e),this)},Be.prototype.removeMatching=function(e){const t=this.array();for(let n=0;n=Math.pow(2,t)?e(t,n):a};return e.rack=function(t,n,i){var r=function(r){var a=0;do{if(a++>10){if(!i)throw new Error("too many ID collisions, use more bits");t+=i}var s=e(t,n)}while(Object.hasOwnProperty.call(o,s));return o[s]=r,s},o=r.hats={};return r.get=function(e){return r.hats[e]},r.set=function(e,t){return r.hats[e]=t,r},r.bits=t||128,r.base=n||16,r},qe.exports}(),Xe=Ke(Ye);function Ze(e){if(!(this instanceof Ze))return new Ze(e);e=e||[128,36,1],this._seed=e.length?Xe.rack(e[0],e[1],e[2]):e}Ze.prototype.next=function(e){return this._seed(e||!0)},Ze.prototype.nextPrefixed=function(e,t){var n;do{n=e+this.next(!0)}while(this.assigned(n));return this.claim(n,t),n},Ze.prototype.claim=function(e,t){this._seed.set(e,t||!0)},Ze.prototype.assigned=function(e){return this._seed.get(e)||!1},Ze.prototype.unclaim=function(e){delete this._seed.hats[e]},Ze.prototype.clear=function(){var e,t=this._seed.hats;for(e in t)this.unclaim(e)};var Je=new Ze,Qe=.95;function et(e,t,n,i,r,o,a){A.call(this,t,a);var l=e&&e.defaultFillColor,c=e&&e.defaultStrokeColor,p=e&&e.defaultLabelColor;function u(e){return n.computeStyle(e,{strokeLinecap:"round",strokeLinejoin:"round",stroke:ye,strokeWidth:2,fill:"white"})}function h(e){return n.computeStyle(e,["no-fill"],{strokeLinecap:"round",strokeLinejoin:"round",stroke:ye,strokeWidth:2})}function f(e,t){var{ref:n={x:0,y:0},scale:i=1,element:o,parentGfx:a=r._svg}=t,s=q("marker",{id:e,viewBox:"0 0 20 20",refX:n.x,refY:n.y,markerWidth:20*i,markerHeight:20*i,orient:"auto"});j(s,o);var l=We(":scope > defs",a);l||j(a,l=q("defs")),j(l,s)}function d(e,t,n,i){var r=Je.nextPrefixed("marker-");return function(e,t,n,i,r){if("sequenceflow-end"===n){f(t,{element:q("path",{d:"M 1 5 L 11 10 L 1 15 Z",...u({fill:r,stroke:r,strokeWidth:1})}),ref:{x:11,y:10},scale:.5,parentGfx:e})}if("messageflow-start"===n){f(t,{element:q("circle",{cx:6,cy:6,r:3.5,...u({fill:i,stroke:r,strokeWidth:1,strokeDasharray:[1e4,1]})}),ref:{x:6,y:6},parentGfx:e})}if("messageflow-end"===n){f(t,{element:q("path",{d:"m 1 5 l 0 -3 l 7 3 l -7 3 z",...u({fill:i,stroke:r,strokeWidth:1,strokeDasharray:[1e4,1]})}),ref:{x:8.5,y:5},parentGfx:e})}if("association-start"===n){f(t,{element:q("path",{d:"M 11 5 L 1 10 L 11 15",...h({fill:"none",stroke:r,strokeWidth:1.5,strokeDasharray:[1e4,1]})}),ref:{x:1,y:10},scale:.5,parentGfx:e})}if("association-end"===n){f(t,{element:q("path",{d:"M 1 5 L 11 10 L 1 15",...h({fill:"none",stroke:r,strokeWidth:1.5,strokeDasharray:[1e4,1]})}),ref:{x:11,y:10},scale:.5,parentGfx:e})}if("conditional-flow-marker"===n){f(t,{element:q("path",{d:"M 0 10 L 8 6 L 16 10 L 8 14 Z",...u({fill:i,stroke:r})}),ref:{x:-1,y:10},scale:.5,parentGfx:e})}if("conditional-default-flow-marker"===n){f(t,{element:q("path",{d:"M 6 4 L 10 16",...u({stroke:r,fill:"none"})}),ref:{x:0,y:10},scale:.5,parentGfx:e})}}(e,r,t,n,i),"url(#"+r+")"}function y(e,t,n,i,r={}){s(i)&&(r=i,i=0),i=i||0,r=u(r);var o=q("circle",{cx:t/2,cy:n/2,r:Math.round((t+n)/4-i),...r});return j(e,o),o}function g(e,t,n,i,r,o){s(r)&&(o=r,r=0),r=r||0,o=u(o);var a=q("rect",{x:r,y:r,width:t-2*r,height:n-2*r,rx:i,ry:i,...o});return j(e,a),a}function v(e,t,n,i){var r=me(t,n=h(n),i);return j(e,r),r}function x(e,t,n){return v(e,t,n,5)}function b(e,t,n){n=h(n);var i=q("path",{...n,d:t});return j(e,i),i}function w(e,t,n,i){return b(t,n,k({"data-marker":e},i))}function E(e){return ue[e]}function _(e){return function(t,n,i){return E(e)(t,n,i)}}var N=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_MESSAGE",{xScaleFactor:.9,yScaleFactor:.9,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.235,my:.315}}),{fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:r?ve(t,l,n.fill):xe(t,c,n.stroke),strokeWidth:1})},B=function(e,t,n={}){var r=n.width||t.width,o=n.height||t.height,a=n.width?1:2,s=y(e,r,o,.2*o,{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:a});b(e,i.getScaledPath("EVENT_TIMER_WH",{xScaleFactor:.75,yScaleFactor:.75,containerWidth:r,containerHeight:o,position:{mx:.5,my:.5}}),{stroke:xe(t,c,n.stroke),strokeWidth:a});for(var p=0;p<12;p++){var u=r/2,h=o/2;b(e,i.getScaledPath("EVENT_TIMER_LINE",{xScaleFactor:.75,yScaleFactor:.75,containerWidth:r,containerHeight:o,position:{mx:.5,my:.5}}),{strokeWidth:1,stroke:xe(t,c,n.stroke),transform:"rotate("+30*p+","+h+","+u+")"})}return s},D=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_ESCALATION",{xScaleFactor:1,yScaleFactor:1,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.5,my:.2}}),{fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},O=function(e,t,n={}){return b(e,i.getScaledPath("EVENT_CONDITIONAL",{xScaleFactor:1,yScaleFactor:1,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.5,my:.222}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},L=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_LINK",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:.57,my:.263}}),{fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},I=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_ERROR",{xScaleFactor:1.1,yScaleFactor:1.1,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.2,my:.722}}),{fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},F=function(e,t,n={},r){var o=b(e,i.getScaledPath("EVENT_CANCEL_45",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:.638,my:-.055}}),{fill:r?xe(t,c,n.stroke):"none",stroke:xe(t,c,n.stroke),strokeWidth:1});return function(e,t){var n=J();n.setRotate(t,0,0),se(e,n)}(o,45),o},V=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_COMPENSATION",{xScaleFactor:1,yScaleFactor:1,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.22,my:.5}}),{fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},W=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_SIGNAL",{xScaleFactor:.9,yScaleFactor:.9,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.5,my:.2}}),{strokeWidth:1,fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})},G=function(e,t,n={},r){return b(e,i.getScaledPath("EVENT_MULTIPLE",{xScaleFactor:1.1,yScaleFactor:1.1,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.211,my:.36}}),{fill:r?xe(t,c,n.stroke):ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},H=function(e,t,n={}){return b(e,i.getScaledPath("EVENT_PARALLEL_MULTIPLE",{xScaleFactor:1.2,yScaleFactor:1.2,containerWidth:n.width||t.width,containerHeight:n.height||t.height,position:{mx:.458,my:.194}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1})},K=function(e,t,n={}){return y(e,t.width,t.height,8,{fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke),strokeWidth:4})};function U(e,t,n={},i){var r=R(e),o=function(e){return"bpmn:IntermediateThrowEvent"===e.$type||"bpmn:EndEvent"===e.$type}(r),a=i||e;return r.get("eventDefinitions")&&r.get("eventDefinitions").length>1?r.get("parallelMultiple")?H(t,a,n,o):G(t,a,n,o):ge(r,"bpmn:MessageEventDefinition")?N(t,a,n,o):ge(r,"bpmn:TimerEventDefinition")?B(t,a,n,o):ge(r,"bpmn:ConditionalEventDefinition")?O(t,a,n,o):ge(r,"bpmn:SignalEventDefinition")?W(t,a,n,o):ge(r,"bpmn:EscalationEventDefinition")?D(t,a,n,o):ge(r,"bpmn:LinkEventDefinition")?L(t,a,n,o):ge(r,"bpmn:ErrorEventDefinition")?I(t,a,n,o):ge(r,"bpmn:CancelEventDefinition")?F(t,a,n,o):ge(r,"bpmn:CompensateEventDefinition")?V(t,a,n,o):ge(r,"bpmn:TerminateEventDefinition")?K(t,a,n,o):null}var Y={ParticipantMultiplicityMarker:function(e,t,n={}){var r=_e(t,n),o=ke(t,n);w("participant-multiplicity",e,i.getScaledPath("MARKER_PARALLEL",{xScaleFactor:1,yScaleFactor:1,containerWidth:r,containerHeight:o,position:{mx:(r/2-6)/r,my:(o-15)/o}}),{strokeWidth:2,fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})},SubProcessMarker:function(e,t,n={}){He(g(e,14,14,0,{strokeWidth:1,fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)}),t.width/2-7.5,t.height-20),w("sub-process",e,i.getScaledPath("MARKER_SUB_PROCESS",{xScaleFactor:1.5,yScaleFactor:1.5,containerWidth:t.width,containerHeight:t.height,position:{mx:(t.width/2-7.5)/t.width,my:(t.height-20)/t.height}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})},ParallelMarker:function(e,t,n){var r=_e(t,n),o=ke(t,n);w("parallel",e,i.getScaledPath("MARKER_PARALLEL",{xScaleFactor:1,yScaleFactor:1,containerWidth:r,containerHeight:o,position:{mx:(r/2+n.parallel)/r,my:(o-20)/o}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})},SequentialMarker:function(e,t,n){w("sequential",e,i.getScaledPath("MARKER_SEQUENTIAL",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:(t.width/2+n.seq)/t.width,my:(t.height-19)/t.height}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})},CompensationMarker:function(e,t,n){w("compensation",e,i.getScaledPath("MARKER_COMPENSATION",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:(t.width/2+n.compensation)/t.width,my:(t.height-13)/t.height}}),{strokeWidth:1,fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})},LoopMarker:function(e,t,n){var r=_e(t,n),o=ke(t,n);w("loop",e,i.getScaledPath("MARKER_LOOP",{xScaleFactor:1,yScaleFactor:1,containerWidth:r,containerHeight:o,position:{mx:(r/2+n.loop)/r,my:(o-7)/o}}),{strokeWidth:1.5,fill:"none",stroke:xe(t,c,n.stroke),strokeMiterlimit:.5})},AdhocMarker:function(e,t,n){var r=_e(t,n),o=ke(t,n);w("adhoc",e,i.getScaledPath("MARKER_ADHOC",{xScaleFactor:1,yScaleFactor:1,containerWidth:r,containerHeight:o,position:{mx:(r/2+n.adhoc)/r,my:(o-15)/o}}),{strokeWidth:1,fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke)})}};function X(e,t,n,i){Y[e](t,n,i)}function Z(e,t,n=[],i={}){i={fill:i.fill,stroke:i.stroke,width:_e(t,i),height:ke(t,i)};var r=R(t),o=n.includes("SubProcessMarker");i=o?{...i,seq:-21,parallel:-22,compensation:-25,loop:-18,adhoc:10}:{...i,seq:-5,parallel:-6,compensation:-7,loop:0,adhoc:-8},r.get("isForCompensation")&&n.push("CompensationMarker"),S(r,"bpmn:AdHocSubProcess")&&(n.push("AdhocMarker"),o||k(i,{compensation:i.compensation-18}));var a=r.get("loopCharacteristics"),s=a&&a.get("isSequential");a&&(k(i,{compensation:i.compensation-18}),n.includes("AdhocMarker")&&k(i,{seq:-23,loop:-18,parallel:-24}),void 0===s&&n.push("LoopMarker"),!1===s&&n.push("ParallelMarker"),!0===s&&n.push("SequentialMarker")),n.includes("CompensationMarker")&&1===n.length&&k(i,{compensation:-8}),m(n,function(n){X(n,e,t,i)})}function Q(e,t,n={}){n=k({size:{width:100}},n);var i=o.createText(t||"",n);return $(i).add("djs-label"),j(e,i),i}function ee(e,t,n,i={}){var r=R(t),o=Ee({x:t.x,y:t.y,width:t.width,height:t.height},i);return Q(e,r.name,{align:n,box:o,padding:7,style:{fill:be(t,p,c,i.stroke)}})}function te(e,t,n,i={}){var r=P(n),o=Q(e,t,{box:{height:30,width:r?ke(n,i):_e(n,i)},align:"center-middle",style:{fill:be(n,p,c,i.stroke)}});r&&Ge(o,0,-(-1*ke(n,i)),270)}function ne(e,t,n={}){var{width:i,height:r}=Ee(t,n);return g(e,i,r,10,{...n,fill:ve(t,l,n.fill),fillOpacity:Qe,stroke:xe(t,c,n.stroke)})}function ie(e,t,n={}){var i=R(t),r=ve(t,l,n.fill),o=xe(t,c,n.stroke);return"One"!==i.get("associationDirection")&&"Both"!==i.get("associationDirection")||(n.markerEnd=d(e,"association-end",r,o)),"Both"===i.get("associationDirection")&&(n.markerStart=d(e,"association-start",r,o)),n=tt(n,["markerStart","markerEnd"]),x(e,t.waypoints,{...n,stroke:o,strokeDasharray:"0, 5"})}function re(e,t,n={}){var r=ve(t,l,n.fill),o=xe(t,c,n.stroke),a=b(e,i.getScaledPath("DATA_OBJECT_PATH",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:.474,my:.296}}),{fill:r,fillOpacity:Qe,stroke:o});(function(e){var t=e.dataObjectRef;return e.isCollection||t&&t.isCollection})(R(t))&&b(e,i.getScaledPath("DATA_OBJECT_COLLECTION_PATH",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:.33,my:(t.height-18)/t.height}}),{strokeWidth:2,fill:r,stroke:o});return a}function oe(e,t,n={}){return y(e,t.width,t.height,{fillOpacity:Qe,...n,fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke)})}function ae(e,t,n={}){return function(e,t,n,i){var r=t/2,o=n/2,a=[{x:r,y:0},{x:t,y:o},{x:r,y:n},{x:0,y:o}].map(function(e){return e.x+","+e.y}).join(" ");i=u(i);var s=q("polygon",{...i,points:a});return j(e,s),s}(e,t.width,t.height,{fill:ve(t,l,n.fill),fillOpacity:Qe,stroke:xe(t,c,n.stroke)})}function le(e,t,n={}){var i=g(e,_e(t,n),ke(t,n),0,{fill:ve(t,l,n.fill),fillOpacity:n.fillOpacity||Qe,stroke:xe(t,c,n.stroke),strokeWidth:1.5}),r=R(t);S(r,"bpmn:Lane")&&te(e,r.get("name"),t,n);return i}function ce(e,t,n={}){var i=ne(e,t,n),r=M(t);if(function(e){return e&&!!R(e).triggeredByEvent}(t)&&(z(i,{strokeDasharray:"0, 5.5",strokeWidth:2.5}),!r)){var o=(R(t).flowElements||[]).filter(e=>S(e,"bpmn:StartEvent"));1===o.length&&function(e,t,n,i){var r=22,o={fill:ve(i,l,n.fill),stroke:xe(i,c,n.stroke),width:r,height:r},a=R(e).isInterrupting,s=a?0:3,p=a?1:1.2,u=20,h=(r-u)/2,f="translate("+h+","+h+")";y(t,u,u,{fill:o.fill,stroke:o.stroke,strokeWidth:p,strokeDasharray:s,transform:f}),U(e,t,o,i)}(o[0],e,n,t)}return ee(e,t,r?"center-top":"center-middle",n),Z(e,t,r?void 0:["SubProcessMarker"],n),i}function pe(e,t,n={}){var i=ne(e,t,n);return ee(e,t,"center-middle",n),Z(e,t,void 0,n),i}var ue=this.handlers={"bpmn:AdHocSubProcess":function(e,t,n={}){return ce(e,t,n=M(t)?tt(n,["fill","stroke","width","height"]):tt(n,["fill","stroke"]))},"bpmn:Association":function(e,t,n={}){return ie(e,t,n=tt(n,["fill","stroke"]))},"bpmn:BoundaryEvent":function(e,t,n={}){var{renderIcon:i=!0}=n;n=tt(n,["fill","stroke"]);var r=R(t).get("cancelActivity");n={strokeWidth:1.5,fill:ve(t,l,n.fill),fillOpacity:1,stroke:xe(t,c,n.stroke)},r||(n.strokeDasharray="6");var o=oe(e,t,n);return y(e,t.width,t.height,3,{...n,fill:"none"}),i&&U(t,e,n),o},"bpmn:BusinessRuleTask":function(e,t,n={}){var r=pe(e,t,n=tt(n,["fill","stroke"]));return z(b(e,i.getScaledPath("TASK_TYPE_BUSINESS_RULE_MAIN",{abspos:{x:8,y:8}})),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1}),z(b(e,i.getScaledPath("TASK_TYPE_BUSINESS_RULE_HEADER",{abspos:{x:8,y:8}})),{fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke),strokeWidth:1}),r},"bpmn:CallActivity":function(e,t,n={}){return n=tt(n,["fill","stroke"]),ce(e,t,{strokeWidth:5,...n})},"bpmn:ComplexGateway":function(e,t,n={}){var r=ae(e,t,n=tt(n,["fill","stroke"]));return b(e,i.getScaledPath("GATEWAY_COMPLEX",{xScaleFactor:.5,yScaleFactor:.5,containerWidth:t.width,containerHeight:t.height,position:{mx:.46,my:.26}}),{fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke),strokeWidth:1}),r},"bpmn:DataInput":function(e,t,n={}){n=tt(n,["fill","stroke"]);var r=i.getRawPath("DATA_ARROW"),o=re(e,t,n);return b(e,r,{fill:"none",stroke:xe(t,c,n.stroke),strokeWidth:1}),o},"bpmn:DataInputAssociation":function(e,t,n={}){return n=tt(n,["fill","stroke"]),ie(e,t,{...n,markerEnd:d(e,"association-end",ve(t,l,n.fill),xe(t,c,n.stroke))})},"bpmn:DataObject":function(e,t,n={}){return re(e,t,n=tt(n,["fill","stroke"]))},"bpmn:DataObjectReference":_("bpmn:DataObject"),"bpmn:DataOutput":function(e,t,n={}){n=tt(n,["fill","stroke"]);var r=i.getRawPath("DATA_ARROW"),o=re(e,t,n);return b(e,r,{strokeWidth:1,fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke)}),o},"bpmn:DataOutputAssociation":function(e,t,n={}){return n=tt(n,["fill","stroke"]),ie(e,t,{...n,markerEnd:d(e,"association-end",ve(t,l,n.fill),xe(t,c,n.stroke))})},"bpmn:DataStoreReference":function(e,t,n={}){return n=tt(n,["fill","stroke"]),b(e,i.getScaledPath("DATA_STORE",{xScaleFactor:1,yScaleFactor:1,containerWidth:t.width,containerHeight:t.height,position:{mx:0,my:.133}}),{fill:ve(t,l,n.fill),fillOpacity:Qe,stroke:xe(t,c,n.stroke),strokeWidth:2})},"bpmn:EndEvent":function(e,t,n={}){var{renderIcon:i=!0}=n;n=tt(n,["fill","stroke"]);var r=oe(e,t,{...n,strokeWidth:4});return i&&U(t,e,n),r},"bpmn:EventBasedGateway":function(e,t,n={}){n=tt(n,["fill","stroke"]);var r=R(t),o=ae(e,t,n);y(e,t.width,t.height,.2*t.height,{fill:ve(t,"none",n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1});var a=r.get("eventGatewayType"),s=!!r.get("instantiate");if("Parallel"===a){var l=i.getScaledPath("GATEWAY_PARALLEL",{xScaleFactor:.4,yScaleFactor:.4,containerWidth:t.width,containerHeight:t.height,position:{mx:.474,my:.296}});b(e,l,{fill:"none",stroke:xe(t,c,n.stroke),strokeWidth:1})}else"Exclusive"===a&&(s||y(e,t.width,t.height,.26*t.height,{fill:"none",stroke:xe(t,c,n.stroke),strokeWidth:1}),function(){var r=i.getScaledPath("GATEWAY_EVENT_BASED",{xScaleFactor:.18,yScaleFactor:.18,containerWidth:t.width,containerHeight:t.height,position:{mx:.36,my:.44}});b(e,r,{fill:"none",stroke:xe(t,c,n.stroke),strokeWidth:2})}());return o},"bpmn:ExclusiveGateway":function(e,t,n={}){var r=ae(e,t,n=tt(n,["fill","stroke"])),o=i.getScaledPath("GATEWAY_EXCLUSIVE",{xScaleFactor:.4,yScaleFactor:.4,containerWidth:t.width,containerHeight:t.height,position:{mx:.32,my:.3}});return C(t).get("isMarkerVisible")&&b(e,o,{fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke),strokeWidth:1}),r},"bpmn:Gateway":function(e,t,n={}){return ae(e,t,n=tt(n,["fill","stroke"]))},"bpmn:Group":function(e,t,n={}){return n=tt(n,["fill","stroke","width","height"]),g(e,t.width,t.height,10,{stroke:xe(t,c,n.stroke),strokeWidth:1.5,strokeDasharray:"10, 6, 0, 6",fill:"none",pointerEvents:"none",width:_e(t,n),height:ke(t,n)})},"bpmn:InclusiveGateway":function(e,t,n={}){var i=ae(e,t,n=tt(n,["fill","stroke"]));return y(e,t.width,t.height,.24*t.height,{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:2.5}),i},"bpmn:IntermediateEvent":function(e,t,n={}){var{renderIcon:i=!0}=n;n=tt(n,["fill","stroke"]);var r=oe(e,t,{...n,strokeWidth:1.5});return y(e,t.width,t.height,3,{fill:"none",stroke:xe(t,c,n.stroke),strokeWidth:1.5}),i&&U(t,e,n),r},"bpmn:IntermediateCatchEvent":_("bpmn:IntermediateEvent"),"bpmn:IntermediateThrowEvent":_("bpmn:IntermediateEvent"),"bpmn:Lane":function(e,t,n={}){return n=tt(n,["fill","stroke","width","height"]),le(e,t,{...n,fillOpacity:.25})},"bpmn:ManualTask":function(e,t,n={}){var r=pe(e,t,n=tt(n,["fill","stroke"]));return b(e,i.getScaledPath("TASK_TYPE_MANUAL",{abspos:{x:17,y:15}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:.5}),r},"bpmn:MessageFlow":function(e,t,n={}){n=tt(n,["fill","stroke"]);var r=R(t),o=C(t),a=ve(t,l,n.fill),s=xe(t,c,n.stroke),p=x(e,t.waypoints,{markerEnd:d(e,"messageflow-end",a,s),markerStart:d(e,"messageflow-start",a,s),stroke:s,strokeDasharray:"10, 11",strokeWidth:1.5});if(r.get("messageRef")){var u=p.getPointAtLength(p.getTotalLength()/2),h=i.getScaledPath("MESSAGE_FLOW_MARKER",{abspos:{x:u.x,y:u.y}}),f={strokeWidth:1};"initiating"===o.get("messageVisibleKind")?(f.fill=a,f.stroke=s):(f.fill=s,f.stroke=a);var m=b(e,h,f),y=Q(e,r.get("messageRef").get("name"),{align:"center-top",fitBox:!0,style:{fill:s}}),g=m.getBBox(),v=y.getBBox();Ge(y,u.x-v.width/2,u.y+g.height/2+10,0)}return p},"bpmn:ParallelGateway":function(e,t,n={}){var r=ae(e,t,n=tt(n,["fill","stroke"]));return b(e,i.getScaledPath("GATEWAY_PARALLEL",{xScaleFactor:.6,yScaleFactor:.6,containerWidth:t.width,containerHeight:t.height,position:{mx:.46,my:.2}}),{fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke),strokeWidth:1}),r},"bpmn:Participant":function(e,t,n={}){var i=le(e,t,n=tt(n,["fill","stroke","width","height"])),r=M(t),o=P(t),a=R(t),s=a.get("name");if(r){v(e,o?[{x:30,y:0},{x:30,y:ke(t,n)}]:[{x:0,y:30},{x:_e(t,n),y:30}],{stroke:xe(t,c,n.stroke),strokeWidth:1.5}),te(e,s,t,n)}else{var l=Ee(t,n);o||(l.height=_e(t,n),l.width=ke(t,n));var u=Q(e,s,{box:l,align:"center-middle",style:{fill:be(t,p,c,n.stroke)}});if(!o)Ge(u,0,-(-1*ke(t,n)),270)}return a.get("participantMultiplicity")&&X("ParticipantMultiplicityMarker",e,t,n),i},"bpmn:ReceiveTask":function(e,t,n={}){n=tt(n,["fill","stroke"]);var r,o=R(t),a=pe(e,t,n);return o.get("instantiate")?(y(e,28,28,4.4,{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1}),r=i.getScaledPath("TASK_TYPE_INSTANTIATING_SEND",{abspos:{x:7.77,y:9.52}})):r=i.getScaledPath("TASK_TYPE_SEND",{xScaleFactor:.9,yScaleFactor:.9,containerWidth:21,containerHeight:14,position:{mx:.3,my:.4}}),b(e,r,{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1}),a},"bpmn:ScriptTask":function(e,t,n={}){var r=pe(e,t,n=tt(n,["fill","stroke"]));return b(e,i.getScaledPath("TASK_TYPE_SCRIPT",{abspos:{x:15,y:20}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1}),r},"bpmn:SendTask":function(e,t,n={}){var r=pe(e,t,n=tt(n,["fill","stroke"]));return b(e,i.getScaledPath("TASK_TYPE_SEND",{xScaleFactor:1,yScaleFactor:1,containerWidth:21,containerHeight:14,position:{mx:.285,my:.357}}),{fill:xe(t,c,n.stroke),stroke:ve(t,l,n.fill),strokeWidth:1}),r},"bpmn:SequenceFlow":function(e,t,n={}){n=tt(n,["fill","stroke"]);var i=ve(t,l,n.fill),r=xe(t,c,n.stroke),o=x(e,t.waypoints,{markerEnd:d(e,"sequenceflow-end",i,r),stroke:r}),a=R(t),{source:s}=t;if(s){var p=R(s);a.get("conditionExpression")&&S(p,"bpmn:Activity")&&z(o,{markerStart:d(e,"conditional-flow-marker",i,r)}),p.get("default")&&(S(p,"bpmn:Gateway")||S(p,"bpmn:Activity"))&&p.get("default")===a&&z(o,{markerStart:d(e,"conditional-default-flow-marker",i,r)})}return o},"bpmn:ServiceTask":function(e,t,n={}){var r=pe(e,t,n=tt(n,["fill","stroke"]));return y(e,10,10,{fill:ve(t,l,n.fill),stroke:"none",transform:"translate(6, 6)"}),b(e,i.getScaledPath("TASK_TYPE_SERVICE",{abspos:{x:12,y:18}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1}),y(e,10,10,{fill:ve(t,l,n.fill),stroke:"none",transform:"translate(11, 10)"}),b(e,i.getScaledPath("TASK_TYPE_SERVICE",{abspos:{x:17,y:22}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:1}),r},"bpmn:StartEvent":function(e,t,n={}){var{renderIcon:i=!0}=n;n=tt(n,["fill","stroke"]),R(t).get("isInterrupting")||(n={...n,strokeDasharray:"6"});var r=oe(e,t,n);return i&&U(t,e,n),r},"bpmn:SubProcess":function(e,t,n={}){return ce(e,t,n=M(t)?tt(n,["fill","stroke","width","height"]):tt(n,["fill","stroke"]))},"bpmn:Task":function(e,t,n={}){return pe(e,t,n=tt(n,["fill","stroke"]))},"bpmn:TextAnnotation":function(e,t,n={}){n=tt(n,["fill","stroke"]);var{width:r,height:o}=Ee(t,n),a=g(e,r,o,0,0,{fill:"none",stroke:"none"});return b(e,i.getScaledPath("TEXT_ANNOTATION",{xScaleFactor:1,yScaleFactor:1,containerWidth:r,containerHeight:o,position:{mx:0,my:0}}),{stroke:xe(t,c,n.stroke)}),Q(e,R(t).get("text")||"",{align:"left-top",box:Ee(t,n),padding:7,style:{fill:be(t,p,c,n.stroke)}}),a},"bpmn:Transaction":function(e,t,i={}){i=M(t)?tt(i,["fill","stroke","width","height"]):tt(i,["fill","stroke"]);var r=ce(e,t,{strokeWidth:1.5,...i}),o=n.style(["no-fill","no-events"],{stroke:xe(t,c,i.stroke),strokeWidth:1.5});return M(t)||(i={}),g(e,_e(t,i),ke(t,i),7,3,o),r},"bpmn:UserTask":function(e,t,n={}){var r=pe(e,t,n=tt(n,["fill","stroke"]));return b(e,i.getScaledPath("TASK_TYPE_USER_1",{abspos:{x:15,y:12}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:.5}),b(e,i.getScaledPath("TASK_TYPE_USER_2",{abspos:{x:15,y:12}}),{fill:ve(t,l,n.fill),stroke:xe(t,c,n.stroke),strokeWidth:.5}),b(e,i.getScaledPath("TASK_TYPE_USER_3",{abspos:{x:15,y:12}}),{fill:xe(t,c,n.stroke),stroke:xe(t,c,n.stroke),strokeWidth:.5}),r},label:function(e,t,n={}){return function(e,t,n={}){var i={width:t.width,height:t.height,x:t.width/2+t.x,y:t.height/2+t.y};return Q(e,T(t),{box:i,style:k({},o.getExternalStyle(),{fill:be(t,p,c,n.stroke)})})}(e,t,n)}};this._drawPath=b,this._renderer=E}function tt(e,t=[]){return t.reduce((t,n)=>(e[n]&&(t[n]=e[n]),t),{})}e(et,A),et.$inject=["config.bpmnRenderer","eventBus","styles","pathMap","canvas","textRenderer"],et.prototype.canRender=function(e){return S(e,"bpmn:BaseElement")},et.prototype.drawShape=function(e,t,n={}){var{type:i}=t;return this._renderer(i)(e,t,n)},et.prototype.drawConnection=function(e,t,n={}){var{type:i}=t;return this._renderer(i)(e,t,n)},et.prototype.getShapePath=function(e){return s(t=e)&&u(t,"labelTarget")?we(e,4):S(e,"bpmn:Event")?function(e){var t=e.x+e.width/2,n=e.y+e.height/2,i=e.width/2;return le([["M",t,n],["m",0,-i],["a",i,i,0,1,1,0,2*i],["a",i,i,0,1,1,0,-2*i],["z"]])}(e):S(e,"bpmn:Activity")?we(e,10):S(e,"bpmn:Gateway")?function(e){var t=e.width/2,n=e.height/2;return le([["M",e.x+t,e.y],["l",t,n],["l",-t,n],["l",-t,-n],["z"]])}(e):function(e){var t=e.x,n=e.y,i=e.width;return le([["M",t,n],["l",i,0],["l",0,e.height],["l",-i,0],["z"]])}(e);var t};var nt={width:150,height:50};var it=null;function rt(e){if(null!=e)return"number"==typeof e||/^-?\d+(\.\d+)?$/.test(e)?e+"px":e}function ot(e,t){var n=(it||(it=document.createElement("canvas").getContext("2d")),it);if(!n)return{width:0,height:0};n.font=function(e){var t=[];return e.fontStyle&&t.push(e.fontStyle),e.fontVariant&&t.push(e.fontVariant),e.fontWeight&&t.push(e.fontWeight),e.fontStretch&&t.push(e.fontStretch),t.push(rt(e.fontSize)||"12px"),t.push(e.fontFamily||"sans-serif"),t.join(" ")}(t),"letterSpacing"in n&&(n.letterSpacing=rt(t.letterSpacing)||"0px");var i=""===e,r=i?"dummy":e.replace(/\s+$/,""),o=n.measureText(r);return{width:i?0:o.width,height:"fontBoundingBoxAscent"in o?o.fontBoundingBoxAscent+o.fontBoundingBoxDescent:o.actualBoundingBoxAscent+o.actualBoundingBoxDescent}}function at(e,t,n){for(var i,r=e.shift(),o=r;;){if((i=ot(o,n)).width=o?i.width:0," "===o||""===o||i.width<=t||o.length<2)return st(e,o,r,i);o=ct(o,i.width,t)}}function st(e,t,n,i){if(t.length1)for(;n=i.shift();){if(!(n.length+oe?t.width:e},0),y=o.top;"middle"===r.vertical&&(y+=(n.height-h)/2),y-=(l||p[0].height)/4;var g=q("text");return z(g,i),m(p,function(e){var t;switch(y+=l||e.height,r.horizontal){case"left":t=o.left;break;case"right":t=(a?f:u)-o.right-e.width;break;default:t=Math.max(((a?f:u)-e.width)/2+o.left,0)}var n=q("tspan");z(n,{x:t,y:y}),n.textContent=e.text,j(g,n)}),{dimensions:{width:f,height:h},element:g}};function ut(e){var t=k({fontFamily:"Arial, sans-serif",fontSize:12,fontWeight:"normal",lineHeight:1.2},e&&e.defaultStyle||{}),n=parseInt(t.fontSize,10)-1,i=k({},t,{fontSize:n},e&&e.externalStyle||{}),r=new pt({style:t});function o(e,t,n){return r.getDimensions(e,k({box:t},n))}this.getExternalLabelBounds=function(e,t){var n=o(t,{width:Math.max(e.width,N.width),height:30},{style:i});return{x:Math.round(e.x+e.width/2-n.width/2),y:e.y,width:Math.ceil(n.width),height:Math.ceil(n.height)}},this.getTextAnnotationBounds=function(e,n){var i=o(n,e,{style:t,align:"left-top",padding:7});return{x:e.x,y:e.y,width:e.width,height:Math.max(40,Math.round(i.height))}},this.getDimensions=function(e,t){return r.getDimensions(e,t||{})},this.createText=function(e,t){return r.createText(e,t||{})},this.getDefaultStyle=function(){return t},this.getExternalStyle=function(){return i}}ut.$inject=["config.textRenderer"];var ht=/\{([^{}]+)\}/g,ft=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g;var mt={__init__:["bpmnRenderer"],bpmnRenderer:["type",et],textRenderer:["type",ut],pathMap:["type",function(){this.pathMap={EVENT_MESSAGE:{d:"m {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}",height:36,width:36,heightElements:[6,14],widthElements:[10.5,21]},EVENT_SIGNAL:{d:"M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z",height:36,width:36,heightElements:[18],widthElements:[10,20]},EVENT_ESCALATION:{d:"M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z",height:36,width:36,heightElements:[20,7],widthElements:[8]},EVENT_CONDITIONAL:{d:"M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z M {e.x2},{e.y3} l {e.x0},0 M {e.x2},{e.y4} l {e.x0},0 M {e.x2},{e.y5} l {e.x0},0 M {e.x2},{e.y6} l {e.x0},0 M {e.x2},{e.y7} l {e.x0},0 M {e.x2},{e.y8} l {e.x0},0 ",height:36,width:36,heightElements:[8.5,14.5,18,11.5,14.5,17.5,20.5,23.5,26.5],widthElements:[10.5,14.5,12.5]},EVENT_LINK:{d:"m {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z",height:36,width:36,heightElements:[4.4375,6.75,7.8125],widthElements:[9.84375,13.5]},EVENT_ERROR:{d:"m {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z",height:36,width:36,heightElements:[.023,8.737,8.151,16.564,10.591,8.714],widthElements:[.085,6.672,6.97,4.273,5.337,6.636]},EVENT_CANCEL_45:{d:"m {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z",height:36,width:36,heightElements:[4.75,8.5],widthElements:[4.75,8.5]},EVENT_COMPENSATION:{d:"m {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z",height:36,width:36,heightElements:[6.5,13,.4,6.1],widthElements:[9,9.3,8.7]},EVENT_TIMER_WH:{d:"M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ",height:36,width:36,heightElements:[10,2],widthElements:[3,7]},EVENT_TIMER_LINE:{d:"M {mx},{my} m {e.x0},{e.y0} l -{e.x1},{e.y1} ",height:36,width:36,heightElements:[10,3],widthElements:[0,0]},EVENT_MULTIPLE:{d:"m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z",height:36,width:36,heightElements:[6.28099,12.56199],widthElements:[3.1405,9.42149,12.56198]},EVENT_PARALLEL_MULTIPLE:{d:"m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} -{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z",height:36,width:36,heightElements:[2.56228,7.68683],widthElements:[2.56228,7.68683]},GATEWAY_EXCLUSIVE:{d:"m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} {e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} {e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z",height:17.5,width:17.5,heightElements:[8.5,6.5312,-6.5312,-8.5],widthElements:[6.5,-6.5,3,-3,5,-5]},GATEWAY_PARALLEL:{d:"m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z",height:30,width:30,heightElements:[5,12.5],widthElements:[5,12.5]},GATEWAY_EVENT_BASED:{d:"m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z",height:11,width:11,heightElements:[-6,6,12,-12],widthElements:[9,-3,-12]},GATEWAY_COMPLEX:{d:"m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} {e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} {e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} -{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z",height:17.125,width:17.125,heightElements:[4.875,3.4375,2.125,3],widthElements:[3.4375,2.125,4.875,3]},DATA_OBJECT_PATH:{d:"m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0",height:61,width:51,heightElements:[10,50,60],widthElements:[10,40,50,60]},DATA_OBJECT_COLLECTION_PATH:{d:"m{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10",height:10,width:10,heightElements:[],widthElements:[]},DATA_ARROW:{d:"m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z",height:61,width:51,heightElements:[],widthElements:[]},DATA_STORE:{d:"m {mx},{my} l 0,{e.y2} c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 l 0,-{e.y2} c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 m -{e.x2},{e.y0}c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0m -{e.x2},{e.y0}c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0",height:61,width:61,heightElements:[7,10,45],widthElements:[2,58,60]},TEXT_ANNOTATION:{d:"m {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0",height:30,width:10,heightElements:[30],widthElements:[10]},MARKER_SUB_PROCESS:{d:"m{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0",height:10,width:10,heightElements:[],widthElements:[]},MARKER_PARALLEL:{d:"m{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10",height:10,width:10,heightElements:[],widthElements:[]},MARKER_SEQUENTIAL:{d:"m{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0",height:10,width:10,heightElements:[],widthElements:[]},MARKER_COMPENSATION:{d:"m {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z",height:10,width:21,heightElements:[],widthElements:[]},MARKER_LOOP:{d:"m {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 -6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902",height:13.9,width:13.7,heightElements:[],widthElements:[]},MARKER_ADHOC:{d:"m {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 -3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 -2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z",height:4,width:15,heightElements:[],widthElements:[]},TASK_TYPE_SEND:{d:"m {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}",height:14,width:21,heightElements:[6,14],widthElements:[10.5,21]},TASK_TYPE_SCRIPT:{d:"m {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z m -7,-12 l 5,0 m -4.5,3 l 4.5,0 m -3,3 l 5,0m -4,3 l 5,0",height:15,width:12.6,heightElements:[6,14],widthElements:[10.5,21]},TASK_TYPE_USER_1:{d:"m {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 -4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 zm -8,6 l 0,5.5 m 11,0 l 0,-5"},TASK_TYPE_USER_2:{d:"m {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 -2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 "},TASK_TYPE_USER_3:{d:"m {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 -4.20799998,3.36699999 -4.20699998,4.34799999 z"},TASK_TYPE_MANUAL:{d:"m {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 -0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 -1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 -10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 -0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 -1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 -0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 -5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z"},TASK_TYPE_INSTANTIATING_SEND:{d:"m {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6"},TASK_TYPE_SERVICE:{d:"m {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 -1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 -0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 -1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 -0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z m 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z"},TASK_TYPE_SERVICE_FILL:{d:"m {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z"},TASK_TYPE_BUSINESS_RULE_HEADER:{d:"m {mx},{my} 0,4 20,0 0,-4 z"},TASK_TYPE_BUSINESS_RULE_MAIN:{d:"m {mx},{my} 0,12 20,0 0,-12 zm 0,8 l 20,0 m -13,-4 l 0,8"},MESSAGE_FLOW_MARKER:{d:"m {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6"}},this.getRawPath=function(e){return this.pathMap[e].d},this.getScaledPath=function(e,t){var n,i,r=this.pathMap[e];t.abspos?(n=t.abspos.x,i=t.abspos.y):(n=t.containerWidth*t.position.mx,i=t.containerHeight*t.position.my);var o={};if(t.position){for(var a=t.containerHeight/r.height*t.yScaleFactor,s=t.containerWidth/r.width*t.xScaleFactor,l=0;l':""}function wt(e,t,n){return k({id:e.id,type:e.$type,businessObject:e,di:t},n)}function Et(e,t,n){var i=e.waypoint;return!i||i.length<2?[xt(t),xt(n)]:i.map(function(e){return{x:e.x,y:e.y}})}function _t(e,t,n){return new Error(`element ${bt(t)} referenced by ${bt(e)}#${n} not yet drawn`)}function kt(e,t,n,i,r){this._eventBus=e,this._canvas=t,this._elementFactory=n,this._elementRegistry=i,this._textRenderer=r}kt.$inject=["eventBus","canvas","elementFactory","elementRegistry","textRenderer"],kt.prototype.add=function(e,t,n){var i,r,o,a,s,l,c;if(S(t,"bpmndi:BPMNPlane")){var p=S(e,"bpmn:SubProcess")?{id:e.id+"_plane"}:{};i=this._elementFactory.createRoot(wt(e,t,p)),this._canvas.addRootElement(i)}else if(S(t,"bpmndi:BPMNShape")){var u=!M(e,t),h=function(e){return S(e,"bpmn:Group")}(e);r=n&&(n.hidden||n.collapsed);var f=t.bounds;i=this._elementFactory.createShape(wt(e,t,{collapsed:u,hidden:r,x:Math.round(f.x),y:Math.round(f.y),width:Math.round(f.width),height:Math.round(f.height),isFrame:h})),S(e,"bpmn:BoundaryEvent")&&this._attachBoundary(e,i),S(e,"bpmn:Lane")&&(o=0),S(e,"bpmn:DataStoreReference")&&(a=n,s=xt(f),l=s.x,c=s.y,l>=a.x&&l<=a.x+a.width&&c>=a.y&&c<=a.y+a.height||(n=this._canvas.findRoot(n))),this._canvas.addShape(i,n,o)}else{if(!S(t,"bpmndi:BPMNEdge"))throw new Error(`unknown di ${bt(t)} for element ${bt(e)}`);var m=this._getSource(e),d=this._getTarget(e);r=n&&(n.hidden||n.collapsed),i=this._elementFactory.createConnection(wt(e,t,{hidden:r,source:m,target:d,waypoints:Et(t,m,d)})),S(e,"bpmn:DataAssociation")&&(n=this._canvas.findRoot(n)),this._canvas.addConnection(i,n,o)}return function(e){return S(e,"bpmn:Event")||S(e,"bpmn:Gateway")||S(e,"bpmn:DataStoreReference")||S(e,"bpmn:DataObjectReference")||S(e,"bpmn:DataInput")||S(e,"bpmn:DataOutput")||S(e,"bpmn:SequenceFlow")||S(e,"bpmn:MessageFlow")||S(e,"bpmn:Group")}(e)&&T(i)&&this.addLabel(e,t,i),this._eventBus.fire("bpmnElement.added",{element:i}),i},kt.prototype._attachBoundary=function(e,t){var n=e.attachedToRef;if(!n)throw new Error(`missing ${bt(e)}#attachedToRef`);var i=this._elementRegistry.get(n.id),r=i&&i.attachers;if(!i)throw _t(e,n,"attachedToRef");t.host=i,r||(i.attachers=r=[]),-1===r.indexOf(t)&&r.push(t)},kt.prototype.addLabel=function(e,t,n){var i,r,o;return i=D(t,n),(r=T(n))&&(i=this._textRenderer.getExternalLabelBounds(i,r)),o=this._elementFactory.createLabel(wt(e,t,{id:e.id+"_label",labelTarget:n,type:"label",hidden:n.hidden||!T(n),x:Math.round(i.x),y:Math.round(i.y),width:Math.round(i.width),height:Math.round(i.height)})),this._canvas.addShape(o,n.parent)},kt.prototype._getConnectedElement=function(e,t){var n,i,r=e.$type;if(i=e[t+"Ref"],"source"===t&&"bpmn:DataInputAssociation"===r&&(i=i&&i[0]),("source"===t&&"bpmn:DataOutputAssociation"===r||"target"===t&&"bpmn:DataInputAssociation"===r)&&(i=e.$parent),n=i&&this._getElement(i))return n;throw i?_t(e,i,t+"Ref"):new Error(`${bt(e)}#${t} Ref not specified`)},kt.prototype._getSource=function(e){return this._getConnectedElement(e,"source")},kt.prototype._getTarget=function(e){return this._getConnectedElement(e,"target")},kt.prototype._getElement=function(e){return this._elementRegistry.get(e.id)};var At={__depends__:[mt,{__depends__:[dt],bpmnImporter:["type",kt]}]};function St(e){this._counter=0,this._prefix=(e?e+"-":"")+Math.floor(1e9*Math.random())+"-"}St.prototype.next=function(){return this._prefix+ ++this._counter};var Rt=new St("ov");function Ct(e,t,n,i){var r,o;this._eventBus=t,this._canvas=n,this._elementRegistry=i,this._ids=Rt,this._overlayDefaults=k({show:null,scale:!0},e&&e.defaults),this._overlays={},this._overlayContainers=[],this._overlayRoot=(r=n.getContainer(),Ce(o=Se('
'),{position:"absolute",width:0,height:0}),r.insertBefore(o,r.firstChild),o),this._init()}function Mt(e,t,n){Ce(e,{left:t+"px",top:n+"px"})}function Pt(e,t){e.style.display=!1===t?"none":""}function Nt(e,t){e.style["transform-origin"]="top left",["","-ms-","-webkit-"].forEach(function(n){e.style[n+"transform"]=t})}Ct.$inject=["config.overlays","eventBus","canvas","elementRegistry"],Ct.prototype.get=function(e){if(p(e)&&(e={id:e}),p(e.element)&&(e.element=this._elementRegistry.get(e.element)),e.element){var t=this._getOverlayContainer(e.element,!0);return t?e.type?f(t.overlays,v({type:e.type})):t.overlays.slice():[]}return e.type?f(this._overlays,v({type:e.type})):e.id?this._overlays[e.id]:null},Ct.prototype.add=function(e,t,n){if(s(t)&&(n=t,t=null),e.id||(e=this._elementRegistry.get(e)),!n.position)throw new Error("must specifiy overlay position");if(!n.html)throw new Error("must specifiy overlay html");if(!e)throw new Error("invalid element specified");var i=this._ids.next();return n=k({},this._overlayDefaults,n,{id:i,type:t,element:e,html:n.html}),this._addOverlay(n),i},Ct.prototype.remove=function(e){var t=this.get(e)||[];a(t)||(t=[t]);var n=this;m(t,function(e){var t=n._getOverlayContainer(e.element,!0);if(e&&($e(e.html),$e(e.htmlContainer),delete e.htmlContainer,delete e.element,delete n._overlays[e.id]),t){var i=t.overlays.indexOf(e);-1!==i&&t.overlays.splice(i,1)}})},Ct.prototype.isShown=function(){return"none"!==this._overlayRoot.style.display},Ct.prototype.show=function(){Pt(this._overlayRoot)},Ct.prototype.hide=function(){Pt(this._overlayRoot,!1)},Ct.prototype.clear=function(){this._overlays={},this._overlayContainers=[],De(this._overlayRoot)},Ct.prototype._updateOverlayContainer=function(e){var t=e.element,n=e.html,i=t.x,r=t.y;if(t.waypoints){var o=O(t);i=o.x,r=o.y}Mt(n,i,r),Me(e.html,"data-container-id",t.id)},Ct.prototype._updateOverlay=function(e){var t,n,i=e.position,r=e.htmlContainer,o=e.element,a=i.left,s=i.top;void 0!==i.right&&(t=o.waypoints?O(o).width:o.width,a=-1*i.right+t);void 0!==i.bottom&&(n=o.waypoints?O(o).height:o.height,s=-1*i.bottom+n);Mt(r,a||0,s||0),this._updateOverlayVisibilty(e,this._canvas.viewbox())},Ct.prototype._createOverlayContainer=function(e){var t=Se('
');Ce(t,{position:"absolute"}),this._overlayRoot.appendChild(t);var n={html:t,element:e,overlays:[]};return this._updateOverlayContainer(n),this._overlayContainers.push(n),n},Ct.prototype._updateRoot=function(e){var t=e.scale||1,n="matrix("+[t,0,0,t,-1*e.x*t,-1*e.y*t].join(",")+")";Nt(this._overlayRoot,n)},Ct.prototype._getOverlayContainer=function(e,t){var n=h(this._overlayContainers,function(t){return t.element===e});return n||t?n:this._createOverlayContainer(e)},Ct.prototype._addOverlay=function(e){var t,n,i=e.id,r=e.element,o=e.html;o.get&&o.constructor.prototype.jquery&&(o=o.get(0)),p(o)&&(o=Se(o)),n=this._getOverlayContainer(r),Ce(t=Se('
'),{position:"absolute"}),t.appendChild(o),e.type&&Ne(t).add("djs-overlay-"+e.type),Pt(t,this._canvas.findRoot(r)===this._canvas.getRootElement()),e.htmlContainer=t,n.overlays.push(e),n.html.appendChild(t),this._overlays[i]=e,this._updateOverlay(e),this._updateOverlayVisibilty(e,this._canvas.viewbox())},Ct.prototype._updateOverlayVisibilty=function(e,t){var n=e.show,i=this._canvas.findRoot(e.element),o=n&&n.minZoom,a=n&&n.maxZoom,s=e.htmlContainer,l=!0;(i!==this._canvas.getRootElement()||n&&(r(o)&&o>t.scale||r(a)&&ai&&(o=(1/t.scale||1)*i)),r(o)&&(l="scale("+o+","+o+")"),Nt(s,l)},Ct.prototype._updateOverlaysVisibilty=function(e){var t=this;m(this._overlays,function(n){t._updateOverlayVisibilty(n,e)})},Ct.prototype._init=function(){var e=this._eventBus,t=this;e.on("canvas.viewbox.changing",function(e){t.hide()}),e.on("canvas.viewbox.changed",function(e){var n;n=e.viewbox,t._updateRoot(n),t._updateOverlaysVisibilty(n),t.show()}),e.on(["shape.remove","connection.remove"],function(e){var n=e.element;m(t.get({element:n}),function(e){t.remove(e.id)});var i=t._getOverlayContainer(n);if(i){$e(i.html);var r=t._overlayContainers.indexOf(i);-1!==r&&t._overlayContainers.splice(r,1)}}),e.on("element.changed",500,function(e){var n=e.element,i=t._getOverlayContainer(n,!0);i&&(m(i.overlays,function(e){t._updateOverlay(e)}),t._updateOverlayContainer(i))}),e.on("element.marker.update",function(e){var n=t._getOverlayContainer(e.element,!0);n&&Ne(n.html)[e.add?"add":"remove"](e.marker)}),e.on("root.set",function(){t._updateOverlaysVisibilty(t._canvas.viewbox())}),e.on("diagram.clear",this.clear,this)};var Bt={__init__:["overlays"],overlays:["type",Ct]};function Dt(e,t,n,i){e.on("element.changed",function(i){var r=i.element;(r.parent||r===t.getRootElement())&&(i.gfx=n.getGraphics(r)),i.gfx&&e.fire(L(r)+".changed",i)}),e.on("elements.changed",function(t){var n=t.elements;n.forEach(function(t){e.fire("element.changed",{element:t})}),i.updateContainments(n)}),e.on("shape.changed",function(e){i.update("shape",e.element,e.gfx)}),e.on("connection.changed",function(e){i.update("connection",e.element,e.gfx)})}Dt.$inject=["eventBus","canvas","elementRegistry","graphicsFactory"];var Tt={__init__:["changeSupport"],changeSupport:["type",Dt]};function Ot(e){this._eventBus=e}function Lt(e){return function(t,n,i,r,o){(c(t)||l(t))&&(o=r,r=i,i=n,n=t,t=null),this.on(t,e,n,i,r,o)}}function It(e,t){t.invoke(Ot,this),this.executed(function(t){var n=t.context;n.rootElement?e.setRootElement(n.rootElement):n.rootElement=e.getRootElement()}),this.revert(function(t){var n=t.context;n.rootElement&&e.setRootElement(n.rootElement)})}Ot.$inject=["eventBus"],Ot.prototype.on=function(e,t,n,i,r,o){if((c(t)||l(t))&&(o=r,r=i,i=n,n=t,t=null),c(n)&&(o=r,r=i,i=n,n=1e3),s(r)&&(o=r,r=!1),!c(i))throw new Error("handlerFn must be a function");a(e)||(e=[e]);var p=this._eventBus;m(e,function(e){var a=["commandStack",e,t].filter(function(e){return e}).join(".");p.on(a,n,r?function(e,t){return function(n){return e.call(t||null,n.context,n.command,n)}}(i,o):i,o)})},Ot.prototype.canExecute=Lt("canExecute"),Ot.prototype.preExecute=Lt("preExecute"),Ot.prototype.preExecuted=Lt("preExecuted"),Ot.prototype.execute=Lt("execute"),Ot.prototype.executed=Lt("executed"),Ot.prototype.postExecute=Lt("postExecute"),Ot.prototype.postExecuted=Lt("postExecuted"),Ot.prototype.revert=Lt("revert"),Ot.prototype.reverted=Lt("reverted"),e(It,Ot),It.$inject=["canvas","injector"];var jt={__init__:["rootElementsBehavior"],rootElementsBehavior:["type",It]},Ft={"&":"&","<":"<",">":">",'"':""","'":"'"};function Vt(e){return(e=""+e)&&e.replace(/[&<>"']/g,function(e){return Ft[e]})}var zt="_plane";function Wt(e){var t=e.id;return S(e,"bpmn:SubProcess")?function(e){return e+zt}(t):t}function $t(e,t,n){var i=Se('
    '),r=n.getContainer(),o=Ne(r);r.appendChild(i);var a=[];function s(e){e&&(a=function(e){for(var t=R(e),n=[],i=t;i;i=i.$parent)(S(i,"bpmn:SubProcess")||S(i,"bpmn:Process"))&&n.push(i);return n.reverse()}(e));var r=a.flatMap(function(e){var i=n.findRoot(Wt(e))||n.findRoot(e.id);if(!i&&S(e,"bpmn:Process")){var r=t.find(function(t){var n=R(t);return n&&n.get("processRef")===e});i=r&&n.findRoot(r.id)}return i?[{parent:e,parentPlane:i}]:[]});i.innerHTML="";var s=r.length>1;o.toggle("bjs-breadcrumbs-shown",s),r.forEach(function(e,t){var o,a=e.parent,s=e.parentPlane,l=Vt(a.name||a.id);t===r.length-1?o=Se('
  • '+l+"
  • "):(o=Se('
  • '+l+"
  • ")).addEventListener("click",function(e){e.preventDefault(),n.setRootElement(s)});i.appendChild(o)})}e.on("element.changed",function(e){var t=R(e.element);h(a,function(e){return e===t})&&s()}),e.on("root.set",function(e){s(e.element)})}function Gt(e,t){var n=null,i=new Ht;e.on("root.set",function(e){var r=e.element,o=t.viewbox(),a=i.get(r);if(i.set(n,{x:o.x,y:o.y,zoom:o.scale}),n=r,S(r,"bpmn:SubProcess")||a){a=a||{x:0,y:0,zoom:1};var s=(o.x-a.x)*o.scale,l=(o.y-a.y)*o.scale;0===s&&0===l||t.scroll({dx:s,dy:l}),a.zoom!==o.scale&&t.zoom(a.zoom,{x:0,y:0})}}),e.on("diagram.clear",function(){i.clear(),n=null})}function Ht(){this._entries=[],this.set=function(e,t){var n=!1;for(var i in this._entries)if(this._entries[i][0]===e){this._entries[i][1]=t,n=!0;break}n||this._entries.push([e,t])},this.get=function(e){for(var t in this._entries)if(this._entries[t][0]===e)return this._entries[t][1];return null},this.clear=function(){this._entries.length=0},this.remove=function(e){var t=-1;for(var n in this._entries)if(this._entries[n][0]===e){t=n;break}-1!==t&&this._entries.splice(t,1)}}$t.$inject=["eventBus","elementRegistry","canvas"],Gt.$inject=["eventBus","canvas"];var Kt=180,Ut=160;function qt(e,t){this._eventBus=e,this._moddle=t;var n=this;e.on("import.render.start",1500,function(e,t){n._handleImport(t.definitions)})}function Yt(e){return S(e,"bpmndi:BPMNDiagram")?e:Yt(e.$parent)}qt.prototype._handleImport=function(e){if(e.diagrams){var t=this;this._definitions=e,this._processToDiagramMap={},e.diagrams.forEach(function(e){e.plane&&e.plane.bpmnElement&&(t._processToDiagramMap[e.plane.bpmnElement.id]=e)}),e.diagrams.filter(e=>e.plane).flatMap(e=>t._createNewDiagrams(e.plane)).forEach(function(e){t._movePlaneElementsToOrigin(e.plane)})}},qt.prototype._createNewDiagrams=function(e){var t=this,n=[],i=[];e.get("planeElement").forEach(function(t){var r=t.bpmnElement;if(r){var o=r.$parent;S(r,"bpmn:SubProcess")&&!t.isExpanded&&n.push(r),function(e,t){var n=e.$parent;if(!S(n,"bpmn:SubProcess")||n===t.bpmnElement)return!1;if(function(e,t){return g(t,function(t){return S(e,t)})}(e,["bpmn:DataInputAssociation","bpmn:DataOutputAssociation"]))return!1;return!0}(r,e)&&i.push({diElement:t,parent:o})}});var r=[];return n.forEach(function(e){if(!t._processToDiagramMap[e.id]){var n=t._createDiagram(e);t._processToDiagramMap[e.id]=n,r.push(n)}}),i.forEach(function(e){for(var i=e.diElement,r=e.parent;r&&-1===n.indexOf(r);)r=r.$parent;if(r){var o=t._processToDiagramMap[r.id];t._moveToDiPlane(i,o.plane)}}),r},qt.prototype._movePlaneElementsToOrigin=function(e){var t=e.get("planeElement"),n=function(e){var t={top:1/0,right:-1/0,bottom:-1/0,left:1/0};return e.planeElement.forEach(function(e){if(e.bounds){var n=yt(e.bounds);t.top=Math.min(n.top,t.top),t.left=Math.min(n.left,t.left)}}),function(e){return{x:e.left,y:e.top,width:e.right-e.left,height:e.bottom-e.top}}(t)}(e),i=n.x-Kt,r=n.y-Ut;t.forEach(function(e){e.waypoint?e.waypoint.forEach(function(e){e.x=e.x-i,e.y=e.y-r}):e.bounds&&(e.bounds.x=e.bounds.x-i,e.bounds.y=e.bounds.y-r)})},qt.prototype._moveToDiPlane=function(e,t){var n=Yt(e).plane.get("planeElement");n.splice(n.indexOf(e),1),t.get("planeElement").push(e)},qt.prototype._createDiagram=function(e){var t=this._moddle.create("bpmndi:BPMNPlane",{bpmnElement:e}),n=this._moddle.create("bpmndi:BPMNDiagram",{plane:t});return t.$parent=n,t.bpmnElement=e,n.$parent=this._definitions,this._definitions.diagrams.push(n),n},qt.$inject=["eventBus","moddle"];var Xt=250;function Zt(e,t,n,i,r){Ot.call(this,t),this._canvas=e,this._eventBus=t,this._elementRegistry=n,this._overlays=i,this._translate=r;var o=this;this.executed("shape.toggleCollapse",Xt,function(e){var t=e.shape;o._canDrillDown(t)?o._addOverlay(t):o._removeOverlay(t)},!0),this.reverted("shape.toggleCollapse",Xt,function(e){var t=e.shape;o._canDrillDown(t)?o._addOverlay(t):o._removeOverlay(t)},!0),this.executed(["shape.create","shape.move","shape.delete"],Xt,function(e){var t=e.oldParent,n=e.newParent||e.parent,i=e.shape;o._canDrillDown(i)&&o._addOverlay(i),o._updateDrilldownOverlay(t),o._updateDrilldownOverlay(n),o._updateDrilldownOverlay(i)},!0),this.reverted(["shape.create","shape.move","shape.delete"],Xt,function(e){var t=e.oldParent,n=e.newParent||e.parent,i=e.shape;o._canDrillDown(i)&&o._addOverlay(i),o._updateDrilldownOverlay(t),o._updateDrilldownOverlay(n),o._updateDrilldownOverlay(i)},!0),t.on("import.render.complete",function(){n.filter(function(e){return o._canDrillDown(e)}).map(function(e){o._addOverlay(e)})})}e(Zt,Ot),Zt.prototype._updateDrilldownOverlay=function(e){var t=this._canvas;if(e){var n=t.findRoot(e);n&&this._updateOverlayVisibility(n)}},Zt.prototype._canDrillDown=function(e){var t=this._canvas;return S(e,"bpmn:SubProcess")&&t.findRoot(Wt(e))},Zt.prototype._updateOverlayVisibility=function(e){var t=this._overlays,n=R(e),i=t.get({element:n.id,type:"drilldown"})[0];if(i){var r=n&&n.get("flowElements")&&n.get("flowElements").length;Ne(i.html).toggle("bjs-drilldown-empty",!r)}},Zt.prototype._addOverlay=function(e){var t=this._canvas,n=this._overlays,i=R(e);n.get({element:e,type:"drilldown"}).length&&this._removeOverlay(e);var r=Se(''),o=i.get("name")||i.get("id"),a=this._translate("Open {element}",{element:o});r.setAttribute("title",a),r.addEventListener("click",function(){t.setRootElement(t.findRoot(Wt(e)))}),n.add(e,"drilldown",{position:{bottom:-7,right:-8},html:r}),this._updateOverlayVisibility(e)},Zt.prototype._removeOverlay=function(e){this._overlays.remove({element:e,type:"drilldown"})},Zt.$inject=["canvas","eventBus","elementRegistry","overlays","translate"];var Jt={__depends__:[Bt,Tt,jt],__init__:["drilldownBreadcrumbs","drilldownOverlayBehavior","drilldownCentering","subprocessCompatibility"],drilldownBreadcrumbs:["type",$t],drilldownCentering:["type",Gt],drilldownOverlayBehavior:["type",Zt],subprocessCompatibility:["type",qt]};function Qt(e){return e.originalEvent||e.srcEvent}function en(e){return e.pointers&&e.pointers.length&&(e=e.pointers[0]),e.touches&&e.touches.length&&(e=e.touches[0]),e?{x:e.clientX,y:e.clientY}:null}function tn(e,t){return(Qt(e)||e).button===t}function nn(e){return tn(e,0)}function rn(e){return!0}function on(e){return nn(e)||function(e){return tn(e,1)}(e)}function an(e,t,n){var i=this;function r(n,i,r){var o,a;(function(e,t){var n=l[e]||nn;return!n(t)})(n,i)||(r?a=t.getGraphics(r):(o=i.delegateTarget||i.target)&&(a=o,r=t.get(a)),a&&r&&!1===e.fire(n,{element:r,gfx:a,originalEvent:i})&&(i.stopPropagation(),i.preventDefault()))}var o={};function a(e){return o[e]}var s={click:"element.click",contextmenu:"element.contextmenu",dblclick:"element.dblclick",mousedown:"element.mousedown",mousemove:"element.mousemove",mouseover:"element.hover",mouseout:"element.out",mouseup:"element.mouseup"},l={"element.contextmenu":rn,"element.mousedown":on,"element.mouseup":on,"element.click":on,"element.dblclick":on};function c(e,t,n,i){var a=o[n]=function(e){r(n,e)};i&&(l[n]=i),a.$delegate=ze.bind(e,"svg, .djs-element",t,a)}function p(e,t,n){var i=a(n);i&&ze.unbind(e,t,i.$delegate)}e.on("canvas.destroy",function(e){var t;t=e.svg,m(s,function(e,n){p(t,n,e)})}),e.on("canvas.init",function(e){var t;t=e.svg,m(s,function(e,n){c(t,n,e)})}),e.on(["shape.added","connection.added"],function(t){var n=t.element,i=t.gfx;e.fire("interactionEvents.createHit",{element:n,gfx:i})}),e.on(["shape.changed","connection.changed"],500,function(t){var n=t.element,i=t.gfx;e.fire("interactionEvents.updateHit",{element:n,gfx:i})}),e.on("interactionEvents.createHit",500,function(e){var t=e.element,n=e.gfx;i.createDefaultHit(t,n)}),e.on("interactionEvents.updateHit",function(e){var t=e.element,n=e.gfx;i.updateDefaultHit(t,n)});var u=d("djs-hit djs-hit-stroke"),h=d("djs-hit djs-hit-click-stroke"),f={all:d("djs-hit djs-hit-all"),"click-stroke":h,stroke:u,"no-move":d("djs-hit djs-hit-no-move")};function d(e,t){return t=k({stroke:"white",strokeWidth:15},t||{}),n.cls(e,["no-fill","no-border"],t)}function y(e,t){var n=f[t];if(!n)throw new Error("invalid hit type <"+t+">");return z(e,n),e}function g(e,t){j(e,t)}this.removeHits=function(e){var t;m((t=".djs-hit",(e||document).querySelectorAll(t)),oe)},this.createDefaultHit=function(e,t){var n,i=e.waypoints,r=e.isFrame;return i?this.createWaypointsHit(t,i):(n=r?"stroke":"all",this.createBoxHit(t,n,{width:e.width,height:e.height}))},this.createWaypointsHit=function(e,t){var n=me(t);return y(n,"stroke"),g(e,n),n},this.createBoxHit=function(e,t,n){n=k({x:0,y:0},n);var i=q("rect");return y(i,t),z(i,n),g(e,i),i},this.updateDefaultHit=function(e,t){var n=We(".djs-hit",t);if(n)return e.waypoints?de(n,e.waypoints):z(n,{width:e.width,height:e.height}),n},this.fire=r,this.triggerMouseEvent=function(e,t,n){var i=s[e];if(!i)throw new Error("unmapped DOM event name <"+e+">");return r(i,t,n)},this.mouseHandler=a,this.registerEvent=c,this.unregisterEvent=p}an.$inject=["eventBus","elementRegistry","styles"];var sn={__init__:["interactionEvents"],interactionEvents:["type",an]};function ln(e,t){this._eventBus=e,this._canvas=t,this._selectedElements=new Set;var n=this;e.on(["shape.remove","connection.remove"],function(e){var t=e.element;n.deselect(t)}),e.on(["diagram.clear","root.set"],function(e){n.select(null)})}ln.$inject=["eventBus","canvas"],ln.prototype.deselect=function(e){if(this._selectedElements.has(e)){var t=this.get();this._selectedElements.delete(e),this._eventBus.fire("selection.changed",{oldSelection:t,newSelection:this.get()})}},ln.prototype.get=function(){return Array.from(this._selectedElements)},ln.prototype.isSelected=function(e){return this._selectedElements.has(e)},ln.prototype.select=function(e,t){var n=this.get();a(e)||(e=e?[e]:[]);var i=this._canvas,r=i.getRootElement();e=e.filter(function(e){var t=i.findRoot(e);return r===t}),t?m(e,e=>{this._selectedElements.add(e)}):this._selectedElements=new Set(e),this._eventBus.fire("selection.changed",{oldSelection:n,newSelection:this.get()})};var cn="hover",pn="selected";function un(e,t){function n(t,n){e.addMarker(t,n)}function i(t,n){e.removeMarker(t,n)}this._canvas=e,t.on("element.hover",function(e){n(e.element,cn)}),t.on("element.out",function(e){i(e.element,cn)}),t.on("selection.changed",function(e){function t(e){i(e,pn)}function r(e){n(e,pn)}var o=new Set(e.oldSelection),a=new Set(e.newSelection);for(let e of o)a.has(e)||t(e);for(let e of a)o.has(e)||r(e)})}function hn(e,t,n,i){e.on("create.end",500,function(e){var n=e.context,i=n.canExecute,r=n.elements,o=(n.hints||{}).autoSelect;if(i){if(!1===o)return;a(o)?t.select(o):t.select(r.filter(fn))}}),e.on("connect.end",500,function(e){var n=e.context.connection;n&&t.select(n)}),e.on("shape.move.end",500,function(e){var n=e.previousSelection||[],r=i.get(e.context.shape.id);h(n,function(e){return r.id===e.id})||t.select(r)}),e.on("element.click",function(e){if(nn(e)){var i=e.element;i===n.getRootElement()&&(i=null);var r=t.isSelected(i),o=t.get().length>1,a=function(e){var t=Qt(e)||e;return nn(e)&&t.shiftKey}(e);if(r&&o)return a?t.deselect(i):t.select(i);r?t.deselect(i):t.select(i,a)}})}function fn(e){return!e.hidden}un.$inject=["canvas","eventBus"],hn.$inject=["eventBus","selection","canvas","elementRegistry"];var mn={__init__:["selectionVisuals","selectionBehavior"],__depends__:[sn],selection:["type",ln],selectionVisuals:["type",un],selectionBehavior:["type",hn]};const dn=/^class[ {]/;function yn(e){return Array.isArray(e)}function gn(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function vn(...e){1===e.length&&yn(e[0])&&(e=e[0]);const t=(e=[...e]).pop();return t.$inject=e,t}const xn=/constructor\s*[^(]*\(\s*([^)]*)\)/m,bn=/^(?:async\s+)?(?:function\s*[^(]*)?(?:\(\s*([^)]*)\)|(\w+))/m,wn=/\/\*([^*]*)\*\//m;function En(e){if("function"!=typeof e)throw new Error(`Cannot annotate "${e}". Expected a function!`);const t=e.toString().match(function(e){return dn.test(e.toString())}(e)?xn:bn);if(!t)return[];const n=t[1]||t[2];return n&&n.split(",").map(e=>{const t=e.match(wn);return(t&&t[1]||e).trim()})||[]}function _n(e,t){const n=t||{get:function(e,t){if(i.push(e),!1===t)return null;throw s(`No provider for "${e}"!`)}},i=[],r=this._providers=Object.create(n._providers||null),o=this._instances=Object.create(null),a=o.injector=this,s=function(e){const t=i.join(" -> ");return i.length=0,new Error(t?`${e} (Resolving: ${t})`:e)};function l(e,t){if(!r[e]&&e.includes(".")){const t=e.split(".");let n=l(t.shift());for(;t.length;)n=n[t.shift()];return n}if(gn(o,e))return o[e];if(gn(r,e)){if(-1!==i.indexOf(e))throw i.push(e),s("Cannot resolve circular dependency!");return i.push(e),o[e]=r[e][0](r[e][1]),i.pop(),o[e]}return n.get(e,t)}function c(e,t){if(void 0===t&&(t={}),"function"!=typeof e){if(!yn(e))throw s(`Cannot invoke "${e}". Expected a function!`);e=vn(e.slice())}return{fn:e,dependencies:(e.$inject||En(e)).map(e=>gn(t,e)?t[e]:l(e))}}function p(e){const{fn:t,dependencies:n}=c(e);return new(Function.prototype.bind.call(t,null,...n))}function u(e,t,n){const{fn:i,dependencies:r}=c(e,n);return i.apply(t,r)}function h(e){return vn(t=>e.get(t))}function f(e,t){if(t&&t.length){const n=Object.create(null),i=Object.create(null),o=[],a=[],s=[];let l,c,p,u;for(let e in r)l=r[e],-1!==t.indexOf(e)&&("private"===l[2]?(c=o.indexOf(l[3]),-1===c?(p=l[3].createChild([],t),u=h(p),o.push(l[3]),a.push(p),s.push(u),n[e]=[u,e,"private",p]):n[e]=[s[c],e,"private",a[c]]):n[e]=[l[2],l[1]],i[e]=!0),"factory"!==l[2]&&"type"!==l[2]||!l[1].$scope||t.forEach(t=>{-1!==l[1].$scope.indexOf(t)&&(n[e]=[l[2],l[1]],i[t]=!0)});t.forEach(e=>{if(!i[e])throw new Error('No provider for "'+e+'". Cannot use provider from the parent!')}),e.unshift(n)}return new _n(e,a)}const m={factory:u,type:p,value:function(e){return e}};function d(e,t){const n=e.__init__||[];return function(){n.forEach(e=>{"string"==typeof e?t.get(e):t.invoke(e)})}}function y(e){const t=e.__exports__;if(t){const n=e.__modules__,i=Object.keys(e).reduce((t,n)=>("__exports__"!==n&&"__modules__"!==n&&"__init__"!==n&&"__depends__"!==n&&(t[n]=e[n]),t),Object.create(null)),o=f((n||[]).concat(i)),a=vn(function(e){return o.get(e)});t.forEach(function(e){r[e]=[a,e,"private",o]});const s=(e.__init__||[]).slice();return s.unshift(function(){o.init()}),d(e=Object.assign({},e,{__init__:s}),o)}return Object.keys(e).forEach(function(t){if("__init__"===t||"__depends__"===t)return;const n=e[t];if("private"===n[2])return void(r[t]=n);const i=n[0],o=n[1];r[t]=[m[i],kn(i,o),i]}),d(e,a)}function g(e,t){return-1!==e.indexOf(t)||-1!==(e=(t.__depends__||[]).reduce(g,e)).indexOf(t)?e:e.concat(t)}this.get=l,this.invoke=u,this.instantiate=p,this.createChild=f,this.init=function(e){const t=e.reduce(g,[]).map(y);let n=!1;return function(){n||(n=!0,t.forEach(e=>e()))}}(e)}function kn(e,t){return"value"!==e&&yn(t)&&(t=vn(t.slice())),t}function An(e,t){A.call(this,e,1),this.CONNECTION_STYLE=t.style(["no-fill"],{strokeWidth:5,stroke:"fuchsia"}),this.SHAPE_STYLE=t.style({fill:"white",stroke:"fuchsia",strokeWidth:2}),this.FRAME_STYLE=t.style(["no-fill"],{stroke:"fuchsia",strokeDasharray:4,strokeWidth:2})}e(An,A),An.prototype.canRender=function(){return!0},An.prototype.drawShape=function(e,t,n){var i=q("rect");return z(i,{x:0,y:0,width:t.width||0,height:t.height||0}),I(t)?z(i,k({},this.FRAME_STYLE,n||{})):z(i,k({},this.SHAPE_STYLE,n||{})),j(e,i),i},An.prototype.drawConnection=function(e,t,n){var i=me(t.waypoints,k({},this.CONNECTION_STYLE,n||{}));return j(e,i),i},An.prototype.getShapePath=function(e){var t=e.x,n=e.y,i=e.width;return le([["M",t,n],["l",i,0],["l",0,e.height],["l",-i,0],["z"]])},An.prototype.getConnectionPath=function(e){var t,n,i=e.waypoints,r=[];for(t=0;n=i[t];t++)n=n.original||n,r.push([0===t?"M":"L",n.x,n.y]);return le(r)},An.$inject=["eventBus","styles"];var Sn={__init__:["defaultRenderer"],defaultRenderer:["type",An],styles:["type",function(){var e={"no-fill":{fill:"none"},"no-border":{strokeOpacity:0},"no-events":{pointerEvents:"none"}},t=this;this.cls=function(e,t,n){return k(this.style(t,n),{class:e})},this.style=function(t,n){a(t)||n||(n=t,t=[]);var i=d(t,function(t,n){return k(t,e[n]||{})},{});return n?k(i,n):i},this.computeStyle=function(e,n,i){return a(n)||(i=n,n=[]),t.style(n||[],k({},i,e||{}))}}]};function Rn(e,t){return Math.round(e*t)/t}function Cn(e){return l(e)?e+"px":e}function Mn(e){for(;e.parent;)e=e.parent;return e}function Pn(e,t,n){const i=q("g");$(i).add(t);const r=void 0!==n?n:e.childNodes.length-1;return e.insertBefore(i,e.childNodes[r]||null),i}const Nn={shape:["x","y","width","height"],connection:["waypoints"]};function Bn(e,t,n,i){this._eventBus=t,this._elementRegistry=i,this._graphicsFactory=n,this._rootsIdx=0,this._layers={},this._planes=[],this._rootElement=null,this._focused=!1,this._init(e||{})}function Dn(e,t){const n="matrix("+t.a+","+t.b+","+t.c+","+t.d+","+t.e+","+t.f+")";e.setAttribute("transform",n)}Bn.$inject=["config.canvas","eventBus","graphicsFactory","elementRegistry"],Bn.prototype._init=function(e){const t=this._eventBus,n=this._container=function(e){const t=(e=k({},{width:"100%",height:"100%"},e)).container||document.body,n=document.createElement("div");return n.setAttribute("class","djs-container djs-parent bio-theme-parent"),Ce(n,{position:"relative",overflow:"hidden",width:Cn(e.width),height:Cn(e.height)}),t.appendChild(n),n}(e),i=this._svg=q("svg");z(i,{width:"100%",height:"100%"}),Me(i,"tabindex",0),e.autoFocus&&t.on("element.hover",()=>{this.restoreFocus()}),t.on("element.mousedown",500,e=>{this.focus()}),i.addEventListener("focusin",()=>{this._setFocused(!0)}),i.addEventListener("focusout",()=>{this._setFocused(!1)}),i.addEventListener("mouseover",()=>{this._eventBus.fire("canvas.mouseover")}),i.addEventListener("mouseout",()=>{this._eventBus.fire("canvas.mouseout")}),j(n,i);const r=this._viewport=Pn(i,"viewport");e.deferUpdate&&(this._viewboxChanged=E(this._viewboxChanged,300)),this._restoreFocus=E(this._restoreFocus,0),t.on("diagram.init",()=>{t.fire("canvas.init",{svg:i,viewport:r})}),t.on(["shape.added","connection.added","shape.removed","connection.removed","elements.changed","root.set"],()=>{delete this._cachedViewbox}),t.on("diagram.destroy",500,this._destroy,this),t.on("diagram.clear",500,this._clear,this)},Bn.prototype._destroy=function(){this._restoreFocus.cancel(),this._eventBus.fire("canvas.destroy",{svg:this._svg,viewport:this._viewport});const e=this._container.parentNode;e&&e.removeChild(this._container),delete this._svg,delete this._container,delete this._layers,delete this._planes,delete this._rootElement,delete this._viewport},Bn.prototype._setFocused=function(e){e!=this._focused&&(this._focused=e,this._eventBus.fire("canvas.focus.changed",{focused:e}))},Bn.prototype._clear=function(){this._elementRegistry.getAll().forEach(e=>{const t=L(e);"root"===t?this.removeRootElement(e):this._removeElement(e,t)}),this._planes=[],this._rootElement=null,delete this._cachedViewbox},Bn.prototype.focus=function(){this._svg.focus({preventScroll:!0}),this._setFocused(!0)},Bn.prototype.restoreFocus=function(){this._restoreFocus()},Bn.prototype._restoreFocus=function(){document.activeElement===document.body&&this.focus()},Bn.prototype.isFocused=function(){return this._focused},Bn.prototype.getDefaultLayer=function(){return this.getLayer("base",0)},Bn.prototype.getLayer=function(e,t){if(!e)throw new Error("must specify a name");let n=this._layers[e];if(n||(n=this._layers[e]=this._createLayer(e,t)),void 0!==t&&n.index!==t)throw new Error("layer <"+e+"> already created at index <"+t+">");return n.group},Bn.prototype._getChildIndex=function(e){return d(this._layers,function(t,n){return n.visible&&e>=n.index&&t++,t},0)},Bn.prototype._createLayer=function(e,t){void 0===t&&(t=1);const n=this._getChildIndex(t);return{group:Pn(this._viewport,"layer-"+e,n),index:t,visible:!0}},Bn.prototype.showLayer=function(e){if(!e)throw new Error("must specify a name");const t=this._layers[e];if(!t)throw new Error("layer <"+e+"> does not exist");const n=this._viewport,i=t.group,r=t.index;if(t.visible)return i;const o=this._getChildIndex(r);return n.insertBefore(i,n.childNodes[o]||null),t.visible=!0,i},Bn.prototype.hideLayer=function(e){if(!e)throw new Error("must specify a name");const t=this._layers[e];if(!t)throw new Error("layer <"+e+"> does not exist");const n=t.group;return t.visible?(oe(n),t.visible=!1,n):n},Bn.prototype._removeLayer=function(e){const t=this._layers[e];t&&(delete this._layers[e],oe(t.group))},Bn.prototype.getActiveLayer=function(){const e=this._findPlaneForRoot(this.getRootElement());return e?e.layer:null},Bn.prototype.findRoot=function(e){const t=this._resolveElements(e);let n;for(const e of t){const t=this._findPlaneForRoot(Mn(e));if(!t)return;if(n&&t.rootElement!==n)return;n=t.rootElement}return n},Bn.prototype.getRootElements=function(){return this._planes.map(function(e){return e.rootElement})},Bn.prototype._findPlaneForRoot=function(e){return h(this._planes,function(t){return t.rootElement===e})},Bn.prototype._resolveElement=function(e){return"string"==typeof e?this._elementRegistry.get(e):e},Bn.prototype._resolveElements=function(e){return a(e)||(e=[e]),e.map(e=>this._resolveElement(e)).filter(Boolean)},Bn.prototype.getContainer=function(){return this._container},Bn.prototype._updateMarker=function(e,t,n){let i;(e=this._resolveElement(e)).markers=e.markers||new Set,i=this._elementRegistry._elements[e.id],i&&(m([i.gfx,i.secondaryGfx],function(i){i&&(n?(e.markers.add(t),$(i).add(t)):(e.markers.delete(t),$(i).remove(t)))}),this._eventBus.fire("element.marker.update",{element:e,gfx:i.gfx,marker:t,add:!!n}))},Bn.prototype.addMarker=function(e,t){this._updateMarker(e,t,!0)},Bn.prototype.removeMarker=function(e,t){this._updateMarker(e,t,!1)},Bn.prototype.hasMarker=function(e,t){return!!(e=this._resolveElement(e)).markers&&e.markers.has(t)},Bn.prototype.toggleMarker=function(e,t){this.hasMarker(e,t)?this.removeMarker(e,t):this.addMarker(e,t)},Bn.prototype.getRootElement=function(){const e=this._rootElement;return e||this._planes.length?e:this.setRootElement(this.addRootElement(null))},Bn.prototype.addRootElement=function(e){const t=this._rootsIdx++;e||(e={id:"__implicitroot_"+t,children:[],isImplicit:!0});const n=e.layer="root-"+t;this._ensureValid("root",e);const i=this.getLayer(n,0);return this.hideLayer(n),this._addRoot(e,i),this._planes.push({rootElement:e,layer:i}),e},Bn.prototype.removeRootElement=function(e){e=this._resolveElement(e);if(this._findPlaneForRoot(e))return this._removeRoot(e),this._removeLayer(e.layer),this._planes=this._planes.filter(function(t){return t.rootElement!==e}),this._rootElement===e&&(this._rootElement=null),e},Bn.prototype.setRootElement=function(e){if(e===this._rootElement)return e;let t;if(!e)throw new Error("rootElement required");return t=this._findPlaneForRoot(e),t||(e=this.addRootElement(e)),this._setRoot(e),e},Bn.prototype._removeRoot=function(e){const t=this._elementRegistry,n=this._eventBus;n.fire("root.remove",{element:e}),n.fire("root.removed",{element:e}),t.remove(e)},Bn.prototype._addRoot=function(e,t){const n=this._elementRegistry,i=this._eventBus;i.fire("root.add",{element:e}),n.add(e,t),i.fire("root.added",{element:e,gfx:t})},Bn.prototype._setRoot=function(e,t){const n=this._rootElement;n&&(this._elementRegistry.updateGraphics(n,null,!0),this.hideLayer(n.layer)),e&&(t||(t=this._findPlaneForRoot(e).layer),this._elementRegistry.updateGraphics(e,this._svg,!0),this.showLayer(e.layer)),this._rootElement=e,this._eventBus.fire("root.set",{element:e})},Bn.prototype._ensureValid=function(e,t){if(!t.id)throw new Error("element must have an id");if(this._elementRegistry.get(t.id))throw new Error("element <"+t.id+"> already exists");const n=Nn[e],i=y(n,function(e){return void 0!==t[e]});if(!i)throw new Error("must supply { "+n.join(", ")+" } with "+e)},Bn.prototype._setParent=function(e,t,n){!function(e,t,n){if(e&&t){"number"!=typeof n&&(n=-1);var i=e.indexOf(t);if(-1!==i){if(i===n)return;if(-1===n)return;e.splice(i,1)}-1!==n?e.splice(n,0,t):e.push(t)}}(t.children,e,n),e.parent=t},Bn.prototype._addElement=function(e,t,n,i){n=n||this.getRootElement();const r=this._eventBus,o=this._graphicsFactory;this._ensureValid(e,t),r.fire(e+".add",{element:t,parent:n}),this._setParent(t,n,i);const a=o.create(e,t,i);return this._elementRegistry.add(t,a),o.update(e,t,a),r.fire(e+".added",{element:t,gfx:a}),t},Bn.prototype.addShape=function(e,t,n){return this._addElement("shape",e,t,n)},Bn.prototype.addConnection=function(e,t,n){return this._addElement("connection",e,t,n)},Bn.prototype._removeElement=function(e,t){const n=this._elementRegistry,i=this._graphicsFactory,r=this._eventBus;if(e=n.get(e.id||e))return r.fire(t+".remove",{element:e}),i.remove(e),function(e,t){if(!e||!t)return-1;var n=e.indexOf(t);-1!==n&&e.splice(n,1)}(e.parent&&e.parent.children,e),e.parent=null,r.fire(t+".removed",{element:e}),n.remove(e),e},Bn.prototype.removeShape=function(e){return this._removeElement(e,"shape")},Bn.prototype.removeConnection=function(e){return this._removeElement(e,"connection")},Bn.prototype.getGraphics=function(e,t){return this._elementRegistry.getGraphics(e,t)},Bn.prototype._changeViewbox=function(e){this._eventBus.fire("canvas.viewbox.changing"),e.apply(this),this._cachedViewbox=null,this._viewboxChanged()},Bn.prototype._viewboxChanged=function(){this._eventBus.fire("canvas.viewbox.changed",{viewbox:this.viewbox()})},Bn.prototype.viewbox=function(e){if(void 0===e&&this._cachedViewbox)return structuredClone(this._cachedViewbox);const t=this._viewport,n=this.getSize();let i,r,o,a,s,l,c;return e?(this._changeViewbox(function(){s=Math.min(n.width/e.width,n.height/e.height);const i=this._svg.createSVGMatrix().scale(s).translate(-e.x,-e.y);se(t,i)}),e):(o=this._rootElement?this.getActiveLayer():null,i=o&&o.getBBox()||{},a=se(t),r=a?a.matrix:function(e,t,n,i,r,o){var a=X().createSVGMatrix();switch(arguments.length){case 0:return a;case 1:return Z(a,e);case 6:return Z(a,{a:e,b:t,c:n,d:i,e:r,f:o})}}(),s=Rn(r.a,1e3),l=Rn(-r.e||0,1e3),c=Rn(-r.f||0,1e3),e=this._cachedViewbox={x:l?l/s:0,y:c?c/s:0,width:n.width/s,height:n.height/s,scale:s,inner:{width:i.width||0,height:i.height||0,x:i.x||0,y:i.y||0},outer:n})},Bn.prototype.scroll=function(e){const t=this._viewport;let n=t.getCTM();return e&&this._changeViewbox(function(){e=k({dx:0,dy:0},e||{}),n=this._svg.createSVGMatrix().translate(e.dx,e.dy).multiply(n),Dn(t,n)}),{x:n.e,y:n.f}},Bn.prototype.scrollToElement=function(e,t){let n=100;const i=this._resolveElements(e);if(!i.length)return;const r=this.findRoot(i);if(!r)return;r!==this.getRootElement()&&this.setRootElement(r);const o=i.filter(e=>e!=r);if(!o.length)return;t||(t={}),"number"==typeof t&&(n=t),t={top:t.top||n,right:t.right||n,bottom:t.bottom||n,left:t.left||n};const a=O(o),s=yt(a),l=this.viewbox(),c=this.zoom();let p,u;l.y+=t.top/c,l.x+=t.left/c,l.width-=(t.right+t.left)/c,l.height-=(t.bottom+t.top)/c;const h=yt(l);if(a.width=0&&i.y>=0&&i.x+i.width<=n.width&&i.y+i.height<=n.height&&!e?o={x:0,y:0,width:Math.max(i.width+i.x,n.width),height:Math.max(i.height+i.y,n.height)}:(r=Math.min(1,n.width/i.width,n.height/i.height),o={x:i.x+(e?i.width/2-n.width/r/2:0),y:i.y+(e?i.height/2-n.height/r/2:0),width:n.width/r,height:n.height/r}),this.viewbox(o),this.viewbox(!1).scale},Bn.prototype._setZoom=function(e,t){const n=this._svg,i=this._viewport,r=n.createSVGMatrix(),o=n.createSVGPoint();let a,s,l,c,p;l=i.getCTM();const u=l.a;return t?(a=k(o,t),s=a.matrixTransform(l.inverse()),c=r.translate(s.x,s.y).scale(1/u*e).translate(-s.x,-s.y),p=l.multiply(c)):p=r.scale(e),Dn(this._viewport,p),p},Bn.prototype.getSize=function(){return{width:this._container.clientWidth,height:this._container.clientHeight}},Bn.prototype.getAbsoluteBBox=function(e){const t=this.viewbox();let n;if(e.waypoints){n=this.getGraphics(e).getBBox()}else n=e;return{x:n.x*t.scale-t.x*t.scale,y:n.y*t.scale-t.y*t.scale,width:n.width*t.scale,height:n.height*t.scale}},Bn.prototype.resized=function(){delete this._cachedViewbox,this._eventBus.fire("canvas.resized")};var Tn="data-element-id";function On(e){this._elements={},this._eventBus=e}function Ln(e,t,n){var i=function(e,t,n,i){var r=n.inverse;return Object.defineProperty(e,"remove",{value:function(e){var n=this.indexOf(e);return-1!==n&&(this.splice(n,1),t.unset(e,r,i)),e}}),Object.defineProperty(e,"contains",{value:function(e){return-1!==this.indexOf(e)}}),Object.defineProperty(e,"add",{value:function(e,n){var o=this.indexOf(e);if(void 0===n){if(-1!==o)return;n=this.length}-1!==o&&this.splice(o,1),this.splice(n,0,e),-1===o&&t.set(e,r,i)}}),Object.defineProperty(e,"__refs_collection",{value:!0}),e}(n[t.name]||[],e,t,n);Object.defineProperty(n,t.name,{enumerable:t.enumerable,value:i}),i.length&&i.forEach(function(i){e.set(i,t.inverse,n)})}function In(e,t){if(!(this instanceof In))return new In(e,t);e.inverse=t,t.inverse=e,this.props={},this.props[e.name]=e,this.props[t.name]=t}On.$inject=["eventBus"],On.prototype.add=function(e,t,n){var i=e.id;this._validateId(i),z(t,Tn,i),n&&z(n,Tn,i),this._elements[i]={element:e,gfx:t,secondaryGfx:n}},On.prototype.remove=function(e){var t=this._elements,n=e.id||e,i=n&&t[n];i&&(z(i.gfx,Tn,""),i.secondaryGfx&&z(i.secondaryGfx,Tn,""),delete t[n])},On.prototype.updateId=function(e,t){this._validateId(t),"string"==typeof e&&(e=this.get(e)),this._eventBus.fire("element.updateId",{element:e,newId:t});var n=this.getGraphics(e),i=this.getGraphics(e,!0);this.remove(e),e.id=t,this.add(e,n,i)},On.prototype.updateGraphics=function(e,t,n){var i=e.id||e,r=this._elements[i];return n?r.secondaryGfx=t:r.gfx=t,t&&z(t,Tn,i),t},On.prototype.get=function(e){var t;t="string"==typeof e?e:e&&z(e,Tn);var n=this._elements[t];return n&&n.element},On.prototype.filter=function(e){var t=[];return this.forEach(function(n,i){e(n,i)&&t.push(n)}),t},On.prototype.find=function(e){for(var t=this._elements,n=Object.keys(t),i=0;i in ref");t=this.props[t]}t.collection?Ln(this,t,e):function(e,t,n){var i=t.inverse,r=n[t.name];Object.defineProperty(n,t.name,{configurable:t.configurable,enumerable:t.enumerable,get:function(){return r},set:function(t){if(t!==r){var o=r;r=null,o&&e.unset(o,i,n),r=t,e.set(r,i,n)}}})}(this,t,e)},In.prototype.ensureRefsCollection=function(e,t){var n=e[t.name];return function(e){return!0===e.__refs_collection}(n)||Ln(this,t,e),n},In.prototype.ensureBound=function(e,t){(function(e,t){return Object.prototype.hasOwnProperty.call(e,t.name||t)})(e,t)||this.bind(e,t)},In.prototype.unset=function(e,t,n){e&&(this.ensureBound(e,t),t.collection?this.ensureRefsCollection(e,t).remove(n):e[t.name]=void 0)},In.prototype.set=function(e,t,n){e&&(this.ensureBound(e,t),t.collection?this.ensureRefsCollection(e,t).add(n):e[t.name]=n)};var jn=new In({name:"children",enumerable:!0,collection:!0},{name:"parent"}),Fn=new In({name:"labels",enumerable:!0,collection:!0},{name:"labelTarget"}),Vn=new In({name:"attachers",collection:!0},{name:"host"}),zn=new In({name:"outgoing",collection:!0},{name:"source"}),Wn=new In({name:"incoming",collection:!0},{name:"target"});function $n(){Object.defineProperty(this,"businessObject",{writable:!0}),Object.defineProperty(this,"label",{get:function(){return this.labels[0]},set:function(e){var t=this.label,n=this.labels;!e&&t?n.remove(t):n.add(e,0)}}),jn.bind(this,"parent"),Fn.bind(this,"labels"),zn.bind(this,"outgoing"),Wn.bind(this,"incoming")}function Gn(){$n.call(this),jn.bind(this,"children"),Vn.bind(this,"host"),Vn.bind(this,"attachers")}function Hn(){$n.call(this),jn.bind(this,"children")}function Kn(){Gn.call(this),Fn.bind(this,"labelTarget")}function Un(){$n.call(this),zn.bind(this,"source"),Wn.bind(this,"target")}e(Gn,$n),e(Hn,Gn),e(Kn,Gn),e(Un,$n);var qn={connection:Un,shape:Gn,label:Kn,root:Hn};function Yn(){this._uid=12}Yn.prototype.createRoot=function(e){return this.create("root",e)},Yn.prototype.createLabel=function(e){return this.create("label",e)},Yn.prototype.createShape=function(e){return this.create("shape",e)},Yn.prototype.createConnection=function(e){return this.create("connection",e)},Yn.prototype.create=function(e,t){return(t=k({},t||{})).id||(t.id=e+"_"+this._uid++),function(e,t){var n=qn[e];if(!n)throw new Error("unknown type: <"+e+">");return k(new n,t)}(e,t)};var Xn="__fn",Zn=Array.prototype.slice;function Jn(){this._listeners={},this.on("diagram.destroy",1,this._destroy,this)}function Qn(){}function ei(e,t){this._eventBus=e,this._elementRegistry=t}Jn.prototype.on=function(e,t,n,i){if(e=a(e)?e:[e],c(t)&&(i=n,n=t,t=1e3),!l(t))throw new Error("priority must be a number");var r=n;i&&((r=_(n,i))[Xn]=n[Xn]||n);var o=this;e.forEach(function(e){o._addListener(e,{priority:t,callback:r,next:null})})},Jn.prototype.once=function(e,t,n,i){var r=this;if(c(t)&&(i=n,n=t,t=1e3),!l(t))throw new Error("priority must be a number");function o(){o.__isTomb=!0;var t=n.apply(i,arguments);return r.off(e,o),t}o[Xn]=n,this.on(e,t,o)},Jn.prototype.off=function(e,t){e=a(e)?e:[e];var n=this;e.forEach(function(e){n._removeListener(e,t)})},Jn.prototype.createEvent=function(e){var t=new Qn;return t.init(e),t},Jn.prototype.fire=function(e,t){var n,i,r,o;if(o=Zn.call(arguments),"object"==typeof e&&(e=(t=e).type),!e)throw new Error("no event type specified");if(i=this._listeners[e]){n=t instanceof Qn?t:this.createEvent(t),o[0]=n;var a=n.type;e!==a&&(n.type=e);try{r=this._invokeListeners(n,o,i)}finally{e!==a&&(n.type=a)}return void 0===r&&n.defaultPrevented&&(r=!1),r}},Jn.prototype.handleError=function(e){return!1===this.fire("error",{error:e})},Jn.prototype._destroy=function(){this._listeners={}},Jn.prototype._invokeListeners=function(e,t,n){for(var i;n&&!e.cancelBubble;)i=this._invokeListener(e,t,n),n=n.next;return i},Jn.prototype._invokeListener=function(e,t,n){var i;if(n.callback.__isTomb)return i;try{i=function(e,t){return e.apply(null,t)}(n.callback,t),void 0!==i&&(e.returnValue=i,e.stopPropagation()),!1===i&&e.preventDefault()}catch(e){if(!this.handleError(e))throw console.error("unhandled error in event listener",e),e}return i},Jn.prototype._addListener=function(e,t){var n,i=this._getListeners(e);if(i){for(;i;){if(i.priority or , got "+e);n=r[1],i=r[0]}return{name:e=(i?i+":":"")+n,prefix:i,localName:n}}function hi(e){this.ns=e,this.name=e.name,this.allTypes=[],this.allTypesByName=Object.create(null),this.properties=[],this.propertiesByName=Object.create(null)}function fi(e,t){this.packageMap=Object.create(null),this.typeMap=Object.create(null),this.packages=[],this.properties=t,m(e,_(this.registerPackage,this))}function mi(e,t,n){var i=t[n];if(i in e)throw new Error("package with "+n+" <"+i+"> already defined")}function di(e){this.model=e}function yi(e,t,n){Object.defineProperty(e,t.name,{enumerable:!t.isReference,writable:!0,value:n,configurable:!0})}function gi(e){return e.replace(/^:/,"")}function vi(e,t={}){this.properties=new di(this),this.factory=new oi(this,this.properties),this.registry=new fi(e,this.properties),this.typeCache=Object.create(null),this.config=t}hi.prototype.build=function(){return function(e,t){let n={},i=Object(e);return m(t,function(t){t in i&&(n[t]=e[t])}),n}(this,["ns","name","allTypes","allTypesByName","properties","propertiesByName","bodyProperty","idProperty"])},hi.prototype.addProperty=function(e,t,n){"boolean"==typeof t&&(n=t,t=void 0),this.addNamedProperty(e,!1!==n);var i=this.properties;void 0!==t?i.splice(t,0,e):i.push(e)},hi.prototype.replaceProperty=function(e,t,n){var i=e.ns,r=this.properties,o=this.propertiesByName,a=e.name!==t.name;if(e.isId){if(!t.isId)throw new Error("property <"+t.ns.name+"> must be id property to refine <"+e.ns.name+">");this.setIdProperty(t,!1)}if(e.isBody){if(!t.isBody)throw new Error("property <"+t.ns.name+"> must be body property to refine <"+e.ns.name+">");this.setBodyProperty(t,!1)}var s=r.indexOf(e);if(-1===s)throw new Error("property <"+i.name+"> not found in property list");r.splice(s,1),this.addProperty(t,n?void 0:s,a),o[i.name]=o[i.localName]=t},hi.prototype.redefineProperty=function(e,t,n){var i=e.ns.prefix,r=t.split("#"),o=ui(r[0],i),a=ui(r[1],o.prefix).name,s=this.propertiesByName[a];if(!s)throw new Error("refined property <"+a+"> not found");this.replaceProperty(s,e,n),delete e.redefines},hi.prototype.addNamedProperty=function(e,t){var n=e.ns,i=this.propertiesByName;t&&(this.assertNotDefined(e,n.name),this.assertNotDefined(e,n.localName)),i[n.name]=i[n.localName]=e},hi.prototype.removeNamedProperty=function(e){var t=e.ns,n=this.propertiesByName;delete n[t.name],delete n[t.localName]},hi.prototype.setBodyProperty=function(e,t){if(t&&this.bodyProperty)throw new Error("body property defined multiple times (<"+this.bodyProperty.ns.name+">, <"+e.ns.name+">)");this.bodyProperty=e},hi.prototype.setIdProperty=function(e,t){if(t&&this.idProperty)throw new Error("id property defined multiple times (<"+this.idProperty.ns.name+">, <"+e.ns.name+">)");this.idProperty=e},hi.prototype.assertNotTrait=function(e){if((e.extends||[]).length)throw new Error(`cannot create <${e.name}> extending <${e.extends}>`)},hi.prototype.assertNotDefined=function(e,t){var n=e.name,i=this.propertiesByName[n];if(i)throw new Error("property <"+n+"> already defined; override of <"+i.definedBy.ns.name+"#"+i.ns.name+"> by <"+e.definedBy.ns.name+"#"+e.ns.name+"> not allowed without redefines")},hi.prototype.hasProperty=function(e){return this.propertiesByName[e]},hi.prototype.addTrait=function(e,t){t&&this.assertNotTrait(e);var n=this.allTypesByName,i=this.allTypes,r=e.name;r in n||(m(e.properties,_(function(n){n=k({},n,{name:n.ns.localName,inherited:t}),Object.defineProperty(n,"definedBy",{value:e});var i=n.replaces,r=n.redefines;i||r?this.redefineProperty(n,i||r,i):(n.isBody&&this.setBodyProperty(n),n.isId&&this.setIdProperty(n),this.addProperty(n))},this)),i.push(e),n[r]=e)},fi.prototype.getPackage=function(e){return this.packageMap[e]},fi.prototype.getPackages=function(){return this.packages},fi.prototype.registerPackage=function(e){e=k({},e);var t=this.packageMap;mi(t,e,"prefix"),mi(t,e,"uri"),m(e.types,_(function(t){this.registerType(t,e)},this)),t[e.uri]=t[e.prefix]=e,this.packages.push(e)},fi.prototype.registerType=function(e,t){var n=ui((e=k({},e,{superClass:(e.superClass||[]).slice(),extends:(e.extends||[]).slice(),properties:(e.properties||[]).slice(),meta:k({},e.meta||{})})).name,t.prefix),i=n.name,r=Object.create(null);m(e.properties,_(function(e){var t=ui(e.name,n.prefix),i=t.name;ci(e.type)||(e.type=ui(e.type,t.prefix).name),k(e,{ns:t,name:i}),r[i]=e},this)),k(e,{ns:n,name:i,propertiesByName:r}),m(e.extends,_(function(e){var t=ui(e,n.prefix),r=this.typeMap[t.name];r.traits=r.traits||[],r.traits.push(i)},this)),this.definePackage(e,t),this.typeMap[i]=e},fi.prototype.mapTypes=function(e,t,n){var i=ci(e.name)?{name:e.name}:this.typeMap[e.name],r=this;function o(n,i){var o=ui(n,ci(n)?"":e.prefix);r.mapTypes(o,t,i)}function a(e){return o(e,!0)}if(!i)throw new Error("unknown type <"+e.name+">");m(i.superClass,n?a:function(e){return o(e,!1)}),t(i,!n),m(i.traits,a)},fi.prototype.getEffectiveDescriptor=function(e){var t=ui(e),n=new hi(t);this.mapTypes(t,function(e,t){n.addTrait(e,t)});var i=n.build();return this.definePackage(i,i.allTypes[i.allTypes.length-1].$pkg),i},fi.prototype.definePackage=function(e,t){this.properties.define(e,"$pkg",{value:t})},di.prototype.set=function(e,t,n){if(!p(t)||!t.length)throw new TypeError("property name must be a non-empty string");var i=this.getProperty(e,t),r=i&&i.name;void 0===n?i?delete e[r]:delete e.$attrs[gi(t)]:i?r in e?e[r]=n:yi(e,i,n):e.$attrs[gi(t)]=n},di.prototype.get=function(e,t){var n=this.getProperty(e,t);if(!n)return e.$attrs[gi(t)];var i=n.name;return!e[i]&&n.isMany&&yi(e,n,[]),e[i]},di.prototype.define=function(e,t,n){if(!n.writable){var i=n.value;delete(n=k({},n,{get:function(){return i}})).value}Object.defineProperty(e,t,n)},di.prototype.defineDescriptor=function(e,t){this.define(e,"$descriptor",{value:t})},di.prototype.defineModel=function(e,t){this.define(e,"$model",{value:t})},di.prototype.getProperty=function(e,t){var n=this.model,i=n.getPropertyDescriptor(e,t);if(i)return i;if(t.includes(":"))return null;const r=n.config.strict;if(void 0!==r){const n=new TypeError(`unknown property <${t}> on <${e.$type}>`);if(r)throw n;"undefined"!=typeof console&&console.warn(n)}return null},vi.prototype.create=function(e,t){var n=this.getType(e);if(!n)throw new Error("unknown type <"+e+">");return new n(t)},vi.prototype.getType=function(e){var t=this.typeCache,n=p(e)?e:e.ns.name,i=t[n];return i||(e=this.registry.getEffectiveDescriptor(n),i=t[n]=this.factory.createType(e)),i},vi.prototype.createAny=function(e,t,n){var a=ui(e),l={$type:e,$instanceOf:function(e){return e===this.$type},get:function(e){return this[e]},set:function(e,t){!function(e,t,n){let a=e;m(t,function(e,s){if("number"!=typeof e&&"string"!=typeof e)throw new Error("illegal key type: "+typeof e+". Key should be of type number or string.");if("constructor"===e)throw new Error("illegal key: constructor");if("__proto__"===e)throw new Error("illegal key: __proto__");let l=t[s+1],c=a[e];r(l)&&o(c)&&(c=a[e]=isNaN(+l)?{}:[]),i(l)?i(n)?delete a[e]:a[e]=n:a=c})}(this,[e],t)}},c={name:e,isGeneric:!0,ns:{prefix:a.prefix,localName:a.localName,uri:t}};return this.properties.defineDescriptor(l,c),this.properties.defineModel(l,this),this.properties.define(l,"get",{enumerable:!1,writable:!0}),this.properties.define(l,"set",{enumerable:!1,writable:!0}),this.properties.define(l,"$parent",{enumerable:!1,writable:!0}),this.properties.define(l,"$instanceOf",{enumerable:!1,writable:!0}),m(n,function(e,t){s(e)&&void 0!==e.value?l[e.name]=e.value:l[t]=e}),l},vi.prototype.getPackage=function(e){return this.registry.getPackage(e)},vi.prototype.getPackages=function(){return this.registry.getPackages()},vi.prototype.getElementDescriptor=function(e){return e.$descriptor},vi.prototype.hasType=function(e,t){return void 0===t&&(t=e,e=this),t in e.$model.getElementDescriptor(e).allTypesByName},vi.prototype.getPropertyDescriptor=function(e,t){return this.getElementDescriptor(e).propertiesByName[t]},vi.prototype.getTypeDescriptor=function(e){return this.registry.typeMap[e]};var xi=String.fromCharCode,bi=Object.prototype.hasOwnProperty,wi=/&#(\d+);|&#x([0-9a-f]+);|&(\w+);/gi,Ei={amp:"&",apos:"'",gt:">",lt:"<",quot:'"'};function _i(e,t,n,i){return i?bi.call(Ei,i)?Ei[i]:"&"+i+";":xi(t||parseInt(n,16))}function ki(e){return e.length>3&&-1!==e.indexOf("&")?e.replace(wi,_i):e}Object.keys(Ei).forEach(function(e){Ei[e.toUpperCase()]=Ei[e]});var Ai="non-whitespace outside of root node";function Si(e){return new Error(e)}function Ri(e){return{get:e,enumerable:!0}}function Ci(e,t,n,i){n in t||(t[n]=n in e?e[n]:null),e[n]=i}function Mi(e,t){var n,i;for(n in t)null===(i=t[n])?delete e[n]:e[n]=i}function Pi(e){return e+"$uri"}function Ni(){return{line:0,column:0}}function Bi(e){throw e}function Di(e){if(!this)return new Di(e);var t,n,i,r,o,a,s,l,c,p,u,h,f=e&&e.proxy,m=Bi,d=Ni,y=!1,g=!1,v="",x=!1,b=!1,w=null,E=!1;function _(e){e instanceof Error||(e=Si(e)),w=e,m(e,d)}function k(e){o&&(e instanceof Error||(e=Si(e)),o(e,d))}function A(){c=b?[]:null,p=b?function(e){var t,n,i=Object.create(null);for(t in e)i[n=e[t]]=n,i[Pi(n)]=t;return i}(h):null,u=[],d=Ni,E=!1,w=null,g=!1,v=""}function S(e,o=!1){var m,y,v,w,A,S,R,C,M,P,N,B,D=null,T=0,O=!1,L=!1,I=0,j=0,F="",V=0;function z(e,t){var n=e.indexOf(":");if(-1===n)return e;var i=p[e.substring(0,n)];return i?t===i?e.substr(n+1):i+e.substr(n):(k(function(e){return"missing namespace for prefix <"+e+">"}(e.substring(0,n))),null)}function W(){if(null!==B)return B;var e,t,n,i,r,o,a,s,l,c=b&&p.xmlns,u=b&&x?[]:null,f=V,d=F,g=d.length,v={},w=new Set;e:for(;f8)){for((s<65||s>122||s>90&&s<97)&&95!==s&&58!==s&&(k("illegal first char attribute name"),a=!0),l=f+1;l96&&s<123||s>64&&s<91||s>47&&s<59||46===s||45===s||95===s)){if(32===s||s<14&&s>8){k("missing attribute value"),f=l;continue e}if(61===s)break;k("illegal attribute name char"),a=!0}if("xmlns:xmlns"===(o=d.substring(f,l))&&(k("illegal declaration of xmlns"),a=!0),34===(s=d.charCodeAt(l+1)))-1===(l=d.indexOf('"',f=l+2))&&-1!==(l=d.indexOf("'",f))&&(k("attribute value quote missmatch"),a=!0);else if(39===s)-1===(l=d.indexOf("'",f=l+2))&&-1!==(l=d.indexOf('"',f))&&(k("attribute value quote missmatch"),a=!0);else for(k("missing attribute value quotes"),a=!0,l+=1;l8);l++);for(-1===l&&(k("missing closing quotes"),l=g,a=!0),a||(i=d.substring(f,l)),"xmlns:xml"===o&&"http://www.w3.org/XML/1998/namespace"!==i&&_("illegal value of xmlns:xml"),f=l;l+18);l++)f===l&&(k("illegal character after attribute end"),a=!0);if(f=l+1,!a)if(w.has(o))k("attribute <"+o+"> already defined");else if(w.add(o),b)if(x){if(null!==(n="xmlns"===o?"xmlns":120===o.charCodeAt(0)&&"xmlns:"===o.substr(0,6)?o.substr(6):null)){if(e=ki(i),t=Pi(n),!(r=h[e])){if("xmlns"===n||t in p&&p[t]!==e)do{r="ns"+T++}while(void 0!==p[r]);else r=n;h[e]=r}p[n]!==r&&(m||(m=Object.create(null)),D=null,y=null,Ci(p,m,n,r),"xmlns"===n&&(Ci(p,m,Pi(r),e),c=r),Ci(p,m,t,e)),v[o]=i;continue}u.push(o,i)}else null!==(o=z(o,c))&&(v[o]=i);else v[o]=i}if(x)for(f=0,g=u.length;f=a&&(t=i.exec(e))&&!((s=t[0].length+t.index)>I);)r+=1,a=s;return-1==I?(o=s,n=e.substring(j)):0===j?n=e.substring(j,I):(o=I-a,n=-1==j?e.substring(I):e.substring(I,j+1)),{data:n,line:r,column:o}},f&&(N=Object.create({},{name:Ri(function(){return M}),originalName:Ri(function(){return P}),attrs:Ri(W),ns:Ri(function(){return y||(y=Object.assign(Object.create(null),p)),y})}));-1!==j;){if(-1===(I=60===e.charCodeAt(j)?j:e.indexOf("<",j)))return o?e.substring(j):u.length?_("unexpected end of file"):g?void(j",I)))return o?e.substring(I):_("unclosed cdata");if(r&&(r(e.substring(I+9,j),d),E))return;j+=3;continue}if(45===A&&45===e.charCodeAt(I+3)){if(-1===(j=e.indexOf("--\x3e",I)))return o?e.substring(I):_("unclosed comment");if(a&&(a(e.substring(I+4,j),ki,d),E))return;j+=3;continue}}if(63!==S){for(v=I+1;;v++){if(R=e.charCodeAt(v),isNaN(R))return o?e.substring(I):(j=-1,_("unclosed tag"));if(34===R)v=-1!==(A=e.indexOf('"',v+1))?A:v;else if(39===R)v=-1!==(A=e.indexOf("'",v+1))?A:v;else if(62===R){j=v;break}}if(33!==S){if(B={},47===S){if(O=!1,L=!0,!u.length)return _("missing open tag");if(v=M=u.pop(),A=I+2+v.length,e.substring(I+2,A)!==v)return _("closing tag mismatch");for(;A8&&S<14))return _("close tag")}else{if(47===e.charCodeAt(j-1)?(v=M=e.substring(I+1,j-1),O=!0,L=!0):(v=M=e.substring(I+1,j),O=!0,L=!1),!(S>96&&S<123||S>64&&S<91||95===S||58===S))return _("illegal first char nodeName");for(A=1,w=v.length;A96&&S<123||S>64&&S<91||S>47&&S<59||45===S||95===S||46==S)){if(32===S||S<14&&S>8){M=v.substring(0,A),B=null;break}return _("invalid nodeName")}L||u.push(M)}if(b){O&&(m=null,null===B&&(x=-1!==v.indexOf("xmlns",A))&&(V=A,F=v,W(),x=!1),L||c.push(m)),P=M,null===D&&(D=Object.create(null));var $=D[M];if(void 0!==$)M=$;else{if(-1!==(S=M.indexOf(":"))){if(!(C=p[M.substring(0,S)]))return _("missing namespace on <"+P+">");M=M.substr(S+1)}else C=p.xmlns;C&&(M=C+":"+M),D[P]=M}}if(O&&(V=A,F=v,n&&(f?n(N,ki,L,d):n(M,W,ki,L,d),E)))return;if(L){if(i&&(i(f?N:M,ki,O,d),E))return;b&&(O||(m=c.pop()),m&&(Mi(p,m),m=null,D=null,y=null))}j+=1}else{if(l&&(l(e.substring(I,j+1),ki,d),E))return;j+=1}}else{if(-1===(j=e.indexOf("?>",I)))return o?e.substring(I):_("unclosed question");if(s&&(s(e.substring(I,j+2),d),E))return;j+=2}}}this.on=function(e,c){if("function"!=typeof c)throw Si("required args ");switch(e){case"openTag":n=c;break;case"text":t=c;break;case"closeTag":i=c;break;case"error":m=c;break;case"warn":o=c;break;case"cdata":r=c;break;case"attention":l=c;break;case"question":s=c;break;case"comment":a=c;break;default:throw Si("unsupported event: "+e)}return this},this.ns=function(e){if(void 0===e&&(e={}),"object"!=typeof e)throw Si("required args ");var t,n=Object.create(null);for(t in e)n[t]=e[t];return b=!0,h=n,this},this.parse=function(e){if("string"!=typeof e)throw Si("required args ");if(y)throw Si("parse during stream; call end() first");return A(),S(e),d=Ni,E=!1,w},this.write=function(e){if("string"!=typeof e)throw Si("required args ");return y||(A(),y=!0),w||(v=S(v+e,!0)||""),this},this.end=function(){return y||A(),y=!1,w||S(v),v="",d=Ni,E=!1,w},this.stop=function(){E=!0}}function Ti(e){return e.xml&&"lowerCase"===e.xml.tagAlias}var Oi={xsi:"http://www.w3.org/2001/XMLSchema-instance",xml:"http://www.w3.org/XML/1998/namespace"},Li="property";function Ii(e){return e.xml&&e.xml.serialize}function ji(e,t){return Ti(t)?e.prefix+":"+((n=e.localName).charAt(0).toUpperCase()+n.slice(1)):e.name;var n}function Fi(e){return new Error(e)}function Vi(e){return e.$descriptor}function zi(e){k(this,e),this.elementsById={},this.references=[],this.warnings=[],this.addReference=function(e){this.references.push(e)},this.addElement=function(e){if(!e)throw Fi("expected element");var t,n=this.elementsById,i=Vi(e).idProperty;if(i&&(t=e.get(i.name))){if(!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(t))throw new Error("illegal ID <"+t+">");if(n[t])throw Fi("duplicate ID <"+t+">");n[t]=e}},this.addWarning=function(e){this.warnings.push(e)}}function Wi(){}function $i(){}function Gi(){}function Hi(e,t){this.property=e,this.context=t}function Ki(e,t){this.element=t,this.propertyDesc=e}function Ui(){}function qi(e,t,n){this.model=e,this.type=e.getType(t),this.context=n}function Yi(e,t,n){qi.call(this,e,t,n)}function Xi(e,t,n){this.model=e,this.context=n}function Zi(e){e instanceof vi&&(e={model:e}),k(this,{lax:!1},e)}Wi.prototype.handleEnd=function(){},Wi.prototype.handleText=function(){},Wi.prototype.handleNode=function(){},$i.prototype=Object.create(Wi.prototype),$i.prototype.handleNode=function(){return this},Gi.prototype=Object.create(Wi.prototype),Gi.prototype.handleText=function(e){this.body=(this.body||"")+e},Hi.prototype=Object.create(Gi.prototype),Hi.prototype.handleNode=function(e){if(this.element)throw Fi("expected no sub nodes");return this.element=this.createReference(e),this},Hi.prototype.handleEnd=function(){this.element.id=this.body},Hi.prototype.createReference=function(e){return{property:this.property.ns.name,id:""}},Ki.prototype=Object.create(Gi.prototype),Ki.prototype.handleEnd=function(){var e=this.body||"",t=this.element,n=this.propertyDesc;e=li(n.type,e),n.isMany?t.get(n.name).push(e):t.set(n.name,e)},Ui.prototype=Object.create(Gi.prototype),Ui.prototype.handleNode=function(e){var t=this,n=this.element;return n?t=this.handleChild(e):(n=this.element=this.createElement(e),this.context.addElement(n)),t},qi.prototype=Object.create(Ui.prototype),qi.prototype.addReference=function(e){this.context.addReference(e)},qi.prototype.handleText=function(e){if(!Vi(this.element).bodyProperty)throw Fi("unexpected body text <"+e+">");Gi.prototype.handleText.call(this,e)},qi.prototype.handleEnd=function(){var e=this.body,t=this.element,n=Vi(t).bodyProperty;n&&void 0!==e&&(e=li(n.type,e),t.set(n.name,e))},qi.prototype.createElement=function(e){var t,n=e.attributes,i=this.type,r=Vi(i),o=this.context,a=new i({}),s=this.model;return m(n,function(e,n){var i=r.propertiesByName[n];i&&i.isReference?i.isMany?m(e.split(" "),function(e){o.addReference({element:a,property:i.ns.name,id:e})}):o.addReference({element:a,property:i.ns.name,id:e}):(i?e=li(i.type,e):"xmlns"===n?n=":"+n:(t=ui(n,r.ns.prefix),s.getPackage(t.prefix)&&o.addWarning({message:"unknown attribute <"+n+">",element:a,property:n,value:e})),a.set(n,e))}),a},qi.prototype.getPropertyForNode=function(e){var t=ui(e.name),n=this.type,i=this.model,r=Vi(n),o=t.name,a=r.propertiesByName[o];if(a&&!a.isAttr){const t=function(e){const t=Ii(e);return t!==Li&&(t||null)}(a);if(t){const n=e.attributes[t];if(n){const t=function(e,t,n){const i=ui(e,t.xmlns),r=ui(`${t[i.prefix]||i.prefix}:${i.localName}`);return function(e,t){var n=e.name,i=e.localName,r=t&&t.xml&&t.xml.typePrefix;return r&&0===i.indexOf(r)?e.prefix+":"+i.slice(r.length):n}(r,n.getPackage(r.prefix))}(n,e.ns,i);return k({},a,{effectiveType:Vi(i.getType(t)).name})}}return a}var s=i.getPackage(t.prefix);if(s){const e=ji(t,s),n=i.getType(e);if(a=h(r.properties,function(e){return!e.isVirtual&&!e.isReference&&!e.isAttribute&&n.hasType(e.type)}))return k({},a,{effectiveType:Vi(n).name})}else if(a=h(r.properties,function(e){return!e.isReference&&!e.isAttribute&&"Element"===e.type}))return a;throw Fi("unrecognized element <"+t.name+">")},qi.prototype.toString=function(){return"ElementDescriptor["+Vi(this.type).name+"]"},qi.prototype.valueHandler=function(e,t){return new Ki(e,t)},qi.prototype.referenceHandler=function(e){return new Hi(e,this.context)},qi.prototype.handler=function(e){return"Element"===e?new Xi(this.model,e,this.context):new qi(this.model,e,this.context)},qi.prototype.handleChild=function(e){var t,n,i,r;if(t=this.getPropertyForNode(e),i=this.element,pi(n=t.effectiveType||t.type))return this.valueHandler(t,i);var o=(r=t.isReference?this.referenceHandler(t).handleNode(e):this.handler(n).handleNode(e)).element;return void 0!==o&&(t.isMany?i.get(t.name).push(o):i.set(t.name,o),t.isReference?(k(o,{element:i}),this.context.addReference(o)):o.$parent=i),r},Yi.prototype=Object.create(qi.prototype),Yi.prototype.createElement=function(e){var t=e.name,n=ui(t),i=this.model,r=this.type,o=i.getPackage(n.prefix),a=o&&ji(n,o)||t;if(!r.hasType(a))throw Fi("unexpected element <"+e.originalName+">");return qi.prototype.createElement.call(this,e)},Xi.prototype=Object.create(Ui.prototype),Xi.prototype.createElement=function(e){var t=e.name,n=ui(t).prefix,i=e.ns[n+"$uri"],r=e.attributes;return this.model.createAny(t,i,r)},Xi.prototype.handleChild=function(e){var t=new Xi(this.model,"Element",this.context).handleNode(e),n=this.element,i=t.element;return void 0!==i&&((n.$children=n.$children||[]).push(i),i.$parent=n),t},Xi.prototype.handleEnd=function(){this.body&&(this.element.$body=this.body)},Zi.prototype.fromXML=function(e,t,n){var i=t.rootHandler;t instanceof qi?(i=t,t={}):"string"==typeof t?(i=this.handler(t),t={}):"string"==typeof i&&(i=this.handler(i));var r=this.model,o=this.lax,a=new zi(k({},t,{rootHandler:i})),s=new Di({proxy:!0}),l=function(){var e=[];return Object.defineProperty(e,"peek",{value:function(){return this[this.length-1]}}),e}();function c(e,t,n){var i=t(),r=i.line,o=i.column,s=i.data;"<"===s.charAt(0)&&-1!==s.indexOf(" ")&&(s=s.slice(0,s.indexOf(" "))+">");var l="unparsable content "+(s?s+" ":"")+"detected\n\tline: "+r+"\n\tcolumn: "+o+"\n\tnested error: "+e.message;if(n)return a.addWarning({message:l,error:e}),!0;throw Fi(l)}function p(e,t){return c(e,t,!0)}i.context=a,l.push(i);var u=/^<\?xml /i,h=/ encoding="([^"]+)"/i,f=/^utf-8$/i;function m(e,t){try{l.peek().handleText(e)}catch(e){p(e,t)}}var d=r.getPackages().reduce(function(e,t){return e[t.uri]=t.prefix,e},Object.entries(Oi).reduce(function(e,[t,n]){return e[n]=t,e},r.config&&r.config.nsMap||{}));return s.ns(d).on("openTag",function(e,t,n,i){var r=e.attrs||{},a=Object.keys(r).reduce(function(e,n){var i=t(r[n]);return e[n]=i,e},{});!function(e,t){var n=l.peek();try{l.push(n.handleNode(e))}catch(e){c(e,t,o)&&l.push(new $i)}}({name:e.name,originalName:e.originalName,attributes:a,ns:e.ns},i)}).on("question",function(e){if(u.test(e)){var t=h.exec(e),n=t&&t[1];n&&!f.test(n)&&a.addWarning({message:"unsupported document encoding <"+n+">, falling back to UTF-8"})}}).on("closeTag",function(){l.pop().handleEnd()}).on("cdata",m).on("text",function(e,t,n){!function(e,t){e.trim()&&m(e,t)}(t(e),n)}).on("error",c).on("warn",p),new Promise(function(t,n){var r;try{s.parse(e),function(){var e,t,n=a.elementsById,i=a.references;for(e=0;t=i[e];e++){var r=t.element,o=n[t.id],s=Vi(r).propertiesByName[t.property];if(o||a.addWarning({message:"unresolved reference <"+t.id+">",element:t.element,property:t.property,value:t.id}),s.isMany){var l=r.get(s.name),c=l.indexOf(t);-1===c&&(c=l.length),o?l[c]=o:l.splice(c,1)}else r.set(s.name,o)}}()}catch(e){r=e}var o=i.element;r||o||(r=Fi("failed to parse document as <"+i.type.$descriptor.name+">"));var l=a.warnings,c=a.references,p=a.elementsById;return r?(r.warnings=l,n(r)):t({rootElement:o,elementsById:p,references:c,warnings:l})})},Zi.prototype.handler=function(e){return new Yi(this.model,e)};var Ji=/<|>|'|"|&|\n\r|\n/g,Qi=/<|>|&/g;function er(e){this.prefixMap={},this.uriMap={},this.used={},this.wellknown=[],this.custom=[],this.parent=e,this.defaultPrefixMap=e&&e.defaultPrefixMap||{}}function tr(e,t){return Ti(t)?(n=e).charAt(0).toLowerCase()+n.slice(1):e;var n}function nr(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}function ir(e){return p(e)?e:(e.prefix?e.prefix+":":"")+e.localName}er.prototype.mapDefaultPrefixes=function(e){this.defaultPrefixMap=e},er.prototype.defaultUriByPrefix=function(e){return this.defaultPrefixMap[e]},er.prototype.resolve=function(e){for(var t=this;t;){var n=e(t);if(n)return n;t=t.parent}},er.prototype.eachScope=function(e){for(var t=this;t;)e(t),t=t.parent},er.prototype.byUri=function(e){return this.resolve(function(t){return t.uriMap[e]})},er.prototype.add=function(e,t){this.uriMap[e.uri]=e,t?this.wellknown.push(e):this.custom.push(e),this.mapPrefix(e.prefix,e.uri)},er.prototype.uriByPrefix=function(e){var t=e||"xmlns";return this.resolve(function(e){return e.prefixMap[t]})},er.prototype.mapPrefix=function(e,t){this.prefixMap[e||"xmlns"]=t},er.prototype.getNSKey=function(e){return void 0!==e.prefix?e.uri+"|"+e.prefix:e.uri},er.prototype.logUsed=function(e){var t=e.uri,n=this.getNSKey(e),i=this.byUri(t);this.eachScope(function(e){e.used[n]=i})},er.prototype.getUsed=function(e){return[].concat(this.wellknown,this.custom).filter(e=>{var t=this.getNSKey(e);return this.used[t]})};var rr={"\n":"#10","\n\r":"#10",'"':"#34","'":"#39","<":"#60",">":"#62","&":"#38"},or={"<":"lt",">":"gt","&":"amp"};function ar(e,t,n){return(e=p(e)?e:""+e).replace(t,function(e){return"&"+n[e]+";"})}function sr(e){this.tagName=e}function lr(){}function cr(e){this.tagName=e}function pr(e,t){this.body=[],this.attrs=[],this.parent=e,this.propertyDescriptor=t}function ur(e,t,n){pr.call(this,e,t),this.serialization=n}function hr(){this.value="",this.write=function(e){this.value+=e}}function fr(e,t){var n=[""];this.append=function(t){return e.write(t),this},this.appendNewLine=function(){return t&&e.write("\n"),this},this.appendIndent=function(){return t&&e.write(n.join(" ")),this},this.indent=function(){return n.push(""),this},this.unindent=function(){return n.pop(),this}}function mr(e){return e=k({format:!1,preamble:!0},e||{}),{toXML:function(t,n){var i=n||new hr,r=new fr(i,e.format);e.preamble&&r.append('\n');var o=new pr,a=t.$model;if(o.getNamespaces().mapDefaultPrefixes(function(e){const t=e.config&&e.config.nsMap||{},n={};for(const e in Oi)n[e]=Oi[e];for(const e in t){n[t[e]]=e}for(const t of e.getPackages())n[t.prefix]=t.uri;return n}(a)),o.build(t).serializeTo(r),!n)return i.value}}}function dr(e,t){vi.call(this,e,t)}sr.prototype.build=function(e){return this.element=e,this},sr.prototype.serializeTo=function(e){e.appendIndent().append("<"+this.tagName+">"+this.element.id+"").appendNewLine()},lr.prototype.serializeValue=lr.prototype.serializeTo=function(e){e.append(this.escape?ar(this.value,Qi,or):this.value)},lr.prototype.build=function(e,t){return this.value=t,"String"===e.type&&-1!==t.search(Qi)&&(this.escape=!0),this},nr(cr,lr),cr.prototype.serializeTo=function(e){e.appendIndent().append("<"+this.tagName+">"),this.serializeValue(e),e.append("").appendNewLine()},pr.prototype.build=function(e){return function(e,t){var n=[{serializer:e,children:e.enter(t),index:0}];for(;n.length;){var i=n[n.length-1];if(i.index>=i.children.length)i.serializer.exit(),n.pop();else{var r=i.children[i.index++],o=r.create();i.serializer.body.push(o),r.element&&n.push({serializer:o,children:o.enter(r.element),index:0})}}}(this,e),this},pr.prototype.enter=function(e){this.element=e;var t=e.$descriptor,n=this.propertyDescriptor,i=t.isGeneric;if(this.otherAttrs=i?this.parseGenericNsAttributes(e):this.parseNsAttributes(e),this.ns=n?this.nsPropertyTagName(n):this.nsTagName(t),this.tagName=this.addTagName(this.ns),i)return this.collectGenericContainments(e);var r=function(e){return f(e.$descriptor.properties,function(t){var n=t.name;if(t.isVirtual)return!1;if(!u(e,n))return!1;var i=e[n];return i!==t.default&&null!==i&&(!t.isMany||i.length)})}(e);return this.parseAttributes(f(r,function(e){return e.isAttr})),this.collectContainments(function(e){return f(e,function(e){return!e.isAttr})}(r))},pr.prototype.exit=function(){this.parseGenericAttributes(this.element,this.otherAttrs)},pr.prototype.nsTagName=function(e){return function(e,t){return t.isGeneric?k({localName:t.ns.localName},e):k({localName:tr(t.ns.localName,t.$pkg)},e)}(this.logNamespaceUsed(e.ns),e)},pr.prototype.nsPropertyTagName=function(e){return function(e,t){return k({localName:t.ns.localName},e)}(this.logNamespaceUsed(e.ns),e)},pr.prototype.isLocalNs=function(e){return e.uri===this.ns.uri},pr.prototype.nsAttributeName=function(e){var t;if(t=p(e)?ui(e):e.ns,e.inherited)return{localName:t.localName};var n=this.logNamespaceUsed(t);return this.getNamespaces().logUsed(n),this.isLocalNs(n)?{localName:t.localName}:k({localName:t.localName},n)},pr.prototype.parseGenericNsAttributes=function(e){return Object.entries(e).filter(([t,n])=>!t.startsWith("$")&&this.parseNsAttribute(e,t,n)).map(([e,t])=>({name:e,value:t}))},pr.prototype.collectGenericContainments=function(e){var t=this,n=[],i=e.$body;i&&n.push({create:function(){return(new lr).build({type:"String"},i)}});var r=e.$children;return r&&m(r,function(e){n.push({create:function(){return new pr(t)},element:e})}),n},pr.prototype.parseNsAttribute=function(e,t,n){var i,r=e.$model,o=ui(t);if("xmlns"===o.prefix&&(i={prefix:o.localName,uri:n}),o.prefix||"xmlns"!==o.localName||(i={uri:n}),!i)return{name:t,value:n};if(r&&r.getPackage(n))this.logNamespace(i,!0,!0);else{var a=this.logNamespaceUsed(i,!0);this.getNamespaces().logUsed(a)}},pr.prototype.parseNsAttributes=function(e){var t=this,n=e.$attrs,i=[];return m(n,function(n,r){var o=t.parseNsAttribute(e,r,n);o&&i.push(o)}),i},pr.prototype.parseGenericAttributes=function(e,t){var n=this;m(t,function(t){try{n.addAttribute(n.nsAttributeName(t.name),t.value)}catch(n){"undefined"!=typeof console&&console.warn(`missing namespace information for <${t.name}=${t.value}> on`,e,n)}})},pr.prototype.collectContainments=function(e){var t=this,n=this.element,i=[];return m(e,function(e){var r=n.get(e.name),o=e.isReference;if(e.isMany||(r=[r]),e.isBody)i.push({create:function(){return(new lr).build(e,r[0])}});else if(pi(e.type))m(r,function(n){i.push({create:function(){return new cr(t.addTagName(t.nsPropertyTagName(e))).build(e,n)}})});else if(o)m(r,function(n){i.push({create:function(){return new sr(t.addTagName(t.nsPropertyTagName(e))).build(n)}})});else{var a=Ii(e);m(r,function(n){i.push({create:function(){return a?a===Li?new pr(t,e):new ur(t,e,a):new pr(t)},element:n})})}}),i},pr.prototype.getNamespaces=function(e){var t=this.namespaces;if(!t){var n=this.getParentNamespaces();e||!n?this.namespaces=t=new er(n):t=n}return t},pr.prototype.getParentNamespaces=function(){if(void 0!==this.parentNamespaces)return this.parentNamespaces;for(var e=null,t=this.parent;t;){if(t.namespaces){e=t.namespaces;break}if(void 0!==t.parentNamespaces){e=t.parentNamespaces;break}t=t.parent}return this.parentNamespaces=e},pr.prototype.logNamespace=function(e,t,n){var i=this.getNamespaces(n),r=e.uri,o=e.prefix;return i.byUri(r)&&!n||i.add(e,t),i.mapPrefix(o,r),e},pr.prototype.logNamespaceUsed=function(e,t){var n,i,r,o=this.getNamespaces(t),a=e.prefix,s=e.uri;if(!a&&!s)return{localName:e.localName};if(r=o.defaultUriByPrefix(a),!(s=s||r||o.uriByPrefix(a)))throw new Error("no namespace uri given for prefix <"+a+">");if((e=o.byUri(s))||a||(e=this.logNamespace({uri:s},r===s,!0)),!e){for(n=a,i=1;o.uriByPrefix(n);)n=a+"_"+i++;e=this.logNamespace({prefix:n,uri:s},r===s)}return a&&o.mapPrefix(a,s),e},pr.prototype.parseAttributes=function(e){var t=this,n=this.element;m(e,function(e){var i=n.get(e.name);if(e.isReference)if(e.isMany){var r=[];m(i,function(e){r.push(e.id)}),i=r.join(" ")}else i=i.id;t.addAttribute(t.nsAttributeName(e),i)})},pr.prototype.addTagName=function(e){var t=this.logNamespaceUsed(e);return this.getNamespaces().logUsed(t),ir(e)},pr.prototype.addAttribute=function(e,t){var n=this.attrs;p(t)&&(t=ar(t,Ji,rr));var i=function(e,t){const n=x(t);let i=a(e)?-1:void 0;return m(e,function(e,t){if(n(e,t))return i=t,!1}),i}(n,function(t){return t.name.localName===e.localName&&t.name.uri===e.uri&&t.name.prefix===e.prefix}),r={name:e,value:t};-1!==i?n.splice(i,1,r):n.push(r)},pr.prototype.serializeAttributes=function(e){var t=this.attrs,n=this.namespaces;n&&(t=function(e){return e.getUsed().filter(function(e){return"xml"!==e.prefix}).map(function(e){return{name:"xmlns"+(e.prefix?":"+e.prefix:""),value:e.uri}})}(n).concat(t)),m(t,function(t){e.append(" ").append(ir(t.name)).append('="').append(t.value).append('"')})},pr.prototype.serializeTo=function(e){!function(e,t){var n=[{serializer:e,index:0,opened:!1,indent:!1}];for(;n.length;){var i=n[n.length-1],r=i.serializer;if(!i.opened){var o=r.body[0];if(i.indent=o&&o.constructor!==lr,t.appendIndent().append("<"+r.tagName),r.serializeAttributes(t),t.append(o?">":" />"),i.opened=!0,!o){t.appendNewLine(),n.pop();continue}i.indent&&t.appendNewLine().indent()}if(i.index").appendNewLine(),n.pop()}}(this,e)},nr(ur,pr),ur.prototype.parseNsAttributes=function(e){var t=pr.prototype.parseNsAttributes.call(this,e).filter(e=>e.name!==this.serialization),n=e.$descriptor;if(n.name===this.propertyDescriptor.type)return t;var i=this.typeNs=this.nsTagName(n);this.getNamespaces().logUsed(this.typeNs);var r=e.$model.getPackage(i.uri),o=r.xml&&r.xml.typePrefix||"";return this.addAttribute(this.nsAttributeName(this.serialization),(i.prefix?i.prefix+":":"")+o+n.ns.localName),t},ur.prototype.isLocalNs=function(e){return e.uri===(this.typeNs||this.ns).uri},dr.prototype=Object.create(vi.prototype),dr.prototype.fromXML=function(e,t,n){p(t)||(n=t,t="bpmn:Definitions");var i=new Zi(k({model:this,lax:!0},n)),r=i.handler(t);return i.fromXML(e,r)},dr.prototype.toXML=function(e,t){var n=new mr(t);return new Promise(function(t,i){try{return t({xml:n.toXML(e)})}catch(e){return i(e)}})};const yr={bpmn:{name:"BPMN20",uri:"http://www.omg.org/spec/BPMN/20100524/MODEL",prefix:"bpmn",associations:[],types:[{name:"Interface",superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"operations",type:"Operation",isMany:!0},{name:"implementationRef",isAttr:!0,type:"String"}]},{name:"Operation",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"inMessageRef",type:"Message",isReference:!0},{name:"outMessageRef",type:"Message",isReference:!0},{name:"errorRef",type:"Error",isMany:!0,isReference:!0},{name:"implementationRef",isAttr:!0,type:"String"}]},{name:"EndPoint",superClass:["RootElement"]},{name:"Auditing",superClass:["BaseElement"]},{name:"GlobalTask",superClass:["CallableElement"],properties:[{name:"resources",type:"ResourceRole",isMany:!0}]},{name:"Monitoring",superClass:["BaseElement"]},{name:"Performer",superClass:["ResourceRole"]},{name:"Process",superClass:["FlowElementsContainer","CallableElement"],properties:[{name:"processType",type:"ProcessType",isAttr:!0},{name:"isClosed",isAttr:!0,type:"Boolean"},{name:"auditing",type:"Auditing"},{name:"monitoring",type:"Monitoring"},{name:"properties",type:"Property",isMany:!0},{name:"laneSets",isMany:!0,replaces:"FlowElementsContainer#laneSets",type:"LaneSet"},{name:"flowElements",isMany:!0,replaces:"FlowElementsContainer#flowElements",type:"FlowElement"},{name:"artifacts",type:"Artifact",isMany:!0},{name:"resources",type:"ResourceRole",isMany:!0},{name:"correlationSubscriptions",type:"CorrelationSubscription",isMany:!0},{name:"supports",type:"Process",isMany:!0,isReference:!0},{name:"definitionalCollaborationRef",type:"Collaboration",isAttr:!0,isReference:!0},{name:"isExecutable",isAttr:!0,type:"Boolean"}]},{name:"LaneSet",superClass:["BaseElement"],properties:[{name:"lanes",type:"Lane",isMany:!0},{name:"name",isAttr:!0,type:"String"}]},{name:"Lane",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"partitionElementRef",type:"BaseElement",isAttr:!0,isReference:!0},{name:"partitionElement",type:"BaseElement"},{name:"flowNodeRef",type:"FlowNode",isMany:!0,isReference:!0},{name:"childLaneSet",type:"LaneSet",xml:{serialize:"xsi:type"}}]},{name:"GlobalManualTask",superClass:["GlobalTask"]},{name:"ManualTask",superClass:["Task"]},{name:"UserTask",superClass:["Task"],properties:[{name:"renderings",type:"Rendering",isMany:!0},{name:"implementation",isAttr:!0,type:"String"}]},{name:"Rendering",superClass:["BaseElement"]},{name:"HumanPerformer",superClass:["Performer"]},{name:"PotentialOwner",superClass:["HumanPerformer"]},{name:"GlobalUserTask",superClass:["GlobalTask"],properties:[{name:"implementation",isAttr:!0,type:"String"},{name:"renderings",type:"Rendering",isMany:!0}]},{name:"Gateway",isAbstract:!0,superClass:["FlowNode"],properties:[{name:"gatewayDirection",type:"GatewayDirection",default:"Unspecified",isAttr:!0}]},{name:"EventBasedGateway",superClass:["Gateway"],properties:[{name:"instantiate",default:!1,isAttr:!0,type:"Boolean"},{name:"eventGatewayType",type:"EventBasedGatewayType",isAttr:!0,default:"Exclusive"}]},{name:"ComplexGateway",superClass:["Gateway"],properties:[{name:"activationCondition",type:"Expression",xml:{serialize:"xsi:type"}},{name:"default",type:"SequenceFlow",isAttr:!0,isReference:!0}]},{name:"ExclusiveGateway",superClass:["Gateway"],properties:[{name:"default",type:"SequenceFlow",isAttr:!0,isReference:!0}]},{name:"InclusiveGateway",superClass:["Gateway"],properties:[{name:"default",type:"SequenceFlow",isAttr:!0,isReference:!0}]},{name:"ParallelGateway",superClass:["Gateway"]},{name:"RootElement",isAbstract:!0,superClass:["BaseElement"]},{name:"Relationship",superClass:["BaseElement"],properties:[{name:"type",isAttr:!0,type:"String"},{name:"direction",type:"RelationshipDirection",isAttr:!0},{name:"source",isMany:!0,isReference:!0,type:"Element"},{name:"target",isMany:!0,isReference:!0,type:"Element"}]},{name:"BaseElement",isAbstract:!0,properties:[{name:"id",isAttr:!0,type:"String",isId:!0},{name:"documentation",type:"Documentation",isMany:!0},{name:"extensionDefinitions",type:"ExtensionDefinition",isMany:!0,isReference:!0},{name:"extensionElements",type:"ExtensionElements"}]},{name:"Extension",properties:[{name:"mustUnderstand",default:!1,isAttr:!0,type:"Boolean"},{name:"definition",type:"ExtensionDefinition",isAttr:!0,isReference:!0}]},{name:"ExtensionDefinition",properties:[{name:"name",isAttr:!0,type:"String"},{name:"extensionAttributeDefinitions",type:"ExtensionAttributeDefinition",isMany:!0}]},{name:"ExtensionAttributeDefinition",properties:[{name:"name",isAttr:!0,type:"String"},{name:"type",isAttr:!0,type:"String"},{name:"isReference",default:!1,isAttr:!0,type:"Boolean"},{name:"extensionDefinition",type:"ExtensionDefinition",isAttr:!0,isReference:!0}]},{name:"ExtensionElements",properties:[{name:"valueRef",isAttr:!0,isReference:!0,type:"Element"},{name:"values",type:"Element",isMany:!0},{name:"extensionAttributeDefinition",type:"ExtensionAttributeDefinition",isAttr:!0,isReference:!0}]},{name:"Documentation",superClass:["BaseElement"],properties:[{name:"text",type:"String",isBody:!0},{name:"textFormat",default:"text/plain",isAttr:!0,type:"String"}]},{name:"Event",isAbstract:!0,superClass:["FlowNode","InteractionNode"],properties:[{name:"properties",type:"Property",isMany:!0}]},{name:"IntermediateCatchEvent",superClass:["CatchEvent"]},{name:"IntermediateThrowEvent",superClass:["ThrowEvent"]},{name:"EndEvent",superClass:["ThrowEvent"]},{name:"StartEvent",superClass:["CatchEvent"],properties:[{name:"isInterrupting",default:!0,isAttr:!0,type:"Boolean"}]},{name:"ThrowEvent",isAbstract:!0,superClass:["Event"],properties:[{name:"dataInputs",type:"DataInput",isMany:!0},{name:"dataInputAssociations",type:"DataInputAssociation",isMany:!0},{name:"inputSet",type:"InputSet"},{name:"eventDefinitions",type:"EventDefinition",isMany:!0},{name:"eventDefinitionRef",type:"EventDefinition",isMany:!0,isReference:!0}]},{name:"CatchEvent",isAbstract:!0,superClass:["Event"],properties:[{name:"parallelMultiple",isAttr:!0,type:"Boolean",default:!1},{name:"dataOutputs",type:"DataOutput",isMany:!0},{name:"dataOutputAssociations",type:"DataOutputAssociation",isMany:!0},{name:"outputSet",type:"OutputSet"},{name:"eventDefinitions",type:"EventDefinition",isMany:!0},{name:"eventDefinitionRef",type:"EventDefinition",isMany:!0,isReference:!0}]},{name:"BoundaryEvent",superClass:["CatchEvent"],properties:[{name:"cancelActivity",default:!0,isAttr:!0,type:"Boolean"},{name:"attachedToRef",type:"Activity",isAttr:!0,isReference:!0}]},{name:"EventDefinition",isAbstract:!0,superClass:["RootElement"]},{name:"CancelEventDefinition",superClass:["EventDefinition"]},{name:"ErrorEventDefinition",superClass:["EventDefinition"],properties:[{name:"errorRef",type:"Error",isAttr:!0,isReference:!0}]},{name:"TerminateEventDefinition",superClass:["EventDefinition"]},{name:"EscalationEventDefinition",superClass:["EventDefinition"],properties:[{name:"escalationRef",type:"Escalation",isAttr:!0,isReference:!0}]},{name:"Escalation",properties:[{name:"structureRef",type:"ItemDefinition",isAttr:!0,isReference:!0},{name:"name",isAttr:!0,type:"String"},{name:"escalationCode",isAttr:!0,type:"String"}],superClass:["RootElement"]},{name:"CompensateEventDefinition",superClass:["EventDefinition"],properties:[{name:"waitForCompletion",isAttr:!0,type:"Boolean",default:!0},{name:"activityRef",type:"Activity",isAttr:!0,isReference:!0}]},{name:"TimerEventDefinition",superClass:["EventDefinition"],properties:[{name:"timeDate",type:"Expression",xml:{serialize:"xsi:type"}},{name:"timeCycle",type:"Expression",xml:{serialize:"xsi:type"}},{name:"timeDuration",type:"Expression",xml:{serialize:"xsi:type"}}]},{name:"LinkEventDefinition",superClass:["EventDefinition"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"target",type:"LinkEventDefinition",isReference:!0},{name:"source",type:"LinkEventDefinition",isMany:!0,isReference:!0}]},{name:"MessageEventDefinition",superClass:["EventDefinition"],properties:[{name:"messageRef",type:"Message",isAttr:!0,isReference:!0},{name:"operationRef",type:"Operation",isReference:!0}]},{name:"ConditionalEventDefinition",superClass:["EventDefinition"],properties:[{name:"condition",type:"Expression",xml:{serialize:"xsi:type"}}]},{name:"SignalEventDefinition",superClass:["EventDefinition"],properties:[{name:"signalRef",type:"Signal",isAttr:!0,isReference:!0}]},{name:"Signal",superClass:["RootElement"],properties:[{name:"structureRef",type:"ItemDefinition",isAttr:!0,isReference:!0},{name:"name",isAttr:!0,type:"String"}]},{name:"ImplicitThrowEvent",superClass:["ThrowEvent"]},{name:"DataState",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"}]},{name:"ItemAwareElement",superClass:["BaseElement"],properties:[{name:"itemSubjectRef",type:"ItemDefinition",isAttr:!0,isReference:!0},{name:"dataState",type:"DataState"}]},{name:"DataAssociation",superClass:["BaseElement"],properties:[{name:"sourceRef",type:"ItemAwareElement",isMany:!0,isReference:!0},{name:"targetRef",type:"ItemAwareElement",isReference:!0},{name:"transformation",type:"FormalExpression",xml:{serialize:"property"}},{name:"assignment",type:"Assignment",isMany:!0}]},{name:"DataInput",superClass:["ItemAwareElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"isCollection",default:!1,isAttr:!0,type:"Boolean"},{name:"inputSetRef",type:"InputSet",isMany:!0,isVirtual:!0,isReference:!0},{name:"inputSetWithOptional",type:"InputSet",isMany:!0,isVirtual:!0,isReference:!0},{name:"inputSetWithWhileExecuting",type:"InputSet",isMany:!0,isVirtual:!0,isReference:!0}]},{name:"DataOutput",superClass:["ItemAwareElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"isCollection",default:!1,isAttr:!0,type:"Boolean"},{name:"outputSetRef",type:"OutputSet",isMany:!0,isVirtual:!0,isReference:!0},{name:"outputSetWithOptional",type:"OutputSet",isMany:!0,isVirtual:!0,isReference:!0},{name:"outputSetWithWhileExecuting",type:"OutputSet",isMany:!0,isVirtual:!0,isReference:!0}]},{name:"InputSet",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"dataInputRefs",type:"DataInput",isMany:!0,isReference:!0},{name:"optionalInputRefs",type:"DataInput",isMany:!0,isReference:!0},{name:"whileExecutingInputRefs",type:"DataInput",isMany:!0,isReference:!0},{name:"outputSetRefs",type:"OutputSet",isMany:!0,isReference:!0}]},{name:"OutputSet",superClass:["BaseElement"],properties:[{name:"dataOutputRefs",type:"DataOutput",isMany:!0,isReference:!0},{name:"name",isAttr:!0,type:"String"},{name:"inputSetRefs",type:"InputSet",isMany:!0,isReference:!0},{name:"optionalOutputRefs",type:"DataOutput",isMany:!0,isReference:!0},{name:"whileExecutingOutputRefs",type:"DataOutput",isMany:!0,isReference:!0}]},{name:"Property",superClass:["ItemAwareElement"],properties:[{name:"name",isAttr:!0,type:"String"}]},{name:"DataInputAssociation",superClass:["DataAssociation"]},{name:"DataOutputAssociation",superClass:["DataAssociation"]},{name:"InputOutputSpecification",superClass:["BaseElement"],properties:[{name:"dataInputs",type:"DataInput",isMany:!0},{name:"dataOutputs",type:"DataOutput",isMany:!0},{name:"inputSets",type:"InputSet",isMany:!0},{name:"outputSets",type:"OutputSet",isMany:!0}]},{name:"DataObject",superClass:["FlowElement","ItemAwareElement"],properties:[{name:"isCollection",default:!1,isAttr:!0,type:"Boolean"}]},{name:"InputOutputBinding",properties:[{name:"inputDataRef",type:"InputSet",isAttr:!0,isReference:!0},{name:"outputDataRef",type:"OutputSet",isAttr:!0,isReference:!0},{name:"operationRef",type:"Operation",isAttr:!0,isReference:!0}]},{name:"Assignment",superClass:["BaseElement"],properties:[{name:"from",type:"Expression",xml:{serialize:"xsi:type"}},{name:"to",type:"Expression",xml:{serialize:"xsi:type"}}]},{name:"DataStore",superClass:["RootElement","ItemAwareElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"capacity",isAttr:!0,type:"Integer"},{name:"isUnlimited",default:!0,isAttr:!0,type:"Boolean"}]},{name:"DataStoreReference",superClass:["ItemAwareElement","FlowElement"],properties:[{name:"dataStoreRef",type:"DataStore",isAttr:!0,isReference:!0}]},{name:"DataObjectReference",superClass:["ItemAwareElement","FlowElement"],properties:[{name:"dataObjectRef",type:"DataObject",isAttr:!0,isReference:!0}]},{name:"ConversationLink",superClass:["BaseElement"],properties:[{name:"sourceRef",type:"InteractionNode",isAttr:!0,isReference:!0},{name:"targetRef",type:"InteractionNode",isAttr:!0,isReference:!0},{name:"name",isAttr:!0,type:"String"}]},{name:"ConversationAssociation",superClass:["BaseElement"],properties:[{name:"innerConversationNodeRef",type:"ConversationNode",isAttr:!0,isReference:!0},{name:"outerConversationNodeRef",type:"ConversationNode",isAttr:!0,isReference:!0}]},{name:"CallConversation",superClass:["ConversationNode"],properties:[{name:"calledCollaborationRef",type:"Collaboration",isAttr:!0,isReference:!0},{name:"participantAssociations",type:"ParticipantAssociation",isMany:!0}]},{name:"Conversation",superClass:["ConversationNode"]},{name:"SubConversation",superClass:["ConversationNode"],properties:[{name:"conversationNodes",type:"ConversationNode",isMany:!0}]},{name:"ConversationNode",isAbstract:!0,superClass:["InteractionNode","BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"participantRef",type:"Participant",isMany:!0,isReference:!0},{name:"messageFlowRefs",type:"MessageFlow",isMany:!0,isReference:!0},{name:"correlationKeys",type:"CorrelationKey",isMany:!0}]},{name:"GlobalConversation",superClass:["Collaboration"]},{name:"PartnerEntity",superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"participantRef",type:"Participant",isMany:!0,isReference:!0}]},{name:"PartnerRole",superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"participantRef",type:"Participant",isMany:!0,isReference:!0}]},{name:"CorrelationProperty",superClass:["RootElement"],properties:[{name:"correlationPropertyRetrievalExpression",type:"CorrelationPropertyRetrievalExpression",isMany:!0},{name:"name",isAttr:!0,type:"String"},{name:"type",type:"ItemDefinition",isAttr:!0,isReference:!0}]},{name:"Error",superClass:["RootElement"],properties:[{name:"structureRef",type:"ItemDefinition",isAttr:!0,isReference:!0},{name:"name",isAttr:!0,type:"String"},{name:"errorCode",isAttr:!0,type:"String"}]},{name:"CorrelationKey",superClass:["BaseElement"],properties:[{name:"correlationPropertyRef",type:"CorrelationProperty",isMany:!0,isReference:!0},{name:"name",isAttr:!0,type:"String"}]},{name:"Expression",superClass:["BaseElement"],isAbstract:!1,properties:[{name:"body",isBody:!0,type:"String"}]},{name:"FormalExpression",superClass:["Expression"],properties:[{name:"language",isAttr:!0,type:"String"},{name:"evaluatesToTypeRef",type:"ItemDefinition",isAttr:!0,isReference:!0}]},{name:"Message",superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"itemRef",type:"ItemDefinition",isAttr:!0,isReference:!0}]},{name:"ItemDefinition",superClass:["RootElement"],properties:[{name:"itemKind",type:"ItemKind",isAttr:!0},{name:"structureRef",isAttr:!0,type:"String"},{name:"isCollection",default:!1,isAttr:!0,type:"Boolean"},{name:"import",type:"Import",isAttr:!0,isReference:!0}]},{name:"FlowElement",isAbstract:!0,superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"auditing",type:"Auditing"},{name:"monitoring",type:"Monitoring"},{name:"categoryValueRef",type:"CategoryValue",isMany:!0,isReference:!0}]},{name:"SequenceFlow",superClass:["FlowElement"],properties:[{name:"isImmediate",isAttr:!0,type:"Boolean"},{name:"conditionExpression",type:"Expression",xml:{serialize:"xsi:type"}},{name:"sourceRef",type:"FlowNode",isAttr:!0,isReference:!0},{name:"targetRef",type:"FlowNode",isAttr:!0,isReference:!0}]},{name:"FlowElementsContainer",isAbstract:!0,superClass:["BaseElement"],properties:[{name:"laneSets",type:"LaneSet",isMany:!0},{name:"flowElements",type:"FlowElement",isMany:!0}]},{name:"CallableElement",isAbstract:!0,superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"ioSpecification",type:"InputOutputSpecification",xml:{serialize:"property"}},{name:"supportedInterfaceRef",type:"Interface",isMany:!0,isReference:!0},{name:"ioBinding",type:"InputOutputBinding",isMany:!0,xml:{serialize:"property"}}]},{name:"FlowNode",isAbstract:!0,superClass:["FlowElement"],properties:[{name:"incoming",type:"SequenceFlow",isMany:!0,isReference:!0},{name:"outgoing",type:"SequenceFlow",isMany:!0,isReference:!0},{name:"lanes",type:"Lane",isMany:!0,isVirtual:!0,isReference:!0}]},{name:"CorrelationPropertyRetrievalExpression",superClass:["BaseElement"],properties:[{name:"messagePath",type:"FormalExpression"},{name:"messageRef",type:"Message",isAttr:!0,isReference:!0}]},{name:"CorrelationPropertyBinding",superClass:["BaseElement"],properties:[{name:"dataPath",type:"FormalExpression"},{name:"correlationPropertyRef",type:"CorrelationProperty",isAttr:!0,isReference:!0}]},{name:"Resource",superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"resourceParameters",type:"ResourceParameter",isMany:!0}]},{name:"ResourceParameter",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"isRequired",isAttr:!0,type:"Boolean"},{name:"type",type:"ItemDefinition",isAttr:!0,isReference:!0}]},{name:"CorrelationSubscription",superClass:["BaseElement"],properties:[{name:"correlationKeyRef",type:"CorrelationKey",isAttr:!0,isReference:!0},{name:"correlationPropertyBinding",type:"CorrelationPropertyBinding",isMany:!0}]},{name:"MessageFlow",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"sourceRef",type:"InteractionNode",isAttr:!0,isReference:!0},{name:"targetRef",type:"InteractionNode",isAttr:!0,isReference:!0},{name:"messageRef",type:"Message",isAttr:!0,isReference:!0}]},{name:"MessageFlowAssociation",superClass:["BaseElement"],properties:[{name:"innerMessageFlowRef",type:"MessageFlow",isAttr:!0,isReference:!0},{name:"outerMessageFlowRef",type:"MessageFlow",isAttr:!0,isReference:!0}]},{name:"InteractionNode",isAbstract:!0,properties:[{name:"incomingConversationLinks",type:"ConversationLink",isMany:!0,isVirtual:!0,isReference:!0},{name:"outgoingConversationLinks",type:"ConversationLink",isMany:!0,isVirtual:!0,isReference:!0}]},{name:"Participant",superClass:["InteractionNode","BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"interfaceRef",type:"Interface",isMany:!0,isReference:!0},{name:"participantMultiplicity",type:"ParticipantMultiplicity"},{name:"endPointRefs",type:"EndPoint",isMany:!0,isReference:!0},{name:"processRef",type:"Process",isAttr:!0,isReference:!0}]},{name:"ParticipantAssociation",superClass:["BaseElement"],properties:[{name:"innerParticipantRef",type:"Participant",isAttr:!0,isReference:!0},{name:"outerParticipantRef",type:"Participant",isAttr:!0,isReference:!0}]},{name:"ParticipantMultiplicity",properties:[{name:"minimum",default:0,isAttr:!0,type:"Integer"},{name:"maximum",default:1,isAttr:!0,type:"Integer"}],superClass:["BaseElement"]},{name:"Collaboration",superClass:["RootElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"isClosed",isAttr:!0,type:"Boolean"},{name:"participants",type:"Participant",isMany:!0},{name:"messageFlows",type:"MessageFlow",isMany:!0},{name:"artifacts",type:"Artifact",isMany:!0},{name:"conversations",type:"ConversationNode",isMany:!0},{name:"conversationAssociations",type:"ConversationAssociation"},{name:"participantAssociations",type:"ParticipantAssociation",isMany:!0},{name:"messageFlowAssociations",type:"MessageFlowAssociation",isMany:!0},{name:"correlationKeys",type:"CorrelationKey",isMany:!0},{name:"choreographyRef",type:"Choreography",isMany:!0,isReference:!0},{name:"conversationLinks",type:"ConversationLink",isMany:!0}]},{name:"ChoreographyActivity",isAbstract:!0,superClass:["FlowNode"],properties:[{name:"participantRef",type:"Participant",isMany:!0,isReference:!0},{name:"initiatingParticipantRef",type:"Participant",isAttr:!0,isReference:!0},{name:"correlationKeys",type:"CorrelationKey",isMany:!0},{name:"loopType",type:"ChoreographyLoopType",default:"None",isAttr:!0}]},{name:"CallChoreography",superClass:["ChoreographyActivity"],properties:[{name:"calledChoreographyRef",type:"Choreography",isAttr:!0,isReference:!0},{name:"participantAssociations",type:"ParticipantAssociation",isMany:!0}]},{name:"SubChoreography",superClass:["ChoreographyActivity","FlowElementsContainer"],properties:[{name:"artifacts",type:"Artifact",isMany:!0}]},{name:"ChoreographyTask",superClass:["ChoreographyActivity"],properties:[{name:"messageFlowRef",type:"MessageFlow",isMany:!0,isReference:!0}]},{name:"Choreography",superClass:["Collaboration","FlowElementsContainer"]},{name:"GlobalChoreographyTask",superClass:["Choreography"],properties:[{name:"initiatingParticipantRef",type:"Participant",isAttr:!0,isReference:!0}]},{name:"TextAnnotation",superClass:["Artifact"],properties:[{name:"text",type:"String"},{name:"textFormat",default:"text/plain",isAttr:!0,type:"String"}]},{name:"Group",superClass:["Artifact"],properties:[{name:"categoryValueRef",type:"CategoryValue",isAttr:!0,isReference:!0}]},{name:"Association",superClass:["Artifact"],properties:[{name:"associationDirection",type:"AssociationDirection",isAttr:!0},{name:"sourceRef",type:"BaseElement",isAttr:!0,isReference:!0},{name:"targetRef",type:"BaseElement",isAttr:!0,isReference:!0}]},{name:"Category",superClass:["RootElement"],properties:[{name:"categoryValue",type:"CategoryValue",isMany:!0},{name:"name",isAttr:!0,type:"String"}]},{name:"Artifact",isAbstract:!0,superClass:["BaseElement"]},{name:"CategoryValue",superClass:["BaseElement"],properties:[{name:"categorizedFlowElements",type:"FlowElement",isMany:!0,isVirtual:!0,isReference:!0},{name:"value",isAttr:!0,type:"String"}]},{name:"Activity",isAbstract:!0,superClass:["FlowNode"],properties:[{name:"isForCompensation",default:!1,isAttr:!0,type:"Boolean"},{name:"default",type:"SequenceFlow",isAttr:!0,isReference:!0},{name:"ioSpecification",type:"InputOutputSpecification",xml:{serialize:"property"}},{name:"boundaryEventRefs",type:"BoundaryEvent",isMany:!0,isReference:!0},{name:"properties",type:"Property",isMany:!0},{name:"dataInputAssociations",type:"DataInputAssociation",isMany:!0},{name:"dataOutputAssociations",type:"DataOutputAssociation",isMany:!0},{name:"startQuantity",default:1,isAttr:!0,type:"Integer"},{name:"resources",type:"ResourceRole",isMany:!0},{name:"completionQuantity",default:1,isAttr:!0,type:"Integer"},{name:"loopCharacteristics",type:"LoopCharacteristics"}]},{name:"ServiceTask",superClass:["Task"],properties:[{name:"implementation",isAttr:!0,type:"String"},{name:"operationRef",type:"Operation",isAttr:!0,isReference:!0}]},{name:"SubProcess",superClass:["Activity","FlowElementsContainer","InteractionNode"],properties:[{name:"triggeredByEvent",default:!1,isAttr:!0,type:"Boolean"},{name:"artifacts",type:"Artifact",isMany:!0}]},{name:"LoopCharacteristics",isAbstract:!0,superClass:["BaseElement"]},{name:"MultiInstanceLoopCharacteristics",superClass:["LoopCharacteristics"],properties:[{name:"isSequential",default:!1,isAttr:!0,type:"Boolean"},{name:"behavior",type:"MultiInstanceBehavior",default:"All",isAttr:!0},{name:"loopCardinality",type:"Expression",xml:{serialize:"xsi:type"}},{name:"loopDataInputRef",type:"ItemAwareElement",isReference:!0},{name:"loopDataOutputRef",type:"ItemAwareElement",isReference:!0},{name:"inputDataItem",type:"DataInput",xml:{serialize:"property"}},{name:"outputDataItem",type:"DataOutput",xml:{serialize:"property"}},{name:"complexBehaviorDefinition",type:"ComplexBehaviorDefinition",isMany:!0},{name:"completionCondition",type:"Expression",xml:{serialize:"xsi:type"}},{name:"oneBehaviorEventRef",type:"EventDefinition",isAttr:!0,isReference:!0},{name:"noneBehaviorEventRef",type:"EventDefinition",isAttr:!0,isReference:!0}]},{name:"StandardLoopCharacteristics",superClass:["LoopCharacteristics"],properties:[{name:"testBefore",default:!1,isAttr:!0,type:"Boolean"},{name:"loopCondition",type:"Expression",xml:{serialize:"xsi:type"}},{name:"loopMaximum",type:"Integer",isAttr:!0}]},{name:"CallActivity",superClass:["Activity","InteractionNode"],properties:[{name:"calledElement",type:"String",isAttr:!0}]},{name:"Task",superClass:["Activity","InteractionNode"]},{name:"SendTask",superClass:["Task"],properties:[{name:"implementation",isAttr:!0,type:"String"},{name:"operationRef",type:"Operation",isAttr:!0,isReference:!0},{name:"messageRef",type:"Message",isAttr:!0,isReference:!0}]},{name:"ReceiveTask",superClass:["Task"],properties:[{name:"implementation",isAttr:!0,type:"String"},{name:"instantiate",default:!1,isAttr:!0,type:"Boolean"},{name:"operationRef",type:"Operation",isAttr:!0,isReference:!0},{name:"messageRef",type:"Message",isAttr:!0,isReference:!0}]},{name:"ScriptTask",superClass:["Task"],properties:[{name:"scriptFormat",isAttr:!0,type:"String"},{name:"script",type:"String"}]},{name:"BusinessRuleTask",superClass:["Task"],properties:[{name:"implementation",isAttr:!0,type:"String"}]},{name:"AdHocSubProcess",superClass:["SubProcess"],properties:[{name:"completionCondition",type:"Expression",xml:{serialize:"xsi:type"}},{name:"ordering",type:"AdHocOrdering",isAttr:!0},{name:"cancelRemainingInstances",default:!0,isAttr:!0,type:"Boolean"}]},{name:"Transaction",superClass:["SubProcess"],properties:[{name:"protocol",isAttr:!0,type:"String"},{name:"method",isAttr:!0,type:"String"}]},{name:"GlobalScriptTask",superClass:["GlobalTask"],properties:[{name:"scriptLanguage",isAttr:!0,type:"String"},{name:"script",isAttr:!0,type:"String"}]},{name:"GlobalBusinessRuleTask",superClass:["GlobalTask"],properties:[{name:"implementation",isAttr:!0,type:"String"}]},{name:"ComplexBehaviorDefinition",superClass:["BaseElement"],properties:[{name:"condition",type:"FormalExpression"},{name:"event",type:"ImplicitThrowEvent"}]},{name:"ResourceRole",superClass:["BaseElement"],properties:[{name:"resourceRef",type:"Resource",isReference:!0},{name:"resourceParameterBindings",type:"ResourceParameterBinding",isMany:!0},{name:"resourceAssignmentExpression",type:"ResourceAssignmentExpression"},{name:"name",isAttr:!0,type:"String"}]},{name:"ResourceParameterBinding",properties:[{name:"expression",type:"Expression",xml:{serialize:"xsi:type"}},{name:"parameterRef",type:"ResourceParameter",isAttr:!0,isReference:!0}],superClass:["BaseElement"]},{name:"ResourceAssignmentExpression",properties:[{name:"expression",type:"Expression",xml:{serialize:"xsi:type"}}],superClass:["BaseElement"]},{name:"Import",properties:[{name:"importType",isAttr:!0,type:"String"},{name:"location",isAttr:!0,type:"String"},{name:"namespace",isAttr:!0,type:"String"}]},{name:"Definitions",superClass:["BaseElement"],properties:[{name:"name",isAttr:!0,type:"String"},{name:"targetNamespace",isAttr:!0,type:"String"},{name:"expressionLanguage",default:"http://www.w3.org/1999/XPath",isAttr:!0,type:"String"},{name:"typeLanguage",default:"http://www.w3.org/2001/XMLSchema",isAttr:!0,type:"String"},{name:"imports",type:"Import",isMany:!0},{name:"extensions",type:"Extension",isMany:!0},{name:"rootElements",type:"RootElement",isMany:!0},{name:"diagrams",isMany:!0,type:"bpmndi:BPMNDiagram"},{name:"exporter",isAttr:!0,type:"String"},{name:"relationships",type:"Relationship",isMany:!0},{name:"exporterVersion",isAttr:!0,type:"String"}]}],enumerations:[{name:"ProcessType",literalValues:[{name:"None"},{name:"Public"},{name:"Private"}]},{name:"GatewayDirection",literalValues:[{name:"Unspecified"},{name:"Converging"},{name:"Diverging"},{name:"Mixed"}]},{name:"EventBasedGatewayType",literalValues:[{name:"Parallel"},{name:"Exclusive"}]},{name:"RelationshipDirection",literalValues:[{name:"None"},{name:"Forward"},{name:"Backward"},{name:"Both"}]},{name:"ItemKind",literalValues:[{name:"Physical"},{name:"Information"}]},{name:"ChoreographyLoopType",literalValues:[{name:"None"},{name:"Standard"},{name:"MultiInstanceSequential"},{name:"MultiInstanceParallel"}]},{name:"AssociationDirection",literalValues:[{name:"None"},{name:"One"},{name:"Both"}]},{name:"MultiInstanceBehavior",literalValues:[{name:"None"},{name:"One"},{name:"All"},{name:"Complex"}]},{name:"AdHocOrdering",literalValues:[{name:"Parallel"},{name:"Sequential"}]}],xml:{tagAlias:"lowerCase",typePrefix:"t"}},bpmndi:{name:"BPMNDI",uri:"http://www.omg.org/spec/BPMN/20100524/DI",prefix:"bpmndi",types:[{name:"BPMNDiagram",properties:[{name:"plane",type:"BPMNPlane",redefines:"di:Diagram#rootElement"},{name:"labelStyle",type:"BPMNLabelStyle",isMany:!0}],superClass:["di:Diagram"]},{name:"BPMNPlane",properties:[{name:"bpmnElement",isAttr:!0,isReference:!0,type:"bpmn:BaseElement",redefines:"di:DiagramElement#modelElement"}],superClass:["di:Plane"]},{name:"BPMNShape",properties:[{name:"bpmnElement",isAttr:!0,isReference:!0,type:"bpmn:BaseElement",redefines:"di:DiagramElement#modelElement"},{name:"isHorizontal",isAttr:!0,type:"Boolean"},{name:"isExpanded",isAttr:!0,type:"Boolean"},{name:"isMarkerVisible",isAttr:!0,type:"Boolean"},{name:"label",type:"BPMNLabel"},{name:"isMessageVisible",isAttr:!0,type:"Boolean"},{name:"participantBandKind",type:"ParticipantBandKind",isAttr:!0},{name:"choreographyActivityShape",type:"BPMNShape",isAttr:!0,isReference:!0}],superClass:["di:LabeledShape"]},{name:"BPMNEdge",properties:[{name:"label",type:"BPMNLabel"},{name:"bpmnElement",isAttr:!0,isReference:!0,type:"bpmn:BaseElement",redefines:"di:DiagramElement#modelElement"},{name:"sourceElement",isAttr:!0,isReference:!0,type:"di:DiagramElement",redefines:"di:Edge#source"},{name:"targetElement",isAttr:!0,isReference:!0,type:"di:DiagramElement",redefines:"di:Edge#target"},{name:"messageVisibleKind",type:"MessageVisibleKind",isAttr:!0,default:"initiating"}],superClass:["di:LabeledEdge"]},{name:"BPMNLabel",properties:[{name:"labelStyle",type:"BPMNLabelStyle",isAttr:!0,isReference:!0,redefines:"di:DiagramElement#style"}],superClass:["di:Label"]},{name:"BPMNLabelStyle",properties:[{name:"font",type:"dc:Font"}],superClass:["di:Style"]}],enumerations:[{name:"ParticipantBandKind",literalValues:[{name:"top_initiating"},{name:"middle_initiating"},{name:"bottom_initiating"},{name:"top_non_initiating"},{name:"middle_non_initiating"},{name:"bottom_non_initiating"}]},{name:"MessageVisibleKind",literalValues:[{name:"initiating"},{name:"non_initiating"}]}],associations:[]},dc:{name:"DC",uri:"http://www.omg.org/spec/DD/20100524/DC",prefix:"dc",types:[{name:"Boolean"},{name:"Integer"},{name:"Real"},{name:"String"},{name:"Font",properties:[{name:"name",type:"String",isAttr:!0},{name:"size",type:"Real",isAttr:!0},{name:"isBold",type:"Boolean",isAttr:!0},{name:"isItalic",type:"Boolean",isAttr:!0},{name:"isUnderline",type:"Boolean",isAttr:!0},{name:"isStrikeThrough",type:"Boolean",isAttr:!0}]},{name:"Point",properties:[{name:"x",type:"Real",default:"0",isAttr:!0},{name:"y",type:"Real",default:"0",isAttr:!0}]},{name:"Bounds",properties:[{name:"x",type:"Real",default:"0",isAttr:!0},{name:"y",type:"Real",default:"0",isAttr:!0},{name:"width",type:"Real",isAttr:!0},{name:"height",type:"Real",isAttr:!0}]}],associations:[]},di:{name:"DI",uri:"http://www.omg.org/spec/DD/20100524/DI",prefix:"di",types:[{name:"DiagramElement",isAbstract:!0,properties:[{name:"id",isAttr:!0,isId:!0,type:"String"},{name:"extension",type:"Extension"},{name:"owningDiagram",type:"Diagram",isReadOnly:!0,isVirtual:!0,isReference:!0},{name:"owningElement",type:"DiagramElement",isReadOnly:!0,isVirtual:!0,isReference:!0},{name:"modelElement",isReadOnly:!0,isVirtual:!0,isReference:!0,type:"Element"},{name:"style",type:"Style",isReadOnly:!0,isVirtual:!0,isReference:!0},{name:"ownedElement",type:"DiagramElement",isReadOnly:!0,isMany:!0,isVirtual:!0}]},{name:"Node",isAbstract:!0,superClass:["DiagramElement"]},{name:"Edge",isAbstract:!0,superClass:["DiagramElement"],properties:[{name:"source",type:"DiagramElement",isReadOnly:!0,isVirtual:!0,isReference:!0},{name:"target",type:"DiagramElement",isReadOnly:!0,isVirtual:!0,isReference:!0},{name:"waypoint",isUnique:!1,isMany:!0,type:"dc:Point",xml:{serialize:"xsi:type"}}]},{name:"Diagram",isAbstract:!0,properties:[{name:"id",isAttr:!0,isId:!0,type:"String"},{name:"rootElement",type:"DiagramElement",isReadOnly:!0,isVirtual:!0},{name:"name",isAttr:!0,type:"String"},{name:"documentation",isAttr:!0,type:"String"},{name:"resolution",isAttr:!0,type:"Real"},{name:"ownedStyle",type:"Style",isReadOnly:!0,isMany:!0,isVirtual:!0}]},{name:"Shape",isAbstract:!0,superClass:["Node"],properties:[{name:"bounds",type:"dc:Bounds"}]},{name:"Plane",isAbstract:!0,superClass:["Node"],properties:[{name:"planeElement",type:"DiagramElement",subsettedProperty:"DiagramElement-ownedElement",isMany:!0}]},{name:"LabeledEdge",isAbstract:!0,superClass:["Edge"],properties:[{name:"ownedLabel",type:"Label",isReadOnly:!0,subsettedProperty:"DiagramElement-ownedElement",isMany:!0,isVirtual:!0}]},{name:"LabeledShape",isAbstract:!0,superClass:["Shape"],properties:[{name:"ownedLabel",type:"Label",isReadOnly:!0,subsettedProperty:"DiagramElement-ownedElement",isMany:!0,isVirtual:!0}]},{name:"Label",isAbstract:!0,superClass:["Node"],properties:[{name:"bounds",type:"dc:Bounds"}]},{name:"Style",isAbstract:!0,properties:[{name:"id",isAttr:!0,isId:!0,type:"String"}]},{name:"Extension",properties:[{name:"values",isMany:!0,type:"Element"}]}],associations:[],xml:{tagAlias:"lowerCase"}},bioc:{name:"bpmn.io colors for BPMN",uri:"http://bpmn.io/schema/bpmn/biocolor/1.0",prefix:"bioc",types:[{name:"ColoredShape",extends:["bpmndi:BPMNShape"],properties:[{name:"stroke",isAttr:!0,type:"String"},{name:"fill",isAttr:!0,type:"String"}]},{name:"ColoredEdge",extends:["bpmndi:BPMNEdge"],properties:[{name:"stroke",isAttr:!0,type:"String"},{name:"fill",isAttr:!0,type:"String"}]}],enumerations:[],associations:[]},color:{name:"BPMN in Color",uri:"http://www.omg.org/spec/BPMN/non-normative/color/1.0",prefix:"color",types:[{name:"ColoredLabel",extends:["bpmndi:BPMNLabel"],properties:[{name:"color",isAttr:!0,type:"String"}]},{name:"ColoredShape",extends:["bpmndi:BPMNShape"],properties:[{name:"background-color",isAttr:!0,type:"String"},{name:"border-color",isAttr:!0,type:"String"}]},{name:"ColoredEdge",extends:["bpmndi:BPMNEdge"],properties:[{name:"border-color",isAttr:!0,type:"String"}]}],enumerations:[],associations:[]}};function gr(e,t){return new dr(k({},yr,e),t)}function vr(e,t){return e.$instanceOf(t)}function xr(e){var t={},n=[],i={};function r(e,t){return function(n){e(n,t)}}function o(e){t[e.id]=e}function a(t,n){try{var r=i[t.id]&&function(t,n){if(t.gfx)throw new Error(`already rendered ${bt(t)}`);return e.element(t,i[t.id],n)}(t,n);return o(t),r}catch(e){s(e.message,{element:t,error:e}),console.error(`failed to import ${bt(t)}`,e)}}function s(t,n){e.error(t,n)}var l=this.registerDi=function(e){var t=e.bpmnElement;t?i[t.id]?s(`multiple DI elements defined for ${bt(t)}`,{element:t}):i[t.id]=e:s(`no bpmnElement referenced in ${bt(e)}`,{element:e})};function c(e){var t;t=e.plane,l(t),m(t.planeElement,p)}function p(e){l(e)}this.handleDefinitions=function(o,a){var p=o.diagrams;if(a&&-1===p.indexOf(a))throw new Error("diagram not part of ");if(!a&&p&&p.length&&(a=p[0]),!a)throw new Error("no diagram to display");i={},c(a);var g=a.plane;if(!g)throw new Error(`no plane for ${bt(a)}`);var v=g.bpmnElement;if(!v){if(v=function(e){return h(e.rootElements,function(e){return vr(e,"bpmn:Process")||vr(e,"bpmn:Collaboration")})}(o),!v)throw new Error("no process or collaboration to display");s(`correcting missing bpmnElement on ${bt(g)} to ${bt(v)}`),g.bpmnElement=v,l(g)}var x,b,E=function(t,n){return e.root(t,i[t.id],n)}(v,g);if(vr(v,"bpmn:Process")||vr(v,"bpmn:SubProcess"))d(v,E);else{if(!vr(v,"bpmn:Collaboration"))throw new Error(`unsupported bpmnElement for ${bt(g)}: ${bt(v)}`);b=E,m((x=v).participants,r(M,b)),n.push(function(){!function(e,t){m(e,r(y,t))}(x.messageFlows,b)}),w(x.artifacts,b),function(e,n){var i=f(e,function(e){return!t[e.id]&&vr(e,"bpmn:Process")&&e.laneSets});i.forEach(r(d,n))}(o.rootElements,E)}u(n)};var u=this.handleDeferred=function(){for(;n.length;)n.shift()()};function d(e,t){C(e,t),E(e.ioSpecification,t),w(e.artifacts,t),o(e)}function y(e,t){a(e,t)}function g(e,t){a(e,t)}function v(e,t){a(e,t)}function x(e,t){a(e,t)}function b(e,t){a(e,t)}function w(e,t){m(e,function(e){vr(e,"bpmn:Association")?n.push(function(){b(e,t)}):b(e,t)})}function E(e,t){e&&(m(e.dataInputs,r(v,t)),m(e.dataOutputs,r(x,t)))}var _=this.handleSubProcess=function(e,t){C(e,t),w(e.artifacts,t)};function k(e,t){var i=a(e,t);vr(e,"bpmn:SubProcess")&&_(e,i||t),vr(e,"bpmn:Activity")&&E(e.ioSpecification,t),n.push(function(){m(e.dataInputAssociations,r(g,t)),m(e.dataOutputAssociations,r(g,t))})}function A(e,t){a(e,t)}function S(e,t){n.push(function(){var n=a(e,t);e.childLaneSet&&R(e.childLaneSet,n||t),function(e){m(e.flowNodeRef,function(t){var n=t.get("lanes");n&&n.push(e)})}(e)})}function R(e,t){m(e.lanes,r(S,t))}function C(e,t){!function(e,t){m(e,function(e){vr(e,"bpmn:SequenceFlow")?n.push(function(){!function(e,t){a(e,t)}(e,t)}):vr(e,"bpmn:BoundaryEvent")?n.unshift(function(){k(e,t)}):vr(e,"bpmn:FlowNode")?k(e,t):vr(e,"bpmn:DataObject")||(vr(e,"bpmn:DataStoreReference")||vr(e,"bpmn:DataObjectReference")?A(e,t):s(`unrecognized flowElement ${bt(e)} in context ${bt(t&&t.businessObject)}`,{element:e,context:t}))})}(e.flowElements,t),e.laneSets&&function(e,t){m(e,r(R,t))}(e.laneSets,t)}function M(e,t){var n=a(e,t),i=e.processRef;i&&d(i,n||t)}}function br(e,t,n){var i,r,o,a=[];function s(e,t){var n=new xr({root:function(e,t){return i.add(e,t)},element:function(e,t,n){return i.add(e,t,n)},error:function(e,t){a.push({message:e,context:t})}});t=t||e.diagrams&&e.diagrams[0];var r=function(e,t){if(!t||!t.plane)return;var n,i=t.plane.bpmnElement,r=i;S(i,"bpmn:Process")||S(i,"bpmn:Collaboration")||(r=function(e){var t=e;for(;t;){if(S(t,"bpmn:Process"))return t;t=t.$parent}} +/** + * This file must not be changed or exchanged. + * + * @see http://bpmn.io/license for more information. + */(i));n=S(r,"bpmn:Collaboration")?r:h(e.rootElements,function(e){if(S(e,"bpmn:Collaboration"))return h(e.participants,function(e){return e.processRef===r})});var o=[r];n&&(o=function(e,t){let n=[];return m(e,function(e,i){n.push(t(e,i))}),n}(n.participants,function(e){return e.processRef})).push(n);var a=wr(o),s=[t],l=[i];return m(e.diagrams,function(e){if(e.plane){var t=e.plane.bpmnElement;-1!==a.indexOf(t)&&-1===l.indexOf(t)&&(s.push(e),l.push(t))}}),s}(e,t);if(!r)throw new Error("no diagram to display");m(r,function(t){n.handleDefinitions(e,t)});var s=t.plane.bpmnElement.id;o.setRootElement(o.findRoot(s+"_plane")||o.findRoot(s))}return new Promise(function(l,c){try{return i=e.get("bpmnImporter"),r=e.get("eventBus"),o=e.get("canvas"),r.fire("import.render.start",{definitions:t}),s(t,n),r.fire("import.render.complete",{error:void 0,warnings:a}),l({warnings:a})}catch(e){return e.warnings=a,c(e)}})}function wr(e){var t=[];return m(e,function(e){e&&(t.push(e),t=t.concat(wr(e.flowElements)))}),t}var Er,_r='',kr={verticalAlign:"middle"},Ar={color:"#404040"},Sr={zIndex:"1001",position:"fixed",top:"0",left:"0",right:"0",bottom:"0"},Rr={width:"100%",height:"100%",background:"rgba(40,40,40,0.2)"},Cr={position:"absolute",left:"50%",top:"40%",transform:"translate(-50%)",width:"260px",padding:"10px",background:"white",boxShadow:"0 1px 4px rgba(0,0,0,0.3)",fontFamily:"Helvetica, Arial, sans-serif",fontSize:"14px",display:"flex",lineHeight:"1.3"},Mr='
    '+_r+'Web-based tooling for BPMN, DMN and forms powered by bpmn.io.
    ';function Pr(){Er||(Ce(Er=Se(Mr),Sr),Ce(We("svg",Er),kr),Ce(We(".backdrop",Er),Rr),Ce(We(".notice",Er),Cr),Ce(We(".link",Er),Ar,{margin:"15px 20px 15px 10px",alignSelf:"center"}),ze.bind(Er,".backdrop","click",function(e){document.body.removeChild(Er)})),document.body.appendChild(Er)} +/** + * The code in the area + * must not be changed. + * + * @see http://bpmn.io/license for more information. + */const Nr="djs-outline-hidden";function Br(e){e=k({},Tr,e),this._moddle=this._createModdle(e),this._container=this._createContainer(e),this._init(this._container,this._moddle,e), +/** + * Adds the project logo to the diagram container as + * required by the bpmn.io license. + * + * @see http://bpmn.io/license + * + * @param {Element} container + */ +function(e){const t=Se(''+_r+"");Ce(We("svg",t),kr),Ce(t,Ar,{position:"absolute",bottom:"15px",right:"15px",zIndex:"100"}),e.appendChild(t),Fe.bind(t,"click",function(e){Pr(),e.preventDefault()})}(this._container)}function Dr(e,t){return e.warnings=t,e}e(Br,ii),Br.prototype.importXML=async function(e,t){const n=this;let i=[];try{let o;e=this._emit("import.parse.start",{xml:e})||e;try{o=await this._moddle.fromXML(e,"bpmn:Definitions")}catch(e){throw this._emit("import.parse.complete",{error:e}),e}let a=o.rootElement;const s=o.references,l=o.warnings,c=o.elementsById;i=i.concat(l),a=this._emit("import.parse.complete",(r={error:null,definitions:a,elementsById:c,references:s,warnings:i},n.get("eventBus").createEvent(r)))||a;const p=await this.importDefinitions(a,t);return i=i.concat(p.warnings),this._emit("import.done",{error:null,warnings:i}),{warnings:i}}catch(e){let t=e;throw i=i.concat(t.warnings||[]),Dr(t,i),t=function(e){const t=/unparsable content <([^>]+)> detected([\s\S]*)$/.exec(e.message);t&&(e.message="unparsable content <"+t[1]+"> detected; this may indicate an invalid BPMN 2.0 diagram file"+t[2]);return e}(t),this._emit("import.done",{error:t,warnings:t.warnings}),t}var r},Br.prototype.importDefinitions=async function(e,t){this._setDefinitions(e);return{warnings:(await this.open(t)).warnings}},Br.prototype.open=async function(e){const t=this._definitions;let n=e;if(!t){const e=new Error("no XML imported");throw Dr(e,[]),e}if("string"==typeof e&&(n=function(e,t){if(!t)return null;return h(e.diagrams,function(e){return e.id===t})||null}(t,e),!n)){const t=new Error("BPMNDiagram <"+e+"> not found");throw Dr(t,[]),t}try{this.clear()}catch(e){throw Dr(e,[]),e}const{warnings:i}=await br(this,t,n);return{warnings:i}},Br.prototype.saveXML=async function(e){e=e||{};let t,n,i=this._definitions;try{if(!i)throw new Error("no definitions loaded");i=this._emit("saveXML.start",{definitions:i})||i;n=(await this._moddle.toXML(i,e)).xml,n=this._emit("saveXML.serialized",{xml:n})||n}catch(e){t=e}const r=t?{error:t}:{xml:n};if(this._emit("saveXML.done",r),t)throw t;return r},Br.prototype.saveSVG=async function(){let e,t;this._emit("saveSVG.start");try{const t=this.get("canvas"),n=t.getActiveLayer(),i=We(":scope > defs",t._svg),r=re(n),o=i?""+re(i)+"":"",a=t.getContainer();let s;Ne(a).add(Nr);try{s=n.getBBox()}finally{Ne(a).remove(Nr)}const l=s.x-5,c=s.y-5,p=s.width+10,u=s.height+10;e='\n\x3c!-- created with bpmn-js / http://bpmn.io --\x3e\n\n'+o+r+""}catch(e){t=e}if(this._emit("saveSVG.done",{error:t,svg:e}),t)throw t;return{svg:e}},Br.prototype._setDefinitions=function(e){this._definitions=e},Br.prototype.getModules=function(){return this._modules},Br.prototype.clear=function(){this.getDefinitions()&&ii.prototype.clear.call(this)},Br.prototype.destroy=function(){ii.prototype.destroy.call(this),$e(this._container)},Br.prototype.on=function(e,t,n,i){return this.get("eventBus").on(e,t,n,i)},Br.prototype.off=function(e,t){this.get("eventBus").off(e,t)},Br.prototype.attachTo=function(e){if(!e)throw new Error("parentNode required");this.detach(),e.get&&e.constructor.prototype.jquery&&(e=e.get(0)),"string"==typeof e&&(e=We(e)),e.appendChild(this._container),this._emit("attach",{}),this.get("canvas").resized()},Br.prototype.getDefinitions=function(){return this._definitions},Br.prototype.detach=function(){const e=this._container,t=e.parentNode;t&&(this._emit("detach",{}),t.removeChild(e))},Br.prototype._init=function(e,t,n){const i=n.modules||this.getModules(n),r=n.additionalModules||[],o=[].concat([{bpmnjs:["value",this],moddle:["value",t]}],i,r),a=k(function(e,t){let n={};return m(Object(e),function(e,i){-1===t.indexOf(i)&&(n[i]=e)}),n}(n,["additionalModules"]),{canvas:k({},n.canvas,{container:e}),modules:o});ii.call(this,a),n&&n.container&&this.attachTo(n.container)},Br.prototype._emit=function(e,t){return this.get("eventBus").fire(e,t)},Br.prototype._createContainer=function(e){const t=Se('
    ');return Ce(t,{width:Or(e.width),height:Or(e.height),position:e.position}),t},Br.prototype._createModdle=function(e){return new gr(k({},this._moddleExtensions,e.moddleExtensions))},Br.prototype._modules=[];const Tr={width:"100%",height:"100%",position:"relative"};function Or(e){return e+(l(e)?"px":"")}function Lr(e){Br.call(this,e)}e(Lr,Br),Lr.prototype._modules=[At,Jt,Bt,mn,dt],Lr.prototype._moddleExtensions={};function Ir(e,t,n){this._selectionWasVisible=!1;const i=E(()=>{this._selectionWasVisible=r()},300);function r(){var e=n.get();if(!e.length)return!1;var i=O(e),r=t.viewbox(),o=yt(i),a=yt(r);return o.left>=a.left&&o.right<=a.right&&o.top>=a.top&&o.bottom<=a.bottom}e.on("canvas.viewbox.changed",i),e.on("selection.changed",i),e.on("diagram.destroy",()=>{i.cancel()}),e.on("canvas.resized",()=>{var e=n.get();if(e.length&&this._selectionWasVisible&&!r()){var i=t.getRootElement();t.scrollToElement(e.concat(i))}}),this._flush=i.flush}Ir.$inject=["eventBus","canvas","selection"];var jr={__init__:["keepSelectionVisible"],__depends__:[mn],keepSelectionVisible:["type",Ir]},Fr=["c","C"],Vr=["v","V"],zr=["d","D"],Wr=["x","X"],$r=["y","Y"],Gr=["z","Z"];function Hr(e){return!e.altKey&&(e.ctrlKey||e.metaKey)}function Kr(e,t){return-1!==(e=a(e)?e:[e]).indexOf(t.key)||-1!==e.indexOf(t.code)||-1!==e.indexOf(function(e){var t=e.key,n=/^Key([A-Z])$/.exec(e.code||"");if(!n||"string"!=typeof t)return null;var i=n[1];if(Ur.test(t))return e.shiftKey?i:i.toLowerCase();if(e.shiftKey&&qr.test(t)&&e.keyCode===i.charCodeAt(0))return i;return null}(t))}var Ur=/^(?!\p{Script_Extensions=Latin}|\p{Script=Common})\p{Letter}$/u,qr=/^\P{Letter}$/u;function Yr(e){return e.shiftKey}var Xr="keyboard.keydown",Zr="Keyboard binding is now implicit; explicit binding to an element got removed. For more information, see https://github.com/bpmn-io/diagram-js/issues/661";function Jr(e,t){var n=this;this._config=e=e||{},this._eventBus=t,this._keydownHandler=this._keydownHandler.bind(this),this._keyupHandler=this._keyupHandler.bind(this),t.on("diagram.destroy",function(){n._fire("destroy"),n.unbind()}),e.bindTo&&console.error("unsupported configuration ",new Error(Zr));var i=e&&!1!==e.bind;t.on("canvas.init",function(e){n._target=e.svg,i&&n.bind(),n._fire("init")})}Jr.$inject=["config.keyboard","eventBus"],Jr.prototype._keydownHandler=function(e){this._keyHandler(e,Xr)},Jr.prototype._keyupHandler=function(e){this._keyHandler(e,"keyboard.keyup")},Jr.prototype._keyHandler=function(e,t){if(!this._isEventIgnored(e)){var n={keyEvent:e};this._eventBus.fire(t||Xr,n)&&e.preventDefault()}},Jr.prototype._isEventIgnored=function(e){return!1},Jr.prototype.bind=function(e){e&&console.error("unsupported argument ",new Error(Zr)),this.unbind(),e=this._node=this._target,Fe.bind(e,"keydown",this._keydownHandler),Fe.bind(e,"keyup",this._keyupHandler),this._fire("bind")},Jr.prototype.getBinding=function(){return this._node},Jr.prototype.unbind=function(){var e=this._node;e&&(this._fire("unbind"),Fe.unbind(e,"keydown",this._keydownHandler),Fe.unbind(e,"keyup",this._keyupHandler)),this._node=null},Jr.prototype._fire=function(e){this._eventBus.fire("keyboard."+e,{node:this._node})},Jr.prototype.addListener=function(e,t,n){c(e)&&(n=t,t=e,e=1e3),this._eventBus.on(n||Xr,e,t)},Jr.prototype.removeListener=function(e,t){this._eventBus.off(t||Xr,e)},Jr.prototype.hasModifier=function(e){return e.ctrlKey||e.metaKey||e.shiftKey||e.altKey},Jr.prototype.isCmd=Hr,Jr.prototype.isShift=Yr,Jr.prototype.isKey=Kr;function Qr(e,t){var n=this;e.on("editorActions.init",500,function(e){var i=e.editorActions;n.registerBindings(t,i)})}Qr.$inject=["eventBus","keyboard"],Qr.prototype.registerBindings=function(e,t){function n(n,i){t.isRegistered(n)&&e.addListener(i)}n("undo",function(e){if(function(e){return Hr(e)&&!Yr(e)&&Kr(Gr,e)}(e.keyEvent))return t.trigger("undo"),!0}),n("redo",function(e){if(function(e){return Hr(e)&&(Kr($r,e)||Kr(Gr,e)&&Yr(e))}(e.keyEvent))return t.trigger("redo"),!0}),n("copy",function(e){if(function(e){return Hr(e)&&Kr(Fr,e)}(e.keyEvent))return t.trigger("copy"),!0}),n("paste",function(e){if(function(e){return Hr(e)&&Kr(Vr,e)}(e.keyEvent))return t.trigger("paste"),!0}),n("duplicate",function(e){if(function(e){return Hr(e)&&Kr(zr,e)}(e.keyEvent))return t.trigger("duplicate"),!0}),n("cut",function(e){if(function(e){return Hr(e)&&Kr(Wr,e)}(e.keyEvent))return t.trigger("cut"),!0}),n("stepZoom",function(e){var n=e.keyEvent;if(Kr(["+","="],n)&&Hr(n))return t.trigger("stepZoom",{value:1}),!0}),n("stepZoom",function(e){var n=e.keyEvent;if(Kr("-",n)&&Hr(n))return t.trigger("stepZoom",{value:-1}),!0}),n("zoom",function(e){var n=e.keyEvent;if(Kr("0",n)&&Hr(n))return t.trigger("zoom",{value:1}),!0}),n("removeSelection",function(e){if(Kr(["Backspace","Delete"],e.keyEvent))return t.trigger("removeSelection"),!0})};var eo={__init__:["keyboard","keyboardBindings"],keyboard:["type",Jr],keyboardBindings:["type",Qr]},to={moveSpeed:50,moveSpeedAccelerated:200};function no(e,t,n){var i=this;this._config=k({},to,e||{}),t.addListener(function(e){var n=e.keyEvent,r=i._config;if(!t.isCmd(n))return;if(t.isKey(["ArrowLeft","ArrowUp","ArrowDown","ArrowRight"],n)){var o,a=t.isShift(n)?r.moveSpeedAccelerated:r.moveSpeed;switch(n.key){case"ArrowLeft":o="left";break;case"ArrowUp":o="up";break;case"ArrowRight":o="right";break;case"ArrowDown":o="down"}return i.moveCanvas({speed:a,direction:o}),!0}}),this.moveCanvas=function(e){var t=0,i=0,r=e.speed/Math.min(Math.sqrt(n.viewbox().scale),1);switch(e.direction){case"left":t=r;break;case"up":i=r;break;case"right":t=-r;break;case"down":i=-r}n.scroll({dx:t,dy:i})}}no.$inject=["config.keyboardMove","keyboard","canvas"];var io={__depends__:[eo],__init__:["keyboardMove"],keyboardMove:["type",no]},ro=/^djs-cursor-.*$/;function oo(e){var t=Ne(document.body);t.removeMatching(ro),e&&t.add("djs-cursor-"+e)}function ao(e,t){return{x:e.x-t.x,y:e.y-t.y}}function so(e,t){var n;function i(e){return a(e.originalEvent)}function r(i){var r,o=n.start,a=n.button,s=en(i),l=ao(s,o);(!n.dragging&&(r=l,Math.sqrt(Math.pow(r.x,2)+Math.pow(r.y,2))>15)&&(n.dragging=!0,0===a&&function(e,t){function n(){return!1}t=t||"element.click",e.once(t,5e3,n)}(e),oo("grab")),n.dragging)&&(l=ao(s,n.last||n.start),t.scroll({dx:l.x,dy:l.y}),n.last=s);i.preventDefault()}function o(e){Fe.unbind(document,"mousemove",r),Fe.unbind(document,"mouseup",o),n=null,oo(null)}function a(e){if(!Te(e.target,".djs-draggable")){var t=e.button;if(!(t>=2||e.ctrlKey||e.shiftKey||e.altKey))return n={button:t,start:en(e)},Fe.bind(document,"mousemove",r),Fe.bind(document,"mouseup",o),!0}}e.on("canvas.focus.changed",function(t){t.focused?e.on("element.mousedown",500,i):e.off("element.mousedown",i)}),e.on("diagram.destroy",function(){n&&o()}),this.isActive=function(){return!!n}}so.$inject=["eventBus","canvas"];var lo={__init__:["moveCanvas"],moveCanvas:["type",so]};function co(e){return Math.log(e)/Math.log(10)}function po(e,t){var n=co(e.min),i=co(e.max);return(Math.abs(n)+Math.abs(i))/t}var uo=Math.sign||function(e){return e>=0?1:-1},ho={min:.2,max:4};function fo(e,t,n){e=e||{},this._enabled=!1,this._canvas=n,this._container=n._container,this._handleWheel=_(this._handleWheel,this),this._totalDelta=0,this._scale=e.scale||.75;var i=this;t.on("canvas.mouseover",function(){i._init(!1!==e.enabled)}),t.on("canvas.mouseout",function(){i._init(!1)})}fo.$inject=["config.zoomScroll","eventBus","canvas"],fo.prototype.scroll=function(e){this._canvas.scroll(e)},fo.prototype.reset=function(){this._canvas.zoom("fit-viewport")},fo.prototype.zoom=function(e,t){var n=po(ho,20);this._totalDelta+=e,Math.abs(this._totalDelta)>.1&&(this._zoom(e,t,n),this._totalDelta=0)},fo.prototype._handleWheel=function(e){if(this._enabled){var t=this._container;e.preventDefault();var n,i=e.ctrlKey||/mac/i.test(navigator.platform)&&e.metaKey,r=e.shiftKey,o=-1*this._scale;if(o*=i?0===e.deltaMode?.02:.32:0===e.deltaMode?1:16,i){var a=t.getBoundingClientRect(),s={x:e.clientX-a.left,y:e.clientY-a.top};n=Math.sqrt(Math.pow(e.deltaY,2)+Math.pow(e.deltaX,2))*uo(e.deltaY)*o,this.zoom(n,s)}else n=r?{dx:o*e.deltaY,dy:0}:{dx:o*e.deltaX,dy:o*e.deltaY},this.scroll(n)}},fo.prototype.stepZoom=function(e,t){var n=po(ho,10);this._zoom(e,t,n)},fo.prototype._zoom=function(e,t,n){var i=this._canvas,r=e>0?1:-1,o=co(i.zoom()),a=Math.round(o/n)*n;a+=n*r;var s,l,c=Math.pow(10,a);i.zoom((s=ho,l=c,Math.max(s.min,Math.min(s.max,l))),t)},fo.prototype.toggle=function(e){var t=this._container,n=this._handleWheel,i=this._enabled;return void 0===e&&(e=!i),i!==e&&Fe[e?"bind":"unbind"](t,"wheel",n,!1),this._enabled=e,e},fo.prototype._init=function(e){this.toggle(e)};var mo={__init__:["zoomScroll"],zoomScroll:["type",fo]};function yo(e){Lr.call(this,e)}return e(yo,Lr),yo.prototype._navigationModules=[jr,io,lo,mo],yo.prototype._modules=[].concat(Lr.prototype._modules,yo.prototype._navigationModules),yo}); diff --git a/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/diagram-js.css b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/diagram-js.css new file mode 100644 index 00000000..6408e056 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/vendor/bpmn-js/diagram-js.css @@ -0,0 +1,1325 @@ +/** + * Semantic tokens, copied from `@bpmn-io/theme` to style the components + * according to this theme. + * + * Names and values of the tokens should stay intact with the `@bpmn-io/theme` + * source of truth, to ensure matching styles and allow overrides with an + * alternative theme. + */ +.bio-theme-parent { + --bio-surface: hsl(0, 0%, 100%); + --bio-surface-overlay: hsl(0, 0%, 100%); + --bio-surface-subtle: hsl(225, 10%, 97%); + --bio-surface-medium: hsl(225, 10%, 95%); + --bio-surface-inverted: hsl(0, 0%, 22%); + --bio-surface-inverted-subtle: hsl(225, 10%, 35%); + --bio-text: hsl(225, 10%, 15%); + --bio-text-subtle: hsl(225, 10%, 35%); + --bio-text-subtlest: hsl(225, 10%, 55%); + --bio-text-on-inverted: hsl(0, 0%, 100%); + --bio-border: hsl(225, 10%, 75%); + --bio-border-disabled: hsl(225, 10%, 90%); + --bio-primary: hsl(205, 100%, 40%); + --bio-primary-surface: hsl(205, 100%, 95%); + --bio-danger: hsl(360, 100%, 45%); + --bio-danger-surface-subtle: hsl(360, 100%, 97%); + --bio-selected-surface: var(--bio-surface-medium); + --bio-selected-text: var(--bio-text); + --bio-focus: hsl(205, 100%, 40%); + --bio-focus-surface: hsl(205, 100%, 95%); + --bio-canvas-accent: hsl(205, 100%, 40%); + --bio-canvas-accent-subtle: hsl(205, 100%, 75%); + --bio-shadow: hsla(0, 0%, 0%, 30%); + --bio-shadow-subtle: hsla(0, 0%, 0%, 10%); + --bio-radius-sm: 2px; + --bio-radius-md: 3px; + --bio-radius-lg: 4px; +} + +/** + * color definitions + * + * Colours resolve through the semantic tokens in `@bpmn-io/theme`; a handful of + * values with no matching token stay literal. + */ +.djs-parent { + + --button-border-radius: var(--bio-radius-md); + --input-border-radius: var(--bio-radius-sm); + --surface-border-radius: var(--bio-radius-lg); + + --canvas-fill-color: var(--bio-surface); + + --accent-color: var(--bio-canvas-accent); + + --bendpoint-fill-color: var(--bio-canvas-accent); + --bendpoint-stroke-color: var(--canvas-fill-color); + + --context-pad-entry-background-color: var(--bio-surface); + --context-pad-entry-hover-background-color: var(--bio-surface-medium); + + --element-dragger-color: var(--accent-color); + --element-selected-outline-stroke-color: var(--accent-color); + --element-selected-outline-secondary-stroke-color: var(--bio-canvas-accent-subtle); + + --lasso-fill-color: color-mix(in srgb, var(--bio-canvas-accent) 15%, transparent); + --lasso-stroke-color: var(--element-selected-outline-stroke-color); + + --palette-entry-color: var(--bio-text); + --palette-entry-hover-color: var(--bio-primary); + --palette-entry-selected-color: var(--bio-primary); + --palette-separator-color: var(--bio-border); + --palette-toggle-hover-background-color: var(--bio-surface-medium); + --palette-background-color: var(--bio-surface-subtle); + --palette-border-color: var(--bio-border); + + --popup-font-family: "IBM Plex Sans", sans-serif; + --popup-font-size: 14px; + --popup-secondary-font-size: .9em; + --popup-header-entry-selected-color: var(--bio-primary); + --popup-header-font-weight: bolder; + --popup-header-group-divider-color: var(--bio-border); + --popup-background-color: var(--bio-surface-overlay); + --popup-border-color: transparent; + --popup-shadow-color: var(--bio-shadow); + /* translucent, so the thumb darkens whatever it scrolls over */ + --popup-scrollbar-thumb-color: rgba(0, 0, 0, 0.2); + --popup-button-background-color: var(--bio-surface-overlay); + --popup-button-hover-background-color: var(--bio-surface-medium); + --popup-disabled-color: var(--bio-text-subtle); + --popup-description-color: var(--bio-text-subtle); + --popup-no-results-color: var(--bio-text-subtlest); + --popup-entry-title-color: var(--bio-text-subtlest); + + --popup-entry-selected-background-color: var(--bio-selected-surface); + --popup-entry-selected-color: var(--bio-selected-text); + --popup-search-border-color: var(--bio-border); + --popup-search-focus-border-color: var(--bio-focus); + --popup-search-focus-background-color: var(--bio-focus-surface); + --popup-tab-color: var(--bio-text-subtlest); + --popup-tab-hover-color: var(--bio-text); + --popup-tab-active-color: var(--bio-text); + --popup-tab-active-border-color: var(--bio-primary); + --popup-tab-strip-border-color: var(--bio-border-disabled); + --popup-tab-focus-border-color: var(--bio-focus); + + --resizer-fill-color: var(--bio-canvas-accent); + --resizer-stroke-color: var(--canvas-fill-color); + + --search-font-family: "IBM Plex Sans", sans-serif; + --search-font-size: 14px; + --search-container-background-color: var(--bio-surface-overlay); + --search-shadow-color: var(--bio-shadow); + --search-input-border-color: var(--bio-border); + --search-input-focus-border-color: var(--bio-focus); + --search-input-focus-background-color: var(--bio-primary-surface); + --search-result-hover-background-color: var(--bio-surface-medium); + --search-result-secondary-color: var(--bio-text-subtlest); + --search-preselected-background-color: color-mix(in srgb, var(--bio-canvas-accent) 15%, transparent); + + --shape-attach-allowed-stroke-color: var(--accent-color); + --shape-connect-allowed-fill-color: var(--bio-surface-subtle); + --shape-drop-allowed-fill-color: var(--bio-surface-subtle); + --shape-drop-not-allowed-fill-color: var(--bio-danger-surface-subtle); + --shape-resize-preview-stroke-color: var(--accent-color); + + --snap-line-stroke-color: color-mix(in srgb, var(--bio-canvas-accent) 30%, transparent); + + --space-tool-crosshair-stroke-color: var(--bio-text); + + --tooltip-error-background-color: var(--bio-danger-surface-subtle); + --tooltip-error-border-color: var(--bio-danger); + --tooltip-error-color: var(--bio-danger); + + --hover-tooltip-font-family: "IBM Plex Sans", sans-serif; + --hover-tooltip-font-size: 12px; + --hover-tooltip-secondary-font-size: 11px; + --hover-tooltip-background-color: var(--bio-surface-inverted); + --hover-tooltip-color: var(--bio-text-on-inverted); + --hover-tooltip-shadow-color: var(--bio-shadow-subtle); + + --palette-tooltip-shortcut-background-color: var(--bio-surface-inverted-subtle); +} + +/** + * SVG styles + */ + +.djs-container svg.drop-not-ok { + background: var(--shape-drop-not-allowed-fill-color) !important; +} + +.djs-container svg.new-parent { + background: var(--shape-drop-allowed-fill-color) !important; +} + +.djs-container svg:focus { + outline-offset: -1px; +} + +/** + * outline styles + */ + +.djs-outline, +.djs-selection-outline { + fill: none; + shape-rendering: geometricPrecision; + stroke-width: 2px; +} + +.djs-outline { + visibility: hidden; +} + +.djs-selection-outline { + stroke: var(--element-selected-outline-stroke-color); +} + +.djs-element.selected .djs-outline { + visibility: visible; + + stroke: var(--element-selected-outline-stroke-color); +} + +.djs-connection.selected .djs-outline { + display: none; +} + +.djs-multi-select .djs-element.selected .djs-outline, +.djs-dragging-active-lasso .djs-element.selected .djs-outline { + stroke: var(--element-selected-outline-secondary-stroke-color); + display: block; +} + +.djs-outline-hidden .djs-outline { + display: none !important; +} + +.djs-shape.connect-ok .djs-visual > :nth-child(1) { + fill: var(--shape-connect-allowed-fill-color) !important; +} + +.djs-shape.connect-not-ok .djs-visual > :nth-child(1), +.djs-shape.drop-not-ok .djs-visual > :nth-child(1) { + fill: var(--shape-drop-not-allowed-fill-color) !important; +} + +.djs-shape.new-parent .djs-visual > :nth-child(1) { + fill: var(--shape-drop-allowed-fill-color) !important; +} + + +/* Override move cursor during drop and connect */ +.drop-not-ok, +.connect-not-ok, +.drop-not-ok *, +.connect-not-ok * { + cursor: not-allowed !important; +} + +.drop-ok, +.connect-ok, +.drop-ok *, +.connect-ok * { + cursor: default !important; +} + +.djs-element.attach-ok .djs-visual > :nth-child(1) { + stroke-width: 5px !important; + stroke: var(--shape-attach-allowed-stroke-color) !important; +} + +.djs-frame.connect-not-ok .djs-visual > :nth-child(1), +.djs-frame.drop-not-ok .djs-visual > :nth-child(1) { + stroke-width: 3px !important; + stroke: var(--shape-drop-not-allowed-fill-color) !important; + fill: none !important; +} + +/** +* Selection box style +* +*/ +.djs-lasso-overlay { + fill: var(--lasso-fill-color); + stroke: var(--lasso-stroke-color); + stroke-width: 2px; + shape-rendering: geometricPrecision; + pointer-events: none; +} + +/** + * Resize styles + */ +.djs-resize-overlay { + fill: none; + + stroke-dasharray: 5 1 3 1; + stroke: var(--shape-resize-preview-stroke-color); + + pointer-events: none; +} + +.djs-resizer-hit { + fill: none; + pointer-events: all; +} + +.djs-resizer-visual { + fill: var(--resizer-fill-color); + stroke-width: 1px; + stroke: var(--resizer-stroke-color); + shape-rendering: geometricPrecision; +} + +.djs-resizer:hover .djs-resizer-visual { + stroke: var(--resizer-stroke-color); + stroke-opacity: 1; +} + +.djs-cursor-resize-ns, +.djs-resizer-n, +.djs-resizer-s { + cursor: ns-resize; +} + +.djs-cursor-resize-ew, +.djs-resizer-e, +.djs-resizer-w { + cursor: ew-resize; +} + +.djs-cursor-resize-nwse, +.djs-resizer-nw, +.djs-resizer-se { + cursor: nwse-resize; +} + +.djs-cursor-resize-nesw, +.djs-resizer-ne, +.djs-resizer-sw { + cursor: nesw-resize; +} + +.djs-shape.djs-resizing > .djs-outline { + visibility: hidden !important; +} + +.djs-shape.djs-resizing > .djs-resizer { + visibility: hidden; +} + +.djs-dragger > .djs-resizer { + visibility: hidden; +} + +/** + * drag styles + */ +.djs-dragger * { + fill: none !important; + stroke: var(--element-dragger-color) !important; +} + +.djs-dragger tspan, +.djs-dragger text { + fill: var(--element-dragger-color) !important; + stroke: none !important; +} + +.djs-dragger marker circle, +.djs-dragger marker path, +.djs-dragger marker polygon, +.djs-dragger marker polyline, +.djs-dragger marker rect { + fill: var(--element-dragger-color) !important; + stroke: none !important; +} + +.djs-dragger marker text, +.djs-dragger marker tspan { + fill: none !important; + stroke: var(--element-dragger-color) !important; +} + +.djs-dragging, +.djs-dragging > * { + opacity: 0.3 !important; + pointer-events: none !important; +} + +/** + * no pointer events for visual + */ +.djs-visual, +.djs-outline { + pointer-events: none; +} + +.djs-element.attach-ok .djs-hit { + stroke-width: 60px !important; +} + +/** + * all pointer events for hit shape + */ +.djs-element > .djs-hit-all, +.djs-element > .djs-hit-no-move { + pointer-events: all; +} + +.djs-element > .djs-hit-stroke, +.djs-element > .djs-hit-click-stroke { + pointer-events: stroke; +} + +/** + * shape / connection basic styles + */ +.djs-connection .djs-visual { + stroke-width: 2px; + fill: none; +} + +.djs-cursor-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +.djs-cursor-grabbing { + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; +} + +.djs-cursor-crosshair { + cursor: crosshair; +} + +.djs-cursor-move { + cursor: move; +} + +.djs-cursor-resize-ns { + cursor: ns-resize; +} + +.djs-cursor-resize-ew { + cursor: ew-resize; +} + + +/** + * snapping + */ +.djs-snap-line { + stroke: var(--snap-line-stroke-color); + stroke-linecap: round; + stroke-width: 2px; + pointer-events: none; +} + +/** + * snapping + */ +.djs-crosshair { + stroke: var(--space-tool-crosshair-stroke-color); + stroke-linecap: round; + stroke-width: 1px; + pointer-events: none; + shape-rendering: geometricPrecision; + stroke-dasharray: 5, 5; +} + +/** + * palette + */ + +.djs-palette { + position: absolute; + left: 20px; + top: 20px; + + box-sizing: border-box; + width: 48px; +} + +.djs-palette .separator { + margin: 5px; + padding-top: 5px; + + border: none; + border-bottom: solid 1px var(--palette-separator-color); + + clear: both; +} + +.djs-palette .entry:before { + vertical-align: initial; +} + +.djs-palette .djs-palette-toggle { + cursor: pointer; +} + +.djs-palette .entry, +.djs-palette .djs-palette-toggle { + color: var(--palette-entry-color); + font-size: 30px; + + text-align: center; +} + +.djs-palette .entry { + float: left; +} + +.djs-palette .entry img { + max-width: 100%; +} + +.djs-palette .djs-palette-entries:after { + content: ''; + display: table; + clear: both; +} + +.djs-palette .djs-palette-toggle:hover { + background: var(--palette-toggle-hover-background-color); +} + +.djs-palette .entry:hover { + color: var(--palette-entry-hover-color); +} + +.djs-palette .highlighted-entry { + color: var(--palette-entry-selected-color) !important; +} + +.djs-palette .entry, +.djs-palette .djs-palette-toggle { + width: 46px; + height: 46px; + line-height: 46px; + cursor: default; +} + +/** + * Palette open / two-column layout is controlled via + * classes on the palette. Events to hook into palette + * changed life-cycle are available in addition. + */ +.djs-palette.two-column.open { + width: 94px; +} + +.djs-palette:not(.open) .djs-palette-entries { + display: none; +} + +.djs-palette:not(.open) { + overflow: hidden; +} + +.djs-palette.open .djs-palette-toggle { + display: none; +} + +.djs-hover-tooltip { + position: fixed; + z-index: 1000; + + display: flex; + align-items: center; + gap: 6px; + + padding: 6px 10px; + border-radius: var(--surface-border-radius); + + background-color: var(--hover-tooltip-background-color); + color: var(--hover-tooltip-color); + + font-family: var(--hover-tooltip-font-family); + font-size: var(--hover-tooltip-font-size); + line-height: 1.2; + white-space: nowrap; + + pointer-events: none; + box-shadow: 0 1px 4px var(--hover-tooltip-shadow-color); +} + +.djs-hover-tooltip--right { + transform: translateY(-50%); +} + +.djs-hover-tooltip--top { + transform: translate(-50%, -100%); +} + +.djs-palette-tooltip-shortcut { + padding: 1px 5px; + + border-radius: var(--button-border-radius); + + background-color: var(--palette-tooltip-shortcut-background-color); + + font-family: monospace; + font-size: var(--hover-tooltip-secondary-font-size); + line-height: 1.2; +} + +/** + * context-pad + */ +.djs-context-pad { + position: absolute; + display: none; + pointer-events: none; + line-height: 1; + width: 72px; + z-index: 100; +} + +.djs-context-pad .entry { + width: 22px; + height: 22px; + text-align: center; + display: inline-block; + font-size: 22px; + margin: 0 2px 2px 0; + + border-radius: 3px; + + cursor: default; + + background-color: var(--context-pad-entry-background-color); + box-shadow: 0 0 2px 1px var(--context-pad-entry-background-color); + pointer-events: all; + vertical-align: middle; +} + +.djs-context-pad .entry:hover { + background: var(--context-pad-entry-hover-background-color); +} + +.djs-context-pad.open { + display: block; +} + +/** + * popup styles + */ +.djs-popup { + line-height: 1; + box-sizing: border-box; + background: var(--popup-background-color); + overflow: hidden; + position: fixed; + z-index: 200; + box-shadow: 0px 2px 6px var(--popup-shadow-color); + border: solid 1px var(--popup-border-color); + min-width: 120px; + max-width: 300px; + outline: none; + font-size: var(--popup-font-size); + font-family: var(--popup-font-family); +} + +.djs-popup-search input { + width: 100%; + box-sizing: border-box; + font-size: var(--popup-font-size); + padding: 3px 6px 3px 28px; + border-radius: var(--input-border-radius); + border: solid 1px var(--popup-search-border-color); + line-height: 21px; +} + +.djs-popup-search input:focus { + background-color: var(--popup-search-focus-background-color); + border: solid 1px var(--popup-search-focus-border-color); + outline: none; +} + +.djs-popup-header { + display: flex; + align-items: stretch; + line-height: 20px; + margin: 10px 12px 10px 12px; +} + +.djs-popup-header .entry { + border-radius: 2px; +} + +.djs-popup button.entry { + padding: 0; + background: transparent; + border: 0; +} + +.djs-popup-header .entry.active { + color: var(--popup-header-entry-selected-color); +} + +.djs-popup-header .entry.disabled { + color: inherit; +} + +.djs-popup-header-group { + display: flex; + flex-direction: row; + align-items: center; + list-style: none; + margin: 0; + padding: 0; +} + +.djs-popup-header-group .entry { + display: flex; + flex-direction: row; + align-items: center; +} + +.djs-popup-header-group + .djs-popup-header-group:before { + content: ''; + width: 1px; + height: 20px; + background: var(--popup-header-group-divider-color); + margin: 0 5px; +} + +.djs-popup-search { + position: relative; + width: auto; + margin: 10px 12px; +} + +.djs-popup-tabs-container { + position: relative; + border-bottom: solid 1px var(--popup-tab-strip-border-color); +} + +/** + * The scroll control doubles as the edge fade: tabs scrolled past it fade out + * rather than being cut off behind an opaque block. + */ +.djs-popup-tabs-scroll { + appearance: none; + position: absolute; + top: 0; + bottom: 1px; + width: 28px; + display: flex; + align-items: center; + padding: 0; + border: none; + border-radius: 0; + color: var(--popup-tab-color); + cursor: pointer; +} + +.djs-popup-tabs-scroll:hover { + color: var(--popup-tab-hover-color); +} + +.djs-popup-tabs-scroll-start { + left: 0; + justify-content: flex-start; + background: linear-gradient(to left, transparent, var(--popup-background-color) 60%); +} + +.djs-popup-tabs-scroll-end { + right: 0; + justify-content: flex-end; + background: linear-gradient(to right, transparent, var(--popup-background-color) 60%); +} + +.djs-popup-tabs-scroll-start svg { + transform: rotate(180deg); +} + +.djs-popup-tabs { + display: flex; + flex-wrap: nowrap; + gap: 2px; + overflow-x: auto; + overflow-y: hidden; + padding: 0 12px; + + /* keep scrolled-to tabs clear of the scroll controls */ + scroll-padding: 0 28px; + scrollbar-width: none; +} + +.djs-popup-tabs::-webkit-scrollbar { + display: none; +} + +.djs-popup-tab { + appearance: none; + background: transparent; + border: none; + border-bottom: solid 2px transparent; + border-radius: 0; + flex: 0 0 auto; + padding: 6px 10px; + font: inherit; + font-size: var(--popup-font-size); + color: var(--popup-tab-color); + cursor: pointer; + white-space: nowrap; +} + +.djs-popup-tab:hover { + color: var(--popup-tab-hover-color); +} + +.djs-popup-tab:focus-visible { + outline: solid 1px var(--popup-tab-focus-border-color); + outline-offset: -2px; +} + +.djs-popup-tab[aria-selected="true"] { + color: var(--popup-tab-active-color); + border-bottom-color: var(--popup-tab-active-border-color); + font-weight: var(--popup-header-font-weight); +} + +.djs-popup-title { + font-size: var(--popup-font-size); + font-weight: var(--popup-header-font-weight); + flex: 1; + margin: 0; +} + +.djs-popup-breadcrumbs { + display: flex; + line-height: 20px; + margin: 10px 12px; +} + +.djs-popup-breadcrumbs-item { + display: inline-flex; + min-width: 0; + padding: 0; + margin: 0; + background: transparent; + border: 0; + outline: none; + appearance: none; + color: var(--popup-description-color); + font: inherit; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.djs-popup-breadcrumbs-item--separator::before { + display: inline-block; + content: '/'; + color: var(--popup-description-color); + margin: 0 4px; +} + +.djs-popup-breadcrumbs-item:hover { + color: inherit; + text-decoration: underline; +} + +.djs-popup-breadcrumbs-item:focus-visible { + outline: 1px solid var(--popup-search-focus-border-color); + outline-offset: 2px; +} + +.djs-popup-breadcrumbs-item--back { + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 2px 3px; + margin-right: 6px; + border: 1px solid var(--popup-search-border-color); + border-radius: 4px; +} + +.djs-popup-breadcrumbs-item--back:hover { + background: var(--popup-button-hover-background-color); +} + +.djs-popup-breadcrumbs-item--current { + font-weight: var(--popup-header-font-weight); + color: inherit; + cursor: default; +} + +.djs-popup-breadcrumbs-item--current:hover { + text-decoration: none; +} + +.djs-popup-entry-actions { + display: flex; + align-items: center; + flex-shrink: 0; + gap: 6px; + margin-left: 6px; +} + +.djs-popup-entry-chevron { + display: flex; + align-items: center; + color: var(--popup-description-color); +} + +.djs-popup-search-icon { + position: absolute; + left: 8px; + top: 7px; +} + +.djs-popup-search-count { + margin: 0 12px; + font-size: var(--popup-secondary-font-size); + color: var(--popup-description-color); +} + +.djs-popup-results { + margin: 7px 3px 7px 12px; + list-style: none; + max-height: 280px; + overflow: auto; + padding-right: 9px; +} + +/** + * The results list is focusable (as a listbox holding + * `aria-activedescendant`) when there is no search field. + * The active entry is indicated via `.entry.selected`, so we hide + * the outline on the list container itself. + */ +.djs-popup-results:focus { + outline: none; +} + +.djs-popup-group { + margin: 0; + padding: 0; + width: 100%; +} + +.djs-popup-body .entry, +.djs-popup-body .entry-header { + padding: 5px 7px; + cursor: default; + border-radius: 4px; +} + +.djs-popup-body .entry-header { + font-weight: var(--popup-header-font-weight); + color: var(--popup-entry-title-color); + padding-left: 0; +} + +.djs-popup [class*="icon"] .djs-popup-label, +.djs-popup-label:not(:first-child) { + margin-left: .5em; +} + +.djs-popup [class*="icon"]:before, +.djs-popup-entry-icon { + width: 1em; + height: 1em; + display: inline-block; + font-size: 1.4em; + vertical-align: middle; +} + +.djs-popup-body .entry-header:not(:first-child) { + margin-top: 8px; + margin-bottom: 2px; +} + +.djs-popup-body .entry { + display: flex; + flex-direction: row; + align-items: stretch; + height: min-content; +} + +.djs-popup .entry.selected { + color: var(--popup-entry-selected-color); + background-color: var(--popup-entry-selected-background-color); +} + +.djs-popup .entry.disabled { + color: var(--popup-disabled-color); + cursor: not-allowed; +} + +.djs-popup-body .entry:not(:first-child) { + margin-top: 2px; +} + +.djs-popup-entry-content { + display: flex; + flex-direction: column; + flex: 1; +} + +.djs-popup-entry-description { + font-size: var(--popup-secondary-font-size); + margin-top: .25em; + + color: var(--popup-description-color); +} + +.djs-popup-label, +.djs-popup-entry-description { + line-height: 1.4em; +} + +.djs-popup .entry, +.djs-popup .entry-header { + margin: 1px; +} + +.djs-popup-body { + flex-direction: column; + width: auto; +} + +.djs-popup *::-webkit-scrollbar { + width: 6px; +} + +.djs-popup *::-webkit-scrollbar-thumb { + border-radius: 3px; + background-color: var(--popup-scrollbar-thumb-color); +} + +.djs-popup *::-webkit-scrollbar-track { + box-shadow: none; + background: transparent; + margin: 0; + padding: 5px; +} + +.djs-popup-no-results { + padding: 0 12px 12px 12px; + color: var(--popup-no-results-color); +} + +.djs-popup-footer { + display: flex; + padding: 5px 12px 7px 12px; + border-top: solid 1px var(--popup-border-color); +} + +/** + * The footer holds the keyboard/assistive-technology reachable documentation + * link. Pointer interaction switches the popup to the per-entry + * `.djs-popup-entry-docs` affordance instead. + */ +.djs-popup--pointer .djs-popup-footer { + display: none; +} + +.djs-popup-entry-docs { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + visibility: hidden; + padding: 2px 3px; + color: var(--popup-description-color); + background: var(--popup-button-background-color); + border: 1px solid var(--popup-search-border-color); + border-radius: 4px; + cursor: pointer; + transition: background-color .12s ease, border-color .12s ease, color .12s ease; +} + +.djs-popup--pointer .djs-popup-body .entry:hover .djs-popup-entry-docs { + visibility: visible; +} + +.djs-popup-body .entry .djs-popup-entry-docs:hover { + background: var(--popup-button-hover-background-color); +} + +.djs-popup-entry-docs svg { + vertical-align: middle; +} + +.djs-popup-footer-docs { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: var(--popup-secondary-font-size); + color: var(--popup-description-color); + text-decoration: none; +} + +.djs-popup-footer-docs:hover { + text-decoration: underline; +} + +.djs-popup-footer-docs svg { + vertical-align: middle; + margin: 2px; +} + +/** + * palette styles + */ +.djs-palette { + background: var(--palette-background-color); + border: solid 1px var(--palette-border-color); + border-radius: var(--surface-border-radius); +} + +/** + * bendpoints + */ +.djs-segment-dragger, +.djs-bendpoint { + display: none; +} + +.djs-segment-dragger .djs-visual { + display: none; + + fill: var(--bendpoint-fill-color); + stroke: var(--bendpoint-stroke-color); + stroke-width: 1px; + stroke-opacity: 1; +} + +.djs-segment-dragger:hover .djs-visual { + display: block; +} + +.djs-bendpoint .djs-visual { + fill: var(--bendpoint-fill-color); + stroke: var(--bendpoint-stroke-color); + stroke-width: 1px; +} + +.djs-segment-dragger:hover, +.djs-bendpoints.hover .djs-segment-dragger, +.djs-bendpoints.selected .djs-segment-dragger, +.djs-bendpoint:hover, +.djs-bendpoints.hover .djs-bendpoint, +.djs-bendpoints.selected .djs-bendpoint { + display: block; +} + +.djs-drag-active .djs-bendpoints * { + display: none; +} + +.djs-bendpoints:not(.hover) .floating { + display: none; +} + +.djs-bendpoint.floating:not(.positioned) { + display: none; +} + +.djs-segment-dragger:hover .djs-visual, +.djs-segment-dragger.djs-dragging .djs-visual, +.djs-bendpoint:hover .djs-visual, +.djs-bendpoint.floating .djs-visual { + fill: var(--bendpoint-fill-color); + stroke: var(--bendpoint-stroke-color); + stroke-opacity: 1; +} + +.djs-bendpoint.floating .djs-hit { + pointer-events: none; +} + +.djs-segment-dragger .djs-hit, +.djs-bendpoint .djs-hit { + fill: none; + pointer-events: all; +} + +.djs-segment-dragger.horizontal .djs-hit { + cursor: ns-resize; +} + +.djs-segment-dragger.vertical .djs-hit { + cursor: ew-resize; +} + +.djs-segment-dragger.djs-dragging .djs-hit { + pointer-events: none; +} + +.djs-updating, +.djs-updating > * { + pointer-events: none !important; +} + +.djs-updating .djs-context-pad, +.djs-updating .djs-outline, +.djs-updating .djs-bendpoint, +.djs-multi-select .djs-bendpoint, +.djs-multi-select .djs-segment-dragger, +.connect-ok .djs-bendpoint, +.connect-not-ok .djs-bendpoint, +.drop-ok .djs-bendpoint, +.drop-not-ok .djs-bendpoint { + display: none !important; +} + +.djs-segment-dragger.djs-dragging, +.djs-bendpoint.djs-dragging { + display: block; + opacity: 1.0; +} + + +/** + * tooltips + */ +.djs-tooltip-error { + width: 160px; + padding: 6px; + + background: var(--tooltip-error-background-color); + border: solid 1px var(--tooltip-error-border-color); + border-radius: var(--surface-border-radius); + color: var(--tooltip-error-color); + font-size: 12px; + line-height: 16px; + + opacity: 0.75; +} + +.djs-tooltip-error:hover { + opacity: 1; +} + + +/** + * search pad + */ +.djs-search-open .djs-context-pad { + display: none; +} + +.djs-search-open .djs-connection.selected .djs-outline { + display: block; +} + +.djs-search-container { + position: absolute; + top: 20px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + + width: 25%; + min-width: 300px; + max-width: 400px; + z-index: 10; + + font-family: var(--search-font-family); + font-size: var(--search-font-size); + border-radius: var(--surface-border-radius); + box-shadow: 0px 2px 6px var(--search-shadow-color); +} + +.djs-search-container:not(.open) { + display: none; +} + +.djs-search-input { + position: relative; +} + +.djs-search-input svg { + position: absolute; + left: 8px; + top: 7px; +} + +.djs-search-input input { + font-size: var(--search-font-size); + width: 100%; + padding: 3px 6px 3px 28px; + border: 1px solid var(--search-input-border-color); + border-radius: var(--input-border-radius); + box-sizing: border-box; + line-height: 21px; +} + +.djs-search-input input:focus { + background-color: var(--search-input-focus-background-color); + border: solid 1px var(--search-input-focus-border-color); + outline: none; +} + +.djs-search-results { + position: relative; + overflow-y: auto; + max-height: 200px; + background: var(--search-container-background-color); +} + +.djs-search-result { + padding: 6px 8px; +} + +.djs-search-result-primary { + margin: 0 0 3px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.djs-search-result-secondary { + font-family: monospace; + margin: 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + color: var(--search-result-secondary-color); +} + +.djs-search-result:hover { + background: var(--search-result-hover-background-color); +} + +.djs-search-result-selected { + background: var(--search-result-hover-background-color); +} + +.djs-search-result-selected:hover { + background: var(--search-result-hover-background-color); +} + +.djs-search-open .djs-element .djs-outline { + fill: var(--search-preselected-background-color) !important; +} + +/** + * hidden styles + */ +.djs-element-hidden, +.djs-element-hidden .djs-hit, +.djs-element-hidden .djs-outline, +.djs-label-hidden .djs-label { + display: none !important; +} + +.djs-element .djs-hit-stroke, +.djs-element .djs-hit-click-stroke, +.djs-element .djs-hit-all { + cursor: move; +} \ No newline at end of file diff --git a/order-fulfillment-workflow/src/main/resources/static/workflow-bpmn.mjs b/order-fulfillment-workflow/src/main/resources/static/workflow-bpmn.mjs new file mode 100644 index 00000000..b58f20fa --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/workflow-bpmn.mjs @@ -0,0 +1,160 @@ +import { terminal, friendlyStatus, duration, inspectTrace } from "./workflow-trace.mjs"; + +// This model is authored alongside the Java definition, not inferred from a single run. +export const BPMN_STEPS = { + awaitPayment: "WAIT_FOR_EVENT", + reserveStock: "ACTION", + initiatePayment: "ACTION", + shipOrder: "ACTION", + notifyCustomer: "ACTION", + recordOutOfStock: "PUBLISH", + recordPaymentTimeout: "PUBLISH", + recordPaymentFailure: "PUBLISH", +}; + +export function bpmnStates(trace) { + const idle = terminal(trace.status) ? "SKIPPED" : "PENDING"; + const step = (name) => trace.rows.find((row) => row.name === name); + const seen = (name) => Boolean(step(name)?.events.length); + const done = (name) => step(name)?.status === "COMPLETED"; + const reserved = inspectTrace(trace, "reserveStock")?.output?.reserved; + const waitStatus = step("awaitPayment")?.status; + const firstPaymentFailure = trace.events.find( + (event) => + ["awaitPayment", "initiatePayment"].includes(event.step) && + ["FAILED", "TIMED_OUT", "CANCELLED"].includes(event.status), + )?.step; + const started = trace.events.some((event) => !event.step && event.status === "STARTED"); + const states = Object.fromEntries(Object.keys(BPMN_STEPS).map((name) => [name, step(name)?.status || idle])); + // A row that was only included to keep replay layout stable has not executed. + if (terminal(trace.status)) for (const name of Object.keys(BPMN_STEPS)) if (!seen(name)) states[name] = "SKIPPED"; + Object.assign(states, { + OrderStarted: started ? "COMPLETED" : "PENDING", + ParallelStart: seen("awaitPayment") || seen("reserveStock") ? "COMPLETED" : idle, + StockAvailable: done("reserveStock") ? "COMPLETED" : idle, + PaymentJoined: + done("awaitPayment") && done("initiatePayment") + ? "COMPLETED" + : !terminal(trace.status) && (done("awaitPayment") || done("initiatePayment")) + ? "STARTED" + : idle, + PaymentDeadline: waitStatus === "TIMED_OUT" ? "TIMED_OUT" : idle, + PaymentError: + firstPaymentFailure === "awaitPayment" && + (waitStatus === "FAILED" || waitStatus === "CANCELLED") && + reserved !== false + ? "FAILED" + : idle, + PaymentInitiationError: firstPaymentFailure === "initiatePayment" ? "FAILED" : idle, + OrderCompleted: trace.status === "COMPLETED" ? "COMPLETED" : idle, + StockFailed: trace.status === "FAILED" && done("recordOutOfStock") ? "FAILED" : idle, + PaymentTimedOut: trace.status === "FAILED" && done("recordPaymentTimeout") ? "FAILED" : idle, + PaymentFailed: trace.status === "FAILED" && done("recordPaymentFailure") ? "FAILED" : idle, + }); + const flows = { + f_start: started, + f_wait: seen("awaitPayment"), + f_stock: seen("reserveStock"), + f_stock_result: done("reserveStock"), + f_stock_yes: reserved === true, + f_stock_no: reserved === false, + f_initiated: done("initiatePayment"), + f_paid: done("awaitPayment"), + f_ship: seen("shipOrder"), + f_notify: seen("notifyCustomer"), + f_done: trace.status === "COMPLETED", + f_stock_failed: done("recordOutOfStock"), + f_timeout: waitStatus === "TIMED_OUT", + f_timeout_end: done("recordPaymentTimeout"), + f_error: states.PaymentError === "FAILED", + f_initiation_error: states.PaymentInitiationError === "FAILED", + f_error_end: done("recordPaymentFailure"), + }; + for (const [id, traversed] of Object.entries(flows)) states[id] = traversed ? "COMPLETED" : idle; + return states; +} + +export class BpmnDiagram { + constructor(onSelect) { + this.onSelect = onSelect; + this.markers = new Map(); + this.chips = new Map(); + } + async show(trace, selectedStep) { + this.trace = trace; + this.selectedStep = selectedStep; + this.loading ||= this.initialize(); + await this.loading; + this.update(this.trace, this.selectedStep); + this.fit(); + } + async initialize() { + this.viewer = new window.BpmnJS({ + container: "#bpmn-canvas", + bpmnRenderer: { defaultFillColor: "#ffffff", defaultStrokeColor: "#b0b8c6", defaultLabelColor: "#596577" }, + }); + const response = await fetch("/order-fulfillment.bpmn"); + if (!response.ok) throw new Error(`BPMN model unavailable (${response.status})`); + const result = await this.viewer.importXML(await response.text()); + if (result.warnings.length) console.warn("BPMN import warnings", result.warnings); + this.canvas = this.viewer.get("canvas"); + this.registry = this.viewer.get("elementRegistry"); + this.viewer.on("element.click", ({ element }) => { + const id = element.labelTarget?.id || element.id; + if (BPMN_STEPS[id]) this.onSelect(id); + else if (id === "OrderStarted" || id === "OrderCompleted") this.onSelect("@root"); + }); + const overlays = this.viewer.get("overlays"); + for (const name of Object.keys(BPMN_STEPS)) { + const shape = this.registry.get(name); + const chip = document.createElement("button"); + chip.className = "bpmn-state-chip"; + chip.style.width = `${shape.width - 12}px`; + chip.onclick = () => this.onSelect(name); + overlays.add(name, { position: { top: shape.height - 23, left: 6 }, html: chip, scale: true }); + this.chips.set(name, chip); + } + document.getElementById("bpmn-status").hidden = true; + document.getElementById("bpmn-canvas").dataset.loaded = "true"; + this.ready = true; + } + update(trace, selectedStep) { + this.trace = trace; + this.selectedStep = selectedStep; + if (!this.ready) return; + for (const [id, status] of Object.entries(bpmnStates(trace))) { + const marker = `state-${status.toLowerCase()}`; + if (this.markers.get(id) !== marker) { + if (this.markers.has(id)) this.canvas.removeMarker(id, this.markers.get(id)); + this.canvas.addMarker(id, marker); + this.markers.set(id, marker); + } + const chip = this.chips.get(id); + if (chip) { + const row = trace.rows.find((row) => row.name === id); + const label = + status === "SKIPPED" + ? "Not taken" + : status === "STARTED" && id === "awaitPayment" + ? "Waiting" + : friendlyStatus(status); + chip.textContent = `${label}${row?.events.length && row.spans.some((s) => !s.instant) ? " · " + duration(row.duration) : ""}`; + chip.className = `bpmn-state-chip ${marker}`; + chip.setAttribute("aria-label", `Inspect BPMN ${id}: ${label}`); + chip.setAttribute("aria-pressed", String(id === selectedStep)); + if (id === selectedStep) this.canvas.addMarker(id, "bpmn-selected"); + else this.canvas.removeMarker(id, "bpmn-selected"); + } + } + } + fit() { + if (this.ready && !document.getElementById("bpmn-view").hidden) { + this.canvas.resized(); + this.canvas.zoom("fit-viewport", "auto"); + this.canvas.zoom(this.canvas.zoom() * 0.94); + } + } + zoom(factor) { + if (this.ready) this.canvas.zoom(Math.max(0.25, Math.min(3, this.canvas.zoom() * factor))); + } +} diff --git a/order-fulfillment-workflow/src/main/resources/static/workflow-trace.mjs b/order-fulfillment-workflow/src/main/resources/static/workflow-trace.mjs new file mode 100644 index 00000000..4c240b0a --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/workflow-trace.mjs @@ -0,0 +1,165 @@ +export const terminal = (status) => ["COMPLETED", "FAILED", "TIMED_OUT", "CANCELLED"].includes(status); +export const friendlyStatus = (status) => + ({ + STARTED: "Running", + COMPLETED: "Completed", + FAILED: "Failed", + TIMED_OUT: "Timed out", + CANCELLED: "Cancelled", + RETRYING: "Retrying", + RETRY_STARTED: "Retrying", + PENDING: "Pending", + SKIPPED: "Not taken", + })[status] || status; +export const scenarioName = (value) => + ({ + happy: "Automatic payment", + "manual-payment": "Interactive payment", + "out-of-stock": "Out of stock", + "payment-timeout": "Payment timeout", + })[value] || + value || + "Standard run"; +export function duration(ms) { + if (!Number.isFinite(ms)) return "—"; + ms = Math.max(0, ms); + if (ms < 1000) return `${Math.round(ms)} ms`; + if (ms < 60000) return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)} s`; + return `${Math.floor(ms / 60000)}m ${Math.floor(ms / 1000) % 60}s`; +} +export function unionDuration(intervals) { + const sorted = intervals.map(([a, b]) => [a, Math.max(a, b)]).sort((a, b) => a[0] - b[0]); + let sum = 0, + left, + right; + for (const [start, end] of sorted) { + if (left === undefined) { + left = start; + right = end; + } else if (start > right) { + sum += right - left; + left = start; + right = end; + } else right = Math.max(right, end); + } + return sum + (left === undefined ? 0 : right - left); +} +/** All timing comes from the journal. Missing start events remain point markers. */ +export function buildTrace(workflow, journal, now, cutoff = Infinity, throughSequence = Infinity) { + const ordered = [...journal].sort((a, b) => a.sequence - b.sequence); + const replay = cutoff < Infinity || throughSequence < Infinity; + const events = ordered.filter((e) => Date.parse(e.timestamp) <= cutoff && e.sequence <= throughSequence); + const lifecycle = events.filter((e) => !e.step && e.metadata?.workflowStatus); + const start = Date.parse(lifecycle.find((e) => e.status === "STARTED")?.timestamp || workflow.startedAt); + const closing = lifecycle.findLast((e) => terminal(e.status)); + const status = lifecycle.at(-1)?.status || (replay ? "PENDING" : workflow.status); + const ended = closing ? Date.parse(closing.timestamp) : null; + const end = Math.max(start, ended ?? Math.min(now, cutoff)); + const grouped = new Map(); + // Preserve the recorded step order while rewinding. Only identity and primitive + // are known before a step starts; no future events, results, or timing leak in. + if (replay) for (const event of ordered) if (event.step && !grouped.has(event.step)) grouped.set(event.step, []); + for (const event of events) { + if (!event.step) continue; + if (!grouped.has(event.step)) grouped.set(event.step, []); + grouped.get(event.step).push(event); + } + const rows = []; + for (const [name, history] of grouped) { + const kind = + ordered.find((e) => e.step === name && e.metadata?.stepPrimitive)?.metadata.stepPrimitive || "ACTION"; + const spans = []; + let open; + for (const event of history) { + const t = Date.parse(event.timestamp); + if (event.status === "STARTED" || event.status === "RETRY_STARTED") { + if (open) { + open.end = t; + open.active = false; + } + open = { start: t, end: null, status: event.status, active: true, events: [event], instant: false }; + spans.push(open); + } else if (terminal(event.status) || event.status === "RETRYING") { + if (open) { + open.end = Math.max(open.start, t); + open.status = event.status; + open.active = false; + open.events.push(event); + open = null; + } else + spans.push({ + start: t, + end: t, + status: event.status, + active: false, + events: [event], + instant: true, + }); + } else if (open) open.events.push(event); + else spans.push({ start: t, end: t, status: event.status, active: false, events: [event], instant: true }); + } + for (const span of spans) { + span.end ??= Math.max(span.start, end); + span.active &&= !terminal(status); + } + rows.push({ + name, + kind, + events: history, + spans, + status: history.at(-1)?.status || "PENDING", + start: spans[0]?.start, + end: spans.at(-1)?.end, + duration: spans.reduce((sum, span) => sum + (span.instant ? 0 : span.end - span.start), 0), + }); + } + const waitDuration = unionDuration( + rows + .filter((row) => row.kind === "WAIT_FOR_EVENT") + .flatMap((row) => row.spans.filter((span) => !span.instant).map((span) => [span.start, span.end])), + ); + return { + start, + end, + status, + ended, + rows, + events, + duration: end - start, + waitDuration, + input: lifecycle.find((e) => e.status === "STARTED")?.payload, + }; +} + +/** Position is an event cursor, not wall-clock speed. Fractions animate between events. */ +export function replayTrace(workflow, journal, position) { + const ordered = [...journal].sort((a, b) => a.sequence - b.sequence); + const bounded = Math.max(0, Math.min(ordered.length, position)); + const count = Math.floor(bounded); + const prefix = ordered.slice(0, count); + const lastTime = Math.max(Date.parse(workflow.startedAt), ...prefix.map((e) => Date.parse(e.timestamp))); + const nextTime = Math.max(lastTime, Date.parse(ordered[count]?.timestamp || workflow.startedAt)); + const clock = count === 0 ? lastTime : lastTime + (nextTime - lastTime) * (bounded - count); + return buildTrace(workflow, ordered, clock, clock, prefix.at(-1)?.sequence ?? -Infinity); +} + +/** The inspector consumes the same as-of state as the waterfall and journal. */ +export function inspectTrace(trace, name) { + if (name === "@root") + return { + status: trace.status, + input: trace.input, + duration: trace.duration, + events: trace.events.filter((e) => !e.step), + output: Object.fromEntries( + trace.rows.filter((r) => r.status === "COMPLETED").map((r) => [r.name, r.events.at(-1).payload]), + ), + }; + const row = trace.rows.find((r) => r.name === name); + if (!row) return undefined; + return { + ...row, + input: row.events.findLast((e) => e.status === "STARTED" || e.status === "RETRY_STARTED")?.payload, + output: terminal(row.status) ? row.events.at(-1)?.payload : undefined, + }; +} diff --git a/order-fulfillment-workflow/src/main/resources/static/workflows.css b/order-fulfillment-workflow/src/main/resources/static/workflows.css new file mode 100644 index 00000000..00a967f9 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/workflows.css @@ -0,0 +1,2090 @@ +@import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;450;500;550;600;650;700&family=IBM+Plex+Mono:wght@400;450;500&display=swap"); +:root { + --ink: #20252e; + --muted: #818895; + --line: #e7e9ee; + --orange: #ed633c; + --green: #27947a; + --purple: #8a6ad4; + --red: #d66370; + --label: 206px; + font-family: + "DM Sans", + -apple-system, + BlinkMacSystemFont, + sans-serif; + color: var(--ink); + font-size: 13px; + background: #f8f9fb; + font-synthesis: none; +} +* { + box-sizing: border-box; +} +body { + margin: 0; + display: grid; + grid-template-columns: 60px 264px minmax(0, 1fr); + min-height: 100vh; +} +button, +input, +select { + font: inherit; +} +button, +a, +input, +select { + -webkit-tap-highlight-color: transparent; +} +button, +select { + cursor: pointer; +} +button { + color: inherit; + background: white; + border: 1px solid var(--line); + border-radius: 6px; + font-weight: 550; +} +button:hover { + background: #f4f5f8; +} +button:disabled { + opacity: 0.45; + cursor: default; +} +button:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible { + outline: 2px solid var(--orange); + outline-offset: 3px; +} +a { + color: inherit; + text-decoration: none; +} +svg { + width: 18px; + height: 18px; + fill: none; + stroke: currentColor; + stroke-width: 1.6; + stroke-linecap: round; + stroke-linejoin: round; + flex-shrink: 0; +} +.svg-library { + position: absolute; + width: 0; + height: 0; + overflow: hidden; +} +[hidden] { + display: none !important; +} +.mono, +pre, +.execution-id, +.bar-duration, +.axis-tick, +.row-duration { + font-family: "IBM Plex Mono", ui-monospace, monospace; +} +.rail { + background: #20232b; + display: flex; + align-items: center; + flex-direction: column; + gap: 13px; + position: sticky; + top: 0; + height: 100vh; + padding: 17px 0; + color: #858996; +} +.brand { + font-size: 37px; + font-weight: 700; + line-height: 1; + color: #fff; + letter-spacing: -5px; + margin-bottom: 31px; + padding-right: 6px; +} +.brand span { + color: var(--orange); + font-size: 29px; +} +.rail-link { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 9px; +} +.rail-link:hover { + background: #303440; + color: #fff; +} +.rail-link.active { + background: #41404a; + color: #ff9672; + box-shadow: inset 0 0 0 1px #63514e; +} +.rail-bottom { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + font-size: 8px; + letter-spacing: 1.3px; + padding-bottom: 3px; +} +.env-dot { + width: 7px; + height: 7px; + background: #5dbe98; + border-radius: 50%; + box-shadow: 0 0 0 4px #2a3b38; +} +.runs-panel { + border-right: 1px solid var(--line); + background: #f1f3f6; + display: flex; + flex-direction: column; + position: sticky; + top: 0; + height: 100vh; + overflow: hidden; +} +.workspace { + display: flex; + align-items: center; + gap: 10px; + min-height: 75px; + padding: 18px; + border-bottom: 1px solid #e2e5eb; +} +.workspace-logo { + width: 31px; + height: 31px; + display: grid; + place-items: center; + background: #fff; + border: 1px solid #dedfe6; + border-radius: 8px; + font-weight: 700; +} +.workspace strong { + font-size: 12px; + font-weight: 650; +} +.workspace small { + display: block; + color: var(--muted); + font-size: 10px; + margin-top: 4px; +} +.workspace-chevron { + margin-left: auto; + color: #a0a5af; +} +.runs-heading { + display: flex; + align-items: center; + justify-content: space-between; + padding: 25px 20px 17px; +} +h1 { + font-size: 15px; + margin: 0; + font-weight: 650; +} +.counter { + font-size: 10px; + background: #e3e6ed; + color: #747c8b; + min-width: 23px; + padding: 3px 7px; + border-radius: 5px; + text-align: center; +} +.search-box { + display: flex; + align-items: center; + gap: 8px; + border: 1px solid #dde1e8; + border-radius: 6px; + background: white; + margin: 0 17px; + padding: 9px 10px; + color: #9ba1ac; +} +.search-box svg { + width: 15px; + height: 15px; +} +.search-box input { + border: 0; + background: transparent; + min-width: 0; + width: 100%; + font-size: 11px; + outline: none; +} +.filter-row { + display: flex; + align-items: center; + justify-content: space-between; + margin: 17px 20px; + font-size: 10px; + color: #878e9c; +} +.filter-row select { + border: 0; + background: none; + color: #646c7b; + font-size: 11px; + max-width: 145px; +} +.run-list { + overflow: auto; + flex: 1; + padding: 0 10px 12px; +} +.run-row { + display: block; + text-align: left; + width: 100%; + padding: 14px 12px; + background: transparent; + border: 1px solid transparent; + border-radius: 8px; + margin-bottom: 5px; + transition: background 0.15s; +} +.run-row:hover { + background: #e9ecf2; +} +.run-row.selected { + background: #fff; + border-color: #e0e3e9; + box-shadow: 0 2px 6px #1a233308; + position: relative; +} +.run-row.selected:before { + content: ""; + position: absolute; + width: 3px; + background: var(--orange); + left: -1px; + top: 18px; + bottom: 18px; + border-radius: 3px; +} +.run-top { + display: flex; + align-items: center; + gap: 7px; +} +.run-top strong { + font-size: 11px; + font-weight: 600; +} +.run-top time { + font-size: 9px; + color: var(--muted); + margin-left: auto; + font-weight: 400; +} +.run-id { + display: block; + font: + 10px "IBM Plex Mono", + monospace; + color: #89909e; + margin: 8px 0 10px 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.run-bottom { + display: flex; + align-items: center; + justify-content: space-between; + margin-left: 14px; + font-size: 10px; + color: #89909e; +} +.scenario-label { + color: #707888; +} +.pagination { + border-top: 1px solid var(--line); + padding: 10px 17px; + display: flex; + align-items: center; + gap: 5px; + font-size: 10px; + color: var(--muted); +} +.pagination span { + flex: 1; +} +.pagination button { + width: 25px; + height: 25px; + font-size: 17px; +} +.storage-note { + border-top: 1px solid #e1e4eb; + padding: 17px; + display: flex; + align-items: center; + gap: 9px; +} +.storage-note svg { + width: 16px; + height: 16px; + color: #8a92a0; +} +.storage-note strong { + font-size: 10px; + font-weight: 550; + display: block; +} +.storage-note small { + font-size: 9px; + color: #9198a4; + display: block; + margin-top: 4px; +} +.storage-note .status-dot { + margin-left: auto; +} +main { + min-width: 0; + padding: 0 32px 20px; +} +.topbar { + height: 75px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--line); + gap: 12px; + font-size: 11px; + color: #858c98; +} +.breadcrumbs { + display: flex; + gap: 12px; + align-items: center; +} +.breadcrumbs strong { + color: #505967; + font-weight: 500; +} +.breadcrumbs span { + color: #c4c9d1; +} +.top-actions { + display: flex; + gap: 20px; + align-items: center; +} +.top-actions a:hover { + color: var(--orange); +} +.environment { + font-size: 9px; + padding: 4px 8px; + border: 1px solid #e1e5eb; + border-radius: 5px; + color: #828a98; + background: #fff; +} +.page-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; + padding: 32px 0 29px; +} +.eyebrow { + font-size: 9px; + color: #949aa5; + font-weight: 600; + letter-spacing: 1.8px; + margin-bottom: 9px; +} +h2 { + font-size: 25px; + letter-spacing: -0.9px; + font-weight: 550; + margin: 0; +} +.run-controls { + display: flex; + align-items: center; + gap: 9px; +} +.run-controls select { + padding: 9px 7px; + font-size: 11px; + border: 1px solid #e1e4ea; + border-radius: 6px; + background: #fff; + color: #646d7c; + max-width: 156px; +} +.primary { + background: var(--orange); + color: #fff; + border: 1px solid var(--orange); + padding: 10px 13px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 11px; + white-space: nowrap; + box-shadow: 0 2px 4px #ba3c2014; +} +.primary:hover { + background: #d9502b; +} +.primary svg { + width: 12px; + height: 12px; + fill: currentColor; + stroke-width: 1; +} +.execution { + background: #fff; + border: 1px solid #e0e4eb; + border-radius: 10px; + box-shadow: 0 4px 25px #293b5a04; + overflow: hidden; +} +.execution-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 15px; + padding: 24px 25px 22px; +} +.execution-name { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; +} +h3 { + font-size: 18px; + letter-spacing: -0.35px; + margin: 0; + font-weight: 600; +} +.execution-id { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + color: #949baa; + font-size: 10px; + margin-top: 10px; +} +.text-button { + background: none; + border: 0; + padding: 0; + color: #808896; + font-family: "DM Sans", sans-serif; + font-size: 10px; +} +.text-button:hover { + color: var(--orange); + background: none; +} +.version { + font-size: 9px; + padding: 2px 5px; + border: 1px solid var(--line); + border-radius: 4px; + font-family: "DM Sans", sans-serif; + color: #8d95a2; +} +.badge { + display: inline-flex; + align-items: center; + gap: 5px; + border-radius: 5px; + padding: 4px 7px; + font-size: 9px; + font-weight: 550; + white-space: nowrap; + border: 1px solid #e0e6e4; + color: #4f8b78; + background: #edf7f2; +} +.badge:before { + content: ""; + height: 5px; + width: 5px; + border-radius: 50%; + background: currentColor; +} +.badge.STARTED, +.badge.RETRY_STARTED { + background: #f2edfd; + border-color: #e9defd; + color: #8a69cb; +} +.badge.FAILED, +.badge.TIMED_OUT { + background: #fff0f1; + border-color: #f8dcdf; + color: #c3606e; +} +.badge.CANCELLED { + color: #9299a5; + background: #f3f4f6; + border-color: #e8eaee; +} +.badge.RETRYING { + color: #b38b47; + background: #fff8e8; + border-color: #f4e7c6; +} +.live-button { + background: #f6faf8; + border: 1px solid #e2eee7; + padding: 6px 9px; + display: flex; + align-items: center; + gap: 7px; + font-size: 10px; + color: #508e73; + white-space: nowrap; +} +.status-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + flex-shrink: 0; +} +.green { + color: var(--green); +} +.red { + color: var(--red); +} +.purple { + color: var(--purple); +} +.gray { + color: #9299a5; +} +.live-button.connected .status-dot { + box-shadow: 0 0 0 3px #65bb9220; + animation: pulse 2.5s infinite; +} +.live-button.paused, +.live-button.disconnected { + color: #9299a5; + background: #f5f6f8; + border-color: var(--line); +} +.metrics { + display: grid; + grid-template-columns: 1.2fr 1fr 1.25fr 1fr; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + padding: 20px 0; + background: linear-gradient(#fcfcfd, #fff); +} +.metrics > div { + padding: 0 25px; + border-right: 1px solid var(--line); + min-width: 0; +} +.metrics > div:last-child { + border: 0; +} +.metrics span { + display: block; + font-size: 10px; + color: #7f8897; +} +.metrics strong { + display: block; + font-size: 25px; + font-weight: 500; + letter-spacing: -1px; + margin: 7px 0 4px; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} +.metrics small { + color: #a0a7b2; + font-size: 9px; + display: block; + line-height: 1.5; +} +.metrics strong .unit { + display: inline; + font-size: 13px; + letter-spacing: 0; + color: #acb1bb; + font-weight: 400; +} +.payment-callout { + margin: 18px 22px 0; + display: flex; + gap: 12px; + align-items: center; + padding: 13px 15px; + background: #f5f1fd; + border: 1px solid #e7dcfc; + border-radius: 7px; +} +.waiting-icon { + width: 30px; + height: 30px; + background: #eae1fa; + border-radius: 8px; + display: grid; + place-items: center; + color: #9270cb; +} +.payment-callout strong { + font-size: 12px; + font-weight: 600; + color: #7b5daf; +} +.payment-callout p { + font-size: 10px; + color: #9883b9; + margin: 4px 0 0; + line-height: 1.4; +} +.payment-callout button { + margin-left: auto; + background: #9875d1; + border-color: #9875d1; +} +.payment-callout button:hover { + background: #8461bd; +} +.console-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 285px; + min-height: 480px; +} +.trace-panel { + min-width: 0; + border-right: 1px solid var(--line); +} +.trace-toolbar { + height: 57px; + border-bottom: 1px solid var(--line); + display: flex; + justify-content: space-between; + gap: 6px; + padding: 0 20px; +} +.tabs { + display: flex; + gap: 22px; +} +.tab { + position: relative; + display: flex; + align-items: center; + gap: 7px; + border: 0; + background: none; + font-size: 11px; + padding: 0; + color: #939aa6; + border-radius: 0; + white-space: nowrap; +} +.tab svg { + width: 14px; + height: 14px; +} +.tab:hover { + color: #414b5a; + background: none; +} +.tab.active { + color: #404956; +} +.tab.active:after { + content: ""; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: var(--orange); +} +.tab span { + font-size: 9px; + border: 1px solid var(--line); + padding: 1px 4px; + border-radius: 4px; + color: #8f98a5; +} +.zoom-controls { + display: flex; + align-items: center; +} +.zoom-controls button { + font-size: 11px; + border-radius: 0; + height: 24px; + min-width: 23px; + border-right: 0; + padding: 0 5px; + color: #8e97a5; +} +.zoom-controls button:first-child { + border-radius: 4px 0 0 4px; +} +.zoom-controls button:last-child { + border-right: 1px solid var(--line); + border-radius: 0 4px 4px 0; +} +.trace-subhead { + padding: 17px 20px 14px; + display: flex; + justify-content: space-between; + font-size: 10px; + color: #858e9d; + gap: 10px; +} +.legend { + display: flex; + gap: 10px; + font-size: 9px; + color: #9aa2ad; +} +.legend span { + display: flex; + align-items: center; + gap: 4px; +} +.legend i { + width: 6px; + height: 6px; + border-radius: 2px; +} +.legend-action { + background: #75b5a3; +} +.legend-wait { + background: #af97dc; +} +.legend-error { + background: #de929b; +} +.waterfall-scroll { + overflow: auto; + max-height: 600px; + padding-bottom: 10px; + scrollbar-width: thin; +} +.waterfall { + min-width: 100%; + position: relative; + --ticks: 20%; +} +.axis { + height: 32px; + position: relative; + display: grid; + grid-template-columns: var(--label) minmax(0, 1fr); + font-size: 9px; + color: #9aa3b0; +} +.axis-label { + padding-left: 22px; + position: sticky; + left: 0; + background: #fff; + z-index: 3; + font-size: 9px; + letter-spacing: 0.4px; + display: flex; + align-items: center; + justify-content: space-between; + padding-right: 18px; +} +.axis-track { + position: relative; + margin-right: 22px; +} +.axis-tick { + position: absolute; + top: 4px; + transform: translateX(-50%); + white-space: nowrap; + font-size: 9px; +} +.axis-tick:first-child { + transform: none; +} +.axis-tick:last-child { + transform: translateX(-100%); +} +.trace-row { + height: 47px; + display: grid; + grid-template-columns: var(--label) minmax(0, 1fr); + border-top: 1px solid #f1f3f6; + position: relative; +} +.trace-row.root { + height: 48px; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + background: #fcfcfd; +} +.row-label { + position: sticky; + left: 0; + z-index: 2; + padding: 0 16px 0 22px; + display: flex; + align-items: center; + gap: 9px; + background: #fff; + border: 0; + border-right: 1px solid var(--line); + border-radius: 0; + text-align: left; + min-width: 0; +} +.root .row-label { + background: #fcfcfd; +} +.row-label:hover { + background: #f7f8fb; +} +.row-symbol { + height: 20px; + width: 20px; + display: grid; + place-items: center; + flex-shrink: 0; + background: #f1f6f5; + border-radius: 5px; + color: #70a592; + font-size: 11px; +} +.row-symbol svg { + height: 11px; + width: 11px; +} +.wait .row-symbol { + color: #9b7ecd; + background: #f1ecf9; +} +.root .row-symbol { + color: #e48460; + background: #fdf0e9; +} +.row-name { + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-weight: 500; +} +.row-duration { + font-size: 9px; + color: #98a2af; + margin-left: auto; + white-space: nowrap; + padding-left: 3px; +} +.row-track { + position: relative; + margin-right: 22px; + background-image: linear-gradient(to right, #e9edf280 1px, transparent 1px); + background-size: var(--ticks) 100%; +} +.span-bar { + height: 19px; + position: absolute; + top: 13px; + min-width: 5px; + max-width: 100%; + border-radius: 4px; + background: #9fcfbd; + border: 1px solid #76b59d; + box-shadow: 0 1px 2px #273f4e09; + transition: filter 0.2s; + padding: 0; + overflow: visible; + z-index: 1; + cursor: pointer; +} +.span-bar:hover { + background: #85bca6; + filter: brightness(0.97); +} +.wait .span-bar { + background: #d8c7f0; + border-color: #b49ad6; +} +.wait .span-bar:hover { + background: #cbb3e9; +} +.root .span-bar { + background: #f4c6ae; + border-color: #e8a885; + height: 21px; + top: 13px; +} +.span-bar.FAILED, +.span-bar.TIMED_OUT { + background: #edb9c0; + border-color: #d8939e; +} +.span-bar.CANCELLED { + background: #e2e5ea; + border-color: #c4cad3; +} +.span-bar.RETRYING { + background: #f6e2b7; + border-color: #d8ba85; +} +.span-bar.running { + background-image: repeating-linear-gradient(120deg, transparent, transparent 8px, #ffffff35 8px, #ffffff35 16px); + background-size: 37px 100%; + animation: flow 2s linear infinite; +} +.span-bar.instant { + min-width: 8px; + width: 8px !important; + height: 8px; + border-radius: 1px; + transform: translateX(-4px) rotate(45deg); + top: 19px; +} +.bar-caption { + position: absolute; + left: 8px; + right: 5px; + top: 2px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-align: left; + font-size: 9px; + color: #795e9b; + pointer-events: none; +} +.root .bar-caption { + color: #a36242; +} +.bar-duration { + position: absolute; + top: 3px; + left: calc(100% + 7px); + font-size: 9px; + white-space: nowrap; + color: #8b98a6; + pointer-events: none; +} +.trace-row.selected .row-label { + background: #f5f3fa; +} +.trace-row.selected .row-track { + background-color: #faf9fd; +} +.trace-row.selected .row-label:before { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 2px; + background: #aa8ad5; +} +.trace-row.future { + opacity: 0.3; +} +.playhead { + position: absolute; + top: 0; + bottom: 0; + border-left: 1px solid #eb8462; + z-index: 2; + pointer-events: none; + opacity: 0.8; +} +.playhead:before { + content: ""; + position: absolute; + top: 0; + left: -3px; + width: 5px; + height: 5px; + background: #eb8462; + border-radius: 1px; +} +.trace-caption { + display: flex; + justify-content: space-between; + gap: 8px; + padding: 8px 20px 15px; + color: #a1a8b4; + font-size: 9px; + line-height: 1.5; +} +.trace-caption .mono { + white-space: nowrap; +} +.replay-controls { + display: flex; + align-items: center; + gap: 10px; + padding: 13px 20px; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + background: #fafbfd; +} +.replay-button { + display: flex; + align-items: center; + gap: 6px; + white-space: nowrap; + font-size: 10px; + padding: 6px 8px; +} +.replay-button svg { + width: 10px; + height: 10px; +} +.replay-controls input { + accent-color: var(--orange); + height: 3px; + min-width: 30px; + flex: 1; +} +.replay-controls > span { + font-size: 9px; + color: #9ca4b1; + min-width: 45px; + text-align: right; +} +.insight { + display: flex; + align-items: flex-start; + gap: 11px; + padding: 22px 23px; + min-height: 100px; +} +.insight-icon { + color: #acb8c6; + font-size: 24px; + line-height: 1; +} +.insight strong { + font-size: 11px; + font-weight: 500; + color: #647182; +} +.insight p { + font-size: 10px; + line-height: 1.8; + color: #9ca5b1; + margin: 4px 0 0; + max-width: 490px; +} +.inspector { + background: #fcfcfe; + min-width: 0; + overflow: hidden; +} +.inspector-heading { + height: 57px; + padding: 0 19px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--line); + font-size: 9px; + letter-spacing: 1px; + color: #919ba9; +} +.inspector-heading svg { + width: 15px; + height: 15px; + color: #afb7c2; +} +#inspector-content { + padding: 20px 19px; +} +.inspector h4 { + font-size: 15px; + font-weight: 550; + letter-spacing: -0.3px; + margin: 0 0 10px; + overflow-wrap: anywhere; +} +.inspector-subtitle { + font-size: 10px; + color: #8b95a5; + line-height: 1.7; + margin: 10px 0 17px; + overflow-wrap: anywhere; +} +.detail-facts { + margin: 18px 0; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px 12px; +} +.detail-facts label { + display: block; + font-size: 9px; + color: #99a3b1; + margin-bottom: 5px; +} +.detail-facts strong { + font-size: 10px; + font-weight: 500; + color: #5f6b7c; +} +.detail-title { + font-size: 9px; + letter-spacing: 1px; + color: #8a95a5; + font-weight: 550; + margin: 22px 0 10px; + display: flex; + align-items: center; + justify-content: space-between; +} +.payload-tabs { + display: flex; + gap: 15px; + border-bottom: 1px solid var(--line); + margin-top: 22px; +} +.payload-tabs button { + border: 0; + background: none; + font-size: 10px; + color: #8e99a8; + padding: 0 0 9px; + border-radius: 0; +} +.payload-tabs button.active { + color: #615576; + border-bottom: 2px solid #b49dd2; +} +.code-block { + font-size: 10px; + line-height: 1.85; + color: #727b90; + margin: 12px 0 0; + padding: 12px 11px; + background: #f3f5f9; + border: 1px solid #e8ebf1; + border-radius: 6px; + max-height: 330px; + overflow: auto; + white-space: pre-wrap; + overflow-wrap: anywhere; +} +.json-key { + color: #8c6da4; +} +.json-string { + color: #4d9780; +} +.json-number { + color: #c18e51; +} +.json-literal { + color: #969fc0; +} +.route { + display: flex; + align-items: center; + justify-content: space-between; + gap: 5px; + margin: 19px 0; + padding: 12px 0; + border-block: 1px solid var(--line); + font-size: 11px; + font-weight: 500; +} +.route svg { + width: 17px; + height: 17px; + color: #a4adbb; +} +.route small { + display: block; + color: #a3acb8; + font-size: 8px; + font-weight: 400; + margin-bottom: 5px; +} +.route > div:last-child { + text-align: right; +} +.step-events { + display: grid; + gap: 0; + margin: 8px 0; +} +.step-event { + background: none; + border: 0; + border-radius: 0; + display: flex; + align-items: center; + text-align: left; + gap: 8px; + padding: 10px 0; + border-bottom: 1px solid #edf0f4; + font-size: 10px; + color: #7c889a; +} +.step-event span:last-child { + margin-left: auto; + font-size: 9px; +} +.step-event:hover { + color: #5d467a; + background: none; +} +.event-detail-back { + margin-bottom: 17px; +} +.journal-heading { + display: flex; + justify-content: space-between; + padding: 17px 20px; + font-size: 9px; + color: #99a3b1; +} +.journal { + max-height: 490px; + overflow: auto; +} +.journal-row { + display: grid; + grid-template-columns: 36px minmax(0, 1fr) 74px 65px; + gap: 10px; + width: 100%; + align-items: center; + border: 0; + border-top: 1px solid #eff1f5; + border-radius: 0; + text-align: left; + padding: 12px 20px; + background: transparent; + font-size: 10px; +} +.journal-row:hover, +.journal-row.selected { + background: #f6f4fb; +} +.journal-row > .mono { + color: #a0a9b6; + font-size: 9px; +} +.journal-name { + min-width: 0; + color: #667285; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.journal-name small { + font-size: 8px; + color: #a2a9b5; + display: block; + margin-top: 5px; +} +.journal-row .badge { + font-size: 8px; + justify-self: start; +} +.journal-row time { + font-size: 9px; + color: #94a0b0; + text-align: right; +} +.console-footer { + padding: 13px 22px; + border-top: 1px solid var(--line); + display: flex; + justify-content: space-between; + gap: 12px; + font-size: 9px; + color: #a2aab6; + background: #fdfdfe; +} +.console-footer > span:first-child { + display: flex; + align-items: center; + gap: 7px; +} +.console-footer b { + font-weight: 400; + color: #85909f; +} +.empty { + padding: 80px 30px; + text-align: center; + color: #94a0b0; + border: 1px dashed #dbe0e7; + border-radius: 10px; + background: #fff; +} +.empty > svg { + width: 35px; + height: 35px; + margin-bottom: 25px; + color: #c0a5dd; +} +.empty h3 { + font-size: 20px; + font-weight: 500; + color: #6c798b; +} +.empty p { + font-size: 12px; + margin-top: 12px; +} +.error { + color: #ba5867; + border: 1px solid #f0d3d9; + background: #fff4f6; + padding: 13px 18px; + border-radius: 7px; + margin-bottom: 18px; + font-size: 12px; +} +.loader { + width: 25px; + height: 25px; + border: 2px solid #e5e9ef; + border-top-color: var(--orange); + border-radius: 50%; + margin: 0 auto 24px; + animation: spin 1s linear infinite; +} +.list-empty { + padding: 30px 10px; + line-height: 1.8; + color: #9aa4b1; + text-align: center; + font-size: 11px; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} +@keyframes flow { + to { + background-position: 37px 0; + } +} +@keyframes pulse { + 50% { + box-shadow: 0 0 0 5px #65bb9209; + } +} +@media (min-width: 1700px) { + main { + padding-inline: 45px; + } + .console-layout { + grid-template-columns: minmax(0, 1fr) 320px; + } + .trace-row { + height: 53px; + } + .span-bar { + top: 16px; + } + .span-bar.instant { + top: 22px; + } + .root .span-bar { + top: 13px; + } + .page-heading { + padding-block: 39px; + } +} +@media (max-width: 1300px) { + body { + grid-template-columns: 52px 230px minmax(0, 1fr); + } + main { + padding-inline: 22px; + } + .console-layout { + grid-template-columns: minmax(0, 1fr) 245px; + } + :root { + --label: 172px; + } + .row-label { + padding-left: 14px; + padding-right: 10px; + gap: 6px; + } + .row-duration { + font-size: 8px; + } + .row-name { + font-size: 10px; + } + .page-heading { + gap: 12px; + } + h2 { + font-size: 22px; + } + .run-controls { + gap: 6px; + } + .run-controls select { + max-width: 127px; + font-size: 10px; + } + .metrics > div { + padding-inline: 18px; + } + .metrics small { + font-size: 8px; + } + .trace-toolbar { + padding-inline: 14px; + } + .tabs { + gap: 14px; + } + .legend { + gap: 6px; + font-size: 8px; + } + .trace-subhead { + padding-inline: 14px; + } + .execution-name h3 { + font-size: 16px; + } + .replay-controls { + padding-inline: 13px; + gap: 7px; + } + .trace-caption { + padding-inline: 14px; + } + .inspector-heading, + #inspector-content { + padding-inline: 15px; + } + .insight { + padding-inline: 17px; + } + .bar-duration { + font-size: 8px; + } +} +@media (max-width: 1100px) { + body { + grid-template-columns: 48px 210px minmax(0, 1fr); + } + main { + padding-inline: 18px; + } + .page-heading { + align-items: flex-start; + flex-direction: column; + padding-block: 23px; + gap: 15px; + } + .console-layout { + grid-template-columns: minmax(0, 1fr); + } + .inspector { + border-top: 1px solid var(--line); + } + #inspector-content { + display: block; + } + .trace-panel { + border: 0; + } + .inspector .code-block { + max-height: 230px; + } + .metrics > div { + padding-inline: 13px; + } + .execution-heading { + padding: 20px 17px; + } + .top-actions a { + display: none; + } + .metrics strong { + font-size: 23px; + } + .run-id { + font-size: 9px; + } + .run-top strong { + font-size: 10px; + } + .run-top time { + font-size: 8px; + } +} +@media (max-width: 700px) { + body { + grid-template-columns: 42px minmax(0, 1fr); + } + .rail { + grid-row: 1 / 3; + } + .runs-panel { + position: relative; + height: auto; + max-height: 290px; + border-bottom: 1px solid var(--line); + } + .workspace { + display: none; + } + .runs-heading { + padding: 15px 18px 10px; + } + .search-box { + margin: 0 17px; + } + .filter-row { + margin-block: 10px; + } + .run-list { + display: flex; + gap: 7px; + padding-bottom: 8px; + min-height: 100px; + } + .run-row { + min-width: 200px; + max-width: 200px; + margin: 0; + } + .storage-note, + .pagination { + display: none; + } + main { + grid-column: 2; + padding: 0 12px 18px; + } + .topbar { + height: 50px; + font-size: 10px; + } + .environment { + font-size: 8px; + } + .page-heading { + padding-block: 21px; + } + h2 { + font-size: 21px; + } + .metrics { + grid-template-columns: 1fr 1fr; + row-gap: 16px; + } + .metrics > div:nth-child(2) { + border-right: 0; + } + .metrics small { + font-size: 9px; + } + .execution-name { + gap: 8px; + } + .execution-name h3 { + font-size: 14px; + } + .execution-heading { + padding: 16px 13px; + align-items: flex-start; + } + .execution-id { + font-size: 8px; + gap: 5px; + } + .execution-id > span:first-child { + overflow-wrap: anywhere; + max-width: 190px; + } + .live-button { + font-size: 9px; + padding: 5px 7px; + } + .console-footer { + font-size: 8px; + padding: 12px; + align-items: flex-start; + } + .console-footer > span:last-child { + max-width: 80px; + text-align: right; + } + :root { + --label: 150px; + } + .row-duration { + display: none; + } + .trace-subhead > span { + display: none; + } + .legend { + font-size: 8px; + } + .trace-toolbar { + padding-inline: 10px; + } + .tabs { + gap: 10px; + } + .tab { + font-size: 10px; + } + .tab svg { + display: none; + } + .trace-caption { + font-size: 8px; + } + .trace-range { + display: none; + } + .payment-callout { + margin: 12px; + flex-wrap: wrap; + padding: 12px; + } + .payment-callout > div:nth-child(2) { + flex: 1; + } + .payment-callout button { + margin-left: 42px; + } + .replay-controls { + flex-wrap: wrap; + } + .replay-controls > span { + font-size: 8px; + } + .journal-row { + grid-template-columns: 24px minmax(0, 1fr) 70px; + padding-inline: 12px; + gap: 7px; + } + .journal-row time { + display: none; + } + .insight p { + font-size: 10px; + } +} +@media (prefers-reduced-motion: reduce) { + *, + *:before, + *:after { + animation: none !important; + transition: none !important; + scroll-behavior: auto !important; + } +} +#close-inspector { + display: none; +} +@media (min-width: 851px) and (max-width: 1100px) { + .page-heading { + flex-direction: row; + align-items: center; + } + h2 { + font-size: 21px; + } + .run-controls select { + max-width: 124px; + } + .primary { + padding-inline: 10px; + } +} +@media (max-width: 1100px) { + .inspector { + display: none; + } + .inspector.open { + display: block; + position: fixed; + z-index: 10; + right: 20px; + top: 100px; + width: 315px; + max-height: calc(100vh - 125px); + overflow: auto; + border: 1px solid #dfe3ec; + border-radius: 10px; + box-shadow: + 0 16px 60px #27344828, + 0 0 0 1px #ffffff70; + } + .inspector-heading { + position: sticky; + top: 0; + z-index: 1; + background: #fcfcfe; + } + .inspector-heading > svg { + display: none; + } + #close-inspector { + display: block; + width: 25px; + height: 25px; + font-size: 19px; + font-weight: 400; + color: #9da6b4; + border: 0; + background: transparent; + } + .trace-row { + height: 44px; + } + .console-layout { + min-height: 430px; + } +} +@media (max-width: 700px) { + .inspector.open { + top: 55px; + right: 12px; + width: min(330px, calc(100vw - 65px)); + max-height: calc(100vh - 75px); + } +} +@media (max-width: 700px) { + .axis-label span:last-child, + #trace-range { + display: none; + } +} +/* As-of execution states, shared by timeline playback and the BPMN view. */ +.row-identity { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} +.row-state { + font-size: 8px; + color: #9da6b3; + font-weight: 400; +} +.row-state.COMPLETED { + color: #66a68d; +} +.row-state.STARTED, +.row-state.RETRY_STARTED { + color: #9c79c9; +} +.row-state.FAILED, +.row-state.TIMED_OUT { + color: #c97987; +} +.trace-row.PENDING .row-name, +.trace-row.PENDING .row-symbol { + color: #a5aebb; +} +.trace-row.PENDING .row-symbol { + background: #f3f5f8; +} +.pending-track-label { + position: absolute; + top: 17px; + left: 8px; + font-size: 9px; + color: #b1b8c3; +} +.badge.PENDING, +.badge.SKIPPED { + color: #98a2b1; + background: #f1f3f6; + border-color: #e5e9ef; +} +.replay-state { + padding: 12px 20px; + border-top: 1px solid #ebe4f4; + background: #f6f2fc; + color: #8a6bb7; + font-size: 10px; + line-height: 1.5; +} +.event-control { + min-width: 24px; + height: 25px; + font-size: 18px; + font-weight: 400; + color: #8f98a7; +} +.bpmn-heading { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 20px 12px; + font-size: 10px; +} +.bpmn-heading strong { + display: block; + color: #687589; + font-size: 12px; + font-weight: 550; +} +.bpmn-heading span { + display: block; + color: #a1aab7; + font-size: 9px; + margin-top: 5px; +} +.bpmn-heading a { + color: #a18abb; + font-size: 9px; +} +.bpmn-legend { + display: flex; + flex-wrap: wrap; + gap: 13px; + padding: 0 20px 15px; + font-size: 9px; + color: #9aa4b3; +} +.bpmn-legend span { + display: flex; + align-items: center; + gap: 5px; +} +.bpmn-legend span:before { + content: ""; + width: 6px; + height: 6px; + border-radius: 50%; + background: #c6cdd6; +} +.bpmn-legend .running:before { + background: #a887d8; +} +.bpmn-legend .completed:before { + background: #69b091; +} +.bpmn-legend .failed:before { + background: #d5808a; +} +.bpmn-legend .skipped:before { + background: #e1e5ec; +} +#bpmn-canvas { + height: 410px; + position: relative; + background-color: #fcfcfe; + background-image: radial-gradient(#e6eaf1 0.7px, transparent 0.7px); + background-size: 14px 14px; + border-block: 1px solid var(--line); +} +#bpmn-canvas .bjs-container { + --bjs-font-family: "DM Sans", sans-serif; +} +#bpmn-canvas .djs-container > svg { + width: 100%; + height: 100%; +} +#bpmn-status { + position: absolute; + top: 35%; + width: 100%; + text-align: center; + color: #a0a9b6; + font-size: 11px; +} +.bpmn-caption { + padding: 14px 20px; + font-size: 9px; + line-height: 1.8; + color: #9ba5b4; +} +.bpmn-state-chip { + border: 0; + background: transparent; + border-radius: 3px; + font: + 10px "DM Sans", + sans-serif; + padding: 1px 0; + color: #a4adba; + text-align: center; + height: 18px; + white-space: nowrap; +} +.bpmn-state-chip:hover { + background: #f1ebfb; +} +.bpmn-state-chip.state-started, +.bpmn-state-chip.state-retry_started { + color: #9367c8; +} +.bpmn-state-chip.state-completed { + color: #559b7d; +} +.bpmn-state-chip.state-failed, +.bpmn-state-chip.state-timed_out { + color: #c56476; +} +#bpmn-canvas .state-started .djs-visual > :first-child, +#bpmn-canvas .state-retry_started .djs-visual > :first-child { + stroke: #aa80d6 !important; + fill: #f4ecfd !important; +} +#bpmn-canvas .state-completed.djs-shape .djs-visual > :first-child { + stroke: #75ad93 !important; + fill: #edf7f1 !important; +} +#bpmn-canvas .state-completed.djs-connection .djs-visual > path { + stroke: #91bca6 !important; +} +#bpmn-canvas .state-failed .djs-visual > :first-child, +#bpmn-canvas .state-timed_out .djs-visual > :first-child { + stroke: #ce7c89 !important; + fill: #fff0f3 !important; +} +#bpmn-canvas .state-cancelled .djs-visual > :first-child { + stroke: #b0b8c5 !important; + fill: #f0f2f6 !important; +} +#bpmn-canvas .state-skipped { + opacity: 0.38; +} +#bpmn-canvas .bpmn-selected .djs-outline { + stroke: #b194d2 !important; + stroke-width: 2px !important; + visibility: visible !important; +} +#bpmn-canvas .djs-label { + font-family: "DM Sans", sans-serif !important; +} +#bpmn-canvas .djs-shape[data-element-id] { + cursor: pointer; +} +@media (max-width: 700px) { + .tabs { + gap: 9px; + } + .tab { + font-size: 9px; + } + .tab span { + display: none; + } + .replay-controls { + gap: 6px; + } + #bpmn-canvas { + height: 330px; + } +} +/* Preserve the viewer's required watermark dimensions despite our general icon rules. */ +.bjs-powered-by svg, +.bjs-powered-by-lightbox svg { + width: 53px; + height: 21px; + stroke: none; +} +#bpmn-canvas .djs-container > svg { + stroke: none; +} +.console-layout.bpmn-mode { + grid-template-columns: minmax(0, 1fr); +} +.bpmn-mode .trace-panel { + border-right: 0; +} +.bpmn-mode .inspector { + display: none; +} +.bpmn-mode .inspector.open { + display: block; + position: fixed; + z-index: 10; + right: 20px; + top: 100px; + width: 315px; + max-height: calc(100vh - 125px); + overflow: auto; + border: 1px solid #dfe3ec; + border-radius: 10px; + box-shadow: 0 16px 60px #27344828; +} +.bpmn-mode .inspector-heading { + position: sticky; + top: 0; + z-index: 1; + background: #fcfcfe; +} +.bpmn-mode .inspector-heading > svg { + display: none; +} +.bpmn-mode #close-inspector { + display: block; + width: 25px; + height: 25px; + font-size: 19px; + font-weight: 400; + color: #9da6b4; + border: 0; + background: transparent; +} +@media (max-width: 700px) { + .bpmn-mode .inspector.open { + top: 55px; + right: 12px; + width: min(330px, calc(100vw - 65px)); + max-height: calc(100vh - 75px); + } +} diff --git a/order-fulfillment-workflow/src/main/resources/static/workflows.html b/order-fulfillment-workflow/src/main/resources/static/workflows.html new file mode 100644 index 00000000..ab60a205 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/workflows.html @@ -0,0 +1,312 @@ + + + + + + + Axon · Workflow console + + + + + + + + +
    +
    + +
    + Delivery map ↗Development +
    +
    +
    +
    +
    WORKFLOW OBSERVABILITY
    +

    Every step. The whole story.

    +
    +
    + +
    +
    + + +
    +
    +

    Reading execution history…

    +

    Loading the persisted workflow journal.

    +
    + +
    + + + + diff --git a/order-fulfillment-workflow/src/main/resources/static/workflows.js b/order-fulfillment-workflow/src/main/resources/static/workflows.js new file mode 100644 index 00000000..db9b2b74 --- /dev/null +++ b/order-fulfillment-workflow/src/main/resources/static/workflows.js @@ -0,0 +1,924 @@ +import { + buildTrace, + replayTrace, + inspectTrace, + duration, + terminal, + friendlyStatus, + scenarioName, +} from "./workflow-trace.mjs"; +import { BpmnDiagram, BPMN_STEPS } from "./workflow-bpmn.mjs"; +const diagram = new BpmnDiagram(selectStep); +let activeView = "waterfall"; +const $ = (id) => document.getElementById(id); +const PAGE_SIZE = 25; +let selected = new URLSearchParams(location.search).get("id"); +let workflow, + journal = [], + offset = 0, + generation = 0, + awaitingFirstEvent = false; +let refreshing = false, + refreshAgain = false, + refreshTimer, + listSignature = "", + rowSignature = "", + inspectorSignature = "", + journalSignature = ""; +let selectedStep = "@root", + selectedEvent = null, + payloadTab = "Input", + zoom = 1, + rowNodes = [], + axisRange = -1; +let live = true, + connected = false, + stream, + frozenAt, + serverOffset = 0; +let replaying = false, + playing = false, + replayPosition = 0, + replayAnchor = 0, + replayAnchorPosition = 0; +let model, + fullModel, + frameAt = 0; +const date = (value) => + new Date(value).toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +const now = () => (live ? Date.now() + serverOffset : frozenAt); +function el(tag, text, className) { + const node = document.createElement(tag); + if (text !== undefined && text !== null) node.textContent = text; + if (className) node.className = className; + return node; +} +function icon(name) { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const use = document.createElementNS(svg.namespaceURI, "use"); + use.setAttribute("href", `#i-${name}`); + svg.append(use); + return svg; +} +function dot(status) { + return el( + "span", + "", + `status-dot ${terminal(status) ? (status === "COMPLETED" ? "green" : status === "CANCELLED" ? "gray" : "red") : "purple"}`, + ); +} +function badge(status) { + return el("span", friendlyStatus(status), `badge ${status}`); +} +function jsonBlock(value) { + const pre = el("pre", null, "code-block"); + const source = JSON.stringify(value ?? null, null, 2); + // Tokenize into text nodes, never render payload or metadata as HTML. + const pattern = + /("(?:\\.|[^"\\])*"\s*:)|("(?:\\.|[^"\\])*")|\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|\b(true|false|null)\b/g; + let cursor = 0; + for (const match of source.matchAll(pattern)) { + pre.append(document.createTextNode(source.slice(cursor, match.index))); + pre.append( + el( + "span", + match[0], + match[1] ? "json-key" : match[2] ? "json-string" : match[3] ? "json-number" : "json-literal", + ), + ); + cursor = match.index + match[0].length; + } + pre.append(document.createTextNode(source.slice(cursor))); + return pre; +} +async function request(url, options) { + const response = await fetch(url, options); + if (!response.ok) { + const error = new Error( + `Request failed (${response.status}). ${response.status === 404 ? "Execution not found." : "Check that the application is running."}`, + ); + error.status = response.status; + throw error; + } + return response.status === 204 || response.headers.get("content-length") === "0" ? null : response.json(); +} +function showError(error) { + $("error").textContent = error.message; + $("error").hidden = false; +} +function scheduleRefresh() { + clearTimeout(refreshTimer); + refreshTimer = setTimeout(refresh, 100); +} +function selectWorkflow(id, justStarted = false) { + selected = id; + awaitingFirstEvent = justStarted; + generation++; + workflow = null; + journal = []; + selectedStep = "@root"; + selectedEvent = null; + payloadTab = "Input"; + replaying = playing = false; + replayPosition = 0; + zoom = 1; + rowSignature = inspectorSignature = journalSignature = ""; + axisRange = -1; + document.querySelector(".inspector").classList.remove("open"); + $("execution").hidden = true; + $("empty").hidden = true; + $("loading").hidden = false; + $("error").hidden = true; + history.replaceState(null, "", `?id=${encodeURIComponent(id)}`); + refresh(); +} +function renderList(data) { + const signature = JSON.stringify([data, selected]); + if (signature === listSignature) return; + listSignature = signature; + $("total").textContent = data.total; + $("page").textContent = data.total + ? `${offset + 1}–${Math.min(offset + PAGE_SIZE, data.total)} of ${data.total}` + : "No executions"; + $("previous").disabled = offset === 0; + $("next").disabled = offset + PAGE_SIZE >= data.total; + const rows = data.items.map((run) => { + const button = el("button", null, `run-row${run.workflowId === selected ? " selected" : ""}`); + button.setAttribute("aria-pressed", String(run.workflowId === selected)); + button.setAttribute("aria-label", `${friendlyStatus(run.status)} execution ${run.workflowId}`); + const top = el("span", null, "run-top"); + top.append( + dot(run.status), + el("strong", friendlyStatus(run.status)), + el( + "time", + new Date(run.startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false }), + ), + ); + const bottom = el("span", null, "run-bottom"); + bottom.append( + el("span", scenarioName(run.payload?.scenario), "scenario-label"), + el( + "span", + terminal(run.status) ? duration(Date.parse(run.updatedAt) - Date.parse(run.startedAt)) : "In progress", + ), + ); + button.append(top, el("span", run.workflowId, "run-id"), bottom); + button.onclick = () => selectWorkflow(run.workflowId); + return button; + }); + $("list").replaceChildren(...(rows.length ? rows : [el("p", "No executions match your filters.", "list-empty")])); +} +async function refresh() { + if (refreshing) { + refreshAgain = true; + return; + } + refreshing = true; + const version = generation, + id = selected; + try { + const query = new URLSearchParams({ search: $("search").value, limit: PAGE_SIZE, offset }); + if ($("status").value) query.set("status", $("status").value); + const data = await request(`/api/workflows?${query}`); + if (version !== generation) return; + renderList(data); + if (!id && data.items.length) { + selectWorkflow(data.items[0].workflowId); + return; + } + if (id) { + const detail = await request(`/api/workflows/${encodeURIComponent(id)}`); + let after = journal.at(-1)?.sequence || 0, + additions = [], + batch; + do { + batch = await request(`/api/workflows/${encodeURIComponent(id)}/events?after=${after}&limit=500`); + if (version !== generation) return; + additions.push(...batch); + after = batch.at(-1)?.sequence || after; + } while (batch.length === 500); + workflow = detail; + journal.push(...additions); + awaitingFirstEvent = false; + $("loading").hidden = $("empty").hidden = true; + $("execution").hidden = false; + renderHeader(); + updateTrace(); + renderJournal(); + renderInspector(); + $("sync").textContent = + `${live ? "Synced" : "Snapshot"} ${new Date().toLocaleTimeString([], { hour12: false })} · durable history`; + // Catch a journal/state read that crossed a commit. Notifications are invalidations, + // and reconnect also fetches the journal, so missing an SSE message cannot lose history. + if (additions.at(-1) && Date.parse(additions.at(-1).timestamp) > Date.parse(detail.updatedAt)) + refreshAgain = true; + } else { + $("loading").hidden = true; + $("empty").hidden = false; + } + $("error").hidden = true; + } catch (error) { + if (version === generation) { + if (error.status === 404 && awaitingFirstEvent) scheduleRefresh(); + else { + $("loading").hidden = true; + showError(error); + } + } + } finally { + refreshing = false; + if (refreshAgain) { + refreshAgain = false; + scheduleRefresh(); + } + } +} +function renderHeader() { + $("definition").textContent = workflow.definition.split(".").pop(); + $("workflow-id").textContent = workflow.workflowId; + $("version").textContent = `v${workflow.version}`; + $("started").textContent = date(workflow.startedAt); + $("last-event").textContent = + `Last event ${new Date(journal.at(-1)?.timestamp || workflow.updatedAt).toLocaleTimeString([], { hour12: false })}`; + const waiting = + workflow.payload?.scenario === "manual-payment" && + workflow.status === "STARTED" && + journal.some((e) => e.step === "initiatePayment" && e.status === "COMPLETED") && + !journal.some((e) => e.step === "awaitPayment" && terminal(e.status)); + $("payment-callout").hidden = !waiting; + $("confirm-payment").disabled = false; +} +function currentTrace() { + fullModel = buildTrace(workflow, journal, now()); + if (playing) { + // Give even millisecond actions a visible running state. Source timestamps + // still determine span widths; only the playback pace is stretched. + replayPosition = Math.min(journal.length, replayAnchorPosition + (performance.now() - replayAnchor) / 900); + if (replayPosition >= journal.length) playing = false; + } + return replaying ? replayTrace(workflow, journal, replayPosition) : fullModel; +} +function updateTrace() { + if (!workflow || !journal.length) return; + model = currentTrace(); + const stateBadge = $("execution-status"); + stateBadge.textContent = `${replaying ? "Replay · " : ""}${friendlyStatus(model.status)}`; + stateBadge.className = `badge ${model.status}`; + $("duration").textContent = duration(model.duration); + const done = model.rows.filter((row) => row.status === "COMPLETED").length; + $("step-count").textContent = `${done} / ${fullModel.rows.length}`; + $("step-hint").textContent = model.rows.some((row) => row.spans.some((span) => span.active)) + ? "Execution in progress" + : model.status === "COMPLETED" + ? "All steps completed" + : friendlyStatus(model.status); + $("wait-time").textContent = duration(model.waitDuration); + $("event-count").textContent = model.events.length; + $("journal-count").textContent = model.events.length; + $("last-event").textContent = model.events.length + ? `Last event ${new Date(model.events.at(-1).timestamp).toLocaleTimeString([], { hour12: false })}` + : "No events played yet"; + if (selectedEvent !== null && !model.events.some((event) => event.sequence === selectedEvent)) selectedEvent = null; + const rows = [ + { + name: "@root", + kind: "WORKFLOW", + status: model.status, + events: model.events.filter((e) => !e.step), + duration: model.duration, + spans: + model.status === "PENDING" + ? [] + : [{ start: model.start, end: model.end, status: model.status, active: !terminal(model.status) }], + }, + ...model.rows, + ]; + const signature = JSON.stringify([ + rows.map((row) => [row.name, row.status, row.spans.map((span) => [span.start, span.status, span.instant])]), + selectedStep, + replaying, + ]); + if (signature !== rowSignature) { + rowSignature = signature; + createRows(rows); + } + draw(rows); + $("replay").disabled = !terminal(fullModel.status); + $("replay").querySelector("span").textContent = playing + ? "Pause replay" + : replaying && replayPosition < journal.length + ? "Play replay" + : "Replay trace"; + $("scrubber").disabled = !terminal(fullModel.status); + $("scrubber").max = String(journal.length); + $("scrubber").value = String(replaying ? Math.floor(replayPosition) : journal.length); + $("scrubber").setAttribute("aria-valuetext", `Event ${model.events.length} of ${journal.length}`); + $("previous-event").disabled = !replaying || replayPosition < 1; + $("next-event").disabled = !replaying || replayPosition >= journal.length; + $("replay-state").hidden = !replaying; + const lastPlayed = model.events.at(-1); + $("replay-state").textContent = lastPlayed + ? `Event ${model.events.length} / ${journal.length} · ${lastPlayed.step || "Workflow"} → ${friendlyStatus(lastPlayed.status)}` + : "Before the first event · all recorded steps are pending"; + $("replay-time").textContent = duration(model.duration); + $("exit-replay").hidden = !replaying; + $("trace-caption").textContent = replaying + ? "Event-by-event replay · every view shows the state at the playhead." + : "Select a span to inspect. Diamond = event without a recorded start."; + const longestWait = model.rows + .filter((row) => row.kind === "WAIT_FOR_EVENT") + .sort((a, b) => b.duration - a.duration)[0]; + if (longestWait) { + $("insight-title").textContent = longestWait.spans.some((s) => s.active) + ? "The workflow is waiting, not working." + : `${Math.round((model.waitDuration / Math.max(1, model.duration)) * 100)}% of this execution was spent awaiting an event.`; + $("insight-text").textContent = + "The payment wait starts before stock reservation and overlaps the actions below it. The waterfall makes that concurrency visible."; + } else { + $("insight-title").textContent = "A trace of what actually happened."; + $("insight-text").textContent = + "Each span is positioned from recorded lifecycle events. Inspect a step to see its input, output, and event metadata."; + } + const deadline = journal.find((e) => e.step === "awaitPayment" && e.status === "STARTED")?.payload?.timeoutTime; + if (deadline) + $("payment-countdown").textContent = `Times out in ${duration(Math.max(0, Date.parse(deadline) - now()))}.`; + renderInspector(); + renderJournal(); + diagram.update(model, selectedStep); + const inspectorDuration = $("inspector-duration"); + if (inspectorDuration) { + const row = model.rows.find((item) => item.name === selectedStep); + inspectorDuration.textContent = + selectedStep === "@root" + ? duration(model.duration) + : row?.status === "PENDING" + ? "Not started" + : row?.spans.every((s) => s.instant) + ? "Point event" + : duration(row?.duration); + } +} +function createRows(rows) { + rowNodes = rows.map((row) => { + const root = row.name === "@root", + wait = row.kind === "WAIT_FOR_EVENT"; + const node = el( + "div", + null, + `trace-row ${row.status}${root ? " root" : ""}${wait ? " wait" : ""}${row.name === selectedStep ? " selected" : ""}`, + ); + const label = el("button", null, "row-label"); + node.dataset.step = row.name; + node.dataset.status = row.status; + label.title = `${root ? workflow.workflowId : row.name} · ${friendlyStatus(row.status)}`; + label.setAttribute("aria-label", `Inspect ${root ? "workflow" : row.name}`); + label.setAttribute("aria-pressed", String(row.name === selectedStep)); + const symbol = el("span", null, "row-symbol"); + symbol.append( + icon( + row.status === "COMPLETED" + ? "check" + : root + ? "workflow" + : wait + ? "clock" + : row.kind === "PUBLISH" + ? "arrow" + : "code", + ), + ); + const timing = el("span", "", "row-duration"); + const identity = el("span", null, "row-identity"); + identity.append( + el("span", root ? "Order fulfillment" : row.name, "row-name"), + el( + "span", + row.status === "STARTED" && wait ? "Waiting for event" : friendlyStatus(row.status), + `row-state ${row.status}`, + ), + ); + label.append(symbol, identity, timing); + label.onclick = () => selectStep(row.name); + const track = el("div", null, "row-track"); + if (row.status === "PENDING") track.append(el("span", "Not started", "pending-track-label")); + const bars = row.spans.map((span, index) => { + const bar = el( + "button", + null, + `span-bar ${span.status}${span.active ? " running" : ""}${span.instant ? " instant" : ""}`, + ); + bar.setAttribute( + "aria-label", + `${root ? "Workflow" : row.name}, ${friendlyStatus(span.status)}, ${span.instant ? "point event" : duration(span.end - span.start)}`, + ); + bar.onclick = () => selectStep(row.name); + if (!span.instant && (wait || root)) + bar.append( + el( + "span", + root + ? friendlyStatus(row.status) + : span.active + ? "Awaiting PaymentConfirmed" + : friendlyStatus(span.status) === "Completed" + ? "PaymentConfirmed received" + : friendlyStatus(span.status), + "bar-caption", + ), + ); + const ms = el("span", "", "bar-duration"); + if (!wait && !root && !span.instant) bar.append(ms); + track.append(bar); + return { bar, ms }; + }); + const playhead = el("div", "", "playhead"); + track.append(playhead); + node.append(label, track); + return { node, timing, bars, playhead }; + }); + $("waterfall-rows").replaceChildren(...rowNodes.map((row) => row.node)); +} +function draw(rows) { + const displayDuration = replaying ? fullModel.duration : model.duration; + const range = Math.max( + 100, + terminal(fullModel.status) ? displayDuration * 1.045 : Math.ceil((displayDuration + 500) / 1000) * 1000, + ); + const available = $("waterfall-scroll").clientWidth; + const labelWidth = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--label")); + const width = Math.max(available, labelWidth + (available - labelWidth) * zoom); + $("waterfall").style.width = `${width}px`; + const tickCount = Math.max(2, Math.min(5, Math.floor((width - labelWidth - 22) / 75))); + const axisSignature = `${range}:${tickCount}`; + $("waterfall").style.setProperty("--ticks", `${100 / tickCount}%`); + if (axisRange !== axisSignature) { + axisRange = axisSignature; + const label = el("div", null, "axis-label"); + label.append(el("span", "STEP"), el("span", "DURATION")); + const axis = el("div", null, "axis-track"); + for (let i = 0; i <= tickCount; i++) { + const tick = el("span", i === 0 ? "0" : duration((range * i) / tickCount), "axis-tick"); + tick.style.left = `${(i * 100) / tickCount}%`; + axis.append(tick); + } + $("time-axis").replaceChildren(label, axis); + } + rows.forEach((row, index) => { + const nodes = rowNodes[index]; + nodes.timing.textContent = + row.status === "PENDING" + ? "—" + : ["PENDING", "SKIPPED"].includes(row.status) + ? "Not started" + : row.spans.every((s) => s.instant) + ? "event" + : duration(row.duration); + row.spans.forEach((span, i) => { + const left = Math.max(0, ((span.start - model.start) / range) * 100), + barWidth = Math.max(0, ((span.end - span.start) / range) * 100); + nodes.bars[i].bar.style.left = `${left}%`; + nodes.bars[i].bar.style.width = `${barWidth}%`; + nodes.bars[i].bar.title = + `${row.name === "@root" ? "Workflow" : row.name} · ${friendlyStatus(span.status)}\n${new Date(span.start).toISOString()}${span.instant ? " · no start event" : ` → ${new Date(span.end).toISOString()}\n${duration(span.end - span.start)}`}`; + nodes.bars[i].ms.textContent = duration(span.end - span.start); + nodes.bars[i].bar.setAttribute( + "aria-label", + `${row.name === "@root" ? "Workflow" : row.name}, ${friendlyStatus(span.status)}, ${span.instant ? "point event" : duration(span.end - span.start)}`, + ); + nodes.bars[i].ms.hidden = left + barWidth > 83; + }); + nodes.playhead.hidden = !replaying && (terminal(model.status) || !live); + nodes.playhead.style.left = `${Math.min(100, (model.duration / range) * 100)}%`; + }); + $("trace-range").textContent = `${duration(model.duration)} total`; +} +function selectStep(name) { + selectedStep = name; + selectedEvent = null; + payloadTab = "Input"; + inspectorSignature = journalSignature = ""; + updateTrace(); + renderInspector(); + document.querySelector(".inspector").classList.add("open"); +} +function fact(label, value) { + const node = el("div"); + node.append(el("label", label), el("strong", value)); + return node; +} +function renderInspector() { + if (!workflow || !model) return; + const signature = JSON.stringify([ + workflow.workflowId, + model.events.at(-1)?.sequence, + model.status, + replaying, + selectedStep, + selectedEvent, + payloadTab, + ]); + if (signature === inspectorSignature) return; + inspectorSignature = signature; + const box = $("inspector-content"); + box.replaceChildren(); + const event = selectedEvent == null ? null : model.events.find((e) => e.sequence === selectedEvent); + const row = + model.rows.find((row) => row.name === selectedStep) || + (BPMN_STEPS[selectedStep] + ? { + name: selectedStep, + kind: BPMN_STEPS[selectedStep], + status: terminal(model.status) ? "SKIPPED" : "PENDING", + spans: [], + events: [], + duration: 0, + } + : null); + const details = inspectTrace(model, selectedStep) || row || inspectTrace(model, "@root"); + const root = selectedStep === "@root" || !row; + $("inspector-kind").textContent = + `${replaying ? "REPLAY · " : ""}${event ? "RECORDED EVENT" : root ? "EXECUTION DETAILS" : "STEP DETAILS"}`; + if (event) { + const back = el("button", "← Back to step", "text-button event-detail-back"); + back.onclick = () => { + selectedEvent = null; + renderInspector(); + }; + box.append( + back, + el("h4", event.type.split(".").pop()), + badge(event.status), + el("p", event.timestamp, "inspector-subtitle mono"), + ); + box.append( + el("div", "PAYLOAD", "detail-title"), + jsonBlock(event.payload), + el("div", "METADATA", "detail-title"), + jsonBlock(event.metadata), + el("div", "EVENT ID", "detail-title"), + el("p", event.eventId, "inspector-subtitle mono"), + ); + return; + } + box.append(el("h4", root ? "Order fulfillment" : row.name), badge(details.status)); + box.append( + el( + "p", + root + ? "Reserve stock, collect payment, and deliver the order. Every transition is recorded." + : row.kind === "WAIT_FOR_EVENT" + ? "Suspended until a matching event arrives or the deadline is reached." + : row.kind === "PUBLISH" + ? "A published event. A point marker is used when no start event was recorded." + : "A workflow action with its recorded input and result.", + "inspector-subtitle", + ), + ); + if (root && model.input?.originCity) { + const route = el("div", null, "route"), + from = el("div"), + to = el("div"); + from.append(el("small", "ORIGIN"), el("span", model.input.originCity)); + to.append(el("small", "DESTINATION"), el("span", model.input.destinationCity)); + route.append(from, icon("arrow"), to); + box.append(route); + } + const facts = el("div", null, "detail-facts"); + facts.append( + fact( + root ? "Scenario" : "Primitive", + root + ? scenarioName(model.input?.scenario) + : row.kind === "WAIT_FOR_EVENT" + ? "Wait for event" + : row.kind === "PUBLISH" + ? "Publish" + : "Action", + ), + fact( + "Duration", + root + ? duration(model.duration) + : ["PENDING", "SKIPPED"].includes(row.status) + ? "Not started" + : row.spans.every((s) => s.instant) + ? "Point event" + : duration(row.duration), + ), + fact( + root ? "Version" : "Started at", + root + ? `v${workflow.version}` + : ["PENDING", "SKIPPED"].includes(row.status) + ? "Not started" + : `+${duration(row.start - model.start)}`, + ), + fact("Events", String(root ? model.events.length : row.events.length)), + ); + facts.children[1].querySelector("strong").id = "inspector-duration"; + box.append(facts); + const events = details.events; + const tabs = el("div", null, "payload-tabs"); + for (const title of ["Input", "Output"]) { + const tab = el("button", title, payloadTab === title ? "active" : ""); + tab.onclick = () => { + payloadTab = title; + renderInspector(); + }; + tabs.append(tab); + } + box.append(tabs); + const { input, output } = details; + if ((payloadTab === "Input" ? input : output) === undefined) + box.append( + el( + "p", + details.status === "SKIPPED" + ? "This branch was not taken in this execution." + : details.status === "PENDING" + ? "This step has not started at the playhead." + : payloadTab === "Input" + ? "No input event was recorded." + : "No result recorded at the playhead.", + "inspector-subtitle", + ), + ); + else box.append(jsonBlock(payloadTab === "Input" ? input : output)); + box.append(el("div", "LIFECYCLE EVENTS", "detail-title")); + const timeline = el("div", null, "step-events"); + for (const item of events) { + const button = el("button", null, "step-event"); + button.append( + dot(item.status), + el("span", friendlyStatus(item.status)), + el("span", `+${duration(Date.parse(item.timestamp) - model.start)}`, "mono"), + ); + button.onclick = () => { + selectedEvent = item.sequence; + renderInspector(); + }; + timeline.append(button); + } + box.append(timeline); +} +function renderJournal() { + if (!model) return; + const signature = JSON.stringify([ + workflow.workflowId, + model.events.length, + model.events.at(-1)?.sequence, + selectedEvent, + replaying, + ]); + if (signature === journalSignature) return; + journalSignature = signature; + $("events").replaceChildren( + ...model.events.map((event) => { + const button = el("button", null, `journal-row${selectedEvent === event.sequence ? " selected" : ""}`); + const name = el("span", event.step || "Workflow", "journal-name"); + name.append(el("small", event.type.split(".").pop())); + button.append( + el("span", `#${event.sequence}`, "mono"), + name, + badge(event.status), + el("time", `+${duration(Date.parse(event.timestamp) - Date.parse(workflow.startedAt))}`, "mono"), + ); + button.onclick = () => { + selectedStep = event.step || "@root"; + selectedEvent = event.sequence; + renderInspector(); + renderJournal(); + document.querySelector(".inspector").classList.add("open"); + }; + return button; + }), + ); +} +function setConnection() { + $("live-label").textContent = !live ? "Paused" : connected ? "Live" : "Reconnecting"; + $("live").className = `live-button ${!live ? "paused" : connected ? "connected" : "disconnected"}`; + $("live").setAttribute("aria-pressed", String(live)); + $("live").title = live ? "Pause incoming updates" : "Resume live history updates"; +} +function connect() { + stream?.close(); + if (!live) return; + stream = new EventSource("/api/workflows/stream"); + const synchronizeClock = (event) => { + const data = JSON.parse(event.data); + serverOffset = data.serverTime - Date.now(); + }; + stream.addEventListener("ready", (event) => { + synchronizeClock(event); + connected = true; + setConnection(); + refresh(); + }); + stream.addEventListener("heartbeat", synchronizeClock); + stream.addEventListener("workflow", () => { + if (live) scheduleRefresh(); + }); + stream.onerror = () => { + connected = false; + setConnection(); + }; +} +$("live").onclick = () => { + if (live) { + frozenAt = now(); + live = false; + generation++; + stream?.close(); + connected = false; + refreshAgain = false; + clearTimeout(refreshTimer); + $("sync").textContent = "Live updates paused · showing a snapshot"; + } else { + live = true; + connect(); + } + setConnection(); +}; +$("run").onclick = async () => { + $("run").disabled = true; + try { + if (!live) { + live = true; + connect(); + } + const result = await request(`/simulate/single?scenario=${encodeURIComponent($("scenario").value)}`, { + method: "POST", + }); + offset = 0; + $("search").value = ""; + $("status").value = ""; + selectWorkflow(result.orderId, true); + } catch (error) { + showError(error); + } finally { + $("run").disabled = false; + } +}; +$("confirm-payment").onclick = async () => { + const id = selected; + $("confirm-payment").disabled = true; + try { + const response = await fetch(`/orders/${encodeURIComponent(id)}/payment`, { method: "POST" }); + if (!response.ok) throw new Error(`Payment failed (${response.status}).`); + scheduleRefresh(); + } catch (error) { + showError(error); + $("confirm-payment").disabled = false; + } +}; +$("copy-id").onclick = async () => { + try { + await navigator.clipboard.writeText(selected); + $("copy-id").textContent = "Copied"; + setTimeout(() => ($("copy-id").textContent = "Copy ID"), 1500); + } catch { + $("copy-id").textContent = "Select ID to copy"; + } +}; +let searchTimer; +$("search").oninput = () => { + clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + offset = 0; + generation++; + refresh(); + }, 250); +}; +$("status").onchange = () => { + offset = 0; + generation++; + refresh(); +}; +$("previous").onclick = () => { + offset = Math.max(0, offset - PAGE_SIZE); + generation++; + refresh(); +}; +$("next").onclick = () => { + offset += PAGE_SIZE; + generation++; + refresh(); +}; +for (const name of ["waterfall", "bpmn", "events"]) { + $(name + "-tab").onclick = async () => { + activeView = name; + document.querySelector(".console-layout").classList.toggle("bpmn-mode", name === "bpmn"); + if (name === "bpmn") document.querySelector(".inspector").classList.remove("open"); + for (const view of ["waterfall", "bpmn", "events"]) { + $(view + "-view").hidden = view !== name; + $(view + "-tab").classList.toggle("active", view === name); + $(view + "-tab").setAttribute("aria-selected", String(view === name)); + } + document.querySelector(".zoom-controls").hidden = name === "events"; + updateTrace(); + if (name === "bpmn" && model) { + try { + await diagram.show(model, selectedStep); + } catch (error) { + $("bpmn-status").textContent = error.message; + showError(error); + } + } + }; +} +$("zoom-in").onclick = () => { + if (activeView === "bpmn") { + diagram.zoom(1.25); + return; + } + zoom = Math.min(16, zoom * 2); + updateTrace(); +}; +$("zoom-out").onclick = () => { + if (activeView === "bpmn") { + diagram.zoom(0.8); + return; + } + zoom = Math.max(1, zoom / 2); + updateTrace(); +}; +$("fit").onclick = () => { + if (activeView === "bpmn") { + diagram.fit(); + return; + } + zoom = 1; + $("waterfall-scroll").scrollLeft = 0; + updateTrace(); +}; +$("close-inspector").onclick = () => document.querySelector(".inspector").classList.remove("open"); +document.addEventListener("keydown", (event) => { + if (event.key === "Escape") document.querySelector(".inspector").classList.remove("open"); +}); +function startReplay() { + replaying = true; + playing = true; + replayAnchor = performance.now(); + replayAnchorPosition = replayPosition; +} +$("replay").onclick = () => { + if (playing) playing = false; + else { + if (!replaying || replayPosition >= journal.length) replayPosition = 0; + startReplay(); + } + updateTrace(); +}; +$("scrubber").oninput = () => { + replaying = true; + playing = false; + replayPosition = Number($("scrubber").value); + updateTrace(); +}; +for (const [id, direction] of [ + ["previous-event", -1], + ["next-event", 1], +]) { + $(id).onclick = () => { + playing = false; + replayPosition = Math.max(0, Math.min(journal.length, Math.floor(replayPosition) + direction)); + updateTrace(); + }; +} +$("exit-replay").onclick = () => { + replaying = playing = false; + replayPosition = 0; + updateTrace(); +}; +function frame(time) { + if (time - frameAt > 80 && !document.hidden && workflow && (playing || (live && !terminal(fullModel?.status)))) { + frameAt = time; + updateTrace(); + } + requestAnimationFrame(frame); +} +window.addEventListener("resize", () => { + updateTrace(); + diagram.fit(); +}); +window.addEventListener("pagehide", () => stream?.close()); +window.addEventListener("pageshow", (event) => { + if (event.persisted && live) connect(); +}); +document.addEventListener("visibilitychange", () => { + if (!document.hidden && live) refresh(); +}); +// Low-frequency reconciliation also covers a quiet dropped connection or missed invalidation. +setInterval(() => { + if (live && !document.hidden) refresh(); +}, 30000); +refresh(); +connect(); +requestAnimationFrame(frame); diff --git a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java new file mode 100644 index 00000000..219f6423 --- /dev/null +++ b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java @@ -0,0 +1,106 @@ +package io.axoniq.demo.orderfulfillment; + +import io.axoniq.demo.orderfulfillment.projection.OrderStatus; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.resttestclient.TestRestTemplate; + +import java.util.concurrent.TimeUnit; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +abstract class AbstractOrderFulfillmentTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void orderIsDeliveredAfterAutomaticPayment() { + var orderId = restTemplate.postForObject( + "/orders?customerId=customer-1&email=customer-1@example.com&amount=99.95", + null, + String.class); + assertThat(orderId).isNotBlank(); + + // Auto-payment robot confirms shortly after the workflow registers `awaitPayment`, so the + // order should advance through IN_TRANSIT and reach DELIVERED without a manual payment POST. + await().atMost(45, TimeUnit.SECONDS).untilAsserted(() -> { + var status = restTemplate.getForObject("/orders/" + orderId, OrderStatus.class); + assertThat(status).isNotNull(); + assertThat(status.status()).isEqualTo(OrderStatus.Status.DELIVERED); + assertThat(status.trackingNumber()).startsWith("TRK-"); + }); + assertWorkflowHistory(orderId, "COMPLETED", "shipOrder"); + } + + @Test + void unavailableStockIsProjectedAsFailed() { + assertFailure("out-of-stock", "Out of stock"); + } + + @Test + void missingPaymentIsProjectedAsFailed() { + assertFailure("payment-timeout", "Payment timed out"); + } + + @Test + void interactivePaymentWaitsForAnExplicitSignal() { + var orderId = restTemplate.postForObject( + "/orders?customerId=customer-1&email=customer-1@example.com&amount=99.95&scenario=manual-payment", + null, String.class); + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + var events = restTemplate.getForObject("/api/workflows/" + orderId + "/events", List.class); + assertThat(events.stream().map(e -> ((Map) e).get("step") + ":" + ((Map) e).get("status"))) + .contains("initiatePayment:COMPLETED"); + }); + // Longer than the robot's maximum 3.5s delay: this scenario must remain suspended. + await().during(4, TimeUnit.SECONDS).atMost(6, TimeUnit.SECONDS).untilAsserted(() -> + assertThat(restTemplate.getForObject("/api/workflows/" + orderId, Map.class)) + .containsEntry("status", "STARTED")); + restTemplate.postForEntity("/orders/" + orderId + "/payment", null, Void.class); + assertWorkflowHistory(orderId, "COMPLETED", "shipOrder"); + } + + private void assertFailure(String scenario, String reason) { + var orderId = restTemplate.postForObject( + "/orders?customerId=customer-1&email=customer-1@example.com&amount=99.95&scenario=" + scenario, + null, String.class); + assertThat(orderId).isNotBlank(); + + await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> { + var status = restTemplate.getForObject("/orders/" + orderId, OrderStatus.class); + assertThat(status).isNotNull(); + assertThat(status.status()).isEqualTo(OrderStatus.Status.FAILED); + assertThat(status.failureReason()).isEqualTo(reason); + assertThat(status.trackingNumber()).isNull(); + }); + assertWorkflowHistory(orderId, "FAILED", "awaitPayment"); + } + + private void assertWorkflowHistory(String orderId, String expectedStatus, String expectedStep) { + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + var history = restTemplate.getForObject("/api/workflows/" + orderId, Map.class); + assertThat(history).containsEntry("status", expectedStatus).containsEntry("workflowId", orderId); + var events = restTemplate.getForObject("/api/workflows/" + orderId + "/events", List.class); + assertThat(events).isNotEmpty(); + assertThat(events.stream().map(e -> ((Map) e).get("step"))) + .contains(expectedStep); + assertThat(events.stream().map(e -> ((Map) e).get("step") + ":" + ((Map) e).get("status"))) + .contains("awaitPayment:STARTED"); + var listing = restTemplate.getForObject("/api/workflows?search=" + orderId, Map.class); + assertThat((List) listing.get("items")).hasSize(1); + }); + } + + @Test + void workflowHistoryEndpointsValidateRequests() { + assertThat(restTemplate.getForEntity("/api/workflows/missing", String.class).getStatusCode().value()).isEqualTo(404); + assertThat(restTemplate.getForEntity("/api/workflows?limit=0", String.class).getStatusCode().value()).isEqualTo(400); + assertThat(restTemplate.getForEntity("/api/workflows?offset=-1", String.class).getStatusCode().value()).isEqualTo(400); + assertThat(restTemplate.getForEntity("/api/workflows?status=INVALID", String.class).getStatusCode().value()).isEqualTo(400); + assertThat(restTemplate.getForEntity("/workflows.html", String.class).getStatusCode().value()).isEqualTo(200); + } +} diff --git a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/JdbcWorkflowHistoryRepositoryTest.java b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/JdbcWorkflowHistoryRepositoryTest.java new file mode 100644 index 00000000..0cf9c947 --- /dev/null +++ b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/JdbcWorkflowHistoryRepositoryTest.java @@ -0,0 +1,188 @@ +package io.axoniq.demo.orderfulfillment; + +import com.zaxxer.hikari.HikariDataSource; +import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository; +import io.axoniq.framework.workflow.dsl.api.StepRetryInfo; +import io.axoniq.framework.workflow.dsl.api.StepStatus; +import io.axoniq.framework.workflow.dsl.api.WorkflowError; +import io.axoniq.framework.workflow.dsl.api.WorkflowStatus; +import io.axoniq.framework.workflow.history.inmemory.WorkflowHistoryProjector; +import io.axoniq.framework.workflow.query.api.WorkflowStateQuery; +import io.axoniq.framework.workflow.runtime.execution.payload.PayloadReducerRegistry; +import io.axoniq.framework.workflow.runtime.util.MetadataUtils; +import org.axonframework.conversion.jackson.JacksonConverter; +import org.axonframework.messaging.core.ApplicationContext; +import org.axonframework.messaging.core.MessageStream; +import org.axonframework.messaging.core.MessageType; +import org.axonframework.messaging.core.Metadata; +import org.axonframework.messaging.core.VersionedType; +import org.axonframework.messaging.core.unitofwork.SimpleUnitOfWorkFactory; +import org.axonframework.messaging.eventhandling.EventMessage; +import org.axonframework.messaging.eventhandling.GenericEventMessage; +import org.axonframework.messaging.eventhandling.conversion.DelegatingEventConverter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; +import java.time.Instant; +import java.util.Map; +import java.util.ArrayList; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class JdbcWorkflowHistoryRepositoryTest { + + @TempDir + Path directory; + + private final DelegatingEventConverter converter = new DelegatingEventConverter(new JacksonConverter()); + private final PayloadReducerRegistry reducers = new PayloadReducerRegistry(); + private final SimpleUnitOfWorkFactory units = new SimpleUnitOfWorkFactory(new ApplicationContext() { + @Override + public C component(Class type, String name) { + if (type == PayloadReducerRegistry.class) return type.cast(reducers); + throw new IllegalArgumentException("Unexpected component: " + type); + } + }); + + @Test + void fileHistoryReopensAndContinuesAnUnfinishedWorkflowWithoutDuplicatingEvents() { + var started = started("order-1"); + var waiting = event(Map.of("orderId", "order-1"), MetadataUtils.create("order-1", "awaitPayment", StepStatus.STARTED)); + try (var database = database()) { + var history = repository(database); + project(history, started); + project(history, waiting); + assertThat(history.detail("order-1")).isPresent(); + } + + // A fresh connection pool and repository; H2 is closed and reopened from disk. + try (var database = database()) { + var history = repository(database); + var restored = history.findById("order-1").join().orElseThrow().state(); + assertThat(restored.workflowStatus()).isEqualTo(WorkflowStatus.STARTED); + assertThat(restored.payload()).containsEntry("customerId", "alice"); + assertThat(restored.getStep("awaitPayment").status()).isEqualTo(StepStatus.STARTED); + + var paid = event(Map.of("transactionId", "txn-1"), MetadataUtils.create("order-1", "awaitPayment", StepStatus.COMPLETED)); + var completed = event(Map.of(), MetadataUtils.create("order-1", WorkflowStatus.COMPLETED)); + project(history, paid); + project(history, completed); + // Simulate tokens lagging the committed history after a crash or token reset. + project(history, started); + project(history, waiting); + project(history, paid); + project(history, completed); + + var state = history.findById("order-1").join().orElseThrow().state(); + assertThat(state.workflowStatus()).isEqualTo(WorkflowStatus.COMPLETED); + assertThat(state.getStep("awaitPayment").status()).isEqualTo(StepStatus.COMPLETED); + assertThat(state.getStep("awaitPayment").result()).isEqualTo(Map.of("transactionId", "txn-1")); + assertThat(history.timeline("order-1", 0, 100)).hasSize(4); + assertThat(history.findAll(WorkflowStateQuery.byPayloadValue("customerId", "alice") + .stepStatus("awaitPayment", StepStatus.COMPLETED)).join()).hasSize(1); + assertThat(history.findAll(WorkflowStateQuery.byWorkflowStatus(WorkflowStatus.STARTED)).join()).isEmpty(); + assertThat(history.list("order-1", "COMPLETED", 10, 0).get("total")).isEqualTo(1L); + var first = history.timeline("order-1", 0, 1).getFirst(); + assertThat(history.timeline("order-1", (Long) first.get("sequence"), 100)).hasSize(3); + } + try (var database = database()) { + var history = repository(database); + assertThat(history.findById("order-1").join().orElseThrow().state().workflowStatus()) + .isEqualTo(WorkflowStatus.COMPLETED); + assertThat(history.timeline("order-1", 0, 100)).hasSize(4); + } + } + + @Test + void errorsRetriesAndVersionMigrationsSurviveReload() { + try (var database = database()) { + var history = repository(database); + project(history, started("failed-order")); + var error = WorkflowError.from(new IllegalStateException("Gateway unavailable")); + project(history, event(new StepRetryInfo(1, 3, error), + MetadataUtils.create("failed-order", "payment", StepStatus.RETRYING))); + var retry = history.findById("failed-order").join().orElseThrow().state().getStep("payment"); + assertThat(retry.result()).isInstanceOf(StepRetryInfo.class); + assertThat(retry.error()).hasMessageContaining("Gateway unavailable"); + project(history, event(Map.of(), MetadataUtils.createVersionMigrationStep("failed-order", "payment-v2", "2.0.0"))); + project(history, event(error, MetadataUtils.create("failed-order", "payment", StepStatus.FAILED))); + project(history, event(error, MetadataUtils.create("failed-order", WorkflowStatus.FAILED))); + } + try (var database = database()) { + var history = repository(database); + var state = history.findById("failed-order").join().orElseThrow().state(); + assertThat(state.workflowStatus()).isEqualTo(WorkflowStatus.FAILED); + assertThat(state.getStep("payment").error()).hasMessageContaining("Gateway unavailable"); + assertThat(state.versionMigrations()).containsEntry("payment-v2", "2.0.0"); + assertThat(history.timeline("failed-order", 0, 100)).hasSize(5); + history.clear(); + assertThat(history.findById("failed-order").join()).isEmpty(); + assertThat(history.timeline("failed-order", 0, 100)).isEmpty(); + } + } + + @Test + void failedProjectionRollsBackBothTheEventAndStateAndCanBeRetried() { + try (var database = database()) { + var notifications = new ArrayList(); + var history = new JdbcWorkflowHistoryRepository(database, converter, notifications::add); + history.initialize(units); + var event = started("rollback-order"); + var projector = new WorkflowHistoryProjector(history); + assertThatThrownBy(() -> units.create().executeWithResult(context -> { + history.interceptOnHandle(event, context, (message, pc) -> { + projector.handle(message, pc); + assertThat(notifications).isEmpty(); // No notification before commit. + return MessageStream.failed(new IllegalStateException("Simulated failure after save")); + }); + return CompletableFuture.completedFuture(null); + }).join()).hasRootCauseMessage("Simulated failure after save"); + assertThat(history.detail("rollback-order")).isEmpty(); + assertThat(history.timeline("rollback-order", 0, 100)).isEmpty(); + assertThat(notifications).isEmpty(); + project(history, event); + assertThat(history.findById("rollback-order").join()).isPresent(); + assertThat(history.timeline("rollback-order", 0, 100)).hasSize(1); + assertThat(notifications).containsExactly("rollback-order"); + project(history, event); + assertThat(notifications).containsExactly("rollback-order"); // No duplicate notification. + } + } + + private HikariDataSource database() { + var source = new HikariDataSource(); + source.setJdbcUrl("jdbc:h2:file:" + directory.resolve("history") + ";DB_CLOSE_ON_EXIT=FALSE"); + source.setUsername("sa"); + source.setMaximumPoolSize(2); + return source; + } + + private JdbcWorkflowHistoryRepository repository(HikariDataSource source) { + var history = new JdbcWorkflowHistoryRepository(source, converter); + history.initialize(units); + return history; + } + + private void project(JdbcWorkflowHistoryRepository history, EventMessage event) { + units.create().executeWithResult(context -> { + history.interceptOnHandle(event, context, new WorkflowHistoryProjector(history)::handle); + return CompletableFuture.completedFuture(null); + }).join(); + } + + private EventMessage started(String id) { + var metadata = MetadataUtils.withWorkflowDefinitionId(MetadataUtils.create(id, WorkflowStatus.STARTED), + VersionedType.of("OrderFulfillmentWorkflow", "1.0.0")) + .and(MetadataUtils.METADATA_KEY_MODIFY_PAYLOAD, "local_only"); + return event(Map.of("orderId", id, "customerId", "alice"), metadata); + } + + private EventMessage event(Object payload, Metadata metadata) { + return new GenericEventMessage(UUID.randomUUID().toString(), new MessageType("test.HistoryEvent", "1.0.0"), + payload, metadata, Instant.now()).withConverter(converter); + } +} diff --git a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentApplicationTest.java b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentApplicationTest.java new file mode 100644 index 00000000..e2b9bb67 --- /dev/null +++ b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentApplicationTest.java @@ -0,0 +1,39 @@ +package io.axoniq.demo.orderfulfillment; + +import org.axonframework.eventsourcing.eventstore.EventStorageEngine; +import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine; +import org.axonframework.messaging.core.MessageDispatchInterceptor; +import org.axonframework.messaging.eventhandling.EventMessage; +import org.axonframework.messaging.eventhandling.GenericEventMessage; +import org.axonframework.messaging.eventhandling.conversion.EventConverter; +import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"axon.axonserver.enabled=false", + "spring.datasource.url=jdbc:h2:mem:order-fulfillment-test;DB_CLOSE_DELAY=-1"}) +@AutoConfigureTestRestTemplate +@Import(OrderFulfillmentApplicationTest.InMemoryStorage.class) +class OrderFulfillmentApplicationTest extends AbstractOrderFulfillmentTest { + + @TestConfiguration(proxyBeanMethods = false) + static class InMemoryStorage { + + @Bean + EventStorageEngine eventStorageEngine() { + return new InMemoryEventStorageEngine(); + } + + @Bean + MessageDispatchInterceptor inMemoryEventConversion(EventConverter converter) { + // Axon Server attaches a converter when reading persisted events. The in-memory + // store retains the original message, so attach it before publication here. + return (message, context, chain) -> chain.proceed( + new GenericEventMessage(message.identifier(), message.type(), message.payload(), + message.metadata(), message.timestamp()).withConverter(converter), context); + } + } +} diff --git a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentIT.java b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentIT.java index 4cdbced4..b877e523 100644 --- a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentIT.java +++ b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentIT.java @@ -1,10 +1,6 @@ package io.axoniq.demo.orderfulfillment; -import io.axoniq.demo.orderfulfillment.projection.OrderStatus; import io.axoniq.framework.testcontainer.AxonServerContainer; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertyRegistry; @@ -13,15 +9,12 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.time.Duration; -import java.util.concurrent.TimeUnit; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.datasource.url=jdbc:h2:mem:order-fulfillment-it;DB_CLOSE_DELAY=-1") @AutoConfigureTestRestTemplate @Testcontainers -class OrderFulfillmentIT { +class OrderFulfillmentIT extends AbstractOrderFulfillmentTest { @Container static final AxonServerContainer AXON_SERVER = new AxonServerContainer("docker.axoniq.io/axoniq/axonserver:2026.0.0") @@ -36,24 +29,4 @@ static void axonProperties(DynamicPropertyRegistry registry) { () -> AXON_SERVER.getHost() + ":" + AXON_SERVER.getMappedPort(8124)); } - @Autowired - private TestRestTemplate restTemplate; - - @Test - void test_order_fulfillment_process() { - var orderId = restTemplate.postForObject( - "/orders?customerId=customer-1&email=customer-1@example.com&amount=99.95", - null, - String.class); - assertThat(orderId).isNotBlank(); - - // Auto-payment robot confirms shortly after the workflow registers `awaitPayment`, so the - // order should advance through IN_TRANSIT and reach DELIVERED without a manual payment POST. - await().atMost(45, TimeUnit.SECONDS).untilAsserted(() -> { - var status = restTemplate.getForObject("/orders/" + orderId, OrderStatus.class); - assertThat(status).isNotNull(); - assertThat(status.status()).isEqualTo(OrderStatus.Status.DELIVERED); - assertThat(status.trackingNumber()).startsWith("TRK-"); - }); - } } diff --git a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentWorkflowTest.java b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentWorkflowTest.java new file mode 100644 index 00000000..e7942feb --- /dev/null +++ b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/OrderFulfillmentWorkflowTest.java @@ -0,0 +1,175 @@ +package io.axoniq.demo.orderfulfillment; + +import io.axoniq.demo.orderfulfillment.api.OrderPlaced; +import io.axoniq.demo.orderfulfillment.api.PaymentConfirmed; +import io.axoniq.demo.orderfulfillment.service.InventoryService; +import io.axoniq.demo.orderfulfillment.service.NotificationService; +import io.axoniq.demo.orderfulfillment.service.PaymentService; +import io.axoniq.demo.orderfulfillment.service.ShippingService; +import io.axoniq.demo.orderfulfillment.workflow.OrderFulfillmentWorkflow; +import io.axoniq.framework.axonserver.connector.configuration.AxonServerConfigurationEnhancer; +import io.axoniq.framework.workflow.configuration.WorkflowModule; +import io.axoniq.framework.workflow.dsl.api.StepStatus; +import io.axoniq.framework.workflow.dsl.api.StepFailedException; +import io.axoniq.framework.workflow.dsl.api.WorkflowStatus; +import io.axoniq.framework.workflow.dsl.simple.SimpleWorkflowContext; +import io.axoniq.framework.workflow.runtime.test.fixture.GivenWhen; +import io.axoniq.framework.workflow.runtime.test.fixture.Then; +import io.axoniq.framework.workflow.runtime.test.fixture.WorkflowTestFixture; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +class OrderFulfillmentWorkflowTest { + + private WorkflowTestFixture fixture; + + @BeforeEach + void setUp() { + var workflow = new OrderFulfillmentWorkflow(new InventoryService(), new PaymentService(), + new ShippingService(), new NotificationService()); + var module = WorkflowModule.defaults("OrderFulfillment", SimpleWorkflowContext.class) + .definition(d -> d.autodetected(c -> workflow)); + fixture = WorkflowTestFixture.of(module, configurer -> configurer.componentRegistry( + registry -> registry.disableEnhancer(AxonServerConfigurationEnhancer.class))); + } + + @AfterEach + void tearDown() { + if (fixture != null) { + fixture.then().stop(); + } + } + + @Test + void confirmedPaymentShipsAndNotifiesCustomer() { + fixture.given().publishEvent(order("happy")) + .execute("reserveStock") + .execute("initiatePayment"); + fixture.then().workflowNotFinished().waitingIn("awaitPayment") + .noStep("shipOrder").noStep("notifyCustomer"); + + fixture.when().publishEvent(new PaymentConfirmed("order-1", "txn-1")) + .execute("shipOrder") + .execute("notifyCustomer"); + + fixture.then().workflowFinished(WorkflowStatus.COMPLETED) + .step("awaitPayment", StepStatus.COMPLETED) + .stepsPassed("reserveStock", "initiatePayment", "shipOrder", "notifyCustomer") + .noStep("recordPaymentTimeout"); + } + + @Test + void paymentArrivingBeforePaymentInitiationIsRetained() { + fixture.given().publishEvent(order("happy")); + fixture.then().executionExists().waitingIn("awaitPayment", "reserveStock"); + + fixture.when().publishEvent(new PaymentConfirmed("order-1", "txn-early")) + .execute("reserveStock"); + fixture.then().workflowNotFinished() + .step("awaitPayment", StepStatus.COMPLETED) + .waitingIn("initiatePayment") + .noStep("shipOrder").noStep("notifyCustomer"); + + fixture.when().execute("initiatePayment") + .execute("shipOrder") + .execute("notifyCustomer"); + + fixture.then().workflowFinished(WorkflowStatus.COMPLETED) + .step("awaitPayment", StepStatus.COMPLETED); + } + + @Test + void failedInitiationCancelsConfirmationWaitAndPreventsShipping() { + fixture.given().publishEvent(order("happy")).execute("reserveStock"); + + fixture.when().executeFailing("initiatePayment", new StepFailedException("Payment provider unavailable")); + + fixture.then().workflowFinished(WorkflowStatus.FAILED) + .step("initiatePayment", StepStatus.FAILED) + .step("awaitPayment", StepStatus.CANCELLED) + .step("recordPaymentFailure", StepStatus.COMPLETED) + .noStep("shipOrder").noStep("notifyCustomer"); + } + + @Test + void earlyConfirmationCannotHideFailedInitiation() { + fixture.given().publishEvent(order("happy")).execute("reserveStock"); + fixture.when().publishEvent(new PaymentConfirmed("order-1", "txn-early")); + fixture.then().step("awaitPayment", StepStatus.COMPLETED).noStep("shipOrder"); + + fixture.when().executeFailing("initiatePayment", new StepFailedException("Payment provider unavailable")); + + fixture.then().workflowFinished(WorkflowStatus.FAILED) + .step("awaitPayment", StepStatus.COMPLETED) + .step("initiatePayment", StepStatus.FAILED) + .step("recordPaymentFailure", StepStatus.COMPLETED) + .noStep("shipOrder").noStep("notifyCustomer"); + } + + @Test + void confirmationTimeoutCancelsPendingInitiation() { + fixture.given().publishEvent(order("payment-timeout")).execute("reserveStock"); + + fixture.when().timePasses(Duration.ofSeconds(5)); + + fixture.then().workflowFinished(WorkflowStatus.FAILED) + .step("awaitPayment", StepStatus.TIMED_OUT) + .step("initiatePayment", StepStatus.CANCELLED) + .step("recordPaymentTimeout", StepStatus.COMPLETED) + .noStep("shipOrder").noStep("notifyCustomer"); + } + + @Test + void initiationTimeoutPreventsShippingEvenAfterConfirmation() { + fixture.given().publishEvent(order("happy")).execute("reserveStock"); + fixture.when().publishEvent(new PaymentConfirmed("order-1", "txn-early")); + fixture.then().step("awaitPayment", StepStatus.COMPLETED).noStep("shipOrder"); + + fixture.when().timePasses(Duration.ofSeconds(31)); + + fixture.then().workflowFinished(WorkflowStatus.FAILED) + .step("awaitPayment", StepStatus.COMPLETED) + .step("initiatePayment", StepStatus.TIMED_OUT) + .step("recordPaymentFailure", StepStatus.COMPLETED) + .noStep("shipOrder").noStep("notifyCustomer"); + } + + @Test + void paymentForAnotherOrderDoesNotPreventTimeout() { + fixture.given().publishEvent(order("payment-timeout")) + .execute("reserveStock") + .execute("initiatePayment"); + fixture.then().executionExists().waitingIn("awaitPayment"); + + fixture.when().publishEvent(new PaymentConfirmed("another-order", "txn-unrelated")) + .timePasses(Duration.ofSeconds(5)); + + fixture.then().workflowFinished(WorkflowStatus.FAILED) + .step("awaitPayment", StepStatus.TIMED_OUT) + .step("recordPaymentTimeout", StepStatus.COMPLETED) + .noStep("shipOrder") + .noStep("notifyCustomer"); + } + + @Test + void unavailableStockCancelsPaymentWaitAndFailsOrder() { + fixture.given().publishEvent(order("out-of-stock")); + + fixture.when().execute("reserveStock"); + + fixture.then().workflowFinished(WorkflowStatus.FAILED) + .step("awaitPayment", StepStatus.CANCELLED) + .step("recordOutOfStock", StepStatus.COMPLETED) + .noStep("initiatePayment") + .noStep("shipOrder"); + } + + private static OrderPlaced order(String scenario) { + return new OrderPlaced("order-1", "customer-1", "customer@example.com", 99.95, + "New York", 40.7128, -74.0060, + "Boston", 42.3601, -71.0589, scenario); + } +} diff --git a/order-fulfillment-workflow/src/test/js/workflow-trace.test.mjs b/order-fulfillment-workflow/src/test/js/workflow-trace.test.mjs new file mode 100644 index 00000000..9cf8942a --- /dev/null +++ b/order-fulfillment-workflow/src/test/js/workflow-trace.test.mjs @@ -0,0 +1,239 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { buildTrace, unionDuration } from "../../main/resources/static/workflow-trace.mjs"; +const start = Date.parse("2026-10-05T09:00:00Z"); +const workflow = { startedAt: new Date(start).toISOString(), status: "COMPLETED" }; +const event = (sequence, ms, step, status, primitive) => ({ + sequence, + timestamp: new Date(start + ms).toISOString(), + step, + status, + metadata: step ? { stepPrimitive: primitive } : { workflowStatus: status }, +}); + +test("overlapping waits use recorded starts, count once, and stop on workflow completion", () => { + const trace = buildTrace( + workflow, + [ + event(1, 0, null, "STARTED"), + event(2, 10, "wait", "STARTED", "WAIT_FOR_EVENT"), + event(3, 20, "action", "STARTED"), + event(4, 30, "action", "COMPLETED"), + event(5, 50, "secondWait", "STARTED", "WAIT_FOR_EVENT"), + event(6, 100, "wait", "COMPLETED"), + event(7, 150, "secondWait", "COMPLETED"), + event(8, 160, null, "COMPLETED"), + ], + start + 5000, + ); + assert.equal(trace.duration, 160); + assert.equal(trace.rows[0].start, start + 10); + assert.equal(trace.rows[1].duration, 10); + assert.equal(trace.waitDuration, 140); + assert.equal( + unionDuration([ + [10, 20], + [15, 25], + [30, 40], + ]), + 25, + ); +}); +test("live spans grow, while recorded playback hides future events and results", () => { + const events = [ + event(1, 0, null, "STARTED"), + event(2, 10, "wait", "STARTED", "WAIT_FOR_EVENT"), + event(3, 100, "wait", "COMPLETED"), + event(4, 110, null, "COMPLETED"), + ]; + const playback = buildTrace(workflow, events, start + 1000, start + 50); + assert.equal(playback.status, "STARTED"); + assert.equal(playback.events.length, 2); + assert.equal(playback.rows[0].duration, 40); + assert.equal(playback.rows[0].spans[0].active, true); + const live = buildTrace({ ...workflow, status: "STARTED" }, events.slice(0, 2), start + 500); + assert.equal(live.rows[0].duration, 490); +}); +test("publish without start remains a point; timeout and cancellation close waits", () => { + for (const status of ["TIMED_OUT", "CANCELLED"]) { + const trace = buildTrace( + workflow, + [ + event(1, 0, null, "STARTED"), + event(2, 5, "wait", "STARTED", "WAIT_FOR_EVENT"), + event(3, 100, "wait", status), + event(4, 105, "recordFailure", "COMPLETED", "PUBLISH"), + event(5, 110, null, "FAILED"), + ], + start + 10000, + ); + assert.equal(trace.rows[0].duration, 95); + assert.equal(trace.rows[0].spans[0].active, false); + assert.equal(trace.rows[1].spans[0].instant, true); + assert.equal(trace.rows[1].duration, 0); + } +}); +test("retries are separate spans rather than including backoff in action duration", () => { + const trace = buildTrace( + workflow, + [ + event(1, 0, null, "STARTED"), + event(2, 10, "action", "STARTED"), + event(3, 20, "action", "RETRYING"), + event(4, 80, "action", "RETRY_STARTED"), + event(5, 100, "action", "COMPLETED"), + event(6, 110, null, "COMPLETED"), + ], + start + 1000, + ); + assert.equal(trace.rows[0].spans.length, 2); + assert.equal(trace.rows[0].duration, 30); + assert.equal(trace.rows[0].spans[0].status, "RETRYING"); +}); + +test("replay rebuilds pending, running, completed and output state in both directions", async () => { + const { replayTrace, inspectTrace } = await import("../../main/resources/static/workflow-trace.mjs"); + const history = [ + { ...event(1, 0, null, "STARTED"), payload: { orderId: "one" } }, + { ...event(2, 10, "reserveStock", "STARTED"), payload: { amount: 20 } }, + { ...event(3, 11, "reserveStock", "COMPLETED"), payload: { reserved: true } }, + { ...event(4, 12, "shipOrder", "STARTED"), payload: { orderId: "one" } }, + { ...event(5, 13, "shipOrder", "COMPLETED"), payload: { trackingNumber: "tracking-1" } }, + event(6, 14, null, "COMPLETED"), + ]; + for (const cursor of [0, 2, 3, 6, 2, 0]) { + const trace = replayTrace(workflow, history, cursor); + assert.equal(trace.events.length, cursor); + assert.equal(trace.rows.length, 2); + const reserve = inspectTrace(trace, "reserveStock"); + assert.equal(reserve.status, cursor < 2 ? "PENDING" : cursor < 3 ? "STARTED" : "COMPLETED"); + assert.deepEqual(reserve.input, cursor < 2 ? undefined : { amount: 20 }); + assert.deepEqual(reserve.output, cursor < 3 ? undefined : { reserved: true }); + const shipping = inspectTrace(trace, "shipOrder"); + assert.equal(shipping.status, cursor < 4 ? "PENDING" : "COMPLETED"); + assert.deepEqual(inspectTrace(trace, "@root").input, cursor ? { orderId: "one" } : undefined); + assert.equal(trace.status, cursor === 0 ? "PENDING" : cursor === 6 ? "COMPLETED" : "STARTED"); + if (cursor < 5) assert.equal(inspectTrace(trace, "@root").output.shipOrder, undefined); + } +}); + +test("event cursor distinguishes events with identical timestamps and sparse sequences", async () => { + const { replayTrace } = await import("../../main/resources/static/workflow-trace.mjs"); + const history = [ + event(10, 0, null, "STARTED"), + event(20, 5, "action", "STARTED"), + event(40, 5, "action", "COMPLETED"), + event(50, 5, null, "COMPLETED"), + ]; + assert.equal(replayTrace(workflow, history, 2.8).rows[0].status, "STARTED"); + assert.equal(replayTrace(workflow, history, 3).rows[0].status, "COMPLETED"); + assert.equal(replayTrace(workflow, history, 3).status, "STARTED"); + assert.equal(replayTrace(workflow, history, 4).status, "COMPLETED"); +}); + +test("rewinding a failure hides its result and exposes retrying only when recorded", async () => { + const { replayTrace, inspectTrace } = await import("../../main/resources/static/workflow-trace.mjs"); + const history = [ + event(1, 0, null, "STARTED"), + event(2, 10, "action", "STARTED"), + event(3, 20, "action", "RETRYING"), + event(4, 40, "action", "RETRY_STARTED"), + { ...event(5, 50, "action", "FAILED"), payload: { message: "Unavailable" } }, + event(6, 60, null, "FAILED"), + ]; + assert.equal(inspectTrace(replayTrace(workflow, history, 3), "action").status, "RETRYING"); + assert.deepEqual(inspectTrace(replayTrace(workflow, history, 5), "action").output, { message: "Unavailable" }); + assert.equal(inspectTrace(replayTrace(workflow, history, 4), "action").output, undefined); + assert.equal(inspectTrace(replayTrace(workflow, history, 0), "action").status, "PENDING"); +}); + +test("BPMN branch states follow the same replay cursor and skip unvisited branches only at the end", async () => { + const { replayTrace } = await import("../../main/resources/static/workflow-trace.mjs"); + const { bpmnStates } = await import("../../main/resources/static/workflow-bpmn.mjs"); + const history = [ + event(1, 0, null, "STARTED"), + event(2, 10, "awaitPayment", "STARTED", "WAIT_FOR_EVENT"), + event(3, 20, "reserveStock", "STARTED"), + { ...event(4, 30, "reserveStock", "COMPLETED"), payload: { reserved: false } }, + event(5, 35, "awaitPayment", "CANCELLED"), + event(6, 40, "recordOutOfStock", "COMPLETED", "PUBLISH"), + event(7, 45, null, "FAILED"), + ]; + const at = (cursor) => bpmnStates(replayTrace(workflow, history, cursor)); + assert.equal(at(0).reserveStock, "PENDING"); + assert.equal(at(3).reserveStock, "STARTED"); + assert.equal(at(3).f_stock_no, "PENDING"); + assert.equal(at(4).f_stock_no, "COMPLETED"); + assert.equal(at(4).f_stock_yes, "PENDING"); + assert.equal(at(5).awaitPayment, "CANCELLED"); + assert.equal(at(5).PaymentError, "PENDING"); // Stock cancellation must not take the payment-error branch. + assert.equal(at(6).shipOrder, "PENDING"); + assert.equal(at(7).shipOrder, "SKIPPED"); + assert.equal(at(7).StockFailed, "FAILED"); + assert.equal(at(2).recordOutOfStock, "PENDING"); +}); + +test("BPMN join waits for payment and initiation; timeout takes only the timeout branch", async () => { + const { replayTrace } = await import("../../main/resources/static/workflow-trace.mjs"); + const { bpmnStates } = await import("../../main/resources/static/workflow-bpmn.mjs"); + const history = [ + event(1, 0, null, "STARTED"), + event(2, 10, "awaitPayment", "STARTED", "WAIT_FOR_EVENT"), + event(3, 20, "initiatePayment", "STARTED"), + event(4, 30, "initiatePayment", "COMPLETED"), + event(5, 50, "awaitPayment", "COMPLETED"), + event(6, 60, "shipOrder", "STARTED"), + ]; + assert.equal(bpmnStates(replayTrace(workflow, history, 4)).PaymentJoined, "STARTED"); + assert.equal(bpmnStates(replayTrace(workflow, history, 5)).PaymentJoined, "COMPLETED"); + assert.equal(bpmnStates(replayTrace(workflow, history, 4)).shipOrder, "PENDING"); + const timeout = [ + ...history.slice(0, 4), + event(5, 50, "awaitPayment", "TIMED_OUT"), + event(6, 60, "recordPaymentTimeout", "COMPLETED", "PUBLISH"), + event(7, 70, null, "FAILED"), + ]; + const failed = bpmnStates(replayTrace(workflow, timeout, 7)); + assert.equal(failed.awaitPayment, "TIMED_OUT"); + assert.equal(failed.f_timeout, "COMPLETED"); + assert.equal(failed.PaymentTimedOut, "FAILED"); + assert.equal(failed.f_paid, "SKIPPED"); + assert.equal(failed.shipOrder, "SKIPPED"); + assert.equal(bpmnStates(replayTrace(workflow, timeout, 4)).PaymentDeadline, "PENDING"); +}); + +test("BPMN routes initiation failure without treating companion cancellation as another failure", async () => { + const { replayTrace } = await import("../../main/resources/static/workflow-trace.mjs"); + const { bpmnStates } = await import("../../main/resources/static/workflow-bpmn.mjs"); + for (const confirmationStatus of ["COMPLETED", "CANCELLED"]) { + const history = [ + event(1, 0, null, "STARTED"), + event(2, 10, "awaitPayment", "STARTED", "WAIT_FOR_EVENT"), + event(3, 20, "initiatePayment", "STARTED"), + event(4, 30, "initiatePayment", "FAILED"), + event(5, 40, "awaitPayment", confirmationStatus), + event(6, 50, "recordPaymentFailure", "COMPLETED", "PUBLISH"), + event(7, 60, null, "FAILED"), + ]; + const states = bpmnStates(replayTrace(workflow, history, 7)); + assert.equal(states.PaymentInitiationError, "FAILED"); + assert.equal(states.f_initiation_error, "COMPLETED"); + assert.equal(states.PaymentError, "SKIPPED"); + assert.equal(states.PaymentJoined, "SKIPPED"); + assert.equal(states.shipOrder, "SKIPPED"); + assert.equal(bpmnStates(replayTrace(workflow, history, 3)).PaymentInitiationError, "PENDING"); + } + const timeout = [ + event(1, 0, null, "STARTED"), + event(2, 10, "awaitPayment", "STARTED", "WAIT_FOR_EVENT"), + event(3, 20, "initiatePayment", "STARTED"), + event(4, 30, "awaitPayment", "TIMED_OUT"), + event(5, 40, "initiatePayment", "CANCELLED"), + event(6, 50, "recordPaymentTimeout", "COMPLETED", "PUBLISH"), + event(7, 60, null, "FAILED"), + ]; + const states = bpmnStates(replayTrace(workflow, timeout, 7)); + assert.equal(states.PaymentDeadline, "TIMED_OUT"); + assert.equal(states.PaymentInitiationError, "SKIPPED"); + assert.equal(states.f_initiation_error, "SKIPPED"); +}); From f8cdf7eac1559936e6f6ab59512547b8f5cb79f3 Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Mon, 5 Oct 2026 16:01:14 +0200 Subject: [PATCH 2/3] Fix workflow history interceptor scope and connector dependencies --- order-fulfillment-workflow/README.md | 7 ++++- order-fulfillment-workflow/pom.xml | 15 ++++++----- .../config/WorkflowHistoryConfig.java | 4 ++- .../JdbcWorkflowHistoryRepository.java | 9 ++++--- .../AbstractOrderFulfillmentTest.java | 27 ++++++++++++++++++- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/order-fulfillment-workflow/README.md b/order-fulfillment-workflow/README.md index b43a3188..f073001c 100644 --- a/order-fulfillment-workflow/README.md +++ b/order-fulfillment-workflow/README.md @@ -144,7 +144,7 @@ WorkflowModule.defaults("OrderFulfillment", SimpleWorkflowContext.class) .tokenStore(historyTokens) .initialSegmentCount(1) .batchSize(1) - .withInterceptor(history)); + .withInterceptor(history::interceptOnHandle)); ``` `axon.workflow.enabled=false` disables automatic workflow module registration; @@ -158,6 +158,11 @@ receives the resulting state. It stores the event and a UI document in one H2 transaction. It uses no business `@EventHandler` methods and does not subclass the projector. +The interceptor is registered only on the history processor. It is a method +reference, not an interceptor bean: Spring would register an interceptor bean +globally, allowing history's duplicate checks to suppress events needed by the +workflow engine, projections, and simulators. + The event journal has two purposes: it supplies the inspection timeline, and it restores the framework's actual state for further projection after a restart. The projector requires `EventSourcedWorkflowState`; it cannot continue diff --git a/order-fulfillment-workflow/pom.xml b/order-fulfillment-workflow/pom.xml index 6782b4f6..9d7feba5 100644 --- a/order-fulfillment-workflow/pom.xml +++ b/order-fulfillment-workflow/pom.xml @@ -23,13 +23,6 @@ - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - org.axonframework axon-framework-bom @@ -44,6 +37,14 @@ pom import + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + org.testcontainers testcontainers-bom diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java index e9cb5af3..f136a0a8 100644 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/config/WorkflowHistoryConfig.java @@ -47,6 +47,8 @@ WorkflowModule orderFulfillmentModule( .tokenStore(historyTokens) .initialSegmentCount(1) .batchSize(1) - .withInterceptor(history)); + // A method reference keeps this interceptor local to the history processor. + // An interceptor bean would be auto-registered on every event processor. + .withInterceptor(history::interceptOnHandle)); } } diff --git a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java index 851a5273..1dbfa8e8 100644 --- a/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java +++ b/order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/history/JdbcWorkflowHistoryRepository.java @@ -12,7 +12,6 @@ import io.axoniq.framework.workflow.query.api.WorkflowStateQuery; import io.axoniq.framework.workflow.query.utils.WorkflowStateQueryMatcher; import io.axoniq.framework.workflow.runtime.util.MetadataUtils; -import org.axonframework.messaging.core.MessageHandlerInterceptor; import org.axonframework.messaging.core.MessageHandlerInterceptorChain; import org.axonframework.messaging.core.MessageStream; import org.axonframework.messaging.core.Metadata; @@ -50,8 +49,7 @@ * Stored events also reconstruct the framework's workflow state so history * projection can continue after an application restart. */ -public class JdbcWorkflowHistoryRepository - implements MutableWorkflowHistoryRepository, MessageHandlerInterceptor { +public class JdbcWorkflowHistoryRepository implements MutableWorkflowHistoryRepository { private final JdbcTemplate jdbc; private final TransactionTemplate transactions; @@ -98,7 +96,10 @@ public void initialize(UnitOfWorkFactory unitOfWorkFactory) { this.unitOfWorkFactory = unitOfWorkFactory; } - @Override + /** + * Wraps history projection in a transaction and ignores events already stored in the journal. + * Register this method only on the history processor; other processors must still receive those events. + */ public MessageStream interceptOnHandle(EventMessage event, ProcessingContext context, MessageHandlerInterceptorChain chain) { if (!MetadataUtils.hasWorkflowId().test(event.metadata())) { diff --git a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java index 219f6423..d6ae84de 100644 --- a/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java +++ b/order-fulfillment-workflow/src/test/java/io/axoniq/demo/orderfulfillment/AbstractOrderFulfillmentTest.java @@ -5,9 +5,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.resttestclient.TestRestTemplate; -import java.util.concurrent.TimeUnit; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -36,6 +37,30 @@ void orderIsDeliveredAfterAutomaticPayment() { assertWorkflowHistory(orderId, "COMPLETED", "shipOrder"); } + @Test + void concurrentOrdersCompleteWithTheirOwnShipmentAndHistory() { + var orderIds = IntStream.range(0, 8) + .mapToObj(i -> restTemplate.postForObject( + "/orders?customerId=concurrent-" + i + "&email=customer@example.com&amount=99.95", + null, String.class)) + .toList(); + assertThat(orderIds).doesNotContainNull().doesNotHaveDuplicates(); + + await().atMost(45, TimeUnit.SECONDS).untilAsserted(() -> { + var shipments = orderIds.stream().map(orderId -> { + var status = restTemplate.getForObject("/orders/" + orderId, OrderStatus.class); + assertThat(status).as("Order %s projection", orderId).isNotNull(); + assertThat(status.status()).as("Order %s", orderId).isEqualTo(OrderStatus.Status.DELIVERED); + assertThat(status.trackingNumber()).as("Order %s tracking number", orderId).startsWith("TRK-"); + assertThat(restTemplate.getForObject("/api/workflows/" + orderId, Map.class)) + .containsEntry("status", "COMPLETED") + .containsEntry("workflowId", orderId); + return status.trackingNumber(); + }).toList(); + assertThat(shipments).doesNotHaveDuplicates(); + }); + } + @Test void unavailableStockIsProjectedAsFailed() { assertFailure("out-of-stock", "Out of stock"); From 8d1bb972d9d066759a6a01b79315c087a94b0aae Mon Sep 17 00:00:00 2001 From: Stefan <91stefan@gmail.com> Date: Mon, 5 Oct 2026 16:11:04 +0200 Subject: [PATCH 3/3] Align workflow saga Protobuf runtime with Axon Server connector --- workflow-saga/pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/workflow-saga/pom.xml b/workflow-saga/pom.xml index 75c8ea16..f66dd8c2 100644 --- a/workflow-saga/pom.xml +++ b/workflow-saga/pom.xml @@ -20,11 +20,20 @@ 5.3.2 0.2.0 4.1.1 + 4.36.2 2.0.5 + + + com.google.protobuf + protobuf-bom + ${protobuf.version} + pom + import + org.springframework.boot spring-boot-dependencies