I can add tables

Refactoring DbEngine

Fixing unit tests

Fixing unit tests

Fixing unit tests

Refactored DbManager for datagrid

Improving front end performance

I can add new table

Fixed sidebar closing when clicking on it

Fix drag event rebinding, improve listener options, and add debug

Prevent duplicate drag event bindings with a dataset flag and ensure consistent scrollbar functionality. Change wheel event listener to passive mode for better performance. Refactor function naming for consistency, and add debug logs for event handling.

Refactor Datagrid bindings and default state handling.

Updated Javascript to conditionally rebind Datagrid on specific events. Improved Python components by handling empty DataFrame cases and removing redundant code. Revised default state initialization in settings for better handling of mutable fields.

Added Rowindex visualisation support

Working on Debugger with own implementation of JsonViewer

Working on JsonViewer.py

Fixed unit tests

Adding unit tests

I can fold and unfold

fixed unit tests

Adding css for debugger

Added tooltip management

Adding debugger functionalities

Refactor serializers and improve error handling in DB engine

Fixed error where tables were overwritten

I can display footer menu

Working on footer. Refactoring how heights are managed

Refactored scrollbars management

Working on footer menu

I can display footer menu + fixed unit tests

Fixed unit tests

Updated click management

I can display aggregations in footers

Added docker management

Refactor input handling and improve config defaults

Fixed scrollbars colors

Refactored tooltip management

Improved tooltip management

Improving FilterAll
This commit is contained in:
2025-05-11 18:27:32 +02:00
parent e1c10183eb
commit 66ea45f501
70 changed files with 2884 additions and 1258 deletions

View File

@@ -1,52 +0,0 @@
import logging
from fasthtml.fastapp import fast_app
from components.addstuff.components.Repositories import Repositories
from components.addstuff.constants import Routes
from core.instance_manager import InstanceManager, debug_session
logger = logging.getLogger("AddStuffApp")
add_stuff_app, rt = fast_app()
@rt(Routes.AddRepository)
def get(session):
_id = Repositories.create_component_id(session) # there is only one instance of Repositories
instance = InstanceManager.get(session, _id)
return instance.request_new_repository()
@rt(Routes.AddRepository)
def post(session, _id: str, tab_id: str, form_id: str, repository: str, table: str):
logger.debug(
f"Entering {Routes.AddRepository} with args {debug_session(session)}, {_id=}, {tab_id=}, {form_id=}, {repository=}, {table=}")
instance = InstanceManager.get(session, _id) # Repository
return instance.add_new_repository(tab_id, form_id, repository, table)
@rt(Routes.AddTable)
def get(session, _id: str, repository_name: str):
instance = InstanceManager.get(session, _id)
return instance.request_new_table(repository_name)
@rt(Routes.AddTable)
def post(session, _id: str, tab_id: str, form_id: str, repository_name: str, table_name: str):
instance = InstanceManager.get(session, _id)
return instance.add_new_table(tab_id, form_id, repository_name, table_name)
@rt(Routes.SelectRepository)
def put(session, _id: str, repository: str):
logger.debug(f"Entering {Routes.SelectRepository} with args {debug_session(session)}, {_id=}, {repository=}")
instance = InstanceManager.get(session, _id)
return instance.select_repository(repository)
@rt(Routes.ShowTable)
def get(session, _id: str, repository: str, table: str):
logger.debug(f"Entering {Routes.ShowTable} with args {debug_session(session)}, {_id=}, {repository=}, {table=}")
instance = InstanceManager.get(session, _id)
return instance.show_table(repository, table)

View File

@@ -1,3 +0,0 @@
function bindRepositories(repositoryId) {
bindTooltipsWithDelegation(repositoryId)
}

View File

@@ -1,21 +0,0 @@
from fastcore.basics import NotStr
# Fluent Database20Regular
icon_database = NotStr("""
<svg name="database" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20">
<g fill="none">
<path d="M4 5c0-1.007.875-1.755 1.904-2.223C6.978 2.289 8.427 2 10 2s3.022.289 4.096.777C15.125 3.245 16 3.993 16 5v10c0 1.007-.875 1.755-1.904 2.223C13.022 17.71 11.573 18 10 18s-3.022-.289-4.096-.777C4.875 16.755 4 16.007 4 15V5zm1 0c0 .374.356.875 1.318 1.313C7.234 6.729 8.536 7 10 7s2.766-.27 3.682-.687C14.644 5.875 15 5.373 15 5c0-.374-.356-.875-1.318-1.313C12.766 3.271 11.464 3 10 3s-2.766.27-3.682.687C5.356 4.125 5 4.627 5 5zm10 1.698a4.92 4.92 0 0 1-.904.525C13.022 7.711 11.573 8 10 8s-3.022-.289-4.096-.777A4.92 4.92 0 0 1 5 6.698V15c0 .374.356.875 1.318 1.313c.916.416 2.218.687 3.682.687s2.766-.27 3.682-.687C14.644 15.875 15 15.373 15 15V6.698z" fill="currentColor">
</path>
</g>
</svg>
""")
# Fluent Table20Regular
icon_table = NotStr("""
<svg name="table" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 20">
<g fill="none">
<path d="M17 5.5A2.5 2.5 0 0 0 14.5 3h-9A2.5 2.5 0 0 0 3 5.5v9A2.5 2.5 0 0 0 5.5 17h9a2.5 2.5 0 0 0 2.5-2.5v-9zm-13 9V13h3v3H5.5l-.144-.007A1.5 1.5 0 0 1 4 14.5zm8-1.5v3H8v-3h4zm2.5 3H13v-3h3v1.5l-.007.145A1.5 1.5 0 0 1 14.5 16zM12 8v4H8V8h4zm1 0h3v4h-3V8zm-1-4v3H8V4h4zm1 0h1.5l.145.007A1.5 1.5 0 0 1 16 5.5V7h-3V4zM7 4v3H4V5.5l.007-.144A1.5 1.5 0 0 1 5.5 4H7zm0 4v4H4V8h3z" fill="currentColor">
</path>
</g>
</svg>
""")

View File

@@ -1,23 +0,0 @@
from components.addstuff.constants import ROUTE_ROOT, Routes
class Commands:
def __init__(self, owner):
self._owner = owner
self._id = owner.get_id()
def request_add_table(self, repository_name):
return {
"hx-get": f"{ROUTE_ROOT}{Routes.AddTable}",
"hx-target": f"#{self._owner.tabs_manager.get_id()}",
"hx-swap": "outerHTML",
"hx-vals": f'{{"_id": "{self._id}", "repository_name": "{repository_name}"}}',
}
def add_table(self):
return {
"hx-post": f"{ROUTE_ROOT}{Routes.AddTable}",
"hx-target": f"#{self._owner.tabs_manager.get_id()}",
"hx-swap": "outerHTML",
# The repository_name and the table_name will be given by the form
}

View File

@@ -1,26 +1,22 @@
from fasthtml.components import *
from components.BaseComponent import BaseComponent
from components.addstuff.constants import ADD_STUFF_INSTANCE_ID, ROUTE_ROOT, Routes
from components.addstuff.settings import RepositoriesDbManager
from components.addstuff.constants import ADD_STUFF_INSTANCE_ID
from components.repositories.components.Repositories import Repositories
from core.instance_manager import InstanceManager
class AddStuffMenu(BaseComponent):
def __init__(self, session: dict, _id: str, settings_manager=None, tabs_manager=None):
super().__init__(session, _id)
self.tabs_manager = tabs_manager # MyTabs component id
self.mappings = {} # to keep track of when element is displayed on which tab
self.settings = RepositoriesDbManager(session, settings_manager)
self.repositories = InstanceManager.get(session, Repositories.create_component_id(session), Repositories)
def __ft__(self):
return Div(
Div("Add stuff...", tabindex="0"),
Ul(
Li(A("Add Database",
hx_get=f"{ROUTE_ROOT}{Routes.AddRepository}",
hx_target=f"#{self.tabs_manager.get_id()}",
hx_swap="outerHTML",
)),
Li(A("Add Database", **self.repositories.commands.request_add_repository())),
Li(A("Add Application")),
tabindex="0",
cls="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"

View File

@@ -1,209 +0,0 @@
import logging
from fasthtml.components import *
from fasthtml.xtend import Script
from components.BaseComponent import BaseComponent
from components.addstuff.assets.icons import icon_database, icon_table
from components.addstuff.commands import Commands
from components.addstuff.constants import REPOSITORIES_INSTANCE_ID, ROUTE_ROOT, Routes
from components.addstuff.settings import RepositoriesDbManager, Repository
from components.datagrid_new.components.DataGrid import DataGrid
from components.form.components.MyForm import MyForm, FormField
from components_helpers import mk_icon, mk_ellipsis, mk_tooltip_container
from core.instance_manager import InstanceManager
logger = logging.getLogger("Repositories")
class Repositories(BaseComponent):
def __init__(self, session: dict, _id: str, settings_manager=None, tabs_manager=None):
super().__init__(session, _id)
self._settings_manager = settings_manager
self.db = RepositoriesDbManager(session, settings_manager)
self.tabs_manager = tabs_manager
self._contents = {} # ket tracks of already displayed contents
self._commands = Commands(self)
def request_new_repository(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_repository_form = self._mk_add_repository_form(new_tab_id)
# create and display the form in a new tab
self.tabs_manager.add_tab("Add Database", add_repository_form, tab_id=new_tab_id)
return self.tabs_manager
def request_new_table(self, repository_name: str):
# 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_table_form = self._mk_add_table_form(new_tab_id, repository_name)
# create and display the form in a new tab
self.tabs_manager.add_tab("Add New Table", add_table_form, tab_id=new_tab_id)
return self.tabs_manager
def add_new_repository(self, tab_id: str, form_id: str, repository_name: str, table_name: str):
"""
: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 repository_name: new repository name
:param table_name: default table name
:return:
"""
try:
# Add the new repository and its default table to the list of repositories
tables = [MyTable(table_name, {})] if table_name else []
repository = self.db.add_repository(repository_name, tables)
# update the tab content with table content
key = (repository_name, table_name)
self.tabs_manager.set_tab_content(tab_id,
self._get_table_content(key),
title=table_name,
key=key,
active=True)
return self._mk_repository(repository, True), self.tabs_manager.refresh()
except ValueError as ex:
logger.debug(f" Repository '{repository_name}' already exists.")
add_repository_form = InstanceManager.get(self._session, form_id)
add_repository_form.set_error(ex)
return self.tabs_manager.refresh()
def add_new_table(self, tab_id: str, form_id: str, repository_name: str, table_name: str):
"""
: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 repository_name: new repository name
:param table_name: default table name
:return:
"""
try:
self.db.add_table(repository_name, table_name, {})
repository = self.db.get_repository(repository_name)
# update the tab content with table content
key = (repository_name, table_name)
self.tabs_manager.set_tab_content(tab_id,
self._get_table_content(key),
title=table_name,
key=key,
active=True)
return self._mk_repository(repository, True), self.tabs_manager.refresh()
except ValueError as ex:
logger.debug(f" Repository '{repository_name}' already exists.")
add_repository_form = InstanceManager.get(self._session, form_id)
add_repository_form.set_error(ex)
return self.tabs_manager.refresh()
def select_repository(self, repository_name: str):
self.db.select_repository(repository_name)
def show_table(self, repository_name: str, table_name: str):
key = (repository_name, table_name)
self.tabs_manager.add_tab(table_name, self._get_table_content(key), key)
return self.tabs_manager
def refresh(self):
return self._mk_repositories(oob=True)
def __ft__(self):
return Div(
mk_tooltip_container(self._id),
Div(cls="divider"),
mk_ellipsis("Repositories", cls="text-sm font-medium mb-1"),
self._mk_repositories(),
Script(f"bindRepositories('{self._id}')")
)
def _mk_repositories(self, oob=False):
settings = self.db._get_settings()
return Div(
*[self._mk_repository(repo, repo.name == settings.selected_repository_name)
for repo in settings.repositories],
id=self._id,
hx_swap_oob="true" if oob else None,
)
def _mk_repository(self, repo: Repository, selected):
return Div(
Input(type="radio",
name=f"repo-accordion-{self._id}",
checked="checked" if selected else None,
cls="p-0! min-h-0!",
hx_put=f"{ROUTE_ROOT}{Routes.SelectRepository}",
hx_vals=f'{{"_id": "{self._id}", "repository": "{repo.name}"}}',
# hx_trigger="changed delay:500ms",
),
Div(
mk_icon(icon_database, can_select=False), mk_ellipsis(repo.name),
cls="collapse-title p-0 min-h-0 flex truncate",
),
Div(
*[
Div(mk_icon(icon_table, can_select=False), mk_ellipsis(table.name),
name="repo-table",
hx_get=f"{ROUTE_ROOT}{Routes.ShowTable}",
hx_target=f"#{self.tabs_manager.get_id()}",
hx_swap="outerHTML",
hx_vals=f'{{"_id": "{self._id}", "repository": "{repo.name}", "table": "{table.name}"}}',
cls="flex")
for table in repo.tables
],
Div("+ Add Table", **self._commands.request_add_table(repo.name)),
cls="collapse-content pr-0! truncate",
),
tabindex="0", cls="collapse mb-2")
def _mk_add_repository_form(self, tab_id: str):
htmx_params = {
"hx-post": f"{ROUTE_ROOT}{Routes.AddRepository}",
"hx-target": f"#{self._id}",
"hx-swap": "beforeend",
}
return InstanceManager.get(self._session, MyForm.create_component_id(self._session), MyForm,
title="Add Repository",
fields=[FormField("repository", 'Repository Name', 'input'),
FormField("table", 'First Table Name', 'input')],
htmx_params=htmx_params,
extra_values={"_id": self._id, "tab_id": tab_id})
def _mk_add_table_form(self, tab_id: str, repository_name: str = None):
htmx_request = self._commands.add_table()
return InstanceManager.get(self._session, MyForm.create_component_id(self._session), MyForm,
title="Add Table",
fields=[FormField("repository_name", 'Repository Name', 'input',
value=repository_name,
disabled=True),
FormField("table_name", 'Table Name', 'input')],
htmx_request=htmx_request,
extra_values={"_id": self._id, "tab_id": tab_id, "repository_name": repository_name})
def _get_table_content(self, key):
if key in self._contents:
return self._contents[key]
dg = InstanceManager.get(self._session,
DataGrid.create_component_id(self._session),
DataGrid,
settings_manager=self._settings_manager,
key=key)
self._contents[key] = dg
return dg
@staticmethod
def create_component_id(session):
return f"{REPOSITORIES_INSTANCE_ID}{session['user_id']}"

View File

@@ -1,10 +1 @@
ADD_STUFF_INSTANCE_ID = "__AddStuff__"
ADD_DATABASE_INSTANCE_ID = "__AddDatabase__"
REPOSITORIES_INSTANCE_ID = "__Repositories__"
ROUTE_ROOT = "/add"
class Routes:
AddRepository = "/add-repository"
SelectRepository = "/select-repository"
AddTable = "/add-table"
ShowTable = "/show-table"

View File

@@ -1,112 +0,0 @@
import dataclasses
import logging
from core.settings_management import SettingsManager
from core.settings_objects import BaseSettingObj
ADD_STUFF_SETTINGS_ENTRY = "AddStuffSettings"
REPOSITORIES_SETTINGS_ENTRY = "Repositories"
logger = logging.getLogger("AddStuffSettings")
@dataclasses.dataclass
class Repository:
name: str
tables: list[str]
@dataclasses.dataclass
class RepositoriesSettings:
repositories: list[Repository] = dataclasses.field(default_factory=list)
selected_repository_name: str = None
class RepositoriesDbManager:
def __init__(self, session: dict, settings_manager: SettingsManager):
self.session = session
self.settings_manager = settings_manager
def _get_settings(self):
return self.settings_manager.get(self.session, REPOSITORIES_SETTINGS_ENTRY, default=RepositoriesSettings())
def add_repository(self, repository_name: str, tables: list[str] = None):
"""
Adds a new repository to the list of repositories. The repository is identified
by its name and can optionally include a list of associated tables.
:param repository_name: The name of the repository to be added.
:param tables: A list of Table objects to be associated with the repository,
defaulting to an empty list if not provided.
:type tables: list[Table], optional
:return: None
"""
settings = self._get_settings()
if repository_name is None or repository_name == "":
raise ValueError("Repository name cannot be empty.")
if repository_name in [repo.name for repo in settings.repositories]:
raise ValueError(f"Repository '{repository_name}' already exists.")
existing_repositories = [r.name for r in settings.repositories]
logger.info(f"Existing repositories:{existing_repositories}")
repository = Repository(repository_name, tables or [])
settings.repositories.append(repository)
self.settings_manager.put(self.session, REPOSITORIES_SETTINGS_ENTRY, settings)
return repository
def get_repository(self, repository_name: str):
if repository_name is None or repository_name == "":
raise ValueError("Repository name cannot be empty.")
settings = self._get_settings()
if repository_name not in [repo.name for repo in settings.repositories]:
raise ValueError(f"Repository '{repository_name}' does not exists.")
return next(filter(lambda r: r.name == repository_name, settings.repositories))
def modify_repository(self, repository_name: str, tables: list[str]):
repository = self.get_repository(repository_name)
def get_repositories(self):
return self._get_settings().repositories
def add_table(self, repository_name: str, table_name: str, table_settings: dict):
"""
Adds a table to the specified repository
:param repository_name: The name of the target repository.
:param table_name: The name of the table to add.
:param table_settings: A dictionary containing the settings or configuration details
of the table.
:return: None
"""
settings = self._get_settings()
repository = next(filter(lambda r: r.name == repository_name, settings.repositories), None)
if repository is None:
raise ValueError(f"Repository '{repository_name}' does not exists.")
if table_name in (t.name for t in repository.tables):
raise ValueError(f"Table '{table_name}' already exists.")
repository.tables.append(MyTable(table_name, table_settings))
self.settings_manager.put(self.session, ADD_STUFF_SETTINGS_ENTRY, settings)
def select_repository(self, repository_name: str):
"""
Select and save the specified repository name in the current session's settings.
:param repository_name: The name of the repository to be selected and stored.
:type repository_name: str
:return: None
"""
settings = self._get_settings()
settings.selected_repository_name = repository_name
self.settings_manager.put(self.session, ADD_STUFF_SETTINGS_ENTRY, settings)