Files
ss-tools/frontend/src/routes/settings/settings-utils.js
busya 36643024cf refactor(connections): remove ConnectionConfig, migrate mapper to SQL Lab
Backend:
- Remove ConnectionConfig model, CRUD routes (connections.py), and tests
- Remove ensure_connection_configs_table from database.py
- Migrate MapperPlugin from psycopg2 direct PostgreSQL to SupersetSqlLabExecutor
- Add DatasetMapper.get_sqllab_mappings() with default information_schema query
- Update MapColumnsRequest: connection_id → database_id + sql_query
- Fix pydantic_settings v2 deprecation (Field(env=...) → validation_alias)
- Clean up app.py, routes/__init__.py, database.py imports

Frontend:
- Remove DatabaseConnectionsTab, ConnectionForm, ConnectionList components
- Remove /settings/connections route page and Navbar link
- Remove connectionService.js, connections i18n files
- Update MapperTool.svelte: postgres → sqllab source
- Update Map Columns modal: database selector + SQL query instead of connection_id
- Clean up settings page tabs, nav links, test mocks
- Clean up i18n keys (settings, nav, mapper)

Tests: 25/25 pass (14 030-feature + 11 route tests)
Build: succeeds
2026-05-21 18:47:41 +03:00

160 lines
6.3 KiB
JavaScript

/**
* Shared utilities for the Settings page.
* Centralises constants, URL helpers, normalisation functions and validation
* helpers that are consumed by +page.svelte and EnvironmentsTab.svelte.
*/
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
export const SETTINGS_TABS = [
"environments",
"logging",
"git",
"llm",
"migration",
"storage",
"features",
"automation",
];
export const DEFAULT_LLM_PROMPTS = {
dashboard_validation_prompt:
'Analyze the attached dashboard screenshot and the following execution logs for health and visual issues.\\n\\nLogs:\\n{logs}\\n\\nProvide the analysis in JSON format with the following structure:\\n{\\n \\"status\\": \\"PASS\\" | \\"WARN\\" | \\"FAIL\\",\\n \\"summary\\": \\"Short summary of findings\\",\\n \\"issues\\": [\\n {\\n \\"severity\\": \\"WARN\\" | \\"FAIL\\",\\n \\"message\\": \\"Description of the issue\\",\\n \\"location\\": \\"Optional location info (e.g. chart name)\\"\\n }\\n ]\\n}',
documentation_prompt:
'Generate professional documentation for the following dataset and its columns.\\nDataset: {dataset_name}\\nColumns: {columns_json}\\n\\nProvide the documentation in JSON format:\\n{\\n \\"dataset_description\\": \\"General description of the dataset\\",\\n \\"column_descriptions\\": [\\n {\\n \\"name\\": \\"column_name\\",\\n \\"description\\": \\"Generated description\\"\\n }\\n ]\\n}',
git_commit_prompt:
'Generate a concise and professional git commit message based on the following diff and recent history.\\nUse Conventional Commits format (e.g., feat: ..., fix: ..., docs: ...).\\n\\nRecent History:\\n{history}\\n\\nDiff:\\n{diff}\\n\\nCommit Message:',
};
export const DEFAULT_LLM_PROVIDER_BINDINGS = {
dashboard_validation: "",
documentation: "",
git_commit: "",
};
// ---------------------------------------------------------------------------
// URL tab helpers
// ---------------------------------------------------------------------------
/**
* Read the current settings tab from the URL hash fragment.
* Falls back to "environments" when the hash is missing or invalid.
*/
export function readTabFromUrl() {
const hash = typeof window !== "undefined" ? window.location.hash : "";
const tab = hash.replace(/^#\/?settings\//, "") || "";
return normalizeTab(tab) || "environments";
}
/**
* Write the active settings tab to the URL hash fragment so it survives
* browser refreshes and can be linked to directly.
*/
export function writeTabToUrl(tab) {
if (typeof window === "undefined") return;
const normalized = normalizeTab(tab);
if (normalized) {
window.location.hash = `/settings/${normalized}`;
}
}
/**
* Normalise an arbitrary tab identifier to a valid tab key, or return null
* when the identifier does not match any known tab.
*/
export function normalizeTab(tab) {
if (!tab || typeof tab !== "string") return null;
const candidate = tab.trim().toLowerCase();
return SETTINGS_TABS.includes(candidate) ? candidate : null;
}
// ---------------------------------------------------------------------------
// LLM helpers
// ---------------------------------------------------------------------------
/**
* Merge incoming LLM settings with the default prompts and bindings so that
* individual fields are never accidentally set to `undefined`.
*/
export function normalizeLlmSettings(llm) {
if (!llm || typeof llm !== "object") {
return {
prompts: { ...DEFAULT_LLM_PROMPTS },
provider_bindings: { ...DEFAULT_LLM_PROVIDER_BINDINGS },
};
}
return {
...llm,
prompts: { ...DEFAULT_LLM_PROMPTS, ...llm.prompts },
provider_bindings: {
...DEFAULT_LLM_PROVIDER_BINDINGS,
...llm.provider_bindings,
},
};
}
/**
* Check whether the `dashboard_validation` provider binding resolves to a
* multimodal model that can process screenshots.
*
* `settings` is the full consolidated settings object, expected to contain
* `llm.provider_bindings` and the providers list at `settings.providers` or
* `settings.llm_providers`.
*/
export function isDashboardValidationBindingValid(settings) {
if (!settings?.llm?.provider_bindings?.dashboard_validation) return false;
const providerId = settings.llm.provider_bindings.dashboard_validation;
const providers = settings.providers || settings.llm_providers || [];
const provider = providers.find((p) => p.id === providerId);
return !!provider && Boolean(provider.is_multimodal);
}
/**
* Look up a provider by its id from the providers list on the settings object.
*/
export function getProviderById(settings, providerId) {
if (!providerId || !settings) return null;
const providers = settings.providers || settings.llm_providers || [];
return providers.find((p) => p.id === providerId) || null;
}
// ---------------------------------------------------------------------------
// Environment helpers
// ---------------------------------------------------------------------------
/**
* Strip trailing slashes and the `/api/v1` suffix from a Superset base URL,
* auto-prepend https:// if no scheme is present,
* returning a clean base URL suitable for constructing dashboard links.
*/
export function normalizeSupersetBaseUrl(rawUrl) {
let baseUrl = String(rawUrl || "").trim().replace(/\/+$/, "");
if (!baseUrl) return null;
if (baseUrl.endsWith("/api/v1")) {
baseUrl = baseUrl.slice(0, -"/api/v1".length);
}
// Auto-prepend https:// if no scheme is present
if (baseUrl && !/^https?:\/\//i.test(baseUrl)) {
baseUrl = `https://${baseUrl}`;
}
return baseUrl;
}
/**
* Classify an environment's deployment stage from its `name` or `url`.
* Returns one of: `"PROD"`, `"PREPROD"`, or `"DEV"`.
*/
export function resolveEnvStage(env) {
if (!env) return "DEV";
const needle = ((env.name || "") + " " + (env.url || "")).toLowerCase();
if (/prod(uction)?/.test(needle) && !/preprod/.test(needle)) return "PROD";
if (/preprod|staging|stage/.test(needle)) return "PREPROD";
return "DEV";
}
// ---------------------------------------------------------------------------
// Connection draft helper
// ---------------------------------------------------------------------------