Commit 2eb8c2c3 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into contract-creation-code

parents f32bcfe6 d0f5a962
export default function formatNumberToMetricPrefix(number: number) {
return Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 3,
}).format(number);
}
...@@ -9,7 +9,7 @@ import Stats from '../ui/pages/Stats'; ...@@ -9,7 +9,7 @@ import Stats from '../ui/pages/Stats';
const StatsPage: NextPage = () => { const StatsPage: NextPage = () => {
return ( return (
<> <>
<Head><title>{ appConfig.network.name } Stats</title></Head> <Head><title>{ appConfig.network.name } stats</title></Head>
<Stats/> <Stats/>
</> </>
); );
......
...@@ -4,6 +4,7 @@ export enum StatsSectionId { ...@@ -4,6 +4,7 @@ export enum StatsSectionId {
'all', 'all',
'accounts', 'accounts',
'blocks', 'blocks',
'tokens',
'transactions', 'transactions',
'gas', 'gas',
} }
...@@ -19,7 +20,7 @@ export enum StatsIntervalId { ...@@ -19,7 +20,7 @@ export enum StatsIntervalId {
} }
export type StatsChart = { export type StatsChart = {
id: string; apiId: string;
title: string; title: string;
description: string; description: string;
} }
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import ChartWidget from 'ui/shared/chart/ChartWidget'; import ChartWidget from 'ui/shared/chart/ChartWidget';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
interface Props { interface Props {
addressHash: string; addressHash: string;
...@@ -19,6 +20,10 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -19,6 +20,10 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(), value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(),
})), [ data ]); })), [ data ]);
if (isError) {
return <DataFetchAlert/>;
}
if (!items?.length) { if (!items?.length) {
return null; return null;
} }
...@@ -28,7 +33,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -28,7 +33,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
chartHeight="200px" chartHeight="200px"
title="Balances" title="Balances"
items={ items } items={ items }
isLoading={ isLoading || isError } isLoading={ isLoading }
/> />
); );
}; };
......
...@@ -22,7 +22,7 @@ const Stats = () => { ...@@ -22,7 +22,7 @@ const Stats = () => {
return ( return (
<Page> <Page>
<PageTitle text={ `${ appConfig.network.name } Stats` }/> <PageTitle text={ `${ appConfig.network.name } stats` }/>
<Box mb={{ base: 6, sm: 8 }}> <Box mb={{ base: 6, sm: 8 }}>
<NumberWidgetsList/> <NumberWidgetsList/>
......
...@@ -51,6 +51,8 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -51,6 +51,8 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
}, []); }, []);
const handleFileSaveClick = useCallback(() => { const handleFileSaveClick = useCallback(() => {
// wait for context menu to close
setTimeout(() => {
if (ref.current) { if (ref.current) {
domToImage.toPng(ref.current, domToImage.toPng(ref.current,
{ {
...@@ -73,6 +75,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -73,6 +75,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
link.remove(); link.remove();
}); });
} }
}, 100);
}, [ pngBackgroundColor, title ]); }, [ pngBackgroundColor, title ]);
const handleSVGSavingClick = useCallback(() => { const handleSVGSavingClick = useCallback(() => {
......
...@@ -29,9 +29,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -29,9 +29,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
const ref = React.useRef<SVGSVGElement>(null); const ref = React.useRef<SVGSVGElement>(null);
const color = useToken('colors', 'blue.200'); const color = useToken('colors', 'blue.200');
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const chartId = useMemo(() => `chart-${ title.split(' ').join('') }`, [ title ]);
const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]); const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]);
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN); const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;
const displayedData = useMemo(() => items.slice(range[0], range[1]), [ items, range ]); const displayedData = useMemo(() => items.slice(range[0], range[1]), [ items, range ]);
const chartData = [ { items: items, name: 'Value', color } ]; const chartData = [ { items: items, name: 'Value', color } ];
......
...@@ -3,6 +3,8 @@ import { useMemo } from 'react'; ...@@ -3,6 +3,8 @@ import { useMemo } from 'react';
import type { TimeChartData } from 'ui/shared/chart/types'; import type { TimeChartData } from 'ui/shared/chart/types';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
interface Props { interface Props {
data: TimeChartData; data: TimeChartData;
width: number; width: number;
...@@ -50,7 +52,7 @@ export default function useTimeChartController({ data, width, height }: Props) { ...@@ -50,7 +52,7 @@ export default function useTimeChartController({ data, width, height }: Props) {
); );
const xTickFormat = (d: d3.AxisDomain) => d.toLocaleString(); const xTickFormat = (d: d3.AxisDomain) => d.toLocaleString();
const yTickFormat = (d: d3.AxisDomain) => d.toLocaleString(); const yTickFormat = (d: d3.AxisDomain) => formatNumberToMetricPrefix(Number(d));
return { return {
xTickFormat, xTickFormat,
......
...@@ -44,10 +44,10 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => { ...@@ -44,10 +44,10 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => {
> >
{ section.charts.map((chart) => ( { section.charts.map((chart) => (
<GridItem <GridItem
key={ chart.id } key={ chart.apiId }
> >
<ChartWidgetContainer <ChartWidgetContainer
id={ chart.id } id={ chart.apiId }
title={ chart.title } title={ chart.title }
description={ chart.description } description={ chart.description }
interval={ interval } interval={ interval }
......
...@@ -2,6 +2,7 @@ import { Grid } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Grid } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import formatNumberToMetricPrefix from 'lib/formatNumberToMetricPrefix';
import { numberWidgetsScheme } from './constants/number-widgets-scheme'; import { numberWidgetsScheme } from './constants/number-widgets-scheme';
import NumberWidget from './NumberWidget'; import NumberWidget from './NumberWidget';
...@@ -12,15 +13,32 @@ const skeletonsCount = 8; ...@@ -12,15 +13,32 @@ const skeletonsCount = 8;
const NumberWidgetsList = () => { const NumberWidgetsList = () => {
const { data, isLoading } = useApiQuery('stats_counters'); const { data, isLoading } = useApiQuery('stats_counters');
const skeletonElement = [ ...Array(skeletonsCount) ]
.map((e, i) => <NumberWidgetSkeleton key={ i }/>);
return ( return (
<Grid <Grid
gridTemplateColumns={{ base: 'repeat(2, 1fr)', lg: 'repeat(4, 1fr)' }} gridTemplateColumns={{ base: 'repeat(2, 1fr)', lg: 'repeat(4, 1fr)' }}
gridGap={ 4 } gridGap={ 4 }
> >
{ isLoading ? [ ...Array(skeletonsCount) ] { isLoading ? skeletonElement :
.map((e, i) => <NumberWidgetSkeleton key={ i }/>) : numberWidgetsScheme.map(({ id, title, formatFn }) => {
numberWidgetsScheme.map(({ id, title }) => if (!data?.counters[id]) {
data?.counters[id] ? <NumberWidget key={ id } label={ title } value={ Number(data.counters[id]).toLocaleString() }/> : null) } return null;
}
const value = formatNumberToMetricPrefix(Number(data.counters[id]));
return (
<NumberWidget
key={ id }
label={ title }
value={ formatFn ?
formatFn(value) :
value }
/>
);
}) }
</Grid> </Grid>
); );
}; };
......
import type { StatsSection } from 'types/client/stats'; import type { StatsSection } from 'types/client/stats';
export const statsChartsScheme: Array<StatsSection> = [ export const statsChartsScheme: Array<StatsSection> = [
{
id: 'accounts',
title: 'Accounts',
charts: [
{
apiId: 'activeAccounts',
title: 'Active accounts',
description: 'Active accounts number per period',
},
{
apiId: 'accountsGrowth',
title: 'Accounts growth',
description: 'Cumulative accounts number per period',
},
],
},
{
id: 'transactions',
title: 'Transactions',
charts: [
{
apiId: 'averageTxnFee',
title: 'Average transaction fee',
description: 'The average amount in USD spent per transaction',
},
{
apiId: 'txnsFee',
title: 'Transactions fees',
description: 'Amount of tokens paid as fees',
},
{
apiId: 'newTxns',
title: 'New transactions',
description: 'New transactions number',
},
{
apiId: 'txnsGrowth',
title: 'Transactions growth',
description: 'Cumulative transactions number',
},
],
},
{ {
id: 'blocks', id: 'blocks',
title: 'Blocks', title: 'Blocks',
charts: [ charts: [
{ {
id: 'newBlocksPerDay', apiId: 'newBlocksPerDay',
title: 'New blocks', title: 'New blocks',
description: 'New blocks number per day', description: 'New blocks number',
},
{
apiId: 'averageBlockSize',
title: 'Average block size',
description: 'Average size of blocks in bytes',
},
],
},
{
id: 'tokens',
title: 'Tokens',
charts: [
{
apiId: 'nativeCoinHoldersGrowth',
title: 'Native coin holders growth',
description: 'Cumulative token holders number for the period',
},
{
apiId: 'newNativeCoinTransfers',
title: 'New native coins transfers',
description: 'New token transfers number for the period',
},
{
apiId: 'nativeCoinSupply',
title: 'Native coin circulating supply',
description: 'Amount of token circulating supply for the period',
},
],
},
{
id: 'gas',
title: 'Gas',
charts: [
{
apiId: 'averageGasLimit',
title: 'Average gas limit',
description: 'Average gas limit per block for the period',
},
{
apiId: 'gasUsedGrowth',
title: 'Gas used growth',
description: 'Cumulative gas used for the period',
},
{
apiId: 'averageGasPrice',
title: 'Average gas price',
description: 'Average gas price for the period',
}, },
// {
// id: 'average-block-size',
// title: 'Average block size',
// description: 'Average size of blocks in bytes',
// },
], ],
}, },
// {
// id: 'transactions',
// title: 'Transactions',
// charts: [
// {
// id: 'average-transaction-fee',
// title: 'Average transaction fee',
// description: 'The average amount in USD spent per transaction',
// },
// {
// id: 'transactions-fees',
// title: 'Transactions fees',
// description: 'Amount of tokens paid as fees',
// },
// {
// id: 'new-transactions',
// title: 'Transactions fees',
// description: 'New transactions number per period',
// },
// {
// id: 'transactions-growth',
// title: 'Transactions growth',
// description: 'Cumulative transactions number per period',
// },
// ],
// },
// {
// id: 'accounts',
// title: 'Accounts',
// charts: [
// {
// id: 'active-accounts',
// title: 'Active accounts',
// description: 'Active accounts number per period',
// },
// {
// id: 'accounts-growth',
// title: 'Accounts growth',
// description: 'Cumulative accounts number per period',
// },
// ],
// },
]; ];
...@@ -2,7 +2,7 @@ import type { Stats } from 'types/api/stats'; ...@@ -2,7 +2,7 @@ import type { Stats } from 'types/api/stats';
type Key = keyof Stats['counters']; type Key = keyof Stats['counters'];
export const numberWidgetsScheme: Array<{id: Key; title: string}> = [ export const numberWidgetsScheme: Array<{id: Key; title: string; formatFn?: (n: string) => string}> = [
{ {
id: 'totalBlocks', id: 'totalBlocks',
title: 'Total blocks', title: 'Total blocks',
...@@ -10,6 +10,7 @@ export const numberWidgetsScheme: Array<{id: Key; title: string}> = [ ...@@ -10,6 +10,7 @@ export const numberWidgetsScheme: Array<{id: Key; title: string}> = [
{ {
id: 'averageBlockTime', id: 'averageBlockTime',
title: 'Average block time', title: 'Average block time',
formatFn: (n) => `${ n } s`,
}, },
{ {
id: 'totalTransactions', id: 'totalTransactions',
...@@ -35,8 +36,4 @@ export const numberWidgetsScheme: Array<{id: Key; title: string}> = [ ...@@ -35,8 +36,4 @@ export const numberWidgetsScheme: Array<{id: Key; title: string}> = [
id: 'totalNativeCoinTransfers', id: 'totalNativeCoinTransfers',
title: 'Total native coin transfers', title: 'Total native coin transfers',
}, },
{
id: 'totalAccounts',
title: 'Total accounts',
},
]; ];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment