feat(data-compare): support recompare and endpoint distinction

This commit is contained in:
zipg authored and GitHub committed 2026-09-15 20:08:26 +08:00
1 parent 7942c59aa7
commit d0f8bd0f24
12 files changed
+68 -6

No files matched your search

@@ -32,7 +32,7 @@ import {
import * as api from "@/lib/backend/api";
import { executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard";
import TableMultiSelect from "@/components/diff/TableMultiSelect.vue";
import { ArrowLeftRight, CheckSquare, ChevronDown, ChevronRight, Copy, GitCompareArrows, Loader2, Play, Square } from "@lucide/vue";
import { ArrowLeftRight, CheckSquare, ChevronDown, ChevronRight, Copy, GitCompareArrows, Loader2, Play, RotateCcw, Square } from "@lucide/vue";
const PREVIEW_LIMIT_OPTIONS = [50, 100, 200, 500];
const SYNC_EXECUTE_BATCH_SIZE = 500;
@@ -510,7 +510,7 @@ async function rebuildSyncPlan() {
}
function startCompare(): void {
if (!canCompare.value || comparing.value) return;
if (!canCompare.value || comparing.value || executing.value) return;
const tasks = buildCompareTasks();
if (tasks.length === 0) {
toast(t("dataCompare.noComparableTables"), 5000);
@@ -785,8 +785,11 @@ onBeforeUnmount(() => {
<div class="flex-1 min-h-0 overflow-auto space-y-4 py-2">
<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>
<div class="space-y-2 rounded-lg border border-blue-500/35 bg-blue-500/5 p-3">
<div class="flex items-center gap-2 text-sm font-medium text-blue-600 dark:text-blue-400">
<span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-blue-500/15 text-[11px] font-semibold">S</span>
{{ t("diff.source") }}
</div>
<ConnectionTreeSelect
v-model="sourceConnectionId"
:disabled="comparing"
@@ -838,8 +841,11 @@ onBeforeUnmount(() => {
</Button>
</div>
<div class="space-y-2">
<Label class="text-xs font-medium">{{ t("diff.target") }}</Label>
<div class="space-y-2 rounded-lg border border-emerald-500/35 bg-emerald-500/5 p-3">
<div class="flex items-center gap-2 text-sm font-medium text-emerald-600 dark:text-emerald-400">
<span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-emerald-500/15 text-[11px] font-semibold">T</span>
{{ t("diff.target") }}
</div>
<ConnectionTreeSelect
v-model="targetConnectionId"
:disabled="comparing"
@@ -1110,6 +1116,11 @@ onBeforeUnmount(() => {
<DialogFooter v-else class="flex items-center gap-2">
<Button variant="outline" @click="open = false">{{ t("common.close") }}</Button>
<Button variant="outline" size="sm" :disabled="comparing || executing || !canCompare" @click="startCompare">
<Loader2 v-if="comparing" class="w-3 h-3 animate-spin mr-1" />
<RotateCcw v-else class="w-3 h-3 mr-1" />
{{ t("dataCompare.recompare") }}
</Button>
<span v-if="comparing" class="text-xs text-muted-foreground mr-auto">{{ compareProgressLabel || t("diff.progress.comparing") }}</span>
<span v-else-if="executing" class="text-xs text-muted-foreground mr-auto">
{{ t("diff.syncProgress", { current: executedCount, total: executeTotal }) }}
@@ -13,6 +13,7 @@ const mocks = vi.hoisted(() => ({
listTables: vi.fn().mockResolvedValue([{ name: "CODEX_7467_META", table_type: "TABLE" }]),
getColumns: vi.fn().mockResolvedValue([{ name: "ID", data_type: "NUMBER", is_primary_key: true }]),
buildDataCompareSyncPlan: vi.fn(),
executeBatch: vi.fn(),
}));
const sessionMocks = vi.hoisted(() => ({
@@ -58,6 +59,7 @@ vi.mock("@/lib/backend/api", () => ({
listTables: mocks.listTables,
getColumns: mocks.getColumns,
buildDataCompareSyncPlan: mocks.buildDataCompareSyncPlan,
executeBatch: mocks.executeBatch,
}));
const mountedApps: App[] = [];
@@ -259,6 +261,45 @@ describe("DataCompareDialog session restore", () => {
expect(document.querySelector<HTMLTextAreaElement>("textarea[readonly]")?.value).toBe("INSERT 1;\nINSERT 2;");
});
it("starts another comparison and distinguishes source and target panels after results", async () => {
mountSessionDialog(completedSession());
await flushAsyncSetup();
const recompareButton = [...document.querySelectorAll<HTMLButtonElement>("button")].find((button) => button.textContent?.includes(i18n.global.t("dataCompare.recompare")));
expect(recompareButton).toBeDefined();
expect([...document.querySelectorAll("div")].filter((element) => element.classList.contains("border-blue-500/35"))).toHaveLength(1);
expect([...document.querySelectorAll("div")].filter((element) => element.classList.contains("border-emerald-500/35"))).toHaveLength(1);
recompareButton?.click();
await flushAsyncSetup();
expect(mocks.ensureConnected).toHaveBeenCalledWith("oracle-11g");
expect(mocks.ensureConnected).toHaveBeenCalledWith("oracle-jdbc-11g");
});
it("disables recompare while sync SQL is still executing against the target", async () => {
mocks.executeBatch.mockImplementation(() => new Promise(() => {}));
mountSessionDialog(completedSession());
await flushAsyncSetup();
mocks.ensureConnected.mockClear();
const executeButton = [...document.querySelectorAll<HTMLButtonElement>("button")].find((button) => button.textContent?.includes(i18n.global.t("diff.executeSync")));
expect(executeButton).toBeDefined();
executeButton?.click();
await flushAsyncSetup();
const recompareButton = [...document.querySelectorAll<HTMLButtonElement>("button")].find((button) => button.textContent?.includes(i18n.global.t("dataCompare.recompare")));
expect(recompareButton).toBeDefined();
expect(recompareButton?.disabled).toBe(true);
recompareButton?.click();
await flushAsyncSetup();
expect(mocks.ensureConnected).not.toHaveBeenCalledWith("oracle-11g");
});
it("persists the latest selection plan when the dialog closes during planning", async () => {
const session = completedSession();
let resolvePlan!: (plan: DataCompareSession["syncPlan"]) => void;
+1
View File
@@ -6633,6 +6633,7 @@ export default withEnglishFallback({
rowCounts: "Mənbədə {source} sətir, hədəfdə {target} sətir; tam müqayisə tamamlandı",
previewLimitOption: "Önbaxış: {count}",
compare: "Verilənləri müqayisə et",
recompare: "Yenidən müqayisə et",
summary: "Əlavə edilib: {added}, çıxarılıb: {removed}, dəyişdirilib: {modified}",
batchSummary: "{tables} cədvəl müqayisə edilib: {different} fərqli, {same} eyni, {failed} uğursuz; əlavə edilib: {added}, çıxarılıb: {removed}, dəyişdirilib: {modified}",
comparingTable: "Müqayisə edilir {current}/{total}: {table}",
+1
View File
@@ -6820,6 +6820,7 @@ export default {
rowCounts: "Source {source} rows, target {target} rows; full compare completed",
previewLimitOption: "Preview {count}",
compare: "Compare Data",
recompare: "Compare Again",
summary: "Added {added}, removed {removed}, modified {modified}",
batchSummary: "Compared {tables} tables: {different} different, {same} identical, {failed} failed; added {added}, removed {removed}, modified {modified}",
comparingTable: "Comparing {current}/{total}: {table}",
+1
View File
@@ -6393,6 +6393,7 @@ export default withEnglishFallback({
rowCounts: "Origen {source} filas, destino {target} filas; comparación completa terminada",
previewLimitOption: "Vista previa {count}",
compare: "Comparar datos",
recompare: "Comparar de nuevo",
summary: "Agregadas {added}, eliminadas {removed}, modificadas {modified}",
batchSummary: "Se compararon {tables} tablas: {different} con diferencias, {same} iguales, {failed} con error; agregadas {added}, eliminadas {removed}, modificadas {modified}",
comparingTable: "Comparando {current}/{total}: {table}",
+1
View File
@@ -6393,6 +6393,7 @@ export default withEnglishFallback({
rowCounts: "Sorgente {source} righe, destinazione {target} righe; confronto completo completato",
previewLimitOption: "Anteprima {count}",
compare: "Confronta Dati",
recompare: "Confronta di nuovo",
summary: "Aggiunte {added}, rimosse {removed}, modificate {modified}",
batchSummary: "Confrontate {tables} tabelle: {different} diverse, {same} identiche, {failed} non riuscite; aggiunte {added}, rimosse {removed}, modificate {modified}",
comparingTable: "Confronto di {current}/{total}: {table}",
+1
View File
@@ -6431,6 +6431,7 @@ export default withEnglishFallback({
rowCounts: "ソース {source}行、ターゲット {target}行、完全比較が完了しました",
previewLimitOption: "{count}件プレビュー",
compare: "データを比較",
recompare: "再比較",
summary: "追加 {added}、削除 {removed}、変更 {modified}",
batchSummary: "{tables}テーブルを比較: {different}不一致、{same}一致、{failed}失敗; 追加 {added}、削除 {removed}、変更 {modified}",
comparingTable: "比較中 {current}/{total}: {table}",
+1
View File
@@ -6143,6 +6143,7 @@ export default withEnglishFallback({
rowCounts: "소스 {source}행, 대상 {target}행; 전체 비교 완료",
previewLimitOption: "미리보기 {count}",
compare: "데이터 비교",
recompare: "다시 비교",
summary: "추가 {added}, 제거 {removed}, 수정 {modified}",
batchSummary: "테이블 {tables}개 비교: {different}개 차이, {same}개 동일, {failed}개 실패; 추가 {added}, 제거 {removed}, 수정 {modified}",
comparingTable: "{current}/{total} 비교 중: {table}",
+1
View File
@@ -6395,6 +6395,7 @@ export default withEnglishFallback({
rowCounts: "Origem com {source} linhas, destino com {target} linhas; comparação completa concluída",
previewLimitOption: "Pré-visualizar {count}",
compare: "Comparar Dados",
recompare: "Comparar novamente",
summary: "{added} adicionados, {removed} removidos, {modified} modificados",
batchSummary: "{tables} tabelas comparadas: {different} diferentes, {same} idênticas, {failed} com falha; {added} adicionados, {removed} removidos, {modified} modificados",
comparingTable: "Comparando {current}/{total}: {table}",
+1
View File
@@ -6515,6 +6515,7 @@ export default withEnglishFallback({
rowCounts: "Kaynak {source} satır, hedef {target} satır; tam karşılaştırma tamamlandı",
previewLimitOption: "Önizleme {count}",
compare: "Verileri Karşılaştır",
recompare: "Tekrar karşılaştır",
summary: "{added} eklendi, {removed} kaldırıldı, {modified} değiştirildi",
batchSummary: "{tables} tablo karşılaştırıldı: {different} farklı, {same} aynı, {failed} başarısız; {added} eklendi, {removed} kaldırıldı, {modified} değiştirildi",
comparingTable: "Karşılaştırılıyor {current}/{total}: {table}",
+1
View File
@@ -6794,6 +6794,7 @@ export default withEnglishFallback({
rowCounts: "源表 {source} 行,目标表 {target} 行;已完成全量比较",
previewLimitOption: "预览 {count}",
compare: "开始比较数据",
recompare: "重新比较",
summary: "新增 {added} 行,删除 {removed} 行,修改 {modified} 行",
batchSummary: "已比较 {tables} 张表:存在差异 {different} 张,一致 {same} 张,失败 {failed} 张;累计新增 {added} 行,删除 {removed} 行,修改 {modified} 行",
comparingTable: "正在比较 {current}/{total}:{table}",
+1
View File
@@ -5713,6 +5713,7 @@ export default withEnglishFallback({
rowCounts: "來源 {source} 列,目標 {target} 列;已完成完整比較",
previewLimitOption: "預覽 {count}",
compare: "開始比較資料",
recompare: "重新比較",
summary: "新增 {added} 列,刪除 {removed} 列,修改 {modified} 列",
batchSummary: "已比較 {tables} 張資料表:{different} 張有差異,{same} 張一致,{failed} 張失敗;新增 {added} 列,刪除 {removed} 列,修改 {modified} 列",
comparingTable: "正在比較 {current}/{total}:{table}",