Skip to content

Commit 18fc7f0

Browse files
UI: Remove and replace Tremor from the codebase (#3627)
- Removes a dependency - Tremor - from PeerDB UI. - Charts: Introduces [chart.js](https://www.chartjs.org/docs/latest/) and implements the bar and line charts we have using it. - Tabs in CDC overview now done using [Radix tabs](https://www.radix-ui.com/primitives/docs/components/tabs) - Adds date-fns library as we are using it in a file - Fixes mirror editing in UI where it asks you to select atleast one table <img width="1820" height="1171" alt="Screenshot 2025-10-27 at 3 14 16 PM" src="https://github.com/user-attachments/assets/f3eb2dfa-92ae-41e9-9cf4-756d99384ebe" /> <br></br> <img width="1504" height="422" alt="Screenshot 2025-10-27 at 3 22 12 PM" src="https://github.com/user-attachments/assets/5cd42447-f195-4f43-b657-20fe9732e3ab" />
1 parent 9d7f872 commit 18fc7f0

22 files changed

+494
-863
lines changed

ui/app/mirrors/[mirrorId]/cdc.tsx

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
'use client';
22
import useLocalStorage from '@/app/utils/useLocalStorage';
33
import { MirrorStatusResponse } from '@/grpc_generated/route';
4+
import { useTheme } from '@/lib/AppTheme';
45
import { Label } from '@/lib/Label';
56
import { ProgressCircle } from '@/lib/ProgressCircle';
6-
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@tremor/react';
7+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@radix-ui/react-tabs';
78
import { useEffect, useState } from 'react';
9+
import styled from 'styled-components';
810
import CdcDetails from './cdcDetails';
911
import { SnapshotStatusTable } from './snapshot';
12+
import { TabListStyle, TabsRootStyle } from './styles/tab.styles';
13+
14+
const StyledTabTrigger = styled(TabsTrigger)`
15+
&[data-state='active'] {
16+
font-weight: bold;
17+
}
18+
`;
1019

1120
type CDCMirrorStatusProps = {
1221
status: MirrorStatusResponse;
1322
syncStatusChild?: React.ReactNode;
1423
};
1524
export function CDCMirror({ status, syncStatusChild }: CDCMirrorStatusProps) {
1625
const LocalStorageTabKey = 'cdctab';
26+
const theme = useTheme();
1727
const [selectedTab, setSelectedTab] = useLocalStorage(LocalStorageTabKey, 0);
1828
const [mounted, setMounted] = useState(false);
1929
const handleTab = (index: number) => {
@@ -39,31 +49,32 @@ export function CDCMirror({ status, syncStatusChild }: CDCMirrorStatusProps) {
3949
);
4050
}
4151
return (
42-
<TabGroup
43-
index={selectedTab}
44-
onIndexChange={handleTab}
45-
style={{ marginTop: '1rem' }}
52+
<Tabs
53+
style={TabsRootStyle}
54+
className='TabsRoot'
55+
defaultValue={selectedTab.toString()}
56+
onValueChange={(value) => handleTab(Number(value))}
4657
>
47-
<TabList
48-
color='neutral'
49-
style={{ display: 'flex', justifyContent: 'space-around' }}
50-
className='[&_button]:text-gray-600 dark:[&_button]:text-gray-300 [&_button:hover]:text-gray-900 dark:[&_button:hover]:text-white [&_button[data-state=active]]:text-gray-900 dark:[&_button[data-state=active]]:text-white'
51-
>
52-
<Tab>Overview</Tab>
53-
<Tab>Sync Status</Tab>
54-
<Tab>Initial Copy</Tab>
55-
</TabList>
56-
<TabPanels>
57-
<TabPanel>
58-
<CdcDetails
59-
createdAt={status.createdAt}
60-
mirrorConfig={status.cdcStatus!}
61-
mirrorStatus={status.currentFlowState}
62-
/>
63-
</TabPanel>
64-
<TabPanel>{syncStatusChild}</TabPanel>
65-
<TabPanel>{snapshot}</TabPanel>
66-
</TabPanels>
67-
</TabGroup>
58+
<TabsList style={TabListStyle(theme.theme)} className='TabsList'>
59+
<StyledTabTrigger className='TabsTrigger' value='0'>
60+
Overview
61+
</StyledTabTrigger>
62+
<StyledTabTrigger value='1'>Sync Status</StyledTabTrigger>
63+
<StyledTabTrigger value='2'>Initial Copy</StyledTabTrigger>
64+
</TabsList>
65+
<TabsContent className='TabsContent' value='0'>
66+
<CdcDetails
67+
createdAt={status.createdAt}
68+
mirrorConfig={status.cdcStatus!}
69+
mirrorStatus={status.currentFlowState}
70+
/>
71+
</TabsContent>
72+
<TabsContent className='TabsContent' value='1'>
73+
{syncStatusChild}
74+
</TabsContent>
75+
<TabsContent className='TabsContent' value='2'>
76+
{snapshot}
77+
</TabsContent>
78+
</Tabs>
6879
);
6980
}

ui/app/mirrors/[mirrorId]/cdcGraph.tsx

Lines changed: 111 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,115 @@
22
import SelectTheme from '@/app/styles/select';
33
import { formatGraphLabel, timeOptions } from '@/app/utils/graph';
44
import { TimeAggregateType } from '@/grpc_generated/route';
5+
import { useTheme } from '@/lib/AppTheme';
6+
import { Icon } from '@/lib/Icon';
57
import { Label } from '@/lib/Label';
6-
import { BarChart } from '@tremor/react';
7-
import { useEffect, useState } from 'react';
8+
import {
9+
BarElement,
10+
CategoryScale,
11+
Chart as ChartJS,
12+
ChartOptions,
13+
Legend,
14+
LinearScale,
15+
Title,
16+
Tooltip,
17+
} from 'chart.js';
18+
import { useState } from 'react';
19+
import { Bar } from 'react-chartjs-2';
820
import ReactSelect from 'react-select';
21+
import { BarLoader } from 'react-spinners';
22+
import useSWR from 'swr';
23+
24+
const CdcSyncingLoader = () => {
25+
return (
26+
<div
27+
style={{
28+
display: 'flex',
29+
rowGap: '1rem',
30+
flexDirection: 'column',
31+
alignItems: 'center',
32+
}}
33+
>
34+
Loading sync data...
35+
<BarLoader />
36+
</div>
37+
);
38+
};
39+
40+
const CdcSyncHistoryError = () => {
41+
return (
42+
<div style={{ display: 'flex', alignItems: 'center', columnGap: '0.5rem' }}>
43+
<Icon name='error' /> Failed to load sync history
44+
</div>
45+
);
46+
};
947

1048
type CdcGraphProps = { mirrorName: string };
1149

1250
export default function CdcGraph({ mirrorName }: CdcGraphProps) {
1351
const [aggregateType, setAggregateType] = useState<TimeAggregateType>(
1452
TimeAggregateType.TIME_AGGREGATE_TYPE_ONE_HOUR
1553
);
16-
const [graphValues, setGraphValues] = useState<
17-
{ name: string; 'Rows synced at a point in time': number }[]
18-
>([]);
1954

20-
useEffect(() => {
21-
const fetchData = async () => {
22-
const req: any = {
23-
flowJobName: mirrorName,
24-
aggregateType,
25-
};
55+
ChartJS.register(
56+
BarElement,
57+
CategoryScale,
58+
LinearScale,
59+
Title,
60+
Tooltip,
61+
Legend
62+
);
63+
const theme = useTheme();
64+
const isDarkMode = theme.theme === 'dark';
65+
const chartOptions: ChartOptions<'bar'> = {
66+
maintainAspectRatio: false,
67+
scales: {
68+
x: {
69+
grid: { display: false },
70+
},
71+
y: {
72+
grid: {
73+
color: isDarkMode ? '#333333' : '#e5e7eb',
74+
},
75+
},
76+
},
77+
};
78+
const fetcher = async ([mirrorName, aggregateType]: [
79+
string,
80+
TimeAggregateType,
81+
]) => {
82+
const req = {
83+
flowJobName: mirrorName,
84+
aggregateType,
85+
};
86+
const res = await fetch('/api/v1/mirrors/cdc/graph', {
87+
method: 'POST',
88+
cache: 'no-store',
89+
body: JSON.stringify(req),
90+
});
91+
const data: { data: { time: number; rows: number }[] } = await res.json();
2692

27-
const res = await fetch('/api/v1/mirrors/cdc/graph', {
28-
method: 'POST',
29-
cache: 'no-store',
30-
body: JSON.stringify(req),
31-
});
32-
const data: { data: { time: number; rows: number }[] } = await res.json();
33-
setGraphValues(
34-
data.data.map(({ time, rows }) => ({
35-
name: formatGraphLabel(new Date(time), aggregateType),
36-
'Rows synced at a point in time': Number(rows),
37-
}))
38-
);
93+
const chartData = {
94+
labels: data.data.map(({ time }) =>
95+
formatGraphLabel(new Date(time), aggregateType)
96+
),
97+
datasets: [
98+
{
99+
label: 'Rows synced at a point in time',
100+
data: data.data.map(({ rows }) => rows),
101+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
102+
borderColor: 'rgba(75, 192, 192, 1)',
103+
borderWidth: 1,
104+
},
105+
],
39106
};
107+
return chartData;
108+
};
40109

41-
fetchData();
42-
}, [mirrorName, aggregateType]);
110+
const { data, isLoading, error } = useSWR(
111+
[mirrorName, aggregateType],
112+
fetcher
113+
);
43114

44115
return (
45116
<div>
@@ -56,12 +127,21 @@ export default function CdcGraph({ mirrorName }: CdcGraphProps) {
56127
<div style={{ height: '3rem' }}>
57128
<Label variant='headline'>Sync history</Label>
58129
</div>
59-
<BarChart
60-
className='mt-3'
61-
data={graphValues}
62-
index='name'
63-
categories={['Rows synced at a point in time']}
64-
/>
130+
<div
131+
style={{
132+
height: '320px',
133+
alignItems: 'center',
134+
justifyContent: 'center',
135+
display: 'flex',
136+
width: '100%',
137+
}}
138+
>
139+
{isLoading && <CdcSyncingLoader />}
140+
{error && <CdcSyncHistoryError />}
141+
{data && !isLoading && !error && (
142+
<Bar data={data} options={chartOptions} />
143+
)}
144+
</div>
65145
</div>
66146
);
67147
}

0 commit comments

Comments
 (0)