-
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 48a1995
Showing
7 changed files
with
346 additions
and
6 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
ui/app/mirrors/edit/[mirrorId]/aggregatedCountsByInterval.js
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,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 |
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,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; |
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,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, ","); | ||
} |
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
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 |