Resolve migration reconciliation items

This commit is contained in:
ZacharyZhang-NY committed 2026-05-23 22:46:50 -04:00
1 parent 971bdd95e6
commit 16855b696a
9 files changed
+125 -3

No files matched your search

@@ -209,6 +209,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
employeeId={employee.id}
reports={migrationReports}
rollbackSnapshots={rollbackSnapshots}
reconciliationItems={snapshot.migrationReconciliationItems.filter((item) => item.orgId === orgId)}
actions={actions}
/>
)}
@@ -1,7 +1,7 @@
import { useState, type FormEvent } from "react";
import { Button, Field, Icon, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import type { MigrationReportRecord, RollbackSnapshotRecord } from "../../../../shared/domain";
import type { MigrationReconciliationItemRecord, MigrationReportRecord, RollbackSnapshotRecord } from "../../../../shared/domain";
import type { SnapshotActions } from "../../app/useSnapshot";
import { formatRelative } from "../types";
@@ -18,12 +18,14 @@ export function MigrationPanel({
employeeId,
reports,
rollbackSnapshots,
reconciliationItems,
actions,
}: {
orgId: string;
employeeId: string;
reports: MigrationReportRecord[];
rollbackSnapshots: RollbackSnapshotRecord[];
reconciliationItems: MigrationReconciliationItemRecord[];
actions: SnapshotActions;
}) {
const [open, setOpen] = useState(false);
@@ -78,6 +80,22 @@ export function MigrationPanel({
/>
)) : <div style={{ padding: 16 }}><Pill>0 rollback snapshots</Pill></div>}
</section>
<section className="card" style={{ marginTop: 16, padding: 0, overflow: "hidden" }}>
<div className="row" style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-soft)" }}>
<Icon name="list" size={14} color="var(--text-3)" />
<div style={{ fontSize: 13, fontWeight: 600 }}>Reconciliation</div>
</div>
{reconciliationItems.length > 0 ? reconciliationItems.map((item, index) => (
<ReconciliationRow
key={item.id}
orgId={orgId}
employeeId={employeeId}
item={item}
actions={actions}
last={index === reconciliationItems.length - 1}
/>
)) : <div style={{ padding: 16 }}><Pill>0 reconciliation items</Pill></div>}
</section>
<Dialog title="Migration report" open={open} submitLabel="Create" onClose={() => setOpen(false)} onSubmit={submit}>
<Field label="Source database path"><input className="input" name="sourceDatabasePath" required /></Field>
</Dialog>
@@ -167,6 +185,43 @@ function RollbackRow({
);
}
function ReconciliationRow({
orgId,
employeeId,
item,
actions,
last,
}: {
orgId: string;
employeeId: string;
item: MigrationReconciliationItemRecord;
actions: SnapshotActions;
last: boolean;
}) {
const resolved = item.state === "resolved";
return (
<div
className="table-row"
style={{ gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr) 86px 112px 84px", borderBottom: last ? "none" : "1px solid var(--border-soft)" }}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.targetType} · {item.targetId}</div>
<div style={{ fontSize: 11, color: "var(--text-3)" }}>{formatRelative(item.createdAt)}</div>
</div>
<div style={{ fontSize: 12, color: "var(--text-2)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.reason}</div>
<Pill tone={resolved ? "green" : "amber"}>{item.state}</Pill>
<Pill>{item.sourceEventId.slice(0, 12)}</Pill>
<Button
size="small"
disabled={resolved}
onClick={() => void actions.run(() => window.agentCompany.resolveMigrationReconciliation({ orgId, reconciliationItemId: item.id, employeeId }))}
>
{resolved ? "Resolved" : "Resolve"}
</Button>
</div>
);
}
function parseReport(reportJson: string): ParsedReport {
const parsed = JSON.parse(reportJson) as ParsedReport;
if (!parsed.tableCounts || !parsed.tableChecksums || !parsed.idMaps || !Array.isArray(parsed.failures)) {