chore: ag grid row data and value can be undefined (#1709)

* chore: improve ag-grid typings - fix runtime bug

* chore: improve ag-grid typings - add numeric type checking

* chore: improve ag-grid typings - remove redundant cl

* chore: improve ag-grid typings - remove redundant cl

* chore: improve ag-grid typings - add some basic unit test

* chore: improve ag-grid typings - add some basic unit test

Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
macqbat 2022-10-12 11:06:19 +02:00 committed by GitHub
parent 50df63c858
commit 00381a2b3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 103 additions and 35 deletions

View File

@ -1,7 +1,11 @@
import { forwardRef, useState } from 'react'; import { forwardRef, useState } from 'react';
import type { ValueFormatterParams } from 'ag-grid-community'; import type { ValueFormatterParams } from 'ag-grid-community';
import type { Asset } from '@vegaprotocol/assets'; import type { Asset } from '@vegaprotocol/assets';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers'; import {
addDecimalsFormatNumber,
isNumeric,
t,
} from '@vegaprotocol/react-helpers';
import type { import type {
ValueProps, ValueProps,
VegaICellRendererParams, VegaICellRendererParams,
@ -95,7 +99,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
cellRenderer={({ cellRenderer={({
value, value,
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => { }: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
return ( return value ? (
<ButtonLink <ButtonLink
data-testid="deposit" data-testid="deposit"
onClick={() => { onClick={() => {
@ -104,7 +108,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
> >
{value} {value}
</ButtonLink> </ButtonLink>
); ) : null;
}} }}
maxWidth={300} maxWidth={300}
/> />
@ -120,6 +124,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
}: VegaValueFormatterParams<AccountFields, 'deposited'>) => }: VegaValueFormatterParams<AccountFields, 'deposited'>) =>
data && data &&
data.asset && data.asset &&
isNumeric(value) &&
addDecimalsFormatNumber(value, data.asset.decimals) addDecimalsFormatNumber(value, data.asset.decimals)
} }
maxWidth={300} maxWidth={300}
@ -162,7 +167,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
cellRenderer={({ cellRenderer={({
data, data,
}: VegaICellRendererParams<AccountFields>) => { }: VegaICellRendererParams<AccountFields>) => {
return ( return data ? (
<div className="flex gap-2 justify-end"> <div className="flex gap-2 justify-end">
<Button <Button
size="xs" size="xs"
@ -184,7 +189,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
{t('Withdraw')} {t('Withdraw')}
</Button> </Button>
</div> </div>
); ) : null;
}} }}
/> />
</AgGrid> </AgGrid>

View File

@ -1,6 +1,7 @@
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
isNumeric,
PriceCell, PriceCell,
t, t,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
@ -48,7 +49,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
valueFormatter={({ valueFormatter={({
value, value,
}: VegaValueFormatterParams<AccountFields, 'type'>) => }: VegaValueFormatterParams<AccountFields, 'type'>) =>
AccountTypeMapping[value] value ? AccountTypeMapping[value] : ''
} }
/> />
<AgGridColumn <AgGridColumn
@ -81,7 +82,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
value, value,
data, data,
}: VegaValueFormatterParams<AccountFields, 'balance'>) => { }: VegaValueFormatterParams<AccountFields, 'balance'>) => {
if (data && data.asset) { if (data && data.asset && isNumeric(value)) {
return addDecimalsFormatNumber(value, data.asset.decimals); return addDecimalsFormatNumber(value, data.asset.decimals);
} }
return '-'; return '-';

View File

@ -4,6 +4,7 @@ import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
getDateTimeFormat, getDateTimeFormat,
truncateByChars, truncateByChars,
isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@ -36,7 +37,9 @@ export const DepositsTable = ({ deposits }: DepositsTableProps) => {
value, value,
data, data,
}: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => { }: VegaValueFormatterParams<DepositFieldsFragment, 'amount'>) => {
return addDecimalsFormatNumber(value, data.asset.decimals); return isNumeric(value) && data
? addDecimalsFormatNumber(value, data.asset.decimals)
: null;
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -48,7 +51,7 @@ export const DepositsTable = ({ deposits }: DepositsTableProps) => {
DepositFieldsFragment, DepositFieldsFragment,
'createdTimestamp' 'createdTimestamp'
>) => { >) => {
return getDateTimeFormat().format(new Date(value)); return value ? getDateTimeFormat().format(new Date(value)) : '';
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -57,7 +60,7 @@ export const DepositsTable = ({ deposits }: DepositsTableProps) => {
valueFormatter={({ valueFormatter={({
value, value,
}: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => { }: VegaValueFormatterParams<DepositFieldsFragment, 'status'>) => {
return DepositStatusMapping[value]; return value ? DepositStatusMapping[value] : '';
}} }}
/> />
<AgGridColumn <AgGridColumn

View File

@ -7,6 +7,7 @@ import {
positiveClassNames, positiveClassNames,
negativeClassNames, negativeClassNames,
t, t,
isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { Side } from '@vegaprotocol/types'; import { Side } from '@vegaprotocol/types';
import { AgGridColumn } from 'ag-grid-react'; import { AgGridColumn } from 'ag-grid-react';
@ -81,7 +82,7 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
valueFormatter={({ valueFormatter={({
value, value,
}: VegaValueFormatterParams<Trade, 'createdAt'>) => { }: VegaValueFormatterParams<Trade, 'createdAt'>) => {
return getDateTimeFormat().format(new Date(value)); return value ? getDateTimeFormat().format(new Date(value)) : '';
}} }}
/> />
</AgGrid> </AgGrid>
@ -93,7 +94,7 @@ const formatPrice = ({
value, value,
data, data,
}: VegaValueFormatterParams<Trade, 'price'>) => { }: VegaValueFormatterParams<Trade, 'price'>) => {
if (!data.market) { if (!data?.market || !isNumeric(value)) {
return '-'; return '-';
} }
const asset = const asset =
@ -107,7 +108,7 @@ const formatPrice = ({
const formatSize = (partyId: string) => { const formatSize = (partyId: string) => {
return ({ value, data }: VegaValueFormatterParams<Trade, 'size'>) => { return ({ value, data }: VegaValueFormatterParams<Trade, 'size'>) => {
if (!data.market) { if (!data?.market || !isNumeric(value)) {
return '-'; return '-';
} }
let prefix = ''; let prefix = '';
@ -144,7 +145,7 @@ const formatTotal = ({
value, value,
data, data,
}: VegaValueFormatterParams<Trade, 'price'>) => { }: VegaValueFormatterParams<Trade, 'price'>) => {
if (!data?.market) { if (!data?.market || !isNumeric(value)) {
return '-'; return '-';
} }
const asset = const asset =
@ -189,7 +190,7 @@ const formatFee = (partyId: string) => {
Trade, Trade,
'market.tradableInstrument.instrument.product' 'market.tradableInstrument.instrument.product'
>) => { >) => {
if (!value?.settlementAsset) { if (!value?.settlementAsset || !data) {
return '-'; return '-';
} }
const asset = value.settlementAsset; const asset = value.settlementAsset;

View File

@ -1,6 +1,6 @@
import { act, render, screen, within } from '@testing-library/react'; import { act, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { addDecimal, getDateTimeFormat } from '@vegaprotocol/react-helpers'; import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types'; import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import { import {
OrderRejectionReasonMapping, OrderRejectionReasonMapping,
@ -108,7 +108,7 @@ describe('OrderListTable', () => {
OrderTypeMapping[limitOrder.type || OrderType.TYPE_LIMIT], OrderTypeMapping[limitOrder.type || OrderType.TYPE_LIMIT],
OrderStatusMapping[limitOrder.status], OrderStatusMapping[limitOrder.status],
'5', '5',
addDecimal(limitOrder.price, limitOrder.market?.decimalPlaces ?? 0), '-',
`${ `${
OrderTimeInForceMapping[limitOrder.timeInForce] OrderTimeInForceMapping[limitOrder.timeInForce]
}: ${getDateTimeFormat().format(new Date(limitOrder.expiresAt ?? ''))}`, }: ${getDateTimeFormat().format(new Date(limitOrder.expiresAt ?? ''))}`,

View File

@ -14,6 +14,7 @@ import {
t, t,
positiveClassNames, positiveClassNames,
negativeClassNames, negativeClassNames,
isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@ -128,7 +129,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
value, value,
data, data,
}: VegaValueFormatterParams<Order, 'size'>) => { }: VegaValueFormatterParams<Order, 'size'>) => {
if (!data.market) { if (!data?.market || !isNumeric(value)) {
return '-'; return '-';
} }
const prefix = data const prefix = data
@ -148,8 +149,8 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
value, value,
}: VegaValueFormatterParams<Order, 'type'>) => { }: VegaValueFormatterParams<Order, 'type'>) => {
if (!value) return '-'; if (!value) return '-';
if (order.peggedOrder) return t('Pegged'); if (order?.peggedOrder) return t('Pegged');
if (order.liquidityProvision) return t('Liquidity provision'); if (order?.liquidityProvision) return t('Liquidity provision');
return OrderTypeMapping[value]; return OrderTypeMapping[value];
}} }}
/> />
@ -161,11 +162,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
}: VegaValueFormatterParams<Order, 'status'>) => { }: VegaValueFormatterParams<Order, 'status'>) => {
if (value === OrderStatus.STATUS_REJECTED) { if (value === OrderStatus.STATUS_REJECTED) {
return `${OrderStatusMapping[value]}: ${ return `${OrderStatusMapping[value]}: ${
data.rejectionReason && data?.rejectionReason &&
OrderRejectionReasonMapping[data.rejectionReason] OrderRejectionReasonMapping[data.rejectionReason]
}`; }`;
} }
return OrderStatusMapping[value]; return value ? OrderStatusMapping[value] : '';
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -177,7 +178,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
data, data,
value, value,
}: VegaValueFormatterParams<Order, 'remaining'>) => { }: VegaValueFormatterParams<Order, 'remaining'>) => {
if (!data.market) { if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
return '-'; return '-';
} }
const dps = data.market.positionDecimalPlaces; const dps = data.market.positionDecimalPlaces;
@ -198,7 +199,11 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
value, value,
data, data,
}: VegaValueFormatterParams<Order, 'price'>) => { }: VegaValueFormatterParams<Order, 'price'>) => {
if (!data.market || data.type === OrderType.TYPE_MARKET) { if (
!data?.market ||
data.type === OrderType.TYPE_MARKET ||
!isNumeric(value)
) {
return '-'; return '-';
} }
return addDecimal(value, data.market.decimalPlaces); return addDecimal(value, data.market.decimalPlaces);
@ -212,7 +217,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
}: VegaValueFormatterParams<Order, 'timeInForce'>) => { }: VegaValueFormatterParams<Order, 'timeInForce'>) => {
if ( if (
value === OrderTimeInForce.TIME_IN_FORCE_GTT && value === OrderTimeInForce.TIME_IN_FORCE_GTT &&
data.expiresAt data?.expiresAt
) { ) {
const expiry = getDateTimeFormat().format( const expiry = getDateTimeFormat().format(
new Date(data.expiresAt) new Date(data.expiresAt)
@ -220,7 +225,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
return `${OrderTimeInForceMapping[value]}: ${expiry}`; return `${OrderTimeInForceMapping[value]}: ${expiry}`;
} }
return OrderTimeInForceMapping[value]; return value ? OrderTimeInForceMapping[value] : '';
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -245,7 +250,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
field="status" field="status"
cellRenderer={({ data }: VegaICellRendererParams<Order>) => { cellRenderer={({ data }: VegaICellRendererParams<Order>) => {
if (isOrderAmendable(data)) { if (isOrderAmendable(data)) {
return ( return data ? (
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
data-testid="edit" data-testid="edit"
@ -262,7 +267,7 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
{t('Cancel')} {t('Cancel')}
</Button> </Button>
</div> </div>
); ) : null;
} }
return null; return null;

View File

@ -1,5 +1,10 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { formatNumber, formatNumberPercentage, toNumberParts } from './number'; import {
formatNumber,
formatNumberPercentage,
toNumberParts,
isNumeric,
} from './number';
describe('formatNumber and formatNumberPercentage', () => { describe('formatNumber and formatNumberPercentage', () => {
it.each([ it.each([
@ -46,3 +51,41 @@ describe('toNumberParts', () => {
expect(toNumberParts(v, d)).toStrictEqual(o); expect(toNumberParts(v, d)).toStrictEqual(o);
}); });
}); });
describe('isNumeric', () => {
it.each([
{ i: null, o: false },
{ i: undefined, o: false },
{ i: 1, o: true },
{ i: '1', o: true },
{ i: '-1', o: true },
{ i: 0.1, o: true },
{ i: '.1', o: true },
{ i: '-.1', o: true },
{ i: 123, o: true },
{ i: -123, o: true },
{ i: '123', o: true },
{ i: '123.01', o: true },
{ i: '-123.01', o: true },
{ i: '--123.01', o: false },
{ i: '123.', o: false },
{ i: '123.1.1', o: false },
{ i: new BigNumber(123), o: true },
{ i: new BigNumber(123.123), o: true },
{ i: new BigNumber(123.123).toString(), o: true },
{ i: new BigNumber(123), o: true },
{ i: Infinity, o: false },
{ i: NaN, o: false },
])(
'returns correct results',
({
i,
o,
}: {
i: number | string | undefined | null | BigNumber;
o: boolean;
}) => {
expect(isNumeric(i)).toStrictEqual(o);
}
);
});

View File

@ -96,3 +96,7 @@ export const useNumberParts = (
): [integers: string, decimalPlaces: string] => { ): [integers: string, decimalPlaces: string] => {
return React.useMemo(() => toNumberParts(value, decimals), [decimals, value]); return React.useMemo(() => toNumberParts(value, decimals), [decimals, value]);
}; };
export const isNumeric = (
value?: string | number | BigNumber | null
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));

View File

@ -16,8 +16,8 @@ type RowHelper<TObj, TRow, TField extends Field> = Omit<
TObj, TObj,
'data' | 'value' 'data' | 'value'
> & { > & {
data: TRow; data?: TRow;
value: Get<TRow, TField>; value?: Get<TRow, TField>;
}; };
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper< export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<

View File

@ -4,6 +4,7 @@ import {
t, t,
truncateByChars, truncateByChars,
addDecimalsFormatNumber, addDecimalsFormatNumber,
isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { import type {
TypedDataAgGrid, TypedDataAgGrid,
@ -60,7 +61,9 @@ export const PendingWithdrawalsTable = (
value, value,
data, data,
}: VegaValueFormatterParams<WithdrawalFields, 'amount'>) => { }: VegaValueFormatterParams<WithdrawalFields, 'amount'>) => {
return addDecimalsFormatNumber(value, data.asset.decimals); return isNumeric(value) && data?.asset
? addDecimalsFormatNumber(value, data.asset.decimals)
: null;
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -105,7 +108,7 @@ export const PendingWithdrawalsTable = (
WithdrawalFields, WithdrawalFields,
'createdTimestamp' 'createdTimestamp'
>) => { >) => {
return getDateTimeFormat().format(new Date(value)); return value ? getDateTimeFormat().format(new Date(value)) : '';
}} }}
/> />
<AgGridColumn <AgGridColumn

View File

@ -4,6 +4,7 @@ import {
t, t,
truncateByChars, truncateByChars,
addDecimalsFormatNumber, addDecimalsFormatNumber,
isNumeric,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import type { import type {
TypedDataAgGrid, TypedDataAgGrid,
@ -37,7 +38,9 @@ export const WithdrawalsTable = (props: TypedDataAgGrid<WithdrawalFields>) => {
value, value,
data, data,
}: VegaValueFormatterParams<WithdrawalFields, 'amount'>) => { }: VegaValueFormatterParams<WithdrawalFields, 'amount'>) => {
return addDecimalsFormatNumber(value, data.asset.decimals); return isNumeric(value) && data?.asset
? addDecimalsFormatNumber(value, data.asset.decimals)
: '';
}} }}
/> />
<AgGridColumn <AgGridColumn
@ -64,7 +67,7 @@ export const WithdrawalsTable = (props: TypedDataAgGrid<WithdrawalFields>) => {
WithdrawalFields, WithdrawalFields,
'withdrawnTimestamp' 'withdrawnTimestamp'
>) => { >) => {
const ts = data.withdrawnTimestamp; const ts = data?.withdrawnTimestamp;
if (!ts) return '-'; if (!ts) return '-';
return getDateTimeFormat().format(new Date(ts)); return getDateTimeFormat().format(new Date(ts));
}} }}