Feat/300 network parameters table (#333)

* network parameters table with key value rows and syntax blobs only for json values

* inline row not for syntax

* add unit test for network param table

* add cypress test to verify if values are non-empty

* remove some comments

* rename formatNumber method to addDecimalsFormatNumber and simplify formatNumber

* remove duplicate expect line

* use AsyncRenderer and sort params asc

* refactor and add extra tests to check ordering and loading cases

* format big number params with addDecimals formatNumber

* Update apps/explorer/src/app/routes/network-parameters/network-parameters.tsx

Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>

* capitalize and refactor tests

* missing ; caused formatting to fail

Co-authored-by: madalinaraicu <“madalina@raygroup.uk”>
Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
This commit is contained in:
m.ray 2022-05-04 18:15:54 +03:00 committed by GitHub
parent 431ea1bc80
commit d03e4cf785
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 280 additions and 56 deletions

View File

@ -3,6 +3,7 @@
"nrwl.angular-console",
"esbenp.prettier-vscode",
"firsttris.vscode-jest-runner",
"dbaeumer.vscode-eslint"
"dbaeumer.vscode-eslint",
"stevejpurves.cucumber"
]
}

View File

@ -9,3 +9,15 @@ Feature: Network parameters Page
Given I am on mobile and open the toggle menu
When I navigate to network parameters page
Then network parameters page is correctly displayed
Scenario: Navigate to network parameters page and check each value is non-empty
Given I am on the homepage
When I navigate to network parameters page
Then network parameters page is correctly displayed
And each value is non-empty
Scenario: Navigate to network parameters page and check each value using mobile
Given I am on mobile and open the toggle menu
When I navigate to network parameters page
Then network parameters page is correctly displayed
And each value is non-empty

View File

@ -11,4 +11,12 @@ export default class NetworkParametersPage extends BasePage {
);
cy.getByTestId(this.parameters).should('not.be.empty');
}
eachValueIsNonEmpty() {
cy.getByTestId(this.parameters).then(($parameters) => {
$parameters.each((_index, element) => {
cy.wrap(element).should('not.be.empty');
});
});
}
}

View File

@ -9,3 +9,7 @@ When('I navigate to network parameters page', () => {
Then('network parameters page is correctly displayed', () => {
networkPage.verifyNetworkParametersDisplayed();
});
Then('each value is non-empty', () => {
networkPage.eachValueIsNonEmpty();
});

View File

@ -1,28 +1 @@
import { gql, useQuery } from '@apollo/client';
import { RouteTitle } from '../../components/route-title';
import type { NetworkParametersQuery } from './__generated__/NetworkParametersQuery';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
export const NETWORK_PARAMETERS_QUERY = gql`
query NetworkParametersQuery {
networkParameters {
key
value
}
}
`;
const NetworkParameters = () => {
const { data } = useQuery<NetworkParametersQuery>(NETWORK_PARAMETERS_QUERY);
return (
<section>
<RouteTitle data-testid="network-param-header">
{t('Network Parameters')}
</RouteTitle>
{data ? <SyntaxHighlighter data={data} /> : null}
</section>
);
};
export default NetworkParameters;
export * from './network-parameters';

View File

@ -0,0 +1,73 @@
import { render, screen } from '@testing-library/react';
import { NetworkParametersTable } from './network-parameters';
import type { NetworkParametersQuery } from './__generated__/NetworkParametersQuery';
describe('NetworkParametersTable', () => {
it('renders correctly when it has network params', () => {
const data: NetworkParametersQuery = {
networkParameters: [
{
__typename: 'NetworkParameter',
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
value: '1',
},
{
__typename: 'NetworkParameter',
key: 'market.fee.factors.infrastructureFee',
value: '0.0005',
},
],
};
render(<NetworkParametersTable data={data} loading={false} />);
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
'Network Parameters'
);
const rows = screen.getAllByTestId('key-value-table-row');
expect(rows[0].children[0]).toHaveTextContent(
'market.fee.factors.infrastructureFee'
);
expect(rows[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
);
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');
});
it('renders the rows in ascending order', () => {
const data: NetworkParametersQuery = {
networkParameters: [
{
__typename: 'NetworkParameter',
key: 'market.fee.factors.infrastructureFee',
value: '0.0005',
},
{
__typename: 'NetworkParameter',
key: 'market.liquidityProvision.minLpStakeQuantumMultiple',
value: '1',
},
],
};
render(<NetworkParametersTable data={data} loading={false} />);
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
'Network Parameters'
);
const rows = screen.getAllByTestId('key-value-table-row');
expect(rows[0].children[0]).toHaveTextContent(
'market.fee.factors.infrastructureFee'
);
expect(rows[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
);
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');
});
it('does not render rows when is loading', () => {
render(<NetworkParametersTable data={undefined} loading={true} />);
expect(screen.getByTestId('network-param-header')).toHaveTextContent(
'Network Parameters'
);
expect(screen.queryByTestId('key-value-table-row')).not.toBeInTheDocument();
});
});

View File

@ -0,0 +1,118 @@
import { gql, useQuery } from '@apollo/client';
import {
AsyncRenderer,
KeyValueTable,
KeyValueTableRow,
SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
formatNumber,
t,
} from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import type {
NetworkParametersQuery,
NetworkParametersQuery_networkParameters,
} from './__generated__/NetworkParametersQuery';
import orderBy from 'lodash/orderBy';
const BIG_NUMBER_PARAMS = [
'spam.protection.delegation.min.tokens',
'validators.delegation.minAmount',
'reward.staking.delegation.minimumValidatorStake',
'reward.staking.delegation.maxPayoutPerParticipant',
'reward.staking.delegation.maxPayoutPerEpoch',
'spam.protection.voting.min.tokens',
'governance.proposal.freeform.minProposerBalance',
'governance.proposal.updateNetParam.minVoterBalance',
'governance.proposal.updateMarket.minVoterBalance',
'governance.proposal.asset.minVoterBalance',
'governance.proposal.updateNetParam.minProposerBalance',
'governance.proposal.freeform.minVoterBalance',
'spam.protection.proposal.min.tokens',
'governance.proposal.updateMarket.minProposerBalance',
'governance.proposal.asset.minProposerBalance',
];
export const renderRow = ({
key,
value,
}: NetworkParametersQuery_networkParameters) => {
const isSyntaxRow = isJsonObject(value);
return (
<KeyValueTableRow key={key} inline={!isSyntaxRow}>
{key}
{isSyntaxRow ? (
<SyntaxHighlighter data={JSON.parse(value)} />
) : isNaN(Number(value)) ? (
value
) : BIG_NUMBER_PARAMS.includes(key) ? (
addDecimalsFormatNumber(Number(value), 4)
) : (
formatNumber(Number(value), 4)
)}
</KeyValueTableRow>
);
};
export const isJsonObject = (str: string) => {
try {
return JSON.parse(str) && Object.keys(JSON.parse(str)).length > 0;
} catch (e) {
return false;
}
};
export const NETWORK_PARAMETERS_QUERY = gql`
query NetworkParametersQuery {
networkParameters {
key
value
}
}
`;
export interface NetworkParametersTableProps
extends React.HTMLAttributes<HTMLTableElement> {
data?: NetworkParametersQuery;
error?: Error;
loading: boolean;
}
export const NetworkParametersTable = ({
data,
error,
loading,
}: NetworkParametersTableProps) => (
<section>
<RouteTitle data-testid="network-param-header">
{t('Network Parameters')}
</RouteTitle>
<AsyncRenderer
data={data}
loading={loading}
error={error}
render={(data) => {
const ascParams = orderBy(
data.networkParameters || [],
(param) => param.key,
'asc'
);
return (
<KeyValueTable data-testid="parameters">
{(ascParams || []).map((row) => renderRow(row))}
</KeyValueTable>
);
}}
/>
</section>
);
export const NetworkParameters = () => {
const { data, loading, error } = useQuery<NetworkParametersQuery>(
NETWORK_PARAMETERS_QUERY
);
return <NetworkParametersTable data={data} error={error} loading={loading} />;
};

View File

@ -9,7 +9,6 @@ import { Party as PartySingle } from './parties/id';
import Txs from './txs';
import Validators from './validators';
import Genesis from './genesis';
import NetworkParameters from './network-parameters';
import { Block } from './blocks/id';
import { Blocks } from './blocks/home';
import { Tx } from './txs/id';
@ -18,6 +17,7 @@ import { PendingTxs } from './pending';
import flags from '../lib/flags';
import { t } from '@vegaprotocol/react-helpers';
import { Routes } from './route-names';
import { NetworkParameters } from './network-parameters';
const partiesRoutes = flags.parties
? [

View File

@ -2,7 +2,7 @@ import { forwardRef } from 'react';
import type { ColumnApi, ValueFormatterParams } from 'ag-grid-community';
import {
PriceCell,
formatNumber,
addDecimalsFormatNumber,
t,
addSummaryRows,
} from '@vegaprotocol/react-helpers';
@ -136,7 +136,7 @@ export const AccountsTable = forwardRef<AgGridReact, AccountsTableProps>(
value,
data,
}: AccountsTableValueFormatterParams) =>
formatNumber(value, data.asset.decimals)
addDecimalsFormatNumber(value, data.asset.decimals)
}
/>
</AgGrid>

View File

@ -1,7 +1,7 @@
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import type { OrderEvent_busEvents_event_Order } from './__generated__/OrderEvent';
import { formatNumber, t } from '@vegaprotocol/react-helpers';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import type { VegaTxState } from '@vegaprotocol/wallet';
import { VegaTxStatus } from '@vegaprotocol/wallet';
@ -76,7 +76,7 @@ export const OrderDialog = ({
{finalizedOrder.type === 'Limit' && finalizedOrder.market && (
<p>
{t(
`Price: ${formatNumber(
`Price: ${addDecimalsFormatNumber(
finalizedOrder.price,
finalizedOrder.market.decimalPlaces
)}`

View File

@ -3,7 +3,7 @@ import {
PriceCell,
Vol,
CumulativeVol,
formatNumber,
addDecimalsFormatNumber,
} from '@vegaprotocol/react-helpers';
interface OrderbookRowProps {
@ -33,7 +33,7 @@ export const OrderbookRow = React.memo(
<Vol value={bid} relativeValue={relativeBidVol} type="bid" />
<PriceCell
value={BigInt(price)}
valueFormatted={formatNumber(price, decimalPlaces)}
valueFormatted={addDecimalsFormatNumber(price, decimalPlaces)}
/>
<Vol value={ask} relativeValue={relativeAskVol} type="ask" />
<CumulativeVol

View File

@ -1,6 +1,10 @@
import { forwardRef } from 'react';
import type { ValueFormatterParams } from 'ag-grid-community';
import { PriceFlashCell, formatNumber, t } from '@vegaprotocol/react-helpers';
import {
PriceFlashCell,
addDecimalsFormatNumber,
t,
} from '@vegaprotocol/react-helpers';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import type {
Markets_markets,
@ -60,7 +64,7 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
type="rightAligned"
cellRenderer="PriceFlashCell"
valueFormatter={({ value, data }: ValueFormatterParams) =>
formatNumber(value, data.decimalPlaces)
addDecimalsFormatNumber(value, data.decimalPlaces)
}
/>
<AgGridColumn
@ -68,7 +72,7 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
field="data.bestOfferPrice"
type="rightAligned"
valueFormatter={({ value, data }: ValueFormatterParams) =>
formatNumber(value, data.decimalPlaces)
addDecimalsFormatNumber(value, data.decimalPlaces)
}
cellRenderer="PriceFlashCell"
/>
@ -78,7 +82,7 @@ export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
type="rightAligned"
cellRenderer="PriceFlashCell"
valueFormatter={({ value, data }: ValueFormatterParams) =>
formatNumber(value, data.decimalPlaces)
addDecimalsFormatNumber(value, data.decimalPlaces)
}
/>
<AgGridColumn headerName={t('Description')} field="name" />

View File

@ -1,4 +1,4 @@
import { formatNumber, t } from '@vegaprotocol/react-helpers';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import type { Stats as IStats, StatFields as IStatFields } from './types';
// Stats fields config. Keys will correspond to graphql queries when used, and values
@ -60,7 +60,7 @@ export const statsFields: { [key in keyof IStats]: IStatFields[] } = {
{
title: t('Total staked'),
formatter: (total: string) => {
return formatNumber(total, 18, 2);
return addDecimalsFormatNumber(total, 18, 2);
},
description: t('Sum of VEGA associated with a Vega key'),
},

View File

@ -2,7 +2,7 @@ import { forwardRef } from 'react';
import type { ValueFormatterParams } from 'ag-grid-community';
import {
PriceFlashCell,
formatNumber,
addDecimalsFormatNumber,
volumePrefix,
addDecimal,
t,
@ -100,7 +100,7 @@ export const PositionsTable = forwardRef<AgGridReact, PositionsTableProps>(
value,
data,
}: PositionsTableValueFormatterParams) =>
formatNumber(value, data.market.decimalPlaces)
addDecimalsFormatNumber(value, data.market.decimalPlaces)
}
/>
<AgGridColumn

View File

@ -27,12 +27,16 @@ export const getNumberFormat = memoize(
})
);
export const formatNumber = (
export const formatNumber = (rawValue: string | number, formatDecimals = 0) => {
return getNumberFormat(formatDecimals).format(Number(rawValue));
};
export const addDecimalsFormatNumber = (
rawValue: string | number,
decimalPlaces: number,
formatDecimals: number = decimalPlaces
) => {
const x = addDecimal(rawValue, decimalPlaces);
return getNumberFormat(formatDecimals).format(Number(x));
return formatNumber(x, formatDecimals);
};

View File

@ -4,7 +4,7 @@ import { forwardRef, useMemo } from 'react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import type { TradeFields } from './__generated__/TradeFields';
import {
formatNumber,
addDecimalsFormatNumber,
getDateTimeFormat,
t,
} from '@vegaprotocol/react-helpers';
@ -67,7 +67,7 @@ export const TradesTable = forwardRef<AgGridReact, TradesTableProps>(
field="price"
cellClass={changeCellClass('price')}
valueFormatter={({ value, data }: ValueFormatterParams) => {
return formatNumber(value, data.market.decimalPlaces);
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
}}
/>
<AgGridColumn

View File

@ -12,7 +12,7 @@ const props: KeyValueTableProps = {
it('Renders the correct elements', () => {
const { container } = render(
<KeyValueTable {...props}>
<KeyValueTableRow>
<KeyValueTableRow inline={true}>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>
@ -38,10 +38,10 @@ it('Renders the correct elements', () => {
expect(rows[1].children[1]).toHaveTextContent('My value 2');
});
it('Applies numeric class if prop is passed', () => {
it('Applies numeric class if prop is passed row not inline', () => {
render(
<KeyValueTable {...props} numerical={true}>
<KeyValueTableRow>
<KeyValueTableRow inline={false}>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>
@ -51,12 +51,35 @@ it('Applies numeric class if prop is passed', () => {
expect(screen.getByTestId('key-value-table')).toHaveClass(
'w-full border-collapse mb-8 [border-spacing:0] break-all'
);
expect(screen.getByTestId('key-value-table-row')).toHaveClass(
' flex gap-1 flex-wrap justify-between border-b first:border-t border-black dark:border-white flex-col items-start'
);
});
it('Applies numeric class if prop is passed row inline', () => {
render(
<KeyValueTable {...props} numerical={true}>
<KeyValueTableRow inline={true}>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>
</KeyValueTable>
);
expect(screen.getByTestId('key-value-table')).toHaveClass(
'w-full border-collapse mb-8 [border-spacing:0] break-all'
);
expect(screen.getByTestId('key-value-table-row')).toHaveClass(
'flex gap-1 flex-wrap justify-between border-b first:border-t border-black dark:border-white flex-row items-center'
);
});
it('Applies muted class if prop is passed', () => {
render(
<KeyValueTable {...props} muted={true}>
<KeyValueTableRow>
<KeyValueTableRow inline={false}>
<span>My label</span>
<span>My value</span>
</KeyValueTableRow>

View File

@ -62,6 +62,7 @@ export interface KeyValueTableRowProps
className?: string;
numerical?: boolean; // makes all values monospace
muted?: boolean;
inline?: boolean;
}
export const KeyValueTableRow = ({
@ -69,25 +70,28 @@ export const KeyValueTableRow = ({
className,
muted,
numerical,
inline = true,
}: KeyValueTableRowProps) => {
const dlClassName = classNames(
'flex flex-wrap justify-between items-center border-b first:border-t border-black dark:border-white',
'flex gap-1 flex-wrap justify-between border-b first:border-t border-black dark:border-white',
{ 'flex-col items-start': !inline },
{ 'flex-row items-center': inline },
{
'border-black/60 dark:border-white/60 first:[border-top:none] last:[border-bottom:none]':
muted,
},
className
);
const dtClassName = `break-normal font-medium uppercase align-top p-4`;
const dtClassName = `break-words font-medium uppercase align-top p-4 capitalize`;
const ddClassName = classNames(
'align-top p-4 text-black/60 dark:text-white/60 break-normal',
'align-top p-4 text-black/60 dark:text-white/60 break-words',
{
'font-mono': numerical,
}
);
return (
<dl className={dlClassName}>
<dl className={dlClassName} data-testid="key-value-table-row">
<dt className={dtClassName}>{children[0]}</dt>
<dd className={ddClassName}>{children[1]}</dd>
</dl>