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
This commit is contained in:
2026-01-20 19:31:17 +03:00
parent 3bbe320949
commit c96d50a3f4
10 changed files with 219 additions and 7 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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}")

Binary file not shown.

99
backend/test_fix.py Normal file
View File

@@ -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()

View File

@@ -39,7 +39,7 @@
<button class="text-gray-600 hover:text-blue-600 font-medium pb-1 {$page.url.pathname.startsWith('/tools') ? 'text-blue-600 border-b-2 border-blue-600' : ''}">
Tools
</button>
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100">
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
<a href="/tools/search" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Dataset Search</a>
<a href="/tools/mapper" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Dataset Mapper</a>
<a href="/tools/debug" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">System Debug</a>
@@ -49,7 +49,7 @@
<button class="text-gray-600 hover:text-blue-600 font-medium pb-1 {$page.url.pathname.startsWith('/settings') ? 'text-blue-600 border-b-2 border-blue-600' : ''}">
Settings
</button>
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100">
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
<a href="/settings" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">General Settings</a>
<a href="/settings/connections" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Connections</a>
</div>

View File

@@ -29,6 +29,14 @@ from superset_tool.utils.network import APIClient
# @RELATION: CREATES_INSTANCE_OF -> APIClient
# @RELATION: USES -> SupersetConfig
class SupersetClient:
# [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:__init__:Function]
# @PURPOSE: Инициализирует клиент, проверяет конфигурацию и создает сетевой клиент.
# @PRE: `config` должен быть валидным объектом SupersetConfig.