This is an automated email from the ASF dual-hosted git repository.
ptyin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new a0b2d9b78d feature: support autolayout in seata-statemachine-designer
(#6415)
a0b2d9b78d is described below
commit a0b2d9b78dd09011fe7b540e8bd73af48bf3873e
Author: Durgesh Kadwe <[email protected]>
AuthorDate: Sat Mar 16 07:21:34 2024 +0530
feature: support autolayout in seata-statemachine-designer (#6415)
* issue: Resolve issue related to autolayout in seata-statemachine-designer
* Delete all/.factorypath
* Delete console/.factorypath
* Delete integration/motan/.factorypath
* Delete saga/seata-saga-statemachine-designer/package-lock.json
* Delete
seata-spring-autoconfigure/seata-spring-autoconfigure-client/.factorypath
* Delete
seata-spring-autoconfigure/seata-spring-autoconfigure-core/.factorypath
* Delete
seata-spring-autoconfigure/seata-spring-autoconfigure-server/.factorypath
* Delete seata-spring-boot-starter/.factorypath
* Delete saga/seata-saga-statemachine-designer/src/modeling/SagaExporter.js
* Update Edge.js
* Update SagaImporter.js
* Update Node.js
* removed debugger and console.log from SagaImporter.js
* Added SagaExporter.js file
* Revert "Delete saga/seata-saga-statemachine-designer/package-lock.json"
This reverts commit 4e8f56ef7a64738c5fa251bfac4cff7822f2a20e.
* fix eslint problems and revert package-lock.json
* remove extra space lines
* Added literal constant and optimize the code
* fix eslint problems
* use 'is' to substitute conditions
* register 2.x.md
* register 2.x.md
---------
Co-authored-by: Durgesh.Kadwe <[email protected]>
Co-authored-by: ptyin <[email protected]>
---
changes/en-us/2.x.md | 2 +
changes/zh-cn/2.x.md | 2 +
.../.eslintrc.json | 3 +-
.../src/modeling/SagaImporter.js | 115 +++++-
.../src/spec/style/Node.js | 393 +++++++++++++++++++++
.../src/utils/index.js | 8 +
6 files changed, 508 insertions(+), 15 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index d7b8ff8ad9..60602ebc53 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -8,6 +8,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#6169](https://github.com/apache/incubator-seata/pull/6169)] full support
for states in the refactored state machine designer
- [[#6230](https://github.com/apache/incubator-seata/pull/6230)] RocketMQ
transaction are supported
- [[#6326](https://github.com/apache/incubator-seata/pull/6326)] support raft
node metadata sync
+- [[#6415](https://github.com/apache/incubator-seata/pull/6415)] support
autolayout in seata-statemachine-designer
### bugfix:
- [[#6090](https://github.com/apache/incubator-seata/pull/6090)] fix the TCC
aspect exception handling process, do not wrapping the internal call exceptions
@@ -164,5 +165,6 @@ Thanks to these contributors for their code commits. Please
report an unintended
- [saberyjs](https://github.com/SABERYJS)
- [gggyd123](https://github.com/gggyd123)
- [jonasHanhan](https://github.com/jonasHanhan)
+- [Code-breaker1998](https://github.com/Code-breaker1998)
Also, we receive many valuable issues, questions and advices from our
community. Thanks for you all.
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 78a45a9b8e..0720041568 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -8,6 +8,7 @@
- [[#6169](https://github.com/apache/incubator-seata/pull/6169)] 支持新版本状态机设计器
- [[#6230](https://github.com/apache/incubator-seata/pull/6230)] 支持RocketMQ消息事务
- [[#6326](https://github.com/apache/incubator-seata/pull/6326)]
支持raft节点间的元数据同步
+- [[#6415](https://github.com/apache/incubator-seata/pull/6415)] 支持 Saga
设计器的自动布局
### bugfix:
- [[#6090](https://github.com/apache/incubator-seata/pull/6090)]
修复tcc切面异常处理过程,不对内部调用异常做包装处理,直接向外抛出
@@ -166,5 +167,6 @@
- [saberyjs](https://github.com/SABERYJS)
- [gggyd123](https://github.com/gggyd123)
- [jonasHanhan](https://github.com/jonasHanhan)
+- [Code-breaker1998](https://github.com/Code-breaker1998)
同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。
diff --git a/saga/seata-saga-statemachine-designer/.eslintrc.json
b/saga/seata-saga-statemachine-designer/.eslintrc.json
index 8720470c67..e7a4bad512 100644
--- a/saga/seata-saga-statemachine-designer/.eslintrc.json
+++ b/saga/seata-saga-statemachine-designer/.eslintrc.json
@@ -25,6 +25,7 @@
"react/no-deprecated": 0,
"react/react-in-jsx-scope": 0,
"react/jsx-no-bind": 0,
- "no-underscore-dangle": 0
+ "no-underscore-dangle": 0,
+ "no-restricted-syntax": 0
}
}
diff --git a/saga/seata-saga-statemachine-designer/src/modeling/SagaImporter.js
b/saga/seata-saga-statemachine-designer/src/modeling/SagaImporter.js
index 4234136edf..a97c0d88a7 100644
--- a/saga/seata-saga-statemachine-designer/src/modeling/SagaImporter.js
+++ b/saga/seata-saga-statemachine-designer/src/modeling/SagaImporter.js
@@ -41,6 +41,49 @@ function collectWaypoints(edge) {
return null;
}
+function addArrayList(definitions) {
+ const adjList = new Map();
+ forEach(definitions.States, (semantic) => {
+ // Initialize an array to store values for the key
+ adjList.set(semantic, []);
+
+ if (semantic.Next || semantic.CompensateState || semantic.Choices) {
+ const options = [];
+
+ if (semantic.Next) {
+ options.push(semantic.Next);
+ }
+
+ if (semantic.CompensateState) {
+ options.push(semantic.CompensateState);
+ }
+
+ if (semantic.Choices) {
+ semantic.Choices.forEach((option) => options.push(option.Next));
+ }
+
+ const existingValues = adjList.get(semantic);
+ options.forEach((next) => {
+ existingValues.push(definitions.States[next]);
+ });
+ adjList.set(semantic, existingValues);
+ }
+ });
+ return adjList;
+}
+
+function addCatchList(definitions, nodes) {
+ const adjList = new Map();
+ adjList.set(nodes, []);
+ nodes.Catch.forEach((option) => {
+ const { Next } = option;
+ const existingValues = adjList.get(nodes);
+ existingValues.push(definitions.States[Next]);
+ adjList.set(nodes, existingValues);
+ });
+ return adjList;
+}
+
export default function SagaImporter(
sagaFactory,
eventBus,
@@ -76,38 +119,77 @@ SagaImporter.prototype.import = function (definitions) {
const root = this.sagaFactory.create('StateMachine');
root.importJson(definitions);
this.root(root);
-
// Add start state
let start = this.sagaFactory.create('StartState');
+ let stateArrayList = new Map();
start.importJson(definitions);
+ let begin = start;
start = this.add(start);
-
const edges = [];
const catches = [];
forEach(definitions.States, (semantic) => {
const state = this.sagaFactory.create(semantic.Type);
- state.importJson(semantic);
+
+ if (semantic.style === undefined) {
+ stateArrayList = addArrayList(definitions);
+ state.importStates(definitions, semantic, begin, stateArrayList);
+ } else {
+ state.importJson(semantic);
+ begin = state;
+ }
const host = this.add(state);
- if (semantic.edge) {
+
+ if (semantic.edge === undefined) {
+ state.importEdges(definitions, semantic);
+ if (semantic.edge) {
+ edges.push(...Object.values(semantic.edge));
+ }
+ } else {
edges.push(...Object.values(semantic.edge));
}
- if (semantic.catch) {
- const node = this.sagaFactory.create('Catch');
- node.importJson(semantic.catch);
- const source = this.add(node);
+
+ if (semantic.Catch) {
+ let source;
+ if (semantic.catch === undefined) {
+ const node = this.sagaFactory.create('Catch');
+ const catchList = addCatchList(definitions, semantic);
+ node.addCatch(definitions, semantic, catchList, stateArrayList);
+ source = this.add(node);
+ } else {
+ const node = this.sagaFactory.create('Catch');
+ node.importJson(semantic.catch);
+ source = this.add(node);
+ }
+
+ if (semantic.catch.edge === undefined) {
+ state.importCatchesEdges(definitions, semantic);
+ }
if (semantic.catch.edge) {
semantic.Catch.forEach((exceptionMatch) => {
if (semantic.catch.edge[exceptionMatch.Next]) {
semantic.catch.edge[exceptionMatch.Next].Exceptions =
exceptionMatch.Exceptions;
}
});
+
+ this.modeling.updateAttachment(source, host);
+ catches.push({
+ source,
+ edges: Object.values(semantic.catch.edge),
+ });
}
- this.modeling.updateAttachment(source, host);
- catches.push({ source, edges: Object.values(semantic.catch.edge) });
}
});
- // Add start edge
+ if ((definitions.edge === undefined) && (definitions.States)) {
+ start = this.sagaFactory.create('StartState');
+ definitions.edge = {};
+ start.importJsonEdges(definitions);
+ if (definitions.edge) {
+ const startEdge = this.sagaFactory.create('Transition');
+ startEdge.importJson(definitions.edge);
+ this.add(startEdge, { source: start });
+ }
+ }
if (definitions.edge) {
const startEdge = this.sagaFactory.create('Transition');
startEdge.importJson(definitions.edge);
@@ -121,7 +203,10 @@ SagaImporter.prototype.import = function (definitions) {
});
forEach(catches, (oneCatch) => {
- const { source, edges: exceptionMatches } = oneCatch;
+ const {
+ source,
+ edges: exceptionMatches,
+ } = oneCatch;
forEach(exceptionMatches, (semantic) => {
const exceptionMatch = this.sagaFactory.create(semantic.Type);
exceptionMatch.importJson(semantic);
@@ -133,7 +218,10 @@ SagaImporter.prototype.import = function (definitions) {
console.error(error);
}
- this.eventBus.fire('import.done', { error, warnings });
+ this.eventBus.fire('import.done', {
+ error,
+ warnings,
+ });
};
SagaImporter.prototype.root = function (semantic) {
@@ -181,7 +269,6 @@ SagaImporter.prototype.add = function (semantic, attrs =
{}) {
target,
waypoints,
});
- // console.log(elementDefinition);
element = elementFactory.createConnection(elementDefinition);
diff --git a/saga/seata-saga-statemachine-designer/src/spec/style/Node.js
b/saga/seata-saga-statemachine-designer/src/spec/style/Node.js
index dc06861bb1..b872a16fda 100644
--- a/saga/seata-saga-statemachine-designer/src/spec/style/Node.js
+++ b/saga/seata-saga-statemachine-designer/src/spec/style/Node.js
@@ -18,15 +18,408 @@
import { assign } from 'min-dash';
import BaseSpec from '../BaseSpec';
import NodeStyle from './NodeStyle';
+import { is } from '../../utils/index';
// import THUMBNAIL from '../icons/bpmn-icon-service-task.svg';
+const OFFSET_X = 36; const OFFSET_Y = 18; const OFFSET_TARGET_X = 20;
+const DEFAULT_X = 200; const DEFAULT_Y = 200; const OFFSET_TARGET_Y = 40;
+const DEFAULT_WIDTH = 100; const DEFAULT_HEIGHT = 80;
+
export default class Node extends BaseSpec {
style = new NodeStyle();
importJson(json) {
+ if (json.style === undefined) {
+ json.style = {};
+ json.style.bounds = {
+ x: DEFAULT_X,
+ y: DEFAULT_Y,
+ width: OFFSET_X,
+ height: OFFSET_X,
+ };
+ }
assign(this.style.bounds, json.style.bounds);
}
+ isElementPresent(visited, target) {
+ for (const element of visited) {
+ if (element[0] === target[0] && element[1] === target[1]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ addWaypoints(
+ source,
+ target,
+ definitions,
+ type,
+ targetWidth,
+ targetHeight,
+ sourceWidth,
+ sourceHeight,
+ ) {
+ const sourceX = definitions.States[source].style.bounds.x;
+ const sourceY = definitions.States[source].style.bounds.y;
+ const targetX = definitions.States[target].style.bounds.x;
+ const targetY = definitions.States[target].style.bounds.y;
+ let waypoints1 = [];
+ if (type === 'Transition') {
+ waypoints1 = [{
+ x: sourceX + sourceWidth,
+ y: sourceY + (sourceHeight / 2),
+ }, {
+ x: targetX - OFFSET_TARGET_X,
+ y: targetY + (targetHeight / 2),
+ }, {
+ x: targetX,
+ y: targetY + (targetHeight / 2),
+ }];
+ } else if (type === 'Compensation') {
+ waypoints1 = [{
+ x: sourceX + (sourceWidth / 2),
+ y: sourceY + sourceHeight,
+ }, {
+ x: targetX + (targetWidth / 2),
+ y: targetY - OFFSET_TARGET_X,
+ }, {
+ x: targetX + (targetWidth / 2),
+ y: targetY,
+ }];
+ } else if (sourceX === targetX) {
+ waypoints1 = [{
+ x: sourceX + (sourceWidth / 2),
+ y: sourceY + sourceHeight,
+ }, {
+ x: targetX + (targetWidth / 2),
+ y: targetY - OFFSET_TARGET_X,
+ }, {
+ x: targetX + (targetWidth / 2),
+ y: targetY,
+ }];
+ } else if (sourceY === targetY) {
+ waypoints1 = [{
+ x: sourceX + sourceWidth,
+ y: sourceY + (sourceHeight / 2),
+ }, {
+ x: targetX - OFFSET_TARGET_X,
+ y: targetY + (targetHeight / 2),
+ }, {
+ x: targetX,
+ y: targetY + (targetHeight / 2),
+ }];
+ }
+
+ return {
+ style: {
+ waypoints: waypoints1,
+ source,
+ target,
+ },
+ Type: type,
+ };
+ }
+
+ importEdges(definitions, startState) {
+ if (startState.Next) {
+ this.addEdge(startState.Name, startState.Next, definitions,
'Transition', startState);
+ }
+
+ if (startState.CompensateState) {
+ this.addEdge(startState.Name, startState.CompensateState, definitions,
'Compensation', startState);
+ }
+
+ if (startState.Choices) {
+ for (const option of startState.Choices) {
+ this.addEdge(startState.Name, option.Next, definitions, 'ChoiceEntry',
startState);
+ }
+ }
+
+ this.importJson(startState);
+ }
+
+ addEdge(source, target, definitions, type, startState) {
+ const sourceWidth = this.calculateWidth(definitions.States[source]);
+ const sourceHeight = this.calculateHeight(definitions.States[source]);
+ const targetWidth = this.calculateWidth(definitions.States[target]);
+ const targetHeight = this.calculateHeight(definitions.States[target]);
+
+ const elementJson = this.addWaypoints(
+ source,
+ target,
+ definitions,
+ type,
+ targetWidth,
+ targetHeight,
+ sourceWidth,
+ sourceHeight,
+ );
+ startState.edge = Object.assign(startState.edge || {}, { [target]:
elementJson });
+ }
+
+ calculateWidth(state) {
+ if (is(state, 'Task')) {
+ return 100;
+ }
+ if (is(state, 'Event')) {
+ return 36;
+ }
+ if (is(state, 'Choice')) {
+ return 50;
+ }
+ return 100;
+ }
+
+ calculateHeight(state) {
+ if (is(state, 'Task')) {
+ return 80;
+ }
+ if (is(state, 'Event')) {
+ return 36;
+ }
+ if (is(state, 'Choice')) {
+ return 50;
+ }
+ return 80;
+ }
+
+ importJsonEdges(json) {
+ if (json.States) {
+ const targetX = json.States[json.StartState].style.bounds.x;
+ const targetY = json.States[json.StartState].style.bounds.y;
+ json.edge = {
+ style: {
+ waypoints: [{
+ x: DEFAULT_X + OFFSET_X,
+ y: DEFAULT_X + OFFSET_Y,
+ }, {
+ x: targetX - OFFSET_TARGET_X,
+ y: targetY + OFFSET_TARGET_Y,
+ }, {
+ x: targetX,
+ y: targetY + OFFSET_TARGET_Y,
+ }],
+ target: json.StartState,
+ },
+ Type: 'Transition',
+ };
+ }
+ this.importJson(json);
+ }
+
+ importCatchesEdges(definitions, startState) {
+ const catchEdges = startState.Catch;
+ for (const option of catchEdges) {
+ const sourceX = definitions.States[startState.Name].catch.style.bounds.x;
+ const sourceY = definitions.States[startState.Name].catch.style.bounds.y;
+ const targetX = definitions.States[option.Next].style.bounds.x;
+ const targetY = definitions.States[option.Next].style.bounds.y;
+ let waypoints1 = [];
+ if (is(option.Next, 'Task')) {
+ waypoints1 = [{
+ x: sourceX + OFFSET_Y,
+ y: sourceY,
+ }, {
+ x: targetX + DEFAULT_WIDTH / 2,
+ y: targetY + DEFAULT_WIDTH,
+ }, {
+ x: targetX + DEFAULT_WIDTH / 2,
+ y: (targetY + DEFAULT_WIDTH) - OFFSET_TARGET_X,
+ }];
+ } else {
+ waypoints1 = [{
+ x: sourceX + OFFSET_Y,
+ y: sourceY,
+ }, {
+ x: targetX + OFFSET_Y,
+ y: (targetY + OFFSET_X) + OFFSET_TARGET_X,
+ }, {
+ x: targetX + OFFSET_Y,
+ y: targetY + OFFSET_X,
+ }];
+ }
+ startState.catch.edge = assign(startState.catch.edge || {}, {
+ [option.Next]: {
+ style: {
+ waypoints: waypoints1,
+ source: startState.Name,
+ target: option.Next,
+ },
+ Type: 'ExceptionMatch',
+ },
+ });
+ }
+ this.importJson(startState.catch);
+ }
+
+ addCatch(definitions, node, catchList, adjList) {
+ node.catch = {};
+ if (node.Catch) {
+ const {
+ style: {
+ bounds: {
+ x,
+ y,
+ },
+ },
+ } = node;
+ const newX = x;
+ const newY = y;
+ node.catch.style = {};
+ node.catch.style.bounds = {
+ x: newX + DEFAULT_WIDTH / 2,
+ y: newY - OFFSET_TARGET_X,
+ width: OFFSET_X,
+ height: OFFSET_X,
+ };
+ }
+ this.importJson(node.catch);
+
+ let prev = node.catch;
+ let width1;
+ let height1;
+ catchList.get(node).forEach((semantic) => {
+ if (is(semantic, 'Task')) {
+ width1 = DEFAULT_WIDTH;
+ height1 = DEFAULT_HEIGHT;
+ } else {
+ width1 = OFFSET_X;
+ height1 = OFFSET_X;
+ }
+ semantic.style = {};
+ semantic.style.bounds = {
+ x: prev.style.bounds.x - DEFAULT_WIDTH / 2,
+ y: prev.style.bounds.y - DEFAULT_WIDTH,
+ width: width1,
+ height: height1,
+ };
+ prev = semantic;
+
+ this.importStates(definitions, semantic, null, adjList);
+ });
+ }
+
+ importStates(definitions, startState, begin, adjList) {
+ const visited = [];
+ const queue = [];
+ if (begin !== null) {
+ if (startState.style === undefined) {
+ startState.style = {};
+ if (startState.style.bounds === undefined) {
+ startState.style.bounds = {
+ x: begin.style.bounds.x + 150, // Adjust x-coordinate
+ y: begin.style.bounds.y, // Adjust y-coordinate
+ width: DEFAULT_WIDTH,
+ height: DEFAULT_HEIGHT,
+ };
+ }
+ }
+ this.importJson(startState);
+ }
+ queue.push(startState);
+
+ function setBounds(neighbor, x, y, width1, height1) {
+ if (neighbor.style.bounds === undefined) {
+ neighbor.style.bounds = {
+ x, // Adjust x-coordinate
+ y, // Adjust y-coordinate
+ width: width1,
+ height: height1,
+ };
+ visited.push([x, y]);
+ }
+ }
+
+ while (queue.length) {
+ const currentState = queue.shift();
+
+ adjList.get(currentState).forEach((neighbor) => {
+ if (neighbor.style === undefined) {
+ neighbor.style = {};
+ if (is(neighbor, 'End')) {
+ const target = [];
+ target.push(currentState.style.bounds.x + 150,
currentState.style.bounds.y);
+ if (this.isElementPresent(visited, target)) {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x + 150,
+ currentState.style.bounds.y,
+ OFFSET_X,
+ OFFSET_X,
+ );
+ } else {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x,
+ currentState.style.bounds.y + 150,
+ OFFSET_X,
+ OFFSET_X,
+ );
+ }
+ }
+
+ if (is(neighbor, 'Task') && !neighbor.IsForCompensation) {
+ const target = [];
+ target.push(currentState.style.bounds.x + 150,
currentState.style.bounds.y);
+ if (this.isElementPresent(visited, target)) {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x + 150,
+ currentState.style.bounds.y,
+ DEFAULT_WIDTH,
+ DEFAULT_HEIGHT,
+ );
+ } else {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x,
+ currentState.style.bounds.y + 150,
+ DEFAULT_WIDTH,
+ DEFAULT_HEIGHT,
+ );
+ }
+
+ const { Name } = neighbor;
+ queue.push(definitions.States[Name]);
+ } else if (is(neighbor, 'Task') && neighbor.IsForCompensation) {
+ const target = [];
+ target.push(currentState.style.bounds.x,
currentState.style.bounds.y + 150);
+ if (this.isElementPresent(visited, target)) {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x,
+ currentState.style.bounds.y + 150,
+ DEFAULT_WIDTH,
+ DEFAULT_HEIGHT,
+ );
+ }
+ } else if (is(neighbor, 'CompensationTrigger')) {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x,
+ currentState.style.bounds.y - 150,
+ OFFSET_X,
+ OFFSET_X,
+ );
+ const { Name } = neighbor;
+ queue.push(definitions.States[Name]);
+ } else if (is(neighbor, 'Choice')) {
+ setBounds(
+ neighbor,
+ currentState.style.bounds.x + 150,
+ currentState.style.bounds.y,
+ 50,
+ 50,
+ );
+ const { Name } = neighbor;
+ queue.push(definitions.States[Name]);
+ }
+ }
+ });
+ }
+ }
+
exportJson() {
return assign({}, { style: this.style });
}
diff --git a/saga/seata-saga-statemachine-designer/src/utils/index.js
b/saga/seata-saga-statemachine-designer/src/utils/index.js
index 72054ca1c5..cb5fb3f572 100644
--- a/saga/seata-saga-statemachine-designer/src/utils/index.js
+++ b/saga/seata-saga-statemachine-designer/src/utils/index.js
@@ -61,6 +61,14 @@ export function setProperties(businessObject, properties,
override) {
export function is(element, target) {
const type = element?.businessObject?.Type || element?.Type || element;
+ if (target === 'Event') {
+ return type === 'StartState' || type === 'CompensationTrigger' || type ===
'Catch' || type === 'Fail' || type === 'Succeed';
+ }
+
+ if (target === 'End') {
+ return type === 'Fail' || type === 'Succeed';
+ }
+
if (target === 'Task') {
return type === 'ServiceTask' || type === 'ScriptTask' || type ===
'SubStateMachine';
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]