Feat/152 Trade list MVP (#217)
* add trades lib with data provider * add trades table and cell color logic * ensure we only show last 50 rows * add test for table columns and formatting * update trades table to get cells using col-id * fix linting * use default function param for fetchpolicy
This commit is contained in:
parent
41303190f8
commit
98c1fc82f7
@ -5,10 +5,12 @@ import { useState } from 'react';
|
||||
import { GridTab, GridTabs } from './grid-tabs';
|
||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||
import { OrderListContainer } from '@vegaprotocol/order-list';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import type { Market_market } from './__generated__/Market';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||
|
||||
const Chart = () => (
|
||||
<Splash>
|
||||
@ -20,16 +22,6 @@ const Orderbook = () => (
|
||||
<p>{t('Orderbook')}</p>
|
||||
</Splash>
|
||||
);
|
||||
const Collateral = () => (
|
||||
<Splash>
|
||||
<p>{t('Collateral')}</p>
|
||||
</Splash>
|
||||
);
|
||||
const Trades = () => (
|
||||
<Splash>
|
||||
<p>{t('Trades')}</p>
|
||||
</Splash>
|
||||
);
|
||||
|
||||
const TradingViews = {
|
||||
Chart: Chart,
|
||||
@ -37,8 +29,8 @@ const TradingViews = {
|
||||
Orderbook: Orderbook,
|
||||
Orders: OrderListContainer,
|
||||
Positions: PositionsContainer,
|
||||
Collateral: Collateral,
|
||||
Trades: Trades,
|
||||
Accounts: AccountsContainer,
|
||||
Trades: TradesContainer,
|
||||
};
|
||||
|
||||
type TradingView = keyof typeof TradingViews;
|
||||
@ -50,7 +42,7 @@ interface TradeGridProps {
|
||||
export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
const wrapperClasses = classNames(
|
||||
'h-full max-h-full',
|
||||
'grid gap-[1px] grid-cols-[1fr_325px_325px] grid-rows-[min-content_1fr_200px]',
|
||||
'grid gap-[1px] grid-cols-[1fr_375px_460px] grid-rows-[min-content_1fr_200px]',
|
||||
'bg-black-10 dark:bg-white-10',
|
||||
'text-ui'
|
||||
);
|
||||
@ -70,7 +62,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<GridTabs group="trade">
|
||||
<GridTab id="trades" name={t('Trades')}>
|
||||
<TradingViews.Trades />
|
||||
<TradingViews.Trades marketId={market.id} />
|
||||
</GridTab>
|
||||
<GridTab id="orderbook" name={t('Orderbook')}>
|
||||
<TradingViews.Orderbook />
|
||||
@ -85,8 +77,8 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
<GridTab id="positions" name={t('Positions')}>
|
||||
<TradingViews.Positions />
|
||||
</GridTab>
|
||||
<GridTab id="collateral" name={t('Collateral')}>
|
||||
<TradingViews.Collateral />
|
||||
<GridTab id="accounts" name={t('Accounts')}>
|
||||
<TradingViews.Accounts />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
|
@ -86,6 +86,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
.subscribe<SubscriptionData>({
|
||||
query: subscriptionQuery,
|
||||
variables,
|
||||
fetchPolicy,
|
||||
})
|
||||
.subscribe(({ data: subscriptionData }) => {
|
||||
if (!subscriptionData) {
|
||||
|
12
libs/trades/.babelrc
Normal file
12
libs/trades/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
18
libs/trades/.eslintrc.json
Normal file
18
libs/trades/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
7
libs/trades/README.md
Normal file
7
libs/trades/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# trades
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test trades` to execute the unit tests via [Jest](https://jestjs.io).
|
10
libs/trades/jest.config.js
Normal file
10
libs/trades/jest.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
displayName: 'trades',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/trades',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
};
|
4
libs/trades/package.json
Normal file
4
libs/trades/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/trades",
|
||||
"version": "0.0.1"
|
||||
}
|
43
libs/trades/project.json
Normal file
43
libs/trades/project.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"root": "libs/trades",
|
||||
"sourceRoot": "libs/trades/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/web:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/trades",
|
||||
"tsConfig": "libs/trades/tsconfig.lib.json",
|
||||
"project": "libs/trades/package.json",
|
||||
"entryFile": "libs/trades/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/trades/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/trades/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/libs/trades"],
|
||||
"options": {
|
||||
"jestConfig": "libs/trades/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
libs/trades/src/index.ts
Normal file
1
libs/trades/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './lib/trades-container';
|
57
libs/trades/src/lib/__generated__/TradeFields.ts
generated
Normal file
57
libs/trades/src/lib/__generated__/TradeFields.ts
generated
Normal file
@ -0,0 +1,57 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: TradeFields
|
||||
// ====================================================
|
||||
|
||||
export interface TradeFields_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: 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;
|
||||
}
|
||||
|
||||
export interface TradeFields {
|
||||
__typename: "Trade";
|
||||
/**
|
||||
* The hash of the trade data
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
|
||||
*/
|
||||
size: string;
|
||||
/**
|
||||
* RFC3339Nano time for when the trade occurred
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* The market the trade occurred on
|
||||
*/
|
||||
market: TradeFields_market;
|
||||
}
|
81
libs/trades/src/lib/__generated__/Trades.ts
generated
Normal file
81
libs/trades/src/lib/__generated__/Trades.ts
generated
Normal file
@ -0,0 +1,81 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Trades
|
||||
// ====================================================
|
||||
|
||||
export interface Trades_market_trades_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: 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;
|
||||
}
|
||||
|
||||
export interface Trades_market_trades {
|
||||
__typename: "Trade";
|
||||
/**
|
||||
* The hash of the trade data
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
|
||||
*/
|
||||
size: string;
|
||||
/**
|
||||
* RFC3339Nano time for when the trade occurred
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* The market the trade occurred on
|
||||
*/
|
||||
market: Trades_market_trades_market;
|
||||
}
|
||||
|
||||
export interface Trades_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Trades on a market
|
||||
*/
|
||||
trades: Trades_market_trades[] | null;
|
||||
}
|
||||
|
||||
export interface Trades {
|
||||
/**
|
||||
* An instrument that is trading on the VEGA network
|
||||
*/
|
||||
market: Trades_market | null;
|
||||
}
|
||||
|
||||
export interface TradesVariables {
|
||||
marketId: string;
|
||||
maxTrades: number;
|
||||
}
|
68
libs/trades/src/lib/__generated__/TradesSub.ts
generated
Normal file
68
libs/trades/src/lib/__generated__/TradesSub.ts
generated
Normal file
@ -0,0 +1,68 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL subscription operation: TradesSub
|
||||
// ====================================================
|
||||
|
||||
export interface TradesSub_trades_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: 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;
|
||||
}
|
||||
|
||||
export interface TradesSub_trades {
|
||||
__typename: "Trade";
|
||||
/**
|
||||
* The hash of the trade data
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64)
|
||||
*/
|
||||
size: string;
|
||||
/**
|
||||
* RFC3339Nano time for when the trade occurred
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* The market the trade occurred on
|
||||
*/
|
||||
market: TradesSub_trades_market;
|
||||
}
|
||||
|
||||
export interface TradesSub {
|
||||
/**
|
||||
* Subscribe to the trades updates
|
||||
*/
|
||||
trades: TradesSub_trades[] | null;
|
||||
}
|
||||
|
||||
export interface TradesSubVariables {
|
||||
marketId: string;
|
||||
}
|
65
libs/trades/src/lib/trades-container.tsx
Normal file
65
libs/trades/src/lib/trades-container.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import type { GridApi } from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
MAX_TRADES,
|
||||
sortTrades,
|
||||
tradesDataProvider,
|
||||
} from './trades-data-provider';
|
||||
import { TradesTable } from './trades-table';
|
||||
import type { TradeFields } from './__generated__/TradeFields';
|
||||
import type { TradesVariables } from './__generated__/Trades';
|
||||
|
||||
interface TradesContainerProps {
|
||||
marketId: string;
|
||||
}
|
||||
|
||||
export const TradesContainer = ({ marketId }: TradesContainerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const variables = useMemo<TradesVariables>(
|
||||
() => ({ marketId, maxTrades: MAX_TRADES }),
|
||||
[marketId]
|
||||
);
|
||||
const update = useCallback((delta: TradeFields[]) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const incoming = sortTrades(delta);
|
||||
const currentRows = getAllRows(gridRef.current.api);
|
||||
// Create array of trades whose index is now greater than the max so we
|
||||
// can remove them from the grid
|
||||
const outgoing = [...incoming, ...currentRows].filter(
|
||||
(r, i) => i > MAX_TRADES - 1
|
||||
);
|
||||
|
||||
gridRef.current.api.applyTransactionAsync({
|
||||
add: incoming,
|
||||
remove: outgoing,
|
||||
addIndex: 0,
|
||||
});
|
||||
|
||||
return true;
|
||||
}, []);
|
||||
const { data, error, loading } = useDataProvider(
|
||||
tradesDataProvider,
|
||||
update,
|
||||
variables
|
||||
);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
{(data) => <TradesTable ref={gridRef} data={data} />}
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
const getAllRows = (api: GridApi) => {
|
||||
const rows: TradeFields[] = [];
|
||||
api.forEachNode((node) => {
|
||||
rows.push(node.data);
|
||||
});
|
||||
return rows;
|
||||
};
|
78
libs/trades/src/lib/trades-data-provider.ts
Normal file
78
libs/trades/src/lib/trades-data-provider.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import type { TradeFields } from './__generated__/TradeFields';
|
||||
import type { Trades } from './__generated__/Trades';
|
||||
import type { TradesSub } from './__generated__/TradesSub';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
|
||||
export const MAX_TRADES = 50;
|
||||
|
||||
const TRADES_FRAGMENT = gql`
|
||||
fragment TradeFields on Trade {
|
||||
id
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const TRADES_QUERY = gql`
|
||||
${TRADES_FRAGMENT}
|
||||
query Trades($marketId: ID!, $maxTrades: Int!) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
trades(last: $maxTrades) {
|
||||
...TradeFields
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const TRADES_SUB = gql`
|
||||
${TRADES_FRAGMENT}
|
||||
subscription TradesSub($marketId: ID!) {
|
||||
trades(marketId: $marketId) {
|
||||
...TradeFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const sortTrades = (trades: TradeFields[]) => {
|
||||
return orderBy(
|
||||
trades,
|
||||
(t) => {
|
||||
return new Date(t.createdAt).getTime();
|
||||
},
|
||||
'desc'
|
||||
);
|
||||
};
|
||||
|
||||
const update = (draft: TradeFields[], delta: TradeFields[]) => {
|
||||
const incoming = sortTrades(delta);
|
||||
|
||||
// Add new trades to the top
|
||||
draft.unshift(...incoming);
|
||||
|
||||
// Remove old trades from the bottom
|
||||
if (draft.length > MAX_TRADES) {
|
||||
draft.splice(MAX_TRADES, draft.length - MAX_TRADES);
|
||||
}
|
||||
};
|
||||
|
||||
const getData = (responseData: Trades): TradeFields[] | null =>
|
||||
responseData.market ? responseData.market.trades : null;
|
||||
|
||||
const getDelta = (subscriptionData: TradesSub): TradeFields[] =>
|
||||
subscriptionData?.trades || [];
|
||||
|
||||
export const tradesDataProvider = makeDataProvider(
|
||||
TRADES_QUERY,
|
||||
TRADES_SUB,
|
||||
update,
|
||||
getData,
|
||||
getDelta
|
||||
);
|
74
libs/trades/src/lib/trades-table.spec.tsx
Normal file
74
libs/trades/src/lib/trades-table.spec.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import { DOWN_CLASS, TradesTable, UP_CLASS } from './trades-table';
|
||||
import type { TradeFields } from './__generated__/TradeFields';
|
||||
|
||||
const trade: TradeFields = {
|
||||
__typename: 'Trade',
|
||||
id: 'trade-id',
|
||||
price: '111122200',
|
||||
size: '20',
|
||||
createdAt: new Date('2022-04-06T19:00:00').toISOString(),
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
};
|
||||
|
||||
test('Correct columns are rendered', async () => {
|
||||
await act(async () => {
|
||||
render(<TradesTable data={[trade]} />);
|
||||
});
|
||||
const expectedHeaders = ['Price', 'Size', 'Created at'];
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(expectedHeaders.length);
|
||||
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||
});
|
||||
|
||||
test('Columns are formatted', async () => {
|
||||
await act(async () => {
|
||||
render(<TradesTable data={[trade]} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
'1,111,222.00',
|
||||
'20',
|
||||
getDateTimeFormat().format(new Date(trade.createdAt)),
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
});
|
||||
});
|
||||
|
||||
test('Columns are formatted', async () => {
|
||||
const trade2 = {
|
||||
...trade,
|
||||
id: 'trade-id-2',
|
||||
price: (Number(trade.price) + 10).toString(),
|
||||
size: (Number(trade.size) - 10).toString(),
|
||||
};
|
||||
await act(async () => {
|
||||
render(<TradesTable data={[trade2, trade]} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
|
||||
const priceCells = cells.filter(
|
||||
(cell) => cell.getAttribute('col-id') === 'price'
|
||||
);
|
||||
const sizeCells = cells.filter(
|
||||
(cell) => cell.getAttribute('col-id') === 'size'
|
||||
);
|
||||
|
||||
// For first trade price should have green class and size should have red class
|
||||
// row 1
|
||||
expect(priceCells[0]).toHaveClass(UP_CLASS);
|
||||
expect(priceCells[1]).not.toHaveClass(DOWN_CLASS);
|
||||
expect(priceCells[1]).not.toHaveClass(UP_CLASS);
|
||||
|
||||
expect(sizeCells[0]).toHaveClass(DOWN_CLASS);
|
||||
expect(sizeCells[1]).not.toHaveClass(DOWN_CLASS);
|
||||
expect(sizeCells[1]).not.toHaveClass(UP_CLASS);
|
||||
});
|
88
libs/trades/src/lib/trades-table.tsx
Normal file
88
libs/trades/src/lib/trades-table.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||
import type { TradeFields } from './__generated__/TradeFields';
|
||||
import {
|
||||
formatNumber,
|
||||
getDateTimeFormat,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type { CellClassParams, ValueFormatterParams } from 'ag-grid-community';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { sortTrades } from './trades-data-provider';
|
||||
|
||||
export const UP_CLASS = 'text-vega-green';
|
||||
export const DOWN_CLASS = 'text-vega-pink';
|
||||
|
||||
const changeCellClass =
|
||||
(dataKey: string) =>
|
||||
({ api, value, node }: CellClassParams) => {
|
||||
const rowIndex = node?.rowIndex;
|
||||
let colorClass = '';
|
||||
|
||||
if (typeof rowIndex === 'number') {
|
||||
const prevRowNode = api.getModel().getRow(rowIndex + 1);
|
||||
const prevValue = prevRowNode?.data[dataKey];
|
||||
const valueNum = new BigNumber(value);
|
||||
|
||||
if (valueNum.isGreaterThan(prevValue)) {
|
||||
colorClass = UP_CLASS;
|
||||
} else if (valueNum.isLessThan(prevValue)) {
|
||||
colorClass = DOWN_CLASS;
|
||||
}
|
||||
}
|
||||
|
||||
return ['font-mono', colorClass].join(' ');
|
||||
};
|
||||
|
||||
interface TradesTableProps {
|
||||
data: TradeFields[] | null;
|
||||
}
|
||||
|
||||
export const TradesTable = forwardRef<AgGridReact, TradesTableProps>(
|
||||
({ data }, ref) => {
|
||||
// Sort intial trades
|
||||
const trades = useMemo(() => {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
return sortTrades(data);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No trades')}
|
||||
rowData={trades}
|
||||
getRowNodeId={(data) => data.id}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Price')}
|
||||
field="price"
|
||||
cellClass={changeCellClass('price')}
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) => {
|
||||
return formatNumber(value, data.market.decimalPlaces);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
field="size"
|
||||
cellClass={changeCellClass('size')}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created at')}
|
||||
field="createdAt"
|
||||
valueFormatter={({ value }: ValueFormatterParams) => {
|
||||
return getDateTimeFormat().format(new Date(value));
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
1
libs/trades/src/setup-tests.ts
Normal file
1
libs/trades/src/setup-tests.ts
Normal file
@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
25
libs/trades/tsconfig.json
Normal file
25
libs/trades/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
22
libs/trades/tsconfig.lib.json
Normal file
22
libs/trades/tsconfig.lib.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
19
libs/trades/tsconfig.spec.json
Normal file
19
libs/trades/tsconfig.spec.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
@ -27,6 +27,7 @@
|
||||
"@vegaprotocol/tailwindcss-config": [
|
||||
"libs/tailwindcss-config/src/index.js"
|
||||
],
|
||||
"@vegaprotocol/trades": ["libs/trades/src/index.ts"],
|
||||
"@vegaprotocol/types": ["libs/types/src/index.ts"],
|
||||
"@vegaprotocol/ui-toolkit": ["libs/ui-toolkit/src/index.ts"],
|
||||
"@vegaprotocol/wallet": ["libs/wallet/src/index.ts"],
|
||||
|
@ -16,6 +16,7 @@
|
||||
"stats": "apps/stats",
|
||||
"stats-e2e": "apps/stats-e2e",
|
||||
"tailwindcss-config": "libs/tailwindcss-config",
|
||||
"trades": "libs/trades",
|
||||
"trading": "apps/trading",
|
||||
"trading-e2e": "apps/trading-e2e",
|
||||
"types": "libs/types",
|
||||
|
Loading…
Reference in New Issue
Block a user