Skip to content

Commit

Permalink
Add Node Provider Page (#813)
Browse files Browse the repository at this point in the history
Co-authored-by: sa-github-api <138766536+sa-github-api@users.noreply.github.com>
  • Loading branch information
pietrodimarco-dfinity and sa-github-api authored Aug 27, 2024
1 parent 57440f4 commit 0c411a7
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { NodeRewardsArgs, NodeRewardsResponse } from '../../declarations/trustwo
import { NodeList } from './components/NodeList';
import Header from './components/Header';
import { dateToNanoseconds } from './utils/utils';
import { NodeChart } from './components/NodePage';
import { NodePage } from './components/NodePage';
import { NodeProviderPage } from './components/NodeProviderPage';

// Theme configuration
const darkTheme = createTheme({
Expand Down Expand Up @@ -51,7 +52,7 @@ const App: React.FC = () => {
const [periodFilter, setPeriodFilter] = useState<PeriodFilter>({ dateStart, dateEnd });
const [nodeRewards, setNodeRewards] = useState<NodeRewardsResponse[]>([]);
const [subnets, setSubnets] = useState<Set<string>>(new Set());
const [nodeProviders, setNodeProviders] = useState<Set<string>>(new Set());
const [providers, setProviders] = useState<Set<string>>(new Set());
const [isLoading, setIsLoading] = useState(true);
const [drawerOpen, setDrawerOpen] = useState(false);
const theme = useTheme();
Expand All @@ -69,9 +70,11 @@ const App: React.FC = () => {
const nodeRewardsResponse = await trustworthy_node_metrics.node_rewards(request);
const sortedNodeRewards = nodeRewardsResponse.sort((a, b) => a.rewards_percent - b.rewards_percent);
const subnets = new Set(sortedNodeRewards.flatMap(node => node.daily_node_metrics.map(data => data.subnet_assigned.toText())));
const providers = new Set(sortedNodeRewards.flatMap(node => node.node_provider_id.toText()));

setNodeRewards(sortedNodeRewards);
setSubnets(subnets);
setProviders(providers);
} catch (error) {
console.error("Error fetching node:", error);
} finally {
Expand All @@ -84,12 +87,12 @@ const App: React.FC = () => {

const drawerProps = useMemo(() => ({
subnets,
nodeProviders,
providers,
drawerWidth,
temporary: isSmallScreen,
drawerOpen,
onClosed: () => setDrawerOpen(false)
}), [subnets, nodeProviders, drawerWidth, isSmallScreen, drawerOpen]);
}), [subnets, providers, drawerWidth, isSmallScreen, drawerOpen]);

return (
<ThemeProvider theme={darkTheme}>
Expand All @@ -107,12 +110,16 @@ const App: React.FC = () => {
isLoading ? <LoadingIndicator /> : <NodeList nodeRewards={nodeRewards} periodFilter={periodFilter} />
} />
<Route path="/nodes/:node" element={
isLoading ? <LoadingIndicator /> : <NodeChart nodeRewards={nodeRewards} periodFilter={periodFilter} />
isLoading ? <LoadingIndicator /> : <NodePage nodeRewards={nodeRewards} periodFilter={periodFilter} />
} />
<Route path="/subnets/:subnet" element={
// TODO: Add subnet page
isLoading ? <LoadingIndicator /> : <NodeList nodeRewards={nodeRewards} periodFilter={periodFilter} />
} />
<Route path="/providers/:provider" element={
// TODO: Add subnet page
isLoading ? <LoadingIndicator /> : <NodeProviderPage nodeRewards={nodeRewards} periodFilter={periodFilter} />
} />
</Routes>
</Box>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import { ExpandLess, ExpandMore } from '@mui/icons-material';

interface DrawerProps {
subnets: Set<string>;
nodeProviders: Set<string>;
providers: Set<string>;
drawerWidth: number;
temporary: boolean;
drawerOpen: boolean;
onClosed: () => void;
}

const Drawer: React.FC<DrawerProps> = ({ subnets, nodeProviders, drawerWidth, temporary, drawerOpen, onClosed }) => {
const Drawer: React.FC<DrawerProps> = ({ subnets, providers, drawerWidth, temporary, drawerOpen, onClosed }) => {
const [isSubnetsOpen, setIsSubnetsOpen] = React.useState(false);
const [isNodeProvidersOpen, setIsNodeProvidersOpen] = React.useState(false);

Expand Down Expand Up @@ -85,7 +85,7 @@ const Drawer: React.FC<DrawerProps> = ({ subnets, nodeProviders, drawerWidth, te
</ListItemButton>
</Link>
{renderCollapsibleList("Subnets", subnets, isSubnetsOpen, setIsSubnetsOpen, "subnets")}
{renderCollapsibleList("Node Providers", nodeProviders, isNodeProvidersOpen, setIsNodeProvidersOpen, "node-providers")}
{renderCollapsibleList("Node Providers", providers, isNodeProvidersOpen, setIsNodeProvidersOpen, "providers")}
</List>
</MUIDrawer>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react';
import { DataGrid, GridColDef, GridRowsProp, GridToolbarContainer, GridToolbarExport } from '@mui/x-data-grid';

function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarExport />
</GridToolbarContainer>
);
}

interface ExportCustomToolbarProps {
colDef: GridColDef[];
rows: GridRowsProp;
}

export const ExportTable: React.FC<ExportCustomToolbarProps> = ({ colDef, rows }) => {
return (
<div style={{ height: 1000, width: '100%' }}>
<DataGrid
rows={rows}
columns={colDef}
slots={{
toolbar: CustomToolbar,
}}
/>
</div>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
import React from 'react';
import Typography from '@mui/material/Typography';

interface NodeInfoProps {
nodeId: string;
nodeProviderId: string;
interface InfoFormatterProps {
name: string;
value: string;
}

const NodeInfo: React.FC<NodeInfoProps> = ({ nodeId, nodeProviderId }) => {
const InfoFormatter: React.FC<InfoFormatterProps> = ({ name, value }) => {
return (
<div>
<Typography gutterBottom variant="subtitle1" component="div">
{"Node ID"}
{name}
</Typography>
<Typography gutterBottom variant="subtitle2" sx={{ color: 'text.disabled' }} component="div">
{nodeId}
</Typography>

<Typography gutterBottom variant="subtitle1" component="div">
{"Node Provider ID"}
</Typography>
<Typography gutterBottom variant="subtitle2" sx={{ color: 'text.disabled' }} component="div">
{nodeProviderId}
{value}
</Typography>
</div>
);
};

export default NodeInfo;
export default InfoFormatter;
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React from 'react';
import { ChartData, generateChartData } from '../utils/utils';
import { ChartData, formatDateToUTC, generateChartData } from '../utils/utils';
import { WidgetGauge, WidgetNumber } from './Widgets';
import { PeriodFilter } from './FilterBar';
import { Box, Divider, Grid, Paper, Typography } from '@mui/material';
import { useParams } from 'react-router-dom';
import DailyPerformanceChart from './DailyPerformanceChart';
import NodeInfo from './NodeInfo';
import { paperStyle, boxStyleWidget } from '../Styles';
import { NodeRewardsResponse } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did';
import RewardsInfo, { LinearReductionChart } from './RewardsInfo';
import { ExportCustomToolbar } from './NodeDailyData';
import RewardsInfo from './RewardsInfo';
import { ExportTable } from './ExportTable';
import InfoFormatter from './NodeInfo';
import { GridColDef, GridRowsProp } from '@mui/x-data-grid';

export interface NodeChartProps {
export interface NodePageProps {
nodeRewards: NodeRewardsResponse[];
periodFilter: PeriodFilter;
}
Expand All @@ -30,7 +31,7 @@ const NodePerformanceStats: React.FC<{ rewardsReduction: string }> = ({ rewardsR
</Box>
);

export const NodeChart: React.FC<NodeChartProps> = ({ nodeRewards, periodFilter }) => {
export const NodePage: React.FC<NodePageProps> = ({ nodeRewards, periodFilter }) => {
const { node } = useParams();

const nodeMetrics = nodeRewards.find((metrics) => metrics.node_id.toText() === node);
Expand All @@ -43,6 +44,27 @@ export const NodeChart: React.FC<NodeChartProps> = ({ nodeRewards, periodFilter
const rewardsPercent = Math.round(nodeMetrics.rewards_percent * 100);
const rewardsReduction = 100 - rewardsPercent;

let index = 0;
const rows: GridRowsProp = nodeMetrics.daily_node_metrics.map((data) => {
index = index + 1;
return {
id: index,
col1: new Date(Number(data.ts) / 1000000),
col2: data.num_blocks_proposed,
col3: data.num_blocks_failed,
col4: data.failure_rate,
col5: data.subnet_assigned,
};
});

const colDef: GridColDef[] = [
{ field: 'col1', headerName: 'Date (UTC)', width: 200, valueFormatter: (value: Date) => formatDateToUTC(value)},
{ field: 'col2', headerName: 'Blocks Proposed', width: 150 },
{ field: 'col3', headerName: 'Blocks Failed', width: 150 },
{ field: 'col4', headerName: 'Daily Failure Rate', width: 350 , valueFormatter: (value: number) => `${value * 100}%`,},
{ field: 'col5', headerName: 'Subnet Assigned', width: 550 },
];

return (
<Box sx={{ p: 3 }}>
<Paper sx={paperStyle}>
Expand All @@ -54,7 +76,8 @@ export const NodeChart: React.FC<NodeChartProps> = ({ nodeRewards, periodFilter
<Divider/>
</Grid>
<Grid item xs={12} md={4}>
<NodeInfo nodeId={nodeMetrics.node_id.toText()} nodeProviderId={nodeMetrics.node_provider_id.toText()} />
<InfoFormatter name={"Node ID"} value={nodeMetrics.node_id.toText()} />
<InfoFormatter name={"Node Provider ID"} value={nodeMetrics.node_provider_id.toText()} />
</Grid>
<Grid item xs={12} md={8}>
<WidgetGauge value={rewardsPercent} title={"Rewards Total"} />
Expand All @@ -72,12 +95,12 @@ export const NodeChart: React.FC<NodeChartProps> = ({ nodeRewards, periodFilter
<RewardsInfo failureRate={failureRateAvg} rewardReduction={rewardsReduction}/>
</Grid>
<Grid item xs={12} md={12}>
<ExportCustomToolbar chartDailyData={chartDailyData}/>
<ExportTable colDef={colDef} rows={rows}/>
</Grid>
</Grid>
</Paper>
</Box>
);
};

export default NodeChart;
export default NodePage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useState } from 'react';
import { Box, Grid, Paper, Typography } from '@mui/material';
import { axisClasses, BarChart, StackOrderType } from '@mui/x-charts';
import Divider from '@mui/material/Divider';
import { useParams } from 'react-router-dom';
import { formatDateToUTC, generateChartData, getFormattedDates } from '../utils/utils';
import { PeriodFilter } from './FilterBar';
import { Root } from './NodeList';
import { NodeRewardsResponse } from '../../../declarations/trustworthy-node-metrics/trustworthy-node-metrics.did';
import { paperStyle } from '../Styles';
import InfoFormatter from './NodeInfo';
import { ExportTable } from './ExportTable';
import { GridColDef, GridRowsProp } from '@mui/x-data-grid';

export interface NodeProviderPageProps {
nodeRewards: NodeRewardsResponse[],
periodFilter: PeriodFilter
}

export const NodeProviderPage: React.FC<NodeProviderPageProps> = ({ nodeRewards, periodFilter }) => {
const { provider } = useParams();
const providerNodeMetrics = nodeRewards
.filter((nodeMetrics) => nodeMetrics.node_provider_id.toText() === provider)
const highFailureRateChart = providerNodeMetrics
.filter(nodeMetrics => nodeMetrics.rewards_stats.rewards_reduction > 0)
.flatMap(nodeMetrics => {
const chartData = generateChartData(periodFilter, nodeMetrics.daily_node_metrics);
return {
data: chartData.map(data => data.dailyNodeMetrics? data.dailyNodeMetrics.failure_rate * 100: null),
label: nodeMetrics.node_id.toText(),
stack: 'total'
}
});

let index = 0;
const rows: GridRowsProp = providerNodeMetrics.flatMap((nodeRewards) => {
return nodeRewards.daily_node_metrics.map((data) => {
index = index + 1;
return {
id: index,
col1: new Date(Number(data.ts) / 1000000),
col2: nodeRewards.node_id,
col3: data.num_blocks_proposed,
col4: data.num_blocks_failed,
col5: data.failure_rate,
col6: data.subnet_assigned,
};
})
});

const colDef: GridColDef[] = [
{ field: 'col1', headerName: 'Date (UTC)', width: 200, valueFormatter: (value: Date) => formatDateToUTC(value)},
{ field: 'col2', headerName: 'Node ID', width: 550 },
{ field: 'col3', headerName: 'Blocks Proposed', width: 150 },
{ field: 'col4', headerName: 'Blocks Failed', width: 150 },
{ field: 'col5', headerName: 'Daily Failure Rate', width: 350 , valueFormatter: (value: number) => `${value * 100}%`,},
{ field: 'col6', headerName: 'Subnet Assigned', width: 550 },
];

return (

<Box sx={{ p: 3 }}>
<Paper sx={paperStyle}>
<Grid container spacing={3}>
<Grid item xs={12} md={12}>
<Typography gutterBottom variant="h5" component="div">
{"Node Provider"}
</Typography>
<Divider/>
</Grid>
<Grid item xs={12} md={12}>
<InfoFormatter name={"Provider ID"} value={provider ? provider : "Anonym"} />
</Grid>
<Grid item xs={12}>
<Typography variant="h6" component="div">
Daily Failure Rate
</Typography>
<Typography variant="subtitle2" sx={{ color: 'text.disabled' }} component="div">
For nodes with rewards reduction
</Typography>
</Grid>
<Grid item xs={12} md={12}>
<BarChart
slotProps={{ legend: { hidden: true } }}
xAxis={[{
scaleType: 'band',
data: getFormattedDates(periodFilter),
}]}
yAxis={[{
valueFormatter: (value: number) => `${value}%`,
}]}
leftAxis={null}
borderRadius={9}
series={highFailureRateChart}
height={300}
/>
</Grid>
<Grid item xs={12} md={12}>
<ExportTable colDef={colDef} rows={rows}/>
</Grid>
</Grid>
</Paper>
</Box>
);
};
Loading

0 comments on commit 0c411a7

Please sign in to comment.