diff --git a/src/components/datagrid_new/db_management.py b/src/components/datagrid_new/db_management.py
index ee8f685..a49e31e 100644
--- a/src/components/datagrid_new/db_management.py
+++ b/src/components/datagrid_new/db_management.py
@@ -29,7 +29,7 @@ class DataGridDbManager:
def __init__(self, session: dict, settings_manager: SettingsManager, key: tuple):
self._session = session
self._settings_manager = settings_manager
- self._key = "#".join(make_safe_id(item) for item in key) if key else ""
+ self._key = self._key_as_string(key)
# init the db if needed
if self._settings_manager and not self._settings_manager.exists(self._session, self._get_db_entry()):
@@ -38,6 +38,16 @@ class DataGridDbManager:
def _get_db_entry(self):
return f"{DATAGRID_DB_ENTRY}_{self._key}"
+ @staticmethod
+ def _key_as_string(key):
+ if not key:
+ return ""
+
+ if isinstance(key, tuple):
+ return "#".join(make_safe_id(item) for item in key)
+
+ return make_safe_id(key)
+
def save_settings(self, settings: DataGridSettings):
if self._settings_manager is None:
return
diff --git a/src/components/workflows/WorkflowsApp.py b/src/components/workflows/WorkflowsApp.py
index 6cb9a9e..518e0f3 100644
--- a/src/components/workflows/WorkflowsApp.py
+++ b/src/components/workflows/WorkflowsApp.py
@@ -65,6 +65,7 @@ def post(session, _id: str, from_id: str, to_id: str):
instance = InstanceManager.get(session, _id)
return instance.add_connection(from_id, to_id)
+
@rt(Routes.DeleteConnection)
def post(session, _id: str, from_id: str, to_id: str):
logger.debug(
@@ -72,6 +73,7 @@ def post(session, _id: str, from_id: str, to_id: str):
instance = InstanceManager.get(session, _id)
return instance.delete_connection(from_id, to_id)
+
@rt(Routes.ResizeDesigner)
def post(session, _id: str, designer_height: int):
logger.debug(
@@ -122,4 +124,12 @@ def post(session, _id: str, component_id: str, event_name: str, details: dict):
details.pop("_id")
details.pop("component_id")
details.pop("event_name")
- return instance.on_processor_details_event(component_id, event_name, details)
\ No newline at end of file
+ return instance.on_processor_details_event(component_id, event_name, details)
+
+
+@rt(Routes.PlayWorkflow)
+def post(session, _id: str, tab_boundaries: str):
+ logger.debug(
+ f"Entering {Routes.PlayWorkflow} with args {debug_session(session)}, {_id=}")
+ instance = InstanceManager.get(session, _id)
+ return instance.play_workflow(json.loads(tab_boundaries))
diff --git a/src/components/workflows/assets/icons.py b/src/components/workflows/assets/icons.py
new file mode 100644
index 0000000..da6157d
--- /dev/null
+++ b/src/components/workflows/assets/icons.py
@@ -0,0 +1,25 @@
+from fastcore.basics import NotStr
+
+# Fluent Play20Filled
+icon_play = NotStr(
+ """""")
+
+# Fluent Pause20Filled
+icon_pause = NotStr(
+ """""")
+
+# Fluent Stop20Filled
+icon_stop = NotStr(
+ """""")
+
+# fluent PlayCircle20Regular
+icon_play_circle = NotStr(
+ """""")
+
+# fluent PauseCircle20Regular
+icon_pause_circle = NotStr(
+ """""")
+
+# fluent RecordStop20Regular
+icon_stop_circle = NotStr(
+ """""")
diff --git a/src/components/workflows/commands.py b/src/components/workflows/commands.py
index 8b815c4..4500b38 100644
--- a/src/components/workflows/commands.py
+++ b/src/components/workflows/commands.py
@@ -34,7 +34,7 @@ class WorkflowDesignerCommandManager(BaseCommandManager):
def __init__(self, owner):
super().__init__(owner)
- def select_processor(self, component_id :str):
+ def select_processor(self, component_id: str):
return {
"hx_post": f"{ROUTE_ROOT}{Routes.SelectProcessor}",
"hx-target": f"#p_{self._id}",
@@ -67,3 +67,32 @@ class WorkflowDesignerCommandManager(BaseCommandManager):
"hx-swap": "outerHTML",
"hx-vals": f'js:{{"_id": "{self._id}", "component_id": "{component_id}", "event_name": "{event_name}"}}',
}
+
+ def play_workflow(self):
+ return {
+ "hx_post": f"{ROUTE_ROOT}{Routes.PlayWorkflow}",
+ "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()}")}}',
+ }
+
+ def pause_workflow(self):
+ return {
+ "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()}")}}',
+ }
+
+ def stop_workflow(self):
+ return {
+ "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()}")}}',
+ }
+
+
+class WorkflowPlayerCommandManager(BaseCommandManager):
+ def __init__(self, owner):
+ super().__init__(owner)
diff --git a/src/components/workflows/components/WorkflowDesigner.py b/src/components/workflows/components/WorkflowDesigner.py
index 50b61a1..a4dc46a 100644
--- a/src/components/workflows/components/WorkflowDesigner.py
+++ b/src/components/workflows/components/WorkflowDesigner.py
@@ -5,11 +5,14 @@ from fasthtml.components import *
from fasthtml.xtend import Script
from components.BaseComponent import BaseComponent
+from components.workflows.assets.icons import icon_play, icon_pause, icon_stop
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
-from components_helpers import apply_boundaries, mk_tooltip, mk_dialog_buttons
+ Connection, WorkflowsDesignerDbManager, WorkflowsPlayerSettings
+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
from utils.DbManagementHelper import DbManagementHelper
@@ -47,11 +50,13 @@ class WorkflowDesigner(BaseComponent):
def __init__(self, session,
_id=None,
settings_manager=None,
+ tabs_manager=None,
key: str = None,
designer_settings: WorkflowsDesignerSettings = None,
boundaries: dict = None):
super().__init__(session, _id)
self._settings_manager = settings_manager
+ self.tabs_manager = tabs_manager
self._key = key
self._designer_settings = designer_settings
self._db = WorkflowsDesignerDbManager(session, settings_manager)
@@ -169,6 +174,23 @@ class WorkflowDesigner(BaseComponent):
self._db.save_state(self._key, self._state)
return self.refresh_properties()
+ def play_workflow(self, boundaries: dict):
+ if self._state.selected_component_id is None:
+ return self.error_message("No component selected")
+
+ workflow_name = self._designer_settings.workflow_name
+ player = InstanceManager.get(self._session,
+ WorkflowPlayer.create_component_id(self._session, workflow_name),
+ WorkflowPlayer,
+ settings_manager=self._settings_manager,
+ tabs_manager=self.tabs_manager,
+ player_settings=WorkflowsPlayerSettings(workflow_name),
+ boundaries=boundaries)
+
+ self.tabs_manager.add_tab(f"Workflow {workflow_name}", player, player.key)
+ # player.start(self._state.selected_component_id)
+ return self.tabs_manager.refresh()
+
def on_processor_details_event(self, component_id: str, event_name: str, details: dict):
if component_id in self._state.components:
component = self._state.components[component_id]
@@ -183,6 +205,7 @@ class WorkflowDesigner(BaseComponent):
return Div(
H1(f"{self._designer_settings.workflow_name}", cls="text-xl font-bold"),
P("Drag components from the toolbox to the canvas to create your workflow.", cls="text-sm mb-6"),
+ self._mk_media(),
self._mk_designer(),
Div(cls="wkf-splitter", id=f"s_{self._id}"),
self._mk_properties(),
@@ -259,6 +282,14 @@ class WorkflowDesigner(BaseComponent):
style=f"height:{self._state.designer_height}px;"
)
+ def _mk_media(self):
+ return Div(
+ mk_icon(icon_play, cls="mr-1", **self.commands.play_workflow()),
+ mk_icon(icon_pause, cls="mr-1", **self.commands.play_workflow()),
+ mk_icon(icon_stop, cls="mr-1", **self.commands.play_workflow()),
+ cls=f"media-controls flex m-2"
+ )
+
def _mk_processor_properties(self, component, processor_name):
if processor_name == "Jira":
return self._mk_jira_processor_details(component)
diff --git a/src/components/workflows/components/WorkflowPlayer.py b/src/components/workflows/components/WorkflowPlayer.py
new file mode 100644
index 0000000..0694575
--- /dev/null
+++ b/src/components/workflows/components/WorkflowPlayer.py
@@ -0,0 +1,51 @@
+from fasthtml.components import *
+
+from components.BaseComponent import BaseComponent
+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
+from components.workflows.db_management import WorkflowsPlayerSettings
+from core.utils import get_unique_id, make_safe_id
+
+grid_settings = DataGridSettings(
+ header_visible=True,
+ filter_all_visible=True,
+ views_visible=False,
+ open_file_visible=False,
+ open_settings_visible=False)
+
+
+class WorkflowPlayer(BaseComponent):
+ def __init__(self, session,
+ _id=None,
+ settings_manager=None,
+ tabs_manager=None,
+ player_settings: WorkflowsPlayerSettings = 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 = player_settings
+ self._boundaries = boundaries
+ self.commands = WorkflowPlayerCommandManager(self)
+ self._datagrid = DataGrid(self._session,
+ DataGrid.create_component_id(session),
+ self.key,
+ grid_settings=grid_settings,
+ boundaries=boundaries)
+
+ def __ft__(self):
+ return Div(
+ self._datagrid,
+ id=self._id,
+ )
+
+ @staticmethod
+ def create_component_id(session, suffix=None):
+ prefix = f"{WORKFLOW_PLAYER_INSTANCE_ID}{session['user_id']}"
+ if suffix is None:
+ suffix = get_unique_id()
+
+ return make_safe_id(f"{prefix}{suffix}")
diff --git a/src/components/workflows/components/Workflows.py b/src/components/workflows/components/Workflows.py
index 95c4dc1..3cd863f 100644
--- a/src/components/workflows/components/Workflows.py
+++ b/src/components/workflows/components/Workflows.py
@@ -101,6 +101,7 @@ class Workflows(BaseComponentSingleton):
WorkflowDesigner.create_component_id(self._session, workflow_name),
WorkflowDesigner,
settings_manager=self._settings_manager,
+ tabs_manager=self.tabs_manager,
key=workflow_name,
designer_settings=WorkflowsDesignerSettings(workflow_name=workflow_name),
boundaries=tab_boundaries)
diff --git a/src/components/workflows/constants.py b/src/components/workflows/constants.py
index 4688621..e8c56f7 100644
--- a/src/components/workflows/constants.py
+++ b/src/components/workflows/constants.py
@@ -1,5 +1,6 @@
WORKFLOWS_INSTANCE_ID = "__Workflows__"
WORKFLOW_DESIGNER_INSTANCE_ID = "__WorkflowDesigner__"
+WORKFLOW_PLAYER_INSTANCE_ID = "__WorkflowPlayer__"
WORKFLOWS_DB_ENTRY = "Workflows"
WORKFLOW_DESIGNER_DB_ENTRY = "WorkflowDesigner"
WORKFLOW_DESIGNER_DB_SETTINGS_ENTRY = "Settings"
@@ -27,4 +28,8 @@ class Routes:
SaveProperties = "/save-properties"
CancelProperties = "/cancel-properties"
SelectProcessor = "/select-processor"
- OnProcessorDetailsEvent = "/on-processor-details-event"
\ No newline at end of file
+ OnProcessorDetailsEvent = "/on-processor-details-event"
+ PlayWorkflow = "/play-workflow"
+ PauseWorkflow = "/pause-workflow"
+ StopWorkflow = "/stop-workflow"
+
\ No newline at end of file
diff --git a/src/components/workflows/db_management.py b/src/components/workflows/db_management.py
index e1cc9c4..a77c2cc 100644
--- a/src/components/workflows/db_management.py
+++ b/src/components/workflows/db_management.py
@@ -41,6 +41,11 @@ class WorkflowsDesignerState:
selected_component_id = None
+@dataclass
+class WorkflowsPlayerSettings:
+ workflow_name: str = "No Name"
+
+
@dataclass
class WorkflowsSettings:
workflows: list[str] = field(default_factory=list)