Skip to content

Commit

Permalink
Merge pull request #1482 from blockscout/tom2drum/issue-1405
Browse files Browse the repository at this point in the history
Gas tooltip: periodical updates of data
  • Loading branch information
tom2drum authored Jan 16, 2024
2 parents d71e398 + 18b7f4b commit c0239ee
Show file tree
Hide file tree
Showing 29 changed files with 309 additions and 60 deletions.
6 changes: 4 additions & 2 deletions lib/api/useApiFetch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQueryClient } from '@tanstack/react-query';
import _omit from 'lodash/omit';
import _pickBy from 'lodash/pickBy';
import React from 'react';

Expand All @@ -19,7 +20,7 @@ import type { ApiResource, ResourceName, ResourcePathParams } from './resources'
export interface Params<R extends ResourceName> {
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal'>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal' | 'headers'>;
}

export default function useApiFetch() {
Expand All @@ -40,6 +41,7 @@ export default function useApiFetch() {
'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined,
Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined,
'x-csrf-token': withBody && csrfToken ? csrfToken : undefined,
...fetchParams?.headers,
}, Boolean) as HeadersInit;

return fetch<SuccessType, ErrorType>(
Expand All @@ -51,7 +53,7 @@ export default function useApiFetch() {
// change condition here if something is changed
credentials: config.features.account.isEnabled ? 'include' : 'same-origin',
headers,
...fetchParams,
..._omit(fetchParams, 'headers'),
},
{
resource: resource.path,
Expand Down
21 changes: 18 additions & 3 deletions mocks/stats/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@ import type { HomeStats } from 'types/api/stats';
export const base: HomeStats = {
average_block_time: 6212.0,
coin_price: '0.00199678',
coin_price_change_percentage: -7.42,
gas_prices: {
average: 48.0,
fast: 67.5,
slow: 48.0,
average: {
fiat_price: '1.01',
price: 20.41,
time: 12283,
},
fast: {
fiat_price: '1.26',
price: 25.47,
time: 9321,
},
slow: {
fiat_price: '0.97',
price: 19.55,
time: 24543,
},
},
gas_price_updated_at: '2022-11-11T11:09:49.051171Z',
gas_prices_update_in: 300000,
gas_used_today: '4108680603',
market_cap: '330809.96443288102524',
network_utilization_percentage: 1.55372064,
Expand Down
11 changes: 7 additions & 4 deletions nextjs/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IncomingMessage } from 'http';
import _pick from 'lodash/pick';
import type { NextApiRequest } from 'next';
import type { NextApiRequestCookies } from 'next/dist/server/api-utils';
import type { RequestInit, Response } from 'node-fetch';
Expand All @@ -14,16 +15,18 @@ export default function fetchFactory(
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
return function fetch(url: string, init?: RequestInit): Promise<Response> {
const csrfToken = _req.headers['x-csrf-token'];
const authToken = _req.headers['Authorization'];
const apiToken = _req.cookies[cookies.NAMES.API_TOKEN];

const headers = {
accept: _req.headers['accept'] || 'application/json',
'content-type': _req.headers['content-type'] || 'application/json',
cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '',
...(csrfToken ? { 'x-csrf-token': String(csrfToken) } : {}),
...(authToken ? { Authorization: String(authToken) } : {}),
..._pick(_req.headers, [
'x-csrf-token',
'Authorization',
// feature flags
'updated-gas-oracle',
]) as Record<string, string | undefined>,
};

httpLogger.logger.info({
Expand Down
21 changes: 18 additions & 3 deletions stubs/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@ import type { Counter, HomeStats, StatsChartsSection } from 'types/api/stats';
export const HOMEPAGE_STATS: HomeStats = {
average_block_time: 14346,
coin_price: '1807.68',
coin_price_change_percentage: 42,
gas_prices: {
average: 0.1,
fast: 0.11,
slow: 0.1,
average: {
fiat_price: '1.01',
price: 20.41,
time: 12283,
},
fast: {
fiat_price: '1.26',
price: 25.47,
time: 9321,
},
slow: {
fiat_price: '0.97',
price: 19.55,
time: 24543,
},
},
gas_price_updated_at: '2022-11-11T11:09:49.051171Z',
gas_prices_update_in: 300000,
gas_used_today: '0',
market_cap: '0',
network_utilization_percentage: 22.56,
Expand Down
15 changes: 12 additions & 3 deletions types/api/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ export type HomeStats = {
total_transactions: string;
average_block_time: number;
coin_price: string | null;
coin_price_change_percentage: number | null; // e.g -6.22
total_gas_used: string;
transactions_today: string;
gas_used_today: string;
gas_prices: GasPrices | null;
gas_price_updated_at: string | null;
gas_prices_update_in: number;
static_gas_price: string | null;
market_cap: string;
network_utilization_percentage: number;
Expand All @@ -16,9 +19,15 @@ export type HomeStats = {
}

export type GasPrices = {
average: number | null;
fast: number | null;
slow: number | null;
average: GasPriceInfo | null;
fast: GasPriceInfo | null;
slow: GasPriceInfo | null;
}

export interface GasPriceInfo {
fiat_price: string | null;
price: number | null;
time: number | null;
}

export type Counters = {
Expand Down
5 changes: 5 additions & 0 deletions ui/blocks/BlocksTabSlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ interface Props {

const BlocksTabSlot = ({ pagination }: Props) => {
const statsQuery = useApiQuery('homepage_stats', {
fetchParams: {
headers: {
'updated-gas-oracle': 'true',
},
},
queryOptions: {
placeholderData: HOMEPAGE_STATS,
},
Expand Down
6 changes: 6 additions & 0 deletions ui/home/LatestBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ const LatestBlocks = () => {

const queryClient = useQueryClient();
const statsQueryResult = useApiQuery('homepage_stats', {
fetchParams: {
headers: {
'updated-gas-oracle': 'true',
},
},
queryOptions: {
refetchOnMount: false,
placeholderData: HOMEPAGE_STATS,
},
});
Expand Down
24 changes: 21 additions & 3 deletions ui/home/Stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ const hasGasTracker = config.UI.homepage.showGasTracker;
const hasAvgBlockTime = config.UI.homepage.showAvgBlockTime;

const Stats = () => {
const { data, isPlaceholderData, isError } = useApiQuery('homepage_stats', {
const { data, isPlaceholderData, isError, dataUpdatedAt } = useApiQuery('homepage_stats', {
fetchParams: {
headers: {
'updated-gas-oracle': 'true',
},
},
queryOptions: {
refetchOnMount: false,
placeholderData: HOMEPAGE_STATS,
},
});
Expand Down Expand Up @@ -45,7 +51,19 @@ const Stats = () => {
!data.gas_prices && itemsCount--;
data.rootstock_locked_btc && itemsCount++;
const isOdd = Boolean(itemsCount % 2);
const gasLabel = hasGasTracker && data.gas_prices ? <GasInfoTooltipContent gasPrices={ data.gas_prices }/> : null;
const gasLabel = hasGasTracker && data.gas_prices ? <GasInfoTooltipContent data={ data } dataUpdatedAt={ dataUpdatedAt }/> : null;

const gasPriceText = (() => {
if (data.gas_prices?.average?.fiat_price) {
return `$${ data.gas_prices.average.fiat_price }`;
}

if (data.gas_prices?.average?.price) {
return `${ data.gas_prices.average.price.toLocaleString() } Gwei`;
}

return 'N/A';
})();

content = (
<>
Expand Down Expand Up @@ -92,7 +110,7 @@ const Stats = () => {
<StatsItem
icon="gas"
title="Gas tracker"
value={ data.gas_prices.average !== null ? `${ Number(data.gas_prices.average).toLocaleString() } Gwei` : 'N/A' }
value={ gasPriceText }
_last={ isOdd ? lastItemTouchStyle : undefined }
tooltipLabel={ gasLabel }
isLoading={ isPlaceholderData }
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 12 additions & 1 deletion ui/home/indicators/ChainIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';

import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { HOMEPAGE_STATS } from 'stubs/stats';
import Hint from 'ui/shared/Hint';

import ChainIndicatorChartContainer from './ChainIndicatorChartContainer';
Expand All @@ -29,7 +30,17 @@ const ChainIndicators = () => {
const indicator = indicators.find(({ id }) => id === selectedIndicator);

const queryResult = useFetchChartData(indicator);
const statsQueryResult = useApiQuery('homepage_stats');
const statsQueryResult = useApiQuery('homepage_stats', {
fetchParams: {
headers: {
'updated-gas-oracle': 'true',
},
},
queryOptions: {
refetchOnMount: false,
placeholderData: HOMEPAGE_STATS,
},
});

const bgColorDesktop = useColorModeValue('white', 'gray.900');
const bgColorMobile = useColorModeValue('white', 'black');
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions ui/shared/GasInfoTooltipContent/GasInfoRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { GridItem, chakra } from '@chakra-ui/react';
import React from 'react';

import type { GasPriceInfo } from 'types/api/stats';

import { asymp, space } from 'lib/html-entities';

interface Props {
name: string;
info: GasPriceInfo | null;
}

const GasInfoRow = ({ name, info }: Props) => {
const content = (() => {
if (!info || info.price === null) {
return 'N/A';
}

return (
<>
<span>{ info.fiat_price ? `$${ info.fiat_price }` : `${ info.price } Gwei` }</span>
{ info.time && (
<chakra.span color="text_secondary">
{ space }per tx { asymp } { (info.time / 1000).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s
</chakra.span>
) }
</>
);
})();

return (
<>
<GridItem color="blue.100">{ name }</GridItem>
<GridItem color="text" textAlign="right">{ content }</GridItem>
</>
);
};

export default React.memo(GasInfoRow);
48 changes: 34 additions & 14 deletions ui/shared/GasInfoTooltipContent/GasInfoTooltipContent.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { Grid, GridItem, useColorModeValue } from '@chakra-ui/react';
import { DarkMode, Grid, GridItem } from '@chakra-ui/react';
import React from 'react';

import type { GasPrices } from 'types/api/stats';
import type { HomeStats } from 'types/api/stats';

const GasInfoTooltipContent = ({ gasPrices }: {gasPrices: GasPrices}) => {
const nameStyleProps = {
color: useColorModeValue('blue.100', 'blue.600'),
};
import dayjs from 'lib/date/dayjs';

import GasInfoRow from './GasInfoRow';
import GasInfoUpdateTimer from './GasInfoUpdateTimer';

interface Props {
data: HomeStats;
dataUpdatedAt: number;
}

const GasInfoTooltipContent = ({ data, dataUpdatedAt }: Props) => {

if (!data.gas_prices) {
return null;
}

return (
<Grid templateColumns="repeat(2, max-content)" rowGap={ 2 } columnGap={ 4 } padding={ 4 } fontSize="xs">
<GridItem { ...nameStyleProps }>Slow</GridItem>
<GridItem>{ gasPrices.slow !== null ? `${ gasPrices.slow } Gwei` : 'N/A' }</GridItem>
<GridItem { ...nameStyleProps }>Average</GridItem>
<GridItem>{ gasPrices.average !== null ? `${ gasPrices.average } Gwei` : 'N/A' }</GridItem>
<GridItem { ...nameStyleProps }>Fast</GridItem>
<GridItem>{ gasPrices.fast !== null ? `${ gasPrices.fast } Gwei` : 'N/A' }</GridItem>
</Grid>
<DarkMode>
<Grid templateColumns="repeat(2, max-content)" rowGap={ 2 } columnGap={ 4 } padding={ 4 } fontSize="xs" lineHeight={ 4 }>
{ data.gas_price_updated_at && (
<>
<GridItem color="text_secondary">Last update</GridItem>
<GridItem color="text_secondary" display="flex" justifyContent="flex-end" columnGap={ 2 }>
{ dayjs(data.gas_price_updated_at).format('MMM DD, HH:mm:ss') }
{ data.gas_prices_update_in !== 0 &&
<GasInfoUpdateTimer key={ dataUpdatedAt } startTime={ dataUpdatedAt } duration={ data.gas_prices_update_in }/> }
</GridItem>
</>
) }
<GasInfoRow name="Slow" info={ data.gas_prices.slow }/>
<GasInfoRow name="Normal" info={ data.gas_prices.average }/>
<GasInfoRow name="Fast" info={ data.gas_prices.fast }/>
</Grid>
</DarkMode>
);
};

Expand Down
45 changes: 45 additions & 0 deletions ui/shared/GasInfoTooltipContent/GasInfoUpdateTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CircularProgress } from '@chakra-ui/react';
import React from 'react';

import dayjs from 'lib/date/dayjs';

interface Props {
startTime: number;
duration: number;
}

const getValue = (startDate: dayjs.Dayjs, duration: number) => {
const now = dayjs();
const diff = now.diff(startDate, 'ms');
const value = diff / duration * 100;

if (value >= 99) {
return 99;
}

return value;
};

const GasInfoUpdateTimer = ({ startTime, duration }: Props) => {
const [ value, setValue ] = React.useState(getValue(dayjs(startTime), duration));

React.useEffect(() => {
const startDate = dayjs(startTime);

const intervalId = window.setInterval(() => {
const nextValue = getValue(startDate, duration);
setValue(nextValue);
if (nextValue === 99) {
window.clearInterval(intervalId);
}
}, 100);

return () => {
window.clearInterval(intervalId);
};
}, [ startTime, duration ]);

return <CircularProgress value={ value } trackColor="whiteAlpha.100" size={ 4 }/>;
};

export default React.memo(GasInfoUpdateTimer);
Loading

0 comments on commit c0239ee

Please sign in to comment.