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 { GridTab, GridTabs } from './grid-tabs';
|
||||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||||
import { OrderListContainer } from '@vegaprotocol/order-list';
|
import { OrderListContainer } from '@vegaprotocol/order-list';
|
||||||
|
import { TradesContainer } from '@vegaprotocol/trades';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||||
import type { Market_market } from './__generated__/Market';
|
import type { Market_market } from './__generated__/Market';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||||
|
|
||||||
const Chart = () => (
|
const Chart = () => (
|
||||||
<Splash>
|
<Splash>
|
||||||
@ -20,16 +22,6 @@ const Orderbook = () => (
|
|||||||
<p>{t('Orderbook')}</p>
|
<p>{t('Orderbook')}</p>
|
||||||
</Splash>
|
</Splash>
|
||||||
);
|
);
|
||||||
const Collateral = () => (
|
|
||||||
<Splash>
|
|
||||||
<p>{t('Collateral')}</p>
|
|
||||||
</Splash>
|
|
||||||
);
|
|
||||||
const Trades = () => (
|
|
||||||
<Splash>
|
|
||||||
<p>{t('Trades')}</p>
|
|
||||||
</Splash>
|
|
||||||
);
|
|
||||||
|
|
||||||
const TradingViews = {
|
const TradingViews = {
|
||||||
Chart: Chart,
|
Chart: Chart,
|
||||||
@ -37,8 +29,8 @@ const TradingViews = {
|
|||||||
Orderbook: Orderbook,
|
Orderbook: Orderbook,
|
||||||
Orders: OrderListContainer,
|
Orders: OrderListContainer,
|
||||||
Positions: PositionsContainer,
|
Positions: PositionsContainer,
|
||||||
Collateral: Collateral,
|
Accounts: AccountsContainer,
|
||||||
Trades: Trades,
|
Trades: TradesContainer,
|
||||||
};
|
};
|
||||||
|
|
||||||
type TradingView = keyof typeof TradingViews;
|
type TradingView = keyof typeof TradingViews;
|
||||||
@ -50,7 +42,7 @@ interface TradeGridProps {
|
|||||||
export const TradeGrid = ({ market }: TradeGridProps) => {
|
export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||||
const wrapperClasses = classNames(
|
const wrapperClasses = classNames(
|
||||||
'h-full max-h-full',
|
'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',
|
'bg-black-10 dark:bg-white-10',
|
||||||
'text-ui'
|
'text-ui'
|
||||||
);
|
);
|
||||||
@ -70,7 +62,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
|||||||
<TradeGridChild className="row-start-1 row-end-3">
|
<TradeGridChild className="row-start-1 row-end-3">
|
||||||
<GridTabs group="trade">
|
<GridTabs group="trade">
|
||||||
<GridTab id="trades" name={t('Trades')}>
|
<GridTab id="trades" name={t('Trades')}>
|
||||||
<TradingViews.Trades />
|
<TradingViews.Trades marketId={market.id} />
|
||||||
</GridTab>
|
</GridTab>
|
||||||
<GridTab id="orderbook" name={t('Orderbook')}>
|
<GridTab id="orderbook" name={t('Orderbook')}>
|
||||||
<TradingViews.Orderbook />
|
<TradingViews.Orderbook />
|
||||||
@ -85,8 +77,8 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
|||||||
<GridTab id="positions" name={t('Positions')}>
|
<GridTab id="positions" name={t('Positions')}>
|
||||||
<TradingViews.Positions />
|
<TradingViews.Positions />
|
||||||
</GridTab>
|
</GridTab>
|
||||||
<GridTab id="collateral" name={t('Collateral')}>
|
<GridTab id="accounts" name={t('Accounts')}>
|
||||||
<TradingViews.Collateral />
|
<TradingViews.Accounts />
|
||||||
</GridTab>
|
</GridTab>
|
||||||
</GridTabs>
|
</GridTabs>
|
||||||
</TradeGridChild>
|
</TradeGridChild>
|
||||||
|
@ -86,6 +86,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
.subscribe<SubscriptionData>({
|
.subscribe<SubscriptionData>({
|
||||||
query: subscriptionQuery,
|
query: subscriptionQuery,
|
||||||
variables,
|
variables,
|
||||||
|
fetchPolicy,
|
||||||
})
|
})
|
||||||
.subscribe(({ data: subscriptionData }) => {
|
.subscribe(({ data: subscriptionData }) => {
|
||||||
if (!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": [
|
"@vegaprotocol/tailwindcss-config": [
|
||||||
"libs/tailwindcss-config/src/index.js"
|
"libs/tailwindcss-config/src/index.js"
|
||||||
],
|
],
|
||||||
|
"@vegaprotocol/trades": ["libs/trades/src/index.ts"],
|
||||||
"@vegaprotocol/types": ["libs/types/src/index.ts"],
|
"@vegaprotocol/types": ["libs/types/src/index.ts"],
|
||||||
"@vegaprotocol/ui-toolkit": ["libs/ui-toolkit/src/index.ts"],
|
"@vegaprotocol/ui-toolkit": ["libs/ui-toolkit/src/index.ts"],
|
||||||
"@vegaprotocol/wallet": ["libs/wallet/src/index.ts"],
|
"@vegaprotocol/wallet": ["libs/wallet/src/index.ts"],
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"stats": "apps/stats",
|
"stats": "apps/stats",
|
||||||
"stats-e2e": "apps/stats-e2e",
|
"stats-e2e": "apps/stats-e2e",
|
||||||
"tailwindcss-config": "libs/tailwindcss-config",
|
"tailwindcss-config": "libs/tailwindcss-config",
|
||||||
|
"trades": "libs/trades",
|
||||||
"trading": "apps/trading",
|
"trading": "apps/trading",
|
||||||
"trading-e2e": "apps/trading-e2e",
|
"trading-e2e": "apps/trading-e2e",
|
||||||
"types": "libs/types",
|
"types": "libs/types",
|
||||||
|
Loading…
Reference in New Issue
Block a user