-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add bpmn:InclusiveGateway support
Closes #88
- Loading branch information
Showing
15 changed files
with
615 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
lib/features/context-pads/handler/InclusiveGatewayHandler.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { | ||
ForkIcon | ||
} from '../../../icons'; | ||
|
||
import { getBusinessObject } from '../../../util/ElementHelper'; | ||
import { isSequenceFlow } from '../../../simulator/util/ModelUtil'; | ||
|
||
export default function InclusiveGatewayHandler(inclusiveGatewaySettings) { | ||
this._inclusiveGatewaySettings = inclusiveGatewaySettings; | ||
} | ||
|
||
InclusiveGatewayHandler.prototype.createContextPads = function(element) { | ||
const outgoingFlows = element.outgoing.filter(isSequenceFlow); | ||
|
||
if (outgoingFlows.length < 2) { | ||
return; | ||
} | ||
|
||
const nonDefaultFlows = outgoingFlows.filter(outgoing => { | ||
const flowBo = getBusinessObject(outgoing), | ||
gatewayBo = getBusinessObject(element); | ||
|
||
return gatewayBo.default !== flowBo; | ||
}); | ||
|
||
const html = ` | ||
<div class="bts-context-pad" title="Set Sequence Flow"> | ||
${ForkIcon()} | ||
</div> | ||
`; | ||
|
||
return nonDefaultFlows.map(sequenceFlow => { | ||
const action = () => { | ||
this._inclusiveGatewaySettings.toggleSequenceFlow(element, sequenceFlow); | ||
}; | ||
|
||
return { | ||
action, | ||
element: sequenceFlow, | ||
html | ||
}; | ||
}); | ||
}; | ||
|
||
InclusiveGatewayHandler.$inject = [ | ||
'inclusiveGatewaySettings' | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,6 @@ import { | |
|
||
|
||
const UNSUPPORTED_ELEMENTS = [ | ||
'bpmn:InclusiveGateway', | ||
'bpmn:ComplexGateway' | ||
]; | ||
|
||
|
139 changes: 139 additions & 0 deletions
139
lib/features/inclusive-gateway-settings/InclusiveGatewaySettings.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { | ||
TOGGLE_MODE_EVENT | ||
} from '../../util/EventHelper'; | ||
|
||
|
||
const SELECTED_COLOR = '--token-simulation-grey-darken-30'; | ||
const NOT_SELECTED_COLOR = '--token-simulation-grey-lighten-56'; | ||
|
||
import { | ||
getBusinessObject, | ||
is, | ||
isSequenceFlow | ||
} from '../../simulator/util/ModelUtil'; | ||
|
||
|
||
export default function InclusiveGatewaySettings( | ||
eventBus, elementRegistry, | ||
elementColors, simulator, simulationStyles) { | ||
|
||
this._elementRegistry = elementRegistry; | ||
this._elementColors = elementColors; | ||
this._simulator = simulator; | ||
this._simulationStyles = simulationStyles; | ||
|
||
eventBus.on(TOGGLE_MODE_EVENT, event => { | ||
if (event.active) { | ||
this.setDefaults(); | ||
} else { | ||
this.reset(); | ||
} | ||
}); | ||
} | ||
|
||
InclusiveGatewaySettings.prototype.setDefaults = function() { | ||
const inclusiveGateways = this._elementRegistry.filter(element => { | ||
return is(element, 'bpmn:InclusiveGateway'); | ||
}); | ||
|
||
inclusiveGateways.forEach(inclusiveGateway => { | ||
if (inclusiveGateway.outgoing.filter(isSequenceFlow).length > 1) { | ||
this._setGatewayDefaults(inclusiveGateway); | ||
} | ||
}); | ||
}; | ||
|
||
InclusiveGatewaySettings.prototype.reset = function() { | ||
const inclusiveGateways = this._elementRegistry.filter(element => { | ||
return is(element, 'bpmn:InclusiveGateway'); | ||
}); | ||
|
||
inclusiveGateways.forEach(inclusiveGateway => { | ||
if (inclusiveGateway.outgoing.filter(isSequenceFlow).length > 1) { | ||
this._resetGateway(inclusiveGateway); | ||
} | ||
}); | ||
}; | ||
|
||
InclusiveGatewaySettings.prototype.toggleSequenceFlow = function(gateway, sequenceFlow) { | ||
const activeOutgoing = this._getActiveOutgoing(gateway), | ||
defaultFlow = getDefaultFlow(gateway); | ||
|
||
let newActiveOutgoing; | ||
if (activeOutgoing.includes(sequenceFlow)) { | ||
newActiveOutgoing = without(activeOutgoing, sequenceFlow); | ||
} else { | ||
newActiveOutgoing = without(activeOutgoing, defaultFlow).concat(sequenceFlow); | ||
} | ||
|
||
if (!newActiveOutgoing.length && defaultFlow) { | ||
return this._setActiveOutgoing(gateway, [ defaultFlow ]); | ||
} | ||
|
||
this._setActiveOutgoing(gateway, newActiveOutgoing); | ||
}; | ||
|
||
InclusiveGatewaySettings.prototype._getActiveOutgoing = function(gateway) { | ||
const { | ||
activeOutgoing | ||
} = this._simulator.getConfig(gateway); | ||
|
||
return activeOutgoing; | ||
}; | ||
|
||
InclusiveGatewaySettings.prototype._setActiveOutgoing = function(gateway, activeOutgoing) { | ||
this._simulator.setConfig(gateway, { activeOutgoing }); | ||
|
||
const sequenceFlows = gateway.outgoing.filter(isSequenceFlow); | ||
|
||
// set colors | ||
sequenceFlows.forEach(outgoing => { | ||
|
||
const style = (!activeOutgoing || activeOutgoing.includes(outgoing)) ? | ||
SELECTED_COLOR : NOT_SELECTED_COLOR; | ||
const stroke = this._simulationStyles.get(style); | ||
|
||
this._elementColors.set(outgoing, { | ||
stroke | ||
}); | ||
}); | ||
}; | ||
|
||
InclusiveGatewaySettings.prototype._setGatewayDefaults = function(gateway) { | ||
const sequenceFlows = gateway.outgoing.filter(isSequenceFlow); | ||
|
||
const defaultFlow = getDefaultFlow(gateway); | ||
const nonDefaultFlows = without(sequenceFlows, defaultFlow); | ||
|
||
this._setActiveOutgoing(gateway, nonDefaultFlows); | ||
}; | ||
|
||
InclusiveGatewaySettings.prototype._resetGateway = function(gateway) { | ||
this._setActiveOutgoing(gateway, undefined); | ||
}; | ||
|
||
InclusiveGatewaySettings.$inject = [ | ||
'eventBus', | ||
'elementRegistry', | ||
'elementColors', | ||
'simulator', | ||
'simulationStyles' | ||
]; | ||
|
||
function getDefaultFlow(gateway) { | ||
const defaultFlow = getBusinessObject(gateway).default; | ||
|
||
if (!defaultFlow) { | ||
return; | ||
} | ||
|
||
return gateway.outgoing.find(flow => { | ||
const flowBo = getBusinessObject(flow); | ||
|
||
return flowBo === defaultFlow; | ||
}); | ||
} | ||
|
||
function without(array, element) { | ||
return array.filter(arrayElement => arrayElement !== element); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import InclusiveGatewaySettings from './InclusiveGatewaySettings'; | ||
import ElementColorsModule from '../element-colors'; | ||
import SimlationStylesModule from '../simulation-styles'; | ||
|
||
export default { | ||
__depends__: [ | ||
ElementColorsModule, | ||
SimlationStylesModule | ||
], | ||
inclusiveGatewaySettings: [ 'type', InclusiveGatewaySettings ] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { | ||
filterSequenceFlows, isSequenceFlow | ||
} from '../util/ModelUtil'; | ||
|
||
|
||
export default function InclusiveGatewayBehavior( | ||
simulator, | ||
activityBehavior) { | ||
|
||
this._simulator = simulator; | ||
this._activityBehavior = activityBehavior; | ||
|
||
simulator.registerBehavior('bpmn:InclusiveGateway', this); | ||
} | ||
|
||
InclusiveGatewayBehavior.prototype.enter = function(context) { | ||
const { | ||
scope, | ||
element | ||
} = context; | ||
|
||
if (this._isGuaranteedToJoin(context)) { | ||
return this._join(context); | ||
} | ||
|
||
const { | ||
parent: parentScope | ||
} = scope; | ||
|
||
const sameParentScopes = this._simulator.findScopes(scope => ( | ||
scope.parent === parentScope && scope.element !== element)); | ||
|
||
// There are still some tokens to wait for. | ||
if (this._canReachAnyScope(sameParentScopes, element)) { | ||
return; | ||
} | ||
|
||
this._join(context); | ||
}; | ||
|
||
InclusiveGatewayBehavior.prototype.exit = function(context) { | ||
|
||
const { | ||
element, | ||
scope | ||
} = context; | ||
|
||
// depends on UI to properly configure activeOutgoing for | ||
// each inclusive gateway | ||
|
||
const outgoings = filterSequenceFlows(element.outgoing); | ||
|
||
if (outgoings.length === 1) { | ||
return this._simulator.enter({ | ||
element: outgoings[0], | ||
scope: scope.parent | ||
}); | ||
} | ||
|
||
const { | ||
activeOutgoing | ||
} = this._simulator.getConfig(element); | ||
|
||
if (!activeOutgoing.length) { | ||
throw new Error('no outgoing configured'); | ||
} | ||
|
||
for (const outgoing of activeOutgoing) { | ||
this._simulator.enter({ | ||
element: outgoing, | ||
scope: scope.parent | ||
}); | ||
} | ||
}; | ||
|
||
/** | ||
* Number of tokens waiting at the gateway cannot be higher than the number of incoming flows. | ||
*/ | ||
InclusiveGatewayBehavior.prototype._isGuaranteedToJoin = function(context) { | ||
const elementScopes = this._getElementScopes(context); | ||
|
||
const incomingSequenceFlows = filterSequenceFlows(context.element.incoming); | ||
|
||
return elementScopes.length >= incomingSequenceFlows.length; | ||
}; | ||
|
||
InclusiveGatewayBehavior.prototype._join = function(context) { | ||
const elementScopes = this._getElementScopes(context); | ||
|
||
for (const childScope of elementScopes) { | ||
|
||
if (childScope !== context.scope) { | ||
|
||
// complete joining child scope | ||
this._simulator.destroyScope(childScope.complete(), context.scope); | ||
} | ||
} | ||
|
||
this._simulator.exit(context); | ||
}; | ||
|
||
InclusiveGatewayBehavior.prototype._getElementScopes = function(context) { | ||
const { | ||
element, | ||
parent | ||
} = context; | ||
|
||
return this._simulator.findScopes({ | ||
parent, | ||
element | ||
}); | ||
}; | ||
|
||
InclusiveGatewayBehavior.prototype._canReachAnyScope = function(scopes, currentElement, traversed = new Set()) { | ||
if (traversed.has(currentElement)) { | ||
return false; | ||
} | ||
|
||
if (anyScopeIsOnElement(scopes, currentElement)) { | ||
return true; | ||
} | ||
|
||
if (isSequenceFlow(currentElement)) { | ||
return this._canReachAnyScope(scopes, currentElement.source, traversed); | ||
} | ||
|
||
|
||
const incomingFlows = filterSequenceFlows(currentElement.incoming); | ||
|
||
for (const flow of incomingFlows) { | ||
if (this._canReachAnyScope(scopes, flow, traversed)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
InclusiveGatewayBehavior.$inject = [ | ||
'simulator', | ||
'activityBehavior' | ||
]; | ||
|
||
function anyScopeIsOnElement(scopes, element) { | ||
return scopes.some(scope => scope.element === element); | ||
} |
Oops, something went wrong.