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