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
This commit is contained in:
@@ -54,7 +54,6 @@
|
||||
</button>
|
||||
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
|
||||
<a href="/settings" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.settings_general}</a>
|
||||
<a href="/settings/connections" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.settings_connections}</a>
|
||||
<a href="/settings/git" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.settings_git}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
<!-- #region ConnectionForm [C:3] [TYPE Component] [SEMANTICS connection, form, database, crud, create] -->
|
||||
<!-- @BRIEF Component component: components/tools/ConnectionForm.svelte -->
|
||||
<!-- @LAYER UI -->
|
||||
<!--
|
||||
@SEMANTICS: connection, form, settings
|
||||
@PURPOSE: UI component for creating a new database connection configuration.
|
||||
@LAYER: UI
|
||||
@RELATION: USES -> frontend/src/services/connectionService.js
|
||||
-->
|
||||
<script>
|
||||
// [SECTION: IMPORTS]
|
||||
import { createConnection } from '../../services/connectionService.js';
|
||||
import { addToast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button, Input, Card } from '../../lib/ui';
|
||||
// [/SECTION]
|
||||
let { onsuccess = () => {} } = $props();
|
||||
|
||||
let name = $state('');
|
||||
let type = $state('postgres');
|
||||
let host = $state('');
|
||||
let port = $state("5432");
|
||||
let database = $state('');
|
||||
let username = $state('');
|
||||
let password = $state('');
|
||||
let isSubmitting = $state(false);
|
||||
|
||||
// #region handleSubmit:Function [TYPE Function]
|
||||
// @PURPOSE: Submits the connection form to the backend.
|
||||
// @PRE: All required fields (name, host, database, username, password) must be filled.
|
||||
// @POST: A new connection is created via the connection service and the parent success callback is invoked.
|
||||
async function handleSubmit() {
|
||||
if (!name || !host || !database || !username || !password) {
|
||||
addToast($t.connections?.required_fields, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting = true;
|
||||
try {
|
||||
const newConnection = await createConnection({
|
||||
name, type, host, port: Number(port), database, username, password
|
||||
});
|
||||
addToast($t.connections?.created_success, 'success');
|
||||
onsuccess(newConnection);
|
||||
resetForm();
|
||||
} catch (e) {
|
||||
addToast(e.message, 'error');
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
// #endregion handleSubmit:Function
|
||||
|
||||
// #region resetForm:Function [TYPE Function]
|
||||
/* @PURPOSE: Resets the connection form fields to their default values.
|
||||
@PRE: None.
|
||||
@POST: All form input variables are reset.
|
||||
*/
|
||||
function resetForm() {
|
||||
name = '';
|
||||
host = '';
|
||||
port = "5432";
|
||||
database = '';
|
||||
username = '';
|
||||
password = '';
|
||||
}
|
||||
// #endregion resetForm:Function
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<Card title={$t.connections?.add_new}>
|
||||
<form onsubmit={(event) => { event.preventDefault(); handleSubmit(); }} class="space-y-6">
|
||||
<Input label={$t.connections?.name} bind:value={name} placeholder={$t.connections?.name_placeholder} />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label={$t.connections?.host} bind:value={host} placeholder={$t.connections?.host_placeholder} />
|
||||
<Input label={$t.connections?.port} type="number" bind:value={port} />
|
||||
</div>
|
||||
|
||||
<Input label={$t.connections?.db_name} bind:value={database} />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label={$t.connections?.user} bind:value={username} />
|
||||
<Input label={$t.connections?.pass} type="password" bind:value={password} />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-2">
|
||||
<Button type="submit" disabled={isSubmitting} isLoading={isSubmitting}>
|
||||
{$t.connections?.create}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
<!-- [/SECTION] -->
|
||||
<!-- #endregion ConnectionForm -->
|
||||
@@ -1,88 +0,0 @@
|
||||
<!-- #region ConnectionList [C:3] [TYPE Component] [SEMANTICS connection, list, crud, delete, manage] -->
|
||||
<!-- @BRIEF Component component: components/tools/ConnectionList.svelte -->
|
||||
<!-- @LAYER UI -->
|
||||
<!--
|
||||
@SEMANTICS: connection, list, settings
|
||||
@PURPOSE: UI component for listing and deleting saved database connection configurations.
|
||||
@LAYER: UI
|
||||
@RELATION: USES -> frontend/src/services/connectionService.js
|
||||
-->
|
||||
<script>
|
||||
// [SECTION: IMPORTS]
|
||||
import { onMount } from 'svelte';
|
||||
import { getConnections, deleteConnection } from '../../services/connectionService.js';
|
||||
import { addToast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button, Card } from '../../lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
let connections = $state([]);
|
||||
let isLoading = $state(true);
|
||||
|
||||
// #region fetchConnections:Function [TYPE Function]
|
||||
// @PURPOSE: Fetches the list of connections from the backend.
|
||||
// @PRE: None.
|
||||
// @POST: connections array is populated.
|
||||
async function fetchConnections() {
|
||||
isLoading = true;
|
||||
try {
|
||||
connections = await getConnections();
|
||||
} catch (e) {
|
||||
addToast($t.connections?.fetch_failed, 'error');
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
// #endregion fetchConnections:Function
|
||||
|
||||
// #region handleDelete:Function [TYPE Function]
|
||||
// @PURPOSE: Deletes a connection configuration.
|
||||
// @PRE: id is provided and user confirms deletion.
|
||||
// @POST: Connection is deleted from backend and list is reloaded.
|
||||
async function handleDelete(id) {
|
||||
if (!confirm($t.connections?.delete_confirm)) return;
|
||||
|
||||
try {
|
||||
await deleteConnection(id);
|
||||
addToast($t.connections?.deleted_success, 'success');
|
||||
await fetchConnections();
|
||||
} catch (e) {
|
||||
addToast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
// #endregion handleDelete:Function
|
||||
|
||||
onMount(fetchConnections);
|
||||
|
||||
// Expose fetchConnections to parent
|
||||
export { fetchConnections };
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<Card title={$t.connections?.saved} padding="none">
|
||||
<ul class="divide-y divide-gray-100">
|
||||
{#if isLoading}
|
||||
<li class="p-6 text-center text-gray-500">{$t.common.loading}</li>
|
||||
{:else if connections.length === 0}
|
||||
<li class="p-12 text-center text-gray-500 italic">{$t.connections?.no_saved}</li>
|
||||
{:else}
|
||||
{#each connections as conn}
|
||||
<li class="p-6 flex items-center justify-between hover:bg-gray-50 transition-colors">
|
||||
<div>
|
||||
<div class="text-sm font-medium text-primary truncate">{conn.name}</div>
|
||||
<div class="text-xs text-gray-400 mt-1 font-mono">{conn.type}://{conn.username}@{conn.host}:{conn.port}/{conn.database}</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
onclick={() => handleDelete(conn.id)}
|
||||
>
|
||||
{$t.connections?.delete}
|
||||
</Button>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</Card>
|
||||
<!-- [/SECTION] -->
|
||||
<!-- #endregion ConnectionList -->
|
||||
@@ -1,18 +1,16 @@
|
||||
<!-- #region MapperTool [C:3] [TYPE Component] [SEMANTICS mapper, dataset, column, mapping, postgresql] -->
|
||||
<!-- #region MapperTool [C:3] [TYPE Component] [SEMANTICS mapper, dataset, column, mapping, sqllab] -->
|
||||
<!-- @BRIEF Component component: components/tools/MapperTool.svelte -->
|
||||
<!-- @LAYER UI -->
|
||||
<!--
|
||||
@SEMANTICS: mapper, tool, dataset, postgresql, excel
|
||||
@PURPOSE: UI component for mapping dataset column verbose names using the MapperPlugin.
|
||||
@SEMANTICS: mapper, tool, dataset, sqllab, excel
|
||||
@PURPOSE: UI component for mapping dataset column verbose names using Superset SQL Lab or Excel files.
|
||||
@LAYER: UI
|
||||
@RELATION: USES -> frontend/src/services/toolsService.js
|
||||
@RELATION: USES -> frontend/src/services/connectionService.js
|
||||
-->
|
||||
<script>
|
||||
// [SECTION: IMPORTS]
|
||||
import { onMount } from 'svelte';
|
||||
import { runTask } from '../../services/toolsService.js';
|
||||
import { getConnections } from '../../services/connectionService.js';
|
||||
import { api } from '../../lib/api';
|
||||
import { selectedTask } from '../../lib/stores.js';
|
||||
import { addToast } from '../../lib/toasts.js';
|
||||
@@ -22,26 +20,23 @@
|
||||
// [/SECTION]
|
||||
|
||||
let envs = [];
|
||||
let connections = [];
|
||||
let selectedEnv = '';
|
||||
let datasetId = '';
|
||||
let source = 'postgres';
|
||||
let selectedConnection = '';
|
||||
let tableName = '';
|
||||
let tableSchema = 'public';
|
||||
let source = 'sqllab';
|
||||
let databaseId = '';
|
||||
let sqlQuery = '';
|
||||
let excelPath = '';
|
||||
let isRunning = false;
|
||||
let isGeneratingDocs = false;
|
||||
let generatedDoc = null;
|
||||
|
||||
// #region fetchData:Function [TYPE Function]
|
||||
// @PURPOSE: Fetches environments and saved connections.
|
||||
// @PURPOSE: Fetches environments.
|
||||
// @PRE: None.
|
||||
// @POST: envs and connections arrays are populated.
|
||||
// @POST: envs array is populated.
|
||||
async function fetchData() {
|
||||
try {
|
||||
envs = await api.fetchApi('/environments');
|
||||
connections = await getConnections();
|
||||
} catch (e) {
|
||||
addToast($t.mapper.errors.fetch_failed, 'error');
|
||||
}
|
||||
@@ -49,7 +44,7 @@
|
||||
// #endregion fetchData:Function
|
||||
|
||||
// #region handleRunMapper:Function [TYPE Function]
|
||||
// @PURPOSE: Triggers the MapperPlugin task.
|
||||
// @PURPOSE: Triggers the MapperPlugin task via new sqllab/excel sources.
|
||||
// @PRE: selectedEnv and datasetId are set; source-specific fields are valid.
|
||||
// @POST: Mapper task is started and selectedTask is updated.
|
||||
async function handleRunMapper() {
|
||||
@@ -58,8 +53,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (source === 'postgres' && (!selectedConnection || !tableName)) {
|
||||
addToast($t.mapper.errors.postgres_required, 'warning');
|
||||
if (source === 'sqllab' && !databaseId) {
|
||||
addToast($t.mapper.errors.sqllab_required, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,9 +70,8 @@
|
||||
env: env.name,
|
||||
dataset_id: parseInt(datasetId),
|
||||
source,
|
||||
connection_id: selectedConnection,
|
||||
table_name: tableName,
|
||||
table_schema: tableSchema,
|
||||
database_id: databaseId ? parseInt(databaseId) : undefined,
|
||||
sql_query: sqlQuery || undefined,
|
||||
excel_path: excelPath
|
||||
});
|
||||
|
||||
@@ -169,8 +163,8 @@
|
||||
<legend class="block text-sm font-medium text-gray-700 mb-2">{$t.mapper.source}</legend>
|
||||
<div class="flex space-x-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" bind:group={source} value="postgres" class="focus-visible:ring-primary-ring h-4 w-4 text-primary border-gray-300" />
|
||||
<span class="ml-2 text-sm text-gray-700">{$t.mapper.source_postgres}</span>
|
||||
<input type="radio" bind:group={source} value="sqllab" class="focus-visible:ring-primary-ring h-4 w-4 text-primary border-gray-300" />
|
||||
<span class="ml-2 text-sm text-gray-700">{$t.mapper.source_sqllab}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" bind:group={source} value="excel" class="focus-visible:ring-primary-ring h-4 w-4 text-primary border-gray-300" />
|
||||
@@ -179,33 +173,23 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{#if source === 'postgres'}
|
||||
{#if source === 'sqllab'}
|
||||
<div class="space-y-4 p-4 bg-gray-50 rounded-md border border-gray-100">
|
||||
<div>
|
||||
<Select
|
||||
label={$t.mapper.connection}
|
||||
bind:value={selectedConnection}
|
||||
options={[
|
||||
{ value: '', label: $t.mapper.select_connection },
|
||||
...connections.map(c => ({ value: c.id, label: c.name }))
|
||||
]}
|
||||
<Input
|
||||
label={$t.mapper.database_id}
|
||||
type="number"
|
||||
bind:value={databaseId}
|
||||
placeholder={$t.mapper.database_id_placeholder}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Input
|
||||
label={$t.mapper.table_name}
|
||||
type="text"
|
||||
bind:value={tableName}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
label={$t.mapper.table_schema}
|
||||
type="text"
|
||||
bind:value={tableSchema}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
label={$t.mapper.sql_query_label}
|
||||
type="text"
|
||||
bind:value={sqlQuery}
|
||||
placeholder={$t.mapper.sql_query_placeholder}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -249,4 +233,4 @@
|
||||
/>
|
||||
</div>
|
||||
<!-- [/SECTION] -->
|
||||
<!-- #endregion MapperTool -->
|
||||
<!-- #endregion MapperTool -->
|
||||
Reference in New Issue
Block a user