feat(#993): derived data provider (#1047)

* feat(#473): add positions metrics data provider

* feat(#473) add positions stats

* feat(#473) add positions stats

* feat(#473): add positions stats

* feat(#473): add positions stats

* feat(#473): position metrics, test and refactoring

* feat(#473): add unit tests to positions table

* feat(#473): fix spelling, order positions by updated at desc

* feat(#473): protect from division by 0

* feat(#473): fix trading positions e2e tests

* feat(#473): fix e2e data mocks

* feat(#473): post code review clean up

* feat(#993): dependencies handling in data provider

* feat(#993): fix e2e tests data mocks

* feat(#993): remove position metrics mocks, add market data market id

* feat(#993): add missing mocks, fix combine function

* feat(#993): set loading initially to true, add unit tests

* feat(#993): cleanup, add comments

* feat(#993): remove undefined from client type

* feat(#993): cosmetic changes

* feat:(#993): pass informaton about update callback cause
This commit is contained in:
Bartłomiej Głownia 2022-08-26 17:39:40 +02:00 committed by GitHub
parent 171babc2c9
commit 93a5f911f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1358 additions and 1240 deletions

View File

@ -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;

View File

@ -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,
},
},
],
},
};

View File

@ -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>
): 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);
};

View File

@ -18,6 +18,11 @@ export const generateMarket = (override?: PartialDeep<Market>): Market => {
decimalPlaces: 5,
positionDecimalPlaces: 0,
data: {
auctionEnd: '',
auctionStart: '',
indicativePrice: '',
suppliedStake: '',
targetStake: '',
market: {
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
__typename: 'Market',

View File

@ -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>): Markets => {
const markets: Markets_markets[] = [
export const generateMarkets = (
override?: PartialDeep<MarketList>
): 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>): 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>): 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>): 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>): 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>): Markets => {
tradableInstrument: {
instrument: {
code: 'SOLUSD',
name: '',
metadata: {
__typename: 'InstrumentMetadata',
tags: [],
},
product: {
settlementAsset: {
symbol: 'XYZalpha',

View File

@ -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>
): 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);
};

View File

@ -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>
): 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,
};
}),
},
},
};

View File

@ -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',

View File

@ -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();

View File

@ -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,

View File

@ -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,
});

View File

@ -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';

12
libs/assets/.babelrc Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View File

@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

7
libs/assets/README.md Normal file
View File

@ -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).

View File

@ -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'],
};

4
libs/assets/package.json Normal file
View File

@ -0,0 +1,4 @@
{
"name": "@vegaprotocol/assets",
"version": "0.0.1"
}

43
libs/assets/project.json Normal file
View File

@ -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
}
}
}
}

1
libs/assets/src/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './lib';

View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

25
libs/assets/tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,22 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
]
}

View File

@ -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;

View File

@ -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,
}
);
},
});

View File

@ -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;

View File

@ -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';

View File

@ -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;

View File

@ -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<Positions>(POSITION_QUERY, {
const { data: marketDataPositions } = useQuery<Positions>(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:

View File

@ -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,
});

View File

@ -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,
}
);
},
});

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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);

View File

@ -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,16 +41,37 @@ 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',
},
},
],
},
};
const data: Positions = {
party: {
__typename: 'Party',
id: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
positionsConnection: {
__typename: 'PositionConnection',
edges: [
{
__typename: 'PositionEdge',
node: {
__typename: 'Position',
openVolume: '100',
averageEntryPrice: '8993727',
updatedAt: '2022-07-28T14:53:54.725477Z',
realisedPNL: '0',
unrealisedPNL: '43804770',
marginsConnection: {
__typename: 'MarginConnection',
edges: [
@ -68,6 +93,45 @@ const data: PositionsMetrics = {
},
},
},
],
},
market: {
__typename: 'Market',
name: 'AAVEDAI Monthly (30 Jun 2022)',
id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8',
decimalPlaces: 5,
tradingMode: MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'AAVEDAI Monthly (30 Jun 2022)',
},
},
data: {
__typename: 'MarketData',
markPrice: '9431775',
market: {
__typename: 'Market',
id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8',
},
},
},
},
},
{
__typename: 'PositionEdge',
node: {
__typename: 'Position',
openVolume: '-100',
realisedPNL: '0',
unrealisedPNL: '-9112700',
averageEntryPrice: '840158',
updatedAt: '2022-07-28T15:09:34.441143Z',
marginsConnection: {
__typename: 'MarginConnection',
edges: [
{
__typename: 'MarginEdge',
node: {
@ -88,48 +152,6 @@ const data: PositionsMetrics = {
},
],
},
positionsConnection: {
__typename: 'PositionConnection',
edges: [
{
__typename: 'PositionEdge',
node: {
__typename: 'Position',
openVolume: '100',
averageEntryPrice: '8993727',
updatedAt: '2022-07-28T14:53:54.725477Z',
realisedPNL: '0',
unrealisedPNL: '43804770',
market: {
__typename: 'Market',
name: 'AAVEDAI Monthly (30 Jun 2022)',
id: '5e6035fe6a6df78c9ec44b333c231e63d357acef0a0620d2c243f5865d1dc0d8',
decimalPlaces: 5,
tradingMode: MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'AAVEDAI Monthly (30 Jun 2022)',
},
},
data: {
__typename: 'MarketData',
markPrice: '9431775',
},
},
},
},
{
__typename: 'PositionEdge',
node: {
__typename: 'Position',
openVolume: '-100',
realisedPNL: '0',
unrealisedPNL: '-9112700',
averageEntryPrice: '840158',
updatedAt: '2022-07-28T15:09:34.441143Z',
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');

View File

@ -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,51 +43,17 @@ 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
market {
id
name
decimalPlaces
positionDecimalPlaces
tradingMode
tradableInstrument {
instrument {
name
}
}
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 {
@ -101,10 +70,36 @@ const POSITION_METRICS_QUERY = gql`
}
}
}
market {
id
name
decimalPlaces
positionDecimalPlaces
tradingMode
tradableInstrument {
instrument {
name
}
}
data {
markPrice
market {
id
}
}
}
}
`;
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;
return produce(data, (draft) => {
if (!draft.positionsConnection.edges || !delta) {
return;
}
const edges = produce(data.party.positionsConnection.edges, (draft) => {
const index = draft.findIndex(
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<Position[]>(
[positionDataProvider, accountsDataProvider],
([positions, accounts]) => {
return sortBy(
getMetrics(
positions as Positions_party | null,
accounts as Accounts_party_accounts[] | null
),
'updatedAt'
).reverse();
}
);

View File

@ -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<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const dataRef = useRef<Position[] | null>(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<Position[], never>({
dataProvider,
update,
variables,
});
dataRef.current = data;
const getRows = async ({
successCallback,
startRow,
@ -44,8 +44,8 @@ export const PositionsManager = ({ partyId }: PositionsManagerProps) => {
return (
<AsyncRenderer loading={loading} error={error} data={data}>
<PositionsTable
rowModelType={data?.positions?.length ? 'infinite' : 'clientSide'}
rowData={data?.positions?.length ? undefined : []}
rowModelType={data?.length ? 'infinite' : 'clientSide'}
rowData={data?.length ? undefined : []}
datasource={{ getRows }}
/>
</AsyncRenderer>

View File

@ -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 = {

View File

@ -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,

View File

@ -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';

View File

@ -59,20 +59,29 @@ export function useDataProvider<Data, Delta>({
return Promise.reject();
}, []);
const callback = useCallback<UpdateCallback<Data, Delta>>(
({ 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;
}

View File

@ -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<
const update = jest.fn<
ReturnType<Update<Data, Delta>>,
Parameters<Update<Data, Delta>>
>();
>();
const callback = jest.fn<
const callback = jest.fn<
ReturnType<UpdateCallback<Data, Delta>>,
Parameters<UpdateCallback<Data, Delta>>
>();
>();
const query: Query<QueryData> = {
const query: Query<QueryData> = {
kind: 'Document',
definitions: [],
};
const subscriptionQuery: Query<SubscriptionData> = query;
};
const subscriptionQuery: Query<SubscriptionData> = query;
const subscribe = makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
const subscribe = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
query,
subscriptionQuery,
update,
(r) => r.data,
(r) => r.data
);
getData: (r) => r.data,
getDelta: (r) => r.data,
});
const first = 100;
const paginatedSubscribe = makeDataProvider<
const secondSubscribe = makeDataProvider<
QueryData,
Data,
SubscriptionData,
Delta
>(
>({
query,
subscriptionQuery,
update,
(r) => r.data,
(r) => r.data,
{
getData: (r) => r.data,
getDelta: (r) => r.data,
});
const combineData = jest.fn<
ReturnType<CombineDerivedData<CombinedData>>,
Parameters<CombineDerivedData<CombinedData>>
>();
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) => {
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 clientSubscribeUnsubscribe = jest.fn();
const clientSubscribeSubscribe = jest.fn<
const clientSubscribeUnsubscribe = jest.fn();
const clientSubscribeSubscribe = jest.fn<
Subscription,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[(value: FetchResult<SubscriptionData>) => void, (error: any) => void]
>(() => ({
>(() => ({
unsubscribe: clientSubscribeUnsubscribe,
closed: false,
}));
}));
const clientSubscribe = jest.fn<
const clientSubscribe = jest.fn<
Observable<FetchResult<SubscriptionData>>,
[SubscriptionOptions<OperationVariables, SubscriptionData>]
>(
>(
() =>
({
subscribe: clientSubscribeSubscribe,
} as unknown as Observable<FetchResult<SubscriptionData>>)
);
);
const clientQueryPromise: {
resolve?: (
const clientQueryPromises: {
resolve: (
value:
| ApolloQueryResult<QueryData>
| PromiseLike<ApolloQueryResult<QueryData>>
) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject?: (reason?: any) => void;
} = {};
reject: (reason?: any) => void;
}[] = [];
const clientQuery = jest.fn<
const clientQuery = jest.fn<
Promise<ApolloQueryResult<QueryData>>,
[QueryOptions<OperationVariables, QueryData>]
>(() => {
>(() => {
return new Promise((resolve, reject) => {
clientQueryPromise.resolve = resolve;
clientQueryPromise.reject = reject;
});
clientQueryPromises.push({ resolve, reject });
});
});
const client = {
const client = {
query: clientQuery,
subscribe: clientSubscribe,
} as unknown as ApolloClient<object>;
} as unknown as ApolloClient<object>;
const resolveQuery = async (data: QueryData) => {
if (clientQueryPromise.resolve) {
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<UpdateCallback<CombinedData, never>>,
Parameters<UpdateCallback<CombinedData, never>>
>();
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<UpdateCallback<CombinedData, never>>,
Parameters<UpdateCallback<CombinedData, never>>
>();
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();
});
});

View File

@ -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<Data, Delta> {
delta?: Delta;
insertionData?: Data | null;
isUpdate?: boolean;
isInsert?: boolean;
}
export interface UpdateCallback<Data, Delta> {
(arg: {
(
arg: UpdateData<Data, Delta> & {
data: Data | null;
error?: Error;
loading: boolean;
loaded: boolean;
pageInfo: PageInfo | null;
delta?: Delta;
insertionData?: Data | null;
totalCount?: number;
}): void;
}
): void;
}
export interface Load<Data> {
@ -44,7 +51,7 @@ export interface Subscribe<Data, Delta> {
unsubscribe: () => void;
reload: (forceReset?: boolean) => void;
flush: () => void;
load: Load<Data>;
load?: Load<Data>;
};
}
@ -124,51 +131,66 @@ export function defaultAppend<Data>(
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<QueryData, Data, SubscriptionData, Delta>(
query: Query<QueryData>,
subscriptionQuery: Query<SubscriptionData>,
update: Update<Data, Delta>,
getData: GetData<QueryData, Data>,
getDelta: GetDelta<SubscriptionData, Delta>,
interface DataProviderParams<QueryData, Data, SubscriptionData, Delta> {
query: Query<QueryData>;
subscriptionQuery: Query<SubscriptionData>;
update: Update<Data, Delta>;
getData: GetData<QueryData, Data>;
getDelta: GetDelta<SubscriptionData, Delta>;
pagination?: {
getPageInfo: GetPageInfo<QueryData>;
getTotalCount?: GetTotalCount<QueryData>;
append: Append<Data>;
first: number;
},
fetchPolicy: FetchPolicy = 'no-cache'
): Subscribe<Data, Delta> {
};
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<QueryData, Data, SubscriptionData, Delta>({
query,
subscriptionQuery,
update,
getData,
getDelta,
pagination,
fetchPolicy,
}: DataProviderParams<QueryData, Data, SubscriptionData, Delta>): Subscribe<
Data,
Delta
> {
// list of callbacks passed through subscribe call
const callbacks: UpdateCallback<Data, Delta>[] = [];
// 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<object> | undefined = undefined;
let subscription: Subscription | undefined = undefined;
let error: Error | undefined;
let loading = true;
let loaded = false;
let client: ApolloClient<object>;
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<Data, Delta>,
updateData?: { delta?: Delta; insertionData?: Data | null }
updateData?: UpdateData<Data, Delta>
) => {
callback({
data,
error,
loading,
loaded,
pageInfo,
totalCount,
...updateData,
@ -176,15 +198,12 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
};
// notify all callbacks
const notifyAll = (updateData?: {
delta?: Delta;
insertionData?: Data | null;
}) => {
const notifyAll = (updateData?: UpdateData<Data, Delta>) => {
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<QueryData, Data, SubscriptionData, Delta>(
...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<QueryData, Data, SubscriptionData, Delta>(
totalCount =
(pagination.getTotalCount && pagination.getTotalCount(res.data)) ??
totalCount;
notifyAll({ insertionData });
notifyAll({ insertionData, isInsert: true });
return insertionData;
};
@ -280,6 +299,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
}
}
}
loaded = true;
} catch (e) {
// if error will occur data provider stops subscription
error = e as Error;
@ -310,7 +330,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
};
const initialize = async () => {
if (subscription || loading) {
if (subscription) {
return;
}
loading = true;
@ -339,7 +359,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
return;
}
data = updatedData;
notifyAll({ delta });
notifyAll({ delta, isUpdate: true });
}
},
() => reload()
@ -347,7 +367,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
await initialFetch();
};
const reset = (clean = true) => {
const reset = () => {
if (subscription) {
subscription.unsubscribe();
subscription = undefined;
@ -355,6 +375,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
data = null;
error = undefined;
loading = false;
loaded = false;
notifyAll();
};
@ -417,13 +438,13 @@ const memoize = <Data, Delta>(
* @param pagination pagination related functions { getPageInfo, getTotalCount, append, first }
* @returns Subscribe<Data, Delta> subscribe function
* @example
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
* gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`,
* gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`,
* (draft: Draft<Data>, delta: Delta, reload: (forceReset?: boolean) => void) => { draft.midPrice = delta.midPrice }
* (data:QueryData) => data.market.data.midPrice
* (delta:SubscriptionData) => delta.marketData.market
* )
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
* 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<Data>, 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 = <Data, Delta>(
*
*/
export function makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
query: Query<QueryData>,
subscriptionQuery: Query<SubscriptionData>,
update: Update<Data, Delta>,
getData: GetData<QueryData, Data>,
getDelta: GetDelta<SubscriptionData, Delta>,
pagination?: {
getPageInfo: GetPageInfo<QueryData>;
getTotalCount?: GetTotalCount<QueryData>;
append: Append<Data>;
first: number;
},
fetchPolicy: FetchPolicy = 'no-cache'
params: DataProviderParams<QueryData, Data, SubscriptionData, Delta>
): Subscribe<Data, Delta> {
const getInstance = memoize<Data, Delta>(() =>
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<any, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
type DependencyUpdateCallback = Parameters<DependencySubscribe>['0'];
export type CombineDerivedData<Data> = (
parts: Parameters<DependencyUpdateCallback>['0']['data'][]
) => Data | null;
function makeDerivedDataProviderInternal<Data>(
dependencies: DependencySubscribe[],
combineData: CombineDerivedData<Data>
): Subscribe<Data, never> {
let subscriptions: ReturnType<DependencySubscribe>[] | undefined;
let client: ApolloClient<object>;
const callbacks: UpdateCallback<Data, never>[] = [];
let variables: OperationVariables | undefined;
const parts: Parameters<DependencyUpdateCallback>['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<Data, never>) => {
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<Data, never>) => {
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<Data>(
dependencies: DependencySubscribe[],
combineData: CombineDerivedData<Data>
): Subscribe<Data, never> {
const getInstance = memoize<Data, never>(() =>
makeDerivedDataProviderInternal(dependencies, combineData)
);
return (callback, client, variables) =>
getInstance(variables)(callback, client, variables);

View File

@ -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,
}
);
},
});

View File

@ -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": {

View File

@ -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"],

View File

@ -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",

View File

@ -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"