feat(trading): distinguish between product types (#4543)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
e3ed71bec4
commit
9fcf286e4b
@ -256,6 +256,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="code"]')
|
||||
.find('[data-testid="market-code"]')
|
||||
.should('have.text', settledMarket.tradableInstrument.instrument.code);
|
||||
|
||||
// 6001-MARK-002
|
||||
|
@ -4,7 +4,8 @@ import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row';
|
||||
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||
const colInstrumentCode =
|
||||
'[col-id="tradableInstrument.instrument.code"] [data-testid="market-code"]';
|
||||
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
|
@ -40,21 +40,25 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
code: 'SOLUSD',
|
||||
markPrice: '84.41',
|
||||
vol: '0.00',
|
||||
productType: 'Futr',
|
||||
},
|
||||
{
|
||||
code: 'ETHBTC.QM21',
|
||||
markPrice: '46,126.90058',
|
||||
vol: '0.00',
|
||||
productType: 'Futr',
|
||||
},
|
||||
{
|
||||
code: 'BTCUSD.MF21',
|
||||
markPrice: '46,126.90058',
|
||||
vol: '0.00',
|
||||
productType: 'Futr',
|
||||
},
|
||||
{
|
||||
code: 'AAPL.MF21',
|
||||
markPrice: '46,126.90058',
|
||||
vol: '0.00',
|
||||
productType: 'Futr',
|
||||
},
|
||||
];
|
||||
cy.getByTestId('header-title').should('be.visible').click();
|
||||
@ -64,7 +68,9 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
const market = data[i];
|
||||
// 6001-MARK-021
|
||||
// 6001-MARK-022
|
||||
expect(item.find('h3').text()).equals(market.code);
|
||||
expect(item.find('h3').text()).equals(
|
||||
`${market.code} ${market.productType}`
|
||||
);
|
||||
expect(
|
||||
item.find('[data-testid="market-selector-volume"]').text()
|
||||
).contains(market.vol);
|
||||
|
@ -3,7 +3,7 @@ import type { ProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row';
|
||||
const colMarketId = '[col-id="market"]';
|
||||
const colMarketId = '[col-id="market"] [data-testid="market-code"]';
|
||||
|
||||
describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
@ -155,7 +155,13 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
'AAVEDAI.MF21',
|
||||
'AAPL.MF21',
|
||||
];
|
||||
checkSorting('market', marketColDefault, marketColAsc, marketColDesc);
|
||||
checkSorting(
|
||||
'market',
|
||||
marketColDefault,
|
||||
marketColAsc,
|
||||
marketColDesc,
|
||||
' [data-testid="market-code"]'
|
||||
);
|
||||
|
||||
const stateColDefault = [
|
||||
'Open',
|
||||
|
@ -120,7 +120,9 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.getByTestId('tab-orders')
|
||||
.get(`.ag-center-cols-container [col-id='${orderSymbol}']`)
|
||||
.get(
|
||||
`.ag-center-cols-container [col-id='${orderSymbol}'] [data-testid="market-code"]`
|
||||
)
|
||||
.should('have.length.at.least', expectedOrderList.length)
|
||||
.then(($symbols) => {
|
||||
const symbolNames: string[] = [];
|
||||
|
@ -166,7 +166,8 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
'marketName',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
marketsSortedDesc,
|
||||
' [data-testid="market-code"]'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -214,7 +214,6 @@ describe('Closed', () => {
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
// screen.debug(document, Infinity);
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
const expectedHeaders = [
|
||||
@ -434,7 +433,7 @@ describe('Closed', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'SuccessorCode' })
|
||||
screen.getByRole('button', { name: /^SuccessorCode/ })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
@ -443,6 +442,16 @@ describe('Closed', () => {
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
screen
|
||||
.getAllByRole('gridcell', {
|
||||
name: (_name, element) =>
|
||||
element.getAttribute('col-id') === 'successorMarket',
|
||||
})
|
||||
.forEach((element) => {
|
||||
expect(element.querySelector('[title="Future"]')?.textContent).toEqual(
|
||||
'Futr'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('feature flag should hide successors', async () => {
|
||||
|
@ -4,7 +4,11 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
COL_DEFS,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
@ -47,6 +51,7 @@ interface Row {
|
||||
setlementDataSourceFilter: DataSourceFilterFragment | undefined;
|
||||
tradingTerminationOracleId: string;
|
||||
settlementAsset: SettlementAsset;
|
||||
productType: string;
|
||||
}
|
||||
|
||||
export const Closed = () => {
|
||||
@ -90,6 +95,7 @@ export const Closed = () => {
|
||||
tradingTerminationOracleId:
|
||||
instrument.product.dataSourceSpecForTradingTermination.id,
|
||||
settlementAsset: instrument.product.settlementAsset,
|
||||
productType: instrument.product.__typename || '',
|
||||
};
|
||||
|
||||
return row;
|
||||
@ -115,16 +121,7 @@ const ClosedMarketsDataGrid = ({
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<Row, 'code'>) => {
|
||||
return (
|
||||
<span data-testid="market-code" data-market-id={data?.id}>
|
||||
{value}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
cellRenderer: 'MarketNameCell',
|
||||
},
|
||||
{
|
||||
headerName: t('Description'),
|
||||
@ -279,7 +276,7 @@ const ClosedMarketsDataGrid = ({
|
||||
rowData={rowData}
|
||||
columnDefs={colDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ SuccessorMarketRenderer }}
|
||||
components={{ SuccessorMarketRenderer, MarketNameCell }}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
/>
|
||||
);
|
||||
|
190
apps/trading/client-pages/markets/successor-market-cell.spec.tsx
Normal file
190
apps/trading/client-pages/markets/successor-market-cell.spec.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
import { render, screen, act, waitFor } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
import {
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
|
||||
const mockSuccessorsQuery = [
|
||||
{
|
||||
id: 'market1',
|
||||
parentMarketID: 'parentMarket1',
|
||||
successorMarketID: 'successorMarket1',
|
||||
},
|
||||
{ id: 'market2', parentMarketID: 'parentMarket2' },
|
||||
{ id: 'market3', successorMarketID: 'successorMarket3' },
|
||||
];
|
||||
const parentMarket1 = {
|
||||
id: 'parentMarket1',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code parent 1', id: '1' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const successorMarket1 = {
|
||||
id: 'successorMarket1',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code successor 1', id: '2' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const parentMarket2 = {
|
||||
id: 'parentMarket2',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code parent 2', id: '3' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
const successorMarket3 = {
|
||||
id: 'successorMarket3',
|
||||
tradableInstrument: {
|
||||
instrument: { code: 'code successor 3', id: '4' },
|
||||
},
|
||||
} as unknown as Market;
|
||||
|
||||
const mockMarkets = [
|
||||
parentMarket1,
|
||||
successorMarket1,
|
||||
parentMarket2,
|
||||
successorMarket3,
|
||||
];
|
||||
|
||||
const mockClickHandler = jest.fn();
|
||||
jest.mock('../../lib/hooks/use-market-click-handler', () => ({
|
||||
useMarketClickHandler: jest.fn().mockImplementation(() => mockClickHandler),
|
||||
}));
|
||||
|
||||
const marketMock = {
|
||||
request: {
|
||||
query: MarketsDocument,
|
||||
variables: undefined,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: mockMarkets.map((item) => ({
|
||||
node: {
|
||||
...createMarketFragment(item),
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const successorMock = {
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: mockSuccessorsQuery.map((item) => ({
|
||||
node: {
|
||||
...item,
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mocks = [marketMock, successorMock];
|
||||
|
||||
describe('SuccessorMarketRenderer', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should properly rendered successor market', async () => {
|
||||
const successorValue = 'market1';
|
||||
render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code successor 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket1', false);
|
||||
});
|
||||
});
|
||||
it('should properly rendered parent market', async () => {
|
||||
const successorValue = 'market1';
|
||||
render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code parent 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket1', false);
|
||||
});
|
||||
});
|
||||
it('should properly rendered only parent market', async () => {
|
||||
const successorValue = 'market2';
|
||||
const { rerender } = render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code parent 2')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('parentMarket2', false);
|
||||
});
|
||||
|
||||
rerender(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
it('should properly rendered only successor market', async () => {
|
||||
const successorValue = 'market3';
|
||||
const { rerender } = render(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} />
|
||||
</MockedProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-code')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('code successor 3')).toBeInTheDocument();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockClickHandler).toHaveBeenCalledWith('successorMarket3', false);
|
||||
});
|
||||
|
||||
await act(() => {
|
||||
rerender(
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<SuccessorMarketRenderer value={successorValue} parent />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { MarketNameCell } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketProvider, useSuccessorMarketIds } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import React from 'react';
|
||||
|
||||
export const SuccessorMarketRenderer = ({
|
||||
value,
|
||||
@ -33,6 +33,7 @@ export const SuccessorMarketRenderer = ({
|
||||
value={data.tradableInstrument.instrument.code}
|
||||
data={data}
|
||||
onMarketClick={onMarketClick}
|
||||
productType={data.tradableInstrument.instrument?.product.__typename}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
|
@ -99,6 +99,7 @@ describe('MarketSelectorItem', () => {
|
||||
currentMarketId={market.id}
|
||||
style={{}}
|
||||
onSelect={jest.fn()}
|
||||
allProducts
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
@ -181,5 +182,7 @@ describe('MarketSelectorItem', () => {
|
||||
addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -12,17 +12,20 @@ import {
|
||||
MarketTradingModeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketProductPill } from '@vegaprotocol/datagrid';
|
||||
|
||||
export const MarketSelectorItem = ({
|
||||
market,
|
||||
style,
|
||||
currentMarketId,
|
||||
onSelect,
|
||||
allProducts,
|
||||
}: {
|
||||
market: MarketMaybeWithDataAndCandles;
|
||||
style: CSSProperties;
|
||||
currentMarketId?: string;
|
||||
onSelect: (marketId: string) => void;
|
||||
allProducts: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div style={style} role="row">
|
||||
@ -36,13 +39,19 @@ export const MarketSelectorItem = ({
|
||||
})}
|
||||
onClick={() => onSelect(market.id)}
|
||||
>
|
||||
<MarketData market={market} />
|
||||
<MarketData market={market} allProducts={allProducts} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
const MarketData = ({
|
||||
market,
|
||||
allProducts,
|
||||
}: {
|
||||
market: MarketMaybeWithDataAndCandles;
|
||||
allProducts: boolean;
|
||||
}) => {
|
||||
const { data } = useMarketDataUpdateSubscription({
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
@ -84,7 +93,14 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
<>
|
||||
<div className="w-2/5" role="gridcell">
|
||||
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
||||
{market.tradableInstrument.instrument.code}
|
||||
{market.tradableInstrument.instrument.code}{' '}
|
||||
{allProducts && (
|
||||
<MarketProductPill
|
||||
productType={
|
||||
market.tradableInstrument.instrument.product.__typename
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</h3>
|
||||
{mode && (
|
||||
<p className="text-xs text-vega-orange-500 dark:text-vega-orange-550 whitespace-nowrap">
|
||||
|
@ -43,7 +43,7 @@ export const MarketSelector = ({
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
|
||||
const allProducts = filter.product === Product.All;
|
||||
const { markets, data, loading, error } = useMarketSelectorList(filter);
|
||||
|
||||
return (
|
||||
@ -128,6 +128,7 @@ export const MarketSelector = ({
|
||||
? t('Spot markets coming soon.')
|
||||
: t('No markets')
|
||||
}
|
||||
allProducts={allProducts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -141,6 +142,7 @@ const MarketList = ({
|
||||
currentMarketId,
|
||||
onSelect,
|
||||
noItems,
|
||||
allProducts,
|
||||
}: {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
error: Error | undefined;
|
||||
@ -149,6 +151,7 @@ const MarketList = ({
|
||||
currentMarketId?: string;
|
||||
onSelect: (marketId: string) => void;
|
||||
noItems: string;
|
||||
allProducts: boolean;
|
||||
}) => {
|
||||
const itemSize = 45;
|
||||
const listRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -192,6 +195,7 @@ const MarketList = ({
|
||||
currentMarketId={currentMarketId}
|
||||
onSelect={onSelect}
|
||||
noItems={noItems}
|
||||
allProducts={allProducts}
|
||||
/>
|
||||
</div>
|
||||
</TinyScroll>
|
||||
@ -202,6 +206,7 @@ interface ListItemData {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
onSelect: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
allProducts: boolean;
|
||||
}
|
||||
|
||||
const ListItem = ({
|
||||
@ -218,6 +223,7 @@ const ListItem = ({
|
||||
currentMarketId={data.currentMarketId}
|
||||
style={style}
|
||||
onSelect={data.onSelect}
|
||||
allProducts={data.allProducts}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -229,19 +235,21 @@ const List = ({
|
||||
onSelect,
|
||||
noItems,
|
||||
currentMarketId,
|
||||
allProducts,
|
||||
}: ListItemData & {
|
||||
loading: boolean;
|
||||
height: number;
|
||||
itemSize: number;
|
||||
noItems: string;
|
||||
allProducts: boolean;
|
||||
}) => {
|
||||
const itemKey = useCallback(
|
||||
(index: number, data: ListItemData) => data.data[index].id,
|
||||
[]
|
||||
);
|
||||
const itemData = useMemo(
|
||||
() => ({ data, onSelect, currentMarketId }),
|
||||
[data, onSelect, currentMarketId]
|
||||
() => ({ data, onSelect, currentMarketId, allProducts }),
|
||||
[data, onSelect, currentMarketId, allProducts]
|
||||
);
|
||||
if (!data || loading) {
|
||||
return (
|
||||
|
@ -24,6 +24,9 @@ const singleRow = {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
code: 'BTCUSD.MF21',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
},
|
||||
},
|
||||
},
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
@ -120,6 +123,9 @@ describe('BreakdownTable', () => {
|
||||
__typename: 'Instrument',
|
||||
name: 'BTCUSD Monthly (30 Jun 2022)',
|
||||
code: 'BTCUSD.MF21',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -36,14 +36,24 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
valueFormatter: ({
|
||||
minWidth: 200,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
AccountFields,
|
||||
'market.tradableInstrument.instrument.code'
|
||||
>) => {
|
||||
if (!value) return 'None';
|
||||
return value;
|
||||
return value ? (
|
||||
<MarketNameCell
|
||||
value={value}
|
||||
productType={
|
||||
data?.market?.tradableInstrument.instrument.product.__typename
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
'None'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -126,7 +136,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
|
||||
}
|
||||
ref={ref}
|
||||
rowHeight={34}
|
||||
components={{ PriceCell, MarketNameCell, ProgressBarCell }}
|
||||
components={{ PriceCell, ProgressBarCell }}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={coldefs}
|
||||
|
@ -41,21 +41,22 @@ export const checkSorting = (
|
||||
column: string,
|
||||
orderTabDefault: string[],
|
||||
orderTabAsc: string[],
|
||||
orderTabDesc: string[]
|
||||
orderTabDesc: string[],
|
||||
additionalColumnSelector = ''
|
||||
) => {
|
||||
checkSortChange(orderTabDefault, column);
|
||||
checkSortChange(orderTabDefault, column, additionalColumnSelector);
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).last().click();
|
||||
});
|
||||
checkSortChange(orderTabAsc, column);
|
||||
checkSortChange(orderTabAsc, column, additionalColumnSelector);
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
checkSortChange(orderTabDesc, column);
|
||||
checkSortChange(orderTabDesc, column, additionalColumnSelector);
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
@ -63,13 +64,20 @@ export const checkSorting = (
|
||||
});
|
||||
};
|
||||
|
||||
const checkSortChange = (tabsArr: string[], column: string) => {
|
||||
const checkSortChange = (
|
||||
tabsArr: string[],
|
||||
column: string,
|
||||
additionalColumnSelector = ''
|
||||
) => {
|
||||
cy.get('.ag-center-cols-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
tabsArr.forEach((entry, i) => {
|
||||
cy.get(`[row-index="${i}"]`).within(() => {
|
||||
cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]);
|
||||
cy.get(`[col-id="${column}"]${additionalColumnSelector}`).should(
|
||||
'have.text',
|
||||
tabsArr[i]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,7 +9,10 @@ export const CenteredGridCellWrapper = ({
|
||||
className?: string;
|
||||
}) => (
|
||||
<div
|
||||
className={classNames('flex h-[20px] p-0 justify-items-center', className)}
|
||||
className={classNames(
|
||||
'flex h-[20px] p-0 justify-items-center items-center',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="w-full self-center">{children}</div>
|
||||
</div>
|
||||
|
@ -1,13 +1,39 @@
|
||||
import type { MouseEvent, ReactNode } from 'react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { Pill } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Market } from '@vegaprotocol/types';
|
||||
|
||||
const productTypeMap = {
|
||||
Future: 'Futr',
|
||||
FutureProduct: 'Futr',
|
||||
Spot: 'Spot',
|
||||
SpotProduct: 'Spot',
|
||||
Perpetual: 'Perp',
|
||||
PerpetualProduct: 'Perp',
|
||||
} as const;
|
||||
export type ProductType = keyof typeof productTypeMap | undefined;
|
||||
|
||||
export const MarketProductPill = ({
|
||||
productType,
|
||||
}: {
|
||||
productType?: ProductType;
|
||||
}) => {
|
||||
return productType ? (
|
||||
<Pill size="xxs" className="uppercase ml-0.5" title={productType}>
|
||||
{productTypeMap[productType] || productType}
|
||||
</Pill>
|
||||
) : null;
|
||||
};
|
||||
|
||||
interface MarketNameCellProps {
|
||||
value?: string | null;
|
||||
data?: { id?: string; marketId?: string; market?: { id: string } };
|
||||
data?:
|
||||
| { id?: string; marketId?: string; productType?: string; market?: Market }
|
||||
| Market;
|
||||
idPath?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
defaultValue?: ReactNode;
|
||||
productType?: ProductType;
|
||||
}
|
||||
|
||||
export const MarketNameCell = ({
|
||||
@ -15,29 +41,43 @@ export const MarketNameCell = ({
|
||||
data,
|
||||
idPath,
|
||||
onMarketClick,
|
||||
productType,
|
||||
}: MarketNameCellProps) => {
|
||||
const id = data ? get(data, idPath ?? 'id', 'all') : '';
|
||||
const handleOnClick = useCallback(
|
||||
(ev: MouseEvent<HTMLButtonElement>) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (onMarketClick) {
|
||||
onMarketClick(id, ev.metaKey || ev.ctrlKey);
|
||||
}
|
||||
id && onMarketClick?.(id, ev.metaKey || ev.ctrlKey);
|
||||
},
|
||||
[id, onMarketClick]
|
||||
);
|
||||
if (!value || !data) return null;
|
||||
return onMarketClick ? (
|
||||
|
||||
productType =
|
||||
productType ||
|
||||
(data as { productType?: ProductType }).productType ||
|
||||
(data as Market)?.tradableInstrument?.instrument.product.__typename ||
|
||||
(data as { market: Market })?.market?.tradableInstrument.instrument.product
|
||||
.__typename;
|
||||
|
||||
if (!value) return;
|
||||
const content = (
|
||||
<>
|
||||
<span data-testid="market-code" data-market-id={id}>
|
||||
{value}
|
||||
</span>
|
||||
<MarketProductPill productType={productType} />
|
||||
</>
|
||||
);
|
||||
return onMarketClick && id ? (
|
||||
<button
|
||||
onClick={handleOnClick}
|
||||
tabIndex={0}
|
||||
className="block text-left text-ellipsis overflow-hidden whitespace-nowrap w-full"
|
||||
>
|
||||
{value}
|
||||
{content}
|
||||
</button>
|
||||
) : (
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
<>{value}</>
|
||||
content
|
||||
);
|
||||
};
|
||||
|
@ -60,7 +60,7 @@ export const ledgerEntriesProvider = makeDerivedDataProvider<
|
||||
(partsData) => {
|
||||
const entries = partsData[0] as ReturnType<typeof getData>;
|
||||
const assets = partsData[1] as Record<string, Asset>;
|
||||
const markets = partsData[1] as Record<string, Market>;
|
||||
const markets = partsData[2] as Record<string, Market>;
|
||||
return entries.map((entry) => {
|
||||
const asset = entry.assetId
|
||||
? (assets as Record<string, Asset>)[entry.assetId]
|
||||
|
@ -12,10 +12,12 @@ import type {
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
DateRangeFilter,
|
||||
MarketNameCell,
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
AccountTypeMapping,
|
||||
DescriptionTransferTypeMapping,
|
||||
@ -82,10 +84,14 @@ export const LedgerTable = (props: LedgerEntryProps) => {
|
||||
field: 'marketSender.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
>) =>
|
||||
value && (
|
||||
<MarketNameCell value={value} data={data?.marketSender as Market} />
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver'),
|
||||
@ -112,10 +118,17 @@ export const LedgerTable = (props: LedgerEntryProps) => {
|
||||
field: 'marketReceiver.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
>) =>
|
||||
value && (
|
||||
<MarketNameCell
|
||||
value={value}
|
||||
data={data?.marketReceiver as Market}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Transfer type'),
|
||||
|
@ -51,6 +51,7 @@ export interface Position {
|
||||
totalBalance: string;
|
||||
unrealisedPNL: string;
|
||||
updatedAt: string | null;
|
||||
productType?: string;
|
||||
}
|
||||
|
||||
export const getMetrics = (
|
||||
@ -135,6 +136,7 @@ export const getMetrics = (
|
||||
totalBalance: totalBalance.multipliedBy(10 ** decimals).toFixed(),
|
||||
unrealisedPNL: position.unrealisedPNL,
|
||||
updatedAt: position.updatedAt || null,
|
||||
productType: market?.tradableInstrument.instrument.product.__typename,
|
||||
});
|
||||
});
|
||||
return metrics;
|
||||
|
@ -37,6 +37,7 @@ const singleRow: Position = {
|
||||
totalBalance: '123456',
|
||||
unrealisedPNL: '456',
|
||||
updatedAt: '2022-07-27T15:02:58.400Z',
|
||||
productType: 'Future',
|
||||
};
|
||||
|
||||
const singleRowData = [singleRow];
|
||||
@ -80,6 +81,7 @@ describe('Positions', () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy();
|
||||
expect(screen.getByText('Futr')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Does not fail if the market name does not match the split pattern', async () => {
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
import { MarketNameProposalCell, useColumnDefs } from './use-column-defs';
|
||||
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { useProposalsListQuery } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
@ -43,7 +43,7 @@ export const ProposalsList = ({
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={({ data }) => data.id}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
components={{ SuccessorMarketRenderer }}
|
||||
components={{ SuccessorMarketRenderer, MarketNameProposalCell }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
CenteredGridCellWrapper,
|
||||
COL_DEFS,
|
||||
DateRangeFilter,
|
||||
MarketProductPill,
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import compact from 'lodash/compact';
|
||||
@ -20,13 +21,42 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { InstrumentConfiguration } from '@vegaprotocol/types';
|
||||
import { ProposalStateMapping } from '@vegaprotocol/types';
|
||||
import type { ProposalListFieldsFragment } from '../../lib/proposals-data-provider/__generated__/Proposals';
|
||||
import { VoteProgress } from '../voting-progress';
|
||||
import { ProposalActionsDropdown } from '../proposal-actions-dropdown';
|
||||
import { getMarketProductType } from '../../utils/get-market-product-type';
|
||||
|
||||
export const MarketNameProposalCell = ({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.change.instrument.code'
|
||||
>) => {
|
||||
const { VEGA_TOKEN_URL } = useEnvironment();
|
||||
const { change } = data?.terms || {};
|
||||
if (change?.__typename === 'NewMarket' && VEGA_TOKEN_URL) {
|
||||
const productType = getMarketProductType(
|
||||
change.instrument as InstrumentConfiguration
|
||||
);
|
||||
const content = (
|
||||
<>
|
||||
<span data-testid="market-code">{value as string}</span>
|
||||
<MarketProductPill productType={productType} />
|
||||
</>
|
||||
);
|
||||
if (data?.id) {
|
||||
const link = `${VEGA_TOKEN_URL}/proposals/${data.id}`;
|
||||
return <ExternalLink href={link}>{content}</ExternalLink>;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useColumnDefs = () => {
|
||||
const { VEGA_TOKEN_URL } = useEnvironment();
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
]);
|
||||
@ -43,26 +73,7 @@ export const useColumnDefs = () => {
|
||||
headerName: t('Market'),
|
||||
field: 'terms.change.instrument.code',
|
||||
cellStyle: { lineHeight: '14px' },
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
ProposalListFieldsFragment,
|
||||
'terms.change.instrument.code'
|
||||
>) => {
|
||||
const { change } = data?.terms || {};
|
||||
if (change?.__typename === 'NewMarket' && VEGA_TOKEN_URL) {
|
||||
if (data?.id) {
|
||||
const link = `${VEGA_TOKEN_URL}/proposals/${data.id}`;
|
||||
return (
|
||||
<ExternalLink href={link}>
|
||||
{change.instrument.code}
|
||||
</ExternalLink>
|
||||
);
|
||||
}
|
||||
return change.instrument.code;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
cellRenderer: 'MarketNameProposalCell',
|
||||
},
|
||||
{
|
||||
colId: 'description',
|
||||
@ -155,7 +166,7 @@ export const useColumnDefs = () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
}, [VEGA_TOKEN_URL, requiredMajorityPercentage]);
|
||||
}, [requiredMajorityPercentage]);
|
||||
|
||||
const defaultColDef: ColDef = useMemo(() => {
|
||||
return {
|
||||
|
58
libs/proposals/src/utils/get-market-product-type.spec.ts
Normal file
58
libs/proposals/src/utils/get-market-product-type.spec.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { InstrumentConfiguration } from '@vegaprotocol/types';
|
||||
import { getMarketProductType } from './get-market-product-type';
|
||||
|
||||
describe('getMarketProductType', () => {
|
||||
it('should resolve product type properly', () => {
|
||||
expect(
|
||||
getMarketProductType({
|
||||
futureProduct: {
|
||||
quoteName: 'Market 1',
|
||||
},
|
||||
} as InstrumentConfiguration)
|
||||
).toEqual('Future');
|
||||
expect(
|
||||
getMarketProductType({
|
||||
spotProduct: {
|
||||
quoteName: 'Market 1',
|
||||
},
|
||||
} as unknown as InstrumentConfiguration)
|
||||
).toEqual('Spot');
|
||||
expect(
|
||||
getMarketProductType({
|
||||
perpetualProduct: {
|
||||
quoteName: 'Market 1',
|
||||
},
|
||||
} as unknown as InstrumentConfiguration)
|
||||
).toEqual('Perpetual');
|
||||
expect(
|
||||
getMarketProductType({
|
||||
product: {
|
||||
__typename: 'Perpetual',
|
||||
},
|
||||
futureProduct: {
|
||||
quoteName: 'Market 1',
|
||||
},
|
||||
} as unknown as InstrumentConfiguration)
|
||||
).toEqual('Perpetual');
|
||||
expect(
|
||||
getMarketProductType({
|
||||
product: {
|
||||
__typename: 'Spot',
|
||||
},
|
||||
futureProduct: {
|
||||
quoteName: 'Market 1',
|
||||
},
|
||||
} as unknown as InstrumentConfiguration)
|
||||
).toEqual('Spot');
|
||||
expect(
|
||||
getMarketProductType({
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
},
|
||||
perpetualProduct: {
|
||||
quoteName: 'Market 1',
|
||||
},
|
||||
} as unknown as InstrumentConfiguration)
|
||||
).toEqual('Future');
|
||||
});
|
||||
});
|
16
libs/proposals/src/utils/get-market-product-type.ts
Normal file
16
libs/proposals/src/utils/get-market-product-type.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { InstrumentConfiguration, Product } from '@vegaprotocol/types';
|
||||
|
||||
// it needs to be adjusted after deploy this https://github.com/vegaprotocol/vega/pull/9003
|
||||
export const getMarketProductType = (
|
||||
instrumentConfiguration: InstrumentConfiguration
|
||||
) => {
|
||||
return 'product' in instrumentConfiguration
|
||||
? (instrumentConfiguration.product as Product).__typename
|
||||
: 'futureProduct' in instrumentConfiguration
|
||||
? 'Future'
|
||||
: 'spotProduct' in instrumentConfiguration
|
||||
? 'Spot'
|
||||
: 'perpetualProduct' in instrumentConfiguration
|
||||
? 'Perpetual'
|
||||
: undefined;
|
||||
};
|
@ -1,18 +1,18 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ReactNode, HTMLProps } from 'react';
|
||||
import { Intent } from '../../utils/intent';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Size = 'lg' | 'md' | 'sm' | 'xs' | 'xxs';
|
||||
interface Props {
|
||||
interface Props extends Omit<HTMLProps<HTMLSpanElement>, 'size'> {
|
||||
children: ReactNode;
|
||||
intent?: Intent;
|
||||
size?: Size;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const getClasses = (size: Size, intent?: Intent, className?: string) => {
|
||||
const getClasses = (size: Size, intent: Intent, className?: string) => {
|
||||
return classNames(
|
||||
['rounded-md', 'leading-none', 'font-alpha', 'py-1 px-2'],
|
||||
['rounded-md', 'leading-none', 'font-alpha'],
|
||||
{
|
||||
'bg-vega-yellow dark:bg-vega-yellow': intent === Intent.Primary,
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': intent === Intent.None,
|
||||
@ -25,19 +25,28 @@ const getClasses = (size: Size, intent?: Intent, className?: string) => {
|
||||
intent === Intent.Primary,
|
||||
},
|
||||
{
|
||||
'text-lg': size === 'lg',
|
||||
'text-base': size === 'md',
|
||||
'text-sma': size === 'sm',
|
||||
'text-xs': size === 'xs',
|
||||
'text-[10px]': size === 'xxs',
|
||||
'text-lg py-1 px-2': size === 'lg',
|
||||
'text-base py-1 px-2': size === 'md',
|
||||
'text-sm py-1 px-1': size === 'sm',
|
||||
'text-xs py-1 px-1': size === 'xs',
|
||||
'text-[10px] py-0 px-1': size === 'xxs',
|
||||
},
|
||||
className
|
||||
);
|
||||
};
|
||||
|
||||
export const Pill = ({ intent, size, className, children }: Props) => {
|
||||
export const Pill = ({
|
||||
intent,
|
||||
size,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: Props) => {
|
||||
return (
|
||||
<span className={getClasses(size || 'md', intent, className)}>
|
||||
<span
|
||||
className={getClasses(size || 'md', intent || Intent.None, className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user