feat: support emptying multiple selected tables
* Feature: support emptying multiple selected tables * ci: retrigger checks * Fix batch empty safety and ClickHouse feedback --------- Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
parent
903ab26c9b
commit
7b2f969356
|
|
@ -146,6 +146,7 @@ import { connectionPasteTargetGroupId, selectedConnectionClipboardTargets, selec
|
|||
import { supportsDatabaseUserAdmin } from "@/lib/database/databaseUserAdmin";
|
||||
import { canCloseSidebarDatabaseConnection, isSidebarDatabaseOpened } from "@/lib/sidebar/sidebarDatabaseOpenState";
|
||||
import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { batchTableEmptyFeedback, runBatchTableEmpty } from "@/lib/sidebar/batchTableEmpty";
|
||||
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
|
||||
import ProcedureExecutionDialog from "@/components/objects/ProcedureExecutionDialog.vue";
|
||||
import InstallExtensionDialog from "@/components/objects/InstallExtensionDialog.vue";
|
||||
|
|
@ -1747,6 +1748,7 @@ async function duplicateConnection() {
|
|||
const showDropTableConfirm = ref(false);
|
||||
const showDropTableChildObjectConfirm = ref(false);
|
||||
const showBatchDropConfirm = ref(false);
|
||||
const showBatchEmptyConfirm = ref(false);
|
||||
const showBatchTruncateConfirm = ref(false);
|
||||
const showStructurePreviewDialog = ref(false);
|
||||
const showStructureDocCopyDialog = ref(false);
|
||||
|
|
@ -1772,6 +1774,8 @@ const truncateTableCascade = ref(false);
|
|||
const dropObjectPreviewSql = ref("");
|
||||
const dropTableChildObjectPreviewSql = ref("");
|
||||
const batchDropPreviewSql = ref("");
|
||||
const batchEmptyPreviewSql = ref("");
|
||||
const batchEmptyTargets = ref<TreeNode[]>([]);
|
||||
const batchTruncatePreviewSql = ref("");
|
||||
const batchTruncateCascade = ref(false);
|
||||
const dropDatabasePreviewSql = ref("");
|
||||
|
|
@ -2077,6 +2081,10 @@ function selectedBatchTruncateTargets(): TreeNode[] {
|
|||
return targets.every((node) => supportsTableTruncate(databaseTypeForNode(node))) ? targets : [];
|
||||
}
|
||||
|
||||
function selectedBatchEmptyTargets(): TreeNode[] {
|
||||
return selectedBatchTableTargets();
|
||||
}
|
||||
|
||||
function selectedBatchMongoIndexTargets(): TreeNode[] {
|
||||
const targets = selectedBatchDropTargets();
|
||||
return targets.length > 1 && targets.every((node) => canDropMongoIndexNode(node)) ? targets : [];
|
||||
|
|
@ -2118,6 +2126,22 @@ function batchTruncateMenuLabel(): string {
|
|||
return t("contextMenu.batchTruncate", { count: selectedBatchTruncateTargets().length });
|
||||
}
|
||||
|
||||
function batchEmptyMenuLabel(): string {
|
||||
return t("contextMenu.batchEmpty", { count: selectedBatchEmptyTargets().length });
|
||||
}
|
||||
|
||||
function batchEmptyConfirmTitle(): string {
|
||||
return t("contextMenu.confirmBatchEmptyTitle", { count: batchEmptyTargets.value.length });
|
||||
}
|
||||
|
||||
function batchEmptyConfirmMessage(): string {
|
||||
return t("contextMenu.confirmBatchEmptyMessage", { count: batchEmptyTargets.value.length });
|
||||
}
|
||||
|
||||
function batchEmptyConfirmLabel(): string {
|
||||
return t("contextMenu.batchEmpty", { count: batchEmptyTargets.value.length });
|
||||
}
|
||||
|
||||
function batchTruncateConfirmTitle(): string {
|
||||
return t("contextMenu.confirmBatchTruncateTitle", { count: selectedBatchTruncateTargets().length });
|
||||
}
|
||||
|
|
@ -2155,6 +2179,15 @@ async function truncateSqlForTreeNode(node: TreeNode, options?: { cascade?: bool
|
|||
});
|
||||
}
|
||||
|
||||
async function emptySqlForTreeNode(node: TreeNode): Promise<string | null> {
|
||||
if (node.type !== "table" || !node.connectionId || !node.database) return null;
|
||||
return buildEmptyTableSql({
|
||||
databaseType: databaseTypeForNode(node),
|
||||
schema: node.schema,
|
||||
tableName: node.label,
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshBatchDropPreviewSql() {
|
||||
const targets = selectedBatchDropTargets();
|
||||
const mongoIndexTargets = selectedBatchMongoIndexTargets();
|
||||
|
|
@ -2182,6 +2215,15 @@ async function refreshBatchTruncatePreviewSql() {
|
|||
batchTruncatePreviewSql.value = statements.join("\n");
|
||||
}
|
||||
|
||||
async function refreshBatchEmptyPreviewSql(targets: TreeNode[]) {
|
||||
const statements: string[] = [];
|
||||
for (const target of targets) {
|
||||
const sql = await emptySqlForTreeNode(target);
|
||||
if (sql) statements.push(sql);
|
||||
}
|
||||
batchEmptyPreviewSql.value = statements.join("\n");
|
||||
}
|
||||
|
||||
function requestBatchDrop() {
|
||||
if (!selectedBatchDropTargets().length) return;
|
||||
batchDropCascade.value = false;
|
||||
|
|
@ -2196,6 +2238,22 @@ function requestBatchTruncate() {
|
|||
showBatchTruncateConfirm.value = true;
|
||||
}
|
||||
|
||||
function requestBatchEmpty() {
|
||||
const targets = selectedBatchEmptyTargets();
|
||||
if (!targets.length) return;
|
||||
batchEmptyTargets.value = targets.slice();
|
||||
batchEmptyPreviewSql.value = "";
|
||||
void refreshBatchEmptyPreviewSql(batchEmptyTargets.value)
|
||||
.then(() => {
|
||||
if (!batchEmptyPreviewSql.value.trim()) throw new Error("Empty table SQL preview is unavailable");
|
||||
showBatchEmptyConfirm.value = true;
|
||||
})
|
||||
.catch((e: any) => {
|
||||
batchEmptyTargets.value = [];
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function requestDropSelectedNodes(): boolean {
|
||||
const selected = selectedTreeNodesInVisibleOrder();
|
||||
if (selected.length > 1 && selected.some((node) => node.id === props.node.id)) {
|
||||
|
|
@ -2424,6 +2482,34 @@ async function confirmBatchTruncate() {
|
|||
}
|
||||
}
|
||||
|
||||
async function confirmBatchEmpty() {
|
||||
const targets = batchEmptyTargets.value.slice();
|
||||
if (!targets.length) return;
|
||||
const asynchronousMutation = targets.every((target) => databaseTypeForNode(target) === "clickhouse");
|
||||
const result = await runBatchTableEmpty(targets, async (target) => {
|
||||
if (!target.connectionId || !target.database) throw new Error("Missing table connection context");
|
||||
await connectionStore.ensureConnected(target.connectionId);
|
||||
const sql = await emptySqlForTreeNode(target);
|
||||
if (!sql) throw new Error("Empty table SQL is unavailable");
|
||||
await api.executeQuery(target.connectionId, target.database, sql, target.schema);
|
||||
});
|
||||
for (const failure of result.failed) {
|
||||
console.error(`Failed to empty table "${failure.target.label}":`, failure.error);
|
||||
}
|
||||
const feedback = batchTableEmptyFeedback(result, asynchronousMutation);
|
||||
if (feedback === "success") {
|
||||
toast(t("contextMenu.batchEmptySuccess", { count: result.succeeded.length }), 3000);
|
||||
} else if (feedback === "submitted") {
|
||||
toast(t("contextMenu.batchEmptySubmitted", { count: result.succeeded.length }), 3000);
|
||||
} else if (feedback === "submitted-partial") {
|
||||
toast(t("contextMenu.batchEmptySubmittedPartial", { success: result.succeeded.length, failed: result.failed.length }), 5000);
|
||||
} else {
|
||||
toast(t("contextMenu.batchEmptyPartialFail", { success: result.succeeded.length, failed: result.failed.length }), 5000);
|
||||
}
|
||||
batchEmptyTargets.value = [];
|
||||
showBatchEmptyConfirm.value = false;
|
||||
}
|
||||
|
||||
const isTableNotView = computed(() => props.node.type === "table" && !isSqlServerLinkedNode(props.node));
|
||||
|
||||
const supportsTruncate = computed(() => {
|
||||
|
|
@ -2617,7 +2703,8 @@ async function confirmEmptyTable() {
|
|||
await connectionStore.ensureConnected(node.connectionId);
|
||||
const sql = emptyTablePreviewSql.value || (await buildEmptyTableSql(tableAdminSqlOptions()));
|
||||
await api.executeQuery(node.connectionId, node.database, sql, node.schema);
|
||||
toast(t("contextMenu.emptyTableSuccess", { name: node.label }), 3000);
|
||||
const messageKey = currentDatabaseType() === "clickhouse" ? "contextMenu.emptyTableSubmitted" : "contextMenu.emptyTableSuccess";
|
||||
toast(t(messageKey, { name: node.label }), 3000);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
|
|
@ -4505,11 +4592,14 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
const node = props.node;
|
||||
const items: ContextMenuItem[] = [];
|
||||
const batchDropCount = selectedBatchDropTargets().length;
|
||||
const batchEmptyCount = selectedBatchEmptyTargets().length;
|
||||
const batchTruncateCount = selectedBatchTruncateTargets().length;
|
||||
const deleteMenuLabel = (singleLabel: string) => (batchDropCount > 1 ? batchDropMenuLabel() : singleLabel);
|
||||
const deleteMenuAction = (singleAction: () => void) => (batchDropCount > 1 ? requestBatchDrop : singleAction);
|
||||
const truncateMenuLabel = (singleLabel: string) => (batchTruncateCount > 1 ? batchTruncateMenuLabel() : singleLabel);
|
||||
const truncateMenuAction = (singleAction: () => void) => (batchTruncateCount > 1 ? requestBatchTruncate : singleAction);
|
||||
const emptyMenuLabel = (singleLabel: string) => (batchEmptyCount > 1 ? batchEmptyMenuLabel() : singleLabel);
|
||||
const emptyMenuAction = (singleAction: () => void) => (batchEmptyCount > 1 ? requestBatchEmpty : singleAction);
|
||||
|
||||
// 1. Pin toggle
|
||||
if (canPin.value) {
|
||||
|
|
@ -4934,8 +5024,8 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
});
|
||||
}
|
||||
destructiveActions.push({
|
||||
label: t("contextMenu.emptyTable"),
|
||||
action: emptyTable,
|
||||
label: emptyMenuLabel(t("contextMenu.emptyTable")),
|
||||
action: emptyMenuAction(emptyTable),
|
||||
icon: Eraser,
|
||||
variant: "destructive" as const,
|
||||
});
|
||||
|
|
@ -5388,6 +5478,8 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
|
||||
<DangerConfirmDialog v-model:open="showEmptyTableConfirm" :title="t('contextMenu.confirmEmptyTableTitle')" :message="t('contextMenu.confirmEmptyTableMessage', { name: node.label })" :sql="emptyTablePreviewSql" :confirm-label="t('contextMenu.emptyTable')" @confirm="confirmEmptyTable" />
|
||||
|
||||
<DangerConfirmDialog v-model:open="showBatchEmptyConfirm" :title="batchEmptyConfirmTitle()" :message="batchEmptyConfirmMessage()" :sql="batchEmptyPreviewSql" :confirm-label="batchEmptyConfirmLabel()" @confirm="confirmBatchEmpty" />
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showTruncateTableConfirm"
|
||||
:title="t('contextMenu.confirmTruncateTableTitle')"
|
||||
|
|
|
|||
|
|
@ -1468,6 +1468,7 @@ export default {
|
|||
dropForeignKey: "Drop Foreign Key",
|
||||
dropTrigger: "Drop Trigger",
|
||||
batchDrop: "Drop selected ({count})",
|
||||
batchEmpty: "Empty selected ({count})",
|
||||
batchTruncate: "Truncate selected ({count})",
|
||||
batchDropIndexes: "Drop Indexes ({count})",
|
||||
executeProcedure: "Execute Procedure",
|
||||
|
|
@ -1509,6 +1510,8 @@ export default {
|
|||
confirmDropBatchIndexesMessage: 'Are you sure you want to drop {count} selected indexes from "{table}"? This cannot be undone.',
|
||||
confirmBatchDropTitle: "Drop Selected Objects",
|
||||
confirmBatchDropMessage: "Are you sure you want to drop {count} selected objects? This cannot be undone.",
|
||||
confirmBatchEmptyTitle: "Empty Selected Tables",
|
||||
confirmBatchEmptyMessage: "Are you sure you want to delete all data from {count} selected tables?",
|
||||
confirmBatchTruncateTitle: "Truncate Selected Tables",
|
||||
confirmBatchTruncateMessage: "Are you sure you want to truncate {count} selected tables? This will remove all rows.",
|
||||
confirmDropProcedureTitle: "Drop Procedure",
|
||||
|
|
@ -1521,8 +1524,13 @@ export default {
|
|||
dropTableChildObjectSuccess: '"{name}" dropped',
|
||||
dropAllIndexesSuccess: 'Dropped {count} indexes from "{name}"',
|
||||
batchDropSuccess: "Dropped {count} objects",
|
||||
batchEmptySuccess: "Emptied {count} tables",
|
||||
batchEmptyPartialFail: "Emptied {success} tables, {failed} failed",
|
||||
batchEmptySubmitted: "Submitted empty tasks for {count} tables",
|
||||
batchEmptySubmittedPartial: "Submitted {success} empty tasks, {failed} failed",
|
||||
batchTruncateSuccess: "Truncated {count} tables",
|
||||
emptyTableSuccess: 'All data deleted from "{name}"',
|
||||
emptyTableSubmitted: 'Empty task submitted for "{name}"',
|
||||
truncateTableSuccess: 'Table "{name}" truncated',
|
||||
duplicateStructureSuccess: 'Table cloned as "{name}"',
|
||||
tableOperationFailed: "Operation failed: {message}",
|
||||
|
|
|
|||
|
|
@ -1413,6 +1413,7 @@ export default withEnglishFallback({
|
|||
dropForeignKey: "Eliminar clave foránea",
|
||||
dropTrigger: "Eliminar disparador",
|
||||
batchDrop: "Eliminar seleccionados ({count})",
|
||||
batchEmpty: "Vaciar seleccionadas ({count})",
|
||||
batchTruncate: "Truncar seleccionadas ({count})",
|
||||
executeProcedure: "Ejecutar procedimiento",
|
||||
confirmExecuteProcedureTitle: "Ejecutar procedimiento",
|
||||
|
|
@ -1453,6 +1454,8 @@ export default withEnglishFallback({
|
|||
confirmDropBatchIndexesMessage: '¿Seguro que deseas eliminar {count} índices seleccionados de "{table}"? Esta acción no se puede deshacer.',
|
||||
confirmBatchDropTitle: "Eliminar objetos seleccionados",
|
||||
confirmBatchDropMessage: "¿Seguro que deseas eliminar {count} objetos seleccionados? Esta acción no se puede deshacer.",
|
||||
confirmBatchEmptyTitle: "Vaciar tablas seleccionadas",
|
||||
confirmBatchEmptyMessage: "¿Seguro que deseas eliminar todos los datos de {count} tablas seleccionadas?",
|
||||
confirmBatchTruncateTitle: "Truncar tablas seleccionadas",
|
||||
confirmBatchTruncateMessage: "¿Seguro que deseas truncar {count} tablas seleccionadas? Esto eliminará todas las filas.",
|
||||
confirmDropProcedureTitle: "Eliminar procedimiento",
|
||||
|
|
@ -1465,8 +1468,13 @@ export default withEnglishFallback({
|
|||
dropTableChildObjectSuccess: '"{name}" eliminado',
|
||||
dropAllIndexesSuccess: 'Se eliminaron {count} índices de "{name}"',
|
||||
batchDropSuccess: "{count} objetos eliminados",
|
||||
batchEmptySuccess: "{count} tablas vaciadas",
|
||||
batchEmptyPartialFail: "Se vaciaron {success} tablas; {failed} fallaron",
|
||||
batchEmptySubmitted: "Se enviaron tareas para vaciar {count} tablas",
|
||||
batchEmptySubmittedPartial: "Se enviaron {success} tareas; {failed} fallaron",
|
||||
batchTruncateSuccess: "{count} tablas truncadas",
|
||||
emptyTableSuccess: 'Todos los datos eliminados de "{name}"',
|
||||
emptyTableSubmitted: 'Tarea de vaciado enviada para "{name}"',
|
||||
truncateTableSuccess: 'Tabla "{name}" truncada',
|
||||
duplicateStructureSuccess: 'Tabla clonada como "{name}"',
|
||||
tableOperationFailed: "Error en la operación: {message}",
|
||||
|
|
|
|||
|
|
@ -1412,6 +1412,7 @@ export default withEnglishFallback({
|
|||
dropForeignKey: "Elimina Chiave Esterna",
|
||||
dropTrigger: "Elimina Trigger",
|
||||
batchDrop: "Elimina selezionati ({count})",
|
||||
batchEmpty: "Svuota selezionate ({count})",
|
||||
batchTruncate: "Tronca selezionate ({count})",
|
||||
batchDropIndexes: "Elimina indici ({count})",
|
||||
executeProcedure: "Esegui Procedura",
|
||||
|
|
@ -1451,6 +1452,8 @@ export default withEnglishFallback({
|
|||
confirmDropBatchIndexesMessage: 'Sei sicuro di voler eliminare {count} indici selezionati da "{table}"? Questa azione non può essere annullata.',
|
||||
confirmBatchDropTitle: "Elimina Oggetti Selezionati",
|
||||
confirmBatchDropMessage: "Sei sicuro di voler eliminare {count} oggetti selezionati? Questa azione non può essere annullata.",
|
||||
confirmBatchEmptyTitle: "Svuota Tabelle Selezionate",
|
||||
confirmBatchEmptyMessage: "Sei sicuro di voler eliminare tutti i dati da {count} tabelle selezionate?",
|
||||
confirmBatchTruncateTitle: "Tronca Tabelle Selezionate",
|
||||
confirmBatchTruncateMessage: "Sei sicuro di voler troncare {count} tabelle selezionate? Questa operazione rimuoverà tutte le righe.",
|
||||
confirmDropProcedureTitle: "Elimina Procedura",
|
||||
|
|
@ -1463,8 +1466,13 @@ export default withEnglishFallback({
|
|||
dropTableChildObjectSuccess: '"{name}" eliminato',
|
||||
dropAllIndexesSuccess: 'Eliminati {count} indici da "{name}"',
|
||||
batchDropSuccess: "Eliminati {count} oggetti",
|
||||
batchEmptySuccess: "Svuotate {count} tabelle",
|
||||
batchEmptyPartialFail: "Svuotate {success} tabelle, {failed} non riuscite",
|
||||
batchEmptySubmitted: "Inviate attività di svuotamento per {count} tabelle",
|
||||
batchEmptySubmittedPartial: "Inviate {success} attività, {failed} non riuscite",
|
||||
batchTruncateSuccess: "Troncate {count} tabelle",
|
||||
emptyTableSuccess: 'Tutti i dati eliminati da "{name}"',
|
||||
emptyTableSubmitted: 'Attività di svuotamento inviata per "{name}"',
|
||||
truncateTableSuccess: 'Tabella "{name}" troncata',
|
||||
duplicateStructureSuccess: 'Tabella clonata come "{name}"',
|
||||
tableOperationFailed: "Operazione non riuscita: {message}",
|
||||
|
|
|
|||
|
|
@ -1407,6 +1407,7 @@ export default withEnglishFallback({
|
|||
dropForeignKey: "外部キーを削除",
|
||||
dropTrigger: "トリガーを削除",
|
||||
batchDrop: "選択を削除({count}件)",
|
||||
batchEmpty: "選択したテーブルを空にする({count}件)",
|
||||
batchTruncate: "選択をトランケート({count}件)",
|
||||
executeProcedure: "プロシージャを実行",
|
||||
confirmExecuteProcedureTitle: "プロシージャを実行",
|
||||
|
|
@ -1447,6 +1448,8 @@ export default withEnglishFallback({
|
|||
confirmDropBatchIndexesMessage: "本当に「{table}」から選択した {count} 個のインデックスを削除しますか?この操作は取り消せません。",
|
||||
confirmBatchDropTitle: "選択したオブジェクトを削除",
|
||||
confirmBatchDropMessage: "選択した{count}個のオブジェクトを削除しますか?この操作は取り消せません。",
|
||||
confirmBatchEmptyTitle: "選択したテーブルを空にする",
|
||||
confirmBatchEmptyMessage: "選択した{count}テーブルのすべてのデータを削除しますか?",
|
||||
confirmBatchTruncateTitle: "選択したテーブルをトランケート",
|
||||
confirmBatchTruncateMessage: "選択した{count}テーブルをトランケートしますか?すべての行が削除されます。",
|
||||
confirmDropProcedureTitle: "プロシージャを削除",
|
||||
|
|
@ -1459,8 +1462,13 @@ export default withEnglishFallback({
|
|||
dropTableChildObjectSuccess: "「{name}」を削除しました",
|
||||
dropAllIndexesSuccess: "「{name}」から {count} 個のインデックスを削除しました",
|
||||
batchDropSuccess: "{count}個のオブジェクトを削除しました",
|
||||
batchEmptySuccess: "{count}テーブルを空にしました",
|
||||
batchEmptyPartialFail: "{success}テーブルを空にし、{failed}テーブルは失敗しました",
|
||||
batchEmptySubmitted: "{count}テーブルの空化タスクを送信しました",
|
||||
batchEmptySubmittedPartial: "{success}件の空化タスクを送信し、{failed}件は失敗しました",
|
||||
batchTruncateSuccess: "{count}テーブルをトランケートしました",
|
||||
emptyTableSuccess: "「{name}」のすべてのデータを削除しました",
|
||||
emptyTableSubmitted: "「{name}」の空化タスクを送信しました",
|
||||
truncateTableSuccess: "テーブル「{name}」をトランケートしました",
|
||||
duplicateStructureSuccess: "テーブルを「{name}」として複製しました",
|
||||
tableOperationFailed: "操作に失敗しました: {message}",
|
||||
|
|
|
|||
|
|
@ -1412,6 +1412,7 @@ export default withEnglishFallback({
|
|||
dropForeignKey: "Remover Chave Estrangeira",
|
||||
dropTrigger: "Remover Gatilho",
|
||||
batchDrop: "Remover selecionados ({count})",
|
||||
batchEmpty: "Esvaziar selecionadas ({count})",
|
||||
batchTruncate: "Truncar selecionadas ({count})",
|
||||
executeProcedure: "Executar Procedimento",
|
||||
confirmExecuteProcedureTitle: "Executar Procedimento",
|
||||
|
|
@ -1452,6 +1453,8 @@ export default withEnglishFallback({
|
|||
confirmDropBatchIndexesMessage: 'Tem certeza de que deseja remover {count} índices selecionados de "{table}"? Esta ação não pode ser desfeita.',
|
||||
confirmBatchDropTitle: "Remover Objetos Selecionados",
|
||||
confirmBatchDropMessage: "Tem certeza de que deseja remover {count} objetos selecionados? Esta ação não pode ser desfeita.",
|
||||
confirmBatchEmptyTitle: "Esvaziar tabelas selecionadas",
|
||||
confirmBatchEmptyMessage: "Tem certeza de que deseja excluir todos os dados de {count} tabelas selecionadas?",
|
||||
confirmBatchTruncateTitle: "Truncar tabelas selecionadas",
|
||||
confirmBatchTruncateMessage: "Tem certeza de que deseja truncar {count} tabelas selecionadas? Isso removerá todas as linhas.",
|
||||
confirmDropProcedureTitle: "Remover Procedimento",
|
||||
|
|
@ -1464,8 +1467,13 @@ export default withEnglishFallback({
|
|||
dropTableChildObjectSuccess: '"{name}" removido',
|
||||
dropAllIndexesSuccess: 'Removidos {count} índices de "{name}"',
|
||||
batchDropSuccess: "{count} objetos removidos",
|
||||
batchEmptySuccess: "{count} tabelas esvaziadas",
|
||||
batchEmptyPartialFail: "{success} tabelas esvaziadas; {failed} falharam",
|
||||
batchEmptySubmitted: "Tarefas de esvaziamento enviadas para {count} tabelas",
|
||||
batchEmptySubmittedPartial: "{success} tarefas enviadas; {failed} falharam",
|
||||
batchTruncateSuccess: "{count} tabelas truncadas",
|
||||
emptyTableSuccess: 'Todos os dados excluídos de "{name}"',
|
||||
emptyTableSubmitted: 'Tarefa de esvaziamento enviada para "{name}"',
|
||||
truncateTableSuccess: 'Tabela "{name}" truncada',
|
||||
duplicateStructureSuccess: 'Tabela clonada como "{name}"',
|
||||
tableOperationFailed: "Falha na operação: {message}",
|
||||
|
|
|
|||
|
|
@ -1469,6 +1469,7 @@ export default withEnglishFallback({
|
|||
dropForeignKey: "删除外键",
|
||||
dropTrigger: "删除触发器",
|
||||
batchDrop: "删除所选({count})",
|
||||
batchEmpty: "清空所选({count})",
|
||||
batchTruncate: "截断所选({count})",
|
||||
batchDropIndexes: "删除索引({count})",
|
||||
executeProcedure: "执行过程",
|
||||
|
|
@ -1510,6 +1511,8 @@ export default withEnglishFallback({
|
|||
confirmDropBatchIndexesMessage: "确定要从「{table}」删除已选择的 {count} 个索引吗?此操作不可撤销。",
|
||||
confirmBatchDropTitle: "删除所选对象",
|
||||
confirmBatchDropMessage: "确定要删除已选择的 {count} 个对象吗?此操作不可撤销。",
|
||||
confirmBatchEmptyTitle: "清空所选表",
|
||||
confirmBatchEmptyMessage: "确定要删除已选择的 {count} 张表中的所有数据吗?",
|
||||
confirmBatchTruncateTitle: "截断所选表",
|
||||
confirmBatchTruncateMessage: "确定要截断已选择的 {count} 张表吗?这将删除所有行。",
|
||||
confirmDropProcedureTitle: "删除存储过程",
|
||||
|
|
@ -1522,8 +1525,13 @@ export default withEnglishFallback({
|
|||
dropTableChildObjectSuccess: "「{name}」已删除",
|
||||
dropAllIndexesSuccess: "已从「{name}」删除 {count} 个索引",
|
||||
batchDropSuccess: "已删除 {count} 个对象",
|
||||
batchEmptySuccess: "已清空 {count} 张表",
|
||||
batchEmptyPartialFail: "已清空 {success} 张表,{failed} 张失败",
|
||||
batchEmptySubmitted: "已提交 {count} 张表的清空任务",
|
||||
batchEmptySubmittedPartial: "已提交 {success} 张表的清空任务,{failed} 张提交失败",
|
||||
batchTruncateSuccess: "已截断 {count} 张表",
|
||||
emptyTableSuccess: "已清空「{name}」的所有数据",
|
||||
emptyTableSubmitted: "已提交「{name}」的清空任务",
|
||||
truncateTableSuccess: "表「{name}」已截断",
|
||||
duplicateStructureSuccess: "已克隆为新表「{name}」",
|
||||
tableOperationFailed: "操作失败:{message}",
|
||||
|
|
|
|||
|
|
@ -1411,6 +1411,7 @@ export default withEnglishFallback({
|
|||
dropForeignKey: "刪除外鍵",
|
||||
dropTrigger: "刪除觸發器",
|
||||
batchDrop: "刪除所選({count})",
|
||||
batchEmpty: "清空所選({count})",
|
||||
batchTruncate: "截斷所選({count})",
|
||||
executeProcedure: "執行預存程序",
|
||||
confirmExecuteProcedureTitle: "執行預存程序",
|
||||
|
|
@ -1451,6 +1452,8 @@ export default withEnglishFallback({
|
|||
confirmDropBatchIndexesMessage: "確定要從「{table}」刪除已選擇的 {count} 個索引嗎?此操作無法復原。",
|
||||
confirmBatchDropTitle: "刪除所選物件",
|
||||
confirmBatchDropMessage: "確定要刪除已選擇的 {count} 個物件嗎?此操作無法復原。",
|
||||
confirmBatchEmptyTitle: "清空所選資料表",
|
||||
confirmBatchEmptyMessage: "確定要刪除已選擇的 {count} 張資料表中的所有資料嗎?",
|
||||
confirmBatchTruncateTitle: "截斷所選資料表",
|
||||
confirmBatchTruncateMessage: "確定要截斷已選擇的 {count} 張資料表嗎?這將刪除所有行。",
|
||||
confirmDropProcedureTitle: "刪除預存程序",
|
||||
|
|
@ -1463,8 +1466,13 @@ export default withEnglishFallback({
|
|||
dropTableChildObjectSuccess: "「{name}」已刪除",
|
||||
dropAllIndexesSuccess: "已從「{name}」刪除 {count} 個索引",
|
||||
batchDropSuccess: "已刪除 {count} 個物件",
|
||||
batchEmptySuccess: "已清空 {count} 張資料表",
|
||||
batchEmptyPartialFail: "已清空 {success} 張資料表,{failed} 張失敗",
|
||||
batchEmptySubmitted: "已提交 {count} 張資料表的清空工作",
|
||||
batchEmptySubmittedPartial: "已提交 {success} 張資料表的清空工作,{failed} 張提交失敗",
|
||||
batchTruncateSuccess: "已截斷 {count} 張資料表",
|
||||
emptyTableSuccess: "已清空「{name}」的所有資料",
|
||||
emptyTableSubmitted: "已提交「{name}」的清空工作",
|
||||
truncateTableSuccess: "資料表「{name}」已截斷",
|
||||
duplicateStructureSuccess: "已克隆為新資料表「{name}」",
|
||||
tableOperationFailed: "操作失敗:{message}",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { batchTableEmptyFeedback, runBatchTableEmpty } from "@/lib/sidebar/batchTableEmpty";
|
||||
|
||||
describe("batch table empty", () => {
|
||||
it("continues after a table fails and collects each result", async () => {
|
||||
const executed: string[] = [];
|
||||
const result = await runBatchTableEmpty(["orders", "locked", "customers"], async (table) => {
|
||||
executed.push(table);
|
||||
if (table === "locked") throw new Error("permission denied");
|
||||
});
|
||||
|
||||
expect(executed).toEqual(["orders", "locked", "customers"]);
|
||||
expect(result.succeeded).toEqual(["orders", "customers"]);
|
||||
expect(result.failed.map(({ target }) => target)).toEqual(["locked"]);
|
||||
});
|
||||
|
||||
it("uses submitted feedback for asynchronous mutations", () => {
|
||||
const succeeded = { succeeded: ["events"], failed: [] };
|
||||
const partial = { succeeded: ["events"], failed: [{ target: "logs", error: new Error("failed") }] };
|
||||
const failed = { succeeded: [], failed: [{ target: "logs", error: new Error("failed") }] };
|
||||
|
||||
expect(batchTableEmptyFeedback(succeeded, true)).toBe("submitted");
|
||||
expect(batchTableEmptyFeedback(partial, true)).toBe("submitted-partial");
|
||||
expect(batchTableEmptyFeedback(failed, true)).toBe("submitted-partial");
|
||||
expect(batchTableEmptyFeedback(succeeded, false)).toBe("success");
|
||||
expect(batchTableEmptyFeedback(partial, false)).toBe("partial");
|
||||
expect(batchTableEmptyFeedback(failed, false)).toBe("partial");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
export interface BatchTableEmptyResult<T> {
|
||||
succeeded: T[];
|
||||
failed: Array<{ target: T; error: unknown }>;
|
||||
}
|
||||
|
||||
export type BatchTableEmptyFeedback = "success" | "partial" | "submitted" | "submitted-partial";
|
||||
|
||||
export async function runBatchTableEmpty<T>(targets: readonly T[], execute: (target: T) => Promise<void>): Promise<BatchTableEmptyResult<T>> {
|
||||
const result: BatchTableEmptyResult<T> = { succeeded: [], failed: [] };
|
||||
for (const target of targets) {
|
||||
try {
|
||||
await execute(target);
|
||||
result.succeeded.push(target);
|
||||
} catch (error) {
|
||||
result.failed.push({ target, error });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function batchTableEmptyFeedback(result: BatchTableEmptyResult<unknown>, asynchronousMutation: boolean): BatchTableEmptyFeedback {
|
||||
if (asynchronousMutation) return result.failed.length > 0 ? "submitted-partial" : "submitted";
|
||||
return result.failed.length > 0 ? "partial" : "success";
|
||||
}
|
||||
Loading…
Reference in New Issue