diff --git a/apps/console-lite/src/app/components/simple-market-list/data-provider.ts b/apps/console-lite/src/app/components/simple-market-list/data-provider.ts index d604f0986..67a5d294e 100644 --- a/apps/console-lite/src/app/components/simple-market-list/data-provider.ts +++ b/apps/console-lite/src/app/components/simple-market-list/data-provider.ts @@ -94,6 +94,12 @@ export const dataProvider = makeDataProvider< SimpleMarkets_markets[], SimpleMarketDataSub, SimpleMarketDataSub_marketData ->(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta); +>({ + query: MARKETS_QUERY, + subscriptionQuery: MARKET_DATA_SUB, + update, + getData, + getDelta, +}); export default dataProvider; diff --git a/apps/trading-e2e/src/support/mocks/generate-accounts.ts b/apps/trading-e2e/src/support/mocks/generate-accounts.ts index 7c0c8ef72..40521d572 100644 --- a/apps/trading-e2e/src/support/mocks/generate-accounts.ts +++ b/apps/trading-e2e/src/support/mocks/generate-accounts.ts @@ -23,6 +23,50 @@ export const generateAccounts = ( decimals: 5, }, }, + { + __typename: 'Account', + type: AccountType.General, + balance: '100000000', + market: null, + asset: { + __typename: 'Asset', + id: 'asset-id-2', + symbol: 'tDAI', + decimals: 5, + }, + }, + { + __typename: 'Account', + type: AccountType.Margin, + balance: '1000', + market: { + __typename: 'Market', + name: '', + id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', + }, + asset: { + __typename: 'Asset', + id: 'asset-id', + symbol: 'tEURO', + decimals: 5, + }, + }, + { + __typename: 'Account', + type: AccountType.Margin, + balance: '1000', + market: { + __typename: 'Market', + name: '', + id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', + }, + asset: { + __typename: 'Asset', + id: 'asset-id-2', + symbol: 'tDAI', + decimals: 5, + }, + }, ], }, }; diff --git a/apps/trading-e2e/src/support/mocks/generate-market-depth.ts b/apps/trading-e2e/src/support/mocks/generate-market-depth.ts new file mode 100644 index 000000000..3ebff5bae --- /dev/null +++ b/apps/trading-e2e/src/support/mocks/generate-market-depth.ts @@ -0,0 +1,40 @@ +import merge from 'lodash/merge'; +import { MarketTradingMode } from '@vegaprotocol/types'; +import type { PartialDeep } from 'type-fest'; +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries +import type { MarketDepth } from '../../../../../libs/market-depth/src/lib/__generated__/MarketDepth'; + +export const generateMarketDepth = ( + override?: PartialDeep +): MarketDepth => { + const defaultResult: MarketDepth = { + market: { + id: 'market-0', + decimalPlaces: 5, + positionDecimalPlaces: 0, + data: { + marketTradingMode: MarketTradingMode.Continuous, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + market: { + id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', + __typename: 'Market', + }, + __typename: 'MarketData', + }, + depth: { + __typename: 'MarketDepth', + buy: [], + sell: [], + lastTrade: null, + sequenceNumber: '', + }, + __typename: 'Market', + }, + }; + + return merge(defaultResult, override); +}; diff --git a/apps/trading-e2e/src/support/mocks/generate-market.ts b/apps/trading-e2e/src/support/mocks/generate-market.ts index 322bd3be4..eae019e4e 100644 --- a/apps/trading-e2e/src/support/mocks/generate-market.ts +++ b/apps/trading-e2e/src/support/mocks/generate-market.ts @@ -18,6 +18,11 @@ export const generateMarket = (override?: PartialDeep): Market => { decimalPlaces: 5, positionDecimalPlaces: 0, data: { + auctionEnd: '', + auctionStart: '', + indicativePrice: '', + suppliedStake: '', + targetStake: '', market: { id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', __typename: 'Market', diff --git a/apps/trading-e2e/src/support/mocks/generate-markets.ts b/apps/trading-e2e/src/support/mocks/generate-markets.ts index 8763658a6..ae255e92d 100644 --- a/apps/trading-e2e/src/support/mocks/generate-markets.ts +++ b/apps/trading-e2e/src/support/mocks/generate-markets.ts @@ -5,14 +5,34 @@ import { MarketTradingMode, } from '@vegaprotocol/types'; import type { PartialDeep } from 'type-fest'; -import type { Markets, Markets_markets } from '@vegaprotocol/market-list'; +import type { MarketList, MarketList_markets } from '@vegaprotocol/market-list'; -export const generateMarkets = (override?: PartialDeep): Markets => { - const markets: Markets_markets[] = [ +export const generateMarkets = ( + override?: PartialDeep +): MarketList => { + const markets: MarketList_markets[] = [ { id: 'market-0', name: 'ACTIVE MARKET', decimalPlaces: 5, + positionDecimalPlaces: 0, + tradingMode: MarketTradingMode.Continuous, + state: MarketState.Active, + marketTimestamps: { + __typename: 'MarketTimestamps', + close: '', + open: '', + }, + candles: [], + fees: { + __typename: 'Fees', + factors: { + __typename: 'FeeFactors', + makerFee: '', + infrastructureFee: '', + liquidityFee: '', + }, + }, data: { market: { id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35', @@ -20,6 +40,7 @@ export const generateMarkets = (override?: PartialDeep): Markets => { tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, __typename: 'Market', }, + indicativeVolume: '0', bestBidPrice: '0', bestOfferPrice: '0', markPrice: '4612690058', @@ -29,6 +50,11 @@ export const generateMarkets = (override?: PartialDeep): Markets => { tradableInstrument: { instrument: { code: 'BTCUSD.MF21', + name: '', + metadata: { + __typename: 'InstrumentMetadata', + tags: [], + }, product: { settlementAsset: { symbol: 'tDAI', @@ -46,6 +72,24 @@ export const generateMarkets = (override?: PartialDeep): Markets => { id: 'market-1', name: 'SUSPENDED MARKET', decimalPlaces: 2, + positionDecimalPlaces: 0, + tradingMode: MarketTradingMode.Continuous, + state: MarketState.Active, + marketTimestamps: { + __typename: 'MarketTimestamps', + close: '', + open: '', + }, + candles: [], + fees: { + __typename: 'Fees', + factors: { + __typename: 'FeeFactors', + makerFee: '', + infrastructureFee: '', + liquidityFee: '', + }, + }, data: { market: { id: '34d95e10faa00c21d19d382d6d7e6fc9722a96985369f0caec041b0f44b775ed', @@ -55,6 +99,7 @@ export const generateMarkets = (override?: PartialDeep): Markets => { }, bestBidPrice: '0', bestOfferPrice: '0', + indicativeVolume: '0', markPrice: '8441', trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, __typename: 'MarketData', @@ -62,6 +107,11 @@ export const generateMarkets = (override?: PartialDeep): Markets => { tradableInstrument: { instrument: { code: 'SOLUSD', + name: '', + metadata: { + __typename: 'InstrumentMetadata', + tags: [], + }, product: { settlementAsset: { symbol: 'XYZalpha', diff --git a/apps/trading-e2e/src/support/mocks/generate-positions-metrics.ts b/apps/trading-e2e/src/support/mocks/generate-positions-metrics.ts deleted file mode 100644 index e8fd4c317..000000000 --- a/apps/trading-e2e/src/support/mocks/generate-positions-metrics.ts +++ /dev/null @@ -1,163 +0,0 @@ -import merge from 'lodash/merge'; -import type { PartialDeep } from 'type-fest'; -import type { - PositionsMetrics, - PositionsMetrics_party_positionsConnection_edges_node, -} from '@vegaprotocol/positions'; -import { MarketTradingMode, AccountType } from '@vegaprotocol/types'; - -export const generatePositionsMetrics = ( - override?: PartialDeep -): PositionsMetrics => { - const nodes: PositionsMetrics_party_positionsConnection_edges_node[] = [ - { - realisedPNL: '0', - openVolume: '6', - unrealisedPNL: '895000', - averageEntryPrice: '1129935', - updatedAt: '2022-07-28T15:09:34.441143Z', - market: { - id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', - name: 'UNIDAI Monthly (30 Jun 2022)', - tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, - data: { - markPrice: '17588787', - __typename: 'MarketData', - }, - decimalPlaces: 5, - positionDecimalPlaces: 0, - tradableInstrument: { - instrument: { - name: 'UNIDAI Monthly (30 Jun 2022)', - __typename: 'Instrument', - }, - __typename: 'TradableInstrument', - }, - __typename: 'Market', - }, - __typename: 'Position', - }, - { - realisedPNL: '0', - openVolume: '1', - unrealisedPNL: '-22519', - averageEntryPrice: '84400088', - updatedAt: '2022-07-28T14:53:54.725477Z', - market: { - id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', - name: 'Tesla Quarterly (30 Jun 2022)', - tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, - data: { - markPrice: '84377569', - __typename: 'MarketData', - }, - decimalPlaces: 5, - positionDecimalPlaces: 0, - tradableInstrument: { - instrument: { - name: 'Tesla Quarterly (30 Jun 2022)', - __typename: 'Instrument', - }, - __typename: 'TradableInstrument', - }, - __typename: 'Market', - }, - __typename: 'Position', - }, - ]; - - const defaultResult: PositionsMetrics = { - party: { - id: Cypress.env('VEGA_PUBLIC_KEY'), - accounts: [ - { - __typename: 'Account', - type: AccountType.ACCOUNT_TYPE_GENERAL, - balance: '100000000', - market: null, - asset: { - __typename: 'Asset', - id: 'asset-id-2', - decimals: 5, - }, - }, - { - __typename: 'Account', - type: AccountType.ACCOUNT_TYPE_MARGIN, - balance: '1000', - market: { - __typename: 'Market', - id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', - }, - asset: { - __typename: 'Asset', - id: 'asset-id', - decimals: 5, - }, - }, - { - __typename: 'Account', - type: AccountType.ACCOUNT_TYPE_MARGIN, - balance: '1000', - market: { - __typename: 'Market', - id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', - }, - asset: { - __typename: 'Asset', - id: 'asset-id-2', - decimals: 5, - }, - }, - ], - marginsConnection: { - __typename: 'MarginConnection', - edges: [ - { - __typename: 'MarginEdge', - node: { - __typename: 'MarginLevels', - maintenanceLevel: '0', - searchLevel: '0', - initialLevel: '0', - collateralReleaseLevel: '0', - market: { - __typename: 'Market', - id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', - }, - asset: { - __typename: 'Asset', - symbol: 'tDAI', - }, - }, - }, - { - __typename: 'MarginEdge', - node: { - __typename: 'MarginLevels', - maintenanceLevel: '0', - searchLevel: '0', - initialLevel: '0', - collateralReleaseLevel: '0', - market: { - __typename: 'Market', - id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376 ', - }, - asset: { - __typename: 'Asset', - symbol: 'tEURO', - }, - }, - }, - ], - }, - positionsConnection: { - __typename: 'PositionConnection', - edges: nodes.map((node) => ({ __typename: 'PositionEdge', node })), - }, - __typename: 'Party', - }, - }; - - return merge(defaultResult, override); -}; diff --git a/apps/trading-e2e/src/support/mocks/generate-positions.ts b/apps/trading-e2e/src/support/mocks/generate-positions.ts index 8ea19177e..7e2d8c3e2 100644 --- a/apps/trading-e2e/src/support/mocks/generate-positions.ts +++ b/apps/trading-e2e/src/support/mocks/generate-positions.ts @@ -2,76 +2,103 @@ import merge from 'lodash/merge'; import type { PartialDeep } from 'type-fest'; import type { Positions, - Positions_party_positions, + Positions_party_positionsConnection_edges_node, } from '@vegaprotocol/positions'; import { MarketTradingMode } from '@vegaprotocol/types'; export const generatePositions = ( override?: PartialDeep ): Positions => { - const positions: Positions_party_positions[] = [ + const nodes: Positions_party_positionsConnection_edges_node[] = [ { + __typename: 'Position', realisedPNL: '0', openVolume: '6', unrealisedPNL: '895000', averageEntryPrice: '1129935', + updatedAt: '2022-07-28T15:09:34.441143Z', + marginsConnection: { + __typename: 'MarginConnection', + edges: [ + { + __typename: 'MarginEdge', + node: { + __typename: 'MarginLevels', + maintenanceLevel: '0', + searchLevel: '0', + initialLevel: '0', + collateralReleaseLevel: '0', + market: { + __typename: 'Market', + id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', + }, + asset: { + __typename: 'Asset', + symbol: 'tDAI', + }, + }, + }, + ], + }, market: { id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', name: 'UNIDAI Monthly (30 Jun 2022)', + marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, data: { markPrice: '17588787', - marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, __typename: 'MarketData', - market: { __typename: 'Market', id: '123' }, + market: { + __typename: 'Market', + id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', + }, }, decimalPlaces: 5, positionDecimalPlaces: 0, 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', }, { realisedPNL: '0', openVolume: '1', unrealisedPNL: '-22519', averageEntryPrice: '84400088', + updatedAt: '2022-07-28T14:53:54.725477Z', + marginsConnection: { + __typename: 'MarginConnection', + edges: [ + { + __typename: 'MarginEdge', + node: { + __typename: 'MarginLevels', + maintenanceLevel: '0', + searchLevel: '0', + initialLevel: '0', + collateralReleaseLevel: '0', + market: { + __typename: 'Market', + id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', + }, + asset: { + __typename: 'Asset', + symbol: 'tEURO', + }, + }, + }, + ], + }, market: { id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', name: 'Tesla Quarterly (30 Jun 2022)', + tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, data: { markPrice: '84377569', - marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, __typename: 'MarketData', market: { __typename: 'Market', @@ -82,32 +109,7 @@ export const generatePositions = ( positionDecimalPlaces: 0, tradableInstrument: { instrument: { - id: '', name: 'Tesla Quarterly (30 Jun 2022)', - metadata: { - tags: [ - 'formerly:5A86B190C384997F', - 'quote:EURO', - 'ticker:TSLA', - 'class:equities/single-stock-futures', - 'sector:tech', - 'listing_venue:NASDAQ', - 'country:US', - ], - __typename: 'InstrumentMetadata', - }, - code: 'TSLA.QM21', - product: { - settlementAsset: { - id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4', - symbol: 'tEURO', - name: 'tEURO TEST', - decimals: 5, - __typename: 'Asset', - }, - quoteName: 'EURO', - __typename: 'Future', - }, __typename: 'Instrument', }, __typename: 'TradableInstrument', @@ -118,11 +120,19 @@ export const generatePositions = ( }, ]; - const defaultResult = { + const defaultResult: Positions = { party: { - id: Cypress.env('VEGA_PUBLIC_KEY'), - positions, __typename: 'Party', + id: Cypress.env('VEGA_PUBLIC_KEY'), + positionsConnection: { + __typename: 'PositionConnection', + edges: nodes.map((node) => { + return { + __typename: 'PositionEdge', + node, + }; + }), + }, }, }; diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts index b9eaa9944..d715d9b25 100644 --- a/apps/trading-e2e/src/support/trading.ts +++ b/apps/trading-e2e/src/support/trading.ts @@ -6,10 +6,10 @@ import { generateCandles } from './mocks/generate-candles'; import { generateChart } from './mocks/generate-chart'; import { generateDealTicketQuery } from './mocks/generate-deal-ticket-query'; import { generateMarket } from './mocks/generate-market'; +import { generateMarketDepth } from './mocks/generate-market-depth'; import { generateMarketInfoQuery } from './mocks/generate-market-info-query'; import { generateOrders } from './mocks/generate-orders'; import { generatePositions } from './mocks/generate-positions'; -import { generatePositionsMetrics } from './mocks/generate-positions-metrics'; import { generateTrades } from './mocks/generate-trades'; export const mockTradingPage = ( @@ -26,10 +26,10 @@ export const mockTradingPage = ( }, }) ); + aliasQuery(req, 'MarketDepth', generateMarketDepth()); aliasQuery(req, 'Orders', generateOrders()); aliasQuery(req, 'Accounts', generateAccounts()); aliasQuery(req, 'Positions', generatePositions()); - aliasQuery(req, 'PositionsMetrics', generatePositionsMetrics()); aliasQuery( req, 'DealTicketQuery', diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index d2a09da05..6e041b0be 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -17,7 +17,7 @@ import { useGlobalStore } from '../stores'; import { AssetDetailsDialog, useAssetDetailsDialogStore, -} from '@vegaprotocol/market-list'; +} from '@vegaprotocol/assets'; function AppBody({ Component, pageProps }: AppProps) { const store = useGlobalStore(); diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index 726cb6f2c..a79394cfb 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -31,7 +31,7 @@ import { useGlobalStore } from '../../stores'; import { AccountsContainer } from '@vegaprotocol/accounts'; import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; -import { useAssetDetailsDialogStore } from '@vegaprotocol/market-list'; +import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useEnvironment } from '@vegaprotocol/environment'; import { Tab, diff --git a/libs/accounts/src/lib/accounts-data-provider.ts b/libs/accounts/src/lib/accounts-data-provider.ts index bd2a9f5ef..8ddd8329f 100644 --- a/libs/accounts/src/lib/accounts-data-provider.ts +++ b/libs/accounts/src/lib/accounts-data-provider.ts @@ -78,4 +78,10 @@ export const accountsDataProvider = makeDataProvider< Accounts_party_accounts[], AccountSubscribe, AccountSubscribe_accounts ->(ACCOUNTS_QUERY, ACCOUNTS_SUB, update, getData, getDelta); +>({ + query: ACCOUNTS_QUERY, + subscriptionQuery: ACCOUNTS_SUB, + update, + getData, + getDelta, +}); diff --git a/libs/accounts/src/lib/accounts-table.tsx b/libs/accounts/src/lib/accounts-table.tsx index 9f90015ba..52ac15693 100644 --- a/libs/accounts/src/lib/accounts-table.tsx +++ b/libs/accounts/src/lib/accounts-table.tsx @@ -16,7 +16,7 @@ import { AgGridColumn } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react'; import type { Accounts_party_accounts } from './__generated__/Accounts'; import { getId } from './accounts-data-provider'; -import { useAssetDetailsDialogStore } from '@vegaprotocol/market-list'; +import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import type { AccountType } from '@vegaprotocol/types'; import { AccountTypeMapping } from '@vegaprotocol/types'; diff --git a/libs/assets/.babelrc b/libs/assets/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/assets/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/assets/.eslintrc.json b/libs/assets/.eslintrc.json new file mode 100644 index 000000000..db820c5d0 --- /dev/null +++ b/libs/assets/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "__generated__"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/assets/README.md b/libs/assets/README.md new file mode 100644 index 000000000..2f0edb4ce --- /dev/null +++ b/libs/assets/README.md @@ -0,0 +1,7 @@ +# assets + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test assets` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/assets/jest.config.js b/libs/assets/jest.config.js new file mode 100644 index 000000000..90ad6412f --- /dev/null +++ b/libs/assets/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + displayName: 'assets', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/assets', + setupFilesAfterEnv: ['./src/setup-tests.ts'], +}; diff --git a/libs/assets/package.json b/libs/assets/package.json new file mode 100644 index 000000000..f0c1129d5 --- /dev/null +++ b/libs/assets/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/assets", + "version": "0.0.1" +} diff --git a/libs/assets/project.json b/libs/assets/project.json new file mode 100644 index 000000000..0086aa88e --- /dev/null +++ b/libs/assets/project.json @@ -0,0 +1,43 @@ +{ + "root": "libs/assets", + "sourceRoot": "libs/assets/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/assets", + "tsConfig": "libs/assets/tsconfig.lib.json", + "project": "libs/assets/package.json", + "entryFile": "libs/assets/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/assets/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/assets/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/assets"], + "options": { + "jestConfig": "libs/assets/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/assets/src/index.ts b/libs/assets/src/index.ts new file mode 100644 index 000000000..f41a696fd --- /dev/null +++ b/libs/assets/src/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/libs/market-list/src/lib/components/asset-details-dialog/__generated__/AssetsConnection.ts b/libs/assets/src/lib/__generated__/AssetsConnection.ts similarity index 100% rename from libs/market-list/src/lib/components/asset-details-dialog/__generated__/AssetsConnection.ts rename to libs/assets/src/lib/__generated__/AssetsConnection.ts diff --git a/libs/market-list/src/lib/components/asset-details-dialog/asset-details-dialog.spec.tsx b/libs/assets/src/lib/asset-details-dialog.spec.tsx similarity index 100% rename from libs/market-list/src/lib/components/asset-details-dialog/asset-details-dialog.spec.tsx rename to libs/assets/src/lib/asset-details-dialog.spec.tsx diff --git a/libs/market-list/src/lib/components/asset-details-dialog/asset-details-dialog.tsx b/libs/assets/src/lib/asset-details-dialog.tsx similarity index 100% rename from libs/market-list/src/lib/components/asset-details-dialog/asset-details-dialog.tsx rename to libs/assets/src/lib/asset-details-dialog.tsx diff --git a/libs/market-list/src/lib/components/asset-details-dialog/index.ts b/libs/assets/src/lib/index.ts similarity index 100% rename from libs/market-list/src/lib/components/asset-details-dialog/index.ts rename to libs/assets/src/lib/index.ts diff --git a/libs/assets/src/setup-tests.ts b/libs/assets/src/setup-tests.ts new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/libs/assets/src/setup-tests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/libs/assets/tsconfig.json b/libs/assets/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/assets/tsconfig.json @@ -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" + } + ] +} diff --git a/libs/assets/tsconfig.lib.json b/libs/assets/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/assets/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/assets/tsconfig.spec.json b/libs/assets/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/assets/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/deposits/src/lib/deposit-form.tsx b/libs/deposits/src/lib/deposit-form.tsx index 47738f0be..6167a9a4e 100644 --- a/libs/deposits/src/lib/deposit-form.tsx +++ b/libs/deposits/src/lib/deposit-form.tsx @@ -25,7 +25,7 @@ import type { ReactNode } from 'react'; import { useMemo } from 'react'; import { Controller, useForm, useWatch } from 'react-hook-form'; import { DepositLimits } from './deposit-limits'; -import { useAssetDetailsDialogStore } from '@vegaprotocol/market-list'; +import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; interface FormFields { asset: string; diff --git a/libs/fills/src/lib/fills-data-provider.ts b/libs/fills/src/lib/fills-data-provider.ts index 8219e36dd..fe48713aa 100644 --- a/libs/fills/src/lib/fills-data-provider.ts +++ b/libs/fills/src/lib/fills-data-provider.ts @@ -129,15 +129,15 @@ const getPageInfo = (responseData: Fills): PageInfo | null => const getDelta = (subscriptionData: FillsSub) => subscriptionData.trades || []; -export const fillsDataProvider = makeDataProvider( - FILLS_QUERY, - FILLS_SUB, +export const fillsDataProvider = makeDataProvider({ + query: FILLS_QUERY, + subscriptionQuery: FILLS_SUB, update, getData, getDelta, - { + pagination: { getPageInfo, append, first: 100, - } -); + }, +}); diff --git a/libs/market-depth/src/lib/market-depth-data-provider.ts b/libs/market-depth/src/lib/market-depth-data-provider.ts index 737d91ae3..251acfbbd 100644 --- a/libs/market-depth/src/lib/market-depth-data-provider.ts +++ b/libs/market-depth/src/lib/market-depth-data-provider.ts @@ -102,8 +102,11 @@ const update: Update< } */ sequenceNumbers[delta.market.id] = sequenceNumber; - const updatedData = { ...data }; - data.data = delta.market.data; + const updatedData = { + ...data, + data: delta.market.data, + depth: { ...data.depth }, + }; if (delta.buy) { updatedData.depth.buy = updateLevels(data.depth.buy ?? [], delta.buy); } @@ -124,12 +127,12 @@ const getData = (responseData: MarketDepth) => { const getDelta = (subscriptionData: MarketDepthSubscription) => subscriptionData.marketDepthUpdate; -export const marketDepthDataProvider = makeDataProvider( - MARKET_DEPTH_QUERY, - MARKET_DEPTH_SUBSCRIPTION_QUERY, +export const marketDepthDataProvider = makeDataProvider({ + query: MARKET_DEPTH_QUERY, + subscriptionQuery: MARKET_DEPTH_SUBSCRIPTION_QUERY, update, getData, - getDelta -); + getDelta, +}); export default marketDepthDataProvider; diff --git a/libs/market-list/src/lib/components/index.ts b/libs/market-list/src/lib/components/index.ts index a3a4ada6f..00dfebb45 100644 --- a/libs/market-list/src/lib/components/index.ts +++ b/libs/market-list/src/lib/components/index.ts @@ -1,5 +1,4 @@ export * from './markets-container'; -export * from './asset-details-dialog'; export * from './select-market-columns'; export * from './select-market-table'; export * from './select-market'; diff --git a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx index 4aa2d12eb..b5b03866a 100644 --- a/libs/market-list/src/lib/components/markets-container/market-list-table.tsx +++ b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx @@ -21,7 +21,7 @@ import type { MarketList_markets, MarketList_markets_data, } from '../../__generated__'; -import { useAssetDetailsDialogStore } from '../asset-details-dialog'; +import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; type Props = AgGridReactProps | AgReactUiProps; diff --git a/libs/market-list/src/lib/components/select-market.tsx b/libs/market-list/src/lib/components/select-market.tsx index c073b2537..85034ebc8 100644 --- a/libs/market-list/src/lib/components/select-market.tsx +++ b/libs/market-list/src/lib/components/select-market.tsx @@ -20,7 +20,7 @@ import { columns } from './select-market-columns'; import type { MarketList } from '../__generated__'; import { useVegaWallet } from '@vegaprotocol/wallet'; import type { Positions } from '@vegaprotocol/positions'; -import { POSITION_QUERY } from '@vegaprotocol/positions'; +import { POSITIONS_QUERY } from '@vegaprotocol/positions'; import { mapDataToMarketList } from '../utils/market-utils'; import { SelectMarketTableHeader, @@ -120,7 +120,7 @@ export const SelectMarketPopover = ({ const [open, setOpen] = useState(false); const { data } = useMarkets(); const variables = useMemo(() => ({ partyId: keypair?.pub }), [keypair?.pub]); - const { data: marketDataPositions } = useQuery(POSITION_QUERY, { + const { data: marketDataPositions } = useQuery(POSITIONS_QUERY, { variables, skip: !keypair?.pub, }); @@ -130,14 +130,15 @@ export const SelectMarketPopover = ({ markets: data?.markets ?.filter((market) => - marketDataPositions?.party?.positions?.find( - (position) => position.market.id === market.id + marketDataPositions?.party?.positionsConnection.edges?.find( + (edge) => edge.node.market.id === market.id ) ) .map((market) => { - const position = marketDataPositions?.party?.positions?.find( - (position) => position.market.id === market.id - ); + const position = + marketDataPositions?.party?.positionsConnection.edges?.find( + (edge) => edge.node.market.id === market.id + )?.node; return { ...market, openVolume: diff --git a/libs/market-list/src/lib/markets-data-provider.ts b/libs/market-list/src/lib/markets-data-provider.ts index a24c8e353..ffa51c93e 100644 --- a/libs/market-list/src/lib/markets-data-provider.ts +++ b/libs/market-list/src/lib/markets-data-provider.ts @@ -114,4 +114,10 @@ export const marketsDataProvider = makeDataProvider< MarketList_markets[], MarketDataSub, MarketDataSub_marketData ->(MARKET_LIST_QUERY, MARKET_DATA_SUB, update, getData, getDelta); +>({ + query: MARKET_LIST_QUERY, + subscriptionQuery: MARKET_DATA_SUB, + update, + getData, + getDelta, +}); diff --git a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts index 833e17180..fe4fbaebe 100644 --- a/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts +++ b/libs/orders/src/lib/components/order-data-provider/order-data-provider.ts @@ -115,15 +115,15 @@ const getDelta = (subscriptionData: OrderSub) => subscriptionData.orders || []; const getPageInfo = (responseData: Orders): PageInfo | null => responseData.party?.ordersConnection.pageInfo || null; -export const ordersDataProvider = makeDataProvider( - ORDERS_QUERY, - ORDERS_SUB, +export const ordersDataProvider = makeDataProvider({ + query: ORDERS_QUERY, + subscriptionQuery: ORDERS_SUB, update, getData, getDelta, - { + pagination: { getPageInfo, append, first: 100, - } -); + }, +}); diff --git a/libs/positions/src/index.ts b/libs/positions/src/index.ts index 555a1ac6b..8fb366719 100644 --- a/libs/positions/src/index.ts +++ b/libs/positions/src/index.ts @@ -1,6 +1,4 @@ export * from './lib/positions-table'; export * from './lib/positions-container'; -export { positionsMetricsDataProvider } from './lib/positions-metrics-data-provider'; -export * from './lib/positions-data-provider'; -export * from './lib/__generated__/PositionsMetrics'; +export * from './lib/positions-data-providers'; export * from './lib/__generated__/Positions'; diff --git a/libs/positions/src/lib/__generated__/PositionDetails.ts b/libs/positions/src/lib/__generated__/PositionDetails.ts deleted file mode 100644 index ea481e297..000000000 --- a/libs/positions/src/lib/__generated__/PositionDetails.ts +++ /dev/null @@ -1,122 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -import { MarketTradingMode } from "@vegaprotocol/types"; - -// ==================================================== -// 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 (an unsigned integer) - */ - 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 { - __typename: "Instrument"; - /** - * Full and fairly descriptive name for the instrument - */ - name: string; - /** - * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) - */ - code: string; -} - -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; - /** - * positionDecimalPlaces indicates the number of decimal places that an integer must be shifted in order to get a correct size (uint64). - * i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes. - * 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market. - * This sets how big the smallest order / position on the market can be. - */ - positionDecimalPlaces: 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; -} diff --git a/libs/positions/src/lib/__generated__/PositionsMetricsSubscription.ts b/libs/positions/src/lib/__generated__/PositionFields.ts similarity index 53% rename from libs/positions/src/lib/__generated__/PositionsMetricsSubscription.ts rename to libs/positions/src/lib/__generated__/PositionFields.ts index 13bb2c5f7..a5f25b523 100644 --- a/libs/positions/src/lib/__generated__/PositionsMetricsSubscription.ts +++ b/libs/positions/src/lib/__generated__/PositionFields.ts @@ -6,10 +6,68 @@ import { MarketTradingMode } from "@vegaprotocol/types"; // ==================================================== -// GraphQL subscription operation: PositionsMetricsSubscription +// GraphQL fragment: PositionFields // ==================================================== -export interface PositionsMetricsSubscription_positions_market_tradableInstrument_instrument { +export interface PositionFields_marginsConnection_edges_node_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface PositionFields_marginsConnection_edges_node_asset { + __typename: "Asset"; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; +} + +export interface PositionFields_marginsConnection_edges_node { + __typename: "MarginLevels"; + /** + * market in which the margin is required for this party + */ + market: PositionFields_marginsConnection_edges_node_market; + /** + * minimal margin for the position to be maintained in the network (unsigned integer) + */ + maintenanceLevel: string; + /** + * if the margin is between maintenance and search, the network will initiate a collateral search (unsigned integer) + */ + searchLevel: string; + /** + * this is the minimum margin required for a party to place a new order on the network (unsigned integer) + */ + initialLevel: string; + /** + * If the margin of the party is greater than this level, then collateral will be released from the margin account into + * the general account of the party for the given asset. + */ + collateralReleaseLevel: string; + /** + * asset for the current margins + */ + asset: PositionFields_marginsConnection_edges_node_asset; +} + +export interface PositionFields_marginsConnection_edges { + __typename: "MarginEdge"; + node: PositionFields_marginsConnection_edges_node; +} + +export interface PositionFields_marginsConnection { + __typename: "MarginConnection"; + /** + * The margin levels in this connection + */ + edges: PositionFields_marginsConnection_edges[] | null; +} + +export interface PositionFields_market_tradableInstrument_instrument { __typename: "Instrument"; /** * Full and fairly descriptive name for the instrument @@ -17,23 +75,35 @@ export interface PositionsMetricsSubscription_positions_market_tradableInstrumen name: string; } -export interface PositionsMetricsSubscription_positions_market_tradableInstrument { +export interface PositionFields_market_tradableInstrument { __typename: "TradableInstrument"; /** * An instance of, or reference to, a fully specified instrument. */ - instrument: PositionsMetricsSubscription_positions_market_tradableInstrument_instrument; + instrument: PositionFields_market_tradableInstrument_instrument; } -export interface PositionsMetricsSubscription_positions_market_data { +export interface PositionFields_market_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface PositionFields_market_data { __typename: "MarketData"; /** * the mark price (an unsigned integer) */ markPrice: string; + /** + * market ID of the associated mark price + */ + market: PositionFields_market_data_market; } -export interface PositionsMetricsSubscription_positions_market { +export interface PositionFields_market { __typename: "Market"; /** * Market ID @@ -74,14 +144,14 @@ export interface PositionsMetricsSubscription_positions_market { /** * An instance of, or reference to, a tradable instrument. */ - tradableInstrument: PositionsMetricsSubscription_positions_market_tradableInstrument; + tradableInstrument: PositionFields_market_tradableInstrument; /** * marketData for the given market */ - data: PositionsMetricsSubscription_positions_market_data | null; + data: PositionFields_market_data | null; } -export interface PositionsMetricsSubscription_positions { +export interface PositionFields { __typename: "Position"; /** * Realised Profit and Loss (int64) @@ -103,19 +173,12 @@ export interface PositionsMetricsSubscription_positions { * RFC3339Nano time the position was updated */ updatedAt: string | null; + /** + * Margins of the party for the given position + */ + marginsConnection: PositionFields_marginsConnection; /** * Market relating to this position */ - market: PositionsMetricsSubscription_positions_market; -} - -export interface PositionsMetricsSubscription { - /** - * Subscribe to the positions updates - */ - positions: PositionsMetricsSubscription_positions; -} - -export interface PositionsMetricsSubscriptionVariables { - partyId: string; + market: PositionFields_market; } diff --git a/libs/positions/src/lib/__generated__/PositionMetricsFields.ts b/libs/positions/src/lib/__generated__/PositionMetricsFields.ts deleted file mode 100644 index fd780da1e..000000000 --- a/libs/positions/src/lib/__generated__/PositionMetricsFields.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -import { MarketTradingMode } from "@vegaprotocol/types"; - -// ==================================================== -// GraphQL fragment: PositionMetricsFields -// ==================================================== - -export interface PositionMetricsFields_market_tradableInstrument_instrument { - __typename: "Instrument"; - /** - * Full and fairly descriptive name for the instrument - */ - name: string; -} - -export interface PositionMetricsFields_market_tradableInstrument { - __typename: "TradableInstrument"; - /** - * An instance of, or reference to, a fully specified instrument. - */ - instrument: PositionMetricsFields_market_tradableInstrument_instrument; -} - -export interface PositionMetricsFields_market_data { - __typename: "MarketData"; - /** - * the mark price (an unsigned integer) - */ - markPrice: string; -} - -export interface PositionMetricsFields_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; - /** - * Market full name - */ - name: string; - /** - * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct - * number denominated in the currency of the market. (uint64) - * - * Examples: - * Currency Balance decimalPlaces Real Balance - * GBP 100 0 GBP 100 - * GBP 100 2 GBP 1.00 - * GBP 100 4 GBP 0.01 - * GBP 1 4 GBP 0.0001 ( 0.01p ) - * - * GBX (pence) 100 0 GBP 1.00 (100p ) - * GBX (pence) 100 2 GBP 0.01 ( 1p ) - * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) - * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) - */ - decimalPlaces: number; - /** - * positionDecimalPlaces indicates the number of decimal places that an integer must be shifted in order to get a correct size (uint64). - * i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes. - * 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market. - * This sets how big the smallest order / position on the market can be. - */ - positionDecimalPlaces: number; - /** - * Current mode of execution of the market - */ - tradingMode: MarketTradingMode; - /** - * An instance of, or reference to, a tradable instrument. - */ - tradableInstrument: PositionMetricsFields_market_tradableInstrument; - /** - * marketData for the given market - */ - data: PositionMetricsFields_market_data | null; -} - -export interface PositionMetricsFields { - __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; - /** - * RFC3339Nano time the position was updated - */ - updatedAt: string | null; - /** - * Market relating to this position - */ - market: PositionMetricsFields_market; -} diff --git a/libs/positions/src/lib/__generated__/PositionSubscribe.ts b/libs/positions/src/lib/__generated__/PositionSubscribe.ts deleted file mode 100644 index ed106421e..000000000 --- a/libs/positions/src/lib/__generated__/PositionSubscribe.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -import { MarketTradingMode } from "@vegaprotocol/types"; - -// ==================================================== -// 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 (an unsigned integer) - */ - 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 { - __typename: "Instrument"; - /** - * Full and fairly descriptive name for the instrument - */ - name: string; - /** - * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) - */ - code: string; -} - -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; - /** - * positionDecimalPlaces indicates the number of decimal places that an integer must be shifted in order to get a correct size (uint64). - * i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes. - * 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market. - * This sets how big the smallest order / position on the market can be. - */ - positionDecimalPlaces: 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; -} diff --git a/libs/positions/src/lib/__generated__/Positions.ts b/libs/positions/src/lib/__generated__/Positions.ts index 2a4b695a7..69cc82a81 100644 --- a/libs/positions/src/lib/__generated__/Positions.ts +++ b/libs/positions/src/lib/__generated__/Positions.ts @@ -9,7 +9,7 @@ import { MarketTradingMode } from "@vegaprotocol/types"; // GraphQL query operation: Positions // ==================================================== -export interface Positions_party_positions_market_data_market { +export interface Positions_party_positionsConnection_edges_node_marginsConnection_edges_node_market { __typename: "Market"; /** * Market ID @@ -17,43 +17,93 @@ export interface Positions_party_positions_market_data_market { id: string; } -export interface Positions_party_positions_market_data { +export interface Positions_party_positionsConnection_edges_node_marginsConnection_edges_node_asset { + __typename: "Asset"; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; +} + +export interface Positions_party_positionsConnection_edges_node_marginsConnection_edges_node { + __typename: "MarginLevels"; + /** + * market in which the margin is required for this party + */ + market: Positions_party_positionsConnection_edges_node_marginsConnection_edges_node_market; + /** + * minimal margin for the position to be maintained in the network (unsigned integer) + */ + maintenanceLevel: string; + /** + * if the margin is between maintenance and search, the network will initiate a collateral search (unsigned integer) + */ + searchLevel: string; + /** + * this is the minimum margin required for a party to place a new order on the network (unsigned integer) + */ + initialLevel: string; + /** + * If the margin of the party is greater than this level, then collateral will be released from the margin account into + * the general account of the party for the given asset. + */ + collateralReleaseLevel: string; + /** + * asset for the current margins + */ + asset: Positions_party_positionsConnection_edges_node_marginsConnection_edges_node_asset; +} + +export interface Positions_party_positionsConnection_edges_node_marginsConnection_edges { + __typename: "MarginEdge"; + node: Positions_party_positionsConnection_edges_node_marginsConnection_edges_node; +} + +export interface Positions_party_positionsConnection_edges_node_marginsConnection { + __typename: "MarginConnection"; + /** + * The margin levels in this connection + */ + edges: Positions_party_positionsConnection_edges_node_marginsConnection_edges[] | null; +} + +export interface Positions_party_positionsConnection_edges_node_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * Full and fairly descriptive name for the instrument + */ + name: string; +} + +export interface Positions_party_positionsConnection_edges_node_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of, or reference to, a fully specified instrument. + */ + instrument: Positions_party_positionsConnection_edges_node_market_tradableInstrument_instrument; +} + +export interface Positions_party_positionsConnection_edges_node_market_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface Positions_party_positionsConnection_edges_node_market_data { __typename: "MarketData"; /** * the mark price (an unsigned integer) */ 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; + market: Positions_party_positionsConnection_edges_node_market_data_market; } -export interface Positions_party_positions_market_tradableInstrument_instrument { - __typename: "Instrument"; - /** - * Full and fairly descriptive name for the instrument - */ - name: string; - /** - * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) - */ - code: string; -} - -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 { +export interface Positions_party_positionsConnection_edges_node_market { __typename: "Market"; /** * Market ID @@ -63,10 +113,6 @@ export interface Positions_party_positions_market { * 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) @@ -91,13 +137,21 @@ export interface Positions_party_positions_market { * This sets how big the smallest order / position on the market can be. */ positionDecimalPlaces: number; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; /** * An instance of, or reference to, a tradable instrument. */ - tradableInstrument: Positions_party_positions_market_tradableInstrument; + tradableInstrument: Positions_party_positionsConnection_edges_node_market_tradableInstrument; + /** + * marketData for the given market + */ + data: Positions_party_positionsConnection_edges_node_market_data | null; } -export interface Positions_party_positions { +export interface Positions_party_positionsConnection_edges_node { __typename: "Position"; /** * Realised Profit and Loss (int64) @@ -115,10 +169,31 @@ export interface Positions_party_positions { * Average entry price for this position */ averageEntryPrice: string; + /** + * RFC3339Nano time the position was updated + */ + updatedAt: string | null; + /** + * Margins of the party for the given position + */ + marginsConnection: Positions_party_positionsConnection_edges_node_marginsConnection; /** * Market relating to this position */ - market: Positions_party_positions_market; + market: Positions_party_positionsConnection_edges_node_market; +} + +export interface Positions_party_positionsConnection_edges { + __typename: "PositionEdge"; + node: Positions_party_positionsConnection_edges_node; +} + +export interface Positions_party_positionsConnection { + __typename: "PositionConnection"; + /** + * The positions in this connection + */ + edges: Positions_party_positionsConnection_edges[] | null; } export interface Positions_party { @@ -130,7 +205,7 @@ export interface Positions_party { /** * Trading positions relating to a party */ - positions: Positions_party_positions[] | null; + positionsConnection: Positions_party_positionsConnection; } export interface Positions { diff --git a/libs/positions/src/lib/__generated__/PositionsMetrics.ts b/libs/positions/src/lib/__generated__/PositionsSubscription.ts similarity index 53% rename from libs/positions/src/lib/__generated__/PositionsMetrics.ts rename to libs/positions/src/lib/__generated__/PositionsSubscription.ts index fb7ba4c34..93a61b32b 100644 --- a/libs/positions/src/lib/__generated__/PositionsMetrics.ts +++ b/libs/positions/src/lib/__generated__/PositionsSubscription.ts @@ -3,25 +3,13 @@ // @generated // This file was automatically generated and should not be edited. -import { AccountType, MarketTradingMode } from "@vegaprotocol/types"; +import { MarketTradingMode } from "@vegaprotocol/types"; // ==================================================== -// GraphQL query operation: PositionsMetrics +// GraphQL subscription operation: PositionsSubscription // ==================================================== -export interface PositionsMetrics_party_accounts_asset { - __typename: "Asset"; - /** - * The ID of the asset - */ - id: string; - /** - * The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18 - */ - decimals: number; -} - -export interface PositionsMetrics_party_accounts_market { +export interface PositionsSubscription_positions_marginsConnection_edges_node_market { __typename: "Market"; /** * Market ID @@ -29,35 +17,7 @@ export interface PositionsMetrics_party_accounts_market { id: string; } -export interface PositionsMetrics_party_accounts { - __typename: "Account"; - /** - * Account type (General, Margin, etc) - */ - type: AccountType; - /** - * Asset, the 'currency' - */ - asset: PositionsMetrics_party_accounts_asset; - /** - * Balance as string - current account balance (approx. as balances can be updated several times per second) - */ - balance: string; - /** - * Market (only relevant to margin accounts) - */ - market: PositionsMetrics_party_accounts_market | null; -} - -export interface PositionsMetrics_party_marginsConnection_edges_node_market { - __typename: "Market"; - /** - * Market ID - */ - id: string; -} - -export interface PositionsMetrics_party_marginsConnection_edges_node_asset { +export interface PositionsSubscription_positions_marginsConnection_edges_node_asset { __typename: "Asset"; /** * The symbol of the asset (e.g: GBP) @@ -65,12 +25,12 @@ export interface PositionsMetrics_party_marginsConnection_edges_node_asset { symbol: string; } -export interface PositionsMetrics_party_marginsConnection_edges_node { +export interface PositionsSubscription_positions_marginsConnection_edges_node { __typename: "MarginLevels"; /** * market in which the margin is required for this party */ - market: PositionsMetrics_party_marginsConnection_edges_node_market; + market: PositionsSubscription_positions_marginsConnection_edges_node_market; /** * minimal margin for the position to be maintained in the network (unsigned integer) */ @@ -91,23 +51,23 @@ export interface PositionsMetrics_party_marginsConnection_edges_node { /** * asset for the current margins */ - asset: PositionsMetrics_party_marginsConnection_edges_node_asset; + asset: PositionsSubscription_positions_marginsConnection_edges_node_asset; } -export interface PositionsMetrics_party_marginsConnection_edges { +export interface PositionsSubscription_positions_marginsConnection_edges { __typename: "MarginEdge"; - node: PositionsMetrics_party_marginsConnection_edges_node; + node: PositionsSubscription_positions_marginsConnection_edges_node; } -export interface PositionsMetrics_party_marginsConnection { +export interface PositionsSubscription_positions_marginsConnection { __typename: "MarginConnection"; /** * The margin levels in this connection */ - edges: PositionsMetrics_party_marginsConnection_edges[] | null; + edges: PositionsSubscription_positions_marginsConnection_edges[] | null; } -export interface PositionsMetrics_party_positionsConnection_edges_node_market_tradableInstrument_instrument { +export interface PositionsSubscription_positions_market_tradableInstrument_instrument { __typename: "Instrument"; /** * Full and fairly descriptive name for the instrument @@ -115,23 +75,35 @@ export interface PositionsMetrics_party_positionsConnection_edges_node_market_tr name: string; } -export interface PositionsMetrics_party_positionsConnection_edges_node_market_tradableInstrument { +export interface PositionsSubscription_positions_market_tradableInstrument { __typename: "TradableInstrument"; /** * An instance of, or reference to, a fully specified instrument. */ - instrument: PositionsMetrics_party_positionsConnection_edges_node_market_tradableInstrument_instrument; + instrument: PositionsSubscription_positions_market_tradableInstrument_instrument; } -export interface PositionsMetrics_party_positionsConnection_edges_node_market_data { +export interface PositionsSubscription_positions_market_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface PositionsSubscription_positions_market_data { __typename: "MarketData"; /** * the mark price (an unsigned integer) */ markPrice: string; + /** + * market ID of the associated mark price + */ + market: PositionsSubscription_positions_market_data_market; } -export interface PositionsMetrics_party_positionsConnection_edges_node_market { +export interface PositionsSubscription_positions_market { __typename: "Market"; /** * Market ID @@ -172,14 +144,14 @@ export interface PositionsMetrics_party_positionsConnection_edges_node_market { /** * An instance of, or reference to, a tradable instrument. */ - tradableInstrument: PositionsMetrics_party_positionsConnection_edges_node_market_tradableInstrument; + tradableInstrument: PositionsSubscription_positions_market_tradableInstrument; /** * marketData for the given market */ - data: PositionsMetrics_party_positionsConnection_edges_node_market_data | null; + data: PositionsSubscription_positions_market_data | null; } -export interface PositionsMetrics_party_positionsConnection_edges_node { +export interface PositionsSubscription_positions { __typename: "Position"; /** * Realised Profit and Loss (int64) @@ -201,52 +173,23 @@ export interface PositionsMetrics_party_positionsConnection_edges_node { * RFC3339Nano time the position was updated */ updatedAt: string | null; + /** + * Margins of the party for the given position + */ + marginsConnection: PositionsSubscription_positions_marginsConnection; /** * Market relating to this position */ - market: PositionsMetrics_party_positionsConnection_edges_node_market; + market: PositionsSubscription_positions_market; } -export interface PositionsMetrics_party_positionsConnection_edges { - __typename: "PositionEdge"; - node: PositionsMetrics_party_positionsConnection_edges_node; +export interface PositionsSubscription { + /** + * Subscribe to the positions updates + */ + positions: PositionsSubscription_positions; } -export interface PositionsMetrics_party_positionsConnection { - __typename: "PositionConnection"; - /** - * The positions in this connection - */ - edges: PositionsMetrics_party_positionsConnection_edges[] | null; -} - -export interface PositionsMetrics_party { - __typename: "Party"; - /** - * Party identifier - */ - id: string; - /** - * Collateral accounts relating to a party - */ - accounts: PositionsMetrics_party_accounts[] | null; - /** - * Margin levels for a market - */ - marginsConnection: PositionsMetrics_party_marginsConnection; - /** - * Trading positions relating to a party - */ - positionsConnection: PositionsMetrics_party_positionsConnection; -} - -export interface PositionsMetrics { - /** - * An entity that is trading on the Vega network - */ - party: PositionsMetrics_party | null; -} - -export interface PositionsMetricsVariables { +export interface PositionsSubscriptionVariables { partyId: string; } diff --git a/libs/positions/src/lib/positions-data-provider.ts b/libs/positions/src/lib/positions-data-provider.ts deleted file mode 100644 index 1ae479555..000000000 --- a/libs/positions/src/lib/positions-data-provider.ts +++ /dev/null @@ -1,88 +0,0 @@ -import produce from 'immer'; -import { gql } from '@apollo/client'; -import type { - Positions, - Positions_party_positions, -} from './__generated__/Positions'; -import { makeDataProvider } from '@vegaprotocol/react-helpers'; - -import type { - 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 - positionDecimalPlaces - tradableInstrument { - instrument { - name - code - } - } - } - } -`; - -export 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 = ( - data: Positions_party_positions[], - delta: PositionSubscribe_positions -) => { - return produce(data, (draft) => { - 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); diff --git a/libs/positions/src/lib/positions-metrics-data-provider.spec.ts b/libs/positions/src/lib/positions-data-providers.spec.ts similarity index 70% rename from libs/positions/src/lib/positions-metrics-data-provider.spec.ts rename to libs/positions/src/lib/positions-data-providers.spec.ts index dbec5af0b..3d50e4afe 100644 --- a/libs/positions/src/lib/positions-metrics-data-provider.spec.ts +++ b/libs/positions/src/lib/positions-data-providers.spec.ts @@ -1,8 +1,9 @@ import { AccountType, MarketTradingMode } from '@vegaprotocol/types'; -import type { PositionsMetrics } from './__generated__/PositionsMetrics'; -import { getMetrics } from './positions-metrics-data-provider'; +import type { Accounts } from '@vegaprotocol/accounts'; +import type { Positions } from './__generated__/Positions'; +import { getMetrics } from './positions-data-providers'; -const data: PositionsMetrics = { +const accounts: Accounts = { party: { __typename: 'Party', id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65', @@ -12,6 +13,7 @@ const data: PositionsMetrics = { type: AccountType.ACCOUNT_TYPE_GENERAL, asset: { __typename: 'Asset', + symbol: 'tDAI', id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', decimals: 5, }, @@ -23,12 +25,14 @@ const data: PositionsMetrics = { type: AccountType.ACCOUNT_TYPE_MARGIN, asset: { __typename: 'Asset', + symbol: 'tDAI', id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', decimals: 5, }, balance: '33353727', market: { __typename: 'Market', + name: 'AAVEDAI Monthly (30 Jun 2022)', id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8', }, }, @@ -37,57 +41,25 @@ const data: PositionsMetrics = { type: AccountType.ACCOUNT_TYPE_MARGIN, asset: { __typename: 'Asset', + symbol: 'tDAI', id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', decimals: 5, }, balance: '3274050', market: { __typename: 'Market', + name: 'UNIDAI Monthly (30 Jun 2022)', id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e', }, }, ], - marginsConnection: { - __typename: 'MarginConnection', - edges: [ - { - __typename: 'MarginEdge', - node: { - __typename: 'MarginLevels', - maintenanceLevel: '0', - searchLevel: '0', - initialLevel: '0', - collateralReleaseLevel: '0', - market: { - __typename: 'Market', - id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8', - }, - asset: { - __typename: 'Asset', - symbol: 'tDAI', - }, - }, - }, - { - __typename: 'MarginEdge', - node: { - __typename: 'MarginLevels', - maintenanceLevel: '0', - searchLevel: '0', - initialLevel: '0', - collateralReleaseLevel: '0', - market: { - __typename: 'Market', - id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e', - }, - asset: { - __typename: 'Asset', - symbol: 'tDAI', - }, - }, - }, - ], - }, + }, +}; + +const data: Positions = { + party: { + __typename: 'Party', + id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65', positionsConnection: { __typename: 'PositionConnection', edges: [ @@ -100,6 +72,29 @@ const data: PositionsMetrics = { updatedAt: '2022-07-28T14:53:54.725477Z', realisedPNL: '0', unrealisedPNL: '43804770', + marginsConnection: { + __typename: 'MarginConnection', + edges: [ + { + __typename: 'MarginEdge', + node: { + __typename: 'MarginLevels', + maintenanceLevel: '0', + searchLevel: '0', + initialLevel: '0', + collateralReleaseLevel: '0', + market: { + __typename: 'Market', + id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8', + }, + asset: { + __typename: 'Asset', + symbol: 'tDAI', + }, + }, + }, + ], + }, market: { __typename: 'Market', name: 'AAVEDAI Monthly (30 Jun 2022)', @@ -117,6 +112,10 @@ const data: PositionsMetrics = { data: { __typename: 'MarketData', markPrice: '9431775', + market: { + __typename: 'Market', + id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8', + }, }, }, }, @@ -130,6 +129,29 @@ const data: PositionsMetrics = { unrealisedPNL: '-9112700', averageEntryPrice: '840158', updatedAt: '2022-07-28T15:09:34.441143Z', + marginsConnection: { + __typename: 'MarginConnection', + edges: [ + { + __typename: 'MarginEdge', + node: { + __typename: 'MarginLevels', + maintenanceLevel: '0', + searchLevel: '0', + initialLevel: '0', + collateralReleaseLevel: '0', + market: { + __typename: 'Market', + id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e', + }, + asset: { + __typename: 'Asset', + symbol: 'tDAI', + }, + }, + }, + ], + }, market: { __typename: 'Market', id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e', @@ -147,6 +169,10 @@ const data: PositionsMetrics = { data: { __typename: 'MarketData', markPrice: '869762', + market: { + __typename: 'Market', + id: '10c4b1114d2f6fda239b73d018bca55888b6018f0ac70029972a17fea0a6a56e', + }, }, }, }, @@ -158,12 +184,12 @@ const data: PositionsMetrics = { describe('getMetrics', () => { it('returns positions metrics', () => { - const metrics = getMetrics(data.party); + const metrics = getMetrics(data.party, accounts.party?.accounts ?? null); expect(metrics.length).toEqual(2); }); it('calculates metrics', () => { - const metrics = getMetrics(data.party); + const metrics = getMetrics(data.party, accounts.party?.accounts ?? null); expect(metrics[0].assetSymbol).toEqual('tDAI'); expect(metrics[0].averageEntryPrice).toEqual('8993727'); diff --git a/libs/positions/src/lib/positions-metrics-data-provider.ts b/libs/positions/src/lib/positions-data-providers.ts similarity index 56% rename from libs/positions/src/lib/positions-metrics-data-provider.ts rename to libs/positions/src/lib/positions-data-providers.ts index 4948ef016..c763d137a 100644 --- a/libs/positions/src/lib/positions-metrics-data-provider.ts +++ b/libs/positions/src/lib/positions-data-providers.ts @@ -2,16 +2,19 @@ import { gql } from '@apollo/client'; import produce from 'immer'; import BigNumber from 'bignumber.js'; import sortBy from 'lodash/sortBy'; -import type { - PositionsMetrics, - PositionsMetrics_party, -} from './__generated__/PositionsMetrics'; -import { makeDataProvider } from '@vegaprotocol/react-helpers'; +import type { Accounts_party_accounts } from '@vegaprotocol/accounts'; +import { accountsDataProvider } from '@vegaprotocol/accounts'; +import { toBigNum } from '@vegaprotocol/react-helpers'; +import type { Positions, Positions_party } from './__generated__/Positions'; +import { + makeDataProvider, + makeDerivedDataProvider, +} from '@vegaprotocol/react-helpers'; import type { - PositionsMetricsSubscription, - PositionsMetricsSubscription_positions, -} from './__generated__/PositionsMetricsSubscription'; + PositionsSubscription, + PositionsSubscription_positions, +} from './__generated__/PositionsSubscription'; import { AccountType } from '@vegaprotocol/types'; import type { MarketTradingMode } from '@vegaprotocol/types'; @@ -40,17 +43,33 @@ export interface Position { } export interface Data { - party: PositionsMetrics_party | null; + party: Positions_party | null; positions: Position[] | null; } -const POSITIONS_METRICS_FRAGMENT = gql` - fragment PositionMetricsFields on Position { +const POSITION_FIELDS = gql` + fragment PositionFields on Position { realisedPNL openVolume unrealisedPNL averageEntryPrice updatedAt + marginsConnection { + edges { + node { + market { + id + } + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + asset { + symbol + } + } + } + } market { id name @@ -64,47 +83,23 @@ const POSITIONS_METRICS_FRAGMENT = gql` } data { markPrice - } - } - } -`; - -const POSITION_METRICS_QUERY = gql` - ${POSITIONS_METRICS_FRAGMENT} - query PositionsMetrics($partyId: ID!) { - party(id: $partyId) { - id - accounts { - type - asset { - id - decimals - } - balance market { id } } - marginsConnection { - edges { - node { - market { - id - } - maintenanceLevel - searchLevel - initialLevel - collateralReleaseLevel - asset { - symbol - } - } - } - } + } + } +`; + +export const POSITIONS_QUERY = gql` + ${POSITION_FIELDS} + query Positions($partyId: ID!) { + party(id: $partyId) { + id positionsConnection { edges { node { - ...PositionMetricsFields + ...PositionFields } } } @@ -112,33 +107,36 @@ const POSITION_METRICS_QUERY = gql` } `; -export const POSITIONS_METRICS_SUBSCRIPTION = gql` - ${POSITIONS_METRICS_FRAGMENT} - subscription PositionsMetricsSubscription($partyId: ID!) { +export const POSITIONS_SUBSCRIPTION = gql` + ${POSITION_FIELDS} + subscription PositionsSubscription($partyId: ID!) { positions(partyId: $partyId) { - ...PositionMetricsFields + ...PositionFields } } `; -export const getMetrics = (data: PositionsMetrics_party | null): Position[] => { - if (!data || !data.positionsConnection.edges) { +export const getMetrics = ( + data: Positions_party | null, + accounts: Accounts_party_accounts[] | null +): Position[] => { + if (!data || !data?.positionsConnection.edges) { return []; } const metrics: Position[] = []; - data.positionsConnection.edges.forEach((position) => { + data?.positionsConnection.edges.forEach((position) => { const market = position.node.market; const marketData = market.data; - const marginLevel = data.marginsConnection.edges?.find( + const marginLevel = position.node.marginsConnection.edges?.find( (margin) => margin.node.market.id === market.id )?.node; - const marginAccount = data.accounts?.find( - (account) => account.market?.id === market.id - ); + const marginAccount = accounts?.find((account) => { + return account.market?.id === market.id; + }); if (!marginAccount || !marginLevel || !marketData) { return; } - const generalAccount = data.accounts?.find( + const generalAccount = accounts?.find( (account) => account.asset.id === marginAccount.asset.id && account.type === AccountType.ACCOUNT_TYPE_GENERAL @@ -146,20 +144,20 @@ export const getMetrics = (data: PositionsMetrics_party | null): Position[] => { const assetDecimals = marginAccount.asset.decimals; const { positionDecimalPlaces, decimalPlaces: marketDecimalPlaces } = market; - const openVolume = new BigNumber(position.node.openVolume).dividedBy( - 10 ** positionDecimalPlaces + const openVolume = toBigNum( + position.node.openVolume, + positionDecimalPlaces ); - const marginAccountBalance = marginAccount - ? new BigNumber(marginAccount.balance).dividedBy(10 ** assetDecimals) - : new BigNumber(0); - const generalAccountBalance = generalAccount - ? new BigNumber(generalAccount.balance).dividedBy(10 ** assetDecimals) - : new BigNumber(0); - - const markPrice = new BigNumber(marketData.markPrice).dividedBy( - 10 ** marketDecimalPlaces + const marginAccountBalance = toBigNum( + marginAccount.balance ?? 0, + assetDecimals ); + const generalAccountBalance = toBigNum( + generalAccount?.balance ?? 0, + assetDecimals + ); + const markPrice = toBigNum(marketData.markPrice, marketDecimalPlaces); const notional = ( openVolume.isGreaterThan(0) ? openVolume : openVolume.multipliedBy(-1) @@ -172,13 +170,13 @@ export const getMetrics = (data: PositionsMetrics_party | null): Position[] => { ? new BigNumber(0) : marginAccountBalance.dividedBy(totalBalance).multipliedBy(100); - const marginMaintenance = new BigNumber( - marginLevel.maintenanceLevel - ).multipliedBy(marketDecimalPlaces); - const marginSearch = new BigNumber(marginLevel.searchLevel).multipliedBy( + const marginMaintenance = toBigNum( + marginLevel.maintenanceLevel, marketDecimalPlaces ); - const marginInitial = new BigNumber(marginLevel.initialLevel).multipliedBy( + const marginSearch = toBigNum(marginLevel.searchLevel, marketDecimalPlaces); + const marginInitial = toBigNum( + marginLevel.initialLevel, marketDecimalPlaces ); @@ -232,54 +230,50 @@ export const getMetrics = (data: PositionsMetrics_party | null): Position[] => { }; export const update = ( - data: Data, - delta: PositionsMetricsSubscription_positions + data: Positions_party, + delta: PositionsSubscription_positions | null ) => { - if (!data.party?.positionsConnection.edges) { - return data; - } - const edges = produce(data.party.positionsConnection.edges, (draft) => { - const index = draft.findIndex( + return produce(data, (draft) => { + if (!draft.positionsConnection.edges || !delta) { + return; + } + const index = draft.positionsConnection.edges.findIndex( (edge) => edge.node.market.id === delta.market.id ); if (index !== -1) { - draft[index].node = delta; + draft.positionsConnection.edges[index].node = delta; } else { - draft.push({ __typename: 'PositionEdge', node: delta }); + draft.positionsConnection.edges.push({ + __typename: 'PositionEdge', + node: delta, + }); } }); - const party = produce(data.party, (draft) => { - draft.positionsConnection.edges = edges; - }); - if (party === data.party) { - return data; - } - return { - party, - positions: getMetrics(party), - }; }; -const getData = (responseData: PositionsMetrics): Data => { - return { - party: responseData.party, - positions: sortBy(getMetrics(responseData.party), 'updatedAt').reverse(), - }; -}; - -const getDelta = ( - subscriptionData: PositionsMetricsSubscription -): PositionsMetricsSubscription_positions => subscriptionData.positions; - -export const positionsMetricsDataProvider = makeDataProvider< - PositionsMetrics, - Data, - PositionsMetricsSubscription, - PositionsMetricsSubscription_positions ->( - POSITION_METRICS_QUERY, - POSITIONS_METRICS_SUBSCRIPTION, +export const positionDataProvider = makeDataProvider< + Positions, + Positions_party, + PositionsSubscription, + PositionsSubscription_positions +>({ + query: POSITIONS_QUERY, + subscriptionQuery: POSITIONS_SUBSCRIPTION, update, - getData, - getDelta + getData: (responseData: Positions) => responseData.party, + getDelta: (subscriptionData: PositionsSubscription) => + subscriptionData.positions, +}); + +export const positionsMetricsDataProvider = makeDerivedDataProvider( + [positionDataProvider, accountsDataProvider], + ([positions, accounts]) => { + return sortBy( + getMetrics( + positions as Positions_party | null, + accounts as Accounts_party_accounts[] | null + ), + 'updatedAt' + ).reverse(); + } ); diff --git a/libs/positions/src/lib/positions-manager.tsx b/libs/positions/src/lib/positions-manager.tsx index 45076ab38..d4d8f9a8f 100644 --- a/libs/positions/src/lib/positions-manager.tsx +++ b/libs/positions/src/lib/positions-manager.tsx @@ -1,12 +1,11 @@ import { useRef, useCallback, useMemo } from 'react'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { useDataProvider } from '@vegaprotocol/react-helpers'; -import type { PositionsMetricsSubscription_positions } from './__generated__/PositionsMetricsSubscription'; import type { AgGridReact } from 'ag-grid-react'; import PositionsTable from './positions-table'; import type { GetRowsParams } from './positions-table'; -import { positionsMetricsDataProvider as dataProvider } from './positions-metrics-data-provider'; -import type { Data, Position } from './positions-metrics-data-provider'; +import { positionsMetricsDataProvider as dataProvider } from './positions-data-providers'; +import type { Position } from './positions-data-providers'; interface PositionsManagerProps { partyId: string; @@ -16,19 +15,20 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => { const gridRef = useRef(null); const variables = useMemo(() => ({ partyId }), [partyId]); const dataRef = useRef(null); - const update = useCallback(({ data }: { data: Data }) => { + const update = useCallback(({ data }: { data: Position[] | null }) => { if (!gridRef.current?.api) { return false; } - dataRef.current = data.positions; + dataRef.current = data; gridRef.current.api.refreshInfiniteCache(); return true; }, []); - const { data, error, loading } = useDataProvider< - Data, - PositionsMetricsSubscription_positions - >({ dataProvider, update, variables }); - dataRef.current = data?.positions || null; + const { data, error, loading } = useDataProvider({ + dataProvider, + update, + variables, + }); + dataRef.current = data; const getRows = async ({ successCallback, startRow, @@ -44,8 +44,8 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => { return ( diff --git a/libs/positions/src/lib/positions-table.spec.tsx b/libs/positions/src/lib/positions-table.spec.tsx index 95bd50807..64a001e6b 100644 --- a/libs/positions/src/lib/positions-table.spec.tsx +++ b/libs/positions/src/lib/positions-table.spec.tsx @@ -1,7 +1,7 @@ import type { RenderResult } from '@testing-library/react'; import { act, render, screen } from '@testing-library/react'; import PositionsTable from './positions-table'; -import type { Position } from './positions-metrics-data-provider'; +import type { Position } from './positions-data-providers'; import { MarketTradingMode } from '@vegaprotocol/types'; const singleRow: Position = { diff --git a/libs/positions/src/lib/positions-table.stories.tsx b/libs/positions/src/lib/positions-table.stories.tsx index 9656c5e5c..88cf4ad53 100644 --- a/libs/positions/src/lib/positions-table.stories.tsx +++ b/libs/positions/src/lib/positions-table.stories.tsx @@ -1,6 +1,6 @@ import type { Story, Meta } from '@storybook/react'; import { PositionsTable } from './positions-table'; -import type { Position } from './positions-metrics-data-provider'; +import type { Position } from './positions-data-providers'; import { MarketTradingMode } from '@vegaprotocol/types'; export default { @@ -14,7 +14,7 @@ export const Primary = Template.bind({}); const longPosition: Position = { marketName: 'BTC/USD (31 july 2022)', averageEntryPrice: '1134564', - capitalUtilisation: 10.0, + capitalUtilisation: 10, currentLeverage: 11, assetDecimals: 2, marketDecimalPlaces: 2, @@ -46,7 +46,7 @@ const longPosition: Position = { const shortPosition: Position = { marketName: 'ETH/USD (31 august 2022)', averageEntryPrice: '23976', - capitalUtilisation: 87.0, + capitalUtilisation: 87, currentLeverage: 7, assetDecimals: 2, marketDecimalPlaces: 2, diff --git a/libs/positions/src/lib/positions-table.tsx b/libs/positions/src/lib/positions-table.tsx index c29e71cb6..fbb3fe011 100644 --- a/libs/positions/src/lib/positions-table.tsx +++ b/libs/positions/src/lib/positions-table.tsx @@ -13,7 +13,7 @@ import { AgGridDynamic as AgGrid, ProgressBar } from '@vegaprotocol/ui-toolkit'; import { AgGridColumn } from 'ag-grid-react'; import type { AgGridReact, AgGridReactProps } from 'ag-grid-react'; import type { IDatasource, IGetRowsParams } from 'ag-grid-community'; -import type { Position } from './positions-metrics-data-provider'; +import type { Position } from './positions-data-providers'; import { MarketTradingMode } from '@vegaprotocol/types'; import { Intent } from '@vegaprotocol/ui-toolkit'; diff --git a/libs/react-helpers/src/hooks/use-data-provider.ts b/libs/react-helpers/src/hooks/use-data-provider.ts index 3ff599e5e..105fed608 100644 --- a/libs/react-helpers/src/hooks/use-data-provider.ts +++ b/libs/react-helpers/src/hooks/use-data-provider.ts @@ -59,20 +59,29 @@ export function useDataProvider({ return Promise.reject(); }, []); const callback = useCallback>( - ({ data, error, loading, delta, insertionData, totalCount }) => { + ({ + data, + error, + loading, + delta, + insertionData, + totalCount, + isInsert, + isUpdate, + }) => { setError(error); setLoading(loading); if (!error && !loading) { // if update or insert function returns true it means that component handles updates // component can use flush() which will call callback without delta and cause data state update if (initialized.current && data) { - if (delta && update && update({ delta, data })) { + if (isUpdate && update && (!delta || update({ delta, data }))) { return; } if ( - insertionData && + isInsert && insert && - insert({ insertionData, data, totalCount }) + (!insertionData || insert({ insertionData, data, totalCount })) ) { return; } diff --git a/libs/react-helpers/src/lib/generic-data-provider.spec.ts b/libs/react-helpers/src/lib/generic-data-provider.spec.ts index 0edddc797..fbcff1568 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.spec.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.spec.ts @@ -1,5 +1,10 @@ -import { makeDataProvider, defaultAppend } from './generic-data-provider'; +import { + makeDataProvider, + makeDerivedDataProvider, + defaultAppend, +} from './generic-data-provider'; import type { + CombineDerivedData, Query, UpdateCallback, Update, @@ -28,118 +33,158 @@ type QueryData = { totalCount?: number; }; +type CombinedData = { + totalCount?: number; +}; + type SubscriptionData = QueryData; type Delta = Data; -describe('data provider', () => { - const update = jest.fn< - ReturnType>, - Parameters> - >(); +const update = jest.fn< + ReturnType>, + Parameters> +>(); - const callback = jest.fn< - ReturnType>, - Parameters> - >(); +const callback = jest.fn< + ReturnType>, + Parameters> +>(); - const query: Query = { - kind: 'Document', - definitions: [], - }; - const subscriptionQuery: Query = query; +const query: Query = { + kind: 'Document', + definitions: [], +}; +const subscriptionQuery: Query = query; - const subscribe = makeDataProvider( - query, - subscriptionQuery, - update, - (r) => r.data, - (r) => r.data - ); +const subscribe = makeDataProvider({ + query, + subscriptionQuery, + update, + getData: (r) => r.data, + getDelta: (r) => r.data, +}); - const first = 100; - const paginatedSubscribe = makeDataProvider< - QueryData, - Data, - SubscriptionData, - Delta - >( - query, - subscriptionQuery, - update, - (r) => r.data, - (r) => r.data, - { - first, - append: defaultAppend, - getPageInfo: (r) => r?.pageInfo ?? null, - getTotalCount: (r) => r?.totalCount, - } - ); +const secondSubscribe = makeDataProvider< + QueryData, + Data, + SubscriptionData, + Delta +>({ + query, + subscriptionQuery, + update, + getData: (r) => r.data, + getDelta: (r) => r.data, +}); - const generateData = (start = 0, size = first) => { - return new Array(size).fill(null).map((v, i) => ({ - cursor: (i + start + 1).toString(), - node: { - id: (i + start + 1).toString(), - }, - })); - }; +const combineData = jest.fn< + ReturnType>, + Parameters> +>(); - const clientSubscribeUnsubscribe = jest.fn(); - const clientSubscribeSubscribe = jest.fn< - Subscription, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [(value: FetchResult) => void, (error: any) => void] - >(() => ({ - unsubscribe: clientSubscribeUnsubscribe, - closed: false, +const derivedSubscribe = makeDerivedDataProvider( + [subscribe, secondSubscribe], + combineData +); + +const first = 100; +const paginatedSubscribe = makeDataProvider< + QueryData, + Data, + SubscriptionData, + Delta +>({ + query, + subscriptionQuery, + update, + getData: (r) => r.data, + getDelta: (r) => r.data, + pagination: { + first, + append: defaultAppend, + getPageInfo: (r) => r?.pageInfo ?? null, + getTotalCount: (r) => r?.totalCount, + }, +}); + +const generateData = (start = 0, size = first) => { + return new Array(size).fill(null).map((v, i) => ({ + cursor: (i + start + 1).toString(), + node: { + id: (i + start + 1).toString(), + }, })); +}; - const clientSubscribe = jest.fn< - Observable>, - [SubscriptionOptions] - >( - () => - ({ - subscribe: clientSubscribeSubscribe, - } as unknown as Observable>) - ); +const clientSubscribeUnsubscribe = jest.fn(); +const clientSubscribeSubscribe = jest.fn< + Subscription, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [(value: FetchResult) => void, (error: any) => void] +>(() => ({ + unsubscribe: clientSubscribeUnsubscribe, + closed: false, +})); - const clientQueryPromise: { - resolve?: ( - value: - | ApolloQueryResult - | PromiseLike> - ) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - reject?: (reason?: any) => void; - } = {}; +const clientSubscribe = jest.fn< + Observable>, + [SubscriptionOptions] +>( + () => + ({ + subscribe: clientSubscribeSubscribe, + } as unknown as Observable>) +); - const clientQuery = jest.fn< - Promise>, - [QueryOptions] - >(() => { - return new Promise((resolve, reject) => { - clientQueryPromise.resolve = resolve; - clientQueryPromise.reject = reject; - }); +const clientQueryPromises: { + resolve: ( + value: + | ApolloQueryResult + | PromiseLike> + ) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void; +}[] = []; + +const clientQuery = jest.fn< + Promise>, + [QueryOptions] +>(() => { + return new Promise((resolve, reject) => { + clientQueryPromises.push({ resolve, reject }); }); +}); - const client = { - query: clientQuery, - subscribe: clientSubscribe, - } as unknown as ApolloClient; +const client = { + query: clientQuery, + subscribe: clientSubscribe, +} as unknown as ApolloClient; - const resolveQuery = async (data: QueryData) => { - if (clientQueryPromise.resolve) { - await clientQueryPromise.resolve({ - data, - loading: false, - networkStatus: 8, - }); - } - }; +const resolveQuery = async (data: QueryData) => { + const clientQueryPromise = clientQueryPromises.shift(); + if (clientQueryPromise) { + await clientQueryPromise.resolve({ + data, + loading: false, + networkStatus: 8, + }); + } +}; +const rejectQuery = async (reason: Error) => { + const clientQueryPromise = clientQueryPromises.shift(); + if (clientQueryPromise) { + await clientQueryPromise.reject(reason); + } +}; + +const clearPendingQueries = () => { + while (clientQueryPromises.length) { + clientQueryPromises.pop(); + } +}; + +describe('data provider', () => { it('memoize instance and unsubscribe if no subscribers', () => { const subscription1 = subscribe(jest.fn(), client); const subscription2 = subscribe(jest.fn(), client); @@ -201,6 +246,7 @@ describe('data provider', () => { }); it('refetch data on reload', async () => { + clearPendingQueries(); clientQuery.mockClear(); clientSubscribeUnsubscribe.mockClear(); clientSubscribeSubscribe.mockClear(); @@ -241,8 +287,9 @@ describe('data provider', () => { subscription.unsubscribe(); }); - it('fills data with nulls if paginaton is enabled', async () => { + it('fills data with nulls if pagination is enabled', async () => { callback.mockClear(); + clearPendingQueries(); const totalCount = 1000; const data: Item[] = new Array(first).fill(null).map((v, i) => ({ cursor: i.toString(), @@ -276,7 +323,7 @@ describe('data provider', () => { }); // load next page - subscription.load(); + subscription.load && subscription.load(); let lastQueryArgs = clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0]; expect(lastQueryArgs?.variables?.pagination).toEqual({ @@ -292,7 +339,7 @@ describe('data provider', () => { }); // load page with skip - subscription.load(500, 600); + subscription.load && subscription.load(500, 600); lastQueryArgs = clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0]; expect(lastQueryArgs?.variables?.pagination).toEqual({ @@ -309,7 +356,7 @@ describe('data provider', () => { }); // load in the gap - subscription.load(400, 500); + subscription.load && subscription.load(400, 500); lastQueryArgs = clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0]; expect(lastQueryArgs?.variables?.pagination).toEqual({ @@ -326,7 +373,7 @@ describe('data provider', () => { }); // load page after last block - subscription.load(700, 800); + subscription.load && subscription.load(700, 800); lastQueryArgs = clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0]; expect(lastQueryArgs?.variables?.pagination).toEqual({ @@ -343,7 +390,7 @@ describe('data provider', () => { }); // load last page shorter than expected - subscription.load(950, 1050); + subscription.load && subscription.load(950, 1050); lastQueryArgs = clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0]; expect(lastQueryArgs?.variables?.pagination).toEqual({ @@ -363,11 +410,11 @@ describe('data provider', () => { // load next page when pageInfo.hasNextPage === false const clientQueryCallsLength = clientQuery.mock.calls.length; - subscription.load(); + subscription.load && subscription.load(); expect(clientQuery.mock.calls.length).toBe(clientQueryCallsLength); // load last page longer than expected - subscription.load(960, 1000); + subscription.load && subscription.load(960, 1000); lastQueryArgs = clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0]; expect(lastQueryArgs?.variables?.pagination).toEqual({ @@ -403,7 +450,7 @@ describe('data provider', () => { expect(lastCallbackArgs[0].totalCount).toBe(undefined); // load next page - subscription.load(); + subscription.load && subscription.load(); await resolveQuery({ data: generateData(100), pageInfo: { @@ -415,7 +462,7 @@ describe('data provider', () => { expect(lastCallbackArgs[0].totalCount).toBe(undefined); // load last page - subscription.load(); + subscription.load && subscription.load(); await resolveQuery({ data: generateData(200, 50), pageInfo: { @@ -443,3 +490,77 @@ describe('data provider', () => { subscription.unsubscribe(); }); }); + +describe('derived data provider', () => { + it('memoize instance and unsubscribe if no subscribers', () => { + clientSubscribeSubscribe.mockClear(); + clientSubscribeUnsubscribe.mockClear(); + const variables = {}; + const subscription1 = derivedSubscribe(jest.fn(), client, variables); + const subscription2 = derivedSubscribe(jest.fn(), client, variables); + expect(clientSubscribeSubscribe.mock.calls.length).toEqual(2); + subscription1.unsubscribe(); + expect(clientSubscribeUnsubscribe.mock.calls.length).toEqual(0); + subscription2.unsubscribe(); + expect(clientSubscribeUnsubscribe.mock.calls.length).toEqual(2); + }); + + it('calls callback on each meaningful update, uses combineData function', async () => { + clearPendingQueries(); + const part1: Item[] = []; + const part2: Item[] = []; + const callback = jest.fn< + ReturnType>, + Parameters> + >(); + const subscription = derivedSubscribe(callback, client); + const data = { totalCount: 0 }; + combineData.mockReturnValueOnce(data); + expect(callback.mock.calls.length).toBe(0); + await resolveQuery({ data: part1 }); + expect(combineData.mock.calls.length).toBe(0); + expect(callback.mock.calls.length).toBe(0); + await resolveQuery({ data: part2 }); + expect(combineData.mock.calls.length).toBe(1); + expect(combineData.mock.calls[0][0][0]).toBe(part1); + expect(combineData.mock.calls[0][0][1]).toBe(part2); + expect(callback.mock.calls.length).toBe(1); + expect(callback.mock.calls[0][0].data).toBe(data); + expect(callback.mock.calls[0][0].loading).toBe(false); + subscription.unsubscribe(); + }); + + it('callback with error if any dependency has error, reloads all dependencies on reload', async () => { + combineData.mockClear(); + const part1: Item[] = []; + const part2: Item[] = []; + const callback = jest.fn< + ReturnType>, + Parameters> + >(); + const subscription = derivedSubscribe(callback, client); + const data = { totalCount: 0 }; + combineData.mockReturnValueOnce(data); + expect(callback.mock.calls.length).toBe(0); + await resolveQuery({ data: part1 }); + expect(combineData.mock.calls.length).toBe(0); + expect(callback.mock.calls.length).toBe(0); + const error = new Error(''); + await rejectQuery(error); + expect(combineData.mock.calls.length).toBe(0); + expect(callback.mock.calls.length).toBe(1); + expect(callback.mock.calls[0][0].error).toBe(error); + expect(callback.mock.calls[0][0].loading).toBe(false); + subscription.reload(); + expect(callback.mock.calls.length).toBe(3); + expect(callback.mock.calls[2][0].loading).toBe(true); + await resolveQuery({ data: part1 }); + expect(callback.mock.calls.length).toBe(3); + await resolveQuery({ data: part2 }); + expect(callback.mock.calls.length).toBe(4); + expect(callback.mock.calls[3][0].data).toStrictEqual(data); + expect(callback.mock.calls[3][0].loading).toBe(false); + expect(callback.mock.calls[3][0].error).toBeUndefined(); + subscription.unsubscribe(); + }); +}); diff --git a/libs/react-helpers/src/lib/generic-data-provider.ts b/libs/react-helpers/src/lib/generic-data-provider.ts index 0ea74dce1..d235669a0 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.ts @@ -9,16 +9,23 @@ import type { Subscription } from 'zen-observable-ts'; import isEqual from 'lodash/isEqual'; import type { Pagination as PaginationWithoutSkip } from '@vegaprotocol/types'; +interface UpdateData { + delta?: Delta; + insertionData?: Data | null; + isUpdate?: boolean; + isInsert?: boolean; +} export interface UpdateCallback { - (arg: { - data: Data | null; - error?: Error; - loading: boolean; - pageInfo: PageInfo | null; - delta?: Delta; - insertionData?: Data | null; - totalCount?: number; - }): void; + ( + arg: UpdateData & { + data: Data | null; + error?: Error; + loading: boolean; + loaded: boolean; + pageInfo: PageInfo | null; + totalCount?: number; + } + ): void; } export interface Load { @@ -44,7 +51,7 @@ export interface Subscribe { unsubscribe: () => void; reload: (forceReset?: boolean) => void; flush: () => void; - load: Load; + load?: Load; }; } @@ -124,51 +131,66 @@ export function defaultAppend( return { data, totalCount }; } -/** - * @param subscriptionQuery query that will be used for subscription - * @param update function that will be execued on each onNext, it should update data base on delta, it can reload data provider - * @param getData transforms received query data to format that will be stored in data provider - * @param getDelta transforms delta data to format that will be stored in data provider - * @param fetchPolicy - * @returns subscribe function - */ -function makeDataProviderInternal( - query: Query, - subscriptionQuery: Query, - update: Update, - getData: GetData, - getDelta: GetDelta, +interface DataProviderParams { + query: Query; + subscriptionQuery: Query; + update: Update; + getData: GetData; + getDelta: GetDelta; pagination?: { getPageInfo: GetPageInfo; getTotalCount?: GetTotalCount; append: Append; first: number; - }, - fetchPolicy: FetchPolicy = 'no-cache' -): Subscribe { + }; + fetchPolicy?: FetchPolicy; +} + +/** + * @param subscriptionQuery query that will be used for subscription + * @param update function that will be executed on each onNext, it should update data base on delta, it can reload data provider + * @param getData transforms received query data to format that will be stored in data provider + * @param getDelta transforms delta data to format that will be stored in data provider + * @param fetchPolicy + * @returns subscribe function + */ +function makeDataProviderInternal({ + query, + subscriptionQuery, + update, + getData, + getDelta, + pagination, + fetchPolicy, +}: DataProviderParams): Subscribe< + Data, + Delta +> { // list of callbacks passed through subscribe call const callbacks: UpdateCallback[] = []; // subscription is started before initial query, all deltas that will arrive before initial query response are put on queue const updateQueue: Delta[] = []; - let variables: OperationVariables | undefined = undefined; + let variables: OperationVariables | undefined; let data: Data | null = null; - let error: Error | undefined = undefined; - let loading = false; - let client: ApolloClient | undefined = undefined; - let subscription: Subscription | undefined = undefined; + let error: Error | undefined; + let loading = true; + let loaded = false; + let client: ApolloClient; + let subscription: Subscription | undefined; let pageInfo: PageInfo | null = null; let totalCount: number | undefined; // notify single callback about current state, delta is passes optionally only if notify was invoked onNext const notify = ( callback: UpdateCallback, - updateData?: { delta?: Delta; insertionData?: Data | null } + updateData?: UpdateData ) => { callback({ data, error, loading, + loaded, pageInfo, totalCount, ...updateData, @@ -176,15 +198,12 @@ function makeDataProviderInternal( }; // notify all callbacks - const notifyAll = (updateData?: { - delta?: Delta; - insertionData?: Data | null; - }) => { + const notifyAll = (updateData?: UpdateData) => { callbacks.forEach((callback) => notify(callback, updateData)); }; const load = async (start?: number, end?: number) => { - if (!client || !pagination || !pageInfo || !(data instanceof Array)) { + if (!pagination || !pageInfo || !(data instanceof Array)) { return Promise.reject(); } const paginationVariables: Pagination = { @@ -221,7 +240,7 @@ function makeDataProviderInternal( ...variables, pagination: paginationVariables, }, - fetchPolicy, + fetchPolicy: fetchPolicy || 'no-cache', }); const insertionData = getData(res.data); const insertionPageInfo = pagination.getPageInfo(res.data); @@ -236,7 +255,7 @@ function makeDataProviderInternal( totalCount = (pagination.getTotalCount && pagination.getTotalCount(res.data)) ?? totalCount; - notifyAll({ insertionData }); + notifyAll({ insertionData, isInsert: true }); return insertionData; }; @@ -280,6 +299,7 @@ function makeDataProviderInternal( } } } + loaded = true; } catch (e) { // if error will occur data provider stops subscription error = e as Error; @@ -310,7 +330,7 @@ function makeDataProviderInternal( }; const initialize = async () => { - if (subscription || loading) { + if (subscription) { return; } loading = true; @@ -339,7 +359,7 @@ function makeDataProviderInternal( return; } data = updatedData; - notifyAll({ delta }); + notifyAll({ delta, isUpdate: true }); } }, () => reload() @@ -347,7 +367,7 @@ function makeDataProviderInternal( await initialFetch(); }; - const reset = (clean = true) => { + const reset = () => { if (subscription) { subscription.unsubscribe(); subscription = undefined; @@ -355,6 +375,7 @@ function makeDataProviderInternal( data = null; error = undefined; loading = false; + loaded = false; notifyAll(); }; @@ -417,13 +438,13 @@ const memoize = ( * @param pagination pagination related functions { getPageInfo, getTotalCount, append, first } * @returns Subscribe subscribe function * @example - * const marketMidPriceProvider = makeDataProvider( - * gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`, - * gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`, - * (draft: Draft, delta: Delta, reload: (forceReset?: boolean) => void) => { draft.midPrice = delta.midPrice } - * (data:QueryData) => data.market.data.midPrice - * (delta:SubscriptionData) => delta.marketData.market - * ) + * const marketMidPriceProvider = makeDataProvider({ + * query: gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`, + * subscriptionQuery: gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`, + * update: (draft: Draft, delta: Delta, reload: (forceReset?: boolean) => void) => { draft.midPrice = delta.midPrice } + * getData: (data:QueryData) => data.market.data.midPrice + * getDelta: (delta:SubscriptionData) => delta.marketData.market + * }) * * const { unsubscribe, flush, reload } = marketMidPriceProvider( * ({ data, error, loading, delta }) => { ... }, @@ -433,29 +454,142 @@ const memoize = ( * */ export function makeDataProvider( - query: Query, - subscriptionQuery: Query, - update: Update, - getData: GetData, - getDelta: GetDelta, - pagination?: { - getPageInfo: GetPageInfo; - getTotalCount?: GetTotalCount; - append: Append; - first: number; - }, - fetchPolicy: FetchPolicy = 'no-cache' + params: DataProviderParams ): Subscribe { const getInstance = memoize(() => - makeDataProviderInternal( - query, - subscriptionQuery, - update, - getData, - getDelta, - pagination, - fetchPolicy - ) + makeDataProviderInternal(params) + ); + return (callback, client, variables) => + getInstance(variables)(callback, client, variables); +} + +/** + * Dependency subscribe needs to use any as Data and Delta because it's unknown what dependencies will be used. + * This effects in parts in combine function has any[] type + */ +type DependencySubscribe = Subscribe; // eslint-disable-line @typescript-eslint/no-explicit-any +type DependencyUpdateCallback = Parameters['0']; +export type CombineDerivedData = ( + parts: Parameters['0']['data'][] +) => Data | null; + +function makeDerivedDataProviderInternal( + dependencies: DependencySubscribe[], + combineData: CombineDerivedData +): Subscribe { + let subscriptions: ReturnType[] | undefined; + let client: ApolloClient; + const callbacks: UpdateCallback[] = []; + let variables: OperationVariables | undefined; + const parts: Parameters['0'][] = []; + let data: Data | null = null; + let error: Error | undefined; + let loading = true; + let loaded = false; + + // notify single callback about current state, delta is passes optionally only if notify was invoked onNext + const notify = (callback: UpdateCallback) => { + callback({ + data, + error, + loading, + loaded, + pageInfo: null, + }); + }; + + // notify all callbacks + const notifyAll = () => + callbacks.forEach((callback) => { + notify(callback); + }); + + const combine = () => { + let newError: Error | undefined; + let newLoading = false; + let newLoaded = true; + dependencies + .map((dependency, i) => parts[i]) + .forEach((part) => { + newError = newError || (part && part.error); + newLoading = newLoading || !part || part.loading; + newLoaded = newLoaded && part && part.loaded; + }); + + const newData = newLoaded + ? combineData(parts.map((part) => part.data)) + : data; + if ( + newLoading !== loading || + newError !== error || + newLoaded !== loaded || + newData !== data + ) { + loading = newLoading; + error = newError; + loaded = newLoaded; + data = newData; + notifyAll(); + } + }; + + const initialize = () => { + if (subscriptions) { + return; + } + subscriptions = dependencies.map((dependency, i) => + dependency( + (updateData) => { + parts[i] = updateData; + combine(); + }, + client, + variables + ) + ); + }; + + // remove callback from list, and unsubscribe if there is no more callbacks registered + const unsubscribe = (callback: UpdateCallback) => { + callbacks.splice(callbacks.indexOf(callback), 1); + if (callbacks.length === 0) { + subscriptions?.forEach((subscription) => subscription.unsubscribe()); + subscriptions = undefined; + data = null; + error = undefined; + loading = true; + loaded = false; + } + }; + + return (callback, c, v) => { + callbacks.push(callback); + if (callbacks.length === 1) { + client = c; + variables = v; + initialize(); + } else { + notify(callback); + } + return { + unsubscribe: () => unsubscribe(callback), + reload: (forceReset) => + subscriptions && + subscriptions.forEach((subscription) => + subscription.reload(forceReset) + ), + flush: () => notify(callback), + }; + }; +} + +// Derived data provider has no subscription, hence there is no delta (never) +export function makeDerivedDataProvider( + dependencies: DependencySubscribe[], + combineData: CombineDerivedData +): Subscribe { + const getInstance = memoize(() => + makeDerivedDataProviderInternal(dependencies, combineData) ); return (callback, client, variables) => getInstance(variables)(callback, client, variables); diff --git a/libs/trades/src/lib/trades-data-provider.ts b/libs/trades/src/lib/trades-data-provider.ts index 8e6e5f116..3f17635db 100644 --- a/libs/trades/src/lib/trades-data-provider.ts +++ b/libs/trades/src/lib/trades-data-provider.ts @@ -97,15 +97,15 @@ const getDelta = (subscriptionData: TradesSub): TradeFields[] => const getPageInfo = (responseData: Trades): PageInfo | null => responseData.market?.tradesConnection.pageInfo || null; -export const tradesDataProvider = makeDataProvider( - TRADES_QUERY, - TRADES_SUB, +export const tradesDataProvider = makeDataProvider({ + query: TRADES_QUERY, + subscriptionQuery: TRADES_SUB, update, getData, getDelta, - { + pagination: { getPageInfo, append, first: 100, - } -); + }, +}); diff --git a/package.json b/package.json index 40e067eda..762c1c954 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,8 @@ "react-window": "^1.8.7", "react-window-infinite-loader": "^1.0.7", "recharts": "^2.1.2", + "regenerator-runtime": "0.13.7", + "tslib": "^2.0.0", "uuid": "^8.3.2", "web-vitals": "^2.1.4", "zod": "^3.17.3", @@ -104,6 +106,7 @@ "@svgr/webpack": "^5.4.0", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "13.3.0", + "@testing-library/react-hooks": "7.0.2", "@testing-library/user-event": "^14.4.1", "@types/classnames": "^2.3.1", "@types/faker": "^5.5.8", @@ -148,8 +151,8 @@ "mock-apollo-client": "^1.2.0", "npmlog": "^6.0.2", "nx": "13.10.1", - "prettier": "^2.5.1", "postcss": "^8.4.6", + "prettier": "^2.5.1", "react-test-renderer": "17.0.2", "recast": "^0.21.1", "regenerator-runtime": "0.13.7", @@ -157,11 +160,11 @@ "resize-observer-polyfill": "^1.5.1", "sass": "1.49.9", "storybook-addon-themes": "^6.1.0", - "ts-jest": "27.0.5", "tailwindcss": "^3.0.23", + "ts-jest": "27.0.5", + "tslib": "^2.0.0", "type-fest": "^2.12.2", "typescript": "~4.5.2", - "tslib": "^2.0.0", "url-loader": "^3.0.0" }, "lint-staged": { diff --git a/tsconfig.base.json b/tsconfig.base.json index 83354e23f..9424d0d2b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,6 +17,7 @@ "resolveJsonModule": true, "paths": { "@vegaprotocol/accounts": ["libs/accounts/src/index.ts"], + "@vegaprotocol/assets": ["libs/assets/src/index.ts"], "@vegaprotocol/candles-chart": ["libs/candles-chart/src/index.ts"], "@vegaprotocol/cypress": ["libs/cypress/src/index.ts"], "@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"], diff --git a/workspace.json b/workspace.json index dec711569..66d385075 100644 --- a/workspace.json +++ b/workspace.json @@ -2,6 +2,7 @@ "version": 2, "projects": { "accounts": "libs/accounts", + "assets": "libs/assets", "candles-chart": "libs/candles-chart", "console-lite": "apps/console-lite", "console-lite-e2e": "apps/console-lite-e2e", diff --git a/yarn.lock b/yarn.lock index 072d9bb54..a707ef4de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5628,6 +5628,17 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@7.0.2": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" + integrity sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + react-error-boundary "^3.1.0" + "@testing-library/react@13.3.0": version "13.3.0" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.3.0.tgz#bf298bfbc5589326bbcc8052b211f3bb097a97c5" @@ -6135,7 +6146,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@18.0.6", "@types/react-dom@^18.0.0", "@types/react-dom@^18.0.5": +"@types/react-dom@18.0.6", "@types/react-dom@>=16.9.0", "@types/react-dom@^18.0.0", "@types/react-dom@^18.0.5": version "18.0.6" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.6.tgz#36652900024842b74607a17786b6662dd1e103a1" integrity sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA== @@ -6166,6 +6177,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@>=16.9.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz#7b7f69ca98821ea5501b21ba24ea7b6139da2243" + integrity sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ== + dependencies: + "@types/react" "*" + "@types/react-virtualized-auto-sizer@^1.0.0", "@types/react-virtualized-auto-sizer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4" @@ -6206,6 +6224,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@>=16.9.0": + version "18.0.17" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.17.tgz#4583d9c322d67efe4b39a935d223edcc7050ccf4" + integrity sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -18140,6 +18167,13 @@ react-element-to-jsx-string@^14.3.4: is-plain-object "5.0.0" react-is "17.0.2" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"