diff --git a/ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.ts b/ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.ts index 1da7ed132e..bdadb85b8d 100644 --- a/ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.ts +++ b/ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.ts @@ -21,17 +21,13 @@ function aggregateCountsByInterval( timeUnit = 'YYYY-MM'; break; case 'day': + case 'week': timeUnit = 'YYYY-MM-DD'; break; case '1min': - timeUnit = 'YYYY-MM-DD HH:mm'; - break; case '5min': timeUnit = 'YYYY-MM-DD HH:mm'; break; - case 'week': - timeUnit = 'YYYY-MM-DD'; - break; default: throw new Error('Invalid interval provided'); } diff --git a/ui/app/mirrors/edit/[mirrorId]/cdc.tsx b/ui/app/mirrors/edit/[mirrorId]/cdc.tsx index 36b7d6582e..14b80d54c9 100644 --- a/ui/app/mirrors/edit/[mirrorId]/cdc.tsx +++ b/ui/app/mirrors/edit/[mirrorId]/cdc.tsx @@ -96,7 +96,7 @@ type SnapshotStatusProps = { status: SnapshotStatus; }; -const ROWS_PER_PAGE = 6; +const ROWS_PER_PAGE = 10; export const SnapshotStatusTable = ({ status }: SnapshotStatusProps) => { const allRows = status.clones.map(summarizeTableClone); const [sortField, setSortField] = useState< @@ -107,9 +107,10 @@ export const SnapshotStatusTable = ({ status }: SnapshotStatusProps) => { const startRow = (currentPage - 1) * ROWS_PER_PAGE; const endRow = startRow + ROWS_PER_PAGE; - const [displayedRows, setDisplayedRows] = useState( + const [displayedRows, setDisplayedRows] = useState(() => allRows.slice(startRow, endRow) ); + const handlePrevPage = () => { if (currentPage > 1) { setCurrentPage(currentPage - 1); @@ -186,7 +187,6 @@ export const SnapshotStatusTable = ({ status }: SnapshotStatusProps) => { (val?.value as 'cloneStartTime' | 'avgTimePerPartition') ?? 'cloneStartTime'; setSortField(sortVal); - handleSort(sortVal); }} value={{ value: sortField, diff --git a/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx b/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx index 21ac59d4c8..f52450ade0 100644 --- a/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx +++ b/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx @@ -1,4 +1,5 @@ 'use client'; +import MirrorInfo from '@/components/MirrorInfo'; import PeerButton from '@/components/PeerComponent'; import TimeLabel from '@/components/TimeComponent'; import { FlowConnectionConfigs } from '@/grpc_generated/flow'; @@ -7,7 +8,8 @@ import { Badge } from '@/lib/Badge'; import { Icon } from '@/lib/Icon'; import { Label } from '@/lib/Label'; import moment from 'moment'; -import CdcGraph from './cdcGraph'; +import MirrorValues from './configValues'; +import TablePairs from './tablePairs'; type SyncStatusRow = { batchId: number; @@ -113,109 +115,19 @@ function CdcDetails({ syncs, createdAt, mirrorConfig }: props) { - - -
- -
- -
-
- -
-
- - -
-
- - -
-
- - -
-
- - -
+
+
- - - - - - - - - {tablesSynced?.map((table) => ( - - - - - ))} - -
- Source Table - - Destination Table -
- {table.sourceTableIdentifier} - - {table.destinationTableIdentifier} -
+ + ); } -function numberWithCommas(x: Number): string { +export function numberWithCommas(x: any): string { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } diff --git a/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx b/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx index 94ea57403d..6c5e9988c1 100644 --- a/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx +++ b/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx @@ -1,3 +1,4 @@ +'use client'; import { Label } from '@/lib/Label'; import moment from 'moment'; import { useEffect, useState } from 'react'; @@ -11,16 +12,6 @@ type SyncStatusRow = { import aggregateCountsByInterval from './aggregatedCountsByInterval'; -const aggregateTypeMap: { [key: string]: string } = { - '15min': ' 15 mins', - '5min': '5 mins', - '1min': '1 min', - hour: 'Hour', - day: 'Day', - month: 'Month', - week: 'Week', -}; - function CdcGraph({ syncs }: { syncs: SyncStatusRow[] }) { let [aggregateType, setAggregateType] = useState('hour'); const initialCount: [string, number][] = []; diff --git a/ui/app/mirrors/edit/[mirrorId]/configValues.ts b/ui/app/mirrors/edit/[mirrorId]/configValues.ts new file mode 100644 index 0000000000..7f5172d1cd --- /dev/null +++ b/ui/app/mirrors/edit/[mirrorId]/configValues.ts @@ -0,0 +1,57 @@ +import { FlowConnectionConfigs, QRepSyncMode } from '@/grpc_generated/flow'; + +const syncModeToLabel = (mode: QRepSyncMode) => { + switch (mode) { + case QRepSyncMode.QREP_SYNC_MODE_STORAGE_AVRO: + return 'AVRO'; + case QRepSyncMode.QREP_SYNC_MODE_MULTI_INSERT: + return 'Copy with Binary'; + default: + return 'AVRO'; + } +}; +const MirrorValues = (mirrorConfig: FlowConnectionConfigs | undefined) => { + return [ + { + value: `${mirrorConfig?.maxBatchSize} rows`, + label: 'Pull Batch Size', + }, + { + value: `${mirrorConfig?.snapshotNumRowsPerPartition} rows`, + label: 'Snapshot Rows Per Partition', + }, + { + value: `${mirrorConfig?.snapshotNumTablesInParallel} table(s)`, + label: 'Snapshot Tables In Parallel', + }, + { + value: `${mirrorConfig?.snapshotMaxParallelWorkers} worker(s)`, + label: 'Snapshot Parallel Tables', + }, + { + value: `${syncModeToLabel(mirrorConfig?.cdcSyncMode!)} mode`, + label: 'CDC Sync Mode', + }, + { + value: `${syncModeToLabel(mirrorConfig?.snapshotSyncMode!)} mode`, + label: 'Snapshot Sync Mode', + }, + { + value: `${ + mirrorConfig?.cdcStagingPath?.length + ? mirrorConfig?.cdcStagingPath + : 'Local' + }`, + label: 'CDC Staging Path', + }, + { + value: `${ + mirrorConfig?.snapshotStagingPath?.length + ? mirrorConfig?.snapshotStagingPath + : 'Local' + }`, + label: 'Snapshot Staging Path', + }, + ]; +}; +export default MirrorValues; diff --git a/ui/app/mirrors/edit/[mirrorId]/page.tsx b/ui/app/mirrors/edit/[mirrorId]/page.tsx index 99a5168cee..756684b854 100644 --- a/ui/app/mirrors/edit/[mirrorId]/page.tsx +++ b/ui/app/mirrors/edit/[mirrorId]/page.tsx @@ -33,13 +33,6 @@ export default async function EditMirror({ return
No mirror status found!
; } - let syncStatusChild = <>; - if (mirrorStatus.cdcStatus) { - syncStatusChild = ; - } else { - redirect(`/mirrors/status/qrep/${mirrorId}`); - } - let createdAt = await prisma.flows.findFirst({ select: { created_at: true, @@ -61,6 +54,16 @@ export default async function EditMirror({ }, }); + let syncStatusChild = <>; + if (mirrorStatus.cdcStatus) { + let rowsSynced = syncs.reduce((acc, sync) => acc + sync.rows_in_batch, 0); + syncStatusChild = ( + + ); + } else { + redirect(`/mirrors/status/qrep/${mirrorId}`); + } + const rows = syncs.map((sync) => ({ batchId: sync.id, startTime: sync.start_time, diff --git a/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx b/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx index f1fea81f17..0a481241ca 100644 --- a/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx +++ b/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx @@ -1,11 +1,20 @@ import prisma from '@/app/utils/prisma'; +import { Label } from '@/lib/Label'; +import CdcGraph from './cdcGraph'; import { SyncStatusTable } from './syncStatusTable'; +function numberWithCommas(x: Number): string { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); +} type SyncStatusProps = { flowJobName: string | undefined; + rowsSynced: Number; }; -export default async function SyncStatus({ flowJobName }: SyncStatusProps) { +export default async function SyncStatus({ + flowJobName, + rowsSynced, +}: SyncStatusProps) { if (!flowJobName) { return
Flow job name not provided!
; } @@ -31,6 +40,20 @@ export default async function SyncStatus({ flowJobName }: SyncStatusProps) { return (
+
+
+ +
+
+ +
+
+ +
+ +
); diff --git a/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx b/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx index fb4f6bfd9c..9b6ff1bb5b 100644 --- a/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx +++ b/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx @@ -48,7 +48,7 @@ function TimeWithDurationOrRunning({ } } -const ROWS_PER_PAGE = 6; +const ROWS_PER_PAGE = 10; const sortOptions = [ { value: 'startTime', label: 'Start Time' }, { value: 'endTime', label: 'End Time' }, @@ -143,7 +143,6 @@ export const SyncStatusTable = ({ rows }: SyncStatusTableProps) => { (val?.value as 'startTime' | 'endTime' | 'numRows') ?? 'startTime'; setSortField(sortVal); - handleSort(sortVal); }} defaultValue={{ value: 'startTime', label: 'Start Time' }} /> diff --git a/ui/app/mirrors/edit/[mirrorId]/tablePairs.tsx b/ui/app/mirrors/edit/[mirrorId]/tablePairs.tsx new file mode 100644 index 0000000000..24d2f37f80 --- /dev/null +++ b/ui/app/mirrors/edit/[mirrorId]/tablePairs.tsx @@ -0,0 +1,83 @@ +'use client'; +import SearchBar from '@/components/Search'; +import { TableMapping } from '@/grpc_generated/flow'; +import { useState } from 'react'; + +const TablePairs = ({ tables }: { tables?: TableMapping[] }) => { + const [shownTables, setShownTables] = useState( + tables + ); + if (tables) + return ( + <> +
+ + tables.filter((table: TableMapping) => { + return ( + table.sourceTableIdentifier.includes(query) || + table.destinationTableIdentifier.includes(query) + ); + }) + } + /> +
+ + + + + + + + + {shownTables?.map((table) => ( + + + + + ))} + +
+ Source Table + + Destination Table +
+ {table.sourceTableIdentifier} + + {table.destinationTableIdentifier} +
+ + ); +}; + +export default TablePairs; diff --git a/ui/components/DropDialog.tsx b/ui/components/DropDialog.tsx index f3de3c283f..2de31d9c55 100644 --- a/ui/components/DropDialog.tsx +++ b/ui/components/DropDialog.tsx @@ -79,7 +79,7 @@ export const DropDialog = ({ return ( diff --git a/ui/components/MirrorInfo.tsx b/ui/components/MirrorInfo.tsx new file mode 100644 index 0000000000..e66f9d5345 --- /dev/null +++ b/ui/components/MirrorInfo.tsx @@ -0,0 +1,65 @@ +'use client'; +import { Button } from '@/lib/Button'; +import { Dialog, DialogClose } from '@/lib/Dialog'; +import { Icon } from '@/lib/Icon'; +import { Label } from '@/lib/Label'; + +interface InfoPopoverProps { + configs: { + label: string; + value?: string | number; + }[]; +} + +const MirrorInfo = ({ configs }: InfoPopoverProps) => { + return ( + + + + } + > +
+
+ + + + +
+ + + {configs.map((config, index) => ( + + + + + ))} + +
+ + + +
+
+
+ ); +}; + +export default MirrorInfo; diff --git a/ui/lib/Dialog/Dialog.tsx b/ui/lib/Dialog/Dialog.tsx index 78c5b14b85..51a0a65264 100644 --- a/ui/lib/Dialog/Dialog.tsx +++ b/ui/lib/Dialog/Dialog.tsx @@ -7,6 +7,7 @@ import { DialogContent, DialogContentProps } from './DialogContent'; type DialogProps = RadixDialog.DialogProps & { triggerButton: RenderObject; + noInteract: boolean; } & PropsWithChildren & DialogContentProps; @@ -14,6 +15,7 @@ export function Dialog({ triggerButton, size, children, + noInteract, ...rootProps }: DialogProps) { const TriggerButton = isDefined(triggerButton) && triggerButton; @@ -23,8 +25,17 @@ export function Dialog({ {TriggerButton} e.preventDefault()} + onPointerDownOutside={(e) => { + if (noInteract) e.preventDefault(); + }} size={size} + style={{ + position: 'fixed', + left: '50%', + top: '50%', + transform: 'translate(-0%, -50%)', + boxShadow: '0px 2px 3px rgba(0,0,0,0.2)', + }} > {children} diff --git a/ui/lib/Dialog/DialogContent.styles.ts b/ui/lib/Dialog/DialogContent.styles.ts index c250e28acf..fdcf214583 100644 --- a/ui/lib/Dialog/DialogContent.styles.ts +++ b/ui/lib/Dialog/DialogContent.styles.ts @@ -26,6 +26,9 @@ const sizes = { width: ${({ theme }) => theme.size.xxLarge}; ${css(({ theme }) => theme.dropShadow.xxLarge)}; `, + auto: css` + width: fit-content; + `, }; export type DialogSize = keyof typeof sizes;