feat(positions): filter closed markets in positions table (#4569)

This commit is contained in:
Matthew Russell 2023-08-22 11:17:10 +02:00 committed by GitHub
parent ab4f4e9084
commit 6d130c9cfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 127 additions and 15 deletions

View File

@ -94,7 +94,11 @@ const MainGrid = memo(
> >
<TradeGridChild> <TradeGridChild>
<Tabs storageKey="console-trade-grid-bottom"> <Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}> <Tab
id="positions"
name={t('Positions')}
menu={<TradingViews.positions.menu />}
>
<TradingViews.positions.component /> <TradingViews.positions.component />
</Tab> </Tab>
<Tab <Tab

View File

@ -17,6 +17,7 @@ import type { OrderContainerProps } from '../../components/orders-container';
import { OrdersContainer } from '../../components/orders-container'; import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container'; import { StopOrdersContainer } from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu'; import { AccountsMenu } from '../../components/accounts-menu';
import { PositionsMenu } from '../../components/positions-menu';
type MarketDependantView = type MarketDependantView =
| typeof CandlesChartContainer | typeof CandlesChartContainer
@ -57,7 +58,11 @@ export const TradingViews = {
label: 'Trades', label: 'Trades',
component: requiresMarket(TradesContainer), component: requiresMarket(TradesContainer),
}, },
positions: { label: 'Positions', component: PositionsContainer }, positions: {
label: 'Positions',
component: PositionsContainer,
menu: PositionsMenu,
},
activeOrders: { activeOrders: {
label: 'Active', label: 'Active',
component: (props: OrderContainerProps) => ( component: (props: OrderContainerProps) => (

View File

@ -5,6 +5,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import type { DataGridSlice } from '../../stores/datagrid-store-slice'; import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice'; import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import type { StateCreator } from 'zustand';
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler'; import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
@ -13,6 +14,7 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const onMarketClick = useMarketClickHandler(true); const onMarketClick = useMarketClickHandler(true);
const { pubKey, pubKeys, isReadOnly } = useVegaWallet(); const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
const gridStore = usePositionsStore((store) => store.gridStore); const gridStore = usePositionsStore((store) => store.gridStore);
const updateGridStore = usePositionsStore((store) => store.updateGridStore); const updateGridStore = usePositionsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore); const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore);
@ -40,12 +42,35 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks} gridProps={gridStoreCallbacks}
showClosed={showClosed}
/> />
); );
}; };
const usePositionsStore = create<DataGridSlice>()( type PositionsStoreSlice = {
persist(createDataGridSlice, { showClosedMarkets: boolean;
toggleClosedMarkets: () => void;
};
const createPositionStoreSlice: StateCreator<PositionsStoreSlice> = (set) => ({
showClosedMarkets: false,
toggleClosedMarkets: () => {
set((curr) => {
return {
showClosedMarkets: !curr.showClosedMarkets,
};
});
},
});
export const usePositionsStore = create<PositionsStoreSlice & DataGridSlice>()(
persist(
(...args) => ({
...createPositionStoreSlice(...args),
...createDataGridSlice(...args),
}),
{
name: 'vega_positions_store', name: 'vega_positions_store',
}) }
)
); );

View File

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

View File

@ -0,0 +1,18 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { usePositionsStore } from '../positions-container';
export const PositionsMenu = () => {
const showClosed = usePositionsStore((store) => store.showClosedMarkets);
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={toggle}
>
{showClosed ? t('Hide closed markets') : t('Show closed markets')}
</TradingButton>
);
};

View File

@ -2,7 +2,12 @@ import * as Schema from '@vegaprotocol/types';
import type { Account } from '@vegaprotocol/accounts'; import type { Account } from '@vegaprotocol/accounts';
import type { MarketWithData } from '@vegaprotocol/markets'; import type { MarketWithData } from '@vegaprotocol/markets';
import type { PositionFieldsFragment } from './__generated__/Positions'; import type { PositionFieldsFragment } from './__generated__/Positions';
import { getMetrics, rejoinPositionData } from './positions-data-providers'; import type { Position } from './positions-data-providers';
import {
getMetrics,
preparePositions,
rejoinPositionData,
} from './positions-data-providers';
import { PositionStatus } from '@vegaprotocol/types'; import { PositionStatus } from '@vegaprotocol/types';
const accounts = [ const accounts = [
@ -223,4 +228,29 @@ describe('getMetrics && rejoinPositionData', () => {
); );
expect(metrics[1].status).toEqual(positions[1].positionStatus); expect(metrics[1].status).toEqual(positions[1].positionStatus);
}); });
it('sorts and filters positions', () => {
const createPosition = (override?: Partial<Position>) =>
({
marketState: Schema.MarketState.STATE_ACTIVE,
marketCode: 'a',
...override,
} as Position);
const data = [
createPosition(),
createPosition({
marketCode: 'c',
marketState: Schema.MarketState.STATE_CANCELLED,
}),
createPosition({ marketCode: 'd' }),
createPosition({ marketCode: 'b' }),
];
const withoutClosed = preparePositions(data, false);
expect(withoutClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'd']);
const withClosed = preparePositions(data, true);
expect(withClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'c', 'd']);
});
}); });

View File

@ -41,6 +41,7 @@ export interface Position {
marketId: string; marketId: string;
marketCode: string; marketCode: string;
marketTradingMode: Schema.MarketTradingMode; marketTradingMode: Schema.MarketTradingMode;
marketState: Schema.MarketState;
markPrice: string | undefined; markPrice: string | undefined;
notional: string | undefined; notional: string | undefined;
openVolume: string; openVolume: string;
@ -119,6 +120,7 @@ export const getMetrics = (
marketId: market.id, marketId: market.id,
marketCode: market.tradableInstrument.instrument.code, marketCode: market.tradableInstrument.instrument.code,
marketTradingMode: market.tradingMode, marketTradingMode: market.tradingMode,
marketState: market.state,
markPrice: marketData ? marketData.markPrice : undefined, markPrice: marketData ? marketData.markPrice : undefined,
notional: notional notional: notional
? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0) ? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
@ -248,6 +250,26 @@ export const rejoinPositionData = (
return null; return null;
}; };
export const preparePositions = (metrics: Position[], showClosed: boolean) => {
return sortBy(metrics, 'marketCode').filter((p) => {
if (showClosed) {
return true;
}
if (
[
Schema.MarketState.STATE_ACTIVE,
Schema.MarketState.STATE_PENDING,
Schema.MarketState.STATE_SUSPENDED,
].includes(p.marketState)
) {
return true;
}
return false;
});
};
export const positionsMarketsProvider = makeDerivedDataProvider< export const positionsMarketsProvider = makeDerivedDataProvider<
string[], string[],
never, never,
@ -265,7 +287,7 @@ export const positionsMarketsProvider = makeDerivedDataProvider<
export const positionsMetricsProvider = makeDerivedDataProvider< export const positionsMetricsProvider = makeDerivedDataProvider<
Position[], Position[],
Position[], Position[],
PositionsQueryVariables & { marketIds: string[] } PositionsQueryVariables & { marketIds: string[]; showClosed: boolean }
>( >(
[ [
(callback, client, variables) => (callback, client, variables) =>
@ -281,10 +303,10 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
marketIds: variables.marketIds, marketIds: variables.marketIds,
}), }),
], ],
([positions, accounts, marketsData]) => { ([positions, accounts, marketsData], variables) => {
const positionsData = rejoinPositionData(positions, marketsData); const positionsData = rejoinPositionData(positions, marketsData);
const metrics = getMetrics(positionsData, accounts as Account[] | null); const metrics = getMetrics(positionsData, accounts as Account[] | null);
return sortBy(metrics, 'marketCode'); return preparePositions(metrics, variables.showClosed);
}, },
(data, delta, previousData) => (data, delta, previousData) =>
data.filter((row) => { data.filter((row) => {

View File

@ -16,6 +16,7 @@ interface PositionsManagerProps {
onMarketClick?: (marketId: string) => void; onMarketClick?: (marketId: string) => void;
isReadOnly: boolean; isReadOnly: boolean;
gridProps?: ReturnType<typeof useDataGridEvents>; gridProps?: ReturnType<typeof useDataGridEvents>;
showClosed?: boolean;
} }
export const PositionsManager = ({ export const PositionsManager = ({
@ -23,6 +24,7 @@ export const PositionsManager = ({
onMarketClick, onMarketClick,
isReadOnly, isReadOnly,
gridProps, gridProps,
showClosed = false,
}: PositionsManagerProps) => { }: PositionsManagerProps) => {
const { pubKeys, pubKey } = useVegaWallet(); const { pubKeys, pubKey } = useVegaWallet();
const create = useVegaTransactionStore((store) => store.create); const create = useVegaTransactionStore((store) => store.create);
@ -60,7 +62,7 @@ export const PositionsManager = ({
const { data, error } = useDataProvider({ const { data, error } = useDataProvider({
dataProvider: positionsMetricsProvider, dataProvider: positionsMetricsProvider,
variables: { partyIds, marketIds: marketIds || [] }, variables: { partyIds, marketIds: marketIds || [], showClosed },
skip: !marketIds, skip: !marketIds,
}); });
@ -68,7 +70,7 @@ export const PositionsManager = ({
<PositionsTable <PositionsTable
pubKey={pubKey} pubKey={pubKey}
pubKeys={pubKeys} pubKeys={pubKeys}
rowData={error ? [] : data} rowData={data}
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onClose={onClose} onClose={onClose}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}

View File

@ -27,6 +27,7 @@ const singleRow: Position = {
marketId: 'string', marketId: 'string',
marketCode: 'ETHBTC.QM21', marketCode: 'ETHBTC.QM21',
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
marketState: Schema.MarketState.STATE_ACTIVE,
markPrice: '123', markPrice: '123',
notional: '12300', notional: '12300',
openVolume: '100', openVolume: '100',

View File

@ -473,8 +473,8 @@ export const PositionsTable = ({
</div> </div>
); );
}, },
minWidth: 75, minWidth: 55,
maxWidth: 75, maxWidth: 55,
} }
: null, : null,
]; ];

View File

@ -51,4 +51,8 @@
- **Must** be able to see if your realised PnL was affected by loss socialisation (<a name="7004-POSI-018" href="#7004-POSI-018">7004-POSI-018</a>) - **Must** be able to see if your realised PnL was affected by loss socialisation (<a name="7004-POSI-018" href="#7004-POSI-018">7004-POSI-018</a>)
- **Must** Must be able to see what type of product the position was opened on (<a name="7004-POSI-019" href="#7004-POSI-019">7004-POSI-019</a>) - **Must** be able to see what type of product the position was opened on (<a name="7004-POSI-019" href="#7004-POSI-019">7004-POSI-019</a>)
- **Must** not see positions on markets which are closed (<a name="7004-POSI-020" href="#7004-POSI-020">7004-POSI-020</a>)
- **Must** be able to show closed markets (<a name="7004-POSI-021" href="#7004-POSI-021">7004-POSI-021</a>)