Skip to content

Commit

Permalink
feature: add screen to view mirror activity
Browse files Browse the repository at this point in the history
  • Loading branch information
Pankaj Bhageria authored and Pankaj Bhageria committed Nov 8, 2023
1 parent fb90f2a commit 48a1995
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 6 deletions.
126 changes: 126 additions & 0 deletions ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
function aggregateCountsByInterval(timestamps, interval, startTimestamp, endTimestamp) {
//startTimestamp = roundUpToNearest15Minutes(startTimestamp);
// Define the time unit based on the provided interval
let timeUnit;
switch (interval) {
case 'hour':
timeUnit = 'YYYY-MM-DD HH:00:00';
break;
case '15min':
timeUnit = 'YYYY-MM-DD HH:mm';
break;
case 'month':
timeUnit = 'YYYY-MM';
break;
case 'day':
timeUnit = 'YYYY-MM-DD';
break;
default:
throw new Error('Invalid interval provided');
}

// Create an object to store the aggregated counts
const aggregatedCounts = {};

// Iterate through the timestamps and populate the aggregatedCounts object
for (let { timestamp, count } of timestamps) {
timestamp = roundUpToNearest15Minutes(timestamp);
const date = new Date(timestamp);
const formattedTimestamp = formatTimestamp(date, timeUnit);

if (!aggregatedCounts[formattedTimestamp]) {
aggregatedCounts[formattedTimestamp] = 0;
}

aggregatedCounts[formattedTimestamp] += count;
}


// Get the start and end timestamps or use the earliest and latest timestamps from the data
//const firstTimestamp = new Date(timestamps[0].timestamp);
//const lastTimestamp = new Date(timestamps[timestamps.length - 1].timestamp);
//startTimestamp = startTimestamp ? new Date(startTimestamp) : firstTimestamp;
//endTimestamp = endTimestamp ? new Date(endTimestamp) : lastTimestamp;

// Create an array of intervals between the start and end timestamps
const intervals = [];
//let currentTimestamp = new Date(startTimestamp);
let currentTimestamp = new Date();

if(interval === "15min"){
currentTimestamp = roundUpToNearest15Minutes(currentTimestamp);
}

while (intervals.length < 30) {
intervals.push(formatTimestamp(currentTimestamp, timeUnit));
if (interval === 'hour') {
currentTimestamp.setHours(currentTimestamp.getHours() - 1);
} else if (interval === '15min') {
currentTimestamp.setMinutes(currentTimestamp.getMinutes() - 15);
} else if (interval === 'month') {
currentTimestamp.setMonth(currentTimestamp.getMonth() - 1);
} else if(interval === 'day'){
currentTimestamp.setDate(currentTimestamp.getDate() - 1);
}
}

// Populate the result array with intervals and counts
const resultArray = intervals.map((interval) => [interval, aggregatedCounts[interval] || 0]);
return resultArray;
}

function roundUpToNearest15Minutes(date) {
const minutes = date.getMinutes();
const remainder = minutes % 15;

if (remainder > 0) {
// Round up to the nearest 15 minutes
date.setMinutes(minutes + (15 - remainder));
}

// Reset seconds and milliseconds to zero to maintain the same time
date.setSeconds(0);
date.setMilliseconds(0);

return date;
}

// Helper function to format a timestamp
function formatTimestamp(date, format) {
const year = date.getFullYear();
const month = padZero(date.getMonth() + 1); // Months are zero-based
const day = padZero(date.getDate());
const hour = padZero(date.getHours());
const minutes = padZero(date.getMinutes());

return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hour)
.replace('mm', minutes);
}

// Helper function to pad single digits with leading zeros
function padZero(number) {
return number < 10 ? `0${number}` : `${number}`;
}

// // Example usage
// const timestamps = [
// { timestamp: '2023-11-03T09:30:00', count: 5 },
// { timestamp: '2023-11-03T10:15:00', count: 10 },
// { timestamp: '2023-11-03T11:45:00', count: 7 },
// // Add more timestamps as needed
// ];

// const resultByHour = aggregateCountsByInterval(timestamps, 'hour', '2023-11-03T09:00:00', '2023-11-03T11:00:00');
// const resultBy15Min = aggregateCountsByInterval(timestamps, '15min', '2023-11-03T09:00:00', '2023-11-03T11:30:00');
// const resultByMonth = aggregateCountsByInterval(timestamps, 'month', '2023-11-01', '2023-11-03');

// console.log('Counts by Hour:', resultByHour);
// console.log('Counts by 15 Minutes:', resultBy15Min);
// console.log('Counts by Month:', resultByMonth);


export default aggregateCountsByInterval
82 changes: 82 additions & 0 deletions ui/app/mirrors/edit/[mirrorId]/cdcGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Label } from '@/lib/Label';
import { useState, useEffect } from 'react';
import { format } from 'date-fns'

type SyncStatusRow = {
batchId: number;
startTime: Date;
endTime: Date | null;
numRows: number;
};

import aggregateCountsByInterval from './aggregatedCountsByInterval'

function CdcGraph({syncs}:{syncs:SyncStatusRow[]}) {

let [aggregateType,setAggregateType] = useState('hour');
let [counts,setCounts] = useState([]);


let rows = syncs.map((sync) => ({
timestamp: sync.startTime,
count: sync.numRows,
}))

useEffect(()=>{
let counts = aggregateCountsByInterval(rows,aggregateType, undefined, new Date());
counts = counts.slice(0,29)
counts = counts.reverse();
setCounts(counts)

},[aggregateType, rows])

return <div>
<div className='float-right'>
<button className={aggregateType === "15min" ? "bg-gray-200 px-1 mx-1 rounded-md":"px-1 mx-1"} onClick={()=>setAggregateType('15min')}>15 mins</button>
<button className={aggregateType === "hour" ? "bg-gray-200 px-1 mx-1 rounded-md":"px-1 mx-1"} onClick={()=>setAggregateType('hour')}>Hour</button>
<button className={aggregateType === "day" ? "bg-gray-200 px-1 mx-1 rounded-md":"px-1 mx-1"} onClick={()=>setAggregateType('day')}>Day</button>
<button className={aggregateType === "month" ? "bg-gray-200 px-1 mx-1 rounded-md":"px-1 mx-1"} onClick={()=>setAggregateType('month')}>Month</button>
</div>
<div><Label variant="body">Sync history</Label></div>

<div className='flex space-x-2 justify-left ml-2'>
{counts.map((count,i)=><GraphBar key={i} label={formatGraphLabel(new Date(count[0]),aggregateType)} count={count[1]}/>)}
</div>
</div>
}

type GraphBarProps = {
count: number | undefined;
label: string
};


function formatGraphLabel(date:Date, aggregateType:String) {
switch (aggregateType) {
case "15min":
return format(date, 'MMM dd HH:mm');
case "hour":
return format(date, 'MMM dd HH:mm');
case "day":
return format(date, 'MMM dd');
case "month":
return format(date, 'MMM yy');
}
}

function GraphBar({label,count}:GraphBarProps){
let color = count && count >0 ? 'bg-green-500' : 'bg-gray-500';
let classNames = `relative w-10 h-24 rounded ${color}`;
return <div className={"group"}>
<div className={classNames}>
<div className="group-hover:opacity-100 transition-opacity bg-gray-800 px-1 text-sm text-gray-100 rounded-md absolute left-1/2
-translate-x-1/2 translate-y-full opacity-0 m-4 mx-auto w-28 z-10 text-center">
<div>{label}</div>
<div>{count}</div>
</div>
</div>
</div>
}


export default CdcGraph;
79 changes: 79 additions & 0 deletions ui/app/mirrors/edit/[mirrorId]/newCdcDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client'
import { Label } from '@/lib/Label';
import {formatDistance } from 'date-fns'
import { Badge } from '@/lib/Badge';
import { Icon } from '@/lib/Icon';
import {Action} from '@/lib/Action';
import { FlowConnectionConfigs } from '@/grpc_generated/flow';
import CdcGraph from './cdcGraph'



type SyncStatusRow = {
batchId: number;
startTime: Date;
endTime: Date | null;
numRows: number;
};

type props = {
syncs: SyncStatusRow[];
mirrorConfig: FlowConnectionConfigs | undefined;
};
function CdcDetails({ syncs,mirrorConfig }:props) {

let lastSyncedAt = formatDistance(syncs[0]?.startTime, new Date(), { addSuffix: true })

let rowsSynced = syncs.reduce((acc, sync) => acc + sync.numRows, 0)

return (
<>
<div className='mt-10'>
<div className="flex flex-row">
<div className="basis-1/4 md:basis-1/3">
<div><Label variant="subheadline" colorName='lowContrast'>Status</Label></div>
<div><Label variant="body">
<Badge variant='positive' key={1}>
<Icon name='play_circle' />
Active
</Badge>
</Label>
</div>
</div>
<div className="basis-1/4 md:basis-1/3">
<div><Label variant="subheadline" colorName='lowContrast'>Mirror Type</Label></div>
<div><Label variant="body">CDC</Label></div>
</div>
<div className="basis-1/4 md:basis-1/3">
<div><Label variant="subheadline" colorName='lowContrast'>Source</Label></div>
<div><Action href={"/peers/"+mirrorConfig?.source?.name}>{mirrorConfig?.source?.name}</Action></div>
</div>
<div className="basis-1/4 md:basis-1/3">
<div><Label variant="subheadline" colorName='lowContrast'>Destination</Label></div>
<div><Action href={"/peers/"+ mirrorConfig?.destination?.name}>{mirrorConfig?.destination?.name}</Action></div>
</div>
</div>
<div className="flex flex-row mt-10">
<div className="basis-1/4">
<div><Label variant="subheadline" colorName='lowContrast'>Last Sync</Label></div>
<div><Label variant="body">{lastSyncedAt}</Label></div>
</div>
<div className="basis-1/4">
<div><Label variant="subheadline" colorName='lowContrast'>Rows synced</Label></div>
<div><Label variant="body">{numberWithCommas(rowsSynced)}</Label></div>
</div>
</div>
</div>
<div className='mt-10'>
<CdcGraph syncs={syncs}/>
</div>
</>
);
}

export default CdcDetails


function numberWithCommas(x:Number) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
55 changes: 51 additions & 4 deletions ui/app/mirrors/edit/[mirrorId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { redirect } from 'next/navigation';
import { Suspense } from 'react';
import { CDCMirror } from './cdc';
import SyncStatus from './syncStatus';
import NewCdcDetails from './newCdcDetails';
import prisma from '@/app/utils/prisma';


export const dynamic = 'force-dynamic';

Expand Down Expand Up @@ -44,17 +47,61 @@ 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,
}));

console.log("*********_________-------",mirrorStatus.cdcStatus.config);

return (
<LayoutMain alignSelf='flex-start' justifySelf='flex-start' width='full'>
<Header variant='title2'>{mirrorId}</Header>
<Suspense fallback={<Loading />}>
{mirrorStatus.cdcStatus && (
<CDCMirror
cdc={mirrorStatus.cdcStatus}
syncStatusChild={syncStatusChild}
/>
// <CDCMirror
// cdc={mirrorStatus.cdcStatus}
// syncStatusChild={syncStatusChild}
// />
<NewCdcDetails syncs={rows} mirrorConfig={mirrorStatus.cdcStatus.config} />

)}
<div className='mt-10'>
{syncStatusChild}
</div>
</Suspense>
</LayoutMain>
);
}


// export const getServerSideProps = (async (context) => {
// console.log("*****************in getServerSideProps", context)
// let syncs = await prisma.cdc_batches.findMany({
// where: {
// flow_name: flowJobName,
// start_time: {
// not: undefined,
// },
// },
// orderBy: {
// start_time: 'desc',
// },
// })

// return { props: { syncs } }
// })
5 changes: 4 additions & 1 deletion ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import prisma from '@/app/utils/prisma';
import { SyncStatusTable } from './syncStatusTable';
import NewCdcDetails from './newCdcDetails';

type SyncStatusProps = {
flowJobName: string | undefined;
Expand Down Expand Up @@ -29,5 +30,7 @@ export default async function SyncStatus({ flowJobName }: SyncStatusProps) {
numRows: sync.rows_in_batch,
}));

return <SyncStatusTable rows={rows} />;
return <div>
<SyncStatusTable rows={rows} />
</div>;
}
2 changes: 1 addition & 1 deletion ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const SyncStatusTable = ({ rows }: SyncStatusTableProps) => {

return (
<Table
title={<Label variant='headline'>CDC Syncs</Label>}
title={<Label variant='headline'>Batches</Label>}
toolbar={{
left: (
<>
Expand Down
3 changes: 3 additions & 0 deletions ui/app/mirrors/status/timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

//React component to show a timeline of sync events. Each bar on the time line will
//represent the number of

0 comments on commit 48a1995

Please sign in to comment.