fix(diff): use connection tree select in compare dialogs
This commit is contained in:
1 parent
f9f61cf397
commit
a1f54cf428
4 files changed
+46
-86
No files matched your search
@@ -7,7 +7,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import SearchableSelect from "@/components/ui/searchable-select/SearchableSelect.vue";
|
||||
import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue";
|
||||
import ConnectionTreeSelect from "@/components/connection/ConnectionTreeSelect.vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { databaseOptionsForConnection, fetchNamespaceOptionsForConnection } from "@/composables/useDatabaseOptions";
|
||||
@@ -18,7 +18,6 @@ import { inferCompareKeyColumns } from "@/lib/dataGrid/dataCompare";
|
||||
import type { ColumnInfo, DatabaseType } from "@/types/database";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import TableMultiSelect from "@/components/diff/TableMultiSelect.vue";
|
||||
import { ArrowLeftRight, CheckSquare, ChevronDown, ChevronRight, Copy, GitCompareArrows, Loader2, Play, Square } from "@lucide/vue";
|
||||
|
||||
@@ -208,11 +207,6 @@ function emptySyncPlan(): DataCompareSyncPlan {
|
||||
};
|
||||
}
|
||||
|
||||
function connectionIconType(connectionId: string) {
|
||||
const config = store.getConfig(connectionId);
|
||||
return config?.driver_profile || config?.db_type || "mysql";
|
||||
}
|
||||
|
||||
function targetDatabaseType(): DatabaseType | undefined {
|
||||
return store.getConfig(targetConnectionId.value)?.db_type;
|
||||
}
|
||||
@@ -892,25 +886,16 @@ watch(
|
||||
<div class="grid grid-cols-[1fr_auto_1fr] gap-4 items-start">
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-medium">{{ t("diff.source") }}</Label>
|
||||
<SearchableSelect
|
||||
<ConnectionTreeSelect
|
||||
v-model="sourceConnectionId"
|
||||
:options="sqlConnections.map((c) => c.id)"
|
||||
:connections="sqlConnections"
|
||||
:layout="store.sidebarLayout"
|
||||
:placeholder="t('diff.selectConnection')"
|
||||
:search-placeholder="t('diff.searchConnection')"
|
||||
:empty-text="t('common.noResults')"
|
||||
:display-name="(id) => sqlConnections.find((c) => c.id === id)?.name ?? id"
|
||||
trigger-variant="outline"
|
||||
trigger-class="h-8 w-full justify-between text-xs"
|
||||
content-class="w-[var(--reka-popover-trigger-width)]"
|
||||
>
|
||||
<template #option-label="{ option, label }">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<DatabaseIcon :db-type="connectionIconType(option)" class="h-3.5 w-3.5 shrink-0" />
|
||||
<ConnectionGroupBadge :connection-id="option" />
|
||||
<span class="min-w-0 flex-1 truncate">{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</SearchableSelect>
|
||||
trigger-class="dbx-diff-connection-trigger h-8 w-full max-w-none justify-between gap-1.5 rounded-md border border-input bg-transparent px-2.5 text-xs shadow-none hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
||||
list-class="w-[var(--reka-popover-trigger-width)]"
|
||||
/>
|
||||
<SearchableSelect
|
||||
v-model="sourceDatabase"
|
||||
:options="sourceDatabases"
|
||||
@@ -951,25 +936,16 @@ watch(
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs font-medium">{{ t("diff.target") }}</Label>
|
||||
<SearchableSelect
|
||||
<ConnectionTreeSelect
|
||||
v-model="targetConnectionId"
|
||||
:options="sqlConnections.map((c) => c.id)"
|
||||
:connections="sqlConnections"
|
||||
:layout="store.sidebarLayout"
|
||||
:placeholder="t('diff.selectConnection')"
|
||||
:search-placeholder="t('diff.searchConnection')"
|
||||
:empty-text="t('common.noResults')"
|
||||
:display-name="(id) => sqlConnections.find((c) => c.id === id)?.name ?? id"
|
||||
trigger-variant="outline"
|
||||
trigger-class="h-8 w-full justify-between text-xs"
|
||||
content-class="w-[var(--reka-popover-trigger-width)]"
|
||||
>
|
||||
<template #option-label="{ option, label }">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<DatabaseIcon :db-type="connectionIconType(option)" class="h-3.5 w-3.5 shrink-0" />
|
||||
<ConnectionGroupBadge :connection-id="option" />
|
||||
<span class="min-w-0 flex-1 truncate">{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</SearchableSelect>
|
||||
trigger-class="dbx-diff-connection-trigger h-8 w-full max-w-none justify-between gap-1.5 rounded-md border border-input bg-transparent px-2.5 text-xs shadow-none hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
||||
list-class="w-[var(--reka-popover-trigger-width)]"
|
||||
/>
|
||||
<SearchableSelect
|
||||
v-model="targetDatabase"
|
||||
:options="targetDatabases"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import SearchableSelect from "@/components/ui/searchable-select/SearchableSelect.vue";
|
||||
import ConnectionGroupBadge from "@/components/connection/ConnectionGroupBadge.vue";
|
||||
import ConnectionTreeSelect from "@/components/connection/ConnectionTreeSelect.vue";
|
||||
import TableMultiSelect from "@/components/diff/TableMultiSelect.vue";
|
||||
import { buildSameNameTableMatches } from "@/lib/diff/sameNameTableMatch";
|
||||
import { createSchemaDiffTableListCoordinator, reconcileSchemaDiffSelectedTables, shouldLoadSchemaDiffTableList, type SchemaDiffTableIdentity, type SchemaDiffTableListLoader, type SchemaDiffTableSide } from "@/lib/schema/schemaDiffTableList";
|
||||
@@ -394,26 +394,17 @@ async function fetchDbVersion(connectionId: string, database: string, schema: st
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("diff.connection") }}</Label>
|
||||
<SearchableSelect
|
||||
<ConnectionTreeSelect
|
||||
:model-value="sourceConnectionId"
|
||||
@update:model-value="(v: string) => $emit('update:sourceConnectionId', v)"
|
||||
:options="sqlConnections.map((c) => c.id)"
|
||||
:connections="sqlConnections"
|
||||
:layout="store.sidebarLayout"
|
||||
:placeholder="t('diff.selectConnection')"
|
||||
:search-placeholder="t('diff.searchConnection')"
|
||||
:empty-text="t('common.noResults')"
|
||||
:display-name="(id) => sqlConnections.find((c) => c.id === id)?.name ?? id"
|
||||
trigger-variant="outline"
|
||||
trigger-class="h-8 w-full justify-between text-xs"
|
||||
content-class="w-[var(--reka-popover-trigger-width)]"
|
||||
>
|
||||
<template #option-label="{ option, label }">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<DatabaseIcon :db-type="sqlConnections.find((c) => c.id === option)?.driver_profile || sqlConnections.find((c) => c.id === option)?.db_type || 'mysql'" class="h-3.5 w-3.5 shrink-0" />
|
||||
<ConnectionGroupBadge :connection-id="option" />
|
||||
<span class="min-w-0 flex-1 truncate">{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</SearchableSelect>
|
||||
trigger-class="dbx-diff-connection-trigger h-8 w-full max-w-none justify-between gap-1.5 rounded-md border border-input bg-transparent px-2.5 text-xs shadow-none hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
||||
list-class="w-[var(--reka-popover-trigger-width)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
@@ -479,26 +470,17 @@ async function fetchDbVersion(connectionId: string, database: string, schema: st
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("diff.connection") }}</Label>
|
||||
<SearchableSelect
|
||||
<ConnectionTreeSelect
|
||||
:model-value="targetConnectionId"
|
||||
@update:model-value="(v: string) => $emit('update:targetConnectionId', v)"
|
||||
:options="sqlConnections.map((c) => c.id)"
|
||||
:connections="sqlConnections"
|
||||
:layout="store.sidebarLayout"
|
||||
:placeholder="t('diff.selectConnection')"
|
||||
:search-placeholder="t('diff.searchConnection')"
|
||||
:empty-text="t('common.noResults')"
|
||||
:display-name="(id) => sqlConnections.find((c) => c.id === id)?.name ?? id"
|
||||
trigger-variant="outline"
|
||||
trigger-class="h-8 w-full justify-between text-xs"
|
||||
content-class="w-[var(--reka-popover-trigger-width)]"
|
||||
>
|
||||
<template #option-label="{ option, label }">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<DatabaseIcon :db-type="sqlConnections.find((c) => c.id === option)?.driver_profile || sqlConnections.find((c) => c.id === option)?.db_type || 'mysql'" class="h-3.5 w-3.5 shrink-0" />
|
||||
<ConnectionGroupBadge :connection-id="option" />
|
||||
<span class="min-w-0 flex-1 truncate">{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</SearchableSelect>
|
||||
trigger-class="dbx-diff-connection-trigger h-8 w-full max-w-none justify-between gap-1.5 rounded-md border border-input bg-transparent px-2.5 text-xs shadow-none hover:bg-muted/40 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 dark:bg-input/30 dark:hover:bg-input/50"
|
||||
list-class="w-[var(--reka-popover-trigger-width)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
|
||||
+8
-18
@@ -13,15 +13,6 @@ const mocks = vi.hoisted(() => ({
|
||||
getColumns: vi.fn().mockResolvedValue([{ name: "ID", data_type: "NUMBER", is_primary_key: true }]),
|
||||
}));
|
||||
|
||||
function passthrough(tag: string) {
|
||||
return defineComponent({
|
||||
inheritAttrs: false,
|
||||
setup(_, { attrs, slots }) {
|
||||
return () => h(tag, attrs, slots.default?.());
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
vi.mock("@/stores/connectionStore", () => {
|
||||
const connections = [
|
||||
{ id: "oracle-11g", name: "Oracle XE 11g", db_type: "oracle", driver_profile: "oracle", database: "XE" },
|
||||
@@ -37,16 +28,16 @@ vi.mock("@/stores/connectionStore", () => {
|
||||
return {
|
||||
useConnectionStore: () => ({
|
||||
connections,
|
||||
sidebarLayout: {
|
||||
groups: [{ id: "oracle", name: "Oracle", collapsed: false }],
|
||||
order: [{ type: "group", id: "oracle", children: connections.map((connection) => ({ type: "connection", id: connection.id })) }],
|
||||
},
|
||||
getConfig: (id: string) => connections.find((connection) => connection.id === id),
|
||||
ensureConnected: mocks.ensureConnected,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/components/connection/ConnectionGroupBadge.vue", () => ({
|
||||
default: passthrough("span"),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
listDatabases: mocks.listDatabases,
|
||||
listSchemas: mocks.listSchemas,
|
||||
@@ -94,7 +85,7 @@ describe("DataCompareDialog source prefill", () => {
|
||||
expect(mocks.listSchemas).toHaveBeenCalledWith("oracle-11g", "XE", true);
|
||||
|
||||
const searchableSelectTriggers = [...document.querySelectorAll<HTMLButtonElement>("button.dbx-searchable-select-trigger")];
|
||||
const sourceDatabaseTrigger = searchableSelectTriggers[1];
|
||||
const sourceDatabaseTrigger = searchableSelectTriggers[0];
|
||||
expect(sourceDatabaseTrigger?.title).toBe("DBX_TEST");
|
||||
expect(sourceDatabaseTrigger?.disabled).toBe(false);
|
||||
sourceDatabaseTrigger?.click();
|
||||
@@ -127,13 +118,12 @@ describe("DataCompareDialog source prefill", () => {
|
||||
app.mount(container);
|
||||
await flushAsyncSetup();
|
||||
|
||||
const searchableSelectTriggers = [...document.querySelectorAll<HTMLButtonElement>("button.dbx-searchable-select-trigger")];
|
||||
const targetConnectionTrigger = searchableSelectTriggers[3];
|
||||
const targetConnectionTrigger = document.querySelectorAll<HTMLButtonElement>("button.dbx-diff-connection-trigger")[1];
|
||||
expect(targetConnectionTrigger).toBeDefined();
|
||||
targetConnectionTrigger?.click();
|
||||
await flushAsyncSetup();
|
||||
|
||||
const jdbcConnectionOption = [...document.querySelectorAll<HTMLButtonElement>(".dbx-searchable-select-list button")].find((button) => button.textContent?.trim() === "Oracle JDBC 11g");
|
||||
const jdbcConnectionOption = document.querySelector<HTMLButtonElement>('[data-picker-connection="oracle-jdbc-11g"]');
|
||||
expect(jdbcConnectionOption).toBeDefined();
|
||||
jdbcConnectionOption?.click();
|
||||
await flushAsyncSetup();
|
||||
@@ -141,7 +131,7 @@ describe("DataCompareDialog source prefill", () => {
|
||||
expect(mocks.listSchemas).toHaveBeenCalledWith("oracle-jdbc-11g", "", true);
|
||||
|
||||
const triggersAfterTargetLoad = [...document.querySelectorAll<HTMLButtonElement>("button.dbx-searchable-select-trigger")];
|
||||
const targetDatabaseTrigger = triggersAfterTargetLoad[4];
|
||||
const targetDatabaseTrigger = triggersAfterTargetLoad[2];
|
||||
expect(targetDatabaseTrigger?.disabled).toBe(false);
|
||||
targetDatabaseTrigger?.click();
|
||||
await flushAsyncSetup();
|
||||
|
||||
Reference in new issue
Block a user