From c96d50a3f4d33698349641f75036712c946e036c Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 20 Jan 2026 19:31:17 +0300 Subject: [PATCH] fix(backend): standardize superset client init and auth - Update plugins (debug, mapper, search) to explicitly map environment config to SupersetConfig - Add authenticate method to SupersetClient for explicit session management - Add get_environment method to ConfigManager - Fix navbar dropdown hover stability in frontend with invisible bridge --- backend/delete_running_tasks.py | 35 +++++++++ backend/src/core/config_manager.py | 14 ++++ backend/src/core/superset_client.py | 10 ++- backend/src/plugins/debug.py | 28 +++++++- backend/src/plugins/mapper.py | 14 +++- backend/src/plugins/search.py | 14 +++- backend/tasks.db | Bin 45056 -> 53248 bytes backend/test_fix.py | 99 ++++++++++++++++++++++++++ frontend/src/components/Navbar.svelte | 4 +- superset_tool/client.py | 8 +++ 10 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 backend/delete_running_tasks.py create mode 100644 backend/test_fix.py diff --git a/backend/delete_running_tasks.py b/backend/delete_running_tasks.py new file mode 100644 index 0000000..927bc7a --- /dev/null +++ b/backend/delete_running_tasks.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Script to delete tasks with RUNNING status from the database.""" + +from sqlalchemy.orm import Session +from src.core.database import TasksSessionLocal +from src.models.task import TaskRecord + +def delete_running_tasks(): + """Delete all tasks with RUNNING status from the database.""" + session: Session = TasksSessionLocal() + try: + # Find all task records with RUNNING status + running_tasks = session.query(TaskRecord).filter(TaskRecord.status == "RUNNING").all() + + if not running_tasks: + print("No RUNNING tasks found.") + return + + print(f"Found {len(running_tasks)} RUNNING tasks:") + for task in running_tasks: + print(f"- Task ID: {task.id}, Type: {task.type}") + + # Delete the found tasks + session.query(TaskRecord).filter(TaskRecord.status == "RUNNING").delete(synchronize_session=False) + session.commit() + + print(f"Successfully deleted {len(running_tasks)} RUNNING tasks.") + except Exception as e: + session.rollback() + print(f"Error deleting tasks: {e}") + finally: + session.close() + +if __name__ == "__main__": + delete_running_tasks() diff --git a/backend/src/core/config_manager.py b/backend/src/core/config_manager.py index 7a578f7..741aeb6 100755 --- a/backend/src/core/config_manager.py +++ b/backend/src/core/config_manager.py @@ -186,6 +186,20 @@ class ConfigManager: return len(self.config.environments) > 0 # [/DEF:has_environments:Function] + # [DEF:get_environment:Function] + # @PURPOSE: Returns a single environment by ID. + # @PRE: self.config is set and isinstance(env_id, str) and len(env_id) > 0. + # @POST: Returns Environment object if found, None otherwise. + # @PARAM: env_id (str) - The ID of the environment to retrieve. + # @RETURN: Optional[Environment] - The environment with the given ID, or None. + def get_environment(self, env_id: str) -> Optional[Environment]: + with belief_scope("get_environment"): + for env in self.config.environments: + if env.id == env_id: + return env + return None + # [/DEF:get_environment:Function] + # [DEF:add_environment:Function] # @PURPOSE: Adds a new environment to the configuration. # @PRE: isinstance(env, Environment) diff --git a/backend/src/core/superset_client.py b/backend/src/core/superset_client.py index ebf15dd..a960a7d 100644 --- a/backend/src/core/superset_client.py +++ b/backend/src/core/superset_client.py @@ -9,7 +9,7 @@ # [SECTION: IMPORTS] from typing import List, Dict, Optional, Tuple -from backend.src.core.logger import belief_scope +from .logger import belief_scope from superset_tool.client import SupersetClient as BaseSupersetClient from superset_tool.models import SupersetConfig # [/SECTION] @@ -17,6 +17,14 @@ from superset_tool.models import SupersetConfig # [DEF:SupersetClient:Class] # @PURPOSE: Extended SupersetClient for migration-specific operations. class SupersetClient(BaseSupersetClient): + # [DEF:authenticate:Function] + # @PURPOSE: Authenticates the client using the configured credentials. + # @PRE: self.network must be initialized with valid auth configuration. + # @POST: Client is authenticated and tokens are stored. + # @RETURN: Dict[str, str] - Authentication tokens. + def authenticate(self): + with belief_scope("SupersetClient.authenticate"): + return self.network.authenticate() # [DEF:get_databases_summary:Function] # @PURPOSE: Fetch a summary of databases including uuid, name, and engine. diff --git a/backend/src/plugins/debug.py b/backend/src/plugins/debug.py index 5394f2f..29129b3 100644 --- a/backend/src/plugins/debug.py +++ b/backend/src/plugins/debug.py @@ -145,7 +145,19 @@ class DebugPlugin(PluginBase): if not env_config: raise ValueError(f"Environment '{name}' not found.") - client = SupersetClient(env_config) + # Map Environment model to SupersetConfig + from superset_tool.models import SupersetConfig + superset_config = SupersetConfig( + env=env_config.name, + base_url=env_config.url, + auth={ + "provider": "db", # Defaulting to db provider + "username": env_config.username, + "password": env_config.password, + "refresh": "false" + } + ) + client = SupersetClient(superset_config) client.authenticate() count, dbs = client.get_databases() results[name] = { @@ -176,7 +188,19 @@ class DebugPlugin(PluginBase): if not env_config: raise ValueError(f"Environment '{env_name}' not found.") - client = SupersetClient(env_config) + # Map Environment model to SupersetConfig + from superset_tool.models import SupersetConfig + superset_config = SupersetConfig( + env=env_config.name, + base_url=env_config.url, + auth={ + "provider": "db", # Defaulting to db provider + "username": env_config.username, + "password": env_config.password, + "refresh": "false" + } + ) + client = SupersetClient(superset_config) client.authenticate() dataset_response = client.get_dataset(dataset_id) diff --git a/backend/src/plugins/mapper.py b/backend/src/plugins/mapper.py index 6103d1e..c8dc7a3 100644 --- a/backend/src/plugins/mapper.py +++ b/backend/src/plugins/mapper.py @@ -137,13 +137,25 @@ class MapperPlugin(PluginBase): # Get config and initialize client from ..dependencies import get_config_manager + from superset_tool.models import SupersetConfig config_manager = get_config_manager() env_config = config_manager.get_environment(env_name) if not env_config: logger.error(f"[MapperPlugin.execute][State] Environment '{env_name}' not found.") raise ValueError(f"Environment '{env_name}' not found in configuration.") - client = SupersetClient(env_config) + # Map Environment model to SupersetConfig + superset_config = SupersetConfig( + env=env_config.name, + base_url=env_config.url, + auth={ + "provider": "db", # Defaulting to db provider + "username": env_config.username, + "password": env_config.password, + "refresh": "false" + } + ) + client = SupersetClient(superset_config) client.authenticate() postgres_config = None diff --git a/backend/src/plugins/search.py b/backend/src/plugins/search.py index 9a39949..3f5b768 100644 --- a/backend/src/plugins/search.py +++ b/backend/src/plugins/search.py @@ -106,13 +106,25 @@ class SearchPlugin(PluginBase): # Get config and initialize client from ..dependencies import get_config_manager + from superset_tool.models import SupersetConfig config_manager = get_config_manager() env_config = config_manager.get_environment(env_name) if not env_config: logger.error(f"[SearchPlugin.execute][State] Environment '{env_name}' not found.") raise ValueError(f"Environment '{env_name}' not found in configuration.") - client = SupersetClient(env_config) + # Map Environment model to SupersetConfig + superset_config = SupersetConfig( + env=env_config.name, + base_url=env_config.url, + auth={ + "provider": "db", # Defaulting to db provider + "username": env_config.username, + "password": env_config.password, + "refresh": "false" + } + ) + client = SupersetClient(superset_config) client.authenticate() logger.info(f"[SearchPlugin.execute][Action] Searching for pattern: '{search_query}' in environment: {env_name}") diff --git a/backend/tasks.db b/backend/tasks.db index 028771063fe2e3181568e6e43f41a35902a95e23..568cc61dd48b991799503bad7a10f894fdda5d40 100644 GIT binary patch literal 53248 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 bcTg{V$ZpP;yJsIA4V*=&iYzN?w4d`oIS?;D delta 647 zcma))yKWOf6oz;0uwZO?9YkjWs}&MRFv*$AUK^kyX)FQSDzePX?2Ki_*w*fv1|laZ zXu0?WB3=NYNGW*^$~-};NVKd`5)nb+@|<7upZ_~&_71CiCzVg_Ya51P)IU2<%#H7_ z5653%rDa)QG|!rM8@1ZmjawERt+wu1M{s<4eYXFq(nub)lV`@NLHAAIn)*a2#6u8> zfsiO-0+0sHLl37iU{OAus4Q0n5?t;~CohvuXRGr(fB>gl_$UmqMl9k4iO>Ti0hpj# zVl06G@en46f`g+NASP>U9cVxZ-tPV@vbp)=_G}qrclV%XHWJ*v_lM(P4SO{7l5@Z60);802rEjdMzT|i_=x4kbj7R4v7Xn#Cnq34AW9G*=SPvKrJpXFmbM2j* ztD%yGN_&GecVlN8cON@$KAvV$_0;HX*)F^8{4e>GPgRugmn^!WaqetiF8Q7m)u O%7bcBTV|zSTh4FC=B|4H diff --git a/backend/test_fix.py b/backend/test_fix.py new file mode 100644 index 0000000..7923333 --- /dev/null +++ b/backend/test_fix.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Test script to verify the fixes for SupersetClient initialization.""" + +import sys +sys.path.insert(0, '.') + +from src.core.config_manager import ConfigManager +from src.core.config_models import Environment +from src.plugins.search import SearchPlugin +from src.plugins.mapper import MapperPlugin +from src.plugins.debug import DebugPlugin + +def test_config_manager(): + """Test ConfigManager methods.""" + print("Testing ConfigManager...") + try: + config_manager = ConfigManager() + print(f" ConfigManager initialized") + + # Test get_environment method + if hasattr(config_manager, 'get_environment'): + print(f" get_environment method exists") + + # Add a test environment if none exists + if not config_manager.has_environments(): + test_env = Environment( + id="test-env", + name="Test Environment", + url="http://localhost:8088", + username="admin", + password="admin" + ) + config_manager.add_environment(test_env) + print(f" Added test environment: {test_env.name}") + + # Test retrieving environment + envs = config_manager.get_environments() + if envs: + test_env_id = envs[0].id + env_config = config_manager.get_environment(test_env_id) + print(f" Successfully retrieved environment: {env_config.name}") + return True + else: + print(f" No environments available (add one in settings)") + return False + + except Exception as e: + print(f" Error: {e}") + return False + +def test_plugins(): + """Test plugin initialization.""" + print("\nTesting plugins...") + + plugins = [ + ("Search Plugin", SearchPlugin()), + ("Mapper Plugin", MapperPlugin()), + ("Debug Plugin", DebugPlugin()) + ] + + all_ok = True + + for name, plugin in plugins: + print(f"\nTesting {name}...") + try: + plugin_id = plugin.id + plugin_name = plugin.name + plugin_version = plugin.version + schema = plugin.get_schema() + + print(f" ✓ ID: {plugin_id}") + print(f" ✓ Name: {plugin_name}") + print(f" ✓ Version: {plugin_version}") + print(f" ✓ Schema: {schema}") + + except Exception as e: + print(f" ✗ Error: {e}") + all_ok = False + + return all_ok + +def main(): + """Main test function.""" + print("=" * 50) + print("Superset Tools Fix Verification") + print("=" * 50) + + config_ok = test_config_manager() + plugins_ok = test_plugins() + + print("\n" + "=" * 50) + if config_ok and plugins_ok: + print("✅ All fixes verified successfully!") + else: + print("❌ Some tests failed") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/frontend/src/components/Navbar.svelte b/frontend/src/components/Navbar.svelte index d2d9ba7..98c90bd 100644 --- a/frontend/src/components/Navbar.svelte +++ b/frontend/src/components/Navbar.svelte @@ -39,7 +39,7 @@ -