fix: continue transfer after table failure
This commit is contained in:
parent
1c03917609
commit
d35aeaa08c
|
|
@ -279,9 +279,11 @@ async function startTransfer() {
|
|||
|
||||
try {
|
||||
await api.startTransfer(request, (progress) => {
|
||||
transferProgress.value.set(progress.table, progress);
|
||||
transferProgress.value = new Map(transferProgress.value);
|
||||
currentTable.value = progress.table;
|
||||
if (progress.table) {
|
||||
transferProgress.value.set(progress.table, progress);
|
||||
transferProgress.value = new Map(transferProgress.value);
|
||||
currentTable.value = progress.table;
|
||||
}
|
||||
|
||||
const nextState = nextTransferTerminalState(
|
||||
{
|
||||
|
|
@ -317,13 +319,38 @@ function getConnectionType(id: string): DatabaseType {
|
|||
return store.connections.find((c) => c.id === id)?.db_type ?? "mysql";
|
||||
}
|
||||
|
||||
const processedStatuses = new Set<TransferProgress["status"]>(["tableDone", "done", "error", "cancelled"]);
|
||||
|
||||
function formatRowCount(count: number) {
|
||||
return count.toLocaleString();
|
||||
}
|
||||
|
||||
function formatTableRows(progress: TransferProgress) {
|
||||
if (typeof progress.totalRows === "number") {
|
||||
return `${formatRowCount(progress.rowsTransferred)} / ${formatRowCount(progress.totalRows)}`;
|
||||
}
|
||||
return formatRowCount(progress.rowsTransferred);
|
||||
}
|
||||
|
||||
const completedTables = computed(
|
||||
() => [...transferProgress.value.values()].filter((p) => p.status === "tableDone" || p.status === "done").length,
|
||||
() => [...transferProgress.value.values()].filter((p) => processedStatuses.has(p.status)).length,
|
||||
);
|
||||
|
||||
const failedTables = computed(() => [...transferProgress.value.values()].filter((p) => p.status === "error").length);
|
||||
|
||||
const totalTransferred = computed(() =>
|
||||
[...transferProgress.value.values()].reduce((sum, p) => sum + p.rowsTransferred, 0),
|
||||
);
|
||||
|
||||
const knownTotalRows = computed(() =>
|
||||
[...transferProgress.value.values()].reduce((sum, p) => sum + (typeof p.totalRows === "number" ? p.totalRows : 0), 0),
|
||||
);
|
||||
|
||||
const overallRowsLabel = computed(() =>
|
||||
knownTotalRows.value > 0
|
||||
? `${formatRowCount(totalTransferred.value)} / ${formatRowCount(knownTotalRows.value)}`
|
||||
: formatRowCount(totalTransferred.value),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -532,10 +559,15 @@ const totalTransferred = computed(() =>
|
|||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>
|
||||
{{ t("transfer.overallProgress") }}: {{ completedTables }} / {{ selectedTables.size }}
|
||||
{{ t("transfer.tables").toLowerCase() }} · {{ totalTransferred.toLocaleString() }}
|
||||
{{ t("transfer.tables").toLowerCase() }} · {{ overallRowsLabel }}
|
||||
{{ t("grid.rows", { count: "" }).trim() }}
|
||||
</span>
|
||||
<span v-if="overallDone" class="text-green-600 font-medium">{{ t("transfer.completed") }}</span>
|
||||
<span v-if="overallDone && !failedTables" class="text-green-600 font-medium">{{
|
||||
t("transfer.completed")
|
||||
}}</span>
|
||||
<span v-else-if="overallDone && failedTables" class="text-amber-600 font-medium">
|
||||
{{ t("transfer.completedWithErrors", { count: failedTables }) }}
|
||||
</span>
|
||||
<span v-else-if="overallCancelled" class="text-yellow-600 font-medium">{{ t("transfer.cancelled") }}</span>
|
||||
<span v-else-if="overallError" class="text-destructive font-medium">{{ t("transfer.failed") }}</span>
|
||||
</div>
|
||||
|
|
@ -543,7 +575,15 @@ const totalTransferred = computed(() =>
|
|||
<div class="w-full bg-muted rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded-full transition-all duration-300"
|
||||
:class="overallError ? 'bg-destructive' : overallCancelled ? 'bg-yellow-500' : 'bg-primary'"
|
||||
:class="
|
||||
overallError
|
||||
? 'bg-destructive'
|
||||
: overallCancelled
|
||||
? 'bg-yellow-500'
|
||||
: overallDone && failedTables
|
||||
? 'bg-amber-500'
|
||||
: 'bg-primary'
|
||||
"
|
||||
:style="{
|
||||
width: `${selectedTables.size ? (completedTables / selectedTables.size) * 100 : 0}%`,
|
||||
}"
|
||||
|
|
@ -561,7 +601,7 @@ const totalTransferred = computed(() =>
|
|||
<template v-if="transferProgress.get(table)">
|
||||
<template v-if="transferProgress.get(table)!.status === 'running'">
|
||||
<Loader2 class="w-3 h-3 animate-spin text-primary" />
|
||||
<span>{{ transferProgress.get(table)!.rowsTransferred.toLocaleString() }}</span>
|
||||
<span>{{ formatTableRows(transferProgress.get(table)!) }}</span>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
|
|
@ -570,10 +610,11 @@ const totalTransferred = computed(() =>
|
|||
"
|
||||
>
|
||||
<Check class="w-3 h-3 text-green-500" />
|
||||
<span>{{ transferProgress.get(table)!.rowsTransferred.toLocaleString() }}</span>
|
||||
<span>{{ formatTableRows(transferProgress.get(table)!) }}</span>
|
||||
</template>
|
||||
<template v-else-if="transferProgress.get(table)!.status === 'error'">
|
||||
<X class="w-3 h-3 text-destructive" />
|
||||
<span>{{ formatTableRows(transferProgress.get(table)!) }}</span>
|
||||
<span
|
||||
class="text-destructive truncate max-w-[160px]"
|
||||
:title="transferProgress.get(table)!.error ?? ''"
|
||||
|
|
|
|||
|
|
@ -1226,6 +1226,7 @@ export default {
|
|||
progress: "Transferring...",
|
||||
transferring: "Transferring {table}...",
|
||||
completed: "Transfer completed",
|
||||
completedWithErrors: "Completed with {count} failed table | Completed with {count} failed tables",
|
||||
failed: "Transfer failed",
|
||||
cancelled: "Transfer cancelled",
|
||||
rowsTransferred: "{count} rows transferred",
|
||||
|
|
|
|||
|
|
@ -1118,6 +1118,7 @@ export default {
|
|||
progress: "Transfiriendo...",
|
||||
transferring: "Transfiriendo {table}...",
|
||||
completed: "Transferencia completada",
|
||||
completedWithErrors: "Completada con {count} tabla con error | Completada con {count} tablas con error",
|
||||
failed: "Error en la transferencia",
|
||||
cancelled: "Transferencia cancelada",
|
||||
rowsTransferred: "{count} filas transferidas",
|
||||
|
|
|
|||
|
|
@ -1202,6 +1202,7 @@ export default {
|
|||
progress: "传输中...",
|
||||
transferring: "正在传输 {table}...",
|
||||
completed: "传输完成",
|
||||
completedWithErrors: "传输完成,{count} 个表失败",
|
||||
failed: "传输失败",
|
||||
cancelled: "传输已取消",
|
||||
rowsTransferred: "已传输 {count} 行",
|
||||
|
|
|
|||
|
|
@ -929,7 +929,7 @@ export async function startTransfer(
|
|||
es.onmessage = (e) => {
|
||||
const progress: TransferProgress = JSON.parse(e.data);
|
||||
onProgress(progress);
|
||||
if (progress.status === "done" || progress.status === "error" || progress.status === "cancelled") {
|
||||
if (progress.status === "done" || progress.status === "cancelled") {
|
||||
es.close();
|
||||
resolve();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1246,20 +1246,24 @@ export async function startTransfer(
|
|||
request: TransferRequest,
|
||||
onProgress: (progress: TransferProgress) => void,
|
||||
): Promise<void> {
|
||||
const unlisten: UnlistenFn = await listen<TransferProgress>("transfer-progress", (event) => {
|
||||
if (event.payload.transferId === request.transferId) {
|
||||
onProgress(event.payload);
|
||||
if (event.payload.status === "done" || event.payload.status === "error" || event.payload.status === "cancelled") {
|
||||
unlisten();
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let unlisten: UnlistenFn | null = null;
|
||||
try {
|
||||
unlisten = await listen<TransferProgress>("transfer-progress", (event) => {
|
||||
if (event.payload.transferId !== request.transferId) return;
|
||||
onProgress(event.payload);
|
||||
if (event.payload.status === "done" || event.payload.status === "cancelled") {
|
||||
unlisten?.();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
await invoke("start_transfer", { request });
|
||||
} catch (e) {
|
||||
unlisten?.();
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await invoke("start_transfer", { request });
|
||||
} catch (e) {
|
||||
unlisten();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelTransfer(transferId: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,5 @@ export function nextTransferTerminalState(
|
|||
): TransferTerminalState {
|
||||
if (progress.status === "done") return { ...state, done: true };
|
||||
if (progress.status === "cancelled") return { ...state, cancelled: true };
|
||||
if (progress.status === "error") return { ...state, error: true };
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,28 @@ pub async fn start_transfer(
|
|||
|
||||
let tables = req.tables.clone();
|
||||
for (i, table) in tables.iter().enumerate() {
|
||||
if transfer::is_cancelled(&req.transfer_id).await {
|
||||
let progress = transfer::TransferProgress {
|
||||
transfer_id: req.transfer_id.clone(),
|
||||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables: tables.len(),
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Cancelled,
|
||||
error: None,
|
||||
};
|
||||
if let Ok(json) = serde_json::to_string(&progress) {
|
||||
let _ = tx.send(json);
|
||||
}
|
||||
transfer::clear_cancelled(&req.transfer_id).await;
|
||||
state_clone.remove_sse_channel(&req.transfer_id).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let tx_clone = tx.clone();
|
||||
let mut last_rows_transferred = 0_u64;
|
||||
let mut last_total_rows = None;
|
||||
let result = transfer::transfer_table(
|
||||
&app,
|
||||
&req,
|
||||
|
|
@ -82,6 +103,8 @@ pub async fn start_transfer(
|
|||
&source_pool_key,
|
||||
&target_pool_key,
|
||||
|progress| {
|
||||
last_rows_transferred = progress.rows_transferred;
|
||||
last_total_rows = progress.total_rows;
|
||||
if let Ok(json) = serde_json::to_string(&progress) {
|
||||
let _ = tx_clone.send(json);
|
||||
}
|
||||
|
|
@ -96,8 +119,8 @@ pub async fn start_transfer(
|
|||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables: tables.len(),
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
rows_transferred: last_rows_transferred,
|
||||
total_rows: last_total_rows.or(Some(last_rows_transferred)),
|
||||
status: TransferStatus::TableDone,
|
||||
error: None,
|
||||
};
|
||||
|
|
@ -111,15 +134,14 @@ pub async fn start_transfer(
|
|||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables: tables.len(),
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
rows_transferred: last_rows_transferred,
|
||||
total_rows: last_total_rows,
|
||||
status: TransferStatus::Error,
|
||||
error: Some(e),
|
||||
};
|
||||
if let Ok(json) = serde_json::to_string(&progress) {
|
||||
let _ = tx.send(json);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -139,6 +161,7 @@ pub async fn start_transfer(
|
|||
let _ = tx.send(json);
|
||||
}
|
||||
|
||||
transfer::clear_cancelled(&req.transfer_id).await;
|
||||
state_clone.remove_sse_channel(&req.transfer_id).await;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { strict as assert } from "node:assert";
|
|||
import test from "node:test";
|
||||
import { nextTransferTerminalState } from "../../apps/desktop/src/lib/transferProgressState.ts";
|
||||
|
||||
test("marks transfer as failed when a table progress event reports error", () => {
|
||||
test("does not mark transfer as failed when only a table progress event reports error", () => {
|
||||
const state = nextTransferTerminalState(
|
||||
{ done: false, cancelled: false, error: false },
|
||||
{ status: "error" },
|
||||
);
|
||||
|
||||
assert.deepEqual(state, { done: false, cancelled: false, error: true });
|
||||
assert.deepEqual(state, { done: false, cancelled: false, error: false });
|
||||
});
|
||||
|
||||
test("keeps terminal flags for done and cancelled progress events", () => {
|
||||
|
|
@ -21,3 +21,16 @@ test("keeps terminal flags for done and cancelled progress events", () => {
|
|||
{ done: false, cancelled: true, error: false },
|
||||
);
|
||||
});
|
||||
|
||||
test("still marks transfer as done after earlier table errors", () => {
|
||||
const afterError = nextTransferTerminalState(
|
||||
{ done: false, cancelled: false, error: false },
|
||||
{ status: "error" },
|
||||
);
|
||||
|
||||
assert.deepEqual(nextTransferTerminalState(afterError, { status: "done" }), {
|
||||
done: true,
|
||||
cancelled: false,
|
||||
error: false,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ pub async fn start_transfer(
|
|||
|
||||
log::info!("[transfer] table {}/{}: {}", i + 1, total_tables, table);
|
||||
|
||||
let mut last_rows_transferred = 0_u64;
|
||||
let mut last_total_rows = None;
|
||||
|
||||
match dbx_core::transfer::transfer_table(
|
||||
&state,
|
||||
&request,
|
||||
|
|
@ -63,7 +66,11 @@ pub async fn start_transfer(
|
|||
&target_db_type,
|
||||
&source_pool_key,
|
||||
&target_pool_key,
|
||||
|progress| emit_progress(&app, progress),
|
||||
|progress| {
|
||||
last_rows_transferred = progress.rows_transferred;
|
||||
last_total_rows = progress.total_rows;
|
||||
emit_progress(&app, progress);
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
|
@ -76,12 +83,8 @@ pub async fn start_transfer(
|
|||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: rows,
|
||||
total_rows: Some(rows),
|
||||
status: if i == total_tables - 1 {
|
||||
TransferStatus::Done
|
||||
} else {
|
||||
TransferStatus::TableDone
|
||||
},
|
||||
total_rows: last_total_rows.or(Some(rows)),
|
||||
status: TransferStatus::TableDone,
|
||||
error: None,
|
||||
},
|
||||
);
|
||||
|
|
@ -111,18 +114,29 @@ pub async fn start_transfer(
|
|||
table: table.clone(),
|
||||
table_index: i,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
rows_transferred: last_rows_transferred,
|
||||
total_rows: last_total_rows,
|
||||
status: TransferStatus::Error,
|
||||
error: Some(e),
|
||||
},
|
||||
);
|
||||
dbx_core::transfer::clear_cancelled(&transfer_id).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit_progress(
|
||||
&app,
|
||||
TransferProgress {
|
||||
transfer_id: transfer_id.clone(),
|
||||
table: String::new(),
|
||||
table_index: total_tables,
|
||||
total_tables,
|
||||
rows_transferred: 0,
|
||||
total_rows: None,
|
||||
status: TransferStatus::Done,
|
||||
error: None,
|
||||
},
|
||||
);
|
||||
dbx_core::transfer::clear_cancelled(&transfer_id).await;
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue