diff --git a/.ai/MODULE_MAP.md b/.ai/MODULE_MAP.md index 91360b5ff..6108ff5f8 100644 --- a/.ai/MODULE_MAP.md +++ b/.ai/MODULE_MAP.md @@ -2,12 +2,12 @@ > High-level module structure for AI Context. Generated automatically. -**Generated:** 2026-03-14T09:20:50.362415 +**Generated:** 2026-03-15T16:33:41.022837 ## Summary -- **Total Modules:** 103 -- **Total Entities:** 3134 +- **Total Modules:** 105 +- **Total Entities:** 3256 ## Module Hierarchy @@ -40,7 +40,7 @@ ### 📁 `api/` - 🏗️ **Layers:** API - - 📊 **Tiers:** CRITICAL: 7, STANDARD: 1 + - 📊 **Tiers:** CRITICAL: 1, STANDARD: 7 - 📄 **Files:** 2 - 📦 **Entities:** 8 @@ -52,9 +52,9 @@ ### 📁 `routes/` - 🏗️ **Layers:** API, Infra, UI (API), UI/API - - 📊 **Tiers:** CRITICAL: 12, STANDARD: 272, TRIVIAL: 16 + - 📊 **Tiers:** CRITICAL: 5, STANDARD: 282, TRIVIAL: 15 - 📄 **Files:** 21 - - 📦 **Entities:** 300 + - 📦 **Entities:** 302 **Key Entities:** @@ -90,9 +90,9 @@ ### 📁 `__tests__/` - 🏗️ **Layers:** API, Domain, Domain (Tests), Tests, UI (API Tests), Unknown - - 📊 **Tiers:** STANDARD: 92, TRIVIAL: 195 - - 📄 **Files:** 17 - - 📦 **Entities:** 287 + - 📊 **Tiers:** STANDARD: 93, TRIVIAL: 198 + - 📄 **Files:** 18 + - 📦 **Entities:** 291 **Key Entities:** @@ -125,9 +125,9 @@ ### 📁 `core/` - 🏗️ **Layers:** Core, Domain - - 📊 **Tiers:** CRITICAL: 55, STANDARD: 103, TRIVIAL: 9 + - 📊 **Tiers:** CRITICAL: 10, STANDARD: 149, TRIVIAL: 9 - 📄 **Files:** 13 - - 📦 **Entities:** 167 + - 📦 **Entities:** 168 **Key Entities:** @@ -181,7 +181,7 @@ ### 📁 `auth/` - 🏗️ **Layers:** Core, Domain - - 📊 **Tiers:** CRITICAL: 29 + - 📊 **Tiers:** CRITICAL: 8, STANDARD: 21 - 📄 **Files:** 7 - 📦 **Entities:** 29 @@ -239,7 +239,7 @@ ### 📁 `migration/` - 🏗️ **Layers:** Core, Domain - - 📊 **Tiers:** CRITICAL: 20, TRIVIAL: 1 + - 📊 **Tiers:** CRITICAL: 5, STANDARD: 15, TRIVIAL: 1 - 📄 **Files:** 4 - 📦 **Entities:** 21 @@ -269,9 +269,9 @@ ### 📁 `task_manager/` - 🏗️ **Layers:** Core - - 📊 **Tiers:** CRITICAL: 10, STANDARD: 63, TRIVIAL: 6 + - 📊 **Tiers:** CRITICAL: 10, STANDARD: 64, TRIVIAL: 6 - 📄 **Files:** 7 - - 📦 **Entities:** 79 + - 📦 **Entities:** 80 **Key Entities:** @@ -305,12 +305,14 @@ ### 📁 `__tests__/` - 🏗️ **Layers:** Unknown - - 📊 **Tiers:** TRIVIAL: 9 - - 📄 **Files:** 1 - - 📦 **Entities:** 9 + - 📊 **Tiers:** STANDARD: 2, TRIVIAL: 9 + - 📄 **Files:** 2 + - 📦 **Entities:** 11 **Key Entities:** + - 📦 **backend.src.core.task_manager.__tests__.test_context** (Module) + - Verify TaskContext preserves optional background task schedu... - 📦 **test_task_logger** (Module) `[TRIVIAL]` - Auto-generated module for backend/src/core/task_manager/__te... @@ -355,7 +357,7 @@ ### 📁 `models/` - 🏗️ **Layers:** Domain, Model - - 📊 **Tiers:** CRITICAL: 21, STANDARD: 41, TRIVIAL: 29 + - 📊 **Tiers:** CRITICAL: 18, STANDARD: 44, TRIVIAL: 29 - 📄 **Files:** 14 - 📦 **Entities:** 91 @@ -409,7 +411,7 @@ ### 📁 `plugins/` - 🏗️ **Layers:** App, Plugin, Plugins - - 📊 **Tiers:** CRITICAL: 10, STANDARD: 54 + - 📊 **Tiers:** CRITICAL: 2, STANDARD: 62 - 📄 **Files:** 7 - 📦 **Entities:** 64 @@ -459,9 +461,9 @@ ### 📁 `llm_analysis/` - 🏗️ **Layers:** Unknown - - 📊 **Tiers:** STANDARD: 21, TRIVIAL: 24 + - 📊 **Tiers:** STANDARD: 32, TRIVIAL: 35 - 📄 **Files:** 4 - - 📦 **Entities:** 45 + - 📦 **Entities:** 67 **Key Entities:** @@ -490,6 +492,21 @@ - 🔗 IMPLEMENTS -> backend.src.core.plugin_base.PluginBase + ### 📁 `__tests__/` + + - 📊 **Tiers:** STANDARD: 13, TRIVIAL: 43 + - 📄 **Files:** 3 + - 📦 **Entities:** 56 + + **Key Entities:** + + - 📦 **backend.src.plugins.llm_analysis.__tests__.test_client_headers** (Module) + - Verify OpenRouter client initialization includes provider-sp... + - 📦 **backend.src.plugins.llm_analysis.__tests__.test_screenshot_service** (Module) + - Protect dashboard screenshot navigation from brittle network... + - 📦 **backend.src.plugins.llm_analysis.__tests__.test_service** (Module) + - Verify LLM analysis transport/provider failures do not masqu... + ### 📁 `storage/` - 🏗️ **Layers:** App @@ -557,7 +574,7 @@ ### 📁 `scripts/` - 🏗️ **Layers:** Scripts, UI, Unknown - - 📊 **Tiers:** CRITICAL: 3, STANDARD: 44, TRIVIAL: 30 + - 📊 **Tiers:** CRITICAL: 1, STANDARD: 46, TRIVIAL: 30 - 📄 **Files:** 9 - 📦 **Entities:** 77 @@ -592,9 +609,9 @@ ### 📁 `services/` - 🏗️ **Layers:** Core, Domain, Domain/Service, Service - - 📊 **Tiers:** CRITICAL: 10, STANDARD: 120, TRIVIAL: 17 + - 📊 **Tiers:** CRITICAL: 6, STANDARD: 133, TRIVIAL: 20 - 📄 **Files:** 10 - - 📦 **Entities:** 147 + - 📦 **Entities:** 159 **Key Entities:** @@ -606,6 +623,8 @@ - Raised when environment_id from lookup request is unknown in... - ℂ **GitService** (Class) - Wrapper for GitPython operations with semantic logging and e... + - ℂ **HealthService** (Class) + - Aggregate latest dashboard validation state and manage persi... - ℂ **LLMProviderService** (Class) - Service to manage LLM provider lifecycle. - ℂ **MappingService** (Class) @@ -616,8 +635,6 @@ - Implements profile preference read/update flow and Superset ... - ℂ **ProfileValidationError** (Class) - Domain validation error for profile preference update reques... - - ℂ **ResourceService** (Class) - - Provides centralized access to resource data with enhanced m... **Dependencies:** @@ -630,9 +647,9 @@ ### 📁 `__tests__/` - 🏗️ **Layers:** Domain, Domain Tests, Service, Service Tests, Unknown - - 📊 **Tiers:** STANDARD: 37, TRIVIAL: 40 + - 📊 **Tiers:** STANDARD: 40, TRIVIAL: 46 - 📄 **Files:** 7 - - 📦 **Entities:** 77 + - 📦 **Entities:** 86 **Key Entities:** @@ -1022,6 +1039,11 @@ - 🧩 **StartupEnvironmentWizard** (Component) - Blocking startup wizard for creating the first Superset envi... + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend/src/lib/stores.js, frontend/src/components/tasks/TaskLogPanel.svelte + - 🔗 DEPENDS_ON -> frontend/src/lib/toasts.js + ### 📁 `__tests__/` - 🏗️ **Layers:** UI (Tests) @@ -1037,17 +1059,21 @@ ### 📁 `auth/` - 🏗️ **Layers:** UI - - 📊 **Tiers:** CRITICAL: 2, STANDARD: 1 + - 📊 **Tiers:** STANDARD: 3 - 📄 **Files:** 1 - 📦 **Entities:** 3 **Key Entities:** - - 🧩 **ProtectedRoute** (Component) `[CRITICAL]` + - 🧩 **ProtectedRoute** (Component) - Wraps protected slot content with session and permission ver... - 📦 **ProtectedRoute.svelte** (Module) - Enforces authenticated and authorized access before protecte... + **Dependencies:** + + - 🔗 DEPENDS_ON -> $lib/auth/permissions.hasPermission + ### 📁 `git/` - 🏗️ **Layers:** Component, Unknown @@ -1087,7 +1113,7 @@ ### 📁 `llm/` - 🏗️ **Layers:** UI, Unknown - - 📊 **Tiers:** CRITICAL: 1, STANDARD: 1, TRIVIAL: 12 + - 📊 **Tiers:** STANDARD: 2, TRIVIAL: 12 - 📄 **Files:** 3 - 📦 **Entities:** 14 @@ -1095,7 +1121,7 @@ - 🧩 **DocPreview** (Component) - UI component for previewing generated dataset documentation ... - - 🧩 **ProviderConfig** (Component) `[CRITICAL]` + - 🧩 **ProviderConfig** (Component) - UI form for managing LLM provider configurations. - 📦 **DocPreview** (Module) `[TRIVIAL]` - Auto-generated module for frontend/src/components/llm/DocPre... @@ -1104,6 +1130,11 @@ - 📦 **ValidationReport** (Module) `[TRIVIAL]` - Auto-generated module for frontend/src/components/llm/Valida... + **Dependencies:** + + - 🔗 DEPENDS_ON -> backend/src/api/routes/llm.py + - 🔗 DEPENDS_ON -> backend/src/plugins/llm_analysis/plugin.py + ### 📁 `__tests__/` - 🏗️ **Layers:** UI Tests @@ -1130,6 +1161,10 @@ - 🧩 **FileUpload** (Component) - Provides a form for uploading files to a specific category. + **Dependencies:** + + - 🔗 DEPENDS_ON -> storageService + ### 📁 `tasks/` - 🏗️ **Layers:** UI, Unknown @@ -1232,7 +1267,7 @@ ### 📁 `auth/` - 🏗️ **Layers:** Domain, Feature - - 📊 **Tiers:** CRITICAL: 11, TRIVIAL: 1 + - 📊 **Tiers:** CRITICAL: 2, STANDARD: 9, TRIVIAL: 1 - 📄 **Files:** 2 - 📦 **Entities:** 12 @@ -1272,14 +1307,16 @@ ### 📁 `__tests__/` - 🏗️ **Layers:** UI Tests - - 📊 **Tiers:** STANDARD: 3 - - 📄 **Files:** 1 - - 📦 **Entities:** 3 + - 📊 **Tiers:** STANDARD: 5 + - 📄 **Files:** 2 + - 📦 **Entities:** 5 **Key Entities:** - 📦 **frontend.src.lib.components.assistant.__tests__.assistant_chat_integration** (Module) - Contract-level integration checks for assistant chat panel i... + - 📦 **frontend.src.lib.components.assistant.__tests__.assistant_first_message_integration** (Module) + - Verify first optimistic user message stays visible while a n... ### 📁 `health/` @@ -1328,6 +1365,7 @@ **Dependencies:** - 🔗 DEPENDS_ON -> frontend.src.lib.auth.permissions.hasPermission + - 🔗 DEPENDS_ON -> page store ### 📁 `__tests__/` @@ -1369,6 +1407,7 @@ **Dependencies:** + - 🔗 DEPENDS_ON -> frontend/src/lib/components/reports/reportTypeProfiles.js - 🔗 DEPENDS_ON -> frontend/src/lib/i18n/index.ts ### 📁 `__tests__/` @@ -1552,6 +1591,10 @@ - 🧩 **Settings** (Component) - The main settings page for the application, allowing managem... + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend/src/lib/stores.js + ### 📁 `routes/` - 🏗️ **Layers:** Infra, UI, Unknown @@ -1580,6 +1623,11 @@ - 🧩 **AdminRolesPage** (Component) - UI for managing system roles and their permissions. + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend.src.components.auth.ProtectedRoute + - 🔗 DEPENDS_ON -> frontend.src.services.adminService + ### 📁 `settings/` - 🏗️ **Layers:** Feature @@ -1592,6 +1640,11 @@ - 🧩 **AdminSettingsPage** (Component) - UI for configuring Active Directory Group to local Role mapp... + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend.src.components.auth.ProtectedRoute + - 🔗 DEPENDS_ON -> frontend.src.services.adminService + ### 📁 `llm/` - 🏗️ **Layers:** UI, Unknown @@ -1618,6 +1671,11 @@ - 🧩 **AdminUsersPage** (Component) - UI for managing system users and their roles. + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend.src.components.auth.ProtectedRoute + - 🔗 DEPENDS_ON -> frontend.src.services.adminService + ### 📁 `dashboards/` - 🏗️ **Layers:** UI, Unknown @@ -1656,8 +1714,8 @@ ### 📁 `health/` - - 🏗️ **Layers:** UI/Page, Unknown - - 📊 **Tiers:** STANDARD: 1, TRIVIAL: 3 + - 🏗️ **Layers:** UI/Page + - 📊 **Tiers:** STANDARD: 4 - 📄 **Files:** 1 - 📦 **Entities:** 4 @@ -1665,8 +1723,22 @@ - 🧩 **HealthCenterPage** (Component) - Main page for the Dashboard Health Center. - - 📦 **+page** (Module) `[TRIVIAL]` - - Auto-generated module for frontend/src/routes/dashboards/hea... + + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend.src.lib.components.health.HealthMatrix + + ### 📁 `__tests__/` + + - 🏗️ **Layers:** UI Tests + - 📊 **Tiers:** STANDARD: 1 + - 📄 **Files:** 1 + - 📦 **Entities:** 1 + + **Key Entities:** + + - 📦 **frontend.src.routes.dashboards.health.__tests__.health_page_integration** (Module) + - Lock dashboard health page contract for slug navigation and ... ### 📁 `datasets/` @@ -1718,19 +1790,19 @@ ### 📁 `migration/` - - 📊 **Tiers:** CRITICAL: 21 + - 📊 **Tiers:** STANDARD: 21 - 📄 **Files:** 1 - 📦 **Entities:** 21 **Key Entities:** - - 🧩 **DashboardSelectionSection** (Component) `[CRITICAL]` - - 🧩 **MigrationDashboard** (Component) `[CRITICAL]` + - 🧩 **DashboardSelectionSection** (Component) + - 🧩 **MigrationDashboard** (Component) - Orchestrate migration UI workflow and route user actions to ... ### 📁 `mappings/` - - 📊 **Tiers:** CRITICAL: 8 + - 📊 **Tiers:** CRITICAL: 1, STANDARD: 7 - 📄 **Files:** 1 - 📦 **Entities:** 8 @@ -1790,24 +1862,31 @@ - 📦 **+page** (Module) `[TRIVIAL]` - Auto-generated module for frontend/src/routes/reports/+page.... + **Dependencies:** + + - 🔗 DEPENDS_ON -> frontend/src/lib/api/reports.js + - 🔗 DEPENDS_ON -> frontend/src/lib/components/reports/ReportsList.svelte + ### 📁 `[taskId]/` - - 🏗️ **Layers:** Unknown - - 📊 **Tiers:** TRIVIAL: 11 - - 📄 **Files:** 1 - - 📦 **Entities:** 11 + - 🏗️ **Layers:** UI Tests, Unknown + - 📊 **Tiers:** STANDARD: 2, TRIVIAL: 11 + - 📄 **Files:** 2 + - 📦 **Entities:** 13 **Key Entities:** - 📦 **+page** (Module) `[TRIVIAL]` - Auto-generated module for frontend/src/routes/reports/llm/[t... + - 📦 **frontend.src.routes.reports.llm.taskid.report_page_contract_test** (Module) + - Protect the LLM report page from self-triggering screenshot ... ### 📁 `settings/` - 🏗️ **Layers:** UI, Unknown - - 📊 **Tiers:** CRITICAL: 1, STANDARD: 1, TRIVIAL: 25 + - 📊 **Tiers:** CRITICAL: 1, STANDARD: 1, TRIVIAL: 33 - 📄 **Files:** 2 - - 📦 **Entities:** 27 + - 📦 **Entities:** 35 **Key Entities:** @@ -1926,6 +2005,10 @@ - 🧩 **StoragePage** (Component) - Main page for unified file storage management. + **Dependencies:** + + - 🔗 DEPENDS_ON -> storageService + ### 📁 `services/` - 🏗️ **Layers:** Service @@ -1945,6 +2028,7 @@ **Dependencies:** - 🔗 DEPENDS_ON -> frontend.src.lib.api + - 🔗 DEPENDS_ON -> specs/011-git-integration-dashboard/contracts/api.md ### 📁 `__tests__/` @@ -2000,6 +2084,8 @@ graph TD routes-->|DEPENDS_ON|backend routes-->|DEPENDS_ON|backend routes-->|DEPENDS_ON|backend + routes-->|CALLS|backend + routes-->|CALLS|backend routes-->|DEPENDS_ON|backend routes-->|DEPENDS_ON|backend routes-->|DEPENDS_ON|backend @@ -2043,6 +2129,7 @@ graph TD __tests__-->|TESTS|backend __tests__-->|TESTS|backend __tests__-->|DEPENDS_ON|backend + __tests__-->|VERIFIES|backend __tests__-->|DEPENDS_ON|backend __tests__-->|VERIFIES|backend core-->|DEPENDS_ON|backend @@ -2109,6 +2196,10 @@ graph TD services-->|DEPENDS_ON|backend services-->|DEPENDS_ON|backend services-->|CALLS|backend + services-->|CALLS|backend + services-->|DEPENDS_ON|backend + services-->|DEPENDS_ON|backend + services-->|CALLS|backend services-->|DEPENDS_ON|backend services-->|DEPENDS_ON|backend services-->|DEPENDS_ON|backend @@ -2202,20 +2293,50 @@ graph TD clean_release-->|DEPENDS_ON|backend clean_release-->|TESTS|backend clean_release-->|TESTS|backend + components-->|USED_BY|routes + components-->|USES|lib + components-->|USES|services + components-->|USES|tasks + components-->|USED_BY|routes + components-->|USES|lib + components-->|USES|lib + components-->|DEPENDS_ON|lib + components-->|DEPENDS_ON|lib __tests__-->|VERIFIES|components + git-->|CALLS|services __tests__-->|VERIFIES|components + llm-->|DEPENDS_ON|backend + llm-->|DEPENDS_ON|backend __tests__-->|VERIFIES|components + tasks-->|USES|components + tasks-->|USES|components + tools-->|USES|services + tools-->|USES|services + tools-->|USES|services + tools-->|USES|services + tools-->|USES|services __tests__-->|TESTS|lib __tests__-->|VERIFIES|lib + __tests__-->|VERIFIES|lib __tests__-->|TESTS|lib reports-->|DEPENDS_ON|lib + reports-->|DEPENDS_ON|lib + reports-->|DEPENDS_ON|lib + reports-->|DEPENDS_ON|lib + reports-->|DEPENDS_ON|lib __tests__-->|TESTS|routes __tests__-->|TESTS|routes __tests__-->|TESTS|lib __tests__-->|TESTS|lib __tests__-->|TESTS|lib __tests__-->|TESTS|routes + pages-->|DEPENDS_ON|lib + llm-->|CALLS|components + __tests__-->|TESTS|routes + __tests__-->|VERIFIES|routes __tests__-->|TESTS|routes __tests__-->|TESTS|routes - __tests__-->|TESTS|routes + reports-->|DEPENDS_ON|lib + reports-->|DEPENDS_ON|lib + [taskId]-->|VERIFIES|routes ``` diff --git a/.ai/PROJECT_MAP.md b/.ai/PROJECT_MAP.md index 49d2afe79..1c2b68b55 100644 --- a/.ai/PROJECT_MAP.md +++ b/.ai/PROJECT_MAP.md @@ -154,27 +154,27 @@ - 📝 Manages the global authentication state on the frontend. - 🏗️ Layer: Feature - 🔗 BINDS_TO -> `Navbar, ProtectedRoute` - - 📦 **AuthState** (`Interface`) `[CRITICAL]` + - 📦 **AuthState** (`Interface`) - 📝 Defines the structure of the authentication state. - - ƒ **createAuthStore** (`Function`) `[CRITICAL]` + - ƒ **createAuthStore** (`Function`) - 📝 Creates and configures the auth store with helper methods. - - ƒ **setToken** (`Function`) `[CRITICAL]` + - ƒ **setToken** (`Function`) - 📝 Updates the store with a new JWT token. - - ƒ **setUser** (`Function`) `[CRITICAL]` + - ƒ **setUser** (`Function`) - 📝 Sets the current user profile data. - - ƒ **logout** (`Function`) `[CRITICAL]` + - ƒ **logout** (`Function`) - 📝 Clears authentication state and storage. - - ƒ **setLoading** (`Function`) `[CRITICAL]` + - ƒ **setLoading** (`Function`) - 📝 Updates the loading state. - 📦 **frontend.src.lib.auth.permissions** (`Module`) `[CRITICAL]` - 📝 Shared frontend RBAC utilities for route guards and menu visibility. - 🏗️ Layer: Domain - 🔒 Invariant: Admin role always bypasses explicit permission checks. - - ƒ **normalizePermissionRequirement** (`Function`) `[CRITICAL]` + - ƒ **normalizePermissionRequirement** (`Function`) - 📝 Convert permission requirement string to canonical resource/action tuple. - - ƒ **isAdminUser** (`Function`) `[CRITICAL]` + - ƒ **isAdminUser** (`Function`) - 📝 Determine whether user has Admin role. - - ƒ **hasPermission** (`Function`) `[CRITICAL]` + - ƒ **hasPermission** (`Function`) - 📝 Check if user has a required resource/action permission. - ƒ **normalizeAction** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) @@ -396,6 +396,7 @@ - 🏗️ Layer: Atom - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `locale` + - 🔗 BINDS_TO -> `i18n.locale` - 📦 **i18n** (`Module`) - 📝 Determines the starting locale. - 🏗️ Layer: Infra @@ -415,6 +416,9 @@ - ⬅️ READS_FROM `app` - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `state` + - 🔗 BINDS_TO -> `assistantChatStore` + - 🔗 CALLS -> `frontend.src.lib.api.assistant` + - 🔗 DISPATCHES -> `taskDrawerStore` - ƒ **loadHistory** (`Function`) - 📝 Load current conversation history when panel becomes visible. - ƒ **loadConversations** (`Function`) @@ -452,6 +456,12 @@ - 📝 Auto-detected function (orphan) - ƒ **loadLlmStatus** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) +- 📦 **frontend.src.lib.components.assistant.__tests__.assistant_first_message_integration** (`Module`) + - 📝 Verify first optimistic user message stays visible while a new conversation request is pending. + - 🏗️ Layer: UI Tests + - 🔒 Invariant: Starting a new conversation must not trigger history reload that overwrites the first local user message. + - ƒ **assistant_first_message_tests** (`Function`) + - 📝 Guard optimistic first-message UX against history reload race in new conversation flow. - 📦 **frontend.src.lib.components.assistant.__tests__.assistant_chat_integration** (`Module`) - 📝 Contract-level integration checks for assistant chat panel implementation and localization wiring. - 🏗️ Layer: UI Tests @@ -492,6 +502,8 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `derived` + - 🔗 DEPENDS_ON -> `frontend/src/lib/components/reports/reportTypeProfiles.js` + - 🔗 DEPENDS_ON -> `frontend/src/lib/i18n/index.ts` - 📦 **ReportCard** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/lib/components/reports/ReportCard.svelte - 🏗️ Layer: Unknown @@ -508,6 +520,7 @@ - 🏗️ Layer: UI - 🔒 Invariant: Every rendered row shows task_type label, status, summary, and updated_at. - ➡️ WRITES_TO `props` + - 🔗 DEPENDS_ON -> `frontend/src/lib/i18n/index.ts` - 📦 **ReportsList** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/lib/components/reports/ReportsList.svelte - 🏗️ Layer: Unknown @@ -527,6 +540,7 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `props` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `frontend/src/lib/i18n/index.ts` - 📦 **ReportDetailPanel** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/lib/components/reports/ReportDetailPanel.svelte - 🏗️ Layer: Unknown @@ -578,6 +592,7 @@ - ⬅️ READS_FROM `app` - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `derived` + - 🔗 BINDS_TO -> `sidebarStore` - 📦 **Sidebar** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/lib/components/layout/Sidebar.svelte - 🏗️ Layer: Unknown @@ -602,6 +617,7 @@ - ⬅️ READS_FROM `app` - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `state` + - 🔗 BINDS_TO -> `activityStore, authStore` - 📦 **TopNavbar** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/lib/components/layout/TopNavbar.svelte - 🏗️ Layer: Unknown @@ -644,6 +660,7 @@ - ⬅️ READS_FROM `app` - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `props` + - 🔗 DEPENDS_ON -> `page store` - 📦 **Breadcrumbs** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/lib/components/layout/Breadcrumbs.svelte - 🏗️ Layer: Unknown @@ -660,6 +677,7 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `state` - ➡️ WRITES_TO `derived` + - 🔗 BINDS_TO -> `taskDrawerStore, assistantChatStore, WebSocket` - ƒ **disconnectWebSocket** (`Function`) - 📝 Disconnects the active WebSocket connection - ƒ **loadRecentTasks** (`Function`) @@ -757,10 +775,12 @@ - 📝 Bind global layout shell and conditional login/full-app rendering. - 🏗️ Layer: UI - 🔒 Invariant: Login route bypasses shell; all other routes are wrapped by ProtectedRoute. + - 🔗 BINDS_TO -> `frontend.src.lib.components.layout.Sidebar` - 📦 **DatasetHub** (`Page`) - 📝 Dataset Hub - Dedicated hub for datasets with mapping progress - 🏗️ Layer: UI - 🔒 Invariant: Always shows datasets for the active environment from context store + - 🔗 BINDS_TO -> `sidebarStore, taskDrawerStore` - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/datasets/+page.svelte - 🏗️ Layer: Unknown @@ -796,6 +816,7 @@ - 📝 Dataset Detail View - Shows detailed dataset information with columns, SQL, and linked dashboards - 🏗️ Layer: UI - 🔒 Invariant: Always shows dataset details when loaded + - 🔗 BINDS_TO -> `sidebarStore` - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/datasets/[id]/+page.svelte - 🏗️ Layer: Unknown @@ -816,6 +837,8 @@ - ⬅️ READS_FROM `lib` - ⬅️ READS_FROM `t` - ➡️ WRITES_TO `t` + - 🔗 DEPENDS_ON -> `frontend/src/lib/api/reports.js` + - 🔗 DEPENDS_ON -> `frontend/src/lib/components/reports/ReportsList.svelte` - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/reports/+page.svelte - 🏗️ Layer: Unknown @@ -854,6 +877,11 @@ - 📝 Auto-detected function (orphan) - ƒ **openScreenshot** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) +- 📦 **frontend.src.routes.reports.llm.taskid.report_page_contract_test** (`Module`) + - 📝 Protect the LLM report page from self-triggering screenshot load effects. + - 🏗️ Layer: UI Tests + - ƒ **llm_report_screenshot_effect_contract** (`Function`) + - 📝 Ensure screenshot loading stays untracked from blob-url mutation state. - 🧩 **LoginPage** (`Component`) - 📝 Provides the user interface for local and ADFS authentication. - 🏗️ Layer: UI @@ -861,6 +889,7 @@ - ⬅️ READS_FROM `app` - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `t` + - 🔗 CALLS -> `api.auth.login` - ƒ **handleLogin** (`Function`) - 📝 Submits the local login form to the backend. - ƒ **handleADFSLogin** (`Function`) @@ -880,6 +909,7 @@ - 📝 Dashboard Hub - Central hub for managing dashboards with Git status and task actions - 🏗️ Layer: UI - 🔒 Invariant: Always shows dashboards for the active environment from context store + - 🔗 BINDS_TO -> `sidebarStore, taskDrawerStore` - ƒ **DashboardHub.normalizeTaskStatus** (`Function`) - 📝 Normalize raw task status to stable lowercase token for UI. - ƒ **DashboardHub.normalizeValidationStatus** (`Function`) @@ -1076,17 +1106,22 @@ - ⬅️ READS_FROM `state` - ⬅️ READS_FROM `derived` - ⬅️ READS_FROM `lib` -- 📦 **+page** (`Module`) `[TRIVIAL]` - - 📝 Auto-generated module for frontend/src/routes/dashboards/health/+page.svelte - - 🏗️ Layer: Unknown - - ƒ **loadData** (`Function`) `[TRIVIAL]` - - 📝 Auto-detected function (orphan) - - ƒ **handleEnvChange** (`Function`) `[TRIVIAL]` - - 📝 Auto-detected function (orphan) + - 🔗 DEPENDS_ON -> `frontend.src.lib.components.health.HealthMatrix` + - 🔗 CALLS -> `frontend.src.lib.api.api_module` + - ƒ **loadData** (`Function`) + - 📝 Load health summary rows and environment options for the current filter. + - ƒ **handleEnvChange** (`Function`) + - 📝 Apply environment filter and trigger health summary reload. + - ƒ **handleDeleteReport** (`Function`) + - 📝 Delete one health report row with confirmation and optimistic button lock. +- 📦 **frontend.src.routes.dashboards.health.__tests__.health_page_integration** (`Module`) + - 📝 Lock dashboard health page contract for slug navigation and report deletion. + - 🏗️ Layer: UI Tests - 📦 **DashboardDetail** (`Page`) - 📝 Dashboard Detail View - Overview of charts and datasets linked to a dashboard - 🏗️ Layer: UI - 🔒 Invariant: Shows dashboard metadata, charts, and datasets for selected environment + - 🔗 BINDS_TO -> `dashboard detail API` - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/dashboards/[id]/+page.svelte - 🏗️ Layer: Unknown @@ -1151,6 +1186,8 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `t` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `frontend.src.services.adminService` + - 🔗 DEPENDS_ON -> `frontend.src.components.auth.ProtectedRoute` - ƒ **loadData** (`Function`) - 📝 Fetches roles and available permissions. - ƒ **openCreateModal** (`Function`) @@ -1168,6 +1205,8 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `t` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `frontend.src.services.adminService` + - 🔗 DEPENDS_ON -> `frontend.src.components.auth.ProtectedRoute` - ƒ **loadData** (`Function`) - 📝 Fetches users and roles from the backend. - ƒ **openCreateModal** (`Function`) @@ -1185,6 +1224,8 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `t` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `frontend.src.services.adminService` + - 🔗 DEPENDS_ON -> `frontend.src.components.auth.ProtectedRoute` - ƒ **loadData** (`Function`) - 📝 Fetches AD mappings and roles from the backend to populate the UI. - ƒ **handleCreateMapping** (`Function`) @@ -1198,6 +1239,7 @@ - 🏗️ Layer: UI - ⬅️ READS_FROM `t` - ➡️ WRITES_TO `t` + - 🔗 CALLS -> `frontend/src/components/llm/ProviderConfig.svelte` - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/admin/settings/llm/+page.svelte - 🏗️ Layer: Unknown @@ -1209,61 +1251,61 @@ - 📝 Auto-detected function (orphan) - ƒ **saveSettings** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) -- 🧩 **MigrationDashboard** (`Component`) `[CRITICAL]` +- 🧩 **MigrationDashboard** (`Component`) - 📝 Orchestrate migration UI workflow and route user actions to backend APIs and task store. - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `state` - ⬅️ READS_FROM `effect` - - ƒ **fetchEnvironments** (`Function`) `[CRITICAL]` + - ƒ **fetchEnvironments** (`Function`) - 📝 Fetches the list of environments from the API. - - ƒ **fetchDashboards** (`Function`) `[CRITICAL]` + - ƒ **fetchDashboards** (`Function`) - 📝 Fetches dashboards for the selected source environment. - - ▦ **ReactiveDashboardFetch** (`Block`) `[CRITICAL]` + - ▦ **ReactiveDashboardFetch** (`Block`) - 📝 Automatically fetch dashboards when the source environment is changed. - - ƒ **fetchDatabases** (`Function`) `[CRITICAL]` + - ƒ **fetchDatabases** (`Function`) - 📝 Fetches databases from both environments and gets suggestions. - - ƒ **handleMappingUpdate** (`Function`) `[CRITICAL]` + - ƒ **handleMappingUpdate** (`Function`) - 📝 Saves a mapping to the backend. - - ƒ **handleViewLogs** (`Function`) `[CRITICAL]` + - ƒ **handleViewLogs** (`Function`) - 📝 Opens the log viewer for a specific task. - - ƒ **handlePasswordPrompt** (`Function`) `[CRITICAL]` + - ƒ **handlePasswordPrompt** (`Function`) - 📝 Reactive logic to show password prompt when a task is awaiting input. - - ▦ **ReactivePasswordPrompt** (`Block`) `[CRITICAL]` + - ▦ **ReactivePasswordPrompt** (`Block`) - 📝 Monitor selected task for input requests and trigger password prompt. - - ƒ **handleResumeMigration** (`Function`) `[CRITICAL]` + - ƒ **handleResumeMigration** (`Function`) - 📝 Resumes a migration task with provided passwords. - - ƒ **startMigration** (`Function`) `[CRITICAL]` + - ƒ **startMigration** (`Function`) - 📝 Initiates the migration process by sending the selection to the backend. - - ƒ **startDryRun** (`Function`) `[CRITICAL]` + - ƒ **startDryRun** (`Function`) - 📝 Performs a dry-run migration to identify potential risks and changes. -- ▦ **MigrationDashboardView** (`Block`) `[CRITICAL]` +- ▦ **MigrationDashboardView** (`Block`) - 📝 Render migration configuration controls, action CTAs, dry-run results, and modal entry points. - - ▦ **MigrationHeader** (`Block`) `[CRITICAL]` - - ▦ **TaskHistorySection** (`Block`) `[CRITICAL]` - - ▦ **ActiveTaskSection** (`Block`) `[CRITICAL]` - - ▦ **EnvironmentSelectionSection** (`Block`) `[CRITICAL]` - - 🧩 **DashboardSelectionSection** (`Component`) `[CRITICAL]` - - ▦ **MigrationOptionsSection** (`Block`) `[CRITICAL]` - - ▦ **DryRunResultsSection** (`Block`) `[CRITICAL]` - - ▦ **MigrationModals** (`Block`) `[CRITICAL]` + - ▦ **MigrationHeader** (`Block`) + - ▦ **TaskHistorySection** (`Block`) + - ▦ **ActiveTaskSection** (`Block`) + - ▦ **EnvironmentSelectionSection** (`Block`) + - 🧩 **DashboardSelectionSection** (`Component`) + - ▦ **MigrationOptionsSection** (`Block`) + - ▦ **DryRunResultsSection** (`Block`) + - ▦ **MigrationModals** (`Block`) - 📝 Render overlay components for log viewing and password entry. -- ▦ **MappingsPageScript** (`Block`) `[CRITICAL]` +- ▦ **MappingsPageScript** (`Block`) - 📝 Define imports, state, and handlers that drive migration mappings page FSM. - 🔗 CALLS -> `fetchEnvironments` - 🔗 CALLS -> `fetchDatabases` - 🔗 CALLS -> `handleUpdate` - - ▦ **Imports** (`Block`) `[CRITICAL]` + - ▦ **Imports** (`Block`) - 🗄️ **UiState** (`Store`) `[CRITICAL]` - 📝 Maintain local page state for environments, fetched databases, mappings, suggestions, and UX messages. - - ƒ **belief_scope** (`Function`) `[CRITICAL]` + - ƒ **belief_scope** (`Function`) - 📝 Frontend semantic scope wrapper for CRITICAL trace boundaries without changing business behavior. - - ƒ **fetchEnvironments** (`Function`) `[CRITICAL]` + - ƒ **fetchEnvironments** (`Function`) - 📝 Load environment options for source/target selectors on initial mount. - - ƒ **fetchDatabases** (`Function`) `[CRITICAL]` + - ƒ **fetchDatabases** (`Function`) - 📝 Fetch both environment database catalogs, existing mappings, and suggested matches. - - ƒ **handleUpdate** (`Function`) `[CRITICAL]` + - ƒ **handleUpdate** (`Function`) - 📝 Persist a selected mapping pair and reconcile local mapping list by source database UUID. -- ▦ **MappingsPageTemplate** (`Block`) `[CRITICAL]` +- ▦ **MappingsPageTemplate** (`Block`) - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/profile/+page.svelte - 🏗️ Layer: Unknown @@ -1312,6 +1354,7 @@ - ⬅️ READS_FROM `app` - ➡️ WRITES_TO `state` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `storageService` - ƒ **loadFiles** (`Function`) - 📝 Fetches the list of files from the server. - ƒ **resolveStorageQueryFromPath** (`Function`) @@ -1338,9 +1381,12 @@ - 📝 Consolidated Settings Page - All settings in one place with tabbed navigation - 🏗️ Layer: UI - 🔒 Invariant: Always shows tabbed interface with all settings categories + - 🔗 BINDS_TO -> `sidebarStore` - 📦 **+page** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/routes/settings/+page.svelte - 🏗️ Layer: Unknown + - ƒ **createEmptyConnectionDraft** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - ƒ **normalizeTab** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) - ƒ **readTabFromUrl** (`Function`) `[TRIVIAL]` @@ -1359,6 +1405,18 @@ - 📝 Auto-detected function (orphan) - ƒ **handleTabChange** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **loadConnections** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **resetConnectionForm** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **openConnectionForm** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **closeConnectionForm** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **handleCreateConnection** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **handleDeleteConnection** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - ƒ **loadMigrationSettings** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) - ƒ **loadMappingsPage** (`Function`) `[TRIVIAL]` @@ -1373,6 +1431,8 @@ - 📝 Auto-detected function (orphan) - ƒ **triggerSyncNow** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **handleRefresh** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - ƒ **handleSave** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) - ƒ **handleTestEnv** (`Function`) `[TRIVIAL]` @@ -1473,6 +1533,7 @@ - 📝 Displays the list of available plugins and allows selecting one. - 🏗️ Layer: UI - ⬅️ READS_FROM `plugins` + - 🔗 DEPENDS_ON -> `frontend/src/lib/stores.js` - ƒ **onMount** (`Function`) - 📝 Fetch plugins when the component mounts. - ƒ **selectPlugin** (`Function`) @@ -1483,6 +1544,7 @@ - 🔒 Invariant: Settings changes must be saved to the backend. - ⬅️ READS_FROM `t` - ➡️ WRITES_TO `t` + - 🔗 CALLS -> `api.js` - ƒ **loadSettings** (`Function`) - 📝 Loads settings from the backend. - ƒ **handleSaveGlobal** (`Function`) @@ -1506,6 +1568,7 @@ - 📦 **GitServiceClient** (`Module`) - 📝 API client for Git operations, managing the communication between frontend and backend. - 🏗️ Layer: Service + - 🔗 DEPENDS_ON -> `specs/011-git-integration-dashboard/contracts/api.md` - 📦 **gitService** (`Action`) - 📝 Promotes changes between branches via MR or direct merge. - ƒ **buildDashboardRepoEndpoint** (`Function`) `[TRIVIAL]` @@ -1629,6 +1692,8 @@ - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` - ⬅️ READS_FROM `effect` + - 🔗 BINDS_TO -> `onresume` + - 🔗 BINDS_TO -> `oncancel` - ƒ **handleSubmit** (`Function`) - 📝 Validates and forwards passwords to resume the task. - ƒ **handleCancel** (`Function`) @@ -1640,6 +1705,7 @@ - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `t` + - 🔗 BINDS_TO -> `mappings state` - ƒ **updateMapping** (`Function`) - 📝 Updates a mapping for a specific source database. - ƒ **getSuggestion** (`Function`) @@ -1674,6 +1740,8 @@ - 🔒 Invariant: Modal blocks migration progress until resolved or cancelled. - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` + - 🔗 BINDS_TO -> `onresolve` + - 🔗 BINDS_TO -> `oncancel` - ƒ **resolve** (`Function`) - 📝 Resolves the missing mapping via callback prop. - ƒ **cancel** (`Function`) @@ -1730,12 +1798,14 @@ - 📝 Displays transient notifications (toasts) in the bottom-right corner. - 🏗️ Layer: UI - ⬅️ READS_FROM `toasts` + - 🔗 DEPENDS_ON -> `frontend/src/lib/toasts.js` - 🧩 **TaskRunner** (`Component`) - 📝 Connects to a WebSocket to display real-time logs for a running task with filtering support. - 🏗️ Layer: UI - ⬅️ READS_FROM `t` - ⬅️ READS_FROM `selectedTask` - ➡️ WRITES_TO `t` + - 🔗 DEPENDS_ON -> `frontend/src/lib/stores.js, frontend/src/components/tasks/TaskLogPanel.svelte` - ƒ **connect** (`Function`) - 📝 Establishes WebSocket connection with exponential backoff and filter parameters. - ƒ **handleFilterChange** (`Function`) @@ -1769,6 +1839,7 @@ - 📝 Generates a form dynamically based on a JSON schema. - 🏗️ Layer: UI - ➡️ WRITES_TO `props` + - 🔗 BINDS_TO -> `onsubmit callback` - ƒ **handleSubmit** (`Function`) - 📝 Emits submitted form data via callback prop. - ƒ **initializeForm** (`Function`) @@ -1779,6 +1850,7 @@ - 🔒 Invariant: Source and target environments must be selectable from the list of configured environments. - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `t` + - 🔗 BINDS_TO -> `environments store` - ƒ **handleSelect** (`Function`) - 📝 Propagates the selection change through a callback prop. - 🧩 **StartupEnvironmentWizard** (`Component`) @@ -1788,6 +1860,8 @@ - ⬅️ READS_FROM `app` - ⬅️ READS_FROM `lib` - ➡️ WRITES_TO `props` + - 🔗 CALLS -> `api.addEnvironment` + - 🔗 CALLS -> `environmentContext.refreshEnvironmentContext` - 📦 **StartupEnvironmentWizard** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/components/StartupEnvironmentWizard.svelte - 🏗️ Layer: Unknown @@ -1811,9 +1885,13 @@ - 📝 Enforces authenticated and authorized access before protected route content is rendered. - 🏗️ Layer: UI - 🔒 Invariant: Unauthenticated users are redirected to /login, unauthorized users are redirected to fallbackPath, and protected slot renders only when access is verified. - - 🧩 **ProtectedRoute** (`Component`) `[CRITICAL]` + - 🔗 BINDS_TO -> `frontend.src.lib.auth.store.auth` + - 🔗 CALLS -> `$app/navigation.goto` + - 🔗 DEPENDS_ON -> `$lib/auth/permissions.hasPermission` + - 🔗 CALLS -> `frontend.src.lib.api.api.fetchApi` + - 🧩 **ProtectedRoute** (`Component`) - 📝 Wraps protected slot content with session and permission verification guards. - - ƒ **verifySessionAndAccess** (`Function`) `[CRITICAL]` + - ƒ **verifySessionAndAccess** (`Function`) - 📝 Validates session and optional permission gate before allowing protected content render. - 🧩 **TaskLogPanel** (`Component`) - 📝 Combines log filtering and display into a single cohesive dark-themed panel. @@ -1872,6 +1950,7 @@ - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `t` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `storageService` - ƒ **isDirectory** (`Function`) - 📝 Checks if a file object represents a directory. - ƒ **formatSize** (`Function`) @@ -1886,6 +1965,7 @@ - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` - ⬅️ READS_FROM `t` + - 🔗 DEPENDS_ON -> `storageService` - ƒ **handleUpload** (`Function`) - 📝 Handles the file upload process. - ƒ **handleDrop** (`Function`) @@ -1943,6 +2023,7 @@ - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` - ⬅️ READS_FROM `t` + - 🔗 CALLS -> `gitService.getHistory` - ƒ **onMount** (`Function`) - 📝 Load history when component is mounted. - ƒ **loadHistory** (`Function`) @@ -1954,6 +2035,8 @@ - ➡️ WRITES_TO `bindable` - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` + - 🔗 CALLS -> `frontend/src/services/gitService.js` + - 🔗 DISPATCHES -> `deploy` - 📦 **loadStatus** (`Watcher`) - ƒ **normalizeEnvStage** (`Function`) - 📝 Normalize environment stage with legacy production fallback. @@ -1970,6 +2053,7 @@ - ➡️ WRITES_TO `bindable` - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` + - 🔗 BINDS_TO -> `onresolve` - ƒ **resolve** (`Function`) - 📝 Set resolution strategy for a file. - ƒ **handleSave** (`Function`) @@ -1980,6 +2064,10 @@ - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` - ⬅️ READS_FROM `t` + - 🔗 CALLS -> `gitService.commit` + - 🔗 CALLS -> `gitService.getStatus` + - 🔗 CALLS -> `gitService.getDiff` + - 🔗 DISPATCHES -> `commit` - ƒ **handleGenerateMessage** (`Function`) - 📝 Generates a commit message using LLM. - ƒ **loadStatus** (`Function`) @@ -1992,6 +2080,10 @@ - ➡️ WRITES_TO `bindable` - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` + - 🔗 CALLS -> `gitService.getBranches` + - 🔗 CALLS -> `gitService.checkoutBranch` + - 🔗 CALLS -> `gitService.createBranch` + - 🔗 BINDS_TO -> `onchange` - ƒ **onMount** (`Function`) - 📝 Load branches when component is mounted. - ƒ **loadBranches** (`Function`) @@ -2008,6 +2100,7 @@ - ➡️ WRITES_TO `bindable` - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` + - 🔗 CALLS -> `gitService` - ƒ **normalizeEnvStage** (`Function`) - 📝 Normalize environment stage with legacy fallback. - ƒ **stageBadgeClass** (`Function`) @@ -2090,17 +2183,19 @@ - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `derived` - ➡️ WRITES_TO `state` + - 🔗 DEPENDS_ON -> `backend/src/plugins/llm_analysis/plugin.py` - 📦 **DocPreview** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/components/llm/DocPreview.svelte - 🏗️ Layer: Unknown - ƒ **handleSave** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) -- 🧩 **ProviderConfig** (`Component`) `[CRITICAL]` +- 🧩 **ProviderConfig** (`Component`) - 📝 UI form for managing LLM provider configurations. - 🏗️ Layer: UI - ➡️ WRITES_TO `props` - ➡️ WRITES_TO `state` - ➡️ WRITES_TO `t` + - 🔗 DEPENDS_ON -> `backend/src/api/routes/llm.py` - 📦 **ProviderConfig** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for frontend/src/components/llm/ProviderConfig.svelte - 🏗️ Layer: Unknown @@ -2347,9 +2442,9 @@ - 🏗️ Layer: Scripts - 🔒 Invariant: Safe to run multiple times (idempotent). - 🔗 CALLS -> `backend.src.core.database.init_db` - - ƒ **ensure_encryption_key** (`Function`) `[CRITICAL]` + - ƒ **ensure_encryption_key** (`Function`) - 📝 Ensure backend runtime has a persistent Fernet encryption key during first-time installation. - - ƒ **run_init** (`Function`) `[CRITICAL]` + - ƒ **run_init** (`Function`) - 📝 Main entry point for the initialization script. - 📦 **backend.src.scripts.create_admin** (`Module`) - 📝 CLI tool for creating the initial admin user. @@ -2553,43 +2648,43 @@ - 🔗 BINDS_TO -> `ConfigManager` - ℂ **ConfigManager** (`Class`) `[CRITICAL]` - 📝 Handles application configuration load, validation, mutation, and persistence lifecycle. - - ƒ **__init__** (`Function`) `[CRITICAL]` + - ƒ **__init__** (`Function`) - 📝 Initialize manager state from persisted or migrated configuration. - - ƒ **_default_config** (`Function`) `[CRITICAL]` + - ƒ **_default_config** (`Function`) - 📝 Build default application configuration fallback. - - ƒ **_sync_raw_payload_from_config** (`Function`) `[CRITICAL]` + - ƒ **_sync_raw_payload_from_config** (`Function`) - 📝 Merge typed AppConfig state into raw payload while preserving unsupported legacy sections. - - ƒ **_load_from_legacy_file** (`Function`) `[CRITICAL]` + - ƒ **_load_from_legacy_file** (`Function`) - 📝 Load legacy JSON configuration for migration fallback path. - - ƒ **_get_record** (`Function`) `[CRITICAL]` + - ƒ **_get_record** (`Function`) - 📝 Resolve global configuration record from DB. - - ƒ **_load_config** (`Function`) `[CRITICAL]` + - ƒ **_load_config** (`Function`) - 📝 Load configuration from DB or perform one-time migration from legacy JSON. - - ƒ **_save_config_to_db** (`Function`) `[CRITICAL]` + - ƒ **_save_config_to_db** (`Function`) - 📝 Persist provided AppConfig into the global DB configuration record. - - ƒ **save** (`Function`) `[CRITICAL]` + - ƒ **save** (`Function`) - 📝 Persist current in-memory configuration state. - - ƒ **get_config** (`Function`) `[CRITICAL]` + - ƒ **get_config** (`Function`) - 📝 Return current in-memory configuration snapshot. - - ƒ **get_payload** (`Function`) `[CRITICAL]` + - ƒ **get_payload** (`Function`) - 📝 Return full persisted payload including sections outside typed AppConfig schema. - - ƒ **save_config** (`Function`) `[CRITICAL]` + - ƒ **save_config** (`Function`) - 📝 Persist configuration provided either as typed AppConfig or raw payload dict. - - ƒ **update_global_settings** (`Function`) `[CRITICAL]` + - ƒ **update_global_settings** (`Function`) - 📝 Replace global settings and persist the resulting configuration. - - ƒ **validate_path** (`Function`) `[CRITICAL]` + - ƒ **validate_path** (`Function`) - 📝 Validate that path exists and is writable, creating it when absent. - - ƒ **get_environments** (`Function`) `[CRITICAL]` + - ƒ **get_environments** (`Function`) - 📝 Return all configured environments. - - ƒ **has_environments** (`Function`) `[CRITICAL]` + - ƒ **has_environments** (`Function`) - 📝 Check whether at least one environment exists in configuration. - - ƒ **get_environment** (`Function`) `[CRITICAL]` + - ƒ **get_environment** (`Function`) - 📝 Resolve a configured environment by identifier. - - ƒ **add_environment** (`Function`) `[CRITICAL]` + - ƒ **add_environment** (`Function`) - 📝 Upsert environment by id into configuration and persist. - - ƒ **update_environment** (`Function`) `[CRITICAL]` + - ƒ **update_environment** (`Function`) - 📝 Update existing environment by id and preserve masked password placeholder behavior. - - ƒ **delete_environment** (`Function`) `[CRITICAL]` + - ƒ **delete_environment** (`Function`) - 📝 Delete environment by id and persist when deletion occurs. - 📦 **SchedulerModule** (`Module`) - 📝 Manages scheduled tasks using APScheduler. @@ -2616,17 +2711,17 @@ - 📦 **ConfigModels** (`Module`) `[CRITICAL]` - 📝 Defines the data models for application configuration using Pydantic. - 🏗️ Layer: Core - - 📦 **Schedule** (`DataClass`) `[CRITICAL]` + - 📦 **Schedule** (`DataClass`) - 📝 Represents a backup schedule configuration. - - 📦 **Environment** (`DataClass`) `[CRITICAL]` + - 📦 **Environment** (`DataClass`) - 📝 Represents a Superset environment configuration. - - 📦 **LoggingConfig** (`DataClass`) `[CRITICAL]` + - 📦 **LoggingConfig** (`DataClass`) - 📝 Defines the configuration for the application's logging system. - - 📦 **CleanReleaseConfig** (`DataClass`) `[CRITICAL]` + - 📦 **CleanReleaseConfig** (`DataClass`) - 📝 Configuration for clean release compliance subsystem. - - 📦 **GlobalSettings** (`DataClass`) `[CRITICAL]` + - 📦 **GlobalSettings** (`DataClass`) - 📝 Represents global application settings. - - 📦 **AppConfig** (`DataClass`) `[CRITICAL]` + - 📦 **AppConfig** (`DataClass`) - 📝 The root configuration model containing all application settings. - 📦 **backend.src.core.database** (`Module`) `[CRITICAL]` - 📝 Configures database connection and session management (PostgreSQL-first). @@ -2635,19 +2730,19 @@ - 🔗 DEPENDS_ON -> `sqlalchemy` - 🔗 DEPENDS_ON -> `backend.src.models.mapping` - 🔗 DEPENDS_ON -> `backend.src.core.auth.config` - - 📦 **BASE_DIR** (`Variable`) `[CRITICAL]` + - 📦 **BASE_DIR** (`Variable`) - 📝 Base directory for the backend. - - 📦 **DATABASE_URL** (`Constant`) `[CRITICAL]` + - 📦 **DATABASE_URL** (`Constant`) - 📝 URL for the main application database. - - 📦 **TASKS_DATABASE_URL** (`Constant`) `[CRITICAL]` + - 📦 **TASKS_DATABASE_URL** (`Constant`) - 📝 URL for the tasks execution database. - - 📦 **AUTH_DATABASE_URL** (`Constant`) `[CRITICAL]` + - 📦 **AUTH_DATABASE_URL** (`Constant`) - 📝 URL for the authentication database. - - 📦 **engine** (`Variable`) `[CRITICAL]` + - 📦 **engine** (`Variable`) - 📝 SQLAlchemy engine for mappings database. - - 📦 **tasks_engine** (`Variable`) `[CRITICAL]` + - 📦 **tasks_engine** (`Variable`) - 📝 SQLAlchemy engine for tasks database. - - 📦 **auth_engine** (`Variable`) `[CRITICAL]` + - 📦 **auth_engine** (`Variable`) - 📝 SQLAlchemy engine for authentication database. - ℂ **SessionLocal** (`Class`) `[TRIVIAL]` - 📝 A session factory for the main mappings database. @@ -2655,21 +2750,23 @@ - 📝 A session factory for the tasks execution database. - ℂ **AuthSessionLocal** (`Class`) `[TRIVIAL]` - 📝 A session factory for the authentication database. - - ƒ **_ensure_user_dashboard_preferences_columns** (`Function`) `[CRITICAL]` + - ƒ **_ensure_user_dashboard_preferences_columns** (`Function`) - 📝 Applies additive schema upgrades for user_dashboard_preferences table. - - ƒ **_ensure_user_dashboard_preferences_health_columns** (`Function`) `[CRITICAL]` + - ƒ **_ensure_user_dashboard_preferences_health_columns** (`Function`) - 📝 Applies additive schema upgrades for user_dashboard_preferences table (health fields). - - ƒ **_ensure_llm_validation_results_columns** (`Function`) `[CRITICAL]` + - ƒ **_ensure_llm_validation_results_columns** (`Function`) - 📝 Applies additive schema upgrades for llm_validation_results table. - - ƒ **_ensure_git_server_configs_columns** (`Function`) `[CRITICAL]` + - ƒ **_ensure_git_server_configs_columns** (`Function`) - 📝 Applies additive schema upgrades for git_server_configs table. - - ƒ **init_db** (`Function`) `[CRITICAL]` + - ƒ **ensure_connection_configs_table** (`Function`) + - 📝 Ensures the external connection registry table exists in the main database. + - ƒ **init_db** (`Function`) - 📝 Initializes the database by creating all tables. - - ƒ **get_db** (`Function`) `[CRITICAL]` + - ƒ **get_db** (`Function`) - 📝 Dependency for getting a database session. - - ƒ **get_tasks_db** (`Function`) `[CRITICAL]` + - ƒ **get_tasks_db** (`Function`) - 📝 Dependency for getting a tasks database session. - - ƒ **get_auth_db** (`Function`) `[CRITICAL]` + - ƒ **get_auth_db** (`Function`) - 📝 Dependency for getting an authentication database session. - ƒ **_build_engine** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) @@ -2737,15 +2834,15 @@ - 🔗 DEPENDS_ON -> `yaml` - ℂ **MigrationEngine** (`Class`) `[CRITICAL]` - 📝 Engine for transforming Superset export ZIPs. - - ƒ **__init__** (`Function`) `[CRITICAL]` + - ƒ **__init__** (`Function`) - 📝 Initializes migration orchestration dependencies for ZIP/YAML metadata transformations. - - ƒ **transform_zip** (`Function`) `[CRITICAL]` + - ƒ **transform_zip** (`Function`) - 📝 Extracts ZIP, replaces database UUIDs in YAMLs, patches cross-filters, and re-packages. - - ƒ **_transform_yaml** (`Function`) `[CRITICAL]` + - ƒ **_transform_yaml** (`Function`) - 📝 Replaces database_uuid in a single YAML file. - - ƒ **_extract_chart_uuids_from_archive** (`Function`) `[CRITICAL]` + - ƒ **_extract_chart_uuids_from_archive** (`Function`) - 📝 Scans extracted chart YAML files and builds a source chart ID to UUID lookup map. - - ƒ **_patch_dashboard_metadata** (`Function`) `[CRITICAL]` + - ƒ **_patch_dashboard_metadata** (`Function`) - 📝 Rewrites dashboard json_metadata chart/dataset integer identifiers using target environment mappings. - 📦 **backend.src.core.async_superset_client** (`Module`) `[CRITICAL]` - 📝 Async Superset client for dashboard hot-path requests without blocking FastAPI event loop. @@ -2821,42 +2918,42 @@ - 🔗 DEPENDS_ON -> `pydantic` - ℂ **AuthConfig** (`Class`) `[CRITICAL]` - 📝 Holds authentication-related settings. - - 📦 **auth_config** (`Variable`) `[CRITICAL]` + - 📦 **auth_config** (`Variable`) - 📝 Singleton instance of AuthConfig. - 📦 **backend.src.core.auth.jwt** (`Module`) `[CRITICAL]` - 📝 JWT token generation and validation logic. - 🏗️ Layer: Core - 🔒 Invariant: Tokens must include expiration time and user identifier. - 🔗 DEPENDS_ON -> `jose` - - ƒ **create_access_token** (`Function`) `[CRITICAL]` + - ƒ **create_access_token** (`Function`) - 📝 Generates a new JWT access token. - - ƒ **decode_token** (`Function`) `[CRITICAL]` + - ƒ **decode_token** (`Function`) - 📝 Decodes and validates a JWT token. - 📦 **backend.src.core.auth.oauth** (`Module`) `[CRITICAL]` - 📝 ADFS OIDC configuration and client using Authlib. - 🏗️ Layer: Core - 🔒 Invariant: Must use secure OIDC flows. - 🔗 DEPENDS_ON -> `authlib` - - 📦 **oauth** (`Variable`) `[CRITICAL]` + - 📦 **oauth** (`Variable`) - 📝 Global Authlib OAuth registry. - - ƒ **register_adfs** (`Function`) `[CRITICAL]` + - ƒ **register_adfs** (`Function`) - 📝 Registers the ADFS OIDC client. - - ƒ **is_adfs_configured** (`Function`) `[CRITICAL]` + - ƒ **is_adfs_configured** (`Function`) - 📝 Checks if ADFS is properly configured. - 📦 **backend.src.core.auth.logger** (`Module`) `[CRITICAL]` - 📝 Audit logging for security-related events. - 🏗️ Layer: Core - 🔒 Invariant: Must not log sensitive data like passwords or full tokens. - - ƒ **log_security_event** (`Function`) `[CRITICAL]` + - ƒ **log_security_event** (`Function`) - 📝 Logs a security-related event for audit trails. - 📦 **backend.src.core.auth.security** (`Module`) `[CRITICAL]` - 📝 Utility for password hashing and verification using Passlib. - 🏗️ Layer: Core - 🔒 Invariant: Uses bcrypt for hashing with standard work factor. - 🔗 DEPENDS_ON -> `passlib` - - ƒ **verify_password** (`Function`) `[CRITICAL]` + - ƒ **verify_password** (`Function`) - 📝 Verifies a plain password against a hashed password. - - ƒ **get_password_hash** (`Function`) `[CRITICAL]` + - ƒ **get_password_hash** (`Function`) - 📝 Generates a bcrypt hash for a plain password. - 📦 **backend.src.core.auth.repository** (`Module`) `[CRITICAL]` - 📝 Data access layer for authentication and user preference entities. @@ -2869,29 +2966,29 @@ - ℂ **AuthRepository** (`Class`) `[CRITICAL]` - 📝 Encapsulates database operations for authentication-related entities. - 🔗 DEPENDS_ON -> `sqlalchemy.orm.Session` - - ƒ **__init__** (`Function`) `[CRITICAL]` + - ƒ **__init__** (`Function`) - 📝 Bind repository instance to an existing SQLAlchemy session. - - ƒ **get_user_by_username** (`Function`) `[CRITICAL]` + - ƒ **get_user_by_username** (`Function`) - 📝 Retrieve a user entity by unique username. - - ƒ **get_user_by_id** (`Function`) `[CRITICAL]` + - ƒ **get_user_by_id** (`Function`) - 📝 Retrieve a user entity by identifier. - - ƒ **get_role_by_name** (`Function`) `[CRITICAL]` + - ƒ **get_role_by_name** (`Function`) - 📝 Retrieve a role entity by role name. - - ƒ **update_last_login** (`Function`) `[CRITICAL]` + - ƒ **update_last_login** (`Function`) - 📝 Update last_login timestamp for the provided user entity. - - ƒ **get_role_by_id** (`Function`) `[CRITICAL]` + - ƒ **get_role_by_id** (`Function`) - 📝 Retrieve a role entity by identifier. - - ƒ **get_permission_by_id** (`Function`) `[CRITICAL]` + - ƒ **get_permission_by_id** (`Function`) - 📝 Retrieve a permission entity by identifier. - - ƒ **get_permission_by_resource_action** (`Function`) `[CRITICAL]` + - ƒ **get_permission_by_resource_action** (`Function`) - 📝 Retrieve a permission entity by resource and action pair. - - ƒ **get_user_dashboard_preference** (`Function`) `[CRITICAL]` + - ƒ **get_user_dashboard_preference** (`Function`) - 📝 Retrieve dashboard preference entity owned by specified user. - - ƒ **save_user_dashboard_preference** (`Function`) `[CRITICAL]` + - ƒ **save_user_dashboard_preference** (`Function`) - 📝 Persist dashboard preference entity and return refreshed persistent row. - - ƒ **list_permissions** (`Function`) `[CRITICAL]` + - ƒ **list_permissions** (`Function`) - 📝 List all permission entities available in storage. -- 📦 **src.core.auth** (`Package`) `[CRITICAL]` +- 📦 **src.core.auth** (`Package`) - 📝 Authentication and authorization package root. - 📦 **test_auth** (`Module`) - 📝 Unit tests for authentication module @@ -3158,21 +3255,21 @@ - 🔗 DEPENDS_ON -> `backend.src.core.migration.risk_assessor` - ℂ **MigrationDryRunService** (`Class`) `[CRITICAL]` - 📝 Build deterministic diff/risk payload for migration pre-flight. - - ƒ **__init__** (`Function`) `[CRITICAL]` + - ƒ **__init__** (`Function`) - 📝 Wire parser dependency for archive object extraction. - - ƒ **run** (`Function`) `[CRITICAL]` + - ƒ **run** (`Function`) - 📝 Execute full dry-run computation for selected dashboards. - - ƒ **_load_db_mapping** (`Function`) `[CRITICAL]` + - ƒ **_load_db_mapping** (`Function`) - 📝 Resolve UUID mapping for optional DB config replacement. - - ƒ **_accumulate_objects** (`Function`) `[CRITICAL]` + - ƒ **_accumulate_objects** (`Function`) - 📝 Merge extracted resources by UUID to avoid duplicates. - - ƒ **_index_by_uuid** (`Function`) `[CRITICAL]` + - ƒ **_index_by_uuid** (`Function`) - 📝 Build UUID-index map for normalized resources. - - ƒ **_build_object_diff** (`Function`) `[CRITICAL]` + - ƒ **_build_object_diff** (`Function`) - 📝 Compute create/update/delete buckets by UUID+signature. - - ƒ **_build_target_signatures** (`Function`) `[CRITICAL]` + - ƒ **_build_target_signatures** (`Function`) - 📝 Pull target metadata and normalize it into comparable signatures. - - ƒ **_build_risks** (`Function`) `[CRITICAL]` + - ƒ **_build_risks** (`Function`) - 📝 Build risk items for missing datasource, broken refs, overwrite, owner mismatch. - 📦 **backend.src.core.migration.archive_parser** (`Module`) `[CRITICAL]` - 📝 Parse Superset export ZIP archives into normalized object catalogs for diffing. @@ -3181,11 +3278,11 @@ - 🔗 DEPENDS_ON -> `backend.src.core.logger` - ℂ **MigrationArchiveParser** (`Class`) `[CRITICAL]` - 📝 Extract normalized dashboards/charts/datasets metadata from ZIP archives. - - ƒ **extract_objects_from_zip** (`Function`) `[CRITICAL]` + - ƒ **extract_objects_from_zip** (`Function`) - 📝 Extract object catalogs from Superset archive. - - ƒ **_collect_yaml_objects** (`Function`) `[CRITICAL]` + - ƒ **_collect_yaml_objects** (`Function`) - 📝 Read and normalize YAML manifests for one object type. - - ƒ **_normalize_object_payload** (`Function`) `[CRITICAL]` + - ƒ **_normalize_object_payload** (`Function`) - 📝 Convert raw YAML payload to stable diff signature shape. - 📦 **backend.src.core.migration.risk_assessor** (`Module`) `[CRITICAL]` - 📝 Compute deterministic migration risk items and aggregate score for dry-run reporting. @@ -3193,13 +3290,13 @@ - 🔒 Invariant: Risk scoring must remain bounded to [0,100] and preserve severity-to-weight mapping. - 🔗 DEPENDS_ON -> `backend.src.core.superset_client.SupersetClient` - 🔗 DISPATCHES -> `backend.src.core.migration.dry_run_orchestrator.MigrationDryRunService.run` - - ƒ **index_by_uuid** (`Function`) `[CRITICAL]` + - ƒ **index_by_uuid** (`Function`) - 📝 Build UUID-index from normalized objects. - - ƒ **extract_owner_identifiers** (`Function`) `[CRITICAL]` + - ƒ **extract_owner_identifiers** (`Function`) - 📝 Normalize owner payloads for stable comparison. - - ƒ **build_risks** (`Function`) `[CRITICAL]` + - ƒ **build_risks** (`Function`) - 📝 Build risk list from computed diffs and target catalog state. - - ƒ **score_risks** (`Function`) `[CRITICAL]` + - ƒ **score_risks** (`Function`) - 📝 Aggregate risk list into score and level. - 📦 **backend.src.core.migration.__init__** (`Module`) `[TRIVIAL]` - 📝 Namespace package for migration pre-flight orchestration components. @@ -3377,6 +3474,8 @@ - 📝 Get the TaskLogger instance for this context. - ƒ **params** (`Function`) - 📝 Get the task parameters. + - ƒ **background_tasks** (`Function`) + - 📝 Expose optional background task scheduler for plugins that dispatch deferred side effects. - ƒ **get_param** (`Function`) - 📝 Get a specific parameter value with optional default. - ƒ **create_sub_context** (`Function`) @@ -3402,21 +3501,25 @@ - 📝 Auto-detected function (orphan) - ƒ **test_progress_log** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) +- 📦 **backend.src.core.task_manager.__tests__.test_context** (`Module`) + - 📝 Verify TaskContext preserves optional background task scheduler across sub-context creation. + - ƒ **test_task_context_preserves_background_tasks_across_sub_context** (`Function`) + - 📝 Plugins must be able to access background_tasks from both root and sub-context loggers. - 📦 **backend.src.api.auth** (`Module`) `[CRITICAL]` - 📝 Authentication API endpoints. - 🏗️ Layer: API - 🔒 Invariant: All auth endpoints must return consistent error codes. - - 📦 **router** (`Variable`) `[CRITICAL]` + - 📦 **router** (`Variable`) - 📝 APIRouter instance for authentication routes. - - ƒ **login_for_access_token** (`Function`) `[CRITICAL]` + - ƒ **login_for_access_token** (`Function`) - 📝 Authenticates a user and returns a JWT access token. - - ƒ **read_users_me** (`Function`) `[CRITICAL]` + - ƒ **read_users_me** (`Function`) - 📝 Retrieves the profile of the currently authenticated user. - - ƒ **logout** (`Function`) `[CRITICAL]` + - ƒ **logout** (`Function`) - 📝 Logs out the current user (placeholder for session revocation). - - ƒ **login_adfs** (`Function`) `[CRITICAL]` + - ƒ **login_adfs** (`Function`) - 📝 Initiates the ADFS OIDC login flow. - - ƒ **auth_callback_adfs** (`Function`) `[CRITICAL]` + - ƒ **auth_callback_adfs** (`Function`) - 📝 Handles the callback from ADFS after successful authentication. - 📦 **src.api** (`Package`) - 📝 Backend API package root. @@ -3567,6 +3670,8 @@ - 📦 **ConnectionsRouter** (`Module`) - 📝 Defines the FastAPI router for managing external database connections. - 🏗️ Layer: UI (API) + - ƒ **_ensure_connections_schema** (`Function`) + - 📝 Ensures the connection_configs table exists before CRUD access. - ℂ **ConnectionSchema** (`Class`) - 📝 Pydantic model for connection response. - ℂ **ConnectionCreate** (`Class`) @@ -3581,8 +3686,12 @@ - 📝 API endpoints for dashboard health monitoring and status aggregation. - 🏗️ Layer: UI/API - 🔗 DEPENDS_ON -> `health_service` - - ƒ **get_health_summary** (`Function`) `[TRIVIAL]` - - 📝 Auto-detected function (orphan) + - ƒ **get_health_summary** (`Function`) + - 📝 Get aggregated health status for all dashboards. + - 🔗 CALLS -> `backend.src.services.health_service.HealthService` + - ƒ **delete_health_report** (`Function`) + - 📝 Delete one persisted dashboard validation report from health summary. + - 🔗 CALLS -> `backend.src.services.health_service.HealthService` - 📦 **backend.src.api.routes.environments** (`Module`) - 📝 API endpoints for listing environments and their databases. - 🏗️ Layer: API @@ -3614,19 +3723,19 @@ - 🔗 DEPENDS_ON -> `backend.src.core.mapping_service` - 🔗 DEPENDS_ON -> `backend.src.models.dashboard` - 🔗 DEPENDS_ON -> `backend.src.models.mapping` - - ƒ **get_dashboards** (`Function`) `[CRITICAL]` + - ƒ **get_dashboards** (`Function`) - 📝 Fetch dashboard metadata from a requested environment for migration selection UI. - - ƒ **execute_migration** (`Function`) `[CRITICAL]` + - ƒ **execute_migration** (`Function`) - 📝 Validate migration selection and enqueue asynchronous migration task execution. - - ƒ **dry_run_migration** (`Function`) `[CRITICAL]` + - ƒ **dry_run_migration** (`Function`) - 📝 Build pre-flight migration diff and risk summary without mutating target systems. - - ƒ **get_migration_settings** (`Function`) `[CRITICAL]` + - ƒ **get_migration_settings** (`Function`) - 📝 Read and return configured migration synchronization cron expression. - - ƒ **update_migration_settings** (`Function`) `[CRITICAL]` + - ƒ **update_migration_settings** (`Function`) - 📝 Validate and persist migration synchronization cron expression update. - - ƒ **get_resource_mappings** (`Function`) `[CRITICAL]` + - ƒ **get_resource_mappings** (`Function`) - 📝 Fetch synchronized resource mappings with optional filters and pagination for migration mappings view. - - ƒ **trigger_sync_now** (`Function`) `[CRITICAL]` + - ƒ **trigger_sync_now** (`Function`) - 📝 Trigger immediate ID synchronization for every configured environment. - 📦 **PluginsRouter** (`Module`) - 📝 Defines the FastAPI router for plugin-related endpoints, allowing clients to list available plugins. @@ -4436,6 +4545,15 @@ - 📝 Auto-detected function (orphan) - ƒ **test_release_reject_contract** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) +- 📦 **backend.src.api.routes.__tests__.test_connections_routes** (`Module`) + - 📝 Verifies connection routes bootstrap their table before CRUD access. + - 🏗️ Layer: API + - ƒ **db_session** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **test_list_connections_bootstraps_missing_table** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **test_create_connection_bootstraps_missing_table** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - 📦 **backend.src.api.routes.__tests__.test_git_api** (`Module`) - 📝 API tests for Git configurations and repository operations. - ƒ **__init__** (`Function`) `[TRIVIAL]` @@ -4852,11 +4970,11 @@ - 📝 SQLAlchemy models for multi-user authentication and authorization. - 🏗️ Layer: Domain - 🔒 Invariant: Usernames and emails must be unique. - - ƒ **generate_uuid** (`Function`) `[CRITICAL]` + - ƒ **generate_uuid** (`Function`) - 📝 Generates a unique UUID string. - - 📦 **user_roles** (`Table`) `[CRITICAL]` + - 📦 **user_roles** (`Table`) - 📝 Association table for many-to-many relationship between Users and Roles. - - 📦 **role_permissions** (`Table`) `[CRITICAL]` + - 📦 **role_permissions** (`Table`) - 📝 Association table for many-to-many relationship between Roles and Permissions. - ℂ **User** (`Class`) `[CRITICAL]` - 📝 Represents an identity that can authenticate to the system. @@ -4981,6 +5099,8 @@ - 📝 Initialize service with DB session and config manager. - ƒ **get_my_preference** (`Function`) - 📝 Return current user's persisted preference or default non-configured view. + - ƒ **get_dashboard_filter_binding** (`Function`) + - 📝 Return only dashboard-filter fields required by dashboards listing hot path. - ƒ **update_my_preference** (`Function`) - 📝 Validate and persist current user's profile preference in self-scoped mode. - ƒ **lookup_superset_accounts** (`Function`) @@ -5089,10 +5209,32 @@ - 📝 Business logic for aggregating dashboard health status from validation records. - 🏗️ Layer: Domain/Service - 🔗 DEPENDS_ON -> `ValidationRecord` + - 🔗 DEPENDS_ON -> `backend.src.core.superset_client.SupersetClient` + - 🔗 DEPENDS_ON -> `backend.src.core.task_manager.cleanup.TaskCleanupService` + - ℂ **HealthService** (`Class`) + - 📝 Aggregate latest dashboard validation state and manage persisted health report lifecycle. + - 🔗 CALLS -> `backend.src.core.superset_client.SupersetClient` + - 🔗 CALLS -> `backend.src.core.task_manager.cleanup.TaskCleanupService` + - ƒ **HealthService.__init__** (`Function`) + - 📝 Initialize health service with DB session and optional config access for dashboard metadata resolution. + - ƒ **HealthService._prime_dashboard_meta_cache** (`Function`) + - 📝 Warm dashboard slug/title cache with one Superset list fetch per environment. + - ƒ **HealthService._resolve_dashboard_meta** (`Function`) + - 📝 Resolve slug/title for a dashboard referenced by persisted validation record. + - ƒ **HealthService.get_health_summary** (`Function`) + - 📝 Aggregate latest validation status per dashboard and enrich rows with dashboard slug/title. + - ƒ **HealthService.delete_validation_report** (`Function`) + - 📝 Delete one persisted health report and optionally clean linked task/log artifacts. - ƒ **__init__** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **_prime_dashboard_meta_cache** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_resolve_dashboard_meta** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - ƒ **get_health_summary** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **delete_validation_report** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - 📦 **backend.src.services.llm_provider** (`Module`) - 📝 Service for managing LLM provider configurations with encrypted API keys. - 🏗️ Layer: Domain @@ -5147,8 +5289,12 @@ - 📝 Iterates API route files that may contain RBAC declarations. - ƒ **_discover_route_permissions** (`Function`) - 📝 Extracts explicit has_permission declarations from API route source code. + - ƒ **_discover_route_permissions_cached** (`Function`) + - 📝 Cache route permission discovery because route source files are static during normal runtime. - ƒ **_discover_plugin_execute_permissions** (`Function`) - 📝 Derives dynamic task permissions of form plugin:{plugin_id}:EXECUTE from plugin registry. + - ƒ **_discover_plugin_execute_permissions_cached** (`Function`) + - 📝 Cache dynamic plugin EXECUTE permission pairs by normalized plugin id tuple. - ƒ **discover_declared_permissions** (`Function`) - 📝 Builds canonical RBAC permission catalog from routes and plugin registry. - ƒ **sync_permission_catalog** (`Function`) @@ -5169,13 +5315,13 @@ - 🔗 DEPENDS_ON -> `backend.src.models.auth.Role` - ℂ **AuthService** (`Class`) `[CRITICAL]` - 📝 Provides high-level authentication services. - - ƒ **__init__** (`Function`) `[CRITICAL]` + - ƒ **__init__** (`Function`) - 📝 Initializes the authentication service with repository access over an active DB session. - - ƒ **authenticate_user** (`Function`) `[CRITICAL]` + - ƒ **authenticate_user** (`Function`) - 📝 Validates credentials and account state for local username/password authentication. - - ƒ **create_session** (`Function`) `[CRITICAL]` + - ƒ **create_session** (`Function`) - 📝 Issues an access token payload for an already authenticated user. - - ƒ **provision_adfs_user** (`Function`) `[CRITICAL]` + - ƒ **provision_adfs_user** (`Function`) - 📝 Performs ADFS Just-In-Time provisioning and role synchronization from AD group mappings. - 📦 **backend.src.services.git_service** (`Module`) - 📝 Core Git logic using GitPython to manage dashboard repositories. @@ -5342,6 +5488,16 @@ - 📝 Auto-detected function (orphan) - ƒ **test_get_health_summary_empty** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **test_get_health_summary_resolves_slug_and_title_from_superset** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **test_get_health_summary_reuses_dashboard_metadata_cache_across_service_instances** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **test_delete_validation_report_deletes_dashboard_scope_and_linked_tasks** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **test_delete_validation_report_returns_false_for_unknown_record** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **test_delete_validation_report_swallows_linked_task_cleanup_failure** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - 📦 **backend.src.services.__tests__.test_llm_plugin_persistence** (`Module`) - 📝 Regression test for ValidationRecord persistence fields populated from task context. - ℂ **_DummyLogger** (`Class`) @@ -5409,6 +5565,8 @@ - 📝 Verify provider binding resolution priority. - ƒ **test_normalize_llm_settings_keeps_assistant_planner_settings** (`Function`) - 📝 Ensure assistant planner provider/model fields are preserved and normalized. +- 📦 **_test_encryption_key_fixture** (`Global`) + - 📝 Ensure encryption-dependent provider tests run with a valid Fernet key. - 📦 **test_llm_provider** (`Module`) `[TRIVIAL]` - 📝 Auto-generated module for backend/src/services/__tests__/test_llm_provider.py - 🏗️ Layer: Unknown @@ -5430,6 +5588,8 @@ - 📝 Auto-detected function (orphan) - ƒ **test_get_decrypted_api_key_not_found** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **test_update_provider_ignores_masked_placeholder_api_key** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - 📦 **backend.src.services.__tests__.test_resource_service** (`Module`) - 📝 Unit tests for ResourceService - 🏗️ Layer: Service @@ -5444,6 +5604,8 @@ - ƒ **test_get_last_task_for_resource_empty_tasks** (`Function`) - ƒ **test_get_last_task_for_resource_no_match** (`Function`) - ƒ **test_get_dashboards_with_status_handles_mixed_naive_and_aware_task_datetimes** (`Function`) + - ƒ **test_get_dashboards_with_status_prefers_latest_decisive_validation_status_over_newer_unknown** (`Function`) + - ƒ **test_get_dashboards_with_status_falls_back_to_latest_unknown_without_decisive_history** (`Function`) - ƒ **test_get_last_task_for_resource_handles_mixed_naive_and_aware_created_at** (`Function`) - 📦 **backend.src.services.__tests__.test_rbac_permission_catalog** (`Module`) - 📝 Verifies RBAC permission catalog discovery and idempotent synchronization behavior. @@ -6367,21 +6529,21 @@ - 🔗 DEPENDS_ON -> `superset_tool.utils` - ℂ **MigrationPlugin** (`Class`) `[CRITICAL]` - 📝 Implementation of the migration plugin logic. - - ƒ **id** (`Function`) `[CRITICAL]` + - ƒ **id** (`Function`) - 📝 Returns the unique identifier for the migration plugin. - - ƒ **name** (`Function`) `[CRITICAL]` + - ƒ **name** (`Function`) - 📝 Returns the human-readable name of the migration plugin. - - ƒ **description** (`Function`) `[CRITICAL]` + - ƒ **description** (`Function`) - 📝 Returns a description of the migration plugin. - - ƒ **version** (`Function`) `[CRITICAL]` + - ƒ **version** (`Function`) - 📝 Returns the version of the migration plugin. - - ƒ **ui_route** (`Function`) `[CRITICAL]` + - ƒ **ui_route** (`Function`) - 📝 Returns the frontend route for the migration plugin. - - ƒ **get_schema** (`Function`) `[CRITICAL]` + - ƒ **get_schema** (`Function`) - 📝 Returns the JSON schema for migration plugin parameters. - - ƒ **execute** (`Function`) `[CRITICAL]` + - ƒ **execute** (`Function`) - 📝 Executes the dashboard migration logic with TaskContext support. - - 📦 **MigrationPlugin.execute** (`Action`) `[CRITICAL]` + - 📦 **MigrationPlugin.execute** (`Action`) - 📝 Execute the migration logic with proper task logging. - 📦 **src.plugins** (`Package`) - 📝 Plugin package root for dynamic discovery and runtime imports. @@ -6449,6 +6611,26 @@ - 📝 Handles capturing screenshots of Superset dashboards. - ƒ **ScreenshotService.__init__** (`Function`) - 📝 Initializes the ScreenshotService with environment configuration. + - ƒ **ScreenshotService._find_first_visible_locator** (`Function`) + - 📝 Resolve the first visible locator from multiple Playwright locator strategies. + - ƒ **ScreenshotService._iter_login_roots** (`Function`) + - 📝 Enumerate page and child frames where login controls may be rendered. + - ƒ **ScreenshotService._extract_hidden_login_fields** (`Function`) + - 📝 Collect hidden form fields required for direct login POST fallback. + - ƒ **ScreenshotService._extract_csrf_token** (`Function`) + - 📝 Resolve CSRF token value from main page or embedded login frame. + - ƒ **ScreenshotService._response_looks_like_login_page** (`Function`) + - 📝 Detect when fallback login POST returned the login form again instead of an authenticated page. + - ƒ **ScreenshotService._redirect_looks_authenticated** (`Function`) + - 📝 Treat non-login redirects after form POST as successful authentication without waiting for redirect target. + - ƒ **ScreenshotService._submit_login_via_form_post** (`Function`) + - 📝 Fallback login path that submits credentials directly with csrf token. + - ƒ **ScreenshotService._find_login_field_locator** (`Function`) + - 📝 Resolve login form input using semantic label text plus generic visible-input fallbacks. + - ƒ **ScreenshotService._find_submit_locator** (`Function`) + - 📝 Resolve login submit button from main page or embedded auth frame. + - ƒ **ScreenshotService._goto_resilient** (`Function`) + - 📝 Navigate without relying on networkidle for pages with long-polling or persistent requests. - ƒ **ScreenshotService.capture_dashboard** (`Function`) - 📝 Captures a full-page screenshot of a dashboard using Playwright and CDP. - ℂ **LLMClient** (`Class`) @@ -6459,6 +6641,8 @@ - 📝 Detect whether provider/model is likely compatible with response_format=json_object. - ƒ **LLMClient.get_json_completion** (`Function`) - 📝 Helper to handle LLM calls with JSON mode and fallback parsing. + - ƒ **LLMClient.test_runtime_connection** (`Function`) + - 📝 Validate provider credentials using the same chat completions transport as runtime analysis. - ƒ **LLMClient.analyze_dashboard** (`Function`) - 📝 Sends dashboard data (screenshot + logs) to LLM for health analysis. - 📦 **service** (`Module`) `[TRIVIAL]` @@ -6466,6 +6650,26 @@ - 🏗️ Layer: Unknown - ƒ **__init__** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **_find_first_visible_locator** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_iter_login_roots** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_extract_hidden_login_fields** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_extract_csrf_token** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_response_looks_like_login_page** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_redirect_looks_authenticated** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_submit_login_via_form_post** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_find_login_field_locator** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_find_submit_locator** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_goto_resilient** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - ƒ **capture_dashboard** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) - ƒ **switch_tabs** (`Function`) `[TRIVIAL]` @@ -6478,8 +6682,122 @@ - 📝 Auto-detected function (orphan) - ƒ **get_json_completion** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) + - ƒ **test_runtime_connection** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - ƒ **analyze_dashboard** (`Function`) `[TRIVIAL]` - 📝 Auto-detected function (orphan) +- 📦 **backend.src.plugins.llm_analysis.__tests__.test_service** (`Module`) + - 📝 Verify LLM analysis transport/provider failures do not masquerade as dashboard FAIL results. + - ƒ **test_test_runtime_connection_uses_json_completion_transport** (`Function`) + - 📝 Provider self-test must exercise the same chat completion transport as runtime analysis. + - ƒ **test_analyze_dashboard_provider_error_maps_to_unknown** (`Function`) + - 📝 Infrastructure/provider failures must produce UNKNOWN analysis status rather than FAIL. + - ƒ **_fake_get_json_completion** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **_raise_provider_error** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) +- 📦 **backend.src.plugins.llm_analysis.__tests__.test_client_headers** (`Module`) + - 📝 Verify OpenRouter client initialization includes provider-specific headers. + - ƒ **test_openrouter_client_includes_referer_and_title_headers** (`Function`) + - 📝 OpenRouter requests should carry site/app attribution headers for compatibility. +- 📦 **backend.src.plugins.llm_analysis.__tests__.test_screenshot_service** (`Module`) + - 📝 Protect dashboard screenshot navigation from brittle networkidle waits. + - ƒ **test_iter_login_roots_includes_child_frames** (`Function`) + - 📝 Login discovery must search embedded auth frames, not only the main page. + - ƒ **test_response_looks_like_login_page_detects_login_markup** (`Function`) + - 📝 Direct login fallback must reject responses that render the login screen again. + - ƒ **test_find_first_visible_locator_skips_hidden_first_match** (`Function`) + - 📝 Locator helper must not reject a selector collection just because its first element is hidden. + - ƒ **test_submit_login_via_form_post_uses_browser_context_request** (`Function`) + - 📝 Fallback login must submit hidden fields and credentials through the context request cookie jar. + - ƒ **test_submit_login_via_form_post_accepts_authenticated_redirect** (`Function`) + - 📝 Fallback login must treat non-login 302 redirect as success without waiting for redirect target. + - ƒ **test_submit_login_via_form_post_rejects_login_markup_response** (`Function`) + - 📝 Fallback login must fail when POST response still contains login form content. + - ƒ **test_goto_resilient_falls_back_from_domcontentloaded_to_load** (`Function`) + - 📝 Pages with unstable primary wait must retry with fallback wait strategy. + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **is_visible** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **count** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **nth** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **get_attribute** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **input_value** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **count** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **nth** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **text** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **post** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **locator** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **get_attribute** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **input_value** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **count** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **nth** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **text** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **post** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **locator** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **get_attribute** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **input_value** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **count** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **nth** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **text** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **post** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **locator** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **__init__** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) + - ƒ **goto** (`Function`) `[TRIVIAL]` + - 📝 Auto-detected function (orphan) - 📦 **StoragePlugin** (`Module`) - 📝 Provides core filesystem operations for managing backups and repositories. - 🏗️ Layer: App diff --git a/backend/src/api/routes/__tests__/test_connections_routes.py b/backend/src/api/routes/__tests__/test_connections_routes.py new file mode 100644 index 000000000..ee022fdc5 --- /dev/null +++ b/backend/src/api/routes/__tests__/test_connections_routes.py @@ -0,0 +1,72 @@ +# [DEF:backend.src.api.routes.__tests__.test_connections_routes:Module] +# @TIER: STANDARD +# @PURPOSE: Verifies connection routes bootstrap their table before CRUD access. +# @LAYER: API +# @RELATION: VERIFIES -> backend.src.api.routes.connections + +import os +import sys +import asyncio +from pathlib import Path + +import pytest +from sqlalchemy import create_engine, inspect +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import StaticPool + +# Force SQLite in-memory for database module imports. +os.environ["DATABASE_URL"] = "sqlite:///:memory:" +os.environ["TASKS_DATABASE_URL"] = "sqlite:///:memory:" +os.environ["AUTH_DATABASE_URL"] = "sqlite:///:memory:" +os.environ["ENVIRONMENT"] = "testing" + +backend_dir = str(Path(__file__).parent.parent.parent.parent.resolve()) +if backend_dir not in sys.path: + sys.path.insert(0, backend_dir) + + +@pytest.fixture +def db_session(): + engine = create_engine( + "sqlite:///:memory:", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + session = sessionmaker(bind=engine)() + try: + yield session + finally: + session.close() + + +def test_list_connections_bootstraps_missing_table(db_session): + from src.api.routes.connections import list_connections + + result = asyncio.run(list_connections(db=db_session)) + + inspector = inspect(db_session.get_bind()) + assert result == [] + assert "connection_configs" in inspector.get_table_names() + + +def test_create_connection_bootstraps_missing_table(db_session): + from src.api.routes.connections import ConnectionCreate, create_connection + + payload = ConnectionCreate( + name="Analytics Warehouse", + type="postgres", + host="warehouse.internal", + port=5432, + database="analytics", + username="reporter", + password="secret", + ) + + created = asyncio.run(create_connection(connection=payload, db=db_session)) + + inspector = inspect(db_session.get_bind()) + assert created.name == "Analytics Warehouse" + assert created.host == "warehouse.internal" + assert "connection_configs" in inspector.get_table_names() + +# [/DEF:backend.src.api.routes.__tests__.test_connections_routes:Module] diff --git a/backend/src/api/routes/connections.py b/backend/src/api/routes/connections.py index 6d662a0d0..012fe1a8b 100644 --- a/backend/src/api/routes/connections.py +++ b/backend/src/api/routes/connections.py @@ -9,7 +9,7 @@ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session -from ...core.database import get_db +from ...core.database import get_db, ensure_connection_configs_table from ...models.connection import ConnectionConfig from pydantic import BaseModel from datetime import datetime @@ -18,6 +18,16 @@ from ...core.logger import logger, belief_scope router = APIRouter() + +# [DEF:_ensure_connections_schema:Function] +# @PURPOSE: Ensures the connection_configs table exists before CRUD access. +# @PRE: db is an active SQLAlchemy session. +# @POST: The current bind can safely query ConnectionConfig. +def _ensure_connections_schema(db: Session): + with belief_scope("ConnectionsRouter.ensure_schema"): + ensure_connection_configs_table(db.get_bind()) +# [/DEF:_ensure_connections_schema:Function] + # [DEF:ConnectionSchema:Class] # @PURPOSE: Pydantic model for connection response. class ConnectionSchema(BaseModel): @@ -55,6 +65,7 @@ class ConnectionCreate(BaseModel): @router.get("", response_model=List[ConnectionSchema]) async def list_connections(db: Session = Depends(get_db)): with belief_scope("ConnectionsRouter.list_connections"): + _ensure_connections_schema(db) connections = db.query(ConnectionConfig).all() return connections # [/DEF:list_connections:Function] @@ -69,6 +80,7 @@ async def list_connections(db: Session = Depends(get_db)): @router.post("", response_model=ConnectionSchema, status_code=status.HTTP_201_CREATED) async def create_connection(connection: ConnectionCreate, db: Session = Depends(get_db)): with belief_scope("ConnectionsRouter.create_connection", f"name={connection.name}"): + _ensure_connections_schema(db) db_connection = ConnectionConfig(**connection.dict()) db.add(db_connection) db.commit() @@ -87,6 +99,7 @@ async def create_connection(connection: ConnectionCreate, db: Session = Depends( @router.delete("/{connection_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_connection(connection_id: str, db: Session = Depends(get_db)): with belief_scope("ConnectionsRouter.delete_connection", f"id={connection_id}"): + _ensure_connections_schema(db) db_connection = db.query(ConnectionConfig).filter(ConnectionConfig.id == connection_id).first() if not db_connection: logger.error(f"[ConnectionsRouter.delete_connection][State] Connection {connection_id} not found") @@ -97,4 +110,4 @@ async def delete_connection(connection_id: str, db: Session = Depends(get_db)): return # [/DEF:delete_connection:Function] -# [/DEF:ConnectionsRouter:Module] \ No newline at end of file +# [/DEF:ConnectionsRouter:Module] diff --git a/backend/src/app.py b/backend/src/app.py index 7ddd22a53..5e5eab9fd 100755 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -16,13 +16,14 @@ from starlette.middleware.sessions import SessionMiddleware from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse -import asyncio - -from .dependencies import get_task_manager, get_scheduler_service -from .core.utils.network import NetworkError -from .core.logger import logger, belief_scope -from .api.routes import plugins, tasks, settings, environments, mappings, migration, connections, git, storage, admin, llm, dashboards, datasets, reports, assistant, clean_release, clean_release_v2, profile, health -from .api import auth +import asyncio + +from .dependencies import get_task_manager, get_scheduler_service +from .core.encryption_key import ensure_encryption_key +from .core.utils.network import NetworkError +from .core.logger import logger, belief_scope +from .api.routes import plugins, tasks, settings, environments, mappings, migration, connections, git, storage, admin, llm, dashboards, datasets, reports, assistant, clean_release, clean_release_v2, profile, health +from .api import auth # [DEF:App:Global] # @SEMANTICS: app, fastapi, instance @@ -39,12 +40,13 @@ app = FastAPI( # @PRE: None. # @POST: Scheduler is started. # Startup event -@app.on_event("startup") -async def startup_event(): - with belief_scope("startup_event"): - scheduler = get_scheduler_service() - scheduler.start() -# [/DEF:startup_event:Function] +@app.on_event("startup") +async def startup_event(): + with belief_scope("startup_event"): + ensure_encryption_key() + scheduler = get_scheduler_service() + scheduler.start() +# [/DEF:startup_event:Function] # [DEF:shutdown_event:Function] # @PURPOSE: Handles application shutdown tasks, such as stopping the scheduler. diff --git a/backend/src/core/database.py b/backend/src/core/database.py index c46329111..ec50ef450 100644 --- a/backend/src/core/database.py +++ b/backend/src/core/database.py @@ -14,6 +14,7 @@ from sqlalchemy import create_engine, inspect, text from sqlalchemy.orm import sessionmaker from ..models.mapping import Base +from ..models.connection import ConnectionConfig # Import models to ensure they're registered with Base from ..models import task as _task_models # noqa: F401 from ..models import auth as _auth_models # noqa: F401 @@ -22,6 +23,7 @@ from ..models import llm as _llm_models # noqa: F401 from ..models import assistant as _assistant_models # noqa: F401 from ..models import profile as _profile_models # noqa: F401 from ..models import clean_release as _clean_release_models # noqa: F401 +from ..models import connection as _connection_models # noqa: F401 from .logger import belief_scope, logger from .auth.config import auth_config import os @@ -281,6 +283,23 @@ def _ensure_git_server_configs_columns(bind_engine): # [/DEF:_ensure_git_server_configs_columns:Function] +# [DEF:ensure_connection_configs_table:Function] +# @PURPOSE: Ensures the external connection registry table exists in the main database. +# @PRE: bind_engine points to the application database. +# @POST: connection_configs table exists without dropping existing data. +def ensure_connection_configs_table(bind_engine): + with belief_scope("ensure_connection_configs_table"): + try: + ConnectionConfig.__table__.create(bind=bind_engine, checkfirst=True) + except Exception as migration_error: + logger.warning( + "[database][EXPLORE] ConnectionConfig table ensure failed: %s", + migration_error, + ) + raise +# [/DEF:ensure_connection_configs_table:Function] + + # [DEF:init_db:Function] # @PURPOSE: Initializes the database by creating all tables. # @PRE: engine, tasks_engine and auth_engine are initialized. @@ -295,6 +314,7 @@ def init_db(): _ensure_llm_validation_results_columns(engine) _ensure_user_dashboard_preferences_health_columns(engine) _ensure_git_server_configs_columns(engine) + ensure_connection_configs_table(engine) # [/DEF:init_db:Function] # [DEF:get_db:Function] diff --git a/backend/src/core/encryption_key.py b/backend/src/core/encryption_key.py new file mode 100644 index 000000000..558c56937 --- /dev/null +++ b/backend/src/core/encryption_key.py @@ -0,0 +1,56 @@ +# [DEF:backend.src.core.encryption_key:Module] +# @TIER: CRITICAL +# @SEMANTICS: encryption, key, bootstrap, environment, startup +# @PURPOSE: Resolve and persist the Fernet encryption key required by runtime services. +# @LAYER: Infra +# @RELATION: DEPENDS_ON -> backend.src.core.logger +# @INVARIANT: Runtime key resolution never falls back to an ephemeral secret. + +from __future__ import annotations + +import os +from pathlib import Path + +from cryptography.fernet import Fernet + +from .logger import logger, belief_scope + +DEFAULT_ENV_FILE_PATH = Path(__file__).resolve().parents[2] / ".env" + + +# [DEF:ensure_encryption_key:Function] +# @PURPOSE: Ensure backend runtime has a persistent valid Fernet key. +# @PRE: env_file_path points to a writable backend .env file or ENCRYPTION_KEY exists in process environment. +# @POST: Returns a valid Fernet key and guarantees it is present in process environment. +# @SIDE_EFFECT: May create or append backend/.env when key is missing. +def ensure_encryption_key(env_file_path: Path = DEFAULT_ENV_FILE_PATH) -> str: + with belief_scope("ensure_encryption_key", f"env_file_path={env_file_path}"): + existing_key = os.getenv("ENCRYPTION_KEY", "").strip() + if existing_key: + Fernet(existing_key.encode()) + logger.reason("Using ENCRYPTION_KEY from process environment.") + return existing_key + + if env_file_path.exists(): + for raw_line in env_file_path.read_text(encoding="utf-8").splitlines(): + if raw_line.startswith("ENCRYPTION_KEY="): + persisted_key = raw_line.partition("=")[2].strip() + if persisted_key: + Fernet(persisted_key.encode()) + os.environ["ENCRYPTION_KEY"] = persisted_key + logger.reason(f"Loaded ENCRYPTION_KEY from {env_file_path}.") + return persisted_key + + generated_key = Fernet.generate_key().decode() + with env_file_path.open("a", encoding="utf-8") as env_file: + if env_file.tell() > 0: + env_file.write("\n") + env_file.write(f"ENCRYPTION_KEY={generated_key}\n") + + os.environ["ENCRYPTION_KEY"] = generated_key + logger.reason(f"Generated ENCRYPTION_KEY and persisted it to {env_file_path}.") + logger.reflect("Encryption key is available for runtime services.") + return generated_key +# [/DEF:ensure_encryption_key:Function] + +# [/DEF:backend.src.core.encryption_key:Module] diff --git a/backend/src/scripts/init_auth_db.py b/backend/src/scripts/init_auth_db.py index 587c79948..b65f7b4d0 100644 --- a/backend/src/scripts/init_auth_db.py +++ b/backend/src/scripts/init_auth_db.py @@ -8,54 +8,18 @@ # @INVARIANT: Safe to run multiple times (idempotent). # [SECTION: IMPORTS] -import os import sys from pathlib import Path -from cryptography.fernet import Fernet # Add src to path sys.path.append(str(Path(__file__).parent.parent.parent)) from src.core.database import init_db +from src.core.encryption_key import ensure_encryption_key from src.core.logger import logger, belief_scope from src.scripts.seed_permissions import seed_permissions # [/SECTION] -ENV_FILE_PATH = Path(__file__).resolve().parents[2] / ".env" - - -# [DEF:ensure_encryption_key:Function] -# @PURPOSE: Ensure backend runtime has a persistent Fernet encryption key during first-time installation. -# @PRE: Backend root is writable or ENCRYPTION_KEY is already provided via environment. -# @POST: ENCRYPTION_KEY exists in process environment or backend/.env. -def ensure_encryption_key(env_file_path: Path = ENV_FILE_PATH) -> str: - existing_key = os.getenv("ENCRYPTION_KEY", "").strip() - if existing_key: - Fernet(existing_key.encode()) - logger.info("ENCRYPTION_KEY already provided via environment; skipping generation.") - return existing_key - - if env_file_path.exists(): - for raw_line in env_file_path.read_text(encoding="utf-8").splitlines(): - if raw_line.startswith("ENCRYPTION_KEY="): - persisted_key = raw_line.partition("=")[2].strip() - if persisted_key: - Fernet(persisted_key.encode()) - os.environ["ENCRYPTION_KEY"] = persisted_key - logger.info(f"Loaded existing ENCRYPTION_KEY from {env_file_path}.") - return persisted_key - - generated_key = Fernet.generate_key().decode() - with env_file_path.open("a", encoding="utf-8") as env_file: - if env_file.tell() > 0: - env_file.write("\n") - env_file.write(f"ENCRYPTION_KEY={generated_key}\n") - - os.environ["ENCRYPTION_KEY"] = generated_key - logger.info(f"Generated ENCRYPTION_KEY and persisted it to {env_file_path}.") - return generated_key -# [/DEF:ensure_encryption_key:Function] - # [DEF:run_init:Function] # @PURPOSE: Main entry point for the initialization script. # @POST: auth.db is initialized with the correct schema and seeded permissions. diff --git a/backend/src/services/__tests__/test_health_service.py b/backend/src/services/__tests__/test_health_service.py index 9eae1e03f..87384b18a 100644 --- a/backend/src/services/__tests__/test_health_service.py +++ b/backend/src/services/__tests__/test_health_service.py @@ -122,6 +122,43 @@ async def test_get_health_summary_resolves_slug_and_title_from_superset(): mock_client.get_dashboards_summary.assert_called_once_with() +@pytest.mark.anyio +async def test_get_health_summary_reuses_dashboard_metadata_cache_across_service_instances(): + HealthService._dashboard_summary_cache.clear() + + db = MagicMock() + config_manager = MagicMock() + config_manager.get_environments.return_value = [MagicMock(id="env_1")] + record = ValidationRecord( + id="rec-1", + dashboard_id="42", + environment_id="env_1", + status="PASS", + timestamp=datetime.utcnow(), + summary="Healthy", + issues=[], + ) + db.query.return_value.join.return_value.all.return_value = [record] + + with patch("src.services.health_service.SupersetClient") as mock_client_cls: + mock_client = MagicMock() + mock_client.get_dashboards_summary.return_value = [ + {"id": 42, "slug": "ops-overview", "title": "Ops Overview"} + ] + mock_client_cls.return_value = mock_client + + first_service = HealthService(db, config_manager=config_manager) + second_service = HealthService(db, config_manager=config_manager) + + first_summary = await first_service.get_health_summary(environment_id="env_1") + second_summary = await second_service.get_health_summary(environment_id="env_1") + + assert first_summary.items[0].dashboard_slug == "ops-overview" + assert second_summary.items[0].dashboard_slug == "ops-overview" + mock_client.get_dashboards_summary.assert_called_once_with() + HealthService._dashboard_summary_cache.clear() + + def test_delete_validation_report_deletes_dashboard_scope_and_linked_tasks(): db = MagicMock() config_manager = MagicMock() diff --git a/backend/src/services/health_service.py b/backend/src/services/health_service.py index 8f8bc9dda..172655d00 100644 --- a/backend/src/services/health_service.py +++ b/backend/src/services/health_service.py @@ -8,6 +8,7 @@ # @RELATION: DEPENDS_ON -> backend.src.core.task_manager.cleanup.TaskCleanupService from typing import List, Dict, Any, Optional, Tuple +import time from sqlalchemy.orm import Session from sqlalchemy import func, desc import os @@ -24,6 +25,9 @@ from ..core.task_manager import TaskManager # @RELATION: CALLS -> backend.src.core.superset_client.SupersetClient # @RELATION: CALLS -> backend.src.core.task_manager.cleanup.TaskCleanupService class HealthService: + _dashboard_summary_cache: Dict[str, Tuple[float, Dict[str, Dict[str, Optional[str]]]]] = {} + _dashboard_summary_cache_ttl_seconds = 60.0 + """ @PURPOSE: Service for managing and querying dashboard health data. """ @@ -77,15 +81,27 @@ class HealthService: continue try: - dashboards = SupersetClient(env).get_dashboards_summary() - dashboard_meta_map = { - str(item.get("id")): { - "slug": item.get("slug"), - "title": item.get("title"), + cached_meta = self.__class__._dashboard_summary_cache.get(environment_id) + cache_is_fresh = ( + cached_meta is not None + and (time.monotonic() - cached_meta[0]) < self.__class__._dashboard_summary_cache_ttl_seconds + ) + if cache_is_fresh: + dashboard_meta_map = cached_meta[1] + else: + dashboards = SupersetClient(env).get_dashboards_summary() + dashboard_meta_map = { + str(item.get("id")): { + "slug": item.get("slug"), + "title": item.get("title"), + } + for item in dashboards + if str(item.get("id") or "").strip() } - for item in dashboards - if str(item.get("id") or "").strip() - } + self.__class__._dashboard_summary_cache[environment_id] = ( + time.monotonic(), + dashboard_meta_map, + ) for dashboard_id in dashboard_ids: self._dashboard_meta_cache[(environment_id, dashboard_id)] = dashboard_meta_map.get( dashboard_id, diff --git a/frontend/src/lib/stores/health.js b/frontend/src/lib/stores/health.js index 548a1fa13..b4bcdd57e 100644 --- a/frontend/src/lib/stores/health.js +++ b/frontend/src/lib/stores/health.js @@ -28,25 +28,41 @@ function createHealthStore() { loading: false, lastUpdated: null }); + let inflightRefresh = null; + let inflightEnvironmentId = null; /** * Refresh health summary from API * @param {string|null} environmentId - Optional environment filter */ async function refresh(environmentId = null) { - update(s => ({ ...s, loading: true })); - try { - console.log(`[HealthStore][Action] Refreshing health summary context={{'environmentId': '${environmentId}'}}`); - const summary = await api.getHealthSummary(environmentId); - set({ - ...summary, - loading: false, - lastUpdated: new Date() - }); - } catch (error) { - console.error('[HealthStore][Coherence:Failed] Failed to fetch health summary:', error); - update(s => ({ ...s, loading: false })); + const normalizedEnvironmentId = environmentId || null; + if (inflightRefresh && inflightEnvironmentId === normalizedEnvironmentId) { + return inflightRefresh; } + + update(s => ({ ...s, loading: true })); + inflightEnvironmentId = normalizedEnvironmentId; + inflightRefresh = (async () => { + try { + console.log(`[HealthStore][Action] Refreshing health summary context={{'environmentId': '${environmentId}'}}`); + const summary = await api.getHealthSummary(environmentId); + set({ + ...summary, + loading: false, + lastUpdated: new Date() + }); + return summary; + } catch (error) { + console.error('[HealthStore][Coherence:Failed] Failed to fetch health summary:', error); + update(s => ({ ...s, loading: false })); + throw error; + } finally { + inflightRefresh = null; + inflightEnvironmentId = null; + } + })(); + return inflightRefresh; } return { @@ -63,4 +79,4 @@ export const healthStore = createHealthStore(); */ export const failingCount = derived(healthStore, $health => $health.fail_count); -// [/DEF:health_store:Store] \ No newline at end of file +// [/DEF:health_store:Store] diff --git a/frontend/src/routes/dashboards/health/+page.svelte b/frontend/src/routes/dashboards/health/+page.svelte index 95feecad1..2efdc7f07 100644 --- a/frontend/src/routes/dashboards/health/+page.svelte +++ b/frontend/src/routes/dashboards/health/+page.svelte @@ -14,6 +14,7 @@ import { onMount } from 'svelte'; import HealthMatrix from '$lib/components/health/HealthMatrix.svelte'; import { getHealthSummary, getEnvironments, requestApi } from '$lib/api.js'; + import { healthStore } from '$lib/stores/health.js'; import { t } from '$lib/i18n'; import { addToast } from '$lib/toasts.js'; import { formatDistanceToNow } from 'date-fns'; @@ -40,8 +41,11 @@ loading = true; error = null; try { + const summaryPromise = selectedEnvId + ? getHealthSummary(selectedEnvId) + : healthStore.refresh(); const [summary, envs] = await Promise.all([ - getHealthSummary(selectedEnvId), + summaryPromise, getEnvironments() ]); healthData = summary; diff --git a/frontend/src/routes/dashboards/health/__tests__/health_page.integration.test.js b/frontend/src/routes/dashboards/health/__tests__/health_page.integration.test.js index 2e23d1124..3c7336beb 100644 --- a/frontend/src/routes/dashboards/health/__tests__/health_page.integration.test.js +++ b/frontend/src/routes/dashboards/health/__tests__/health_page.integration.test.js @@ -31,5 +31,13 @@ describe('Dashboard health page contract', () => { expect(source).toContain("$t.health?.delete_confirm.replace('{slug}', item.dashboard_slug || item.dashboard_id)"); expect(source).toContain("$t.common?.delete"); }); + + it('reuses global health store refresh for all-environment summary loads', () => { + const source = fs.readFileSync(PAGE_PATH, 'utf-8'); + + expect(source).toContain("import { healthStore } from '$lib/stores/health.js';"); + expect(source).toContain('? getHealthSummary(selectedEnvId)'); + expect(source).toContain(': healthStore.refresh();'); + }); }); // [/DEF:frontend.src.routes.dashboards.health.__tests__.health_page_integration:Module] diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index c2cfde1b4..44da13c36 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -84,6 +84,25 @@ }, }); + function createEmptyConnectionDraft() { + return { + name: "", + type: "postgres", + host: "", + port: "5432", + database: "", + username: "", + password: "", + }; + } + + let connections = $state([]); + let hasLoadedConnections = $state(false); + let isLoadingConnections = $state(false); + let isSubmittingConnection = $state(false); + let isConnectionFormVisible = $state(false); + let newConnection = $state(createEmptyConnectionDraft()); + function normalizeTab(value) { const normalized = String(value || "") .trim() @@ -122,10 +141,19 @@ activeTab = readTabFromUrl(); const syncTabFromUrl = () => { activeTab = readTabFromUrl(); + if (activeTab === "connections") { + void loadConnections(); + } + if (activeTab === "migration") { + void loadMigrationSettings(); + } }; window.addEventListener("popstate", syncTabFromUrl); window.addEventListener("hashchange", syncTabFromUrl); await loadSettings(); + if (activeTab === "connections") { + await loadConnections(); + } await loadMigrationSettings(); return () => { window.removeEventListener("popstate", syncTabFromUrl); @@ -207,7 +235,85 @@ activeTab = normalizedTab; writeTabToUrl(activeTab); if (normalizedTab === "migration") { - loadMigrationSettings(); + void loadMigrationSettings(); + } + if (normalizedTab === "connections") { + void loadConnections(); + } + } + + async function loadConnections(force = false) { + if (isLoadingConnections || (hasLoadedConnections && !force)) { + return; + } + isLoadingConnections = true; + try { + const response = await api.requestApi("/settings/connections"); + connections = Array.isArray(response) ? response : []; + hasLoadedConnections = true; + } catch (err) { + console.error("[SettingsPage][Connections] Failed to load:", err); + addToast(err.message || $t.connections?.fetch_failed, "error"); + connections = []; + } finally { + isLoadingConnections = false; + } + } + + function resetConnectionForm() { + newConnection = createEmptyConnectionDraft(); + } + + function openConnectionForm() { + isConnectionFormVisible = true; + } + + function closeConnectionForm() { + isConnectionFormVisible = false; + resetConnectionForm(); + } + + async function handleCreateConnection() { + if ( + !newConnection.name || + !newConnection.host || + !newConnection.database || + !newConnection.username || + !newConnection.password + ) { + addToast($t.connections?.required_fields, "warning"); + return; + } + + isSubmittingConnection = true; + try { + await api.requestApi("/settings/connections", "POST", { + ...newConnection, + port: newConnection.port ? Number(newConnection.port) : null, + }); + addToast($t.connections?.created_success, "success"); + closeConnectionForm(); + await loadConnections(true); + } catch (err) { + console.error("[SettingsPage][Connections] Failed to create:", err); + addToast(err.message || $t.settings?.save_failed, "error"); + } finally { + isSubmittingConnection = false; + } + } + + async function handleDeleteConnection(connectionId) { + if (!confirm($t.connections?.delete_confirm)) { + return; + } + + try { + await api.requestApi(`/settings/connections/${connectionId}`, "DELETE"); + addToast($t.connections?.deleted_success, "success"); + await loadConnections(true); + } catch (err) { + console.error("[SettingsPage][Connections] Failed to delete:", err); + addToast(err.message || $t.settings?.save_failed, "error"); } } @@ -309,6 +415,16 @@ } } + async function handleRefresh() { + await loadSettings(); + if (activeTab === "connections") { + await loadConnections(true); + } + if (activeTab === "migration") { + await loadMigrationSettings(); + } + } + // Handle global settings save (Logging, Storage) async function handleSave() { console.log("[SettingsPage][Action] Saving settings"); @@ -466,7 +582,7 @@ @@ -968,32 +1084,240 @@ {:else if activeTab === "connections"}
-

- {$t.settings?.connections} -

-

- {$t.settings?.connections_description} -

+
+
+

+ {$t.settings?.connections} +

+

+ {$t.settings?.connections_description} +

+
- {#if settings.connections && settings.connections.length > 0} - -

- No additional connections configured. Superset database - connections are used by default. -

- {:else} + {#if connections.length > 0 || isConnectionFormVisible} + + {/if} +
+ + {#if isConnectionFormVisible} +
+
{ + event.preventDefault(); + handleCreateConnection(); + }} + > +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ {/if} + + {#if isLoadingConnections}
+ {$t.common?.loading || "Loading..."} +
+ {:else if connections.length > 0} +
+ + + + + + + + + + + {#each connections as connection} + + + + + + + {/each} + +
{$t.connections?.name}{$t.connections?.host}{$t.connections?.user}{$t.settings?.env_actions || "Actions"}
+ {connection.name} + + {connection.type}://{connection.host}:{connection.port}/{connection.database} + + {connection.username} + + +
+
+ {:else if !isConnectionFormVisible} +

{$t.settings?.no_external_connections}

+ {:else} +
+ {$t.connections?.no_saved} +
{/if}
{:else if activeTab === "git"} diff --git a/frontend/src/routes/settings/__tests__/settings_page.integration.test.js b/frontend/src/routes/settings/__tests__/settings_page.integration.test.js index 0df890195..853c67d7e 100644 --- a/frontend/src/routes/settings/__tests__/settings_page.integration.test.js +++ b/frontend/src/routes/settings/__tests__/settings_page.integration.test.js @@ -34,17 +34,34 @@ vi.mock('$lib/i18n', () => ({ environments: 'Environments', logging: 'Logging', connections: 'Connections', + connections_description: 'Configure database connections for data mapping.', llm: 'LLM', storage: 'Storage', + no_external_connections: 'No external connections configured.', save_success: 'Settings saved', save_failed: 'Failed', env_description: 'Configure environments', - migration_sync_failed: 'Sync failed' + migration_sync_failed: 'Sync failed', + saving: 'Saving...' }, - common: { refresh: 'Refresh', save: 'Save' }, + common: { refresh: 'Refresh', save: 'Save', cancel: 'Cancel', loading: 'Loading...' }, tasks: { cron_label: 'Cron Expression', cron_hint: 'Standard cron format' }, nav: { settings_git: 'Git' }, - connections: { name: 'Name', user: 'User' } + connections: { + add_new: 'Add New Connection', + create: 'Create Connection', + name: 'Connection Name', + host: 'Host', + port: 'Port', + db_name: 'Database Name', + user: 'Username', + pass: 'Password', + created_success: 'Connection created successfully', + fetch_failed: 'Failed to fetch connections', + delete_confirm: 'Are you sure you want to delete this connection?', + deleted_success: 'Connection deleted', + required_fields: 'Please fill in all required fields' + } }); return () => { }; } @@ -76,9 +93,17 @@ describe('SettingsPage.integration.test.js', () => { beforeEach(() => { vi.clearAllMocks(); api.getConsolidatedSettings.mockResolvedValue(mockSettings); - api.requestApi.mockImplementation((url) => { + api.requestApi.mockImplementation((url, method = 'GET', body) => { if (url === '/migration/settings') return Promise.resolve(mockMigrationSettings); if (url.includes('/migration/mappings-data')) return Promise.resolve({ items: [], total: 0 }); + if (url === '/settings/connections' && method === 'POST') { + return Promise.resolve({ + id: 'conn-1', + created_at: '2026-03-15T00:00:00Z', + ...body + }); + } + if (url === '/settings/connections') return Promise.resolve([]); return Promise.resolve({}); }); }); @@ -121,4 +146,53 @@ describe('SettingsPage.integration.test.js', () => { expect(addToast).toHaveBeenCalledWith('Synced 1 environment(s)', 'success'); }); }); + + it('opens the connection form and creates a connection from the consolidated settings tab', async () => { + render(SettingsPage); + await waitFor(() => expect(api.getConsolidatedSettings).toHaveBeenCalled()); + + await fireEvent.click(screen.getAllByText('Connections')[0]); + + await waitFor(() => { + expect(api.requestApi).toHaveBeenCalledWith('/settings/connections'); + }); + + await fireEvent.click(screen.getByText('Add New Connection')); + + expect(screen.getByLabelText('Connection Name')).toBeTruthy(); + + await fireEvent.input(screen.getByLabelText('Connection Name'), { + target: { value: 'Analytics Warehouse' } + }); + await fireEvent.input(screen.getByLabelText('Host'), { + target: { value: 'warehouse.internal' } + }); + await fireEvent.input(screen.getByLabelText('Database Name'), { + target: { value: 'analytics' } + }); + await fireEvent.input(screen.getByLabelText('Username'), { + target: { value: 'reporter' } + }); + await fireEvent.input(screen.getByLabelText('Password'), { + target: { value: 'secret' } + }); + + await fireEvent.click(screen.getByText('Create Connection')); + + await waitFor(() => { + expect(api.requestApi).toHaveBeenCalledWith('/settings/connections', 'POST', { + name: 'Analytics Warehouse', + type: 'postgres', + host: 'warehouse.internal', + port: 5432, + database: 'analytics', + username: 'reporter', + password: 'secret' + }); + }); + + await waitFor(() => { + expect(addToast).toHaveBeenCalledWith('Connection created successfully', 'success'); + }); + }); }); diff --git a/run.sh b/run.sh index 02eb72011..99f32b46a 100755 --- a/run.sh +++ b/run.sh @@ -192,13 +192,17 @@ trap cleanup SIGINT start_backend() { echo -e "\033[0;34m[Backend]\033[0m Starting on port $BACKEND_PORT..." cd backend + local -a uvicorn_env_args=() if [ -f ".venv/bin/activate" ]; then source .venv/bin/activate else echo -e "\033[0;31m[Backend]\033[0m Warning: .venv/bin/activate not found. Attempting to run without venv." fi + if [ -f ".env" ]; then + uvicorn_env_args=(--env-file .env) + fi # Use a subshell to prefix output - python3 -m uvicorn src.app:app --reload --port "$BACKEND_PORT" 2>&1 | sed "s/^/$(echo -e '\033[0;34m[Backend]\033[0m ') /" & + python3 -m uvicorn src.app:app --reload --port "$BACKEND_PORT" "${uvicorn_env_args[@]}" 2>&1 | sed "s/^/$(echo -e '\033[0;34m[Backend]\033[0m ') /" & BACKEND_PID=$! cd .. }