-
CDC Details
-
-
-
-
- Source |
- {config.source?.name || '-'} |
-
-
- Destination |
- {config.destination?.name || '-'} |
-
-
- Flow Job Name |
- {config.flowJobName} |
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ >
);
}
+
+function numberWithCommas(x: Number): string {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+}
+
+export default CdcDetails;
diff --git a/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx b/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx
new file mode 100644
index 0000000000..8a8901a836
--- /dev/null
+++ b/ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx
@@ -0,0 +1,135 @@
+import { Label } from '@/lib/Label';
+import moment from 'moment';
+import { useEffect, useState } from 'react';
+
+type SyncStatusRow = {
+ batchId: number;
+ startTime: Date;
+ endTime: Date | null;
+ numRows: number;
+};
+
+import aggregateCountsByInterval from './aggregatedCountsByInterval';
+
+const aggregateTypeMap: { [key: string]: string } = {
+ '15min': ' 15 mins',
+ hour: 'Hour',
+ day: 'Day',
+ month: 'Month',
+};
+
+function CdcGraph({ syncs }: { syncs: SyncStatusRow[] }) {
+ let [aggregateType, setAggregateType] = useState('hour');
+ const initialCount: [string, number][] = [];
+ let [counts, setCounts] = useState(initialCount);
+
+ let rows = syncs.map((sync) => ({
+ timestamp: sync.startTime,
+ count: sync.numRows,
+ }));
+
+ useEffect(() => {
+ let counts = aggregateCountsByInterval(rows, aggregateType);
+ counts = counts.slice(0, 29);
+ counts = counts.reverse();
+ setCounts(counts);
+ }, [aggregateType, rows]);
+
+ return (
+
+
+ {['15min', 'hour', 'day', 'month'].map((type) => {
+ return (
+
+ );
+ })}
+
+
+
+
+
+ {counts.map((count, i) => (
+
+ ))}
+
+
+ );
+}
+
+type filterButtonProps = {
+ aggregateType: string;
+ selectedAggregateType: string;
+ setAggregateType: Function;
+};
+function FilterButton({
+ aggregateType,
+ selectedAggregateType,
+ setAggregateType,
+}: filterButtonProps): React.ReactNode {
+ return (
+
+ );
+}
+
+function formatGraphLabel(date: Date, aggregateType: String): string {
+ switch (aggregateType) {
+ case '15min':
+ return moment(date).format('MMM Do HH:mm');
+ case 'hour':
+ return moment(date).format('MMM Do HH:mm');
+ case 'day':
+ return moment(date).format('MMM Do');
+ case 'month':
+ return moment(date).format('MMM yy');
+ default:
+ return 'Unknown aggregate type: ' + aggregateType;
+ }
+}
+
+type GraphBarProps = {
+ count: number;
+ label: string;
+};
+
+function GraphBar({ label, count }: GraphBarProps) {
+ let color =
+ count && count > 0 ? 'bg-positive-fill-normal' : 'bg-base-border-subtle';
+ let classNames = `relative w-10 h-24 rounded ${color}`;
+ return (
+
+
+
+
{label}
+
{numberWithCommas(count)}
+
+
+
+ );
+}
+
+function numberWithCommas(x: number): string {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+}
+
+export default CdcGraph;
diff --git a/ui/app/mirrors/edit/[mirrorId]/page.tsx b/ui/app/mirrors/edit/[mirrorId]/page.tsx
index e2c5e6596a..0bd23dddb0 100644
--- a/ui/app/mirrors/edit/[mirrorId]/page.tsx
+++ b/ui/app/mirrors/edit/[mirrorId]/page.tsx
@@ -1,10 +1,11 @@
+import prisma from '@/app/utils/prisma';
import { MirrorStatusResponse } from '@/grpc_generated/route';
import { Header } from '@/lib/Header';
import { LayoutMain } from '@/lib/Layout';
import { GetFlowHttpAddressFromEnv } from '@/rpc/http';
import { redirect } from 'next/navigation';
import { Suspense } from 'react';
-import { CDCMirror } from './cdc';
+import CdcDetails from './cdcDetails';
import SyncStatus from './syncStatus';
export const dynamic = 'force-dynamic';
@@ -44,16 +45,36 @@ export default async function EditMirror({
redirect(`/mirrors/status/qrep/${mirrorId}`);
}
+ let syncs = await prisma.cdc_batches.findMany({
+ where: {
+ flow_name: mirrorId,
+ start_time: {
+ not: undefined,
+ },
+ },
+ orderBy: {
+ start_time: 'desc',
+ },
+ });
+
+ const rows = syncs.map((sync) => ({
+ batchId: sync.id,
+ startTime: sync.start_time,
+ endTime: sync.end_time,
+ numRows: sync.rows_in_batch,
+ }));
+
return (
}>
{mirrorStatus.cdcStatus && (
-
)}
+ {syncStatusChild}
);
diff --git a/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx b/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx
index 80cb35701c..f1fea81f17 100644
--- a/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx
+++ b/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx
@@ -29,5 +29,9 @@ export default async function SyncStatus({ flowJobName }: SyncStatusProps) {
numRows: sync.rows_in_batch,
}));
- return
;
+ return (
+
+
+
+ );
}
diff --git a/ui/tailwind.config.ts b/ui/tailwind.config.ts
index 49e081636c..afde1d67cf 100644
--- a/ui/tailwind.config.ts
+++ b/ui/tailwind.config.ts
@@ -16,6 +16,7 @@ module.exports = {
current: 'currentColor',
extend: {
colors: {
+ ...appThemeColors,
blue: {
500: appThemeColors.accent.fill.normal,
},