fix(markets): back with updating by grid api (#4059)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
37247a504a
commit
9184c2374c
@ -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>
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
@ -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]
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user