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'; | ||||
| 
 | ||||
| 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; | ||||
|   await act(async () => { | ||||
|     render( | ||||
|       <Web3Container> | ||||
|         <div>Child</div> | ||||
|       </Web3Container> | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   expect(screen.queryByText('Child')).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(); | ||||
| }); | ||||
| 
 | ||||
| test('Error message is shown', () => { | ||||
| test('Error message is shown', async () => { | ||||
|   const message = 'Opps! An error'; | ||||
|   mockHookValue = { ...defaultHookValue, error: new Error(message) }; | ||||
| 
 | ||||
|   await act(async () => { | ||||
|     render( | ||||
|       <Web3Container> | ||||
|         <div>Child</div> | ||||
|       </Web3Container> | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   expect(screen.queryByText('Child')).not.toBeInTheDocument(); | ||||
|   expect(screen.getByText(`Something went wrong: ${message}`)); | ||||
| }); | ||||
| 
 | ||||
| test('Chain id matches app configuration', () => { | ||||
| test('Chain id matches app configuration', async () => { | ||||
|   const expectedChainId = 4; | ||||
|   mockHookValue = { | ||||
|     ...defaultHookValue, | ||||
|     isActive: true, | ||||
|     chainId: expectedChainId, | ||||
|   }; | ||||
| 
 | ||||
|   await act(async () => { | ||||
|     render( | ||||
|       <Web3Container> | ||||
|         <div>Child</div> | ||||
|       </Web3Container> | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   expect(screen.queryByText('Child')).not.toBeInTheDocument(); | ||||
|   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 merge from 'lodash/merge'; | ||||
| import { useApolloClient } from '@apollo/client'; | ||||
| import { useRouter } from 'next/router'; | ||||
| import { AsyncRenderer } from '../../components/async-renderer'; | ||||
| import { MarketListTable, getRowNodeId } from '@vegaprotocol/market-list'; | ||||
| import { | ||||
|   Markets_markets, | ||||
|   Markets_markets_data, | ||||
|   MarketsDataProviderCallbackArg, | ||||
|   marketsDataProvider, | ||||
| } from '@vegaprotocol/graphql'; | ||||
| import { useDataProvider } from '@vegaprotocol/react-helpers'; | ||||
| 
 | ||||
| import type { AgGridReact } from 'ag-grid-react'; | ||||
| 
 | ||||
| const Markets = () => { | ||||
|   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 initialized = useRef<boolean>(false); | ||||
| 
 | ||||
|   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 = useCallback( | ||||
|     (delta: Markets_markets_data) => { | ||||
|       const update: Markets_markets[] = []; | ||||
|       const add: Markets_markets[] = []; | ||||
| 
 | ||||
|             // split into updates and adds
 | ||||
|             if (!gridRef.current || !delta) return; | ||||
| 
 | ||||
|       if (!gridRef.current) { | ||||
|         return false; | ||||
|       } | ||||
|       const rowNode = gridRef.current.api.getRowNode( | ||||
|         getRowNodeId(delta.market) | ||||
|       ); | ||||
| 
 | ||||
|       if (rowNode) { | ||||
|               const updatedData = produce( | ||||
|         const updatedData = produce<Markets_markets_data>( | ||||
|           rowNode.data.data, | ||||
|           (draft: Markets_markets_data) => merge(draft, delta) | ||||
|         ); | ||||
|         if (updatedData !== rowNode.data.data) { | ||||
|                 update.push({ ...rowNode.data, data: delta }); | ||||
|           update.push({ ...rowNode.data, data: updatedData }); | ||||
|         } | ||||
|       } | ||||
|       // @TODO - else add new market
 | ||||
| @ -61,18 +43,21 @@ const Markets = () => { | ||||
|           addIndex: 0, | ||||
|         }); | ||||
|       } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return true; | ||||
|     }, | ||||
|     [gridRef] | ||||
|   ); | ||||
|   }, [client, initialized]); | ||||
|   const { data, error, loading } = useDataProvider< | ||||
|     Markets_markets, | ||||
|     Markets_markets_data | ||||
|   >(marketsDataProvider, update); | ||||
| 
 | ||||
|   return ( | ||||
|     <AsyncRenderer loading={loading} error={error} data={markets}> | ||||
|     <AsyncRenderer loading={loading} error={error} data={data}> | ||||
|       {(data) => ( | ||||
|         <MarketListTable | ||||
|           ref={gridRef} | ||||
|           markets={data} | ||||
|           data={data} | ||||
|           onRowClicked={(id) => | ||||
|             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 { OrderListContainer } from '../..//components/order-list-container'; | ||||
| import { Splash } from '@vegaprotocol/ui-toolkit'; | ||||
| import { Positions } from './positions'; | ||||
| 
 | ||||
| const Chart = () => ( | ||||
|   <Splash> | ||||
| @ -17,11 +18,6 @@ const Orderbook = () => ( | ||||
|     <p>Orderbook</p> | ||||
|   </Splash> | ||||
| ); | ||||
| const Positions = () => ( | ||||
|   <Splash> | ||||
|     <p>Positions</p> | ||||
|   </Splash> | ||||
| ); | ||||
| const Collateral = () => ( | ||||
|   <Splash> | ||||
|     <p>Collateral</p> | ||||
|  | ||||
| @ -4,6 +4,6 @@ module.exports = { | ||||
|       name: 'vega', | ||||
|       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 './positions-data-provider'; | ||||
| export type { Subscribe } from './generic-data-provider'; | ||||
|  | ||||
| @ -1,8 +1,6 @@ | ||||
| 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 { makeDataProvider } from './generic-data-provider'; | ||||
| 
 | ||||
| import { | ||||
|   MarketDataSub, | ||||
| @ -57,137 +55,21 @@ const MARKET_DATA_SUB = gql` | ||||
|   } | ||||
| `;
 | ||||
| 
 | ||||
| export interface MarketsDataProviderCallbackArg { | ||||
|   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 update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => { | ||||
|   const index = draft.findIndex((m) => m.id === delta.market.id); | ||||
|   if (index !== -1) { | ||||
|     draft[index].data = delta; | ||||
|   } | ||||
|   // @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 () => { | ||||
|   if (subscription) { | ||||
|     return; | ||||
|   } | ||||
|   loading = true; | ||||
|   error = undefined; | ||||
|   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); | ||||
| }; | ||||
| export const marketsDataProvider = makeDataProvider< | ||||
|   Markets, | ||||
|   Markets_markets, | ||||
|   MarketDataSub, | ||||
|   MarketDataSub_marketData | ||||
| >(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta); | ||||
|  | ||||
							
								
								
									
										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__/Guess'; | ||||
| export * from './__generated__/Market'; | ||||
| export * from './__generated__/MarketDataFields'; | ||||
| export * from './__generated__/MarketDataSub'; | ||||
| export * from './__generated__/Markets'; | ||||
| export * from './__generated__/MarketsQuery'; | ||||
| export * from './__generated__/MarketDataSub'; | ||||
| export * from './__generated__/MarketDataFields'; | ||||
| export * from './__generated__/NetworkParametersQuery'; | ||||
| export * from './__generated__/NodesQuery'; | ||||
| export * from './__generated__/OrderEvent'; | ||||
| @ -13,6 +13,9 @@ export * from './__generated__/OrderFields'; | ||||
| export * from './__generated__/Orders'; | ||||
| export * from './__generated__/OrderSub'; | ||||
| export * from './__generated__/PartyAssetsQuery'; | ||||
| export * from './__generated__/PositionDetails'; | ||||
| export * from './__generated__/Positions'; | ||||
| export * from './__generated__/PositionSubscribe'; | ||||
| export * from './__generated__/ProposalsQuery'; | ||||
| 
 | ||||
| export * from './data-providers'; | ||||
|  | ||||
| @ -7,19 +7,19 @@ import { AgGridColumn } from 'ag-grid-react'; | ||||
| import type { AgGridReact } from 'ag-grid-react'; | ||||
| 
 | ||||
| interface MarketListTableProps { | ||||
|   markets: Markets_markets[] | null; | ||||
|   data: Markets_markets[] | null; | ||||
|   onRowClicked: (marketId: string) => void; | ||||
| } | ||||
| 
 | ||||
| export const getRowNodeId = (data: { id: string }) => data.id; | ||||
| 
 | ||||
| export const MarketListTable = forwardRef<AgGridReact, MarketListTableProps>( | ||||
|   ({ markets, onRowClicked }, ref) => { | ||||
|   ({ data, onRowClicked }, ref) => { | ||||
|     return ( | ||||
|       <AgGrid | ||||
|         style={{ width: '100%', height: '100%' }} | ||||
|         overlayNoRowsTemplate="No markets" | ||||
|         rowData={markets} | ||||
|         rowData={data} | ||||
|         getRowNodeId={getRowNodeId} | ||||
|         ref={ref} | ||||
|         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/storage'; | ||||
| 
 | ||||
| export * from './hooks/use-apply-grid-transaction'; | ||||
| export * from './hooks/use-theme-switcher'; | ||||
| export * from './hooks'; | ||||
|  | ||||
| @ -7,6 +7,19 @@ const getUserLocale = () => 'default'; | ||||
| export const splitAt = (index: number) => (x: string) => | ||||
|   [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( | ||||
|   () => | ||||
|     new Intl.DateTimeFormat(getUserLocale(), { | ||||
|  | ||||
| @ -5,16 +5,16 @@ import * as React from 'react'; | ||||
| import { PriceCell } from './price-cell'; | ||||
| 
 | ||||
| describe('<PriceCell />', () => { | ||||
|   it('Displayes formatted value', () => { | ||||
|   it('Displays formatted value', () => { | ||||
|     render(<PriceCell value={100} valueFormatted="100.00" />); | ||||
|     expect(screen.getByTestId('price')).toHaveTextContent('100.00'); | ||||
|   }); | ||||
|   it('Displayes 0', () => { | ||||
|   it('Displays 0', () => { | ||||
|     render(<PriceCell value={0} valueFormatted="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="" />); | ||||
|     expect(screen.getByTestId('price')).toHaveTextContent('-'); | ||||
|   }); | ||||
|  | ||||
| @ -20,6 +20,7 @@ | ||||
|       "@vegaprotocol/market-list": ["libs/market-list/src/index.ts"], | ||||
|       "@vegaprotocol/network-stats": ["libs/network-stats/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/tailwindcss-config": [ | ||||
|         "libs/tailwindcss-config/src/index.js" | ||||
|  | ||||
| @ -8,6 +8,7 @@ | ||||
|     "market-list": "libs/market-list", | ||||
|     "network-stats": "libs/network-stats", | ||||
|     "order-list": "libs/order-list", | ||||
|     "positions": "libs/positions", | ||||
|     "react-helpers": "libs/react-helpers", | ||||
|     "stats": "apps/stats", | ||||
|     "stats-e2e": "apps/stats-e2e", | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user