fix(markets): back with updating by grid api (#4059)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Maciek 2023-06-10 16:49:10 +02:00 committed by GitHub
parent 37247a504a
commit 9184c2374c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 318 additions and 195 deletions

View File

@ -1,29 +1,14 @@
import { forwardRef } from 'react';
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type {
VegaValueGetterParams,
VegaValueFormatterParams,
VegaICellRendererParams,
TypedDataAgGrid,
} from '@vegaprotocol/datagrid';
import { COL_DEFS } from '@vegaprotocol/datagrid';
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import {
AgGridLazy as AgGrid,
PriceFlashCell,
MarketNameCell,
SetFilter,
} from '@vegaprotocol/datagrid';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
import type { AgGridReact } from 'ag-grid-react';
import * as Schema from '@vegaprotocol/types';
import type { MarketMaybeWithData } from '../../markets-provider';
import { MarketTableActions } from './market-table-actions';
import { OracleStatus } from './oracle-status';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
const { MarketTradingMode, AuctionTrigger } = Schema;
import { useColumnDefs } from './use-column-defs';
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
@ -51,199 +36,34 @@ const MarketName = (props: MarketNameCellProps) => (
</>
);
const defaultColDef = {
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
minWidth: 100,
};
export const MarketListTable = forwardRef<
AgGridReact,
TypedDataAgGrid<MarketMaybeWithData> & {
onMarketClick: (marketId: string, metaKey?: boolean) => void;
}
>(({ onMarketClick, ...props }, ref) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const columnDefs = useColumnDefs({ onMarketClick });
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
getRowId={getRowId}
ref={ref}
defaultColDef={{
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
minWidth: 100,
}}
defaultColDef={defaultColDef}
columnDefs={columnDefs}
suppressCellFocus
components={{ PriceFlashCell, MarketName }}
storeKey="allMarkets"
{...props}
>
<AgGridColumn
headerName={t('Market')}
field="tradableInstrument.instrument.code"
cellRenderer="MarketName"
cellRendererParams={{ onMarketClick }}
/>
<AgGridColumn
headerName={t('Description')}
field="tradableInstrument.instrument.name"
/>
<AgGridColumn
headerName={t('Trading mode')}
field="tradingMode"
minWidth={170}
valueFormatter={({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return undefined;
const { trigger, marketTradingMode } = data.data;
return marketTradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
- ${Schema.AuctionTriggerMapping[trigger]}`
: Schema.MarketTradingModeMapping[marketTradingMode];
}}
filter={SetFilter}
filterParams={{
set: Schema.MarketTradingModeMapping,
}}
/>
<AgGridColumn
headerName={t('Status')}
field="state"
valueFormatter={({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
}}
filter={SetFilter}
filterParams={{
set: Schema.MarketStateMapping,
}}
/>
<AgGridColumn
headerName={t('Best bid')}
field="data.bestBidPrice"
type="rightAligned"
cellRenderer="PriceFlashCell"
filter="agNumberColumnFilter"
valueGetter={({
data,
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
return data?.data?.bestBidPrice === undefined
? undefined
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
}}
valueFormatter={({
data,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestBidPrice'
>) =>
data?.data?.bestBidPrice === undefined
? undefined
: addDecimalsFormatNumber(
data.data.bestBidPrice,
data.decimalPlaces
)
}
/>
<AgGridColumn
headerName={t('Best offer')}
field="data.bestOfferPrice"
type="rightAligned"
cellRenderer="PriceFlashCell"
filter="agNumberColumnFilter"
valueGetter={({
data,
}: VegaValueGetterParams<
MarketMaybeWithData,
'data.bestOfferPrice'
>) => {
return data?.data?.bestOfferPrice === undefined
? undefined
: toBigNum(
data?.data?.bestOfferPrice,
data.decimalPlaces
).toNumber();
}}
valueFormatter={({
data,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestOfferPrice'
>) =>
data?.data?.bestOfferPrice === undefined
? undefined
: addDecimalsFormatNumber(
data.data.bestOfferPrice,
data.decimalPlaces
)
}
/>
<AgGridColumn
headerName={t('Mark price')}
field="data.markPrice"
type="rightAligned"
cellRenderer="PriceFlashCell"
filter="agNumberColumnFilter"
valueGetter={({
data,
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
return data?.data?.markPrice === undefined
? undefined
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
}}
valueFormatter={({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
data?.data?.bestOfferPrice === undefined
? undefined
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces)
}
/>
<AgGridColumn
headerName={t('Settlement asset')}
field="tradableInstrument.instrument.product.settlementAsset.symbol"
cellRenderer={({
data,
}: VegaICellRendererParams<
MarketMaybeWithData,
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value =
data?.tradableInstrument.instrument.product.settlementAsset;
return value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
}}
>
{value.symbol}
</ButtonLink>
) : (
''
);
}}
/>
<AgGridColumn
colId="market-actions"
field="id"
{...COL_DEFS.actions}
cellRenderer={({
data,
}: VegaICellRendererParams<MarketMaybeWithData>) => {
if (!data) return null;
return (
<MarketTableActions
marketId={data.id}
assetId={
data.tradableInstrument.instrument.product.settlementAsset.id
}
/>
);
}}
/>
</AgGrid>
/>
);
});

View File

@ -0,0 +1,107 @@
import { render, screen, act, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as DataProviders from '@vegaprotocol/data-provider';
import { MockedProvider } from '@apollo/react-testing';
import type { MarketMaybeWithData } from '../../markets-provider';
import { MarketsContainer } from './markets-container';
const market = {
id: 'id-1',
tradableInstrument: {
instrument: {
product: { settlementAsset: { id: 'assetId-1' } },
},
},
decimalPlaces: 1,
positionDecimalPlaces: 1,
state: 'STATE_ACTIVE',
tradingMode: 'TRADING_MODE_OPENING_AUCTION',
data: {
bestBidPrice: 100,
},
} as unknown as MarketMaybeWithData;
describe('MarketsContainer', () => {
it('context menu should stay open', async () => {
const spyOnSelect = jest.fn();
jest
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
.mockImplementation(() => {
return {
error: null,
reload: jest.fn(),
data: [market],
};
});
let rerenderRef: (ui: React.ReactElement) => void;
await act(async () => {
const { rerender } = render(
<MockedProvider>
<MarketsContainer onSelect={spyOnSelect} />
</MockedProvider>
);
rerenderRef = rerender;
});
// make sure ag grid is finished initializaing
const rowContainer = await screen.findByRole('rowgroup', {
name: (_name, element) =>
element.classList.contains('ag-center-cols-container'),
});
expect(within(rowContainer).getAllByRole('row')).toHaveLength(1);
expect(
screen.getByRole('rowgroup', {
name: (_name, element) =>
element.classList.contains('ag-pinned-right-cols-container'),
})
).toBeInTheDocument();
// open the dropdown
await userEvent.click(
screen.getByRole('button', {
name: (_name, element) =>
(element.parentNode as Element)?.getAttribute('id') ===
'cell-market-actions-8',
})
);
await checkDropdown();
// reset the mock and rerender so the component
// updates with new data
jest
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
.mockImplementation(() => {
return {
error: null,
reload: jest.fn(),
data: [{ ...market, state: 'STATE_PENDING' }],
};
});
// @ts-ignore we await the act above so rerenderRef is definitely defined
rerenderRef(
<MockedProvider mocks={[]}>
<MarketsContainer onSelect={spyOnSelect} />
</MockedProvider>
);
// make sure dropdown is still open
await checkDropdown();
async function checkDropdown() {
const dropdownContent = await screen.findByTestId(
'market-actions-content'
);
expect(dropdownContent).toBeInTheDocument();
expect(
within(dropdownContent).getByRole('menuitem', {
name: 'Copy Market ID',
})
).toBeInTheDocument();
}
});
});

View File

@ -0,0 +1,196 @@
import { useMemo } from 'react';
import type { ColDef } from 'ag-grid-community';
import { t } from '@vegaprotocol/i18n';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
VegaValueGetterParams,
} from '@vegaprotocol/datagrid';
import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import type { MarketMaybeWithData } from '../../markets-provider';
import { MarketTableActions } from './market-table-actions';
interface Props {
onMarketClick: (marketId: string, metaKey?: boolean) => void;
}
const { MarketTradingMode, AuctionTrigger } = Schema;
export const useColumnDefs = ({ onMarketClick }: Props) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
return useMemo<ColDef[]>(
() => [
{
headerName: t('Market'),
field: 'tradableInstrument.instrument.code',
cellRenderer: 'MarketName',
cellRendererParams: { onMarketClick },
},
{
headerName: t('Description'),
field: 'tradableInstrument.instrument.name',
},
{
headerName: t('Trading mode'),
field: 'tradingMode',
minWidth: 170,
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return '-';
const { trigger, marketTradingMode } = data.data;
return marketTradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
- ${Schema.AuctionTriggerMapping[trigger]}`
: Schema.MarketTradingModeMapping[marketTradingMode];
},
filter: SetFilter,
filterParams: {
set: Schema.MarketTradingModeMapping,
},
},
{
headerName: t('Status'),
field: 'state',
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
},
filter: SetFilter,
filterParams: {
set: Schema.MarketStateMapping,
},
},
{
headerName: t('Best bid'),
field: 'data.bestBidPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({
data,
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
return data?.data?.bestBidPrice === undefined
? undefined
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestBidPrice'
>) =>
data?.data?.bestBidPrice === undefined
? '-'
: addDecimalsFormatNumber(
data.data.bestBidPrice,
data.decimalPlaces
),
},
{
headerName: t('Best offer'),
field: 'data.bestOfferPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({
data,
}: VegaValueGetterParams<
MarketMaybeWithData,
'data.bestOfferPrice'
>) => {
return data?.data?.bestOfferPrice === undefined
? undefined
: toBigNum(
data?.data?.bestOfferPrice,
data.decimalPlaces
).toNumber();
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestOfferPrice'
>) =>
data?.data?.bestOfferPrice === undefined
? '-'
: addDecimalsFormatNumber(
data.data.bestOfferPrice,
data.decimalPlaces
),
},
{
headerName: t('Mark price'),
field: 'data.markPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({
data,
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
return data?.data?.markPrice === undefined
? undefined
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
data?.data?.bestOfferPrice === undefined
? '-'
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
},
{
headerName: t('Settlement asset'),
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
cellRenderer: ({
data,
}: VegaICellRendererParams<
MarketMaybeWithData,
'tradableInstrument.instrument.product.settlementAsset.symbol'
>) => {
const value =
data?.tradableInstrument.instrument.product.settlementAsset;
return value ? (
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value.id, e.target as HTMLElement);
}}
>
{value.symbol}
</ButtonLink>
) : (
''
);
},
},
{
colId: 'market-actions',
field: 'id',
...COL_DEFS.actions,
cellRenderer: ({
data,
}: VegaICellRendererParams<MarketMaybeWithData>) => {
if (!data) return null;
return (
<MarketTableActions
marketId={data.id}
assetId={
data.tradableInstrument.instrument.product.settlementAsset.id
}
/>
);
},
},
],
[onMarketClick, openAssetDetailsDialog]
);
};