Compare commits
2 Commits
ec4e3f39d6
...
c66d04f0b5
| Author | SHA1 | Date | |
|---|---|---|---|
| c66d04f0b5 | |||
| 5a05d2e9e8 |
@@ -80,6 +80,42 @@ const remapSourceTenant = (exportData: TenantFullExport, targetTenantId: number)
|
|||||||
return tables
|
return tables
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const splitSourceTablesByAvailability = (
|
||||||
|
sourceTables: Record<string, Record<string, any>[]>,
|
||||||
|
metadata: Record<string, MergeDatabaseMetadata>
|
||||||
|
) => {
|
||||||
|
const available: Record<string, Record<string, any>[]> = {}
|
||||||
|
const unavailable = Object.keys(sourceTables).filter((table) => {
|
||||||
|
if (metadata[table]) {
|
||||||
|
available[table] = sourceTables[table]
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sourceTables[table].length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return { available, unavailable }
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendUnavailableTableConflicts = (plan: TenantMergePlan, unavailableTables: string[]) => {
|
||||||
|
for (const table of unavailableTables.sort()) {
|
||||||
|
plan.items.push({
|
||||||
|
id: `missing-target-table:${table}`,
|
||||||
|
table,
|
||||||
|
kind: "conflict",
|
||||||
|
sourceKey: "Zieltabelle fehlt",
|
||||||
|
targetKey: null,
|
||||||
|
label: `Tabelle ${table} ist im Zielsystem nicht vorhanden`,
|
||||||
|
defaultDecision: "target",
|
||||||
|
allowedDecisions: ["target"],
|
||||||
|
differences: ["Die erforderliche Tabelle fehlt im Zielschema und muss zuerst per Migration angelegt werden."],
|
||||||
|
sourceRow: {},
|
||||||
|
targetRow: null,
|
||||||
|
})
|
||||||
|
plan.summary.conflict += 1
|
||||||
|
}
|
||||||
|
return plan
|
||||||
|
}
|
||||||
|
|
||||||
const loadTargetRows = async (
|
const loadTargetRows = async (
|
||||||
client: any,
|
client: any,
|
||||||
table: string,
|
table: string,
|
||||||
@@ -124,7 +160,8 @@ export const createTenantMergeDryRunWithClient = async (
|
|||||||
targetTenantId: number
|
targetTenantId: number
|
||||||
): Promise<TenantMergePlan> => {
|
): Promise<TenantMergePlan> => {
|
||||||
const metadata = await loadMergeMetadata(client)
|
const metadata = await loadMergeMetadata(client)
|
||||||
const sourceTables = remapSourceTenant(exportData, targetTenantId)
|
const remappedSourceTables = remapSourceTenant(exportData, targetTenantId)
|
||||||
|
const { available: sourceTables, unavailable } = splitSourceTablesByAvailability(remappedSourceTables, metadata)
|
||||||
const targetTables: Record<string, Record<string, any>[]> = {}
|
const targetTables: Record<string, Record<string, any>[]> = {}
|
||||||
|
|
||||||
for (const [table, sourceRows] of Object.entries(sourceTables)) {
|
for (const [table, sourceRows] of Object.entries(sourceTables)) {
|
||||||
@@ -133,7 +170,7 @@ export const createTenantMergeDryRunWithClient = async (
|
|||||||
targetTables[table] = await loadTargetRows(client, table, sourceRows, tableMetadata, targetTenantId)
|
targetTables[table] = await loadTargetRows(client, table, sourceRows, tableMetadata, targetTenantId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildTenantMergePlan(sourceTables, targetTables, metadata)
|
return appendUnavailableTableConflicts(buildTenantMergePlan(sourceTables, targetTables, metadata), unavailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTenantMergeDryRun = async (
|
export const createTenantMergeDryRun = async (
|
||||||
@@ -214,7 +251,9 @@ const topologicalTableOrder = (tables: string[], metadata: Record<string, MergeD
|
|||||||
const ordered: string[] = []
|
const ordered: string[] = []
|
||||||
while (remaining.size) {
|
while (remaining.size) {
|
||||||
const ready = Array.from(remaining).filter((table) =>
|
const ready = Array.from(remaining).filter((table) =>
|
||||||
metadata[table].foreignKeys.every((foreignKey) => !remaining.has(foreignKey.referencedTable) || foreignKey.referencedTable === table)
|
(metadata[table]?.foreignKeys || []).every((foreignKey) =>
|
||||||
|
!remaining.has(foreignKey.referencedTable) || foreignKey.referencedTable === table
|
||||||
|
)
|
||||||
)
|
)
|
||||||
const next = ready.length ? ready.sort() : [Array.from(remaining).sort()[0]]
|
const next = ready.length ? ready.sort() : [Array.from(remaining).sort()[0]]
|
||||||
for (const table of next) {
|
for (const table of next) {
|
||||||
@@ -240,7 +279,8 @@ export const executeTenantMergeWithClient = async (
|
|||||||
decisions: Record<string, TenantMergeDecision>
|
decisions: Record<string, TenantMergeDecision>
|
||||||
): Promise<TenantMergeExecutionResult> => {
|
): Promise<TenantMergeExecutionResult> => {
|
||||||
const metadata = await loadMergeMetadata(client)
|
const metadata = await loadMergeMetadata(client)
|
||||||
const sourceTables = remapSourceTenant(rawExportData, targetTenantId)
|
const remappedSourceTables = remapSourceTenant(rawExportData, targetTenantId)
|
||||||
|
const { available: sourceTables } = splitSourceTablesByAvailability(remappedSourceTables, metadata)
|
||||||
const preparedExport: TenantFullExport = prepareTenantFullExportRowsForImport({
|
const preparedExport: TenantFullExport = prepareTenantFullExportRowsForImport({
|
||||||
...rawExportData,
|
...rawExportData,
|
||||||
tenantId: targetTenantId,
|
tenantId: targetTenantId,
|
||||||
|
|||||||
@@ -115,16 +115,32 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="space-y-4">
|
<UDashboardNavbar title="Tenant-Import prüfen">
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
<template #left>
|
||||||
<div>
|
<UButton
|
||||||
<h1 class="text-2xl font-semibold">Tenant-Import prüfen</h1>
|
color="neutral"
|
||||||
<p class="text-sm text-gray-500">Dry-Run {{ importId }}</p>
|
variant="outline"
|
||||||
</div>
|
icon="i-heroicons-chevron-left"
|
||||||
<UButton color="neutral" variant="soft" icon="i-heroicons-arrow-left" @click="router.push(`/administration/tenants/${tenantId}`)">
|
@click="router.push(`/administration/tenants/${tenantId}`)"
|
||||||
Zurück zum Tenant
|
>
|
||||||
|
Tenant
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<UBadge color="neutral" variant="soft" class="hidden sm:inline-flex">
|
||||||
|
Dry-Run {{ importId.slice(0, 8) }}
|
||||||
|
</UBadge>
|
||||||
|
</template>
|
||||||
|
</UDashboardNavbar>
|
||||||
|
|
||||||
|
<UDashboardPanelContent>
|
||||||
|
<div class="mx-auto w-full max-w-screen-2xl space-y-6 pb-8">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl font-semibold text-highlighted">Import prüfen und zusammenführen</h1>
|
||||||
|
<p class="mt-1 text-sm text-muted">
|
||||||
|
Prüfe neue, vorhandene und abweichende Datensätze, bevor Änderungen am Tenant ausgeführt werden.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<UAlert
|
<UAlert
|
||||||
title="Noch keine Daten wurden verändert"
|
title="Noch keine Daten wurden verändert"
|
||||||
@@ -201,12 +217,14 @@ onMounted(async () => {
|
|||||||
</p>
|
</p>
|
||||||
<div v-if="item.kind === 'conflict'" class="flex flex-wrap gap-2">
|
<div v-if="item.kind === 'conflict'" class="flex flex-wrap gap-2">
|
||||||
<UButton
|
<UButton
|
||||||
|
v-if="item.allowedDecisions.includes('target')"
|
||||||
size="sm"
|
size="sm"
|
||||||
:color="decisions[item.id] === 'target' ? 'primary' : 'neutral'"
|
:color="decisions[item.id] === 'target' ? 'primary' : 'neutral'"
|
||||||
:variant="decisions[item.id] === 'target' ? 'solid' : 'soft'"
|
:variant="decisions[item.id] === 'target' ? 'solid' : 'soft'"
|
||||||
@click="decisions[item.id] = 'target'"
|
@click="decisions[item.id] = 'target'"
|
||||||
>Ziel behalten</UButton>
|
>Ziel behalten</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
|
v-if="item.allowedDecisions.includes('source')"
|
||||||
size="sm"
|
size="sm"
|
||||||
:color="decisions[item.id] === 'source' ? 'warning' : 'neutral'"
|
:color="decisions[item.id] === 'source' ? 'warning' : 'neutral'"
|
||||||
:variant="decisions[item.id] === 'source' ? 'solid' : 'soft'"
|
:variant="decisions[item.id] === 'source' ? 'solid' : 'soft'"
|
||||||
@@ -244,5 +262,6 @@ onMounted(async () => {
|
|||||||
>Merge ausführen</UButton>
|
>Merge ausführen</UButton>
|
||||||
</div>
|
</div>
|
||||||
</UCard>
|
</UCard>
|
||||||
</div>
|
</div>
|
||||||
|
</UDashboardPanelContent>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user