Feature/373 pagination of orders (#809)

* feat(#373): switch orders to infinite scroll mode

* feat(#373): fix orders tests

* feat(#218): improve typing in order-list

* feat(#373): add generic getRows for infinite rowModelType
This commit is contained in:
Bartłomiej Głownia 2022-07-21 15:25:37 +02:00 committed by GitHub
parent be7690a73e
commit b88fda787c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 562 additions and 870 deletions

View File

@ -70,10 +70,10 @@ describe('orders', () => {
it('orders are sorted by most recent order', () => {
const expectedOrderList = [
'UNIDAI.MF21',
'AAVEDAI.MF21',
'TSLA.QM21',
'BTCUSD.MF21',
'AAVEDAI.MF21',
'UNIDAI.MF21',
];
cy.getByTestId('tab-orders')

View File

@ -1,6 +1,9 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { Orders, Orders_party_orders } from '@vegaprotocol/orders';
import type {
Orders,
Orders_party_ordersConnection_edges_node,
} from '@vegaprotocol/orders';
import {
OrderStatus,
OrderTimeInForce,
@ -9,7 +12,7 @@ import {
} from '@vegaprotocol/types';
export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
const orders: Orders_party_orders[] = [
const orders: Orders_party_ordersConnection_edges_node[] = [
{
__typename: 'Order',
id: '066468C06549101DAF7BC51099E1412A0067DC08C246B7D8013C9D0CBF1E8EE7',
@ -34,7 +37,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
remaining: '0',
price: '20000000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2020, 1, 1).toISOString(),
createdAt: new Date(2020, 1, 30).toISOString(),
updatedAt: null,
expiresAt: null,
rejectionReason: null,
@ -63,7 +66,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
remaining: '0',
price: '100',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date().toISOString(),
createdAt: new Date(2020, 1, 29).toISOString(),
updatedAt: null,
expiresAt: null,
rejectionReason: null,
@ -92,7 +95,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
remaining: '0',
price: '20000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2022, 5, 10).toISOString(),
createdAt: new Date(2020, 1, 28).toISOString(),
updatedAt: null,
expiresAt: null,
rejectionReason: null,
@ -121,17 +124,35 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
remaining: '0',
price: '100000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2022, 7, 15).toISOString(),
createdAt: new Date(2020, 1, 27).toISOString(),
updatedAt: null,
expiresAt: null,
rejectionReason: null,
},
];
const defaultResult = {
const defaultResult: Orders = {
party: {
id: Cypress.env('VEGA_PUBLIC_KEY'),
orders,
ordersConnection: {
__typename: 'OrderConnection',
edges: orders.map((f) => {
return {
__typename: 'OrderEdge',
node: f,
cursor: f.id,
};
}),
pageInfo: {
__typename: 'PageInfo',
startCursor:
'066468C06549101DAF7BC51099E1412A0067DC08C246B7D8013C9D0CBF1E8EE7',
endCursor:
'94737d2bafafa4bc3b80a56ef084ae52a983b91aa067c31e243c61a0f962a836',
hasNextPage: false,
hasPreviousPage: false,
},
},
__typename: 'Party',
},
};

View File

@ -1,13 +1,12 @@
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useRef, useMemo } from 'react';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import {
useDataProvider,
makeInfiniteScrollGetRows,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { FillsTable } from './fills-table';
import type {
IGetRowsParams,
BodyScrollEvent,
BodyScrollEndEvent,
} from 'ag-grid-community';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import { fillsDataProvider as dataProvider } from './fills-data-provider';
import type { Fills_party_tradesConnection_edges } from './__generated__/Fills';
@ -90,36 +89,12 @@ export const FillsManager = ({ partyId }: FillsManagerProps) => {
totalCountRef.current = totalCount;
dataRef.current = data;
const getRows = async ({
successCallback,
failCallback,
startRow,
endRow,
}: IGetRowsParams) => {
startRow += newRows.current;
endRow += newRows.current;
try {
if (dataRef.current && dataRef.current.indexOf(null) < endRow) {
await load();
}
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow).map((edge) => edge?.node)
: [];
let lastRow = -1;
if (totalCountRef.current !== undefined) {
if (!totalCountRef.current) {
lastRow = 0;
} else if (totalCountRef.current <= endRow) {
lastRow = totalCountRef.current;
}
} else if (rowsThisBlock.length < endRow - startRow) {
lastRow = rowsThisBlock.length;
}
successCallback(rowsThisBlock, lastRow);
} catch (e) {
failCallback();
}
};
const getRows = makeInfiniteScrollGetRows<Fills_party_tradesConnection_edges>(
newRows,
dataRef,
totalCountRef,
load
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {

View File

@ -3,15 +3,12 @@ import type { Props } from './fills-table';
import type { AgGridReact } from 'ag-grid-react';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useCallback, useRef } from 'react';
import { makeInfiniteScrollGetRows } from '@vegaprotocol/react-helpers';
import { FillsTable } from './fills-table';
import { generateFills, generateFill } from './test-helpers';
import type { Fills_party_tradesConnection_edges } from './__generated__/Fills';
import type { FillsSub_trades } from './__generated__/FillsSub';
import type {
IGetRowsParams,
BodyScrollEvent,
BodyScrollEndEvent,
} from 'ag-grid-community';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
export default {
component: FillsTable,
@ -73,7 +70,7 @@ const useDataProvider = ({
const insertionData = getData(start, end);
data.splice(start, end - start, ...insertionData);
insert({ data, totalCount, insertionData });
return Promise.resolve();
return Promise.resolve(insertionData);
},
totalCount,
};
@ -152,39 +149,12 @@ const PaginationManager = ({ pagination }: PaginationManagerProps) => {
totalCountRef.current = totalCount;
dataRef.current = data;
const getRows = async ({
successCallback,
failCallback,
startRow,
endRow,
}: IGetRowsParams) => {
startRow += newRows.current;
endRow += newRows.current;
try {
if (
dataRef.current &&
dataRef.current.slice(startRow, endRow).some((i) => !i)
) {
await load(startRow, endRow);
}
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow).map((edge) => edge.node)
: [];
let lastRow = -1;
if (totalCountRef.current !== undefined) {
if (!totalCountRef.current) {
lastRow = 0;
} else {
lastRow = totalCountRef.current;
}
} else if (rowsThisBlock.length < endRow - startRow) {
lastRow = rowsThisBlock.length;
}
successCallback(rowsThisBlock, lastRow);
} catch (e) {
failCallback();
}
};
const getRows = makeInfiniteScrollGetRows<Fills_party_tradesConnection_edges>(
newRows,
dataRef,
totalCountRef,
load
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {
@ -313,36 +283,12 @@ const InfiniteScrollManager = () => {
totalCountRef.current = totalCount;
dataRef.current = data;
const getRows = async ({
successCallback,
failCallback,
startRow,
endRow,
}: IGetRowsParams) => {
startRow += newRows.current;
endRow += newRows.current;
try {
if (dataRef.current && dataRef.current.indexOf(null) < endRow) {
await load();
}
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow).map((edge) => edge?.node)
: [];
let lastRow = -1;
if (totalCountRef.current !== undefined) {
if (!totalCountRef.current) {
lastRow = 0;
} else if (totalCountRef.current <= endRow) {
lastRow = totalCountRef.current;
}
} else if (rowsThisBlock.length < endRow - startRow) {
lastRow = rowsThisBlock.length;
}
successCallback(rowsThisBlock, lastRow);
} catch (e) {
failCallback();
}
};
const getRows = makeInfiniteScrollGetRows<Fills_party_tradesConnection_edges>(
newRows,
dataRef,
totalCountRef,
load
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {

View File

@ -1,121 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "@vegaprotocol/types";
// ====================================================
// GraphQL fragment: OrderFields
// ====================================================
export interface OrderFields_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
}
export interface OrderFields_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: OrderFields_market_tradableInstrument_instrument;
}
export interface OrderFields_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: OrderFields_market_tradableInstrument;
}
export interface OrderFields {
__typename: "Order";
/**
* Hash of the order data
*/
id: string;
/**
* The market the order is trading on (probably stored internally as a hash of the market details)
*/
market: OrderFields_market | null;
/**
* Type the order type (defaults to PARTY)
*/
type: OrderType | null;
/**
* Whether the order is to buy or sell
*/
side: Side;
/**
* Total number of contracts that may be bought or sold (immutable) (uint64)
*/
size: string;
/**
* The status of an order, for example 'Active'
*/
status: OrderStatus;
/**
* Reason for the order to be rejected
*/
rejectionReason: OrderRejectionReason | null;
/**
* The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64)
*/
price: string;
/**
* The timeInForce of order (determines how and if it executes, and whether it persists on the book)
*/
timeInForce: OrderTimeInForce;
/**
* Number of contracts remaining of the total that have not yet been bought or sold (uint64)
*/
remaining: string;
/**
* Expiration time of this order (ISO-8601 RFC3339+Nano formatted date)
*/
expiresAt: string | null;
/**
* RFC3339Nano formatted date and time for when the order was created (timestamp)
*/
createdAt: string;
/**
* RFC3339Nano time the order was altered
*/
updatedAt: string | null;
}

View File

@ -1,132 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "@vegaprotocol/types";
// ====================================================
// GraphQL subscription operation: OrderSub
// ====================================================
export interface OrderSub_orders_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
}
export interface OrderSub_orders_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: OrderSub_orders_market_tradableInstrument_instrument;
}
export interface OrderSub_orders_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: OrderSub_orders_market_tradableInstrument;
}
export interface OrderSub_orders {
__typename: "Order";
/**
* Hash of the order data
*/
id: string;
/**
* The market the order is trading on (probably stored internally as a hash of the market details)
*/
market: OrderSub_orders_market | null;
/**
* Type the order type (defaults to PARTY)
*/
type: OrderType | null;
/**
* Whether the order is to buy or sell
*/
side: Side;
/**
* Total number of contracts that may be bought or sold (immutable) (uint64)
*/
size: string;
/**
* The status of an order, for example 'Active'
*/
status: OrderStatus;
/**
* Reason for the order to be rejected
*/
rejectionReason: OrderRejectionReason | null;
/**
* The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64)
*/
price: string;
/**
* The timeInForce of order (determines how and if it executes, and whether it persists on the book)
*/
timeInForce: OrderTimeInForce;
/**
* Number of contracts remaining of the total that have not yet been bought or sold (uint64)
*/
remaining: string;
/**
* Expiration time of this order (ISO-8601 RFC3339+Nano formatted date)
*/
expiresAt: string | null;
/**
* RFC3339Nano formatted date and time for when the order was created (timestamp)
*/
createdAt: string;
/**
* RFC3339Nano time the order was altered
*/
updatedAt: string | null;
}
export interface OrderSub {
/**
* Subscribe to orders updates
*/
orders: OrderSub_orders[] | null;
}
export interface OrderSubVariables {
partyId: string;
}

View File

@ -1,144 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Orders
// ====================================================
export interface Orders_party_orders_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
*/
code: string;
}
export interface Orders_party_orders_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: Orders_party_orders_market_tradableInstrument_instrument;
}
export interface Orders_party_orders_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: Orders_party_orders_market_tradableInstrument;
}
export interface Orders_party_orders {
__typename: "Order";
/**
* Hash of the order data
*/
id: string;
/**
* The market the order is trading on (probably stored internally as a hash of the market details)
*/
market: Orders_party_orders_market | null;
/**
* Type the order type (defaults to PARTY)
*/
type: OrderType | null;
/**
* Whether the order is to buy or sell
*/
side: Side;
/**
* Total number of contracts that may be bought or sold (immutable) (uint64)
*/
size: string;
/**
* The status of an order, for example 'Active'
*/
status: OrderStatus;
/**
* Reason for the order to be rejected
*/
rejectionReason: OrderRejectionReason | null;
/**
* The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64)
*/
price: string;
/**
* The timeInForce of order (determines how and if it executes, and whether it persists on the book)
*/
timeInForce: OrderTimeInForce;
/**
* Number of contracts remaining of the total that have not yet been bought or sold (uint64)
*/
remaining: string;
/**
* Expiration time of this order (ISO-8601 RFC3339+Nano formatted date)
*/
expiresAt: string | null;
/**
* RFC3339Nano formatted date and time for when the order was created (timestamp)
*/
createdAt: string;
/**
* RFC3339Nano time the order was altered
*/
updatedAt: string | null;
}
export interface Orders_party {
__typename: "Party";
/**
* Party identifier
*/
id: string;
/**
* Orders relating to a party
*/
orders: Orders_party_orders[] | null;
}
export interface Orders {
/**
* An entity that is trading on the VEGA network
*/
party: Orders_party | null;
}
export interface OrdersVariables {
partyId: string;
}

View File

@ -1,5 +1,3 @@
export * from './__generated__';
export * from './mocks';
export * from './order-data-provider';
export * from './order-list';
export * from './order-list-manager';

View File

@ -1,28 +1,15 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import {
OrderStatus,
OrderTimeInForce,
OrderType,
Side,
} from '@vegaprotocol/types';
import type { Orders, Orders_party_orders } from '../__generated__/Orders';
import type { Orders_party_ordersConnection_edges_node } from '../';
export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
const orders: Orders_party_orders[] = generateOrdersArray();
const defaultResult = {
party: {
id: 'party-id',
orders,
__typename: 'Party',
},
};
return merge(defaultResult, override);
};
export const generateOrder = (partialOrder?: Partial<Orders_party_orders>) =>
export const generateOrder = (
partialOrder: Partial<Orders_party_ordersConnection_edges_node>
) =>
merge(
{
__typename: 'Order',
@ -52,7 +39,7 @@ export const generateOrder = (partialOrder?: Partial<Orders_party_orders>) =>
updatedAt: null,
expiresAt: null,
rejectionReason: null,
} as Orders_party_orders,
} as Orders_party_ordersConnection_edges_node,
partialOrder
);
@ -71,86 +58,88 @@ export const marketOrder = generateOrder({
status: OrderStatus.Active,
});
export const generateMockOrders = (): Orders_party_orders[] => {
return [
generateOrder({
id: '066468C06549101DAF7BC51099E1412A0067DC08C246B7D8013C9D0CBF1E8EE7',
market: {
__typename: 'Market',
id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d',
name: 'AAVEDAI Monthly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'AAVEDAI.MF21',
export const generateMockOrders =
(): Orders_party_ordersConnection_edges_node[] => {
return [
generateOrder({
id: '066468C06549101DAF7BC51099E1412A0067DC08C246B7D8013C9D0CBF1E8EE7',
market: {
__typename: 'Market',
id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d',
name: 'AAVEDAI Monthly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'AAVEDAI.MF21',
},
},
},
},
size: '10',
type: OrderType.Limit,
status: OrderStatus.Filled,
side: Side.Buy,
remaining: '0',
price: '20000000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2020, 1, 1).toISOString(),
}),
generateOrder({
id: '48DB6767E4E4E0F649C5A13ABFADE39F8451C27DA828DAF14B7A1E8E5EBDAD99',
market: {
__typename: 'Market',
id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376',
name: 'Tesla Quarterly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'TSLA.QM21',
size: '10',
type: OrderType.Limit,
status: OrderStatus.Filled,
side: Side.Buy,
remaining: '0',
price: '20000000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2020, 1, 1).toISOString(),
}),
generateOrder({
id: '48DB6767E4E4E0F649C5A13ABFADE39F8451C27DA828DAF14B7A1E8E5EBDAD99',
market: {
__typename: 'Market',
id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376',
name: 'Tesla Quarterly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'TSLA.QM21',
},
},
},
},
size: '1',
type: OrderType.Limit,
status: OrderStatus.Filled,
side: Side.Buy,
remaining: '0',
price: '100',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date().toISOString(),
}),
generateOrder({
id: '4e93702990712c41f6995fcbbd94f60bb372ad12d64dfa7d96d205c49f790336',
market: {
__typename: 'Market',
id: 'c6f4337b31ed57a961969c3ba10297b369d01b9e75a4cbb96db4fc62886444e6',
name: 'BTCUSD Monthly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'BTCUSD.MF21',
size: '1',
type: OrderType.Limit,
status: OrderStatus.Filled,
side: Side.Buy,
remaining: '0',
price: '100',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date().toISOString(),
}),
generateOrder({
id: '4e93702990712c41f6995fcbbd94f60bb372ad12d64dfa7d96d205c49f790336',
market: {
__typename: 'Market',
id: 'c6f4337b31ed57a961969c3ba10297b369d01b9e75a4cbb96db4fc62886444e6',
name: 'BTCUSD Monthly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'BTCUSD.MF21',
},
},
},
},
size: '1',
type: OrderType.Limit,
status: OrderStatus.Filled,
side: Side.Buy,
remaining: '0',
price: '20000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2022, 5, 10).toISOString(),
}),
];
};
size: '1',
type: OrderType.Limit,
status: OrderStatus.Filled,
side: Side.Buy,
remaining: '0',
price: '20000',
timeInForce: OrderTimeInForce.GTC,
createdAt: new Date(2022, 5, 10).toISOString(),
}),
];
};
export const generateOrdersArray = (): Orders_party_orders[] => {
return [marketOrder, limitOrder, ...generateMockOrders()];
};
export const generateOrdersArray =
(): Orders_party_ordersConnection_edges_node[] => {
return [marketOrder, limitOrder, ...generateMockOrders()];
};

View File

@ -3,13 +3,13 @@
// @generated
// This file was automatically generated and should not be edited.
import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "@vegaprotocol/types";
import { Pagination, OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Orders
// ====================================================
export interface Orders_party_orders_market_tradableInstrument_instrument {
export interface Orders_party_ordersConnection_edges_node_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
@ -17,15 +17,15 @@ export interface Orders_party_orders_market_tradableInstrument_instrument {
code: string;
}
export interface Orders_party_orders_market_tradableInstrument {
export interface Orders_party_ordersConnection_edges_node_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: Orders_party_orders_market_tradableInstrument_instrument;
instrument: Orders_party_ordersConnection_edges_node_market_tradableInstrument_instrument;
}
export interface Orders_party_orders_market {
export interface Orders_party_ordersConnection_edges_node_market {
__typename: "Market";
/**
* Market ID
@ -61,10 +61,10 @@ export interface Orders_party_orders_market {
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: Orders_party_orders_market_tradableInstrument;
tradableInstrument: Orders_party_ordersConnection_edges_node_market_tradableInstrument;
}
export interface Orders_party_orders {
export interface Orders_party_ordersConnection_edges_node {
__typename: "Order";
/**
* Hash of the order data
@ -73,7 +73,7 @@ export interface Orders_party_orders {
/**
* The market the order is trading on (probably stored internally as a hash of the market details)
*/
market: Orders_party_orders_market | null;
market: Orders_party_ordersConnection_edges_node_market | null;
/**
* Type the order type (defaults to PARTY)
*/
@ -120,6 +120,32 @@ export interface Orders_party_orders {
updatedAt: string | null;
}
export interface Orders_party_ordersConnection_edges {
__typename: "OrderEdge";
node: Orders_party_ordersConnection_edges_node;
cursor: string | null;
}
export interface Orders_party_ordersConnection_pageInfo {
__typename: "PageInfo";
startCursor: string;
endCursor: string;
hasNextPage: boolean;
hasPreviousPage: boolean;
}
export interface Orders_party_ordersConnection {
__typename: "OrderConnection";
/**
* The orders in this connection
*/
edges: Orders_party_ordersConnection_edges[] | null;
/**
* The pagination information
*/
pageInfo: Orders_party_ordersConnection_pageInfo | null;
}
export interface Orders_party {
__typename: "Party";
/**
@ -129,7 +155,7 @@ export interface Orders_party {
/**
* Orders relating to a party
*/
orders: Orders_party_orders[] | null;
ordersConnection: Orders_party_ordersConnection;
}
export interface Orders {
@ -141,4 +167,5 @@ export interface Orders {
export interface OrdersVariables {
partyId: string;
pagination?: Pagination | null;
}

View File

@ -1 +1,2 @@
export * from './__generated__';
export * from './order-data-provider';

View File

@ -1,88 +1,59 @@
import {
OrderType,
OrderStatus,
Side,
OrderTimeInForce,
} from '@vegaprotocol/types';
import type { Orders_party_orders } from '../__generated__/Orders';
import { sortOrders } from './order-data-provider';
const marketOrder: Orders_party_orders = {
__typename: 'Order',
id: 'order-id',
market: {
__typename: 'Market',
id: 'market-id',
name: 'market-name',
decimalPlaces: 2,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'instrument-code',
import { update } from './order-data-provider';
import type { OrderSub_orders, Orders_party_ordersConnection_edges } from '../';
describe('order data provider', () => {
it('puts incoming data in proper place', () => {
const data = [
{
node: {
id: '1',
updatedAt: new Date('2022-01-31').toISOString(),
createdAt: new Date('2022-01-29').toISOString(),
},
},
},
},
size: '10',
type: OrderType.Market,
status: OrderStatus.Active,
side: Side.Buy,
remaining: '5',
price: '',
timeInForce: OrderTimeInForce.IOC,
createdAt: new Date('2022-2-3').toISOString(),
updatedAt: null,
expiresAt: null,
rejectionReason: null,
};
const limitOrder: Orders_party_orders = {
__typename: 'Order',
id: 'order-id',
market: {
__typename: 'Market',
id: 'market-id',
name: 'market-name',
decimalPlaces: 2,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
code: 'instrument-code',
{
node: {
id: '2',
createdAt: new Date('2022-01-30').toISOString(),
},
},
},
},
size: '10',
type: OrderType.Limit,
status: OrderStatus.Active,
side: Side.Sell,
remaining: '5',
price: '12345',
timeInForce: OrderTimeInForce.GTT,
createdAt: new Date('2022-3-3').toISOString(),
expiresAt: new Date('2022-3-5').toISOString(),
updatedAt: null,
rejectionReason: null,
};
] as Orders_party_ordersConnection_edges[];
describe('OrderDataProvider', () => {
const orders = [marketOrder, limitOrder];
const delta = [
// this one should be dropped because id don't exits and it's older than newest
{
id: '0',
createdAt: new Date('2022-01-30').toISOString(),
},
// this one should be dropped because new newer below
{
id: '1',
updatedAt: new Date('2022-02-01').toISOString(),
createdAt: new Date('2022-01-29').toISOString(),
},
{
id: '1',
updatedAt: new Date('2022-02-02').toISOString(),
createdAt: new Date('2022-01-29').toISOString(),
},
// this should be added
{
id: '4',
createdAt: new Date('2022-02-04').toISOString(),
},
// this should be move to top
{
id: '2',
updatedAt: new Date('2022-02-03').toISOString(),
createdAt: new Date('2022-01-29').toISOString(),
},
] as OrderSub_orders[];
describe('sortOrders', () => {
it('should sort the orders from the most recent placed to the oldest', () => {
expect(sortOrders(orders)).toStrictEqual([limitOrder, marketOrder]);
});
it('should sort the orders from the most recent updated to the oldest', () => {
const updatedOrder = {
...limitOrder,
updatedAt: new Date('2022-3-4').toISOString(),
};
expect(sortOrders([...orders, updatedOrder])).toStrictEqual([
updatedOrder,
limitOrder,
marketOrder,
]);
});
const updatedData = update(data, delta);
expect(updatedData.findIndex((edge) => edge.node === delta[0])).toEqual(-1);
expect(updatedData[2].node.id).toEqual(delta[2].id);
expect(updatedData[2].node.updatedAt).toEqual(delta[2].updatedAt);
expect(updatedData[0].node).toEqual(delta[3]);
expect(updatedData[1].node.id).toEqual(delta[4].id);
expect(updatedData[1].node.updatedAt).toEqual(delta[4].updatedAt);
});
});

View File

@ -1,11 +1,18 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type { OrderFields } from '../__generated__/OrderFields';
import type { Orders, Orders_party_orders } from '../__generated__/Orders';
import type { OrderSub } from '../__generated__/OrderSub';
import orderBy from 'lodash/orderBy';
import uniqBy from 'lodash/uniqBy';
import {
makeDataProvider,
defaultAppend as append,
} from '@vegaprotocol/react-helpers';
import type { PageInfo } from '@vegaprotocol/react-helpers';
import type {
Orders,
Orders_party_ordersConnection_edges,
OrderSub,
OrderFields,
} from '../';
const ORDER_FRAGMENT = gql`
fragment OrderFields on Order {
@ -37,11 +44,22 @@ const ORDER_FRAGMENT = gql`
export const ORDERS_QUERY = gql`
${ORDER_FRAGMENT}
query Orders($partyId: ID!) {
query Orders($partyId: ID!, $pagination: Pagination) {
party(id: $partyId) {
id
orders {
...OrderFields
ordersConnection(pagination: $pagination) {
edges {
node {
...OrderFields
}
cursor
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}
}
@ -56,53 +74,55 @@ export const ORDERS_SUB = gql`
}
`;
// A single update can contain the same order with multiple updates, so we need to find
// the latest version of the order and only update using that
export const sortOrders = (orders: OrderFields[]) => {
return orderBy(
orders,
(o) => {
if (!o.updatedAt) return new Date(o.createdAt).getTime();
return new Date(o.updatedAt).getTime();
},
'desc'
);
};
export const uniqOrders = (orders: OrderFields[]) => {
return uniqBy(orders, 'id');
};
export const prepareIncomingOrders = (delta: OrderFields[]) => {
const sortedOrders = sortOrders(delta);
const incoming = uniqOrders(sortedOrders);
return incoming;
};
const update = (data: OrderFields[], delta: OrderFields[]) => {
export const update = (
data: Orders_party_ordersConnection_edges[],
delta: OrderFields[]
) => {
return produce(data, (draft) => {
const incoming = prepareIncomingOrders(delta);
// A single update can contain the same order with multiple updates, so we need to find
// the latest version of the order and only update using that
const incoming = uniqBy(
orderBy(delta, (order) => order.updatedAt || order.createdAt, 'desc'),
'id'
);
// Add or update incoming orders
incoming.forEach((order) => {
const index = draft.findIndex((o) => o.id === order.id);
if (index === -1) {
draft.unshift(order);
} else {
draft[index] = order;
incoming.reverse().forEach((node) => {
const index = draft.findIndex((edge) => edge.node.id === node.id);
const newer =
(node.updatedAt || node.createdAt) >=
(draft[0].node.updatedAt || draft[0].node.createdAt);
if (index !== -1) {
Object.assign(draft[index].node, node);
if (newer) {
draft.unshift(...draft.splice(index, 1));
}
} else if (newer) {
draft.unshift({ node, cursor: '', __typename: 'OrderEdge' });
}
});
});
};
const getData = (responseData: Orders): Orders_party_orders[] | null =>
responseData?.party?.orders || null;
const getData = (
responseData: Orders
): Orders_party_ordersConnection_edges[] | null =>
responseData?.party?.ordersConnection.edges || null;
const getDelta = (subscriptionData: OrderSub) => subscriptionData.orders || [];
const getPageInfo = (responseData: Orders): PageInfo | null =>
responseData.party?.ordersConnection.pageInfo || null;
export const ordersDataProvider = makeDataProvider(
ORDERS_QUERY,
ORDERS_SUB,
update,
getData,
getDelta
getDelta,
{
getPageInfo,
append,
first: 100,
}
);

View File

@ -1,8 +1,8 @@
import { render, screen } from '@testing-library/react';
import { OrderListManager } from './order-list-manager';
import * as useDataProviderHook from '@vegaprotocol/react-helpers';
import type { Orders_party_orders } from '../__generated__/Orders';
import * as orderListMock from '../order-list';
import type { Orders_party_ordersConnection_edges_node } from '../';
import * as orderListMock from '../';
import { forwardRef } from 'react';
jest.mock('./order-list');
@ -12,6 +12,10 @@ it('Renders a loading state while awaiting orders', () => {
data: [],
loading: true,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: 0,
});
render(<OrderListManager partyId="0x123" />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
@ -23,6 +27,10 @@ it('Renders an error state', () => {
data: [],
loading: false,
error: new Error(errorMsg),
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
});
render(<OrderListManager partyId="0x123" />);
expect(
@ -35,9 +43,13 @@ it('Renders the order list if orders provided', async () => {
// avoid warnings about padding refs
orderListMock.OrderList = forwardRef(() => <div>OrderList</div>);
jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({
data: [{ id: '1' } as Orders_party_orders],
data: [{ id: '1' } as Orders_party_ordersConnection_edges_node],
loading: false,
error: undefined,
flush: jest.fn(),
reload: jest.fn(),
load: jest.fn(),
totalCount: undefined,
});
render(<OrderListManager partyId="0x123" />);
expect(await screen.findByText('OrderList')).toBeInTheDocument();

View File

@ -1,16 +1,14 @@
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { OrderList } from '../order-list';
import type { OrderFields } from '../__generated__/OrderFields';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import {
ordersDataProvider as dataProvider,
prepareIncomingOrders,
sortOrders,
} from '../order-data-provider';
useDataProvider,
makeInfiniteScrollGetRows,
} from '@vegaprotocol/react-helpers';
import { useCallback, useMemo, useRef } from 'react';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import type { OrderSub_orders } from '../__generated__/OrderSub';
import isEqual from 'lodash/isEqual';
import { OrderList, ordersDataProvider as dataProvider } from '../';
import type { OrderFields, Orders_party_ordersConnection_edges } from '../';
interface OrderListManagerProps {
partyId: string;
@ -18,62 +16,105 @@ interface OrderListManagerProps {
export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<(Orders_party_ordersConnection_edges | null)[] | null>(
null
);
const totalCountRef = useRef<number | undefined>(undefined);
const newRows = useRef(0);
const scrolledToTop = useRef(true);
const variables = useMemo(() => ({ partyId }), [partyId]);
// Apply updates to the table
const update = useCallback(({ delta }: { delta: OrderSub_orders[] }) => {
if (!gridRef.current) {
return false;
const addNewRows = useCallback(() => {
if (newRows.current === 0) {
return;
}
const incoming = prepareIncomingOrders(delta);
const updateRows: OrderFields[] = [];
const add: OrderFields[] = [];
incoming.forEach((d) => {
if (!gridRef.current?.api) {
return;
}
const rowNode = gridRef.current.api.getRowNode(d.id);
if (rowNode) {
if (!isEqual(d, rowNode.data)) {
updateRows.push(d);
}
} else {
add.push(d);
}
});
if (updateRows.length || add.length) {
gridRef.current.api.applyTransactionAsync({
update: updateRows,
add,
addIndex: 0,
});
if (totalCountRef.current !== undefined) {
totalCountRef.current += newRows.current;
}
return true;
newRows.current = 0;
if (!gridRef.current?.api) {
return;
}
gridRef.current.api.refreshInfiniteCache();
}, []);
const { data, error, loading } = useDataProvider({
const update = useCallback(
({
data,
delta,
}: {
data: (Orders_party_ordersConnection_edges | null)[];
delta: OrderFields[];
}) => {
if (!gridRef.current?.api) {
return false;
}
if (!scrolledToTop.current) {
const createdAt = dataRef.current?.[0]?.node.createdAt;
if (createdAt) {
newRows.current += delta.filter(
(trade) => trade.createdAt > createdAt
).length;
}
}
dataRef.current = data;
gridRef.current.api.refreshInfiniteCache();
return true;
},
[]
);
const insert = useCallback(
({
data,
totalCount,
}: {
data: Orders_party_ordersConnection_edges[];
totalCount?: number;
}) => {
dataRef.current = data;
totalCountRef.current = totalCount;
return true;
},
[]
);
const { data, error, loading, load, totalCount } = useDataProvider({
dataProvider,
update,
insert,
variables,
});
totalCountRef.current = totalCount;
dataRef.current = data;
const orders = useMemo(() => {
if (!data) {
return null;
const getRows =
makeInfiniteScrollGetRows<Orders_party_ordersConnection_edges>(
newRows,
dataRef,
totalCountRef,
load
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
}
return sortOrders(data);
}, [data]);
};
const onBodyScroll = (event: BodyScrollEvent) => {
scrolledToTop.current = event.top <= 0;
};
// We can set <OrderList showCancelled={false} to hide cancelled orders
return (
<AsyncRenderer loading={loading} error={error} data={orders}>
<OrderList ref={gridRef} data={orders} />
<AsyncRenderer loading={loading} error={error} data={data}>
<OrderList
ref={gridRef}
rowModelType="infinite"
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
/>
</AsyncRenderer>
);
};

View File

@ -4,24 +4,25 @@ import {
formatLabel,
getDateTimeFormat,
} from '@vegaprotocol/react-helpers';
import type { Orders_party_orders } from '../__generated__/Orders';
import { OrderStatus, OrderRejectionReason } from '@vegaprotocol/types';
import { OrderListTable } from './order-list';
import type { PartialDeep } from 'type-fest';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MockedProvider } from '@apollo/client/testing';
import { OrderListTable } from '../';
import type { Orders_party_ordersConnection_edges_node } from '../';
import { limitOrder, marketOrder } from '../mocks/generate-orders';
const generateJsx = (
orders: Orders_party_orders[] | null,
orders: Orders_party_ordersConnection_edges_node[] | null,
context: PartialDeep<VegaWalletContextShape> = { keypair: { pub: '0x123' } }
) => {
return (
<MockedProvider>
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
<OrderListTable
data={orders}
rowData={orders}
cancel={jest.fn()}
setEditOrderDialogOpen={jest.fn()}
setEditOrder={jest.fn()}

View File

@ -16,7 +16,7 @@ const Template: Story = (args) => {
return (
<div style={{ height: 1000 }}>
<OrderListTable
data={args.data}
rowData={args.data}
cancel={cancel}
setEditOrderDialogOpen={() => {
return;
@ -55,7 +55,7 @@ const Template2: Story = (args) => {
<>
<div style={{ height: 1000 }}>
<OrderListTable
data={args.data}
rowData={args.data}
cancel={cancel}
setEditOrderDialogOpen={() => {
return;

View File

@ -1,5 +1,4 @@
import { OrderTimeInForce, OrderStatus, Side } from '@vegaprotocol/types';
import type { Orders_party_orders } from '../__generated__/Orders';
import {
addDecimal,
formatLabel,
@ -11,27 +10,29 @@ import type {
ICellRendererParams,
ValueFormatterParams,
} from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import type {
AgGridReact,
AgGridReactProps,
AgReactUiProps,
} from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { forwardRef, useState } from 'react';
import type { Orders_party_ordersConnection_edges_node } from '../';
import BigNumber from 'bignumber.js';
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
import { VegaTransactionDialog } from '@vegaprotocol/wallet';
import { useOrderEdit } from '../../order-hooks/use-order-edit';
import { OrderEditDialog } from './order-edit-dialog';
interface OrderListProps {
data: Orders_party_orders[] | null;
showCancelled?: boolean;
}
type OrderListProps = AgGridReactProps | AgReactUiProps;
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
({ data, showCancelled = true }, ref) => {
(props, ref) => {
const [cancelOrderDialogOpen, setCancelOrderDialogOpen] = useState(false);
const [editOrderDialogOpen, setEditOrderDialogOpen] = useState(false);
const [editOrder, setEditOrder] = useState<Orders_party_orders | null>(
null
);
const [editOrder, setEditOrder] =
useState<Orders_party_ordersConnection_edges_node | null>(null);
const { transaction, updatedOrder, reset, cancel } = useOrderCancel();
const {
@ -40,9 +41,6 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
reset: resetEdit,
edit,
} = useOrderEdit();
const ordersData = showCancelled
? data
: data?.filter((o) => o.status !== OrderStatus.Cancelled) || null;
const getCancelDialogTitle = (status?: string) => {
switch (status) {
case OrderStatus.Cancelled:
@ -70,7 +68,7 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
return (
<>
<OrderListTable
data={ordersData}
{...props}
cancel={cancel}
ref={ref}
setEditOrderDialogOpen={setEditOrderDialogOpen}
@ -105,24 +103,32 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
}
);
interface OrderListTableProps {
data: Orders_party_orders[] | null;
type OrderListTableValueFormatterParams = Omit<
ValueFormatterParams,
'data' | 'value'
> & {
data: Orders_party_ordersConnection_edges_node | null;
};
type OrderListTableProps = (AgGridReactProps | AgReactUiProps) & {
cancel: (body?: unknown) => Promise<unknown>;
setEditOrderDialogOpen: (value: boolean) => void;
setEditOrder: (order: Orders_party_orders | null) => void;
}
setEditOrder: (
order: Orders_party_ordersConnection_edges_node | null
) => void;
};
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
({ data, cancel, setEditOrderDialogOpen, setEditOrder }, ref) => {
({ cancel, setEditOrderDialogOpen, setEditOrder, ...props }, ref) => {
return (
<AgGrid
ref={ref}
rowData={data}
overlayNoRowsTemplate="No orders"
defaultColDef={{ flex: 1, resizable: true }}
style={{ width: '100%', height: '100%' }}
getRowId={({ data }) => data.id}
rowHeight={40}
{...props}
>
<AgGridColumn
headerName={t('Market')}
@ -132,7 +138,15 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
headerName={t('Amount')}
field="size"
cellClass="font-mono"
valueFormatter={({ value, data }: ValueFormatterParams) => {
valueFormatter={({
value,
data,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['size'];
}) => {
if (value === undefined || !data || !data.market) {
return undefined;
}
const prefix = data.side === Side.Buy ? '+' : '-';
return (
prefix + addDecimal(value, data.market.positionDecimalPlaces)
@ -142,11 +156,20 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
<AgGridColumn field="type" />
<AgGridColumn
field="status"
valueFormatter={({ value, data }: ValueFormatterParams) => {
if (value === OrderStatus.Rejected) {
return `${value}: ${formatLabel(data.rejectionReason)}`;
valueFormatter={({
value,
data,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['status'];
}) => {
if (value === undefined || !data || !data.market) {
return undefined;
}
if (value === OrderStatus.Rejected) {
return `${value}: ${
data.rejectionReason && formatLabel(data.rejectionReason)
}`;
}
return value;
}}
/>
@ -154,10 +177,18 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
headerName={t('Filled')}
field="remaining"
cellClass="font-mono"
valueFormatter={({ data }: ValueFormatterParams) => {
valueFormatter={({
data,
value,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['remaining'];
}) => {
if (value === undefined || !data || !data.market) {
return undefined;
}
const dps = data.market.positionDecimalPlaces;
const size = new BigNumber(data.size);
const remaining = new BigNumber(data.remaining);
const remaining = new BigNumber(value);
const fills = size.minus(remaining);
return `${addDecimal(fills.toString(), dps)}/${addDecimal(
size.toString(),
@ -168,8 +199,18 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
<AgGridColumn
field="price"
cellClass="font-mono"
valueFormatter={({ value, data }: ValueFormatterParams) => {
if (data.type === 'Market') {
valueFormatter={({
value,
data,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['price'];
}) => {
if (
value === undefined ||
!data ||
!data.market ||
data.type === 'Market'
) {
return '-';
}
return addDecimal(value, data.market.decimalPlaces);
@ -177,7 +218,15 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
/>
<AgGridColumn
field="timeInForce"
valueFormatter={({ value, data }: ValueFormatterParams) => {
valueFormatter={({
value,
data,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['timeInForce'];
}) => {
if (value === undefined || !data || !data.market) {
return undefined;
}
if (value === OrderTimeInForce.GTT && data.expiresAt) {
const expiry = getDateTimeFormat().format(
new Date(data.expiresAt)
@ -190,13 +239,21 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
/>
<AgGridColumn
field="createdAt"
valueFormatter={({ value }: ValueFormatterParams) => {
return getDateTimeFormat().format(new Date(value));
valueFormatter={({
value,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['createdAt'];
}) => {
return value ? getDateTimeFormat().format(new Date(value)) : value;
}}
/>
<AgGridColumn
field="updatedAt"
valueFormatter={({ value }: ValueFormatterParams) => {
valueFormatter={({
value,
}: OrderListTableValueFormatterParams & {
value?: Orders_party_ordersConnection_edges_node['updatedAt'];
}) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>

View File

@ -1,10 +1,11 @@
export * from './hooks';
export * from './lib/context';
export * from './lib/determine-id';
export * from './lib/format';
export * from './lib/generic-data-provider';
export * from './lib/grid';
export * from './lib/i18n';
export * from './lib/pagination';
export * from './lib/remove-0x';
export * from './lib/storage';
export * from './lib/validate';
export * from './lib/determine-id';

View File

@ -0,0 +1,53 @@
import type { IGetRowsParams } from 'ag-grid-community';
import type { Load } from './generic-data-provider';
import type { MutableRefObject } from 'react';
const getLastRow = (
startRow: number,
endRow: number,
blockLength: number,
totalCount?: number
) => {
let lastRow = -1;
if (totalCount !== undefined) {
if (!totalCount) {
lastRow = 0;
} else if (totalCount <= endRow) {
lastRow = totalCount;
}
} else if (blockLength < endRow - startRow) {
lastRow = blockLength;
}
return lastRow;
};
export const makeInfiniteScrollGetRows =
<T extends { node: any }>( // eslint-disable-line @typescript-eslint/no-explicit-any
newRows: MutableRefObject<number>,
data: MutableRefObject<(T | null)[] | null>,
totalCount: MutableRefObject<number | undefined>,
load: Load<(T | null)[]>
) =>
async ({
successCallback,
failCallback,
startRow,
endRow,
}: IGetRowsParams) => {
startRow += newRows.current;
endRow += newRows.current;
try {
if (data.current && data.current.indexOf(null) < endRow) {
await load();
}
const rowsThisBlock = data.current
? data.current.slice(startRow, endRow).map((edge) => edge?.node)
: [];
successCallback(
rowsThisBlock,
getLastRow(startRow, endRow, rowsThisBlock.length, totalCount.current)
);
} catch (e) {
failCallback();
}
};

View File

@ -1,12 +1,11 @@
import { useDataProvider } from '@vegaprotocol/react-helpers';
import {
useDataProvider,
makeInfiniteScrollGetRows,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useMemo, useRef } from 'react';
import type {
IGetRowsParams,
BodyScrollEvent,
BodyScrollEndEvent,
} from 'ag-grid-community';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import {
MAX_TRADES,
tradesDataProvider as dataProvider,
@ -100,36 +99,13 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
totalCountRef.current = totalCount;
dataRef.current = data;
const getRows = async ({
successCallback,
failCallback,
startRow,
endRow,
}: IGetRowsParams) => {
startRow += newRows.current;
endRow += newRows.current;
try {
if (dataRef.current && dataRef.current.indexOf(null) < endRow) {
await load();
}
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow).map((edge) => edge?.node)
: [];
let lastRow = -1;
if (totalCountRef.current !== undefined) {
if (!totalCountRef.current) {
lastRow = 0;
} else if (totalCountRef.current <= endRow) {
lastRow = totalCountRef.current;
}
} else if (rowsThisBlock.length < endRow - startRow) {
lastRow = rowsThisBlock.length;
}
successCallback(rowsThisBlock, lastRow);
} catch (e) {
failCallback();
}
};
const getRows =
makeInfiniteScrollGetRows<Trades_market_tradesConnection_edges>(
newRows,
dataRef,
totalCountRef,
load
);
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {