Adding workflow management
I
This commit is contained in:
@@ -10,3 +10,12 @@ icon_dismiss_regular = NotStr(
|
|||||||
</g>
|
</g>
|
||||||
</svg>"""
|
</svg>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Fluent Add16Regular
|
||||||
|
icon_add_regular = NotStr("""<svg name="addd" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
|
||||||
|
<g fill="none">
|
||||||
|
<path d="M8 2.5a.5.5 0 0 0-1 0V7H2.5a.5.5 0 0 0 0 1H7v4.5a.5.5 0 0 0 1 0V8h4.5a.5.5 0 0 0 0-1H8V2.5z" fill="currentColor">
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
""")
|
||||||
@@ -28,6 +28,21 @@
|
|||||||
transition: opacity 0.3s ease; /* No delay when becoming visible */
|
transition: opacity 0.3s ease; /* No delay when becoming visible */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mmt-visible-on-hover {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0s linear 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* When parent is hovered, show the child elements with this class */
|
||||||
|
*:hover > .mmt-visible-on-hover {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.icon-32 {
|
.icon-32 {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -65,7 +80,6 @@
|
|||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.icon-16 {
|
.icon-16 {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
@@ -82,7 +96,6 @@
|
|||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.icon-bool {
|
.icon-bool {
|
||||||
display: block;
|
display: block;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|||||||
@@ -34,3 +34,20 @@ class BaseComponent:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def create_component_id(session):
|
def create_component_id(session):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseComponentSingleton(BaseComponent):
|
||||||
|
"""
|
||||||
|
Base class for components that will have a single instance per user
|
||||||
|
"""
|
||||||
|
|
||||||
|
COMPONENT_INSTANCE_ID = None
|
||||||
|
|
||||||
|
def __init__(self, session, _id=None, settings_manager=None, tabs_manager=None, **kwargs):
|
||||||
|
super().__init__(session, _id, **kwargs)
|
||||||
|
self._settings_manager = settings_manager
|
||||||
|
self.tabs_manager = tabs_manager
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_component_id(cls, session):
|
||||||
|
return f"{cls.COMPONENT_INSTANCE_ID}{session['user_id']}"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from components.drawerlayout.assets.icons import icon_panel_contract_regular, ic
|
|||||||
from components.drawerlayout.constants import DRAWER_LAYOUT_INSTANCE_ID
|
from components.drawerlayout.constants import DRAWER_LAYOUT_INSTANCE_ID
|
||||||
from components.repositories.components.Repositories import Repositories
|
from components.repositories.components.Repositories import Repositories
|
||||||
from components.tabs.components.MyTabs import MyTabs
|
from components.tabs.components.MyTabs import MyTabs
|
||||||
|
from components.workflows.components.Workflows import Workflows
|
||||||
from core.instance_manager import InstanceManager
|
from core.instance_manager import InstanceManager
|
||||||
from core.settings_management import SettingsManager
|
from core.settings_management import SettingsManager
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ class DrawerLayout(BaseComponent):
|
|||||||
self._settings_manager = settings_manager
|
self._settings_manager = settings_manager
|
||||||
self._tabs = InstanceManager.get(session, MyTabs.create_component_id(session), MyTabs)
|
self._tabs = InstanceManager.get(session, MyTabs.create_component_id(session), MyTabs)
|
||||||
self._repositories = self._create_component(Repositories)
|
self._repositories = self._create_component(Repositories)
|
||||||
|
self._workflows = self._create_component(Workflows)
|
||||||
self._debugger = self._create_component(Debugger)
|
self._debugger = self._create_component(Debugger)
|
||||||
self._add_stuff = self._create_component(AddStuffMenu)
|
self._add_stuff = self._create_component(AddStuffMenu)
|
||||||
self._ai_buddy = self._create_component(AIBuddy)
|
self._ai_buddy = self._create_component(AIBuddy)
|
||||||
@@ -41,6 +43,7 @@ class DrawerLayout(BaseComponent):
|
|||||||
self._ai_buddy,
|
self._ai_buddy,
|
||||||
self._applications,
|
self._applications,
|
||||||
self._repositories,
|
self._repositories,
|
||||||
|
self._workflows,
|
||||||
self._admin,
|
self._admin,
|
||||||
self._debugger,
|
self._debugger,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from core.settings_management import SettingsManager
|
|||||||
|
|
||||||
REPOSITORIES_SETTINGS_ENTRY = "Repositories"
|
REPOSITORIES_SETTINGS_ENTRY = "Repositories"
|
||||||
|
|
||||||
logger = logging.getLogger("AddStuffSettings")
|
logger = logging.getLogger("RepositoriesSettings")
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class MyTabs(BaseComponent):
|
|||||||
|
|
||||||
def render(self, oob=False):
|
def render(self, oob=False):
|
||||||
active_content = self.get_active_tab_content()
|
active_content = self.get_active_tab_content()
|
||||||
if hasattr(active_content, "on_htmx_after_settle"):
|
if hasattr(active_content, "on_htmx_after_settle") and active_content.on_htmx_after_settle is not None:
|
||||||
extra_params = {"hx-on::after-settle": active_content.on_htmx_after_settle()}
|
extra_params = {"hx-on::after-settle": active_content.on_htmx_after_settle()}
|
||||||
else:
|
else:
|
||||||
extra_params = {}
|
extra_params = {}
|
||||||
|
|||||||
26
src/components/workflows/WorkflowsApp.py
Normal file
26
src/components/workflows/WorkflowsApp.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from fasthtml.fastapp import fast_app
|
||||||
|
|
||||||
|
from components.workflows.constants import Routes
|
||||||
|
from core.instance_manager import InstanceManager, debug_session
|
||||||
|
|
||||||
|
logger = logging.getLogger("WorkflowsApp")
|
||||||
|
|
||||||
|
repositories_app, rt = fast_app()
|
||||||
|
|
||||||
|
|
||||||
|
@rt(Routes.AddWorkflow)
|
||||||
|
def get(session, _id: str):
|
||||||
|
logger.debug(f"Entering {Routes.AddWorkflow} with args {debug_session(session)}, {_id=}")
|
||||||
|
instance = InstanceManager.get(session, _id)
|
||||||
|
return instance.request_new_workflow()
|
||||||
|
|
||||||
|
|
||||||
|
@rt(Routes.AddWorkflow)
|
||||||
|
def post(session, _id: str, tab_id: str, form_id: str, name: str, tab_boundaries: str):
|
||||||
|
logger.debug(
|
||||||
|
f"Entering {Routes.AddWorkflow} with args {debug_session(session)}, {_id=}, {tab_id=}, {form_id=}, {name=}, {tab_boundaries=}")
|
||||||
|
instance = InstanceManager.get(session, _id)
|
||||||
|
return instance.add_new_workflow(tab_id, form_id, name, json.loads(tab_boundaries))
|
||||||
0
src/components/workflows/__init__.py
Normal file
0
src/components/workflows/__init__.py
Normal file
0
src/components/workflows/assets/__init__.py
Normal file
0
src/components/workflows/assets/__init__.py
Normal file
22
src/components/workflows/commands.py
Normal file
22
src/components/workflows/commands.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from components.BaseCommandManager import BaseCommandManager
|
||||||
|
from components.workflows.constants import Routes, ROUTE_ROOT
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowsCommandManager(BaseCommandManager):
|
||||||
|
def __init__(self, owner):
|
||||||
|
super().__init__(owner)
|
||||||
|
|
||||||
|
def request_add_workflow(self):
|
||||||
|
return {
|
||||||
|
"hx-get": f"{ROUTE_ROOT}{Routes.AddWorkflow}",
|
||||||
|
"hx-target": f"#{self._owner.tabs_manager.get_id()}",
|
||||||
|
"hx-swap": "outerHTML",
|
||||||
|
"hx-vals": f'{{"_id": "{self._id}"}}',
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_workflow(self, tab_id: str):
|
||||||
|
return {
|
||||||
|
"hx-post": f"{ROUTE_ROOT}{Routes.AddWorkflow}",
|
||||||
|
"hx-target": f"#w_{self._id}",
|
||||||
|
"hx-vals": f'js:{{"_id": "{self._id}", "tab_id": "{tab_id}", "tab_boundaries": getTabContentBoundaries("{self._owner.tabs_manager.get_id()}")}}',
|
||||||
|
}
|
||||||
100
src/components/workflows/components/Workflows.py
Normal file
100
src/components/workflows/components/Workflows.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from fasthtml.components import *
|
||||||
|
|
||||||
|
from assets.icons import icon_add_regular
|
||||||
|
from components.BaseComponent import BaseComponentSingleton
|
||||||
|
from components.form.components.MyForm import MyForm, FormField
|
||||||
|
from components.workflows.commands import WorkflowsCommandManager
|
||||||
|
from components.workflows.constants import WORKFLOWS_INSTANCE_ID
|
||||||
|
from components.workflows.db_management import WorkflowsDbManager
|
||||||
|
from components_helpers import mk_ellipsis, mk_icon
|
||||||
|
from core.instance_manager import InstanceManager
|
||||||
|
|
||||||
|
logger = logging.getLogger("Workflows")
|
||||||
|
|
||||||
|
|
||||||
|
class Workflows(BaseComponentSingleton):
|
||||||
|
COMPONENT_INSTANCE_ID = WORKFLOWS_INSTANCE_ID
|
||||||
|
|
||||||
|
def __init__(self, session, _id, settings_manager=None, tabs_manager=None):
|
||||||
|
super().__init__(session, _id, settings_manager, tabs_manager)
|
||||||
|
self.commands = WorkflowsCommandManager(self)
|
||||||
|
self.db = WorkflowsDbManager(session, settings_manager)
|
||||||
|
|
||||||
|
def request_new_workflow(self):
|
||||||
|
# request for a new tab_id
|
||||||
|
new_tab_id = self.tabs_manager.request_new_tab_id()
|
||||||
|
|
||||||
|
# create a new form to ask for the details of the new database
|
||||||
|
add_workflow_form = self._mk_add_workflow_form(new_tab_id)
|
||||||
|
|
||||||
|
# create and display the form in a new tab
|
||||||
|
self.tabs_manager.add_tab("Add Workflow", add_workflow_form, tab_id=new_tab_id)
|
||||||
|
return self.tabs_manager
|
||||||
|
|
||||||
|
def add_new_workflow(self, tab_id: str, form_id: str, workflow_name: str, tab_boundaries: dict):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param tab_id: tab id where the table content will be displayed (and where the form was displayed)
|
||||||
|
:param form_id: form used to give the repository name (to be used in case of error)
|
||||||
|
:param workflow_name: new workflow name
|
||||||
|
:param tab_boundaries: tab boundaries
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Add the new repository and its default table to the list of repositories
|
||||||
|
self.db.add_workflow(workflow_name)
|
||||||
|
|
||||||
|
# update the tab content with table content
|
||||||
|
self.tabs_manager.set_tab_content(tab_id,
|
||||||
|
self._get_workflow_designer(workflow_name, tab_boundaries),
|
||||||
|
title=workflow_name,
|
||||||
|
key=workflow_name,
|
||||||
|
active=True)
|
||||||
|
|
||||||
|
return self._mk_workflows(), self.tabs_manager.refresh()
|
||||||
|
|
||||||
|
except ValueError as ex:
|
||||||
|
logger.error(f" Workflow '{workflow_name}' already exists.")
|
||||||
|
add_repository_form = InstanceManager.get(self._session, form_id)
|
||||||
|
add_repository_form.set_error(ex)
|
||||||
|
|
||||||
|
return self.tabs_manager.refresh()
|
||||||
|
|
||||||
|
def __ft__(self):
|
||||||
|
return Div(
|
||||||
|
Div(cls="divider"),
|
||||||
|
Div(
|
||||||
|
mk_ellipsis("Workflows", cls="text-sm font-medium mb-1"),
|
||||||
|
mk_icon(icon_add_regular,
|
||||||
|
size=16,
|
||||||
|
tooltip="Add Workflow",
|
||||||
|
cls="ml-2 mmt-visible-on-hover",
|
||||||
|
**self.commands.request_add_workflow()),
|
||||||
|
cls="flex"
|
||||||
|
),
|
||||||
|
self._mk_workflows(),
|
||||||
|
id=f"{self._id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_workflow_designer(self, workflow_name: str, tab_boundaries: dict):
|
||||||
|
return Div(f"Workflow Designer for {workflow_name}")
|
||||||
|
|
||||||
|
def _mk_add_workflow_form(self, tab_id: str):
|
||||||
|
return InstanceManager.get(self._session, MyForm.create_component_id(self._session), MyForm,
|
||||||
|
title="Add Workflow",
|
||||||
|
fields=[FormField("name", 'Workflow Name', 'input')],
|
||||||
|
htmx_request=self.commands.add_workflow(tab_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _mk_workflow(self, workflow_name: str, selected: bool):
|
||||||
|
return mk_ellipsis(workflow_name, cls="text-sm")
|
||||||
|
|
||||||
|
def _mk_workflows(self, oob=False):
|
||||||
|
return Div(
|
||||||
|
*[self._mk_workflow(workflow_name, workflow_name == self.db.get_selected_workflow())
|
||||||
|
for workflow_name in self.db.get_workflows()],
|
||||||
|
id=f"w_{self._id}",
|
||||||
|
hx_swap_oob="true" if oob else None,
|
||||||
|
)
|
||||||
0
src/components/workflows/components/__init__.py
Normal file
0
src/components/workflows/components/__init__.py
Normal file
8
src/components/workflows/constants.py
Normal file
8
src/components/workflows/constants.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
WORKFLOWS_INSTANCE_ID = "__Workflows__"
|
||||||
|
WORKFLOWS_SETTINGS_ENTRY = "Workflows"
|
||||||
|
ROUTE_ROOT = "/workflows"
|
||||||
|
|
||||||
|
|
||||||
|
class Routes:
|
||||||
|
AddWorkflow = "/add-workflow"
|
||||||
|
SelectWorkflow = "/select-workflow"
|
||||||
99
src/components/workflows/db_management.py
Normal file
99
src/components/workflows/db_management.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import logging
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from components.workflows.constants import WORKFLOWS_SETTINGS_ENTRY
|
||||||
|
from core.settings_management import SettingsManager
|
||||||
|
|
||||||
|
logger = logging.getLogger("WorkflowsSettings")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WorkflowsSettings:
|
||||||
|
workflows: list[str] = field(default_factory=list)
|
||||||
|
selected_workflow: str = None
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowsDbManager:
|
||||||
|
def __init__(self, session: dict, settings_manager: SettingsManager):
|
||||||
|
self.session = session
|
||||||
|
self.settings_manager = settings_manager
|
||||||
|
|
||||||
|
def add_workflow(self, workflow_name: str):
|
||||||
|
settings = self._get_settings()
|
||||||
|
|
||||||
|
if not workflow_name:
|
||||||
|
raise ValueError("Workflow name cannot be empty.")
|
||||||
|
|
||||||
|
if workflow_name in settings.workflows:
|
||||||
|
raise ValueError(f"Workflow '{workflow_name}' already exists.")
|
||||||
|
|
||||||
|
settings.workflows.append(workflow_name)
|
||||||
|
self.settings_manager.save(self.session, WORKFLOWS_SETTINGS_ENTRY, settings)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_workflow(self, workflow_name: str):
|
||||||
|
if not workflow_name:
|
||||||
|
raise ValueError("Workflow name cannot be empty.")
|
||||||
|
|
||||||
|
settings = self._get_settings()
|
||||||
|
if workflow_name not in settings.workflows:
|
||||||
|
raise ValueError(f"Workflow '{workflow_name}' does not exist.")
|
||||||
|
|
||||||
|
return next(filter(lambda r: r == workflow_name, settings.workflows))
|
||||||
|
|
||||||
|
# def modify_workflow(self, old_workflow_name, new_workflow_name: str, tables: list[str]):
|
||||||
|
# if not old_workflow_name or not new_workflow_name:
|
||||||
|
# raise ValueError("Workflow name cannot be empty.")
|
||||||
|
#
|
||||||
|
# settings = self._get_settings()
|
||||||
|
# for workflow in settings.workflows:
|
||||||
|
# if workflow == old_workflow_name:
|
||||||
|
# workflow.name = new_workflow_name
|
||||||
|
# workflow.tables = tables
|
||||||
|
#
|
||||||
|
# self.settings_manager.save(self.session, workflows_SETTINGS_ENTRY, settings)
|
||||||
|
# return workflow
|
||||||
|
#
|
||||||
|
# else:
|
||||||
|
# raise ValueError(f"workflow '{old_workflow_name}' not found.")
|
||||||
|
|
||||||
|
def remove_workflow(self, workflow_name):
|
||||||
|
if not workflow_name:
|
||||||
|
raise ValueError("Workflow name cannot be empty.")
|
||||||
|
|
||||||
|
settings = self._get_settings()
|
||||||
|
if workflow_name not in settings.workflows:
|
||||||
|
raise ValueError(f"workflow '{workflow_name}' does not exist.")
|
||||||
|
|
||||||
|
settings.workflows.remove(workflow_name)
|
||||||
|
self.settings_manager.save(self.session, WORKFLOWS_SETTINGS_ENTRY, settings)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def exists_workflow(self, workflow_name):
|
||||||
|
if not workflow_name:
|
||||||
|
raise ValueError("workflow name cannot be empty.")
|
||||||
|
|
||||||
|
settings = self._get_settings()
|
||||||
|
return workflow_name in settings.workflows
|
||||||
|
|
||||||
|
def get_workflows(self):
|
||||||
|
return self._get_settings().workflows
|
||||||
|
|
||||||
|
def select_workflow(self, workflow_name: str):
|
||||||
|
"""
|
||||||
|
Select and save the specified workflow name in the current session's settings.
|
||||||
|
|
||||||
|
:param workflow_name: The name of the workflow to be selected and stored.
|
||||||
|
:type workflow_name: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
settings = self._get_settings()
|
||||||
|
settings.selected_workflow = workflow_name
|
||||||
|
self.settings_manager.save(self.session, WORKFLOWS_SETTINGS_ENTRY, settings)
|
||||||
|
|
||||||
|
def get_selected_workflow(self):
|
||||||
|
settings = self._get_settings()
|
||||||
|
return settings.selected_workflow
|
||||||
|
|
||||||
|
def _get_settings(self):
|
||||||
|
return self.settings_manager.load(self.session, WORKFLOWS_SETTINGS_ENTRY, default=WorkflowsSettings())
|
||||||
@@ -148,6 +148,7 @@ register_component("main_layout", "components.drawerlayout", "DrawerLayoutApp")
|
|||||||
register_component("tabs", "components.tabs", "TabsApp") # before repositories
|
register_component("tabs", "components.tabs", "TabsApp") # before repositories
|
||||||
register_component("applications", "components.applications", "ApplicationsApp")
|
register_component("applications", "components.applications", "ApplicationsApp")
|
||||||
register_component("repositories", "components.repositories", "RepositoriesApp")
|
register_component("repositories", "components.repositories", "RepositoriesApp")
|
||||||
|
register_component("workflows", "components.workflows", "WorkflowsApp")
|
||||||
register_component("add_stuff", "components.addstuff", None)
|
register_component("add_stuff", "components.addstuff", None)
|
||||||
register_component("form", "components.form", "FormApp")
|
register_component("form", "components.form", "FormApp")
|
||||||
register_component("datagrid_new", "components.datagrid_new", "DataGridApp")
|
register_component("datagrid_new", "components.datagrid_new", "DataGridApp")
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ class ComponentsInstancesHelper:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_repositories(session):
|
def get_repositories(session):
|
||||||
return InstanceManager.get(session, Repositories.create_component_id(session))
|
return InstanceManager.get(session, Repositories.create_component_id(session))
|
||||||
|
|
||||||
Reference in New Issue
Block a user