Skip to content

Commit deeaccc

Browse files
committed
Fix workflow editor condition compatibility
1 parent c63eb7a commit deeaccc

5 files changed

Lines changed: 150 additions & 36 deletions

File tree

src/backend/bisheng/api/services/external_workflow.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from bisheng.workflow.common.node import BaseNodeData, NodeType
2121
from bisheng.workflow.edges.edges import EdgeBase
2222
from bisheng.workflow.graph.workflow import Workflow
23+
from bisheng.workflow.authoring.editor_compat import normalize_workflow_editor_graph
2324
from bisheng.workflow.authoring.registry import create_graph_node_payload
2425
from bisheng.workflow.nodes.condition.conidition_case import ConditionCases
2526

@@ -686,17 +687,7 @@ def _build_scaffold_node(cls,
686687

687688
@classmethod
688689
def _normalize_editor_node_types(cls, graph_data: dict) -> dict:
689-
for node in graph_data.get('nodes', []):
690-
if not isinstance(node, dict):
691-
continue
692-
node_data = node.get('data')
693-
if not isinstance(node_data, dict):
694-
continue
695-
node_type = node_data.get('type')
696-
if not node_type:
697-
continue
698-
node['type'] = cls._EDITOR_NOTE_NODE_TYPE if node_type == cls._NOTE_NODE_TYPE else cls._EDITOR_FLOW_NODE_TYPE
699-
return graph_data
690+
return normalize_workflow_editor_graph(graph_data, in_place=True)
700691

701692
@classmethod
702693
def _ensure_create_graph_scaffold(cls, graph_data: dict) -> dict:
@@ -939,6 +930,10 @@ def _normalize_condition_cases(cls, condition_cases: list[dict]) -> list[dict]:
939930
normalized_case = ConditionCases(**raw_case).model_dump()
940931
except Exception as exc:
941932
cls._raise_workflow_error(f'Invalid condition case at index {index}: {exc}')
933+
for condition in normalized_case.get('conditions') or []:
934+
right_value_type = condition.get('right_value_type')
935+
if right_value_type != 'ref':
936+
condition['right_value_type'] = 'input'
942937
case_id = normalized_case['id']
943938
if case_id in seen_case_ids:
944939
cls._raise_workflow_error(f'Duplicate condition case id: {case_id}')

src/backend/bisheng/api/services/flow.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,14 @@
3333
from bisheng.user.domain.models.user import UserDao
3434
from bisheng.user.domain.models.user_role import UserRoleDao
3535
from bisheng.utils import get_request_ip
36+
from bisheng.workflow.authoring.editor_compat import normalize_workflow_editor_graph
3637

3738

3839
class FlowService(BaseService):
3940

4041
@staticmethod
4142
def _normalize_workflow_editor_graph(graph_data: Optional[dict]) -> Optional[dict]:
42-
if not isinstance(graph_data, dict):
43-
return graph_data
44-
45-
nodes = graph_data.get('nodes')
46-
if not isinstance(nodes, list):
47-
return graph_data
48-
49-
normalized_graph = copy.deepcopy(graph_data)
50-
for node in normalized_graph.get('nodes', []):
51-
if not isinstance(node, dict):
52-
continue
53-
node_data = node.get('data')
54-
if not isinstance(node_data, dict):
55-
continue
56-
node_type = node_data.get('type')
57-
if not node_type:
58-
continue
59-
if node.get('type') in {'flowNode', 'noteNode'}:
60-
continue
61-
node['type'] = 'noteNode' if node_type == 'note' else 'flowNode'
62-
63-
return normalized_graph
43+
return normalize_workflow_editor_graph(graph_data)
6444

6545
@classmethod
6646
def get_version_list_by_flow(cls, user: UserPayload, flow_id: str) -> UnifiedResponseModel[List[FlowVersionRead]]:
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import copy
2+
from typing import Any, Optional
3+
4+
5+
_EDITOR_FLOW_NODE_TYPE = 'flowNode'
6+
_EDITOR_NOTE_NODE_TYPE = 'noteNode'
7+
_CONDITION_NODE_TYPE = 'condition'
8+
_CONDITION_PARAM_KEY = 'condition'
9+
_EDITOR_CONDITION_RIGHT_VALUE_TYPES = {'input', 'ref'}
10+
11+
12+
def _normalize_editor_condition_cases(condition_cases: Any) -> Any:
13+
if not isinstance(condition_cases, list):
14+
return condition_cases
15+
16+
normalized_cases = []
17+
for raw_case in condition_cases:
18+
if not isinstance(raw_case, dict):
19+
normalized_cases.append(raw_case)
20+
continue
21+
22+
case = copy.deepcopy(raw_case)
23+
conditions = case.get('conditions')
24+
if isinstance(conditions, list):
25+
normalized_conditions = []
26+
for raw_condition in conditions:
27+
if not isinstance(raw_condition, dict):
28+
normalized_conditions.append(raw_condition)
29+
continue
30+
31+
condition = copy.deepcopy(raw_condition)
32+
condition.setdefault('left_label', '')
33+
condition.setdefault('right_label', '')
34+
if condition.get('left_var') is None:
35+
condition['left_var'] = ''
36+
if condition.get('right_value') is None:
37+
condition['right_value'] = ''
38+
if condition.get('comparison_operation') is None:
39+
condition['comparison_operation'] = ''
40+
41+
right_value_type = condition.get('right_value_type')
42+
if right_value_type not in _EDITOR_CONDITION_RIGHT_VALUE_TYPES:
43+
condition['right_value_type'] = 'ref' if right_value_type == 'ref' else 'input'
44+
45+
normalized_conditions.append(condition)
46+
case['conditions'] = normalized_conditions
47+
48+
normalized_cases.append(case)
49+
50+
return normalized_cases
51+
52+
53+
def normalize_workflow_editor_graph(graph_data: Optional[dict], *, in_place: bool = False) -> Optional[dict]:
54+
if not isinstance(graph_data, dict):
55+
return graph_data
56+
57+
nodes = graph_data.get('nodes')
58+
if not isinstance(nodes, list):
59+
return graph_data
60+
61+
normalized_graph = graph_data if in_place else copy.deepcopy(graph_data)
62+
for node in normalized_graph.get('nodes', []):
63+
if not isinstance(node, dict):
64+
continue
65+
66+
node_data = node.get('data')
67+
if not isinstance(node_data, dict):
68+
continue
69+
70+
node_type = node_data.get('type')
71+
if node_type:
72+
if node.get('type') not in {_EDITOR_FLOW_NODE_TYPE, _EDITOR_NOTE_NODE_TYPE}:
73+
node['type'] = _EDITOR_NOTE_NODE_TYPE if node_type == 'note' else _EDITOR_FLOW_NODE_TYPE
74+
75+
if node_type == _CONDITION_NODE_TYPE:
76+
for group in node_data.get('group_params', []):
77+
if not isinstance(group, dict):
78+
continue
79+
for param in group.get('params', []):
80+
if not isinstance(param, dict):
81+
continue
82+
if param.get('key') == _CONDITION_PARAM_KEY:
83+
param['value'] = _normalize_editor_condition_cases(param.get('value') or [])
84+
85+
return normalized_graph

src/backend/bisheng/workflow/nodes/condition/conidition_case.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
class ConditionOne(BaseModel):
1212
id: str = Field(..., description='Unique id for condition')
1313
left_var: str = Field(..., description='Left variable')
14+
left_label: str = Field('', description='Left variable label for editor display')
1415
comparison_operation: str = Field(..., description='Compare type')
1516
right_value_type: str = Field(..., description='Right value type')
1617
right_value: str = Field(..., description='Right value')
18+
right_label: str = Field('', description='Right variable label for editor display')
1719
variable_key_value: Dict = Field(default={}, description='variable key value')
1820

1921
def evaluate(self, node_instance: BaseNode) -> bool:

src/backend/test/test_external_workflow_service.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -347,15 +347,67 @@ def test_normalize_workflow_editor_graph_rewrites_legacy_node_types(self):
347347
'name': 'End',
348348
'group_params': [],
349349
},
350+
}, {
351+
'id': 'condition-1',
352+
'type': 'condition',
353+
'position': {'x': 160, 'y': 0},
354+
'data': {
355+
'id': 'condition-1',
356+
'type': 'condition',
357+
'name': 'Condition',
358+
'group_params': [{
359+
'params': [{
360+
'key': 'condition',
361+
'type': 'condition',
362+
'value': [{
363+
'id': 'case_a',
364+
'operator': 'and',
365+
'conditions': [{
366+
'id': 'rule_1',
367+
'left_var': 'code_1.score',
368+
'comparison_operation': 'greater_than',
369+
'right_value_type': 'const',
370+
'right_value': '80',
371+
}],
372+
}],
373+
}],
374+
}],
375+
},
350376
}],
351377
'edges': [],
352378
}
353379

354380
normalized = FlowService._normalize_workflow_editor_graph(graph)
355381

356-
self.assertEqual([node['type'] for node in normalized['nodes']], ['flowNode', 'flowNode'])
357-
self.assertEqual([node['data']['type'] for node in normalized['nodes']], ['start', 'end'])
358-
self.assertEqual([node['type'] for node in graph['nodes']], ['start', 'end'])
382+
self.assertEqual([node['type'] for node in normalized['nodes']], ['flowNode', 'flowNode', 'flowNode'])
383+
self.assertEqual([node['data']['type'] for node in normalized['nodes']], ['start', 'end', 'condition'])
384+
self.assertEqual([node['type'] for node in graph['nodes']], ['start', 'end', 'condition'])
385+
condition_item = normalized['nodes'][2]['data']['group_params'][0]['params'][0]['value'][0]['conditions'][0]
386+
self.assertEqual(condition_item['right_value_type'], 'input')
387+
self.assertEqual(condition_item['left_label'], '')
388+
self.assertEqual(condition_item['right_label'], '')
389+
390+
def test_normalize_condition_cases_keeps_editor_friendly_shape(self):
391+
normalized = ExternalWorkflowService._normalize_condition_cases([{
392+
'id': 'case_a',
393+
'operator': 'and',
394+
'conditions': [{
395+
'id': 'rule_1',
396+
'left_var': 'code_1.score',
397+
'left_label': 'Score/priority_score',
398+
'comparison_operation': 'greater_than_or_equal',
399+
'right_value_type': 'const',
400+
'right_value': '90',
401+
'right_label': '',
402+
'variable_key_value': {},
403+
}],
404+
'variable_key_value': {},
405+
}])
406+
407+
condition_item = normalized[0]['conditions'][0]
408+
self.assertEqual(condition_item['left_label'], 'Score/priority_score')
409+
self.assertEqual(condition_item['right_label'], '')
410+
self.assertEqual(condition_item['right_value_type'], 'input')
359411

360412
def test_get_existing_external_draft_version_limits_recent_versions(self):
361413
captured = {'statements': []}

0 commit comments

Comments
 (0)