-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: add screen to view mirror activity
- Loading branch information
Pankaj Bhageria
authored and
Pankaj Bhageria
committed
Nov 8, 2023
1 parent
fb90f2a
commit 79a1938
Showing
6 changed files
with
308 additions
and
32 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
type timestampType ={ | ||
timestamp: string; | ||
count: number; | ||
} | ||
|
||
function aggregateCountsByInterval(timestamps: timestampType[], interval:string) { | ||
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 :{ [key: string]: number } = {}; | ||
|
||
// 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; | ||
} | ||
|
||
|
||
|
||
// Create an array of intervals between the start and end timestamps | ||
const intervals = []; | ||
|
||
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: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:Date, format:string) { | ||
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:number) { | ||
return number < 10 ? `0${number}` : `${number}`; | ||
} | ||
|
||
export default aggregateCountsByInterval |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,76 @@ | ||
'use client' | ||
import { Label } from '@/lib/Label'; | ||
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' | ||
import moment from 'moment'; | ||
|
||
type CDCDetailsProps = { | ||
config: FlowConnectionConfigs | undefined; | ||
|
||
type SyncStatusRow = { | ||
batchId: number; | ||
startTime: Date; | ||
endTime: Date | null; | ||
numRows: number; | ||
}; | ||
|
||
export default function CDCDetails({ config }: CDCDetailsProps) { | ||
if (!config) { | ||
return <div className='text-red-500'>No configuration provided</div>; | ||
} | ||
type props = { | ||
syncs: SyncStatusRow[]; | ||
mirrorConfig: FlowConnectionConfigs | undefined; | ||
}; | ||
function CdcDetails({ syncs,mirrorConfig }:props) { | ||
|
||
let lastSyncedAt = moment(syncs[0]?.startTime).fromNow(); | ||
let rowsSynced = syncs.reduce((acc, sync) => acc + sync.numRows, 0) | ||
|
||
return ( | ||
<div className='p-4 rounded-md'> | ||
<h2 className='text-xl font-semibold mb-4'>CDC Details</h2> | ||
<div className='overflow-x-auto'> | ||
<table className='min-w-full divide-y divide-gray-300'> | ||
<tbody> | ||
<tr> | ||
<td className='px-4 py-2 font-medium'>Source</td> | ||
<td className='px-4 py-2'>{config.source?.name || '-'}</td> | ||
</tr> | ||
<tr> | ||
<td className='px-4 py-2 font-medium'>Destination</td> | ||
<td className='px-4 py-2'>{config.destination?.name || '-'}</td> | ||
</tr> | ||
<tr> | ||
<td className='px-4 py-2 font-medium'>Flow Job Name</td> | ||
<td className='px-4 py-2'>{config.flowJobName}</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<> | ||
<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> | ||
</> | ||
); | ||
} | ||
|
||
function numberWithCommas(x:Number) { | ||
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | ||
} | ||
|
||
export default CdcDetails |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { Label } from '@/lib/Label'; | ||
import { useState, useEffect } from 'react'; | ||
import moment from 'moment'; | ||
|
||
type SyncStatusRow = { | ||
batchId: number; | ||
startTime: Date; | ||
endTime: Date | null; | ||
numRows: number; | ||
}; | ||
|
||
import aggregateCountsByInterval from './aggregatedCountsByInterval' | ||
|
||
const aggregateTypeMap = { | ||
"15min":" 15 mins", | ||
"hour": "Hour", | ||
"day": "Day", | ||
"month": "Month" | ||
} | ||
|
||
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'> | ||
{["15min","hour","day","month"].map((type)=>{ | ||
return <FilterButton key={type} aggregateType={type} selectedAggregateType={aggregateType} setAggregateType={setAggregateType}/> | ||
})} | ||
</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 filterButtonProps = { | ||
aggregateType:String; | ||
selectedAggregateType:String; | ||
setAggregateType:Function; | ||
}; | ||
function FilterButton({aggregateType,selectedAggregateType,setAggregateType}:filterButtonProps){ | ||
return <button | ||
className={aggregateType === selectedAggregateType ? "bg-gray-200 px-1 mx-1 rounded-md":"px-1 mx-1"} | ||
onClick={()=>setAggregateType(aggregateType)}> | ||
{aggregateTypeMap[aggregateType]} | ||
</button> | ||
} | ||
|
||
type GraphBarProps = { | ||
count: number | undefined; | ||
label: string | ||
}; | ||
|
||
|
||
function formatGraphLabel(date:Date, aggregateType: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'); | ||
} | ||
} | ||
|
||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |