Merge pull request #272 from harmony-one/sa/main

View Change Metric
pull/273/head
Artem 2 years ago committed by GitHub
commit 5966a5b6fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      src/api/rpc.ts
  2. 2
      src/components/appHeader/ResourcesButton.tsx
  3. 129
      src/pages/ChartsPage/StatPage.tsx
  4. 55
      src/pages/ChartsPage/ViewChangeStats.tsx
  5. 21
      src/pages/ChartsPage/index.tsx
  6. BIN
      src/pages/ChartsPage/thumbnails/view_change.png
  7. BIN
      src/pages/ChartsPage/thumbnails/view_change_dark.png

@ -145,6 +145,24 @@ export const hmyv2_getTransactionsCount = (address: string, txType: RequestTxTyp
});
};
export const hmyv2_getNodeMetadata = (shard: string) => {
return rpcAdapter<TRPCResponse<any>>(getApiUrl(shard), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "hmyv2_getNodeMetadata",
id: 1,
params: [],
}),
}).then(data => {
if (data.error) {
throw new Error(data.error.message)
}
return data.result
});
};
export const hmyv2_getStakingTransactionsHistory = (params: IGetTxsHistoryParams[]) => {
return rpcAdapter<TRPCResponse<{ staking_transactions: RPCTransactionHarmony[] }>>(API_URL, {
method: "POST",
@ -280,3 +298,16 @@ export const getAllApprovalsForTokens = async (address: string,
return { txnHistory, dataObj };
}
const getApiUrl = (shard: string) => {
switch (shard) {
case "1":
return process.env.REACT_APP_RPC_URL_SHARD1 || 'https://a.api.s1.t.hmny.io/'
case "2":
return process.env.REACT_APP_RPC_URL_SHARD2 || 'https://a.api.s2.t.hmny.io/'
case "3":
return process.env.REACT_APP_RPC_URL_SHARD3 || 'https://a.api.s3.t.hmny.io/'
default:
return 'https://api.s0.t.hmny.io/' || process.env.REACT_APP_RPC_URL_SHARD0
}
}

@ -43,7 +43,6 @@ export function ResourcesButton() {
>
<Box direction={'row'} align={'center'} gap={'4px'}>
Charts & Stats
<Text size={'xsmall'} color={'successText'}>(new)</Text>
</Box>
</Anchor>
<Anchor
@ -55,7 +54,6 @@ export function ResourcesButton() {
>
<Box direction={'row'} align={'center'} gap={'4px'}>
Top Statistics
<Text size={'xsmall'} color={'successText'}>(new)</Text>
</Box>
</Anchor>
</Box>

@ -0,0 +1,129 @@
import {Box, Heading, Spinner, Text} from "grommet";
import {BaseContainer, BasePage} from "../../components/ui";
import styled from "styled-components";
import {Link} from "react-router-dom";
import {useMediaQuery} from "react-responsive";
import { Alert, Info } from 'grommet-icons';
const TextLink = styled(Text)`
cursor: pointer;
text-decoration: underline;
`
const ChartModalContainer = styled(Box)`
`
const LoadingErrorModal = () => {
return <ChartModalContainer
justify={'center'}
gap={'16px'}
pad={'16px'}
align={'center'}
background={'warningBackground'}
round={'8px'}
border={{ size: '1px' }}
style={{ zIndex: 1 }}
>
<Alert size={'medium'} />
<Text>Error on loading data</Text>
<Text size={'small'}>Please try again later</Text>
</ChartModalContainer>
}
const parseKeyValue = (key : string) => {
return key.replace(/([A-Z])/g, ' $1').trim().split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
export interface StatProps {
title: string
description?: string
items: any[]
keys: string[]
infoLeft: string
infoRight?: string
isLoading: boolean
loadingError?: string
}
export const StatPage = (props: StatProps) => {
const { isLoading, loadingError } = props
const rows = []
for (let i = 0; i < props.items.length; i++) {
rows.push(
<Box
key={String(i)}
width={{width: '40%'}}
margin={{bottom: '8px'}}
border={{ size: '1px' }}
round={'8px'}
overflow={'hidden'}
>
<Box align='center' pad={'8px'} background={'backgroundDropdownItem'} >
<Text color={'brand'}>Shard {i}</Text>
</Box>
<Box align='center' pad={'8px'}>
{props.keys.map((key, _) => {
return <Text>{parseKeyValue(key)}: {props.items[i][key]}</Text>
})}
</Box>
</Box>
)
}
const isMobile = useMediaQuery({ query: '(max-width: 868px)' })
return <BaseContainer pad={{ horizontal: "0" }}>
<Heading size="20px" margin={{ bottom: "medium", top: "0" }} style={{ maxWidth: 'unset' }}>
<Box direction={'row'} justify={'between'} align={'center'}>
<Box direction={"row"} gap={'8px'} align={'center'}>
<Box>
<Link to={'/charts'}><TextLink color={'brand'}>Charts</TextLink></Link>
</Box>
<Box>
<Text style={{ opacity: 0.4 }}>/</Text>
</Box>
<Box>
{props.title}
</Box>
</Box>
{!isMobile && props.description &&
<Box align={'end'}>
<Text size={'xsmall'} weight={'normal'}>{props.description}</Text>
</Box>
}
</Box>
</Heading>
<BasePage pad={"small"}>
<Box direction={'row'} justify={'between'} flex={'grow'} wrap={true}>
<Box direction={'row'} gap={'8px'} justify={'center'} align={'center'} style={{ flexGrow: 2 }}>
<Info size={'small'} />
<Text size={'small'}> {props.infoLeft} </Text>
</Box>
{props.infoRight && <Box direction={'row'} gap={'8px'} justify={'center'} align={'center'} style={{ flexGrow: 2 }}>
<Info size={'small'} />
<Text size={'small'}> {props.infoRight} </Text>
</Box>}
</Box>
</BasePage>
<BasePage pad={"small"} style={{overflow: 'inherit', marginTop: '16px'}}>
<Box
width={'100%'}
height='280px'
direction={'row'}
justify={'evenly'}
align={'center'}
wrap={true}
>
{isLoading && <ChartModalContainer justify={'center'} gap={'16px'} align={'center'}>
<Spinner size={'medium'} />
<Text>Loading Data</Text>
</ChartModalContainer>}
{!isLoading && loadingError && <LoadingErrorModal />}
{!isLoading && !loadingError && rows}
</Box>
</BasePage>
</BaseContainer>
}

@ -0,0 +1,55 @@
import {useEffect, useState} from 'react'
import { StatPage } from './StatPage';
import { hmyv2_getNodeMetadata } from 'src/api/rpc';
import { config } from 'src/config';
const CONSENSUS = "consensus"
const BLOCK_NUM = "blocknum"
const VIEW_ID = "viewId"
export const ViewChangeStats = () => {
const { availableShards } = config
const [items, setItems] = useState<any[]>([])
const [isLoading, setIsLoading] = useState<boolean>(false);
const [loadingError, setLoadingError] = useState('')
const [totalViewChange, setTotalViewChange] = useState(0)
useEffect(() => {
const loadData = async () => {
try {
setIsLoading(true)
for (let i in availableShards) {
const nodeMetadata = await hmyv2_getNodeMetadata(i)
const blockHeight = nodeMetadata[CONSENSUS][BLOCK_NUM]
const viewId = nodeMetadata[CONSENSUS][VIEW_ID]
const viewChange = viewId - blockHeight
setItems(items => [...items, {
viewChange: viewChange,
viewId: viewId,
blockHeight: blockHeight,
}])
setTotalViewChange(totalViewChange => totalViewChange + viewChange)
}
} catch (e) {
console.error('Error on loading metrics:', e)
setLoadingError('Loading error')
} finally {
setIsLoading(false)
}
}
loadData()
}, [])
const viewChangeProps = {
title: 'Harmony View Change Statistics',
description: '',
items: items,
keys: ['viewChange', 'viewId', 'blockHeight'],
isLoading,
loadingError,
infoLeft: `Total of ${totalViewChange} view changes across ${availableShards.length} shards`,
infoRight: 'Data is obtained from the explorer node, which may result in a slight delay'
}
return <StatPage {...viewChangeProps} />
}

@ -8,12 +8,14 @@ import {AverageFee} from "./AverageFee";
import {AverageBlockSize} from "./AverageBlockSize";
import styled from "styled-components";
import {useThemeMode} from "../../hooks/themeSwitcherHook";
import { ViewChangeStats } from "./ViewChangeStats";
enum ChartType {
tx = 'tx',
addresses = 'addresses',
fee = 'fee',
blockSize = 'blocksize'
blockSize = 'blocksize',
viewChange = 'viewchange'
}
const PreviewContainer = styled(Box)`
@ -46,6 +48,7 @@ const PreviewCard = (props: { type: ChartType, title: string }) => {
return <PreviewContainer
border={{ size: '1px' }}
margin={{ bottom: '12px'}}
round={'8px'}
overflow={'hidden'}
onClick={onClick}
@ -53,11 +56,12 @@ const PreviewCard = (props: { type: ChartType, title: string }) => {
<Box pad={'8px'} background={'backgroundDropdownItem'}>
<Text size={'small'} color={'brand'}>{title}</Text>
</Box>
<Box pad={'8px'} border={{ side: 'top' }} style={{ filter: themeMode === 'dark' ? 'unset' : 'grayscale(0.8)' }}>
{type === ChartType.tx && <img src={require(`./thumbnails/daily_txs${previewImgPostfix}.png`).default} {...imgProps} />}
{type === ChartType.addresses && <img src={require(`./thumbnails/daily_addresses${previewImgPostfix}.png`).default} {...imgProps} />}
{type === ChartType.fee && <img src={require(`./thumbnails/daily_fee${previewImgPostfix}.png`).default} {...imgProps} />}
{type === ChartType.blockSize && <img src={require(`./thumbnails/daily_blocksize${previewImgPostfix}.png`).default} {...imgProps} />}
<Box pad={'8px'} height={'11.2vh'} border={{ side: 'top' }} style={{ filter: themeMode === 'dark' ? 'unset' : 'grayscale(0.8)' }}>
{type === ChartType.tx && <img src={require(`./thumbnails/daily_txs${previewImgPostfix}.png`).default} height={'100%'} {...imgProps} />}
{type === ChartType.addresses && <img src={require(`./thumbnails/daily_addresses${previewImgPostfix}.png`).default} height={'100%'} {...imgProps} />}
{type === ChartType.fee && <img src={require(`./thumbnails/daily_fee${previewImgPostfix}.png`).default} height={'100%'} {...imgProps} />}
{type === ChartType.blockSize && <img src={require(`./thumbnails/daily_blocksize${previewImgPostfix}.png`).default} height={'100%'} {...imgProps} />}
{type === ChartType.viewChange && <img src={require(`./thumbnails/view_change${previewImgPostfix}.png`).default} height={'100%'} {...imgProps} />}
</Box>
</PreviewContainer>
}
@ -74,6 +78,8 @@ export function ChartsPage() {
return <AverageFee />
} else if(route === ChartType.blockSize) {
return <AverageBlockSize />
} else if(route === ChartType.viewChange) {
return <ViewChangeStats />
}
return (
@ -89,13 +95,14 @@ export function ChartsPage() {
wrap
direction={'row'}
pad={"small"}
justify={'center'}
justify={'start'}
align={'center'}
>
<PreviewCard type={ChartType.tx} title={'Daily Transactions Chart'} />
<PreviewCard type={ChartType.addresses} title={'Daily Active Addresses'} />
<PreviewCard type={ChartType.fee} title={'Average Transaction Fee'} />
<PreviewCard type={ChartType.blockSize} title={'Average Block Size'} />
<PreviewCard type={ChartType.viewChange} title={'View Change Statistics'} />
</Box>
</BasePage>
</BaseContainer>

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Loading…
Cancel
Save