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,10 +363,9 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
.first()
|
||||
.find('button svg')
|
||||
.should('exist');
|
||||
|
||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
||||
cy.get(rowSelector)
|
||||
.find('[col-id="successorMarketID"]')
|
||||
.find('[col-id="successorMarket"]')
|
||||
.first()
|
||||
.should('have.text', '-');
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
'Description',
|
||||
'Trading mode',
|
||||
'Status',
|
||||
'Successor market',
|
||||
'Best bid',
|
||||
'Best offer',
|
||||
'Mark price',
|
||||
|
@ -19,6 +19,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
'Description',
|
||||
'Settlement asset',
|
||||
'State',
|
||||
'Parent market',
|
||||
'Voting',
|
||||
'Closing date',
|
||||
'Enactment date',
|
||||
|
@ -10,15 +10,18 @@ import type {
|
||||
OracleSpecDataConnectionQuery,
|
||||
MarketsDataQuery,
|
||||
MarketsQuery,
|
||||
SuccessorMarketIdsQuery,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
OracleSpecDataConnectionDocument,
|
||||
MarketsDataDocument,
|
||||
MarketsDocument,
|
||||
SuccessorMarketIdsDocument,
|
||||
} from '@vegaprotocol/markets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
createMarketFragment,
|
||||
marketsQuery,
|
||||
@ -51,6 +54,17 @@ jest.mock('@vegaprotocol/environment', () => ({
|
||||
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', () => {
|
||||
let originalNow: typeof Date.now;
|
||||
const mockNowTimestamp = 1672531200000;
|
||||
@ -349,8 +363,25 @@ describe('Closed', () => {
|
||||
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,
|
||||
@ -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(
|
||||
<MemoryRouter>
|
||||
<MockedProvider
|
||||
mocks={[mixedMarketsMock, marketsDataMock, oracleDataMock]}
|
||||
mocks={[
|
||||
mixedMarketsMock,
|
||||
marketsDataMock,
|
||||
oracleDataMock,
|
||||
successorMarketsMock,
|
||||
]}
|
||||
>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey } as VegaWalletContextShape}
|
||||
@ -384,5 +440,107 @@ describe('Closed', () => {
|
||||
screen.getByRole('button', { name: 'SuccessorCode' })
|
||||
).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,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
COL_DEFS,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
@ -23,15 +19,14 @@ import type {
|
||||
import {
|
||||
MarketActionsDropdown,
|
||||
closedMarketsWithDataProvider,
|
||||
useSuccessorMarket,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { SettlementDateCell } from './settlement-date-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
type 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 = ({
|
||||
rowData,
|
||||
error,
|
||||
@ -202,8 +181,8 @@ const ClosedMarketsDataGrid = ({
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
colId: 'successorMarketID',
|
||||
field: 'id',
|
||||
colId: 'successorMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { MarketsContainer } from '@vegaprotocol/markets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Markets = () => {
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
return <MarketsContainer onSelect={handleOnSelect} />;
|
||||
return (
|
||||
<MarketsContainer
|
||||
onSelect={handleOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Proposed = () => {
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
@ -13,7 +14,7 @@ export const Proposed = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="h-[400px]">
|
||||
<ProposalsList />
|
||||
<ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />
|
||||
</div>
|
||||
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
|
||||
{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 {
|
||||
id
|
||||
successorMarketID
|
||||
parentMarketID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export type ParentMarketIdQuery = { __typename?: 'Query', market?: { __typename?
|
||||
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<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
@ -107,6 +107,7 @@ export const SuccessorMarketIdsDocument = gql`
|
||||
node {
|
||||
id
|
||||
successorMarketID
|
||||
parentMarketID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { forwardRef } from 'react';
|
||||
import React, { forwardRef } from 'react';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
@ -48,10 +48,15 @@ export const MarketListTable = forwardRef<
|
||||
AgGridReact,
|
||||
TypedDataAgGrid<MarketMaybeWithData> & {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
}
|
||||
>(({ onMarketClick, ...props }, ref) => {
|
||||
>(({ onMarketClick, SuccessorMarketRenderer, ...props }, ref) => {
|
||||
const columnDefs = useColumnDefs({ onMarketClick });
|
||||
|
||||
const components = {
|
||||
PriceFlashCell,
|
||||
MarketName,
|
||||
...(SuccessorMarketRenderer ? { SuccessorMarketRenderer } : null),
|
||||
};
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
@ -60,7 +65,7 @@ export const MarketListTable = forwardRef<
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
components={{ PriceFlashCell, MarketName }}
|
||||
components={components}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -4,6 +4,21 @@ import * as DataProviders from '@vegaprotocol/data-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
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 = {
|
||||
id: 'id-1',
|
||||
@ -22,8 +37,10 @@ const market = {
|
||||
} as unknown as MarketMaybeWithData;
|
||||
|
||||
describe('MarketsContainer', () => {
|
||||
it('context menu should stay open', async () => {
|
||||
const spyOnSelect = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn<typeof DataProviders, any>(DataProviders, 'useDataProvider')
|
||||
@ -34,12 +51,16 @@ describe('MarketsContainer', () => {
|
||||
data: [market],
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
it('context menu should stay open', async () => {
|
||||
let rerenderRef: (ui: React.ReactElement) => void;
|
||||
await act(async () => {
|
||||
const { rerender } = render(
|
||||
<MockedProvider>
|
||||
<MarketsContainer onSelect={spyOnSelect} />
|
||||
<MarketsContainer
|
||||
onSelect={spyOnSelect}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
rerenderRef = rerender;
|
||||
@ -63,7 +84,7 @@ describe('MarketsContainer', () => {
|
||||
screen.getByRole('button', {
|
||||
name: (_name, element) =>
|
||||
(element.parentNode as Element)?.getAttribute('id') ===
|
||||
'cell-market-actions-8',
|
||||
'cell-market-actions-9',
|
||||
})
|
||||
);
|
||||
|
||||
@ -104,4 +125,55 @@ describe('MarketsContainer', () => {
|
||||
).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 { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@ -11,9 +11,13 @@ import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
const POLLING_TIME = 2000;
|
||||
interface MarketsContainerProps {
|
||||
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 { data, error, reload } = useDataProvider({
|
||||
@ -58,6 +62,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
|
||||
}}
|
||||
onMarketClick={onSelect}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
SuccessorMarketRenderer={SuccessorMarketRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import compact from 'lodash/compact';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
@ -11,6 +12,7 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
|
||||
@ -22,9 +24,9 @@ const { MarketTradingMode, AuctionTrigger } = Schema;
|
||||
|
||||
export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
|
||||
return useMemo<ColDef[]>(
|
||||
() => [
|
||||
() =>
|
||||
compact([
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'tradableInstrument.instrument.code',
|
||||
@ -70,16 +72,26 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
set: Schema.MarketStateMapping,
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Successor market'),
|
||||
field: 'successorMarketID',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
},
|
||||
{
|
||||
headerName: t('Best bid'),
|
||||
field: 'data.bestBidPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
||||
: toBigNum(
|
||||
data?.data?.bestBidPrice,
|
||||
data.decimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
@ -100,7 +112,9 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
@ -127,7 +141,9 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
@ -137,7 +153,10 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
|
||||
data?.data?.bestOfferPrice === undefined
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
|
||||
: addDecimalsFormatNumber(
|
||||
data.data.markPrice,
|
||||
data.decimalPlaces
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Settlement asset'),
|
||||
@ -181,7 +200,7 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
]),
|
||||
[onMarketClick, openAssetDetailsDialog]
|
||||
);
|
||||
};
|
||||
|
@ -27,8 +27,9 @@ import {
|
||||
filterAndSortMarkets,
|
||||
} from './market-utils';
|
||||
import { MarketsDocument } from './__generated__/markets';
|
||||
|
||||
import type { Candle } from './market-candles-provider';
|
||||
import type { SuccessorMarketIdsQuery } from './__generated__/SuccessorMarket';
|
||||
import { SuccessorMarketIdsDocument } from './__generated__';
|
||||
|
||||
export type Market = MarketFieldsFragment;
|
||||
|
||||
@ -240,3 +241,34 @@ export const useMarketList = () => {
|
||||
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 { ProposalsListDocument } from '../../lib';
|
||||
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', () => {
|
||||
const createProposalsMock = (override?: PartialDeep<ProposalsListQuery>) => {
|
||||
@ -64,13 +81,15 @@ describe('ProposalsList', () => {
|
||||
|
||||
return mock;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('should be properly rendered', async () => {
|
||||
const mock = createProposalsMock();
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList />
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
@ -82,33 +101,57 @@ describe('ProposalsList', () => {
|
||||
});
|
||||
|
||||
it('some of states should be filtered out', async () => {
|
||||
const proposalNode = createProposalListFieldsFragment({
|
||||
id: 'id-1',
|
||||
state: Types.ProposalState.STATE_ENACTED,
|
||||
});
|
||||
|
||||
const mock = createProposalsMock({
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
{
|
||||
__typename: 'ProposalEdge',
|
||||
node: createProposalListFieldsFragment({
|
||||
id: 'id-1',
|
||||
state: Types.ProposalState.STATE_ENACTED,
|
||||
}),
|
||||
node: {
|
||||
...proposalNode,
|
||||
terms: {
|
||||
...proposalNode.terms,
|
||||
change: {
|
||||
...proposalNode.terms.change,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} as PartialDeep<ProposalsListQuery>);
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList />
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
const container = document.querySelector('.ag-center-cols-container');
|
||||
await waitFor(() => {
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
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 () => {
|
||||
const mock: MockedResponse<ProposalsListQuery> = {
|
||||
request: {
|
||||
@ -129,10 +172,55 @@ describe('ProposalsList', () => {
|
||||
await act(() => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<ProposalsList />
|
||||
<ProposalsList SuccessorMarketRenderer={spySuccessorMarketRenderer} />
|
||||
</MockedProvider>
|
||||
);
|
||||
});
|
||||
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 { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@ -17,7 +18,13 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
||||
].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 { data } = useProposalsListQuery({
|
||||
variables: {
|
||||
@ -41,6 +48,7 @@ export const ProposalsList = () => {
|
||||
getRowId={({ data }) => data.id}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
components={{ SuccessorMarketRenderer }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,7 +2,8 @@ import { useMemo } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
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 { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@ -32,7 +33,7 @@ export const useColumnDefs = () => {
|
||||
|
||||
const cellCss = 'grid h-full items-center';
|
||||
const columnDefs: ColDef[] = useMemo(() => {
|
||||
return [
|
||||
return compact([
|
||||
{
|
||||
colId: 'market',
|
||||
headerName: t('Market'),
|
||||
@ -83,6 +84,13 @@ export const useColumnDefs = () => {
|
||||
set: ProposalStateMapping,
|
||||
},
|
||||
},
|
||||
FLAGS.SUCCESSOR_MARKETS && {
|
||||
headerName: t('Parent market'),
|
||||
field: 'id',
|
||||
colId: 'parentMarket',
|
||||
cellRenderer: 'SuccessorMarketRenderer',
|
||||
cellRendererParams: { parent: true },
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
headerName: t('Voting'),
|
||||
@ -146,7 +154,7 @@ export const useColumnDefs = () => {
|
||||
},
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
]);
|
||||
}, [VEGA_TOKEN_URL, requiredMajorityPercentage]);
|
||||
|
||||
const defaultColDef: ColDef = useMemo(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user