Merge pull request #157 from vegaprotocol/feature/128-positions-table
Feature/128 positions table
This commit is contained in:
commit
65bb99ea72
@ -1,4 +1,4 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||||
import { Web3Container } from './web3-container';
|
import { Web3Container } from './web3-container';
|
||||||
|
|
||||||
const defaultHookValue = {
|
const defaultHookValue = {
|
||||||
@ -17,13 +17,15 @@ jest.mock('@web3-react/core', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prompt to connect opens dialog', () => {
|
test('Prompt to connect opens dialog', async () => {
|
||||||
mockHookValue = defaultHookValue;
|
mockHookValue = defaultHookValue;
|
||||||
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<Web3Container>
|
<Web3Container>
|
||||||
<div>Child</div>
|
<div>Child</div>
|
||||||
</Web3Container>
|
</Web3Container>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
expect(screen.queryByText('Child')).not.toBeInTheDocument();
|
expect(screen.queryByText('Child')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('web3-connector-list')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('web3-connector-list')).not.toBeInTheDocument();
|
||||||
@ -32,33 +34,35 @@ test('Prompt to connect opens dialog', () => {
|
|||||||
expect(screen.getByTestId('web3-connector-list')).toBeInTheDocument();
|
expect(screen.getByTestId('web3-connector-list')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Error message is shown', () => {
|
test('Error message is shown', async () => {
|
||||||
const message = 'Opps! An error';
|
const message = 'Opps! An error';
|
||||||
mockHookValue = { ...defaultHookValue, error: new Error(message) };
|
mockHookValue = { ...defaultHookValue, error: new Error(message) };
|
||||||
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<Web3Container>
|
<Web3Container>
|
||||||
<div>Child</div>
|
<div>Child</div>
|
||||||
</Web3Container>
|
</Web3Container>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
expect(screen.queryByText('Child')).not.toBeInTheDocument();
|
expect(screen.queryByText('Child')).not.toBeInTheDocument();
|
||||||
expect(screen.getByText(`Something went wrong: ${message}`));
|
expect(screen.getByText(`Something went wrong: ${message}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Chain id matches app configuration', () => {
|
test('Chain id matches app configuration', async () => {
|
||||||
const expectedChainId = 4;
|
const expectedChainId = 4;
|
||||||
mockHookValue = {
|
mockHookValue = {
|
||||||
...defaultHookValue,
|
...defaultHookValue,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
chainId: expectedChainId,
|
chainId: expectedChainId,
|
||||||
};
|
};
|
||||||
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<Web3Container>
|
<Web3Container>
|
||||||
<div>Child</div>
|
<div>Child</div>
|
||||||
</Web3Container>
|
</Web3Container>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
expect(screen.queryByText('Child')).not.toBeInTheDocument();
|
expect(screen.queryByText('Child')).not.toBeInTheDocument();
|
||||||
expect(screen.getByText(`This app only works on chain ID: 3`));
|
expect(screen.getByText(`This app only works on chain ID: 3`));
|
||||||
|
@ -1,56 +1,38 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useRef, useCallback } from 'react';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import { useApolloClient } from '@apollo/client';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { AsyncRenderer } from '../../components/async-renderer';
|
import { AsyncRenderer } from '../../components/async-renderer';
|
||||||
import { MarketListTable, getRowNodeId } from '@vegaprotocol/market-list';
|
import { MarketListTable, getRowNodeId } from '@vegaprotocol/market-list';
|
||||||
import {
|
import {
|
||||||
Markets_markets,
|
Markets_markets,
|
||||||
Markets_markets_data,
|
Markets_markets_data,
|
||||||
MarketsDataProviderCallbackArg,
|
|
||||||
marketsDataProvider,
|
marketsDataProvider,
|
||||||
} from '@vegaprotocol/graphql';
|
} from '@vegaprotocol/graphql';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
|
||||||
const Markets = () => {
|
const Markets = () => {
|
||||||
const { pathname, push } = useRouter();
|
const { pathname, push } = useRouter();
|
||||||
const [markets, setMarkets] = useState<Markets_markets[] | null>(null);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setError] = useState<Error>();
|
|
||||||
const client = useApolloClient();
|
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const initialized = useRef<boolean>(false);
|
const update = useCallback(
|
||||||
|
(delta: Markets_markets_data) => {
|
||||||
useEffect(() => {
|
|
||||||
return marketsDataProvider(
|
|
||||||
client,
|
|
||||||
({ data, error, loading, delta }: MarketsDataProviderCallbackArg) => {
|
|
||||||
setError(error);
|
|
||||||
setLoading(loading);
|
|
||||||
if (!error && !loading) {
|
|
||||||
if (!initialized.current || !gridRef.current) {
|
|
||||||
initialized.current = true;
|
|
||||||
setMarkets(data);
|
|
||||||
} else {
|
|
||||||
const update: Markets_markets[] = [];
|
const update: Markets_markets[] = [];
|
||||||
const add: Markets_markets[] = [];
|
const add: Markets_markets[] = [];
|
||||||
|
if (!gridRef.current) {
|
||||||
// split into updates and adds
|
return false;
|
||||||
if (!gridRef.current || !delta) return;
|
}
|
||||||
|
|
||||||
const rowNode = gridRef.current.api.getRowNode(
|
const rowNode = gridRef.current.api.getRowNode(
|
||||||
getRowNodeId(delta.market)
|
getRowNodeId(delta.market)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rowNode) {
|
if (rowNode) {
|
||||||
const updatedData = produce(
|
const updatedData = produce<Markets_markets_data>(
|
||||||
rowNode.data.data,
|
rowNode.data.data,
|
||||||
(draft: Markets_markets_data) => merge(draft, delta)
|
(draft: Markets_markets_data) => merge(draft, delta)
|
||||||
);
|
);
|
||||||
if (updatedData !== rowNode.data.data) {
|
if (updatedData !== rowNode.data.data) {
|
||||||
update.push({ ...rowNode.data, data: delta });
|
update.push({ ...rowNode.data, data: updatedData });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// @TODO - else add new market
|
// @TODO - else add new market
|
||||||
@ -61,18 +43,21 @@ const Markets = () => {
|
|||||||
addIndex: 0,
|
addIndex: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
}
|
},
|
||||||
}
|
[gridRef]
|
||||||
);
|
);
|
||||||
}, [client, initialized]);
|
const { data, error, loading } = useDataProvider<
|
||||||
|
Markets_markets,
|
||||||
|
Markets_markets_data
|
||||||
|
>(marketsDataProvider, update);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer loading={loading} error={error} data={markets}>
|
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||||
{(data) => (
|
{(data) => (
|
||||||
<MarketListTable
|
<MarketListTable
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
markets={data}
|
data={data}
|
||||||
onRowClicked={(id) =>
|
onRowClicked={(id) =>
|
||||||
push(`${pathname}/${id}?portfolio=orders&trade=orderbook`)
|
push(`${pathname}/${id}?portfolio=orders&trade=orderbook`)
|
||||||
}
|
}
|
||||||
|
71
apps/trading/pages/markets/positions.tsx
Normal file
71
apps/trading/pages/markets/positions.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useRef, useCallback, useMemo } from 'react';
|
||||||
|
import { produce } from 'immer';
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { AsyncRenderer } from '../../components/async-renderer';
|
||||||
|
import { PositionsTable, getRowNodeId } from '@vegaprotocol/positions';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
import {
|
||||||
|
Positions_party_positions,
|
||||||
|
PositionSubscribe_positions,
|
||||||
|
positionsDataProvider,
|
||||||
|
} from '@vegaprotocol/graphql';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
|
||||||
|
export const Positions = () => {
|
||||||
|
const { pathname, push } = useRouter();
|
||||||
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
|
const { keypair } = useVegaWallet();
|
||||||
|
const variables = useMemo(() => ({ partyId: keypair?.pub }), [keypair]);
|
||||||
|
const update = useCallback(
|
||||||
|
(delta: PositionSubscribe_positions) => {
|
||||||
|
const update: Positions_party_positions[] = [];
|
||||||
|
const add: Positions_party_positions[] = [];
|
||||||
|
if (!gridRef.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const rowNode = gridRef.current.api.getRowNode(getRowNodeId(delta));
|
||||||
|
if (rowNode) {
|
||||||
|
const updatedData = produce<Positions_party_positions>(
|
||||||
|
rowNode.data,
|
||||||
|
(draft: Positions_party_positions) => {
|
||||||
|
merge(draft, delta);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (updatedData !== rowNode.data) {
|
||||||
|
update.push(updatedData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add.push(delta);
|
||||||
|
}
|
||||||
|
if (update.length || add.length) {
|
||||||
|
gridRef.current.api.applyTransactionAsync({
|
||||||
|
update,
|
||||||
|
add,
|
||||||
|
addIndex: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[gridRef]
|
||||||
|
);
|
||||||
|
const { data, error, loading } = useDataProvider<
|
||||||
|
Positions_party_positions,
|
||||||
|
PositionSubscribe_positions
|
||||||
|
>(positionsDataProvider, update, variables);
|
||||||
|
return (
|
||||||
|
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||||
|
{(data) => (
|
||||||
|
<PositionsTable
|
||||||
|
ref={gridRef}
|
||||||
|
data={data}
|
||||||
|
onRowClicked={(id) =>
|
||||||
|
push(`${pathname}/${id}?portfolio=orders&trade=orderbook`)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AsyncRenderer>
|
||||||
|
);
|
||||||
|
};
|
@ -6,6 +6,7 @@ import { GridTab, GridTabs } from './grid-tabs';
|
|||||||
import { DealTicketContainer } from '../../components/deal-ticket-container';
|
import { DealTicketContainer } from '../../components/deal-ticket-container';
|
||||||
import { OrderListContainer } from '../..//components/order-list-container';
|
import { OrderListContainer } from '../..//components/order-list-container';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { Positions } from './positions';
|
||||||
|
|
||||||
const Chart = () => (
|
const Chart = () => (
|
||||||
<Splash>
|
<Splash>
|
||||||
@ -17,11 +18,6 @@ const Orderbook = () => (
|
|||||||
<p>Orderbook</p>
|
<p>Orderbook</p>
|
||||||
</Splash>
|
</Splash>
|
||||||
);
|
);
|
||||||
const Positions = () => (
|
|
||||||
<Splash>
|
|
||||||
<p>Positions</p>
|
|
||||||
</Splash>
|
|
||||||
);
|
|
||||||
const Collateral = () => (
|
const Collateral = () => (
|
||||||
<Splash>
|
<Splash>
|
||||||
<p>Collateral</p>
|
<p>Collateral</p>
|
||||||
|
@ -4,6 +4,6 @@ module.exports = {
|
|||||||
name: 'vega',
|
name: 'vega',
|
||||||
url: process.env.NX_VEGA_URL,
|
url: process.env.NX_VEGA_URL,
|
||||||
},
|
},
|
||||||
includes: ['../../{apps,lib}/**/*.{ts,tsx,js,jsx,graphql}'],
|
includes: ['../../{apps,libs}/**/*.{ts,tsx,js,jsx,graphql}'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
167
libs/graphql/src/__generated__/PositionDetails.ts
generated
Normal file
167
libs/graphql/src/__generated__/PositionDetails.ts
generated
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MarketTradingMode } from "./globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL fragment: PositionDetails
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface PositionDetails_market_data_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market_data {
|
||||||
|
__typename: "MarketData";
|
||||||
|
/**
|
||||||
|
* the mark price (actually an unsigned int)
|
||||||
|
*/
|
||||||
|
markPrice: string;
|
||||||
|
/**
|
||||||
|
* what state the market is in (auction, continuous etc)
|
||||||
|
*/
|
||||||
|
marketTradingMode: MarketTradingMode;
|
||||||
|
/**
|
||||||
|
* market id of the associated mark price
|
||||||
|
*/
|
||||||
|
market: PositionDetails_market_data_market;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market_tradableInstrument_instrument_metadata {
|
||||||
|
__typename: "InstrumentMetadata";
|
||||||
|
/**
|
||||||
|
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||||
|
*/
|
||||||
|
tags: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market_tradableInstrument_instrument_product_settlementAsset {
|
||||||
|
__typename: "Asset";
|
||||||
|
/**
|
||||||
|
* The id of the asset
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* The symbol of the asset (e.g: GBP)
|
||||||
|
*/
|
||||||
|
symbol: string;
|
||||||
|
/**
|
||||||
|
* The full name of the asset (e.g: Great British Pound)
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The precision of the asset
|
||||||
|
*/
|
||||||
|
decimals: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market_tradableInstrument_instrument_product {
|
||||||
|
__typename: "Future";
|
||||||
|
/**
|
||||||
|
* The name of the asset (string)
|
||||||
|
*/
|
||||||
|
settlementAsset: PositionDetails_market_tradableInstrument_instrument_product_settlementAsset;
|
||||||
|
/**
|
||||||
|
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||||
|
*/
|
||||||
|
quoteName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market_tradableInstrument_instrument {
|
||||||
|
__typename: "Instrument";
|
||||||
|
/**
|
||||||
|
* Uniquely identify an instrument across all instruments available on Vega (string)
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Full and fairly descriptive name for the instrument
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Metadata for this instrument
|
||||||
|
*/
|
||||||
|
metadata: PositionDetails_market_tradableInstrument_instrument_metadata;
|
||||||
|
/**
|
||||||
|
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
|
||||||
|
*/
|
||||||
|
code: string;
|
||||||
|
/**
|
||||||
|
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||||
|
*/
|
||||||
|
product: PositionDetails_market_tradableInstrument_instrument_product;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market_tradableInstrument {
|
||||||
|
__typename: "TradableInstrument";
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a fully specified instrument.
|
||||||
|
*/
|
||||||
|
instrument: PositionDetails_market_tradableInstrument_instrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Market full name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* marketData for the given market
|
||||||
|
*/
|
||||||
|
data: PositionDetails_market_data | null;
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a tradable instrument.
|
||||||
|
*/
|
||||||
|
tradableInstrument: PositionDetails_market_tradableInstrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionDetails {
|
||||||
|
__typename: "Position";
|
||||||
|
/**
|
||||||
|
* Realised Profit and Loss (int64)
|
||||||
|
*/
|
||||||
|
realisedPNL: string;
|
||||||
|
/**
|
||||||
|
* Open volume (uint64)
|
||||||
|
*/
|
||||||
|
openVolume: string;
|
||||||
|
/**
|
||||||
|
* Unrealised Profit and Loss (int64)
|
||||||
|
*/
|
||||||
|
unrealisedPNL: string;
|
||||||
|
/**
|
||||||
|
* Average entry price for this position
|
||||||
|
*/
|
||||||
|
averageEntryPrice: string;
|
||||||
|
/**
|
||||||
|
* Market relating to this position
|
||||||
|
*/
|
||||||
|
market: PositionDetails_market;
|
||||||
|
}
|
178
libs/graphql/src/__generated__/PositionSubscribe.ts
generated
Normal file
178
libs/graphql/src/__generated__/PositionSubscribe.ts
generated
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MarketTradingMode } from "./globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL subscription operation: PositionSubscribe
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_data_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_data {
|
||||||
|
__typename: "MarketData";
|
||||||
|
/**
|
||||||
|
* the mark price (actually an unsigned int)
|
||||||
|
*/
|
||||||
|
markPrice: string;
|
||||||
|
/**
|
||||||
|
* what state the market is in (auction, continuous etc)
|
||||||
|
*/
|
||||||
|
marketTradingMode: MarketTradingMode;
|
||||||
|
/**
|
||||||
|
* market id of the associated mark price
|
||||||
|
*/
|
||||||
|
market: PositionSubscribe_positions_market_data_market;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_tradableInstrument_instrument_metadata {
|
||||||
|
__typename: "InstrumentMetadata";
|
||||||
|
/**
|
||||||
|
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||||
|
*/
|
||||||
|
tags: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_tradableInstrument_instrument_product_settlementAsset {
|
||||||
|
__typename: "Asset";
|
||||||
|
/**
|
||||||
|
* The id of the asset
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* The symbol of the asset (e.g: GBP)
|
||||||
|
*/
|
||||||
|
symbol: string;
|
||||||
|
/**
|
||||||
|
* The full name of the asset (e.g: Great British Pound)
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The precision of the asset
|
||||||
|
*/
|
||||||
|
decimals: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_tradableInstrument_instrument_product {
|
||||||
|
__typename: "Future";
|
||||||
|
/**
|
||||||
|
* The name of the asset (string)
|
||||||
|
*/
|
||||||
|
settlementAsset: PositionSubscribe_positions_market_tradableInstrument_instrument_product_settlementAsset;
|
||||||
|
/**
|
||||||
|
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||||
|
*/
|
||||||
|
quoteName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_tradableInstrument_instrument {
|
||||||
|
__typename: "Instrument";
|
||||||
|
/**
|
||||||
|
* Uniquely identify an instrument across all instruments available on Vega (string)
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Full and fairly descriptive name for the instrument
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Metadata for this instrument
|
||||||
|
*/
|
||||||
|
metadata: PositionSubscribe_positions_market_tradableInstrument_instrument_metadata;
|
||||||
|
/**
|
||||||
|
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
|
||||||
|
*/
|
||||||
|
code: string;
|
||||||
|
/**
|
||||||
|
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||||
|
*/
|
||||||
|
product: PositionSubscribe_positions_market_tradableInstrument_instrument_product;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market_tradableInstrument {
|
||||||
|
__typename: "TradableInstrument";
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a fully specified instrument.
|
||||||
|
*/
|
||||||
|
instrument: PositionSubscribe_positions_market_tradableInstrument_instrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Market full name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* marketData for the given market
|
||||||
|
*/
|
||||||
|
data: PositionSubscribe_positions_market_data | null;
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a tradable instrument.
|
||||||
|
*/
|
||||||
|
tradableInstrument: PositionSubscribe_positions_market_tradableInstrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe_positions {
|
||||||
|
__typename: "Position";
|
||||||
|
/**
|
||||||
|
* Realised Profit and Loss (int64)
|
||||||
|
*/
|
||||||
|
realisedPNL: string;
|
||||||
|
/**
|
||||||
|
* Open volume (uint64)
|
||||||
|
*/
|
||||||
|
openVolume: string;
|
||||||
|
/**
|
||||||
|
* Unrealised Profit and Loss (int64)
|
||||||
|
*/
|
||||||
|
unrealisedPNL: string;
|
||||||
|
/**
|
||||||
|
* Average entry price for this position
|
||||||
|
*/
|
||||||
|
averageEntryPrice: string;
|
||||||
|
/**
|
||||||
|
* Market relating to this position
|
||||||
|
*/
|
||||||
|
market: PositionSubscribe_positions_market;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribe {
|
||||||
|
/**
|
||||||
|
* Subscribe to the positions updates
|
||||||
|
*/
|
||||||
|
positions: PositionSubscribe_positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionSubscribeVariables {
|
||||||
|
partyId: string;
|
||||||
|
}
|
190
libs/graphql/src/__generated__/Positions.ts
generated
Normal file
190
libs/graphql/src/__generated__/Positions.ts
generated
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MarketTradingMode } from "./globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: Positions
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_data_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_data {
|
||||||
|
__typename: "MarketData";
|
||||||
|
/**
|
||||||
|
* the mark price (actually an unsigned int)
|
||||||
|
*/
|
||||||
|
markPrice: string;
|
||||||
|
/**
|
||||||
|
* what state the market is in (auction, continuous etc)
|
||||||
|
*/
|
||||||
|
marketTradingMode: MarketTradingMode;
|
||||||
|
/**
|
||||||
|
* market id of the associated mark price
|
||||||
|
*/
|
||||||
|
market: Positions_party_positions_market_data_market;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_tradableInstrument_instrument_metadata {
|
||||||
|
__typename: "InstrumentMetadata";
|
||||||
|
/**
|
||||||
|
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||||
|
*/
|
||||||
|
tags: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_tradableInstrument_instrument_product_settlementAsset {
|
||||||
|
__typename: "Asset";
|
||||||
|
/**
|
||||||
|
* The id of the asset
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* The symbol of the asset (e.g: GBP)
|
||||||
|
*/
|
||||||
|
symbol: string;
|
||||||
|
/**
|
||||||
|
* The full name of the asset (e.g: Great British Pound)
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The precision of the asset
|
||||||
|
*/
|
||||||
|
decimals: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_tradableInstrument_instrument_product {
|
||||||
|
__typename: "Future";
|
||||||
|
/**
|
||||||
|
* The name of the asset (string)
|
||||||
|
*/
|
||||||
|
settlementAsset: Positions_party_positions_market_tradableInstrument_instrument_product_settlementAsset;
|
||||||
|
/**
|
||||||
|
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||||
|
*/
|
||||||
|
quoteName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_tradableInstrument_instrument {
|
||||||
|
__typename: "Instrument";
|
||||||
|
/**
|
||||||
|
* Uniquely identify an instrument across all instruments available on Vega (string)
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Full and fairly descriptive name for the instrument
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Metadata for this instrument
|
||||||
|
*/
|
||||||
|
metadata: Positions_party_positions_market_tradableInstrument_instrument_metadata;
|
||||||
|
/**
|
||||||
|
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
|
||||||
|
*/
|
||||||
|
code: string;
|
||||||
|
/**
|
||||||
|
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||||
|
*/
|
||||||
|
product: Positions_party_positions_market_tradableInstrument_instrument_product;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market_tradableInstrument {
|
||||||
|
__typename: "TradableInstrument";
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a fully specified instrument.
|
||||||
|
*/
|
||||||
|
instrument: Positions_party_positions_market_tradableInstrument_instrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Market full name
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* marketData for the given market
|
||||||
|
*/
|
||||||
|
data: Positions_party_positions_market_data | null;
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a tradable instrument.
|
||||||
|
*/
|
||||||
|
tradableInstrument: Positions_party_positions_market_tradableInstrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party_positions {
|
||||||
|
__typename: "Position";
|
||||||
|
/**
|
||||||
|
* Realised Profit and Loss (int64)
|
||||||
|
*/
|
||||||
|
realisedPNL: string;
|
||||||
|
/**
|
||||||
|
* Open volume (uint64)
|
||||||
|
*/
|
||||||
|
openVolume: string;
|
||||||
|
/**
|
||||||
|
* Unrealised Profit and Loss (int64)
|
||||||
|
*/
|
||||||
|
unrealisedPNL: string;
|
||||||
|
/**
|
||||||
|
* Average entry price for this position
|
||||||
|
*/
|
||||||
|
averageEntryPrice: string;
|
||||||
|
/**
|
||||||
|
* Market relating to this position
|
||||||
|
*/
|
||||||
|
market: Positions_party_positions_market;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions_party {
|
||||||
|
__typename: "Party";
|
||||||
|
/**
|
||||||
|
* Party identifier
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Trading positions relating to a party
|
||||||
|
*/
|
||||||
|
positions: Positions_party_positions[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Positions {
|
||||||
|
/**
|
||||||
|
* An entity that is trading on the VEGA network
|
||||||
|
*/
|
||||||
|
party: Positions_party | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionsVariables {
|
||||||
|
partyId: string;
|
||||||
|
}
|
199
libs/graphql/src/data-providers/generic-data-provider.ts
Normal file
199
libs/graphql/src/data-providers/generic-data-provider.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { produce } from 'immer';
|
||||||
|
import type { Draft } from 'immer';
|
||||||
|
import type {
|
||||||
|
ApolloClient,
|
||||||
|
DocumentNode,
|
||||||
|
FetchPolicy,
|
||||||
|
TypedDocumentNode,
|
||||||
|
OperationVariables,
|
||||||
|
} from '@apollo/client';
|
||||||
|
import type { Subscription } from 'zen-observable-ts';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
|
export interface UpdateCallback<Data, Delta> {
|
||||||
|
(arg: {
|
||||||
|
data: Data[] | null;
|
||||||
|
error?: Error;
|
||||||
|
loading: boolean;
|
||||||
|
delta?: Delta;
|
||||||
|
}): void;
|
||||||
|
}
|
||||||
|
export interface Subscribe<Data, Delta> {
|
||||||
|
(
|
||||||
|
callback: UpdateCallback<Data, Delta>,
|
||||||
|
client: ApolloClient<object>,
|
||||||
|
variables?: OperationVariables
|
||||||
|
): () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
||||||
|
|
||||||
|
interface Update<Data, Delta> {
|
||||||
|
(draft: Draft<Data>[], delta: Delta): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetData<QueryData, Data> {
|
||||||
|
(subscriptionData: QueryData): Data[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetDelta<SubscriptionData, Delta> {
|
||||||
|
(subscriptionData: SubscriptionData): Delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||||
|
query: Query<QueryData>,
|
||||||
|
subscriptionQuery: Query<SubscriptionData>,
|
||||||
|
update: Update<Data, Delta>,
|
||||||
|
getData: GetData<QueryData, Data>,
|
||||||
|
getDelta: GetDelta<SubscriptionData, Delta>,
|
||||||
|
fetchPolicy: FetchPolicy = 'no-cache'
|
||||||
|
): Subscribe<Data, Delta> {
|
||||||
|
const callbacks: UpdateCallback<Data, Delta>[] = [];
|
||||||
|
const updateQueue: Delta[] = [];
|
||||||
|
|
||||||
|
let variables: OperationVariables | undefined = undefined;
|
||||||
|
let data: Data[] | null = null;
|
||||||
|
let error: Error | undefined = undefined;
|
||||||
|
let loading = false;
|
||||||
|
let client: ApolloClient<object> | undefined = undefined;
|
||||||
|
let subscription: Subscription | undefined = undefined;
|
||||||
|
|
||||||
|
const notify = (callback: UpdateCallback<Data, Delta>, delta?: Delta) => {
|
||||||
|
callback({
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
delta,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifyAll = (delta?: Delta) => {
|
||||||
|
callbacks.forEach((callback) => notify(callback, delta));
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
if (subscription) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading = true;
|
||||||
|
error = undefined;
|
||||||
|
notifyAll();
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subscription = client
|
||||||
|
.subscribe<SubscriptionData>({
|
||||||
|
query: subscriptionQuery,
|
||||||
|
variables,
|
||||||
|
})
|
||||||
|
.subscribe(({ data: subscriptionData }) => {
|
||||||
|
if (!subscriptionData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const delta = getDelta(subscriptionData);
|
||||||
|
if (loading || !data) {
|
||||||
|
updateQueue.push(delta);
|
||||||
|
} else {
|
||||||
|
const newData = produce(data, (draft) => {
|
||||||
|
update(draft, delta);
|
||||||
|
});
|
||||||
|
if (newData === data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = newData;
|
||||||
|
notifyAll(delta);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const res = await client.query<QueryData>({
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
fetchPolicy,
|
||||||
|
});
|
||||||
|
data = getData(res.data);
|
||||||
|
if (data && updateQueue && updateQueue.length > 0) {
|
||||||
|
data = produce(data, (draft) => {
|
||||||
|
while (updateQueue.length) {
|
||||||
|
const delta = updateQueue.shift();
|
||||||
|
if (delta) {
|
||||||
|
update(draft, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error = e as Error;
|
||||||
|
subscription.unsubscribe();
|
||||||
|
subscription = undefined;
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribe = (callback: UpdateCallback<Data, Delta>) => {
|
||||||
|
callbacks.splice(callbacks.indexOf(callback), 1);
|
||||||
|
if (callbacks.length === 0) {
|
||||||
|
if (subscription) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
subscription = undefined;
|
||||||
|
}
|
||||||
|
data = null;
|
||||||
|
error = undefined;
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (callback, c, v) => {
|
||||||
|
callbacks.push(callback);
|
||||||
|
if (callbacks.length === 1) {
|
||||||
|
client = c;
|
||||||
|
variables = v;
|
||||||
|
initialize();
|
||||||
|
} else {
|
||||||
|
notify(callback);
|
||||||
|
}
|
||||||
|
return () => unsubscribe(callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoize = <Data, Delta>(
|
||||||
|
fn: (variables?: OperationVariables) => Subscribe<Data, Delta>
|
||||||
|
) => {
|
||||||
|
const cache: {
|
||||||
|
subscribe: Subscribe<Data, Delta>;
|
||||||
|
variables?: OperationVariables;
|
||||||
|
}[] = [];
|
||||||
|
return (variables?: OperationVariables) => {
|
||||||
|
const cached = cache.find((c) => isEqual(c.variables, variables));
|
||||||
|
if (cached) {
|
||||||
|
return cached.subscribe;
|
||||||
|
}
|
||||||
|
const subscribe = fn(variables);
|
||||||
|
cache.push({ subscribe, variables });
|
||||||
|
return subscribe;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||||
|
query: Query<QueryData>,
|
||||||
|
subscriptionQuery: Query<SubscriptionData>,
|
||||||
|
update: Update<Data, Delta>,
|
||||||
|
getData: GetData<QueryData, Data>,
|
||||||
|
getDelta: GetDelta<SubscriptionData, Delta>,
|
||||||
|
fetchPolicy: FetchPolicy = 'no-cache'
|
||||||
|
): Subscribe<Data, Delta> {
|
||||||
|
const getInstance = memoize<Data, Delta>((variables) =>
|
||||||
|
makeDataProviderInternal(
|
||||||
|
query,
|
||||||
|
subscriptionQuery,
|
||||||
|
update,
|
||||||
|
getData,
|
||||||
|
getDelta,
|
||||||
|
fetchPolicy
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return (callback, client, variables) =>
|
||||||
|
getInstance(variables)(callback, client, variables);
|
||||||
|
}
|
@ -1 +1,3 @@
|
|||||||
export * from './markets-data-provider';
|
export * from './markets-data-provider';
|
||||||
|
export * from './positions-data-provider';
|
||||||
|
export type { Subscribe } from './generic-data-provider';
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import { produce } from 'immer';
|
|
||||||
import type { ApolloClient } from '@apollo/client';
|
|
||||||
import type { Subscription } from 'zen-observable-ts';
|
|
||||||
import { Markets, Markets_markets } from '../__generated__/Markets';
|
import { Markets, Markets_markets } from '../__generated__/Markets';
|
||||||
|
import { makeDataProvider } from './generic-data-provider';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MarketDataSub,
|
MarketDataSub,
|
||||||
@ -57,137 +55,21 @@ const MARKET_DATA_SUB = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface MarketsDataProviderCallbackArg {
|
const update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||||
data: Markets_markets[] | null;
|
|
||||||
error?: Error;
|
|
||||||
loading: boolean;
|
|
||||||
delta?: MarketDataSub_marketData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MarketsDataProviderCallback {
|
|
||||||
(arg: MarketsDataProviderCallbackArg): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const callbacks: MarketsDataProviderCallback[] = [];
|
|
||||||
const updateQueue: MarketDataSub_marketData[] = [];
|
|
||||||
|
|
||||||
let data: Markets_markets[] | null = null;
|
|
||||||
let error: Error | undefined = undefined;
|
|
||||||
let loading = false;
|
|
||||||
let client: ApolloClient<object> | undefined = undefined;
|
|
||||||
let subscription: Subscription | undefined = undefined;
|
|
||||||
|
|
||||||
const notify = (
|
|
||||||
callback: MarketsDataProviderCallback,
|
|
||||||
delta?: MarketDataSub_marketData
|
|
||||||
) => {
|
|
||||||
callback({
|
|
||||||
data,
|
|
||||||
error,
|
|
||||||
loading,
|
|
||||||
delta,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const notifyAll = (delta?: MarketDataSub_marketData) => {
|
|
||||||
callbacks.forEach((callback) => notify(callback, delta));
|
|
||||||
};
|
|
||||||
|
|
||||||
const update = (
|
|
||||||
draft: Markets_markets[] | null,
|
|
||||||
delta: MarketDataSub_marketData
|
|
||||||
) => {
|
|
||||||
if (!draft) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
draft[index].data = delta;
|
draft[index].data = delta;
|
||||||
}
|
}
|
||||||
// @TODO - else push new market to draft
|
// @TODO - else push new market to draft
|
||||||
};
|
};
|
||||||
|
const getData = (responseData: Markets): Markets_markets[] | null =>
|
||||||
|
responseData.markets;
|
||||||
|
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
||||||
|
subscriptionData.marketData;
|
||||||
|
|
||||||
const initialize = async () => {
|
export const marketsDataProvider = makeDataProvider<
|
||||||
if (subscription) {
|
Markets,
|
||||||
return;
|
Markets_markets,
|
||||||
}
|
MarketDataSub,
|
||||||
loading = true;
|
MarketDataSub_marketData
|
||||||
error = undefined;
|
>(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta);
|
||||||
notifyAll();
|
|
||||||
if (!client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
subscription = client
|
|
||||||
.subscribe<MarketDataSub>({
|
|
||||||
query: MARKET_DATA_SUB,
|
|
||||||
})
|
|
||||||
.subscribe(({ data: delta }) => {
|
|
||||||
if (!delta) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (loading) {
|
|
||||||
updateQueue.push(delta.marketData);
|
|
||||||
} else {
|
|
||||||
const newData = produce(data, (draft) => {
|
|
||||||
update(draft, delta.marketData);
|
|
||||||
});
|
|
||||||
if (newData === data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data = newData;
|
|
||||||
notifyAll(delta.marketData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const res = await client.query<Markets>({
|
|
||||||
query: MARKETS_QUERY,
|
|
||||||
});
|
|
||||||
data = res.data.markets;
|
|
||||||
if (updateQueue && updateQueue.length > 0) {
|
|
||||||
data = produce(data, (draft) => {
|
|
||||||
while (updateQueue.length) {
|
|
||||||
const delta = updateQueue.shift();
|
|
||||||
if (delta) {
|
|
||||||
update(draft, delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
error = e as Error;
|
|
||||||
subscription.unsubscribe();
|
|
||||||
subscription = undefined;
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const unsubscribe = (callback: MarketsDataProviderCallback) => {
|
|
||||||
callbacks.splice(callbacks.indexOf(callback), 1);
|
|
||||||
if (callbacks.length === 0) {
|
|
||||||
if (subscription) {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
subscription = undefined;
|
|
||||||
}
|
|
||||||
data = null;
|
|
||||||
error = undefined;
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const marketsDataProvider = (
|
|
||||||
c: ApolloClient<object>,
|
|
||||||
callback: MarketsDataProviderCallback
|
|
||||||
) => {
|
|
||||||
if (!client) {
|
|
||||||
client = c;
|
|
||||||
}
|
|
||||||
callbacks.push(callback);
|
|
||||||
if (callbacks.length === 1) {
|
|
||||||
initialize();
|
|
||||||
} else {
|
|
||||||
notify(callback);
|
|
||||||
}
|
|
||||||
return () => unsubscribe(callback);
|
|
||||||
};
|
|
||||||
|
98
libs/graphql/src/data-providers/positions-data-provider.ts
Normal file
98
libs/graphql/src/data-providers/positions-data-provider.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import {
|
||||||
|
Positions,
|
||||||
|
Positions_party_positions,
|
||||||
|
} from '../__generated__/Positions';
|
||||||
|
import { makeDataProvider } from './generic-data-provider';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PositionSubscribe,
|
||||||
|
PositionSubscribe_positions,
|
||||||
|
} from '../__generated__/PositionSubscribe';
|
||||||
|
|
||||||
|
const POSITIONS_FRAGMENT = gql`
|
||||||
|
fragment PositionDetails on Position {
|
||||||
|
realisedPNL
|
||||||
|
openVolume
|
||||||
|
unrealisedPNL
|
||||||
|
averageEntryPrice
|
||||||
|
market {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
data {
|
||||||
|
markPrice
|
||||||
|
marketTradingMode
|
||||||
|
market {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decimalPlaces
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
metadata {
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
code
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
settlementAsset {
|
||||||
|
id
|
||||||
|
symbol
|
||||||
|
name
|
||||||
|
decimals
|
||||||
|
}
|
||||||
|
quoteName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const POSITION_QUERY = gql`
|
||||||
|
${POSITIONS_FRAGMENT}
|
||||||
|
query Positions($partyId: ID!) {
|
||||||
|
party(id: $partyId) {
|
||||||
|
id
|
||||||
|
positions {
|
||||||
|
...PositionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const POSITIONS_SUB = gql`
|
||||||
|
${POSITIONS_FRAGMENT}
|
||||||
|
subscription PositionSubscribe($partyId: ID!) {
|
||||||
|
positions(partyId: $partyId) {
|
||||||
|
...PositionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const update = (
|
||||||
|
draft: Positions_party_positions[],
|
||||||
|
delta: PositionSubscribe_positions
|
||||||
|
) => {
|
||||||
|
const index = draft.findIndex((m) => m.market.id === delta.market.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
draft[index] = delta;
|
||||||
|
} else {
|
||||||
|
draft.push(delta);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getData = (responseData: Positions): Positions_party_positions[] | null =>
|
||||||
|
responseData.party ? responseData.party.positions : null;
|
||||||
|
const getDelta = (
|
||||||
|
subscriptionData: PositionSubscribe
|
||||||
|
): PositionSubscribe_positions => subscriptionData.positions;
|
||||||
|
|
||||||
|
export const positionsDataProvider = makeDataProvider<
|
||||||
|
Positions,
|
||||||
|
Positions_party_positions,
|
||||||
|
PositionSubscribe,
|
||||||
|
PositionSubscribe_positions
|
||||||
|
>(POSITION_QUERY, POSITIONS_SUB, update, getData, getDelta);
|
@ -2,10 +2,10 @@ export * from './__generated__/AssetsQuery';
|
|||||||
export * from './__generated__/globalTypes';
|
export * from './__generated__/globalTypes';
|
||||||
export * from './__generated__/Guess';
|
export * from './__generated__/Guess';
|
||||||
export * from './__generated__/Market';
|
export * from './__generated__/Market';
|
||||||
|
export * from './__generated__/MarketDataFields';
|
||||||
|
export * from './__generated__/MarketDataSub';
|
||||||
export * from './__generated__/Markets';
|
export * from './__generated__/Markets';
|
||||||
export * from './__generated__/MarketsQuery';
|
export * from './__generated__/MarketsQuery';
|
||||||
export * from './__generated__/MarketDataSub';
|
|
||||||
export * from './__generated__/MarketDataFields';
|
|
||||||
export * from './__generated__/NetworkParametersQuery';
|
export * from './__generated__/NetworkParametersQuery';
|
||||||
export * from './__generated__/NodesQuery';
|
export * from './__generated__/NodesQuery';
|
||||||
export * from './__generated__/OrderEvent';
|
export * from './__generated__/OrderEvent';
|
||||||
@ -13,6 +13,9 @@ export * from './__generated__/OrderFields';
|
|||||||
export * from './__generated__/Orders';
|
export * from './__generated__/Orders';
|
||||||
export * from './__generated__/OrderSub';
|
export * from './__generated__/OrderSub';
|
||||||
export * from './__generated__/PartyAssetsQuery';
|
export * from './__generated__/PartyAssetsQuery';
|
||||||
|
export * from './__generated__/PositionDetails';
|
||||||
|
export * from './__generated__/Positions';
|
||||||
|
export * from './__generated__/PositionSubscribe';
|
||||||
export * from './__generated__/ProposalsQuery';
|
export * from './__generated__/ProposalsQuery';
|
||||||
|
|
||||||
export * from './data-providers';
|
export * from './data-providers';
|
||||||
|
@ -7,19 +7,19 @@ import { AgGridColumn } from 'ag-grid-react';
|
|||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
|
||||||
interface MarketListTableProps {
|
interface MarketListTableProps {
|
||||||
markets: Markets_markets[] | null;
|
data: Markets_markets[] | null;
|
||||||
onRowClicked: (marketId: string) => void;
|
onRowClicked: (marketId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRowNodeId = (data: { id: string }) => data.id;
|
export const getRowNodeId = (data: { id: string }) => data.id;
|
||||||
|
|
||||||
export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>(
|
||||||
({ markets, onRowClicked }, ref) => {
|
({ data, onRowClicked }, ref) => {
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate="No markets"
|
overlayNoRowsTemplate="No markets"
|
||||||
rowData={markets}
|
rowData={data}
|
||||||
getRowNodeId={getRowNodeId}
|
getRowNodeId={getRowNodeId}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
defaultColDef={{
|
defaultColDef={{
|
||||||
|
6
libs/positions/.babelrc
Normal file
6
libs/positions/.babelrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@nrwl/next/babel"
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
18
libs/positions/.eslintrc.json
Normal file
18
libs/positions/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
libs/positions/README.md
Normal file
7
libs/positions/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# positions
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test positions` to execute the unit tests via [Jest](https://jestjs.io).
|
15
libs/positions/jest.config.js
Normal file
15
libs/positions/jest.config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
displayName: 'positions',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
coverageDirectory: '../../coverage/libs/positions',
|
||||||
|
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||||
|
};
|
4
libs/positions/package.json
Normal file
4
libs/positions/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "@vegaprotocol/positions",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
43
libs/positions/project.json
Normal file
43
libs/positions/project.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"root": "libs/positions",
|
||||||
|
"sourceRoot": "libs/positions/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@nrwl/web:rollup",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/libs/positions",
|
||||||
|
"tsConfig": "libs/positions/tsconfig.lib.json",
|
||||||
|
"project": "libs/positions/package.json",
|
||||||
|
"entryFile": "libs/positions/src/index.ts",
|
||||||
|
"external": ["react/jsx-runtime"],
|
||||||
|
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||||
|
"compiler": "babel",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "libs/positions/README.md",
|
||||||
|
"input": ".",
|
||||||
|
"output": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["libs/positions/**/*.{ts,tsx,js,jsx}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"outputs": ["coverage/libs/positions"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/positions/jest.config.js",
|
||||||
|
"passWithNoTests": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
libs/positions/src/index.ts
Normal file
1
libs/positions/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/positions-table';
|
101
libs/positions/src/lib/positions-table.spec.tsx
Normal file
101
libs/positions/src/lib/positions-table.spec.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { act, render, screen } from '@testing-library/react';
|
||||||
|
import PositionsTable from './positions-table';
|
||||||
|
import {
|
||||||
|
Positions_party_positions,
|
||||||
|
MarketTradingMode,
|
||||||
|
} from '@vegaprotocol/graphql';
|
||||||
|
|
||||||
|
const singleRow: Positions_party_positions = {
|
||||||
|
realisedPNL: '5',
|
||||||
|
openVolume: '100',
|
||||||
|
unrealisedPNL: '895000',
|
||||||
|
averageEntryPrice: '1129935',
|
||||||
|
market: {
|
||||||
|
id: 'b7010da9dbe7fbab2b74d9d5642fc4a8a0ca93ef803d21fa60c2cacd0416bba0',
|
||||||
|
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||||
|
data: {
|
||||||
|
markPrice: '1138885',
|
||||||
|
marketTradingMode: MarketTradingMode.Continuous,
|
||||||
|
__typename: 'MarketData',
|
||||||
|
market: { __typename: 'Market', id: '123' },
|
||||||
|
},
|
||||||
|
decimalPlaces: 5,
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
id: '',
|
||||||
|
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||||
|
metadata: {
|
||||||
|
tags: [
|
||||||
|
'formerly:3C58ED2A4A6C5D7E',
|
||||||
|
'base:UNI',
|
||||||
|
'quote:DAI',
|
||||||
|
'class:fx/crypto',
|
||||||
|
'monthly',
|
||||||
|
'sector:defi',
|
||||||
|
],
|
||||||
|
__typename: 'InstrumentMetadata',
|
||||||
|
},
|
||||||
|
code: 'UNIDAI.MF21',
|
||||||
|
product: {
|
||||||
|
settlementAsset: {
|
||||||
|
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||||
|
symbol: 'tDAI',
|
||||||
|
name: 'tDAI TEST',
|
||||||
|
decimals: 5,
|
||||||
|
__typename: 'Asset',
|
||||||
|
},
|
||||||
|
quoteName: 'DAI',
|
||||||
|
__typename: 'Future',
|
||||||
|
},
|
||||||
|
__typename: 'Instrument',
|
||||||
|
},
|
||||||
|
__typename: 'TradableInstrument',
|
||||||
|
},
|
||||||
|
__typename: 'Market',
|
||||||
|
},
|
||||||
|
__typename: 'Position',
|
||||||
|
};
|
||||||
|
const singleRowData = [singleRow];
|
||||||
|
const onRowClicked = jest.fn;
|
||||||
|
|
||||||
|
test('should render successfully', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<PositionsTable data={[]} onRowClicked={onRowClicked} />
|
||||||
|
);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('Render correct columns', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(<PositionsTable data={singleRowData} onRowClicked={onRowClicked} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = screen.getAllByRole('columnheader');
|
||||||
|
expect(headers).toHaveLength(5);
|
||||||
|
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
||||||
|
'Market',
|
||||||
|
'Amount',
|
||||||
|
'Average Entry Price',
|
||||||
|
'Mark Price',
|
||||||
|
'Realised PNL',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Correct formatting applied', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(<PositionsTable data={singleRowData} onRowClicked={onRowClicked} />);
|
||||||
|
});
|
||||||
|
const cells = screen.getAllByRole('gridcell');
|
||||||
|
const expectedValues = [
|
||||||
|
singleRow.market.tradableInstrument.instrument.code,
|
||||||
|
'+100',
|
||||||
|
'11.29935',
|
||||||
|
'11.38885',
|
||||||
|
'+5',
|
||||||
|
];
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
|
});
|
||||||
|
expect(cells[cells.length - 1]).toHaveClass('color-vega-green');
|
||||||
|
});
|
132
libs/positions/src/lib/positions-table.tsx
Normal file
132
libs/positions/src/lib/positions-table.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { forwardRef, useMemo } from 'react';
|
||||||
|
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||||
|
import {
|
||||||
|
PriceCell,
|
||||||
|
formatNumber,
|
||||||
|
volumePrefix,
|
||||||
|
addDecimal,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import compact from 'lodash/compact';
|
||||||
|
import {
|
||||||
|
Positions_party_positions,
|
||||||
|
MarketTradingMode,
|
||||||
|
} from '@vegaprotocol/graphql';
|
||||||
|
|
||||||
|
interface PositionsTableProps {
|
||||||
|
data: Positions_party_positions[] | null;
|
||||||
|
onRowClicked: (marketId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRowNodeId = (data: { market: { id: string } }) =>
|
||||||
|
data.market.id;
|
||||||
|
|
||||||
|
const sortByName = (
|
||||||
|
a: Positions_party_positions,
|
||||||
|
b: Positions_party_positions
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
a.market.tradableInstrument.instrument.name <
|
||||||
|
b.market.tradableInstrument.instrument.name
|
||||||
|
) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
a.market.tradableInstrument.instrument.name >
|
||||||
|
b.market.tradableInstrument.instrument.name
|
||||||
|
) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PositionsTableValueFormatterParams extends ValueFormatterParams {
|
||||||
|
data: Positions_party_positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PositionsTable = forwardRef<AgGridReact, PositionsTableProps>(
|
||||||
|
({ data, onRowClicked }, ref) => {
|
||||||
|
const sortedData = useMemo(() => {
|
||||||
|
return compact(data).sort(sortByName);
|
||||||
|
}, [data]);
|
||||||
|
return (
|
||||||
|
<AgGrid
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
overlayNoRowsTemplate="No positions"
|
||||||
|
rowData={sortedData}
|
||||||
|
getRowNodeId={getRowNodeId}
|
||||||
|
ref={ref}
|
||||||
|
defaultColDef={{
|
||||||
|
flex: 1,
|
||||||
|
resizable: true,
|
||||||
|
}}
|
||||||
|
onRowClicked={({ data }: { data: Positions_party_positions }) =>
|
||||||
|
onRowClicked(getRowNodeId(data))
|
||||||
|
}
|
||||||
|
components={{ PriceCell }}
|
||||||
|
>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName="Market"
|
||||||
|
field="market.tradableInstrument.instrument.code"
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName="Amount"
|
||||||
|
field="openVolume"
|
||||||
|
valueFormatter={({ value }: PositionsTableValueFormatterParams) =>
|
||||||
|
volumePrefix(value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName="Average Entry Price"
|
||||||
|
field="averageEntryPrice"
|
||||||
|
cellRenderer="PriceCell"
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: PositionsTableValueFormatterParams) =>
|
||||||
|
formatNumber(value, data.market.decimalPlaces)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName="Mark Price"
|
||||||
|
field="market.data.markPrice"
|
||||||
|
type="rightAligned"
|
||||||
|
cellRenderer="PriceCell"
|
||||||
|
valueFormatter={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: PositionsTableValueFormatterParams) => {
|
||||||
|
if (
|
||||||
|
data.market.data?.marketTradingMode ===
|
||||||
|
MarketTradingMode.OpeningAuction
|
||||||
|
) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return addDecimal(value, data.market.decimalPlaces);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName="Realised PNL"
|
||||||
|
field="realisedPNL"
|
||||||
|
type="rightAligned"
|
||||||
|
cellClassRules={{
|
||||||
|
'color-vega-green': ({ value }: { value: string }) =>
|
||||||
|
Number(value) > 0,
|
||||||
|
'color-vega-red': ({ value }: { value: string }) =>
|
||||||
|
Number(value) < 0,
|
||||||
|
}}
|
||||||
|
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||||
|
volumePrefix(value)
|
||||||
|
}
|
||||||
|
cellRenderer="PriceCell"
|
||||||
|
/>
|
||||||
|
</AgGrid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PositionsTable;
|
1
libs/positions/src/setup-tests.ts
Normal file
1
libs/positions/src/setup-tests.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
25
libs/positions/tsconfig.json
Normal file
25
libs/positions/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/positions/tsconfig.lib.json
Normal file
22
libs/positions/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/next/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/positions/tsconfig.spec.json
Normal file
19
libs/positions/tsconfig.spec.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.jsx",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
3
libs/react-helpers/src/hooks/index.ts
Normal file
3
libs/react-helpers/src/hooks/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './use-apply-grid-transaction';
|
||||||
|
export * from './use-data-provider';
|
||||||
|
export * from './use-theme-switcher';
|
33
libs/react-helpers/src/hooks/use-data-provider.ts
Normal file
33
libs/react-helpers/src/hooks/use-data-provider.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { useApolloClient } from '@apollo/client';
|
||||||
|
import type { OperationVariables } from '@apollo/client';
|
||||||
|
import type { Subscribe } from '@vegaprotocol/graphql';
|
||||||
|
|
||||||
|
export function useDataProvider<Data, Delta>(
|
||||||
|
dataProvider: Subscribe<Data, Delta>,
|
||||||
|
update?: (delta: Delta) => boolean,
|
||||||
|
variables?: OperationVariables
|
||||||
|
) {
|
||||||
|
const client = useApolloClient();
|
||||||
|
const [data, setData] = useState<Data[] | null>(null);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [error, setError] = useState<Error | undefined>(undefined);
|
||||||
|
const initialized = useRef<boolean>(false);
|
||||||
|
const callback = useCallback(
|
||||||
|
({ data, error, loading, delta }) => {
|
||||||
|
setError(error);
|
||||||
|
setLoading(loading);
|
||||||
|
if (!error && !loading) {
|
||||||
|
if (!initialized.current || !delta || !update || !update(delta)) {
|
||||||
|
initialized.current = true;
|
||||||
|
setData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[update]
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
return dataProvider(callback, client, variables);
|
||||||
|
}, [client, initialized, dataProvider, callback, variables]);
|
||||||
|
return { data, loading, error };
|
||||||
|
}
|
@ -5,5 +5,4 @@ export * from './lib/format';
|
|||||||
export * from './lib/grid-cells';
|
export * from './lib/grid-cells';
|
||||||
export * from './lib/storage';
|
export * from './lib/storage';
|
||||||
|
|
||||||
export * from './hooks/use-apply-grid-transaction';
|
export * from './hooks';
|
||||||
export * from './hooks/use-theme-switcher';
|
|
||||||
|
@ -7,6 +7,19 @@ const getUserLocale = () => 'default';
|
|||||||
export const splitAt = (index: number) => (x: string) =>
|
export const splitAt = (index: number) => (x: string) =>
|
||||||
[x.slice(0, index), x.slice(index)];
|
[x.slice(0, index), x.slice(index)];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a number prefixed with either a '-' or a '+'. The open volume field
|
||||||
|
* already comes with a '-' if negative so we only need to actually prefix if
|
||||||
|
* its a positive value
|
||||||
|
*/
|
||||||
|
export function volumePrefix(value: string): string {
|
||||||
|
if (value === '0' || value.startsWith('-')) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '+' + value;
|
||||||
|
}
|
||||||
|
|
||||||
export const getTimeFormat = once(
|
export const getTimeFormat = once(
|
||||||
() =>
|
() =>
|
||||||
new Intl.DateTimeFormat(getUserLocale(), {
|
new Intl.DateTimeFormat(getUserLocale(), {
|
||||||
|
@ -5,16 +5,16 @@ import * as React from 'react';
|
|||||||
import { PriceCell } from './price-cell';
|
import { PriceCell } from './price-cell';
|
||||||
|
|
||||||
describe('<PriceCell />', () => {
|
describe('<PriceCell />', () => {
|
||||||
it('Displayes formatted value', () => {
|
it('Displays formatted value', () => {
|
||||||
render(<PriceCell value={100} valueFormatted="100.00" />);
|
render(<PriceCell value={100} valueFormatted="100.00" />);
|
||||||
expect(screen.getByTestId('price')).toHaveTextContent('100.00');
|
expect(screen.getByTestId('price')).toHaveTextContent('100.00');
|
||||||
});
|
});
|
||||||
it('Displayes 0', () => {
|
it('Displays 0', () => {
|
||||||
render(<PriceCell value={0} valueFormatted="0.00" />);
|
render(<PriceCell value={0} valueFormatted="0.00" />);
|
||||||
expect(screen.getByTestId('price')).toHaveTextContent('0.00');
|
expect(screen.getByTestId('price')).toHaveTextContent('0.00');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Displayes - if value is not a number', () => {
|
it('Displays - if value is not a number', () => {
|
||||||
render(<PriceCell value={null} valueFormatted="" />);
|
render(<PriceCell value={null} valueFormatted="" />);
|
||||||
expect(screen.getByTestId('price')).toHaveTextContent('-');
|
expect(screen.getByTestId('price')).toHaveTextContent('-');
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
|
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
|
||||||
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],
|
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],
|
||||||
"@vegaprotocol/order-list": ["libs/order-list/src/index.ts"],
|
"@vegaprotocol/order-list": ["libs/order-list/src/index.ts"],
|
||||||
|
"@vegaprotocol/positions": ["libs/positions/src/index.ts"],
|
||||||
"@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"],
|
"@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"],
|
||||||
"@vegaprotocol/tailwindcss-config": [
|
"@vegaprotocol/tailwindcss-config": [
|
||||||
"libs/tailwindcss-config/src/index.js"
|
"libs/tailwindcss-config/src/index.js"
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"market-list": "libs/market-list",
|
"market-list": "libs/market-list",
|
||||||
"network-stats": "libs/network-stats",
|
"network-stats": "libs/network-stats",
|
||||||
"order-list": "libs/order-list",
|
"order-list": "libs/order-list",
|
||||||
|
"positions": "libs/positions",
|
||||||
"react-helpers": "libs/react-helpers",
|
"react-helpers": "libs/react-helpers",
|
||||||
"stats": "apps/stats",
|
"stats": "apps/stats",
|
||||||
"stats-e2e": "apps/stats-e2e",
|
"stats-e2e": "apps/stats-e2e",
|
||||||
|
Loading…
Reference in New Issue
Block a user