Bläddra i källkod

feat: add if elif (#6094)

Joe 9 månader sedan
förälder
incheckning
5a3e09518c
2 ändrade filer med 155 tillägg och 79 borttagningar
  1. 24 12
      api/core/workflow/nodes/if_else/entities.py
  2. 131 67
      api/core/workflow/nodes/if_else/if_else_node.py

+ 24 - 12
api/core/workflow/nodes/if_else/entities.py

@@ -5,22 +5,34 @@ from pydantic import BaseModel
 from core.workflow.entities.base_node_data_entities import BaseNodeData
 
 
+class Condition(BaseModel):
+    """
+    Condition entity
+    """
+    variable_selector: list[str]
+    comparison_operator: Literal[
+        # for string or array
+        "contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty",
+            # for number
+        "=", "≠", ">", "<", "≥", "≤", "null", "not null"
+    ]
+    value: Optional[str] = None
+
+
 class IfElseNodeData(BaseNodeData):
     """
     Answer Node Data.
     """
-    class Condition(BaseModel):
+
+    class Case(BaseModel):
         """
-        Condition entity
+        Case entity representing a single logical condition group
         """
-        variable_selector: list[str]
-        comparison_operator: Literal[
-            # for string or array
-            "contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty",
-            # for number
-            "=", "≠", ">", "<", "≥", "≤", "null", "not null"
-        ]
-        value: Optional[str] = None
+        case_id: str
+        logical_operator: Literal["and", "or"]
+        conditions: list[Condition]
+
+    logical_operator: Optional[Literal["and", "or"]] = "and"
+    conditions: Optional[list[Condition]] = None
 
-    logical_operator: Literal["and", "or"] = "and"
-    conditions: list[Condition]
+    cases: Optional[list[Case]] = None

+ 131 - 67
api/core/workflow/nodes/if_else/if_else_node.py

@@ -4,7 +4,8 @@ from core.workflow.entities.base_node_data_entities import BaseNodeData
 from core.workflow.entities.node_entities import NodeRunResult, NodeType
 from core.workflow.entities.variable_pool import VariablePool
 from core.workflow.nodes.base_node import BaseNode
-from core.workflow.nodes.if_else.entities import IfElseNodeData
+from core.workflow.nodes.if_else.entities import Condition, IfElseNodeData
+from core.workflow.utils.variable_template_parser import VariableTemplateParser
 from models.workflow import WorkflowNodeExecutionStatus
 
 
@@ -29,68 +30,46 @@ class IfElseNode(BaseNode):
             "condition_results": []
         }
 
+        input_conditions = []
+        final_result = False
+        selected_case_id = None
         try:
-            logical_operator = node_data.logical_operator
-            input_conditions = []
-            for condition in node_data.conditions:
-                actual_value = variable_pool.get_variable_value(
-                    variable_selector=condition.variable_selector
+            # Check if the new cases structure is used
+            if node_data.cases:
+                for case in node_data.cases:
+                    input_conditions, group_result = self.process_conditions(variable_pool, case.conditions)
+                    # Apply the logical operator for the current case
+                    final_result = all(group_result) if case.logical_operator == "and" else any(group_result)
+
+                    process_datas["condition_results"].append(
+                        {
+                            "group": case.model_dump(),
+                            "results": group_result,
+                            "final_result": final_result,
+                        }
+                    )
+
+                    # Break if a case passes (logical short-circuit)
+                    if final_result:
+                        selected_case_id = case.case_id  # Capture the ID of the passing case
+                        break
+
+            else:
+                # Fallback to old structure if cases are not defined
+                input_conditions, group_result = self.process_conditions(variable_pool, node_data.conditions)
+
+                final_result = all(group_result) if node_data.logical_operator == "and" else any(group_result)
+
+                process_datas["condition_results"].append(
+                    {
+                        "group": "default",
+                        "results": group_result,
+                        "final_result": final_result
+                    }
                 )
 
-                expected_value = condition.value
-
-                input_conditions.append({
-                    "actual_value": actual_value,
-                    "expected_value": expected_value,
-                    "comparison_operator": condition.comparison_operator
-                })
-
             node_inputs["conditions"] = input_conditions
 
-            for input_condition in input_conditions:
-                actual_value = input_condition["actual_value"]
-                expected_value = input_condition["expected_value"]
-                comparison_operator = input_condition["comparison_operator"]
-
-                if comparison_operator == "contains":
-                    compare_result = self._assert_contains(actual_value, expected_value)
-                elif comparison_operator == "not contains":
-                    compare_result = self._assert_not_contains(actual_value, expected_value)
-                elif comparison_operator == "start with":
-                    compare_result = self._assert_start_with(actual_value, expected_value)
-                elif comparison_operator == "end with":
-                    compare_result = self._assert_end_with(actual_value, expected_value)
-                elif comparison_operator == "is":
-                    compare_result = self._assert_is(actual_value, expected_value)
-                elif comparison_operator == "is not":
-                    compare_result = self._assert_is_not(actual_value, expected_value)
-                elif comparison_operator == "empty":
-                    compare_result = self._assert_empty(actual_value)
-                elif comparison_operator == "not empty":
-                    compare_result = self._assert_not_empty(actual_value)
-                elif comparison_operator == "=":
-                    compare_result = self._assert_equal(actual_value, expected_value)
-                elif comparison_operator == "≠":
-                    compare_result = self._assert_not_equal(actual_value, expected_value)
-                elif comparison_operator == ">":
-                    compare_result = self._assert_greater_than(actual_value, expected_value)
-                elif comparison_operator == "<":
-                    compare_result = self._assert_less_than(actual_value, expected_value)
-                elif comparison_operator == "≥":
-                    compare_result = self._assert_greater_than_or_equal(actual_value, expected_value)
-                elif comparison_operator == "≤":
-                    compare_result = self._assert_less_than_or_equal(actual_value, expected_value)
-                elif comparison_operator == "null":
-                    compare_result = self._assert_null(actual_value)
-                elif comparison_operator == "not null":
-                    compare_result = self._assert_not_null(actual_value)
-                else:
-                    continue
-
-                process_datas["condition_results"].append({
-                    **input_condition,
-                    "result": compare_result
-                })
         except Exception as e:
             return NodeRunResult(
                 status=WorkflowNodeExecutionStatus.FAILED,
@@ -99,21 +78,106 @@ class IfElseNode(BaseNode):
                 error=str(e)
             )
 
-        if logical_operator == "and":
-            compare_result = False not in [condition["result"] for condition in process_datas["condition_results"]]
-        else:
-            compare_result = True in [condition["result"] for condition in process_datas["condition_results"]]
+        outputs = {
+            "result": final_result
+        }
+        if node_data.cases:
+            outputs["selected_case_id"] = selected_case_id
 
-        return NodeRunResult(
+        data = NodeRunResult(
             status=WorkflowNodeExecutionStatus.SUCCEEDED,
             inputs=node_inputs,
             process_data=process_datas,
-            edge_source_handle="false" if not compare_result else "true",
-            outputs={
-                "result": compare_result
-            }
+            edge_source_handle=selected_case_id if selected_case_id else "false",  # Use case ID or 'default'
+            outputs=outputs
         )
 
+        return data
+
+    def evaluate_condition(
+        self, actual_value: Optional[str | list], expected_value: str, comparison_operator: str
+    ) -> bool:
+        """
+        Evaluate condition
+        :param actual_value: actual value
+        :param expected_value: expected value
+        :param comparison_operator: comparison operator
+
+        :return: bool
+        """
+        if comparison_operator == "contains":
+            return self._assert_contains(actual_value, expected_value)
+        elif comparison_operator == "not contains":
+            return self._assert_not_contains(actual_value, expected_value)
+        elif comparison_operator == "start with":
+            return self._assert_start_with(actual_value, expected_value)
+        elif comparison_operator == "end with":
+            return self._assert_end_with(actual_value, expected_value)
+        elif comparison_operator == "is":
+            return self._assert_is(actual_value, expected_value)
+        elif comparison_operator == "is not":
+            return self._assert_is_not(actual_value, expected_value)
+        elif comparison_operator == "empty":
+            return self._assert_empty(actual_value)
+        elif comparison_operator == "not empty":
+            return self._assert_not_empty(actual_value)
+        elif comparison_operator == "=":
+            return self._assert_equal(actual_value, expected_value)
+        elif comparison_operator == "≠":
+            return self._assert_not_equal(actual_value, expected_value)
+        elif comparison_operator == ">":
+            return self._assert_greater_than(actual_value, expected_value)
+        elif comparison_operator == "<":
+            return self._assert_less_than(actual_value, expected_value)
+        elif comparison_operator == "≥":
+            return self._assert_greater_than_or_equal(actual_value, expected_value)
+        elif comparison_operator == "≤":
+            return self._assert_less_than_or_equal(actual_value, expected_value)
+        elif comparison_operator == "null":
+            return self._assert_null(actual_value)
+        elif comparison_operator == "not null":
+            return self._assert_not_null(actual_value)
+        else:
+            raise ValueError(f"Invalid comparison operator: {comparison_operator}")
+
+    def process_conditions(self, variable_pool: VariablePool, conditions: list[Condition]):
+        input_conditions = []
+        group_result = []
+
+        for condition in conditions:
+            actual_value = variable_pool.get_variable_value(
+                variable_selector=condition.variable_selector
+            )
+
+            if condition.value is not None:
+                variable_template_parser = VariableTemplateParser(template=condition.value)
+                expected_value = variable_template_parser.extract_variable_selectors()
+                variable_selectors = variable_template_parser.extract_variable_selectors()
+                if variable_selectors:
+                    for variable_selector in variable_selectors:
+                        value = variable_pool.get_variable_value(
+                            variable_selector=variable_selector.value_selector
+                        )
+                        expected_value = variable_template_parser.format({variable_selector.variable: value})
+                else:
+                    expected_value = condition.value
+            else:
+                expected_value = None
+
+            comparison_operator = condition.comparison_operator
+            input_conditions.append(
+                {
+                    "actual_value": actual_value,
+                    "expected_value": expected_value,
+                    "comparison_operator": comparison_operator
+                }
+            )
+
+            result = self.evaluate_condition(actual_value, expected_value, comparison_operator)
+            group_result.append(result)
+
+        return input_conditions, group_result
+
     def _assert_contains(self, actual_value: Optional[str | list], expected_value: str) -> bool:
         """
         Assert contains