Compare commits

...
Author SHA1 Message Date
maciek 0f6d307448 Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-25 16:58:27 +02:00
maciek 24023e93c3 feat(trading): successor markets feature flag support - remove unnecessary win variable 2023-07-25 16:58:05 +02:00
maciek 6fe0167b02 feat(trading): successor markets feature flag support - small fix 2023-07-25 16:41:06 +02:00
maciek 933f40ea48 feat(trading): successor markets feature flag support - resolve some conflicts 2023-07-25 16:18:29 +02:00
maciek 483011023a chore: market relationship - resolve some conflicts 2023-07-25 09:20:15 +02:00
maciek 9b5e58200b Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-25 09:03:39 +02:00
asiaznik 7f8a4c36aa fix: bad cypress bad 2023-07-24 17:17:11 +02:00
maciek 8a629c420a chore: market relationship - resolve some conflicts 2023-07-24 11:45:37 +02:00
asiaznik 9f2bb74cf1 fix: cypress 2023-07-24 11:30:35 +02:00
asiaznik a717b54117 chore: take flag from process 2023-07-24 11:30:35 +02:00
asiaznik cfea27565e chore: removed realised 2023-07-24 11:30:35 +02:00
asiaznik 42b2640459 chore: env files 2023-07-24 11:30:35 +02:00
asiaznik 6d2ee907c4 fix: tests 2023-07-24 11:30:35 +02:00
ArtandEdd 378c6119a9 Update libs/environment/src/types.ts
Co-authored-by: Edd <edd@vega.xyz>
2023-07-24 11:30:35 +02:00
asiaznik 29042e9fdd fix: import 2023-07-24 11:30:35 +02:00
asiaznik fcd3fec62b feat(trading): successor markets feature flag support 2023-07-24 11:29:48 +02:00
asiaznik 1760bfcc84 chore: consolidating feature flags 2023-07-24 11:28:14 +02:00
asiaznik aae1bf5838 feat(environment): feature flags 2023-07-24 11:28:14 +02:00
maciek d72d4ef98a chore: market relationship - fix failing int test 2023-07-23 22:27:17 +02:00
maciek 07e02e0f7d chore: market relationship - fix failing unit test 2023-07-23 20:37:30 +02:00
maciek d5f1281093 Merge remote-tracking branch 'origin/feat/feature-flags' into chore/4135-parent-and-successor-markets 2023-07-23 20:06:02 +02:00
asiaznik 31decfdc3a chore: take flag from process 2023-07-21 18:11:41 +02:00
maciek d9304b9520 chore: market relationship - fix failing int test 2023-07-21 17:16:39 +02:00
maciek 3e91851616 chore: market relationship - resolve a couple of conflicts 2023-07-21 15:35:34 +02:00
asiaznik 45ca8890c1 chore: removed realised 2023-07-21 15:20:59 +02:00
asiaznik 33f92a291e chore: env files 2023-07-21 15:16:53 +02:00
asiaznik 16d66cf3c2 fix: tests 2023-07-21 15:16:53 +02:00
ArtandEdd 7db51892c9 Update libs/environment/src/types.ts
Co-authored-by: Edd <edd@vega.xyz>
2023-07-21 15:15:58 +02:00
asiaznik 45a1efc39f fix: import 2023-07-21 15:15:57 +02:00
asiaznik 97cbc69834 feat(trading): successor markets feature flag support 2023-07-21 15:15:57 +02:00
asiaznik ce70caf191 chore: consolidating feature flags 2023-07-21 15:15:57 +02:00
asiaznik 900e849fa5 feat(environment): feature flags 2023-07-21 15:15:57 +02:00
maciek 1ca6267e18 chore: market relationship - fix failing stuff 2023-07-21 14:40:32 +02:00
maciek cd7cb78089 chore: market relationship - resolve conflicts 2023-07-21 14:16:31 +02:00
maciek 59d455d617 chore: market relationship - fix failing type checks 2023-07-21 14:08:12 +02:00
maciek 36a93ae3bc Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-21 13:44:42 +02:00
maciek b470e473eb chore: market relationship - add feature flag working 2023-07-21 13:44:20 +02:00
asiaznik 1bdcd81fac chore: env files 2023-07-21 12:39:21 +02:00
asiaznik 2bb19f8686 fix: tests 2023-07-21 12:32:02 +02:00
ArtandEdd d9a8fd0ba7 Update libs/environment/src/types.ts
Co-authored-by: Edd <edd@vega.xyz>
2023-07-21 10:36:46 +02:00
maciek 32d246290c Merge remote-tracking branch 'origin/feat/feature-flags' into chore/4135-parent-and-successor-markets 2023-07-20 14:38:56 +02:00
maciek 4d2e78c001 Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-20 14:13:05 +02:00
maciek fcfa7c2d43 chore: market relationship - add some further unit tests 2023-07-20 14:12:44 +02:00
asiaznik ec02dee3d1 fix: import 2023-07-20 11:41:50 +02:00
asiaznik a573c3d59d feat(trading): successor markets feature flag support 2023-07-20 11:41:50 +02:00
asiaznik 5b71484cd0 chore: consolidating feature flags 2023-07-20 11:41:50 +02:00
asiaznik e9935cbdca feat(environment): feature flags 2023-07-20 11:41:50 +02:00
maciek 8dbf1cc7df Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-20 11:14:16 +02:00
maciek 62f64195fa chore: market relationship - add some unit tests 2023-07-20 11:13:50 +02:00
maciek ed18b20187 Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-19 15:55:02 +02:00
maciek 883a94fb60 chore: market relationship - fix failing test 2023-07-19 15:54:42 +02:00
maciek 2f9d860008 Merge remote-tracking branch 'origin/develop' into chore/4135-parent-and-successor-markets 2023-07-19 11:16:14 +02:00
maciek 9673cb4779 chore: market relationship - pass renderer to market list 2023-07-19 11:15:10 +02:00
maciek 1f73554127 chore: market relationship - remove back wrong comment 2023-07-18 16:03:35 +02:00
maciek 74bd8bcc23 chore: market relationship - parent market in proposals list 2023-07-18 15:26:28 +02:00
18 changed files with 635 additions and 211 deletions
@@ -363,12 +363,11 @@ 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', ' - ');
.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 -25
View File
@@ -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')}
@@ -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
}
}
}
+3 -2
View File
@@ -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
}
}
}
@@ -182,4 +183,4 @@ export function useSuccessorMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
}
export type SuccessorMarketQueryHookResult = ReturnType<typeof useSuccessorMarketQuery>;
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 {
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();
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,166 +24,183 @@ const { MarketTradingMode, AuctionTrigger } = Schema;
export const useColumnDefs = ({ onMarketClick }: Props) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
return useMemo<ColDef[]>(
() => [
{
headerName: t('Market'),
field: 'tradableInstrument.instrument.code',
cellRenderer: 'MarketName',
cellRendererParams: { onMarketClick },
},
{
headerName: t('Description'),
field: 'tradableInstrument.instrument.name',
},
{
headerName: t('Trading mode'),
field: 'tradingMode',
minWidth: 170,
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return '-';
const { trigger, marketTradingMode } = data.data;
return marketTradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
() =>
compact([
{
headerName: t('Market'),
field: 'tradableInstrument.instrument.code',
cellRenderer: 'MarketName',
cellRendererParams: { onMarketClick },
},
{
headerName: t('Description'),
field: 'tradableInstrument.instrument.name',
},
{
headerName: t('Trading mode'),
field: 'tradingMode',
minWidth: 170,
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return '-';
const { trigger, marketTradingMode } = data.data;
return marketTradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
trigger &&
trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
? `${Schema.MarketTradingModeMapping[marketTradingMode]}
- ${Schema.AuctionTriggerMapping[trigger]}`
: Schema.MarketTradingModeMapping[marketTradingMode];
: Schema.MarketTradingModeMapping[marketTradingMode];
},
filter: SetFilter,
filterParams: {
set: Schema.MarketTradingModeMapping,
},
},
filter: SetFilter,
filterParams: {
set: Schema.MarketTradingModeMapping,
{
headerName: t('Status'),
field: 'state',
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
},
filter: SetFilter,
filterParams: {
set: Schema.MarketStateMapping,
},
},
},
{
headerName: t('Status'),
field: 'state',
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data.state] : '-';
FLAGS.SUCCESSOR_MARKETS && {
headerName: t('Successor market'),
field: 'successorMarketID',
cellRenderer: 'SuccessorMarketRenderer',
},
filter: SetFilter,
filterParams: {
set: Schema.MarketStateMapping,
{
headerName: t('Best bid'),
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 bid'),
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();
{
headerName: t('Best offer'),
field: 'data.bestOfferPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({
data,
}: VegaValueGetterParams<MarketMaybeWithData>) => {
return data?.data?.bestOfferPrice === undefined
? 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,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestBidPrice'
>) =>
data?.data?.bestBidPrice === undefined
? '-'
: addDecimalsFormatNumber(
data.data.bestBidPrice,
data.decimalPlaces
),
},
{
headerName: t('Best offer'),
field: 'data.bestOfferPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
return data?.data?.bestOfferPrice === undefined
? undefined
: toBigNum(
data?.data?.bestOfferPrice,
data.decimalPlaces
).toNumber();
{
headerName: t('Mark price'),
field: 'data.markPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({
data,
}: VegaValueGetterParams<MarketMaybeWithData>) => {
return data?.data?.markPrice === undefined
? undefined
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
data?.data?.bestOfferPrice === undefined
? '-'
: addDecimalsFormatNumber(
data.data.markPrice,
data.decimalPlaces
),
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<
MarketMaybeWithData,
'data.bestOfferPrice'
>) =>
data?.data?.bestOfferPrice === undefined
? '-'
: addDecimalsFormatNumber(
data.data.bestOfferPrice,
data.decimalPlaces
),
},
{
headerName: t('Mark price'),
field: 'data.markPrice',
type: 'rightAligned',
cellRenderer: 'PriceFlashCell',
filter: 'agNumberColumnFilter',
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
return data?.data?.markPrice === undefined
? undefined
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
{
headerName: t('Settlement asset'),
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
cellRenderer: ({
data,
}: VegaICellRendererParams<
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>
) : (
''
);
},
},
valueFormatter: ({
data,
}: VegaValueFormatterParams<MarketMaybeWithData, 'data.markPrice'>) =>
data?.data?.bestOfferPrice === undefined
? '-'
: addDecimalsFormatNumber(data.data.markPrice, data.decimalPlaces),
},
{
headerName: t('Settlement asset'),
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
cellRenderer: ({
data,
}: VegaICellRendererParams<
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
}
/>
);
},
},
},
{
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]
);
};
+33 -1
View File
@@ -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,31 +101,55 @@ 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(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 () => {
@@ -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(() => {