Adding unit tests to WorkflowPlayer.py
This commit is contained in:
@@ -4,10 +4,12 @@
|
||||
|
||||
using `_id={WORKFLOW_DESIGNER_INSTANCE_ID}{session['user_id']}{get_unique_id()}`
|
||||
|
||||
| Name | value |
|
||||
|---------------|------------------|
|
||||
| Canvas | `c_{self._id}` |
|
||||
| Designer | `d_{self._id}` |
|
||||
| Error Message | `err_{self._id}` |
|
||||
| Properties | `p_{self._id}` |
|
||||
| Spliter | `s_{self._id}` |
|
||||
| Name | value |
|
||||
|-----------------|--------------------|
|
||||
| Canvas | `c_{self._id}` |
|
||||
| Designer | `d_{self._id}` |
|
||||
| Error Message | `err_{self._id}` |
|
||||
| Properties | `p_{self._id}` |
|
||||
| Spliter | `s_{self._id}` |
|
||||
| Top element | `t_{self._id}` |
|
||||
|
||||
|
||||
@@ -135,8 +135,8 @@ def post(session, _id: str, tab_boundaries: str):
|
||||
return instance.play_workflow(json.loads(tab_boundaries))
|
||||
|
||||
@rt(Routes.StopWorkflow)
|
||||
def post(session, _id: str, tab_boundaries: str):
|
||||
def post(session, _id: str):
|
||||
logger.debug(
|
||||
f"Entering {Routes.StopWorkflow} with args {debug_session(session)}, {_id=}")
|
||||
instance = InstanceManager.get(session, _id)
|
||||
return instance.stop_workflow(json.loads(tab_boundaries))
|
||||
return instance.stop_workflow()
|
||||
@@ -81,7 +81,7 @@ class WorkflowDesignerCommandManager(BaseCommandManager):
|
||||
"hx_post": f"{ROUTE_ROOT}{Routes.PauseWorkflow}",
|
||||
"hx-target": f"#{self._owner.tabs_manager.get_id()}",
|
||||
"hx-swap": "outerHTML",
|
||||
"hx-vals": f'js:{{"_id": "{self._id}", "tab_boundaries": getTabContentBoundaries("{self._owner.tabs_manager.get_id()}")}}',
|
||||
"hx-vals": f'js:{{"_id": "{self._id}")}}',
|
||||
}
|
||||
|
||||
def stop_workflow(self):
|
||||
@@ -89,7 +89,7 @@ class WorkflowDesignerCommandManager(BaseCommandManager):
|
||||
"hx_post": f"{ROUTE_ROOT}{Routes.StopWorkflow}",
|
||||
"hx-target": f"#{self._owner.tabs_manager.get_id()}",
|
||||
"hx-swap": "outerHTML",
|
||||
"hx-vals": f'js:{{"_id": "{self._id}", "tab_boundaries": getTabContentBoundaries("{self._owner.tabs_manager.get_id()}")}}',
|
||||
"hx-vals": f'js:{{"_id": "{self._id}")}}',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from components.workflows.commands import WorkflowDesignerCommandManager
|
||||
from components.workflows.components.WorkflowPlayer import WorkflowPlayer
|
||||
from components.workflows.constants import WORKFLOW_DESIGNER_INSTANCE_ID, ProcessorTypes
|
||||
from components.workflows.db_management import WorkflowsDesignerSettings, WorkflowComponent, \
|
||||
Connection, WorkflowsDesignerDbManager, WorkflowsPlayerSettings, WorkflowComponentRuntimeState, ComponentState
|
||||
Connection, WorkflowsDesignerDbManager, ComponentState
|
||||
from components_helpers import apply_boundaries, mk_tooltip, mk_dialog_buttons, mk_icon
|
||||
from core.instance_manager import InstanceManager
|
||||
from core.utils import get_unique_id, make_safe_id
|
||||
@@ -71,9 +71,7 @@ class WorkflowDesigner(BaseComponent):
|
||||
WorkflowPlayer,
|
||||
settings_manager=self._settings_manager,
|
||||
tabs_manager=self.tabs_manager,
|
||||
player_settings=WorkflowsPlayerSettings(workflow_name,
|
||||
list(self._state.components.values()),
|
||||
self._state.connections),
|
||||
designer=self,
|
||||
boundaries=boundaries)
|
||||
|
||||
self._error_message = None
|
||||
@@ -202,10 +200,10 @@ class WorkflowDesigner(BaseComponent):
|
||||
# change the tab and display the results
|
||||
self._player.set_boundaries(boundaries)
|
||||
self.tabs_manager.add_tab(f"Workflow {self._designer_settings.workflow_name}", self._player, self._player.key)
|
||||
|
||||
|
||||
return self.tabs_manager.refresh()
|
||||
|
||||
def stop_workflow(self, boundaries):
|
||||
def stop_workflow(self):
|
||||
self._error_message = None
|
||||
self._player.run()
|
||||
return self.tabs_manager.refresh()
|
||||
@@ -220,6 +218,15 @@ class WorkflowDesigner(BaseComponent):
|
||||
|
||||
return self.refresh_properties()
|
||||
|
||||
def get_workflow_name(self):
|
||||
return self._designer_settings.workflow_name
|
||||
|
||||
def get_workflow_components(self):
|
||||
return self._state.components.values()
|
||||
|
||||
def get_workflow_connections(self):
|
||||
return self._state.connections
|
||||
|
||||
def __ft__(self):
|
||||
return Div(
|
||||
H1(f"{self._designer_settings.workflow_name}", cls="text-xl font-bold"),
|
||||
@@ -227,7 +234,8 @@ class WorkflowDesigner(BaseComponent):
|
||||
Div(
|
||||
self._mk_media(),
|
||||
self._mk_error_message(),
|
||||
cls="flex mb-2"
|
||||
cls="flex mb-2",
|
||||
id=f"t_{self._id}"
|
||||
),
|
||||
self._mk_designer(),
|
||||
Div(cls="wkf-splitter", id=f"s_{self._id}"),
|
||||
@@ -268,21 +276,23 @@ class WorkflowDesigner(BaseComponent):
|
||||
</svg>
|
||||
"""
|
||||
|
||||
def _mk_component(self, component: WorkflowComponent, runtime_state: WorkflowComponentRuntimeState):
|
||||
def _mk_component(self, component: WorkflowComponent):
|
||||
|
||||
runtime_state = self._player.get_component_runtime_state(component.id)
|
||||
|
||||
info = COMPONENT_TYPES[component.type]
|
||||
is_selected = self._state.selected_component_id == component.id
|
||||
tooltip_content = None
|
||||
tooltip_class = ""
|
||||
|
||||
if runtime_state.state == ComponentState.FAILURE:
|
||||
state_class = 'error' # To be styled with a red highlight
|
||||
tooltip_content = runtime_state.error_message
|
||||
tooltip_class = "mmt-tooltip"
|
||||
elif runtime_state.state == ComponentState.NOT_RUN:
|
||||
state_class = 'not-run' # To be styled as greyed-out
|
||||
else:
|
||||
state_class = ''
|
||||
if runtime_state.state == ComponentState.FAILURE:
|
||||
tooltip_content = runtime_state.error_message
|
||||
tooltip_class = "mmt-tooltip"
|
||||
else:
|
||||
tooltip_content = None
|
||||
tooltip_class = ""
|
||||
|
||||
return Div(
|
||||
# Input connection point
|
||||
@@ -315,8 +325,7 @@ class WorkflowDesigner(BaseComponent):
|
||||
*[NotStr(self._mk_connection_svg(conn)) for conn in self._state.connections],
|
||||
|
||||
# Render components
|
||||
*[self._mk_component(comp, state) for comp, state in zip(self._state.components.values(),
|
||||
self._player.runtime_states.values())],
|
||||
*[self._mk_component(comp) for comp in self._state.components.values()],
|
||||
)
|
||||
|
||||
def _mk_canvas(self, oob=False):
|
||||
|
||||
@@ -9,7 +9,7 @@ from components.datagrid_new.components.DataGrid import DataGrid
|
||||
from components.datagrid_new.settings import DataGridSettings
|
||||
from components.workflows.commands import WorkflowPlayerCommandManager
|
||||
from components.workflows.constants import WORKFLOW_PLAYER_INSTANCE_ID, ProcessorTypes
|
||||
from components.workflows.db_management import WorkflowsPlayerSettings, WorkflowComponentRuntimeState, \
|
||||
from components.workflows.db_management import WorkflowComponentRuntimeState, \
|
||||
WorkflowComponent, ComponentState
|
||||
from core.instance_manager import InstanceManager
|
||||
from core.utils import get_unique_id, make_safe_id
|
||||
@@ -34,13 +34,13 @@ class WorkflowPlayer(BaseComponent):
|
||||
_id=None,
|
||||
settings_manager=None,
|
||||
tabs_manager=None,
|
||||
player_settings: WorkflowsPlayerSettings = None,
|
||||
designer=None,
|
||||
boundaries: dict = None):
|
||||
super().__init__(session, _id)
|
||||
self._settings_manager = settings_manager
|
||||
self.tabs_manager = tabs_manager
|
||||
self.key = f"__WorkflowPlayer_{player_settings.workflow_name}"
|
||||
self._player_settings: WorkflowsPlayerSettings = player_settings
|
||||
self._designer = designer
|
||||
self.key = f"__WorkflowPlayer_{designer.get_workflow_name()}"
|
||||
self._boundaries = boundaries
|
||||
self.commands = WorkflowPlayerCommandManager(self)
|
||||
self._datagrid = InstanceManager.get(self._session,
|
||||
@@ -49,18 +49,26 @@ class WorkflowPlayer(BaseComponent):
|
||||
key=self.key,
|
||||
grid_settings=grid_settings,
|
||||
boundaries=boundaries)
|
||||
self.runtime_states = {component.id: WorkflowComponentRuntimeState(component.id)
|
||||
for component in player_settings.components}
|
||||
self.runtime_states = {}
|
||||
self.global_error = None
|
||||
self.has_error = False
|
||||
|
||||
def set_boundaries(self, boundaries: dict):
|
||||
self._datagrid.set_boundaries(boundaries)
|
||||
|
||||
def get_component_runtime_state(self, component_id: str):
|
||||
# return a default value if the player hasn't been played yet
|
||||
return self.runtime_states.get(component_id, WorkflowComponentRuntimeState(component_id))
|
||||
|
||||
def run(self):
|
||||
self._reset_state()
|
||||
# at least one connection is required to play
|
||||
if len(self._designer.get_workflow_connections()) == 0:
|
||||
self.global_error = "No connections defined."
|
||||
return
|
||||
|
||||
components_by_id = {c.id: c for c in self._player_settings.components}
|
||||
self._init_state()
|
||||
|
||||
components_by_id = {c.id: c for c in self._designer.get_workflow_components()}
|
||||
|
||||
try:
|
||||
sorted_components = self._get_sorted_components()
|
||||
@@ -122,8 +130,8 @@ class WorkflowPlayer(BaseComponent):
|
||||
self._datagrid.init_from_dataframe(df)
|
||||
|
||||
def stop(self):
|
||||
self._reset_state()
|
||||
|
||||
self._init_state()
|
||||
|
||||
def get_dataframe(self):
|
||||
return self._datagrid.get_dataframe()
|
||||
|
||||
@@ -144,11 +152,11 @@ class WorkflowPlayer(BaseComponent):
|
||||
|
||||
:return: A list of sorted WorkflowComponent objects.
|
||||
"""
|
||||
components_by_id = {c.id: c for c in self._player_settings.components}
|
||||
components_by_id = {c.id: c for c in self._designer.get_workflow_components()}
|
||||
|
||||
# Get all component IDs involved in connections
|
||||
involved_ids = set()
|
||||
for conn in self._player_settings.connections:
|
||||
for conn in self._designer.get_workflow_connections():
|
||||
involved_ids.add(conn.from_id)
|
||||
involved_ids.add(conn.to_id)
|
||||
|
||||
@@ -161,7 +169,7 @@ class WorkflowPlayer(BaseComponent):
|
||||
adj = {cid: [] for cid in involved_ids}
|
||||
in_degree = {cid: 0 for cid in involved_ids}
|
||||
|
||||
for conn in self._player_settings.connections:
|
||||
for conn in self._designer.get_workflow_connections():
|
||||
# from_id -> to_id
|
||||
adj[conn.from_id].append(conn.to_id)
|
||||
in_degree[conn.to_id] += 1
|
||||
@@ -210,12 +218,11 @@ class WorkflowPlayer(BaseComponent):
|
||||
|
||||
return engine
|
||||
|
||||
def _reset_state(self, state: ComponentState = ComponentState.SUCCESS):
|
||||
def _init_state(self):
|
||||
self.global_error = None
|
||||
self.has_error = False
|
||||
for runtime_state in self.runtime_states.values():
|
||||
runtime_state.state = state
|
||||
runtime_state.error_message = None
|
||||
self.runtime_states = {component.id: WorkflowComponentRuntimeState(component.id)
|
||||
for component in self._designer.get_workflow_components()}
|
||||
|
||||
@staticmethod
|
||||
def create_component_id(session, suffix=None):
|
||||
|
||||
@@ -61,13 +61,6 @@ class WorkflowsDesignerState:
|
||||
selected_component_id = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowsPlayerSettings:
|
||||
workflow_name: str
|
||||
components: list[WorkflowComponent]
|
||||
connections: list[Connection]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowsSettings:
|
||||
workflows: list[str] = field(default_factory=list)
|
||||
|
||||
Reference in New Issue
Block a user