chore(trading): market relationship - parent market in proposals list (#4339)
Co-authored-by: asiaznik <artur@vegaprotocol.io> Co-authored-by: Edd <edd@vega.xyz>
This commit is contained in:
parent
caa0076396
commit
d28db485c5
@ -363,12 +363,11 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
|||||||
.first()
|
.first()
|
||||||
.find('button svg')
|
.find('button svg')
|
||||||
.should('exist');
|
.should('exist');
|
||||||
|
|
||||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
||||||
cy.get(rowSelector)
|
cy.get(rowSelector)
|
||||||
.find('[col-id="successorMarketID"]')
|
.find('[col-id="successorMarket"]')
|
||||||
.first()
|
.first()
|
||||||
.should('have.text', ' - ');
|
.should('have.text', '-');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
|||||||
'Description',
|
'Description',
|
||||||
'Trading mode',
|
'Trading mode',
|
||||||
'Status',
|
'Status',
|
||||||
|
'Successor market',
|
||||||
'Best bid',
|
'Best bid',
|
||||||
'Best offer',
|
'Best offer',
|
||||||
'Mark price',
|
'Mark price',
|
||||||
|
@ -19,6 +19,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
|||||||
'Description',
|
'Description',
|
||||||
'Settlement asset',
|
'Settlement asset',
|
||||||
'State',
|
'State',
|
||||||
|
'Parent market',
|
||||||
'Voting',
|
'Voting',
|
||||||
'Closing date',
|
'Closing date',
|
||||||
'Enactment date',
|
'Enactment date',
|
||||||
|
@ -10,15 +10,18 @@ import type {
|
|||||||
OracleSpecDataConnectionQuery,
|
OracleSpecDataConnectionQuery,
|
||||||
MarketsDataQuery,
|
MarketsDataQuery,
|
||||||
MarketsQuery,
|
MarketsQuery,
|
||||||
|
SuccessorMarketIdsQuery,
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import {
|
import {
|
||||||
OracleSpecDataConnectionDocument,
|
OracleSpecDataConnectionDocument,
|
||||||
MarketsDataDocument,
|
MarketsDataDocument,
|
||||||
MarketsDocument,
|
MarketsDocument,
|
||||||
|
SuccessorMarketIdsDocument,
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import {
|
import {
|
||||||
createMarketFragment,
|
createMarketFragment,
|
||||||
marketsQuery,
|
marketsQuery,
|
||||||
@ -51,6 +54,17 @@ jest.mock('@vegaprotocol/environment', () => ({
|
|||||||
FLAGS: { SUCCESSOR_MARKETS: true } as Partial<FeatureFlags>,
|
FLAGS: { SUCCESSOR_MARKETS: true } as Partial<FeatureFlags>,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/environment', () => {
|
||||||
|
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
FLAGS: {
|
||||||
|
...actual.FLAGS,
|
||||||
|
SUCCESSOR_MARKETS: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('Closed', () => {
|
describe('Closed', () => {
|
||||||
let originalNow: typeof Date.now;
|
let originalNow: typeof Date.now;
|
||||||
const mockNowTimestamp = 1672531200000;
|
const mockNowTimestamp = 1672531200000;
|
||||||
@ -349,8 +363,25 @@ describe('Closed', () => {
|
|||||||
state: MarketState.STATE_SETTLED,
|
state: MarketState.STATE_SETTLED,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: {
|
||||||
|
...createMarketFragment({
|
||||||
|
id: 'successorMarketID',
|
||||||
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
|
tradableInstrument: {
|
||||||
|
...createMarketFragment().tradableInstrument,
|
||||||
|
instrument: {
|
||||||
|
...createMarketFragment().tradableInstrument.instrument,
|
||||||
|
id: 'successorAssset',
|
||||||
|
name: 'Successor Market Name',
|
||||||
|
code: 'SuccessorCode',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: MarketsDocument,
|
query: MarketsDocument,
|
||||||
@ -364,11 +395,36 @@ describe('Closed', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: SuccessorMarketIdsDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
marketsConnection: {
|
||||||
|
__typename: 'MarketConnection',
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'include-0',
|
||||||
|
successorMarketID: 'successorMarketID',
|
||||||
|
parentMarketID: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<MockedProvider
|
<MockedProvider
|
||||||
mocks={[mixedMarketsMock, marketsDataMock, oracleDataMock]}
|
mocks={[
|
||||||
|
mixedMarketsMock,
|
||||||
|
marketsDataMock,
|
||||||
|
oracleDataMock,
|
||||||
|
successorMarketsMock,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<VegaWalletContext.Provider
|
<VegaWalletContext.Provider
|
||||||
value={{ pubKey } as VegaWalletContextShape}
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
@ -384,5 +440,107 @@ describe('Closed', () => {
|
|||||||
screen.getByRole('button', { name: 'SuccessorCode' })
|
screen.getByRole('button', { name: 'SuccessorCode' })
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
expect(
|
||||||
|
screen.getByRole('columnheader', {
|
||||||
|
name: (_name, element) =>
|
||||||
|
element.getAttribute('col-id') === 'successorMarket',
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('feature flag should hide successors', async () => {
|
||||||
|
const mockedFlags = jest.mocked(FLAGS);
|
||||||
|
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||||
|
|
||||||
|
const mixedMarkets = [
|
||||||
|
{
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: createMarketFragment({
|
||||||
|
id: 'include-0',
|
||||||
|
state: MarketState.STATE_SETTLED,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: 'MarketEdge' as const,
|
||||||
|
node: {
|
||||||
|
...createMarketFragment({
|
||||||
|
id: 'successorMarketID',
|
||||||
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
}),
|
||||||
|
tradableInstrument: {
|
||||||
|
...createMarketFragment().tradableInstrument,
|
||||||
|
instrument: {
|
||||||
|
...createMarketFragment().tradableInstrument.instrument,
|
||||||
|
id: 'successorAssset',
|
||||||
|
name: 'Successor Market Name',
|
||||||
|
code: 'SuccessorCode',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const mixedMarketsMock: MockedResponse<MarketsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketsDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
marketsConnection: {
|
||||||
|
__typename: 'MarketConnection',
|
||||||
|
edges: mixedMarkets,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const successorMarketsMock: MockedResponse<SuccessorMarketIdsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: SuccessorMarketIdsDocument,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
marketsConnection: {
|
||||||
|
__typename: 'MarketConnection',
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'include-0',
|
||||||
|
successorMarketID: 'successorMarketID',
|
||||||
|
parentMarketID: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider
|
||||||
|
mocks={[
|
||||||
|
mixedMarketsMock,
|
||||||
|
marketsDataMock,
|
||||||
|
oracleDataMock,
|
||||||
|
successorMarketsMock,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<VegaWalletContext.Provider
|
||||||
|
value={{ pubKey } as VegaWalletContextShape}
|
||||||
|
>
|
||||||
|
<Closed />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
screen.getByRole('columnheader', {
|
||||||
|
name: (_name, element) =>
|
||||||
|
element.getAttribute('col-id') === 'settlementDate',
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
screen.getAllByRole('columnheader').forEach((element) => {
|
||||||
|
expect(element.getAttribute('col-id')).not.toEqual('successorMarket');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,11 +4,7 @@ import type {
|
|||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
VegaValueFormatterParams,
|
VegaValueFormatterParams,
|
||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import {
|
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||||
AgGridLazy as AgGrid,
|
|
||||||
COL_DEFS,
|
|
||||||
MarketNameCell,
|
|
||||||
} from '@vegaprotocol/datagrid';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||||
@ -23,15 +19,14 @@ import type {
|
|||||||
import {
|
import {
|
||||||
MarketActionsDropdown,
|
MarketActionsDropdown,
|
||||||
closedMarketsWithDataProvider,
|
closedMarketsWithDataProvider,
|
||||||
useSuccessorMarket,
|
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
import type { ColDef } from 'ag-grid-community';
|
import type { ColDef } from 'ag-grid-community';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import { SettlementDateCell } from './settlement-date-cell';
|
import { SettlementDateCell } from './settlement-date-cell';
|
||||||
import { SettlementPriceCell } from './settlement-price-cell';
|
import { SettlementPriceCell } from './settlement-price-cell';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
|
||||||
|
|
||||||
type SettlementAsset =
|
type SettlementAsset =
|
||||||
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||||
@ -106,22 +101,6 @@ export const Closed = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SuccessorMarketRenderer = ({
|
|
||||||
value,
|
|
||||||
}: VegaICellRendererParams<Row, 'id'>) => {
|
|
||||||
const { data } = useSuccessorMarket(value);
|
|
||||||
const onMarketClick = useMarketClickHandler();
|
|
||||||
return data ? (
|
|
||||||
<MarketNameCell
|
|
||||||
value={data.tradableInstrument.instrument.code}
|
|
||||||
data={data}
|
|
||||||
onMarketClick={onMarketClick}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
' - '
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ClosedMarketsDataGrid = ({
|
const ClosedMarketsDataGrid = ({
|
||||||
rowData,
|
rowData,
|
||||||
error,
|
error,
|
||||||
@ -202,8 +181,8 @@ const ClosedMarketsDataGrid = ({
|
|||||||
},
|
},
|
||||||
FLAGS.SUCCESSOR_MARKETS && {
|
FLAGS.SUCCESSOR_MARKETS && {
|
||||||
headerName: t('Successor market'),
|
headerName: t('Successor market'),
|
||||||
colId: 'successorMarketID',
|
|
||||||
field: 'id',
|
field: 'id',
|
||||||
|
colId: 'successorMarket',
|
||||||
cellRenderer: 'SuccessorMarketRenderer',
|
cellRenderer: 'SuccessorMarketRenderer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { MarketsContainer } from '@vegaprotocol/markets';
|
import { MarketsContainer } from '@vegaprotocol/markets';
|
||||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
|
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||||
|
|
||||||
export const Markets = () => {
|
export const Markets = () => {
|
||||||
const handleOnSelect = useMarketClickHandler();
|
const handleOnSelect = useMarketClickHandler();
|
||||||
return <MarketsContainer onSelect={handleOnSelect} />;
|
return (
|
||||||
|
<MarketsContainer
|
||||||
|
onSelect={handleOnSelect}
|
||||||
|
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from '@vegaprotocol/environment';
|
} from '@vegaprotocol/environment';
|
||||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||||
|
|
||||||
export const Proposed = () => {
|
export const Proposed = () => {
|
||||||
const tokenLink = useLinks(DApp.Token);
|
const tokenLink = useLinks(DApp.Token);
|
||||||
@ -13,7 +14,7 @@ export const Proposed = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-[400px]">
|
<div className="h-[400px]">
|
||||||
<ProposalsList />
|
<ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />
|
||||||
</div>
|
</div>
|
||||||
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
|
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
|
||||||
{t('Propose a new market')}
|
{t('Propose a new market')}
|
||||||
|
40
apps/trading/client-pages/markets/successor-market-cell.tsx
Normal file
40
apps/trading/client-pages/markets/successor-market-cell.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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,
|
||||||
|
parent,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
parent?: boolean;
|
||||||
|
}) => {
|
||||||
|
const successors = useSuccessorMarketIds(value);
|
||||||
|
const onMarketClick = useMarketClickHandler();
|
||||||
|
|
||||||
|
const lookupValue = successors
|
||||||
|
? parent
|
||||||
|
? successors.parentMarketID
|
||||||
|
: successors.successorMarketID
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const { data } = useDataProvider({
|
||||||
|
dataProvider: marketProvider,
|
||||||
|
variables: {
|
||||||
|
marketId: lookupValue || '',
|
||||||
|
},
|
||||||
|
skip: !lookupValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data ? (
|
||||||
|
<MarketNameCell
|
||||||
|
value={data.tradableInstrument.instrument.code}
|
||||||
|
data={data}
|
||||||
|
onMarketClick={onMarketClick}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
);
|
||||||
|
};
|
@ -16,6 +16,7 @@ query SuccessorMarketIds {
|
|||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
successorMarketID
|
successorMarketID
|
||||||
|
parentMarketID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export type ParentMarketIdQuery = { __typename?: 'Query', market?: { __typename?
|
|||||||
export type SuccessorMarketIdsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
export type SuccessorMarketIdsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type SuccessorMarketIdsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, successorMarketID?: string | null } }> } | null };
|
export type SuccessorMarketIdsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, successorMarketID?: string | null, parentMarketID?: string | null } }> } | null };
|
||||||
|
|
||||||
export type SuccessorMarketQueryVariables = Types.Exact<{
|
export type SuccessorMarketQueryVariables = Types.Exact<{
|
||||||
marketId: Types.Scalars['ID'];
|
marketId: Types.Scalars['ID'];
|
||||||
@ -107,6 +107,7 @@ export const SuccessorMarketIdsDocument = gql`
|
|||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
successorMarketID
|
successorMarketID
|
||||||
|
parentMarketID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,4 +183,4 @@ export function useSuccessorMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
|
|||||||
}
|
}
|
||||||
export type SuccessorMarketQueryHookResult = ReturnType<typeof useSuccessorMarketQuery>;
|
export type SuccessorMarketQueryHookResult = ReturnType<typeof useSuccessorMarketQuery>;
|
||||||
export type SuccessorMarketLazyQueryHookResult = ReturnType<typeof useSuccessorMarketLazyQuery>;
|
export type SuccessorMarketLazyQueryHookResult = ReturnType<typeof useSuccessorMarketLazyQuery>;
|
||||||
export type SuccessorMarketQueryResult = Apollo.QueryResult<SuccessorMarketQuery, SuccessorMarketQueryVariables>;
|
export type SuccessorMarketQueryResult = Apollo.QueryResult<SuccessorMarketQuery, SuccessorMarketQueryVariables>;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||||
import {
|
import {
|
||||||
AgGridLazy as AgGrid,
|
AgGridLazy as AgGrid,
|
||||||
@ -48,10 +48,15 @@ export const MarketListTable = forwardRef<
|
|||||||
AgGridReact,
|
AgGridReact,
|
||||||
TypedDataAgGrid<MarketMaybeWithData> & {
|
TypedDataAgGrid<MarketMaybeWithData> & {
|
||||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||||
}
|
}
|
||||||
>(({ onMarketClick, ...props }, ref) => {
|
>(({ onMarketClick, SuccessorMarketRenderer, ...props }, ref) => {
|
||||||
const columnDefs = useColumnDefs({ onMarketClick });
|
const columnDefs = useColumnDefs({ onMarketClick });
|
||||||
|
const components = {
|
||||||
|
PriceFlashCell,
|
||||||
|
MarketName,
|
||||||
|
...(SuccessorMarketRenderer ? { SuccessorMarketRenderer } : null),
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
@ -60,7 +65,7 @@ export const MarketListTable = forwardRef<
|
|||||||
defaultColDef={defaultColDef}
|
defaultColDef={defaultColDef}
|
||||||
columnDefs={columnDefs}
|
columnDefs={columnDefs}
|
||||||
suppressCellFocus
|
suppressCellFocus
|
||||||
components={{ PriceFlashCell, MarketName }}
|
components={components}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,21 @@ import * as DataProviders from '@vegaprotocol/data-provider';
|
|||||||
import { MockedProvider } from '@apollo/react-testing';
|
import { MockedProvider } from '@apollo/react-testing';
|
||||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||||
import { MarketsContainer } from './markets-container';
|
import { MarketsContainer } from './markets-container';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/environment', () => {
|
||||||
|
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
FLAGS: {
|
||||||
|
...actual.FLAGS,
|
||||||
|
SUCCESSOR_MARKETS: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const SuccessorMarketRenderer = ({ value }: { value: string }) => {
|
||||||
|
return '-';
|
||||||
|
};
|
||||||
|
|
||||||
const market = {
|
const market = {
|
||||||
id: 'id-1',
|
id: 'id-1',
|
||||||
@ -22,8 +37,10 @@ const market = {
|
|||||||
} as unknown as MarketMaybeWithData;
|
} as unknown as MarketMaybeWithData;
|
||||||
|
|
||||||
describe('MarketsContainer', () => {
|
describe('MarketsContainer', () => {
|
||||||
it('context menu should stay open', async () => {
|
const spyOnSelect = jest.fn();
|
||||||
const spyOnSelect = jest.fn();
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
jest
|
jest
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||||
@ -34,12 +51,16 @@ describe('MarketsContainer', () => {
|
|||||||
data: [market],
|
data: [market],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
it('context menu should stay open', async () => {
|
||||||
let rerenderRef: (ui: React.ReactElement) => void;
|
let rerenderRef: (ui: React.ReactElement) => void;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
const { rerender } = render(
|
const { rerender } = render(
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<MarketsContainer onSelect={spyOnSelect} />
|
<MarketsContainer
|
||||||
|
onSelect={spyOnSelect}
|
||||||
|
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||||
|
/>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
rerenderRef = rerender;
|
rerenderRef = rerender;
|
||||||
@ -63,7 +84,7 @@ describe('MarketsContainer', () => {
|
|||||||
screen.getByRole('button', {
|
screen.getByRole('button', {
|
||||||
name: (_name, element) =>
|
name: (_name, element) =>
|
||||||
(element.parentNode as Element)?.getAttribute('id') ===
|
(element.parentNode as Element)?.getAttribute('id') ===
|
||||||
'cell-market-actions-8',
|
'cell-market-actions-9',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -104,4 +125,55 @@ describe('MarketsContainer', () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('SuccessorMarketRenderer should be rendered', async () => {
|
||||||
|
const successorMarketName = 'Successor Market Name';
|
||||||
|
const spySuccessorMarketRenderer = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(successorMarketName);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MockedProvider>
|
||||||
|
<MarketsContainer
|
||||||
|
onSelect={spyOnSelect}
|
||||||
|
SuccessorMarketRenderer={spySuccessorMarketRenderer}
|
||||||
|
/>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(spySuccessorMarketRenderer).toHaveBeenCalled();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('columnheader', {
|
||||||
|
name: (_name, element) =>
|
||||||
|
element.getAttribute('col-id') === 'successorMarketID',
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('presentation', {
|
||||||
|
name: (_name, element) =>
|
||||||
|
element.getAttribute('id') === 'cell-successorMarketID-14',
|
||||||
|
})
|
||||||
|
).toHaveTextContent(successorMarketName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('feature flag should hide successorMarketID column', async () => {
|
||||||
|
const mockedFlags = jest.mocked(FLAGS);
|
||||||
|
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||||
|
|
||||||
|
const spySuccessorMarketRenderer = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<MockedProvider>
|
||||||
|
<MarketsContainer
|
||||||
|
onSelect={spyOnSelect}
|
||||||
|
SuccessorMarketRenderer={spySuccessorMarketRenderer}
|
||||||
|
/>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(spySuccessorMarketRenderer).not.toHaveBeenCalled();
|
||||||
|
screen.getAllByRole('columnheader').forEach((element) => {
|
||||||
|
expect(element.getAttribute('col-id')).not.toEqual('successorMarketID');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import type { CellClickedEvent } from 'ag-grid-community';
|
import type { CellClickedEvent } from 'ag-grid-community';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
@ -11,9 +11,13 @@ import type { MarketMaybeWithData } from '../../markets-provider';
|
|||||||
const POLLING_TIME = 2000;
|
const POLLING_TIME = 2000;
|
||||||
interface MarketsContainerProps {
|
interface MarketsContainerProps {
|
||||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
|
export const MarketsContainer = ({
|
||||||
|
onSelect,
|
||||||
|
SuccessorMarketRenderer,
|
||||||
|
}: MarketsContainerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
|
|
||||||
const { data, error, reload } = useDataProvider({
|
const { data, error, reload } = useDataProvider({
|
||||||
@ -58,6 +62,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
|
|||||||
}}
|
}}
|
||||||
onMarketClick={onSelect}
|
onMarketClick={onSelect}
|
||||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||||
|
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { ColDef } from 'ag-grid-community';
|
import type { ColDef } from 'ag-grid-community';
|
||||||
|
import compact from 'lodash/compact';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type {
|
import type {
|
||||||
VegaICellRendererParams,
|
VegaICellRendererParams,
|
||||||
@ -11,6 +12,7 @@ import * as Schema from '@vegaprotocol/types';
|
|||||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||||
import { MarketActionsDropdown } from './market-table-actions';
|
import { MarketActionsDropdown } from './market-table-actions';
|
||||||
|
|
||||||
@ -22,166 +24,183 @@ const { MarketTradingMode, AuctionTrigger } = Schema;
|
|||||||
|
|
||||||
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||||
|
|
||||||
return useMemo<ColDef[]>(
|
return useMemo<ColDef[]>(
|
||||||
() => [
|
() =>
|
||||||
{
|
compact([
|
||||||
headerName: t('Market'),
|
{
|
||||||
field: 'tradableInstrument.instrument.code',
|
headerName: t('Market'),
|
||||||
cellRenderer: 'MarketName',
|
field: 'tradableInstrument.instrument.code',
|
||||||
cellRendererParams: { onMarketClick },
|
cellRenderer: 'MarketName',
|
||||||
},
|
cellRendererParams: { onMarketClick },
|
||||||
{
|
},
|
||||||
headerName: t('Description'),
|
{
|
||||||
field: 'tradableInstrument.instrument.name',
|
headerName: t('Description'),
|
||||||
},
|
field: 'tradableInstrument.instrument.name',
|
||||||
{
|
},
|
||||||
headerName: t('Trading mode'),
|
{
|
||||||
field: 'tradingMode',
|
headerName: t('Trading mode'),
|
||||||
minWidth: 170,
|
field: 'tradingMode',
|
||||||
valueFormatter: ({
|
minWidth: 170,
|
||||||
data,
|
valueFormatter: ({
|
||||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
data,
|
||||||
if (!data?.data) return '-';
|
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
|
||||||
const { trigger, marketTradingMode } = data.data;
|
if (!data?.data) return '-';
|
||||||
return marketTradingMode ===
|
const { trigger, marketTradingMode } = data.data;
|
||||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
return marketTradingMode ===
|
||||||
trigger &&
|
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||||
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
trigger &&
|
||||||
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||||
|
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
|
||||||
- ${Schema.AuctionTriggerMapping[trigger]}`
|
- ${Schema.AuctionTriggerMapping[trigger]}`
|
||||||
: Schema.MarketTradingModeMapping[marketTradingMode];
|
: Schema.MarketTradingModeMapping[marketTradingMode];
|
||||||
|
},
|
||||||
|
filter: SetFilter,
|
||||||
|
filterParams: {
|
||||||
|
set: Schema.MarketTradingModeMapping,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
filter: SetFilter,
|
{
|
||||||
filterParams: {
|
headerName: t('Status'),
|
||||||
set: Schema.MarketTradingModeMapping,
|
field: 'state',
|
||||||
|
valueFormatter: ({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
||||||
|
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
||||||
|
},
|
||||||
|
filter: SetFilter,
|
||||||
|
filterParams: {
|
||||||
|
set: Schema.MarketStateMapping,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
FLAGS.SUCCESSOR_MARKETS && {
|
||||||
{
|
headerName: t('Successor market'),
|
||||||
headerName: t('Status'),
|
field: 'successorMarketID',
|
||||||
field: 'state',
|
cellRenderer: 'SuccessorMarketRenderer',
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
|
|
||||||
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
|
|
||||||
},
|
},
|
||||||
filter: SetFilter,
|
{
|
||||||
filterParams: {
|
headerName: t('Best bid'),
|
||||||
set: Schema.MarketStateMapping,
|
field: 'data.bestBidPrice',
|
||||||
|
type: 'rightAligned',
|
||||||
|
cellRenderer: 'PriceFlashCell',
|
||||||
|
filter: 'agNumberColumnFilter',
|
||||||
|
valueGetter: ({
|
||||||
|
data,
|
||||||
|
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||||
|
return data?.data?.bestBidPrice === undefined
|
||||||
|
? undefined
|
||||||
|
: toBigNum(
|
||||||
|
data?.data?.bestBidPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
).toNumber();
|
||||||
|
},
|
||||||
|
valueFormatter: ({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
MarketMaybeWithData,
|
||||||
|
'data.bestBidPrice'
|
||||||
|
>) =>
|
||||||
|
data?.data?.bestBidPrice === undefined
|
||||||
|
? '-'
|
||||||
|
: addDecimalsFormatNumber(
|
||||||
|
data.data.bestBidPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
headerName: t('Best offer'),
|
||||||
headerName: t('Best bid'),
|
field: 'data.bestOfferPrice',
|
||||||
field: 'data.bestBidPrice',
|
type: 'rightAligned',
|
||||||
type: 'rightAligned',
|
cellRenderer: 'PriceFlashCell',
|
||||||
cellRenderer: 'PriceFlashCell',
|
filter: 'agNumberColumnFilter',
|
||||||
filter: 'agNumberColumnFilter',
|
valueGetter: ({
|
||||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
data,
|
||||||
return data?.data?.bestBidPrice === undefined
|
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||||
? undefined
|
return data?.data?.bestOfferPrice === undefined
|
||||||
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
? undefined
|
||||||
|
: toBigNum(
|
||||||
|
data?.data?.bestOfferPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
).toNumber();
|
||||||
|
},
|
||||||
|
valueFormatter: ({
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<
|
||||||
|
MarketMaybeWithData,
|
||||||
|
'data.bestOfferPrice'
|
||||||
|
>) =>
|
||||||
|
data?.data?.bestOfferPrice === undefined
|
||||||
|
? '-'
|
||||||
|
: addDecimalsFormatNumber(
|
||||||
|
data.data.bestOfferPrice,
|
||||||
|
data.decimalPlaces
|
||||||
|
),
|
||||||
},
|
},
|
||||||
valueFormatter: ({
|
{
|
||||||
data,
|
headerName: t('Mark price'),
|
||||||
}: VegaValueFormatterParams<
|
field: 'data.markPrice',
|
||||||
MarketMaybeWithData,
|
type: 'rightAligned',
|
||||||
'data.bestBidPrice'
|
cellRenderer: 'PriceFlashCell',
|
||||||
>) =>
|
filter: 'agNumberColumnFilter',
|
||||||
data?.data?.bestBidPrice === undefined
|
valueGetter: ({
|
||||||
? '-'
|
data,
|
||||||
: addDecimalsFormatNumber(
|
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||||
data.data.bestBidPrice,
|
return data?.data?.markPrice === undefined
|
||||||
data.decimalPlaces
|
? undefined
|
||||||
),
|
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||||
},
|
},
|
||||||
{
|
valueFormatter: ({
|
||||||
headerName: t('Best offer'),
|
data,
|
||||||
field: 'data.bestOfferPrice',
|
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||||
type: 'rightAligned',
|
data?.data?.bestOfferPrice === undefined
|
||||||
cellRenderer: 'PriceFlashCell',
|
? '-'
|
||||||
filter: 'agNumberColumnFilter',
|
: addDecimalsFormatNumber(
|
||||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
data.data.markPrice,
|
||||||
return data?.data?.bestOfferPrice === undefined
|
data.decimalPlaces
|
||||||
? undefined
|
),
|
||||||
: toBigNum(
|
|
||||||
data?.data?.bestOfferPrice,
|
|
||||||
data.decimalPlaces
|
|
||||||
).toNumber();
|
|
||||||
},
|
},
|
||||||
valueFormatter: ({
|
{
|
||||||
data,
|
headerName: t('Settlement asset'),
|
||||||
}: VegaValueFormatterParams<
|
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||||
MarketMaybeWithData,
|
cellRenderer: ({
|
||||||
'data.bestOfferPrice'
|
data,
|
||||||
>) =>
|
}: VegaICellRendererParams<
|
||||||
data?.data?.bestOfferPrice === undefined
|
MarketMaybeWithData,
|
||||||
? '-'
|
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
||||||
: addDecimalsFormatNumber(
|
>) => {
|
||||||
data.data.bestOfferPrice,
|
const value =
|
||||||
data.decimalPlaces
|
data?.tradableInstrument.instrument.product.settlementAsset;
|
||||||
),
|
return value ? (
|
||||||
},
|
<ButtonLink
|
||||||
{
|
onClick={(e) => {
|
||||||
headerName: t('Mark price'),
|
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
||||||
field: 'data.markPrice',
|
}}
|
||||||
type: 'rightAligned',
|
>
|
||||||
cellRenderer: 'PriceFlashCell',
|
{value.symbol}
|
||||||
filter: 'agNumberColumnFilter',
|
</ButtonLink>
|
||||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
) : (
|
||||||
return data?.data?.markPrice === undefined
|
''
|
||||||
? undefined
|
);
|
||||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
},
|
||||||
},
|
},
|
||||||
valueFormatter: ({
|
{
|
||||||
data,
|
colId: 'market-actions',
|
||||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
field: 'id',
|
||||||
data?.data?.bestOfferPrice === undefined
|
...COL_DEFS.actions,
|
||||||
? '-'
|
cellRenderer: ({
|
||||||
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
|
data,
|
||||||
},
|
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
||||||
{
|
if (!data) return null;
|
||||||
headerName: t('Settlement asset'),
|
return (
|
||||||
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
<MarketActionsDropdown
|
||||||
cellRenderer: ({
|
marketId={data.id}
|
||||||
data,
|
assetId={
|
||||||
}: VegaICellRendererParams<
|
data.tradableInstrument.instrument.product.settlementAsset.id
|
||||||
MarketMaybeWithData,
|
}
|
||||||
'tradableInstrument.instrument.product.settlementAsset.symbol'
|
/>
|
||||||
>) => {
|
);
|
||||||
const value =
|
},
|
||||||
data?.tradableInstrument.instrument.product.settlementAsset;
|
|
||||||
return value ? (
|
|
||||||
<ButtonLink
|
|
||||||
onClick={(e) => {
|
|
||||||
openAssetDetailsDialog(value.id, e.target as HTMLElement);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value.symbol}
|
|
||||||
</ButtonLink>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
]),
|
||||||
{
|
|
||||||
colId: 'market-actions',
|
|
||||||
field: 'id',
|
|
||||||
...COL_DEFS.actions,
|
|
||||||
cellRenderer: ({
|
|
||||||
data,
|
|
||||||
}: VegaICellRendererParams<MarketMaybeWithData>) => {
|
|
||||||
if (!data) return null;
|
|
||||||
return (
|
|
||||||
<MarketActionsDropdown
|
|
||||||
marketId={data.id}
|
|
||||||
assetId={
|
|
||||||
data.tradableInstrument.instrument.product.settlementAsset.id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[onMarketClick, openAssetDetailsDialog]
|
[onMarketClick, openAssetDetailsDialog]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -27,8 +27,9 @@ import {
|
|||||||
filterAndSortMarkets,
|
filterAndSortMarkets,
|
||||||
} from './market-utils';
|
} from './market-utils';
|
||||||
import { MarketsDocument } from './__generated__/markets';
|
import { MarketsDocument } from './__generated__/markets';
|
||||||
|
|
||||||
import type { Candle } from './market-candles-provider';
|
import type { Candle } from './market-candles-provider';
|
||||||
|
import type { SuccessorMarketIdsQuery } from './__generated__/SuccessorMarket';
|
||||||
|
import { SuccessorMarketIdsDocument } from './__generated__';
|
||||||
|
|
||||||
export type Market = MarketFieldsFragment;
|
export type Market = MarketFieldsFragment;
|
||||||
|
|
||||||
@ -240,3 +241,34 @@ export const useMarketList = () => {
|
|||||||
reload,
|
reload,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MarketSuccessors = {
|
||||||
|
__typename?: 'Market';
|
||||||
|
id: string;
|
||||||
|
successorMarketID?: string | null;
|
||||||
|
parentMarketID?: string | null;
|
||||||
|
};
|
||||||
|
const getMarketSuccessorData = (
|
||||||
|
responseData: SuccessorMarketIdsQuery | null
|
||||||
|
): MarketSuccessors[] | null =>
|
||||||
|
responseData?.marketsConnection?.edges.map((edge) => edge.node) || null;
|
||||||
|
|
||||||
|
export const marketSuccessorProvider = makeDataProvider<
|
||||||
|
SuccessorMarketIdsQuery,
|
||||||
|
MarketSuccessors[],
|
||||||
|
never,
|
||||||
|
never
|
||||||
|
>({
|
||||||
|
query: SuccessorMarketIdsDocument,
|
||||||
|
getData: getMarketSuccessorData,
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useSuccessorMarketIds = (marketId: string) => {
|
||||||
|
const { data } = useDataProvider({
|
||||||
|
dataProvider: marketSuccessorProvider,
|
||||||
|
variables: undefined,
|
||||||
|
skip: !marketId,
|
||||||
|
});
|
||||||
|
return data?.find((item) => item.id === marketId) ?? null;
|
||||||
|
};
|
||||||
|
@ -14,6 +14,23 @@ import { createProposalListFieldsFragment } from '../../lib/proposals-data-provi
|
|||||||
import type { ProposalsListQuery } from '../../lib';
|
import type { ProposalsListQuery } from '../../lib';
|
||||||
import { ProposalsListDocument } from '../../lib';
|
import { ProposalsListDocument } from '../../lib';
|
||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/environment', () => {
|
||||||
|
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
FLAGS: {
|
||||||
|
...actual.FLAGS,
|
||||||
|
SUCCESSOR_MARKETS: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const successorMarketName = 'Successor Market Name';
|
||||||
|
const spySuccessorMarketRenderer = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(successorMarketName);
|
||||||
|
|
||||||
describe('ProposalsList', () => {
|
describe('ProposalsList', () => {
|
||||||
const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
|
const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
|
||||||
@ -64,13 +81,15 @@ describe('ProposalsList', () => {
|
|||||||
|
|
||||||
return mock;
|
return mock;
|
||||||
};
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
it('should be properly rendered', async () => {
|
it('should be properly rendered', async () => {
|
||||||
const mock = createProposalsMock();
|
const mock = createProposalsMock();
|
||||||
await act(() => {
|
await act(() => {
|
||||||
render(
|
render(
|
||||||
<MockedProvider mocks={[mock]}>
|
<MockedProvider mocks={[mock]}>
|
||||||
<ProposalsList />
|
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -82,31 +101,55 @@ describe('ProposalsList', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('some of states should be filtered out', async () => {
|
it('some of states should be filtered out', async () => {
|
||||||
|
const proposalNode = createProposalListFieldsFragment({
|
||||||
|
id: 'id-1',
|
||||||
|
state: Types.ProposalState.STATE_ENACTED,
|
||||||
|
});
|
||||||
|
|
||||||
const mock = createProposalsMock({
|
const mock = createProposalsMock({
|
||||||
proposalsConnection: {
|
proposalsConnection: {
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
__typename: 'ProposalEdge',
|
__typename: 'ProposalEdge',
|
||||||
node: createProposalListFieldsFragment({
|
node: {
|
||||||
id: 'id-1',
|
...proposalNode,
|
||||||
state: Types.ProposalState.STATE_ENACTED,
|
terms: {
|
||||||
}),
|
...proposalNode.terms,
|
||||||
|
change: {
|
||||||
|
...proposalNode.terms.change,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
} as PartialDeep<ProposalsListQuery>);
|
||||||
await act(() => {
|
await act(() => {
|
||||||
render(
|
render(
|
||||||
<MockedProvider mocks={[mock]}>
|
<MockedProvider mocks={[mock]}>
|
||||||
<ProposalsList />
|
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const container = document.querySelector('.ag-center-cols-container');
|
const container = document.querySelector('.ag-center-cols-container');
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(container).toBeInTheDocument();
|
expect(container).toBeInTheDocument();
|
||||||
|
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
||||||
});
|
});
|
||||||
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
|
||||||
|
expect(spySuccessorMarketRenderer).toHaveBeenCalled();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('columnheader', {
|
||||||
|
name: (_name, element) =>
|
||||||
|
element.getAttribute('col-id') === 'parentMarket',
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getAllByRole('gridcell', {
|
||||||
|
name: (name, element) =>
|
||||||
|
element.getAttribute('col-id') === 'parentMarket',
|
||||||
|
})[0]
|
||||||
|
).toHaveTextContent(successorMarketName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('empty response should causes no data message display', async () => {
|
it('empty response should causes no data message display', async () => {
|
||||||
@ -129,10 +172,55 @@ describe('ProposalsList', () => {
|
|||||||
await act(() => {
|
await act(() => {
|
||||||
render(
|
render(
|
||||||
<MockedProvider mocks={[mock]}>
|
<MockedProvider mocks={[mock]}>
|
||||||
<ProposalsList />
|
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
expect(await screen.findByText('No markets')).toBeInTheDocument();
|
expect(await screen.findByText('No markets')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('columnheader', {
|
||||||
|
name: (_name, element) =>
|
||||||
|
element.getAttribute('col-id') === 'parentMarket',
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('feature flag should hide parent marketcolumn', async () => {
|
||||||
|
const mockedFlags = jest.mocked(FLAGS);
|
||||||
|
mockedFlags.SUCCESSOR_MARKETS = false;
|
||||||
|
const mock: MockedResponse<ProposalsListQuery> = {
|
||||||
|
request: {
|
||||||
|
query: ProposalsListDocument,
|
||||||
|
variables: {
|
||||||
|
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
proposalsConnection: {
|
||||||
|
__typename: 'ProposalsConnection',
|
||||||
|
edges: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await act(() => {
|
||||||
|
render(
|
||||||
|
<MockedProvider mocks={[mock]}>
|
||||||
|
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
screen.getByRole('columnheader', {
|
||||||
|
name: (_name, element) => element.getAttribute('col-id') === 'market',
|
||||||
|
})
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
screen.getAllByRole('columnheader').forEach((element) => {
|
||||||
|
expect(element.getAttribute('col-id')).not.toEqual('parentMarket');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
@ -17,7 +18,13 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
|||||||
].includes(proposal.state)
|
].includes(proposal.state)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ProposalsList = () => {
|
interface ProposalListProps {
|
||||||
|
SuccessorMarketRenderer: React.FC<{ value: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProposalsList = ({
|
||||||
|
SuccessorMarketRenderer,
|
||||||
|
}: ProposalListProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const { data } = useProposalsListQuery({
|
const { data } = useProposalsListQuery({
|
||||||
variables: {
|
variables: {
|
||||||
@ -41,6 +48,7 @@ export const ProposalsList = () => {
|
|||||||
getRowId={({ data }) => data.id}
|
getRowId={({ data }) => data.id}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate={t('No markets')}
|
overlayNoRowsTemplate={t('No markets')}
|
||||||
|
components={{ SuccessorMarketRenderer }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,8 @@ import { useMemo } from 'react';
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import type { ColDef } from 'ag-grid-community';
|
import type { ColDef } from 'ag-grid-community';
|
||||||
import { COL_DEFS, DateRangeFilter, SetFilter } from '@vegaprotocol/datagrid';
|
import { COL_DEFS, DateRangeFilter, SetFilter } from '@vegaprotocol/datagrid';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import compact from 'lodash/compact';
|
||||||
|
import { useEnvironment, FLAGS } from '@vegaprotocol/environment';
|
||||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
@ -32,7 +33,7 @@ export const useColumnDefs = () => {
|
|||||||
|
|
||||||
const cellCss = 'grid h-full items-center';
|
const cellCss = 'grid h-full items-center';
|
||||||
const columnDefs: ColDef[] = useMemo(() => {
|
const columnDefs: ColDef[] = useMemo(() => {
|
||||||
return [
|
return compact([
|
||||||
{
|
{
|
||||||
colId: 'market',
|
colId: 'market',
|
||||||
headerName: t('Market'),
|
headerName: t('Market'),
|
||||||
@ -83,6 +84,13 @@ export const useColumnDefs = () => {
|
|||||||
set: ProposalStateMapping,
|
set: ProposalStateMapping,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
FLAGS.SUCCESSOR_MARKETS && {
|
||||||
|
headerName: t('Parent market'),
|
||||||
|
field: 'id',
|
||||||
|
colId: 'parentMarket',
|
||||||
|
cellRenderer: 'SuccessorMarketRenderer',
|
||||||
|
cellRendererParams: { parent: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
colId: 'voting',
|
colId: 'voting',
|
||||||
headerName: t('Voting'),
|
headerName: t('Voting'),
|
||||||
@ -146,7 +154,7 @@ export const useColumnDefs = () => {
|
|||||||
},
|
},
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
}, [VEGA_TOKEN_URL, requiredMajorityPercentage]);
|
}, [VEGA_TOKEN_URL, requiredMajorityPercentage]);
|
||||||
|
|
||||||
const defaultColDef: ColDef = useMemo(() => {
|
const defaultColDef: ColDef = useMemo(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user