feat(trading): show when a market may be closed through governance (#5057)
This commit is contained in:
parent
20e1522088
commit
f5868b6f1b
@ -1,6 +1,9 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import type { NewMarketSuccessorFieldsFragment } from '@vegaprotocol/proposals';
|
import {
|
||||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
marketViewProposalsDataProvider,
|
||||||
|
type NewMarketSuccessorFieldsFragment,
|
||||||
|
} from '@vegaprotocol/proposals';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Intent,
|
Intent,
|
||||||
@ -9,17 +12,20 @@ import {
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||||
import * as Types from '@vegaprotocol/types';
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
|
||||||
export const MarketSuccessorProposalBanner = ({
|
export const MarketSuccessorProposalBanner = ({
|
||||||
marketId,
|
marketId,
|
||||||
}: {
|
}: {
|
||||||
marketId?: string;
|
marketId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const proposals = useMarketViewProposals({
|
const { data: proposals } = useDataProvider({
|
||||||
|
dataProvider: marketViewProposalsDataProvider,
|
||||||
skip: !marketId,
|
skip: !marketId,
|
||||||
inState: Types.ProposalState.STATE_OPEN,
|
variables: {
|
||||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
typename: 'NewMarket',
|
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const successors =
|
const successors =
|
||||||
|
@ -20,10 +20,13 @@ const marketMock = {
|
|||||||
},
|
},
|
||||||
} as Market;
|
} as Market;
|
||||||
|
|
||||||
const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: MarketViewProposalsDocument,
|
query: MarketViewProposalsDocument,
|
||||||
variables: { inState: Types.ProposalState.STATE_PASSED },
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_PASSED,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
@ -31,7 +34,7 @@ const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
|||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
id: 'first-id',
|
id: '1',
|
||||||
state: Types.ProposalState.STATE_PASSED,
|
state: Types.ProposalState.STATE_PASSED,
|
||||||
terms: {
|
terms: {
|
||||||
closingDatetime: '2023-09-27T11:48:18Z',
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
@ -56,7 +59,7 @@ const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
id: 'second-id',
|
id: '2',
|
||||||
state: Types.ProposalState.STATE_PASSED,
|
state: Types.ProposalState.STATE_PASSED,
|
||||||
terms: {
|
terms: {
|
||||||
closingDatetime: '2023-09-27T11:48:18Z',
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
@ -84,7 +87,99 @@ const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const mocks: MockedResponse[] = [proposalMock];
|
const openProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketViewProposalsDocument,
|
||||||
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
proposalsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '3',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-01T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-3',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market three name',
|
||||||
|
code: 'Market three',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '4',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-11T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-3',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market three name',
|
||||||
|
code: 'Market three',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: '5',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-01T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-4',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market four name',
|
||||||
|
code: 'Market four',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mocks: MockedResponse[] = [passedProposalMock, openProposalMock];
|
||||||
|
|
||||||
describe('MarketTerminationBanner', () => {
|
describe('MarketTerminationBanner', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -108,4 +203,28 @@ describe('MarketTerminationBanner', () => {
|
|||||||
screen.getByTestId('termination-warning-banner-market-1')
|
screen.getByTestId('termination-warning-banner-market-1')
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render link to proposals', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<MarketTerminationBanner market={{ ...marketMock, id: 'market-3' }} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).not.toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
expect(screen.getByText('View proposals')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render link to proposal', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<MarketTerminationBanner market={{ ...marketMock, id: 'market-4' }} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).not.toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
expect(screen.getByText('View proposal')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,67 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { format, formatDuration, intervalToDuration } from 'date-fns';
|
import { format, formatDuration, intervalToDuration } from 'date-fns';
|
||||||
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
ExternalLink,
|
||||||
|
Intent,
|
||||||
|
NotificationBanner,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
|
import { marketViewProposalsDataProvider } from '@vegaprotocol/proposals';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import * as Types from '@vegaprotocol/types';
|
import * as Types from '@vegaprotocol/types';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import { getQuoteName } from '@vegaprotocol/markets';
|
import { getQuoteName } from '@vegaprotocol/markets';
|
||||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
|
import {
|
||||||
|
DApp,
|
||||||
|
TOKEN_PROPOSAL,
|
||||||
|
TOKEN_PROPOSALS,
|
||||||
|
useLinks,
|
||||||
|
} from '@vegaprotocol/environment';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
|
||||||
|
const filterProposals = (
|
||||||
|
data: MarketViewProposalFieldsFragment[] | null,
|
||||||
|
marketId: string,
|
||||||
|
now: number
|
||||||
|
) =>
|
||||||
|
sortBy(
|
||||||
|
(data || []).filter(
|
||||||
|
(item) =>
|
||||||
|
item.terms.change.__typename === 'UpdateMarketState' &&
|
||||||
|
item.terms.change.market.id === marketId &&
|
||||||
|
item.terms.change.updateType ===
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE &&
|
||||||
|
item.terms.enactmentDatetime &&
|
||||||
|
new Date(item.terms.enactmentDatetime).getTime() > now
|
||||||
|
),
|
||||||
|
(item) => item.terms.enactmentDatetime
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMessageVariables = (proposal: MarketViewProposalFieldsFragment) => {
|
||||||
|
const enactmentDatetime = new Date(proposal.terms.enactmentDatetime);
|
||||||
|
const date = format(enactmentDatetime, 'dd MMMM');
|
||||||
|
const duration = formatDuration(
|
||||||
|
intervalToDuration({
|
||||||
|
start: new Date(),
|
||||||
|
end: enactmentDatetime,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
format: ['days', 'hours'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const price =
|
||||||
|
proposal.terms.change.__typename === 'UpdateMarketState'
|
||||||
|
? proposal.terms.change.price
|
||||||
|
: '';
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
duration,
|
||||||
|
price,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const MarketTerminationBanner = ({
|
export const MarketTerminationBanner = ({
|
||||||
market,
|
market,
|
||||||
@ -15,57 +70,55 @@ export const MarketTerminationBanner = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [visible, setVisible] = useState(true);
|
const [visible, setVisible] = useState(true);
|
||||||
const skip = !market || !visible;
|
const skip = !market || !visible;
|
||||||
const proposalsData = useMarketViewProposals({
|
const { data: passedProposalsData } = useDataProvider({
|
||||||
|
dataProvider: marketViewProposalsDataProvider,
|
||||||
skip,
|
skip,
|
||||||
inState: Types.ProposalState.STATE_PASSED,
|
variables: {
|
||||||
typename: 'UpdateMarketState',
|
inState: Types.ProposalState.STATE_PASSED,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: openProposalsData } = useDataProvider({
|
||||||
|
dataProvider: marketViewProposalsDataProvider,
|
||||||
|
skip,
|
||||||
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const governanceLink = useLinks(DApp.Governance);
|
||||||
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
const marketFound = (proposalsData || []).find(
|
const now = Date.now();
|
||||||
(item) =>
|
const passedProposals = filterProposals(passedProposalsData, market.id, now);
|
||||||
item.terms.change.__typename === 'UpdateMarketState' &&
|
const openProposals = filterProposals(openProposalsData, market.id, now);
|
||||||
item.terms.change.market.id === market.id &&
|
|
||||||
item.terms.change.updateType ===
|
|
||||||
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE &&
|
|
||||||
item.state === Types.ProposalState.STATE_PASSED // subscription doesn't have state parameter
|
|
||||||
);
|
|
||||||
|
|
||||||
const enactmentDatetime = new Date(marketFound?.terms.enactmentDatetime);
|
const name = market.tradableInstrument.instrument.code;
|
||||||
const name =
|
if (!passedProposals.length && !openProposals.length) {
|
||||||
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
return null;
|
||||||
? marketFound.terms.change.market.tradableInstrument.instrument.code
|
}
|
||||||
: '';
|
|
||||||
|
|
||||||
if (name && enactmentDatetime.getTime() > Date.now()) {
|
const assetSymbol = getQuoteName(market);
|
||||||
const dayMonthDate = format(enactmentDatetime, 'dd MMMM');
|
const proposalLink =
|
||||||
const duration = intervalToDuration({
|
!passedProposals.length && openProposals[0]?.id
|
||||||
start: new Date(),
|
? governanceLink(TOKEN_PROPOSAL.replace(':id', openProposals[0]?.id))
|
||||||
end: enactmentDatetime,
|
: undefined;
|
||||||
});
|
const proposalsLink =
|
||||||
const formattedDuration = formatDuration(duration, {
|
openProposals.length > 1 ? governanceLink(TOKEN_PROPOSALS) : undefined;
|
||||||
format: ['days', 'hours'],
|
let content: ReactNode;
|
||||||
});
|
if (passedProposals.length) {
|
||||||
const price =
|
const { date, duration, price } = getMessageVariables(passedProposals[0]);
|
||||||
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
content = (
|
||||||
? marketFound.terms.change.price
|
<>
|
||||||
: '';
|
|
||||||
const assetSymbol = getQuoteName(market);
|
|
||||||
return (
|
|
||||||
<NotificationBanner
|
|
||||||
intent={Intent.Warning}
|
|
||||||
onClose={() => {
|
|
||||||
setVisible(false);
|
|
||||||
}}
|
|
||||||
data-testid={`termination-warning-banner-${market.id}`}
|
|
||||||
>
|
|
||||||
<div className="uppercase mb-1">
|
<div className="uppercase mb-1">
|
||||||
{t('Trading on Market %s will stop on %s', [name, dayMonthDate])}
|
{t('Trading on Market %s will stop on %s', [name, date])}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{t(
|
{t(
|
||||||
'You will no longer be able to hold a position on this market when it closes in %s.',
|
'You will no longer be able to hold a position on this market when it closes in %s.',
|
||||||
[formattedDuration]
|
[duration]
|
||||||
)}{' '}
|
)}{' '}
|
||||||
{price &&
|
{price &&
|
||||||
assetSymbol &&
|
assetSymbol &&
|
||||||
@ -74,8 +127,57 @@ export const MarketTerminationBanner = ({
|
|||||||
assetSymbol,
|
assetSymbol,
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
</NotificationBanner>
|
</>
|
||||||
|
);
|
||||||
|
} else if (openProposals.length > 1) {
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<div className="uppercase mb-1">
|
||||||
|
{t(
|
||||||
|
'Trading on Market %s may stop. There are open proposals to close this market',
|
||||||
|
[name]
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ExternalLink href={proposalsLink}>
|
||||||
|
{t('View proposals')}
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const { date, price } = getMessageVariables(openProposals[0]);
|
||||||
|
content = (
|
||||||
|
<>
|
||||||
|
<div className="uppercase mb-1">
|
||||||
|
{t(
|
||||||
|
'Trading on Market %s may stop on %s. There is open proposal to close this market.',
|
||||||
|
[name, date]
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{price &&
|
||||||
|
assetSymbol &&
|
||||||
|
t('Proposed final price is %s %s.', [
|
||||||
|
addDecimalsFormatNumber(price, market.decimalPlaces),
|
||||||
|
assetSymbol,
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return (
|
||||||
|
<NotificationBanner
|
||||||
|
intent={openProposals.length ? Intent.Warning : Intent.Info}
|
||||||
|
onClose={() => {
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
data-testid={`termination-warning-banner-${market.id}`}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</NotificationBanner>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
export * from './proposals-data-provider';
|
export {
|
||||||
|
marketViewProposalsDataProvider,
|
||||||
|
proposalsDataProvider,
|
||||||
|
} from './proposals-data-provider';
|
||||||
export * from './__generated__/Proposals';
|
export * from './__generated__/Proposals';
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
import { update } from './proposals-data-provider';
|
||||||
|
import type {
|
||||||
|
MarketViewProposalFieldsFragment,
|
||||||
|
MarketViewProposalsQueryVariables,
|
||||||
|
} from './__generated__/Proposals';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
describe('proposals data provider', () => {
|
||||||
|
const proposal = {
|
||||||
|
id: '1',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as MarketViewProposalFieldsFragment;
|
||||||
|
const data = [proposal];
|
||||||
|
const delta = { ...proposal };
|
||||||
|
const reload = jest.fn();
|
||||||
|
|
||||||
|
const variables: MarketViewProposalsQueryVariables = {
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('update existing data', () => {
|
||||||
|
expect(update(data, delta, reload, variables)?.[0]).toBe(delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes existing data if delta do not match inState', () => {
|
||||||
|
expect(
|
||||||
|
update(
|
||||||
|
data,
|
||||||
|
{ ...delta, state: Types.ProposalState.STATE_PASSED },
|
||||||
|
reload,
|
||||||
|
variables
|
||||||
|
)?.length
|
||||||
|
).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do not add delta if it do not match inState', () => {
|
||||||
|
expect(
|
||||||
|
update(
|
||||||
|
data,
|
||||||
|
{ ...delta, id: '2', state: Types.ProposalState.STATE_PASSED },
|
||||||
|
reload,
|
||||||
|
variables
|
||||||
|
)?.length
|
||||||
|
).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('do not add delta if it do not match proposalType', () => {
|
||||||
|
expect(
|
||||||
|
update(
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
...delta,
|
||||||
|
id: '2',
|
||||||
|
terms: { ...delta.terms, change: { __typename: 'UpdateMarket' } },
|
||||||
|
},
|
||||||
|
reload,
|
||||||
|
variables
|
||||||
|
)?.length
|
||||||
|
).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,7 @@
|
|||||||
|
import type { Update } from '@vegaprotocol/data-provider';
|
||||||
import { makeDataProvider } from '@vegaprotocol/data-provider';
|
import { makeDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
import type {
|
import type {
|
||||||
ProposalsListQuery,
|
ProposalsListQuery,
|
||||||
ProposalsListQueryVariables,
|
ProposalsListQueryVariables,
|
||||||
@ -8,6 +10,7 @@ import type {
|
|||||||
MarketViewProposalFieldsFragment,
|
MarketViewProposalFieldsFragment,
|
||||||
MarketViewProposalsQuery,
|
MarketViewProposalsQuery,
|
||||||
MarketViewProposalsQueryVariables,
|
MarketViewProposalsQueryVariables,
|
||||||
|
MarketViewLiveProposalsSubscriptionVariables,
|
||||||
} from './__generated__/Proposals';
|
} from './__generated__/Proposals';
|
||||||
import {
|
import {
|
||||||
MarketViewLiveProposalsDocument,
|
MarketViewLiveProposalsDocument,
|
||||||
@ -42,21 +45,61 @@ export const proposalsDataProvider = makeDataProvider<
|
|||||||
errors.every((e) => e.message.match(/failed to get asset for ID/)),
|
errors.every((e) => e.message.match(/failed to get asset for ID/)),
|
||||||
});
|
});
|
||||||
|
|
||||||
const update = (
|
const ProposalTypeMap: Record<
|
||||||
|
Types.ProposalType,
|
||||||
|
Types.ProposalChange['__typename']
|
||||||
|
> = {
|
||||||
|
[Types.ProposalType.TYPE_CANCEL_TRANSFER]: 'CancelTransfer',
|
||||||
|
[Types.ProposalType.TYPE_NETWORK_PARAMETERS]: 'UpdateNetworkParameter',
|
||||||
|
[Types.ProposalType.TYPE_NEW_ASSET]: 'NewAsset',
|
||||||
|
[Types.ProposalType.TYPE_NEW_FREE_FORM]: 'NewFreeform',
|
||||||
|
[Types.ProposalType.TYPE_NEW_MARKET]: 'NewMarket',
|
||||||
|
[Types.ProposalType.TYPE_NEW_SPOT_MARKET]: 'NewSpotMarket',
|
||||||
|
[Types.ProposalType.TYPE_NEW_TRANSFER]: 'NewTransfer',
|
||||||
|
[Types.ProposalType.TYPE_UPDATE_ASSET]: 'UpdateAsset',
|
||||||
|
[Types.ProposalType.TYPE_UPDATE_MARKET]: 'UpdateMarket',
|
||||||
|
[Types.ProposalType.TYPE_UPDATE_MARKET_STATE]: 'UpdateMarketState',
|
||||||
|
[Types.ProposalType.TYPE_UPDATE_REFERRAL_PROGRAM]: 'UpdateReferralProgram',
|
||||||
|
[Types.ProposalType.TYPE_UPDATE_SPOT_MARKET]: 'UpdateSpotMarket',
|
||||||
|
[Types.ProposalType.TYPE_UPDATE_VOLUME_DISCOUNT_PROGRAM]:
|
||||||
|
'UpdateVolumeDiscountProgram',
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchFilter = (
|
||||||
|
data: MarketViewProposalFieldsFragment,
|
||||||
|
variables: MarketViewProposalsQueryVariables
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
(!variables.inState || data.state === variables.inState) &&
|
||||||
|
(!variables.proposalType ||
|
||||||
|
data.terms.change.__typename === ProposalTypeMap[variables.proposalType])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const update: Update<
|
||||||
|
MarketViewProposalFieldsFragment[] | null,
|
||||||
|
MarketViewProposalFieldsFragment,
|
||||||
|
MarketViewProposalsQueryVariables
|
||||||
|
> = (
|
||||||
data: MarketViewProposalFieldsFragment[] | null,
|
data: MarketViewProposalFieldsFragment[] | null,
|
||||||
delta: MarketViewProposalFieldsFragment
|
delta: MarketViewProposalFieldsFragment,
|
||||||
|
reload,
|
||||||
|
variables
|
||||||
) => {
|
) => {
|
||||||
const updateData = produce(data || [], (draft) => {
|
const updateData = produce(data || [], (draft) => {
|
||||||
const { id } = delta;
|
const { id } = delta;
|
||||||
const index = draft.findIndex((item) => item.id === id);
|
const index = draft.findIndex((item) => item.id === id);
|
||||||
|
const match = matchFilter(delta, variables);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
draft.unshift(delta);
|
if (match) {
|
||||||
|
draft.unshift(delta);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const currNode = draft[index];
|
if (match) {
|
||||||
draft[index] = {
|
draft[index] = delta;
|
||||||
...currNode,
|
} else {
|
||||||
...delta,
|
draft.splice(index, 1);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return updateData;
|
return updateData;
|
||||||
@ -66,12 +109,15 @@ const getMarketProposalsData = (
|
|||||||
responseData: MarketViewProposalsQuery | null
|
responseData: MarketViewProposalsQuery | null
|
||||||
) => removePaginationWrapper(responseData?.proposalsConnection?.edges) || [];
|
) => removePaginationWrapper(responseData?.proposalsConnection?.edges) || [];
|
||||||
|
|
||||||
|
const subscriptionVariables: MarketViewLiveProposalsSubscriptionVariables = {};
|
||||||
|
|
||||||
export const marketViewProposalsDataProvider = makeDataProvider<
|
export const marketViewProposalsDataProvider = makeDataProvider<
|
||||||
MarketViewProposalsQuery,
|
MarketViewProposalsQuery,
|
||||||
MarketViewProposalFieldsFragment[],
|
MarketViewProposalFieldsFragment[],
|
||||||
MarketViewLiveProposalsSubscription,
|
MarketViewLiveProposalsSubscription,
|
||||||
MarketViewProposalFieldsFragment,
|
MarketViewProposalFieldsFragment,
|
||||||
MarketViewProposalsQueryVariables
|
MarketViewProposalsQueryVariables,
|
||||||
|
MarketViewLiveProposalsSubscriptionVariables
|
||||||
>({
|
>({
|
||||||
query: MarketViewProposalsDocument,
|
query: MarketViewProposalsDocument,
|
||||||
subscriptionQuery: MarketViewLiveProposalsDocument,
|
subscriptionQuery: MarketViewLiveProposalsDocument,
|
||||||
@ -79,4 +125,5 @@ export const marketViewProposalsDataProvider = makeDataProvider<
|
|||||||
getDelta: (subscriptionData: MarketViewLiveProposalsSubscription) =>
|
getDelta: (subscriptionData: MarketViewLiveProposalsSubscription) =>
|
||||||
subscriptionData.proposals,
|
subscriptionData.proposals,
|
||||||
getData: getMarketProposalsData,
|
getData: getMarketProposalsData,
|
||||||
|
getSubscriptionVariables: () => subscriptionVariables,
|
||||||
});
|
});
|
||||||
|
@ -7,4 +7,3 @@ export * from './use-update-network-paramaters-toasts';
|
|||||||
export * from './use-successor-market-proposal-details';
|
export * from './use-successor-market-proposal-details';
|
||||||
export * from './use-new-transfer-proposal-details';
|
export * from './use-new-transfer-proposal-details';
|
||||||
export * from './use-cancel-transfer-proposal-details';
|
export * from './use-cancel-transfer-proposal-details';
|
||||||
export * from './use-market-view-proposals';
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { marketViewProposalsDataProvider } from '../proposals-data-provider';
|
|
||||||
import type * as Types from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
export const useMarketViewProposals = ({
|
|
||||||
skip = false,
|
|
||||||
typename,
|
|
||||||
proposalType,
|
|
||||||
inState,
|
|
||||||
}: {
|
|
||||||
typename: Types.ProposalChange['__typename'];
|
|
||||||
skip?: boolean;
|
|
||||||
inState: Types.ProposalState;
|
|
||||||
proposalType?: Types.ProposalType;
|
|
||||||
}) => {
|
|
||||||
const variables = {
|
|
||||||
inState,
|
|
||||||
...(proposalType ? { proposalType } : null),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = useDataProvider({
|
|
||||||
dataProvider: marketViewProposalsDataProvider,
|
|
||||||
skip,
|
|
||||||
variables,
|
|
||||||
});
|
|
||||||
return (data || []).filter(
|
|
||||||
(item) => item.terms.change.__typename === typename
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,9 +1,9 @@
|
|||||||
import { proposalsDataProvider } from '..';
|
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import first from 'lodash/first';
|
import first from 'lodash/first';
|
||||||
import type { ProposalListFieldsFragment } from '..';
|
import { proposalsDataProvider } from '../proposals-data-provider';
|
||||||
|
import type { ProposalListFieldsFragment } from '../proposals-data-provider';
|
||||||
|
|
||||||
type UseUpdateProposalProps = {
|
type UseUpdateProposalProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user