dry run migration
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
import type {
|
||||
DashboardMetadata,
|
||||
DashboardSelection,
|
||||
MigrationDryRunResult,
|
||||
} from "../../types/dashboard";
|
||||
import { t } from "$lib/i18n";
|
||||
import { Button, Card, PageHeader } from "$lib/ui";
|
||||
@@ -44,6 +45,8 @@
|
||||
let mappings: any[] = [];
|
||||
let suggestions: any[] = [];
|
||||
let fetchingDbs = false;
|
||||
let dryRunLoading = false;
|
||||
let dryRunResult: MigrationDryRunResult | null = null;
|
||||
|
||||
// UI State for Modals
|
||||
let showLogViewer = false;
|
||||
@@ -253,6 +256,7 @@
|
||||
|
||||
error = "";
|
||||
try {
|
||||
dryRunResult = null;
|
||||
const selection: DashboardSelection = {
|
||||
selected_ids: selectedDashboardIds,
|
||||
source_env_id: sourceEnvId,
|
||||
@@ -296,6 +300,57 @@
|
||||
}
|
||||
}
|
||||
// [/DEF:startMigration:Function]
|
||||
|
||||
// [DEF:startDryRun:Function]
|
||||
/**
|
||||
* @purpose Builds pre-flight diff and risk summary without applying migration.
|
||||
* @pre source/target environments and selected dashboards are valid.
|
||||
* @post dryRunResult is populated with backend response.
|
||||
* @UX_STATE: Idle -> Dry Run button is enabled when selection is valid.
|
||||
* @UX_STATE: Loading -> Dry Run button shows "Dry Run..." and stays disabled.
|
||||
* @UX_STATE: Error -> error banner is displayed and dryRunResult resets to null.
|
||||
* @UX_FEEDBACK: User sees summary cards + risk block + JSON details after success.
|
||||
* @UX_RECOVERY: User can adjust selection and press Dry Run again.
|
||||
*/
|
||||
async function startDryRun() {
|
||||
if (!sourceEnvId || !targetEnvId) {
|
||||
error =
|
||||
$t.migration?.select_both_envs ||
|
||||
"Please select both source and target environments.";
|
||||
return;
|
||||
}
|
||||
if (sourceEnvId === targetEnvId) {
|
||||
error =
|
||||
$t.migration?.different_envs ||
|
||||
"Source and target environments must be different.";
|
||||
return;
|
||||
}
|
||||
if (selectedDashboardIds.length === 0) {
|
||||
error =
|
||||
$t.migration?.select_dashboards ||
|
||||
"Please select at least one dashboard to migrate.";
|
||||
return;
|
||||
}
|
||||
|
||||
error = "";
|
||||
dryRunLoading = true;
|
||||
try {
|
||||
const selection: DashboardSelection = {
|
||||
selected_ids: selectedDashboardIds,
|
||||
source_env_id: sourceEnvId,
|
||||
target_env_id: targetEnvId,
|
||||
replace_db_config: replaceDb,
|
||||
fix_cross_filters: fixCrossFilters,
|
||||
};
|
||||
dryRunResult = await api.postApi("/migration/dry-run", selection);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
dryRunResult = null;
|
||||
} finally {
|
||||
dryRunLoading = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:startDryRun:Function]
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
@@ -417,15 +472,70 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
on:click={startMigration}
|
||||
disabled={!sourceEnvId ||
|
||||
!targetEnvId ||
|
||||
sourceEnvId === targetEnvId ||
|
||||
selectedDashboardIds.length === 0}
|
||||
>
|
||||
{$t.migration?.start }
|
||||
</Button>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
on:click={startDryRun}
|
||||
disabled={!sourceEnvId ||
|
||||
!targetEnvId ||
|
||||
sourceEnvId === targetEnvId ||
|
||||
selectedDashboardIds.length === 0 ||
|
||||
dryRunLoading}
|
||||
>
|
||||
{dryRunLoading ? "Dry Run..." : "Dry Run"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
on:click={startMigration}
|
||||
disabled={!sourceEnvId ||
|
||||
!targetEnvId ||
|
||||
sourceEnvId === targetEnvId ||
|
||||
selectedDashboardIds.length === 0}
|
||||
>
|
||||
{$t.migration?.start || "Apply"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if dryRunResult}
|
||||
<div class="mt-6 rounded-md border border-slate-200 bg-slate-50 p-4 space-y-3">
|
||||
<h3 class="text-base font-semibold">Pre-flight Diff</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
|
||||
<div class="rounded border border-slate-200 bg-white p-3">
|
||||
<div class="font-medium mb-1">Dashboards</div>
|
||||
<div>create: {dryRunResult.summary.dashboards.create}</div>
|
||||
<div>update: {dryRunResult.summary.dashboards.update}</div>
|
||||
<div>delete: {dryRunResult.summary.dashboards.delete}</div>
|
||||
</div>
|
||||
<div class="rounded border border-slate-200 bg-white p-3">
|
||||
<div class="font-medium mb-1">Charts</div>
|
||||
<div>create: {dryRunResult.summary.charts.create}</div>
|
||||
<div>update: {dryRunResult.summary.charts.update}</div>
|
||||
<div>delete: {dryRunResult.summary.charts.delete}</div>
|
||||
</div>
|
||||
<div class="rounded border border-slate-200 bg-white p-3">
|
||||
<div class="font-medium mb-1">Datasets</div>
|
||||
<div>create: {dryRunResult.summary.datasets.create}</div>
|
||||
<div>update: {dryRunResult.summary.datasets.update}</div>
|
||||
<div>delete: {dryRunResult.summary.datasets.delete}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded border border-slate-200 bg-white p-3 text-sm">
|
||||
<div class="font-medium mb-1">Risk</div>
|
||||
<div>
|
||||
score: {dryRunResult.risk.score}, level: {dryRunResult.risk.level}
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
issues: {dryRunResult.risk.items.length}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="rounded border border-slate-200 bg-white p-3">
|
||||
<summary class="cursor-pointer font-medium">Diff JSON</summary>
|
||||
<pre class="mt-2 max-h-72 overflow-auto text-xs">{JSON.stringify(dryRunResult, null, 2)}</pre>
|
||||
</details>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,5 +16,48 @@ export interface DashboardSelection {
|
||||
source_env_id: string;
|
||||
target_env_id: string;
|
||||
replace_db_config?: boolean;
|
||||
fix_cross_filters?: boolean;
|
||||
}
|
||||
// [/DEF:DashboardTypes:Module]
|
||||
|
||||
export interface DiffObjectRef {
|
||||
uuid: string;
|
||||
title?: string;
|
||||
target_title?: string;
|
||||
}
|
||||
|
||||
export interface DiffBucket {
|
||||
create: DiffObjectRef[];
|
||||
update: DiffObjectRef[];
|
||||
delete: DiffObjectRef[];
|
||||
}
|
||||
|
||||
export interface DryRunRiskItem {
|
||||
code: string;
|
||||
severity: "low" | "medium" | "high";
|
||||
object_type: string;
|
||||
object_uuid: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface MigrationDryRunResult {
|
||||
generated_at: string;
|
||||
selection: DashboardSelection;
|
||||
selected_dashboard_titles: string[];
|
||||
diff: {
|
||||
dashboards: DiffBucket;
|
||||
charts: DiffBucket;
|
||||
datasets: DiffBucket;
|
||||
};
|
||||
summary: {
|
||||
dashboards: Record<"create" | "update" | "delete", number>;
|
||||
charts: Record<"create" | "update" | "delete", number>;
|
||||
datasets: Record<"create" | "update" | "delete", number>;
|
||||
selected_dashboards: number;
|
||||
};
|
||||
risk: {
|
||||
score: number;
|
||||
level: "low" | "medium" | "high";
|
||||
items: DryRunRiskItem[];
|
||||
};
|
||||
}
|
||||
// [/DEF:DashboardTypes:Module]
|
||||
|
||||
Reference in New Issue
Block a user