Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public class BpmnViewerFragment extends Fragment<Div> {
protected ViewerMode mode;
protected BpmnViewer bpmnViewer;

@Subscribe(target = Target.HOST_CONTROLLER)
public void onHostInit(final View.InitEvent event) {
onInit();
}

@Subscribe(target = Target.HOST_CONTROLLER)
public void onHostBeforeShow(final View.BeforeShowEvent event) {
if (!noBorders) {
Expand Down Expand Up @@ -103,7 +108,7 @@ public ViewerMode getMode() {
}

public void initViewer(String bpmnXml) {
this.bpmnViewer = uiComponents.create(BpmnViewer.class);
this.bpmnViewer = createBpmnViewer();
this.bpmnViewer.setBpmnXml(bpmnXml);
this.bpmnViewer.setMode(mode);

Expand Down Expand Up @@ -274,4 +279,12 @@ public void showCalledProcessOverlays() {
});
}
}

protected void onInit() {

}

protected BpmnViewer createBpmnViewer() {
return uiComponents.create(BpmnViewer.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ class FlowsetBpmnViewer extends LitElement {
});
}

public addTransactionBoundaries() {
this.awaitRun(() => {
this.overlayManager.addTransactionBoundaryOverlays();
});
}

public setTransactionBoundariesVisible(visible: boolean) {
this.awaitRun(() => {
this.overlayManager.updateOverlaysVisibility(OverlayType.TRANSACTION_BOUNDARY, visible);
});
}

public addMarker(cmdJson: string) {
const cmd: AddMarkerCmd = JSON.parse(cmdJson);
this.awaitRun(() => this.canvas.addMarker(cmd.elementId, cmd.marker));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@ import BpmnViewer from "../bpm/js/BpmnViewer";
import ElementRegistry from 'diagram-js/lib/core/ElementRegistry';
import Canvas from 'diagram-js/lib/core/Canvas';
import {createNavigationOverlay} from "./createNavigationOverlay";
import {getMessage, isMessageSupported} from "../utils/bpmnEventUtils";
import {getMessage, isMessageSupported} from "../utils/eventDefinitionUtils";
import {createSendMessageOverlay} from "./createSendMessageOverlay";
import {getBinding, getCalledElement, getVersion, getVersionTag} from "../utils/callActivityUtils";
import {ElementLike} from "diagram-js/lib/model/Types";
import {ElementLike, Shape} from "diagram-js/lib/model/Types";
import {findElementDocumentation} from "../utils/documentationUtils";
import {createActivityStatisticsOverlay} from "./createActivityStatisticsOverlay";
import {getElementTransactionBoundary} from "../utils/transactionBoundaryUtils";
import {forEach} from 'min-dash';
import {createTransactionBoundaryOverlay} from "./createTransactionBoundaryOverlay";
import {BeforeElementTransactionType, ElementTransactionBoundary} from "../types";
import {DEFAULT_LABEL_SIZE} from "bpmn-js/lib/util/LabelUtil";
import {Point, Rect} from "diagram-js/lib/util/Types";

/**
* OverlayManager class manages various overlays associated with BPMN diagram elements,
Expand All @@ -51,7 +57,10 @@ export class OverlayManager {
* @param data overlay data
*/
public showIncidentOverlay(data: IncidentOverlayData) {
const incidentOverlay = createIncidentOverlay({tooltipMessage : data.tooltipMessage, incidentCount : data.incidentCount});
const incidentOverlay = createIncidentOverlay({
tooltipMessage: data.tooltipMessage,
incidentCount: data.incidentCount
});
this.overlays.add(data.elementId, OverlayType.INCIDENT_COUNT, incidentOverlay);
}

Expand Down Expand Up @@ -117,7 +126,10 @@ export class OverlayManager {
const handleOverlayClick = () => {
handleClick(data.decisionInstanceId);
}
const decisionInstanceOverlay = createNavigationOverlay({title : data.tooltipMessage, handleClick : handleOverlayClick});
const decisionInstanceOverlay = createNavigationOverlay({
title: data.tooltipMessage,
handleClick: handleOverlayClick
});

this.overlays.add(element.id, OverlayType.DECISION_INSTANCE, decisionInstanceOverlay);
}
Expand Down Expand Up @@ -148,7 +160,10 @@ export class OverlayManager {
}

const overlayTooltip = `${data.tooltipMessage}: ${message.get("name")}`;
const sendMessageOverlay: OverlayAttrs = createSendMessageOverlay({title : overlayTooltip, handleClick : handleOverlayClick});
const sendMessageOverlay: OverlayAttrs = createSendMessageOverlay({
title: overlayTooltip,
handleClick: handleOverlayClick
});
this.overlays.add(element.id, OverlayType.SEND_MESSAGE, sendMessageOverlay);
}
});
Expand All @@ -167,7 +182,10 @@ export class OverlayManager {
handleClick(details);
}

const calledInstancesOverlay = createNavigationOverlay({title : data.tooltipMessage, handleClick : handleOverlayClick});
const calledInstancesOverlay = createNavigationOverlay({
title: data.tooltipMessage,
handleClick: handleOverlayClick
});

this.overlays.add(data.elementId, OverlayType.CALLED_PROCESS_INSTANCE, calledInstancesOverlay);
}
Expand Down Expand Up @@ -197,7 +215,10 @@ export class OverlayManager {
}

const tooltipMessage = `${data.tooltipMessage} (${calledElement})`;
const calledProcessOverlay = createNavigationOverlay({title : tooltipMessage, handleClick : handleOverlayClick});
const calledProcessOverlay = createNavigationOverlay({
title: tooltipMessage,
handleClick: handleOverlayClick
});

this.overlays.add(element.id, OverlayType.CALLED_PROCESS, calledProcessOverlay);
}
Expand Down Expand Up @@ -227,6 +248,111 @@ export class OverlayManager {
this.overlays.add(data.elementId, OverlayType.ACTIVITY_STATISTICS, createActivityStatisticsOverlay(data));
}

/**
* Adds overlays for the transaction boundary (before/after) for all elements.
*/
public addTransactionBoundaryOverlays() {
this.overlays.remove({type: OverlayType.TRANSACTION_BOUNDARY});

const elements: ElementLike[] = this.elementRegistry.filter(element => {
return element.type !== 'label';
});

elements.forEach((shape: ElementLike) => {
const transactionBoundary: ElementTransactionBoundary = getElementTransactionBoundary(shape);

if (!transactionBoundary) {
return;
}

const addIncomingTransactionBoundaryOverlay = (type: BeforeElementTransactionType) => {
const incoming = shape.incoming || [];
const hasIncoming = incoming.length > 0;

if (hasIncoming) {
this.addOverlayForConnections(shape, transactionBoundary, incoming, true, type);
} else {
// no incoming connection, calculate position in the front
this.addTransactionBoundaryOverlay(shape, {
x: shape.x,
y: shape.y + shape.height / 2
}, transactionBoundary, type);
}
};

if (transactionBoundary.engineWaitState) {
addIncomingTransactionBoundaryOverlay(BeforeElementTransactionType.ENGINE_WAIT_STATE)
}

if (transactionBoundary.asyncBefore) {
addIncomingTransactionBoundaryOverlay(BeforeElementTransactionType.ASYNC_BEFORE);
}

if (transactionBoundary.asyncAfter) {
const outgoing = shape.outgoing || [];
const hasOutgoing = outgoing.length > 0;

if (hasOutgoing) {
this.addOverlayForConnections(shape, transactionBoundary, outgoing, false);
} else {
// no outgoing connection, calculate position after the element
this.addTransactionBoundaryOverlay(shape, {
x: shape.x + shape.width,
y: shape.y + shape.height / 2
}, transactionBoundary);
}
}

});
}

/**
* Adds a transaction boundary overlay to a shape element at a specified waypoint.
*
* @param {ElementLike} shape - The shape element to which the transaction boundary overlay will be added.
* @param {Point} waypoint - The waypoint indicating the location of the transaction boundary.
* @param {ElementTransactionBoundary} [transactionBoundary] - Optional parameter representing the transaction boundary element.
* @param {BeforeElementTransactionType} [type] - Required for the transactions before the element.
*/
private addTransactionBoundaryOverlay = (shape: ElementLike, waypoint: Point,
transactionBoundary?: ElementTransactionBoundary, type?: BeforeElementTransactionType): void => {
const rect = {...waypoint};
const overlay = createTransactionBoundaryOverlay({
shape,
waypoint: rect,
transactionBoundary,
beforeType: type
});
this.overlays.add(shape.id, OverlayType.TRANSACTION_BOUNDARY, overlay);
};

/**
* Adds overlays to the provided connections based on transaction boundaries.
*
* @param {ElementLike} shape - The visual object to which the overlays will be added.
* @param {ElementTransactionBoundary} transactionBoundary - The transaction boundary linked to the overlay.
* @param {any[]} connections - An array of connections to process for adding overlays.
* @param {boolean} isIncoming - Specifies whether the connections are incoming or outgoing.
* @return {void} This method does not return a value.
*/
private addOverlayForConnections(shape: ElementLike, transactionBoundary: ElementTransactionBoundary,
connections: any[], isIncoming: boolean, type?: BeforeElementTransactionType): void {
forEach(connections, (connection: any) => {
if (connection.type !== 'bpmn:SequenceFlow') {
return;
}

let waypoint: Rect;
if(isIncoming) {
const lastWaypointIndex = connection.waypoints.length - 1;
waypoint = connection.waypoints[lastWaypointIndex];
} else {
waypoint = connection.waypoints[0];
}
this.addTransactionBoundaryOverlay(shape, waypoint, transactionBoundary, type);
});
}

private shouldRenderSendMessageOverlay(element: ElementLike, data: SendMessageOverlaysData): boolean {
if (data.useActiveEvents) {
const isRunningActivity = this.canvas.hasMarker(element.id, 'running-activity');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) Haulmont 2025. All Rights Reserved.
* Use is subject to license terms.
*/

import {OverlayPosition} from "./types";

import {OverlayAttrs} from "diagram-js/lib/features/overlays/Overlays";
import {BeforeElementTransactionType, ElementTransactionBoundary} from "../types";
import {getOrientation} from "diagram-js/lib/layout/LayoutUtil";
import {ShapeLike} from "bpmn-js/lib/draw/BpmnRenderUtil";

export type TransactionBoundaryOverlayData = {
shape: ShapeLike;
waypoint: any;
beforeType?: BeforeElementTransactionType;
transactionBoundary?: ElementTransactionBoundary;
}

type Orientation =
'left'
| 'right'
| 'top'
| 'bottom'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'intersect';

/**
* Creates an overlay representing a transaction boundary at a given position on a shape.
*
* The function calculates the overlay position based on the relative position of the point and the element.
* For example, in the case of an overlay for a gateway with 3 incoming elements (top/bottom and right):
* 1. For the left connection, there will be a vertical overlay on the left of element
* 2. For the top and bottom connections, there will be a horizontal overlay on the top and bottom of element
*
* @param {TransactionBoundaryOverlayData} config - the configuration object for creating the overlay
* @param {Shape} config.shape - the diagram shape for which the overlay is created.
* @param {Waypoint} config.waypoint - a waypoint used for alignment and orientation.
* @param {string} config.beforeType - the type of transaction if transaction is done before the element.
* @param {TransactionBoundary} config.transactionBoundary - transaction boundary details.
* @returns {OverlayAttrs} The calculated attributes for the overlay, such as HTML structure and position.
*/
export const createTransactionBoundaryOverlay = ({

shape,
waypoint,
beforeType,
transactionBoundary
}: TransactionBoundaryOverlayData): OverlayAttrs => {

let orientation: Orientation = getOrientation(waypoint, shape, -7);

if (orientation === 'intersect') {
// Try again using a bigger padding to get an orientation which is not 'intersect'.
// Otherwise the boundary would not be visible if the connection is attached on the
// diagonal edge of a gateway. Not perfect, but much better than showing no overlay at all.
orientation = getOrientation(waypoint, shape, -20);
}

let strokeWidth = 7; //width of overlay dot line
const offset = 5; //overlay offset from top/right

let margin = 2;

let position: OverlayPosition = {};
let height: number;
let width: number;

if (beforeType === BeforeElementTransactionType.ENGINE_WAIT_STATE && transactionBoundary && transactionBoundary.asyncBefore) {
margin = margin + strokeWidth + 5; //the "waiting state" overlay is always added to the left of the "async before" overlay
}

// if orientation is either 'left', 'top-left' or 'bottom-left'
if (/left/.test(orientation)) {
width = strokeWidth;
height = shape.height + 2 * offset;

// horizontal position: at the left border respecting margin
// vertical position: slightly above the diagram element
position.left = -width - margin;
position.top = -offset;

console.log("left position: ", beforeType, width, margin)

// if orientation is either 'right', 'top-right' or 'bottom-right'
} else if (/right/.test(orientation)) {

width = strokeWidth;
height = shape.height + 2 * offset;

// horizontal position: at the right border respecting margin
// vertical position: slightly above the diagram element
position.right = -margin;
position.top = -offset;

} else if (orientation === 'top') {
width = shape.width;
height = strokeWidth;

// horizontal position: slightly right to the diagram element start
// vertical position: at the top border respecting margin
position.left = -offset;
position.top = -offset - margin;

} else if (orientation === 'bottom') {
width = shape.width;
height = strokeWidth;

// horizontal position: slightly right to the diagram element start
// vertical position: at the bottom border respecting margin
position.bottom = -margin;
position.left = -offset;
}

const isHorizontalOverlay = orientation === 'top' || orientation === 'bottom';
const className = isHorizontalOverlay ? 'transaction-boundary-horizontal-overlay' :
'transaction-boundary-vertical-overlay';

const additionalClassName = beforeType === BeforeElementTransactionType.ENGINE_WAIT_STATE ? 'engine-wait-state' : '';

return {
html: `<div style="width: ${width}px; height: ${height}px" class="${className} ${additionalClassName}"></div>`,
position: position
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum OverlayType {
ACTIVITY_STATISTICS = 'activity-statistics',
INCIDENT_COUNT = 'incident-count',
SEND_MESSAGE = 'send-message',
TRANSACTION_BOUNDARY = 'transaction-boundary'
}

export interface IncidentOverlayData {
Expand Down
Loading