From 5d4da60f05b04d2ba062bfd242450e8af1dd7c25 Mon Sep 17 00:00:00 2001 From: maciek Date: Wed, 27 Sep 2023 17:18:32 +0200 Subject: [PATCH] feat: add market termination warning banner --- .../client-pages/market/trade-grid.tsx | 2 + .../trading/components/market-banner/index.ts | 1 + .../market-termination-banner.spec.tsx | 129 ++++++++++++++++++ .../market-termination-banner.tsx | 96 +++++++++++++ .../market-info/__generated__/MarketInfo.ts | 2 +- .../proposals-data-provider/Proposals.graphql | 46 +++++++ .../__generated__/Proposals.ts | 115 +++++++++++++++- .../proposals-data-provider.tsx | 49 ++++++- .../src/lib/proposals-hooks/index.ts | 1 + .../use-get-termination-proposals.ts | 17 +++ libs/types/src/__generated__/types.ts | 2 +- 11 files changed, 456 insertions(+), 4 deletions(-) create mode 100644 apps/trading/components/market-banner/market-termination-banner.spec.tsx create mode 100644 apps/trading/components/market-banner/market-termination-banner.tsx create mode 100644 libs/proposals/src/lib/proposals-hooks/use-get-termination-proposals.ts diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index 6419e95ca..4ff6f5f5e 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -18,6 +18,7 @@ import { TradingViews } from './trade-views'; import { MarketSuccessorBanner, MarketSuccessorProposalBanner, + MarketTerminationBanner, } from '../../components/market-banner'; import { FLAGS } from '@vegaprotocol/environment'; @@ -169,6 +170,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => { )} +
diff --git a/apps/trading/components/market-banner/index.ts b/apps/trading/components/market-banner/index.ts index ed705d434..9156565e9 100644 --- a/apps/trading/components/market-banner/index.ts +++ b/apps/trading/components/market-banner/index.ts @@ -1,2 +1,3 @@ export * from './market-successor-banner'; export * from './market-successor-proposal-banner'; +export * from './market-termination-banner'; diff --git a/apps/trading/components/market-banner/market-termination-banner.spec.tsx b/apps/trading/components/market-banner/market-termination-banner.spec.tsx new file mode 100644 index 000000000..6da57c204 --- /dev/null +++ b/apps/trading/components/market-banner/market-termination-banner.spec.tsx @@ -0,0 +1,129 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import type { MockedResponse } from '@apollo/client/testing'; +import { MockedProvider } from '@apollo/client/testing'; +import { MarketTerminationBanner } from './market-termination-banner'; +import type { TerminateProposalsListQuery } from '@vegaprotocol/proposals'; +import { TerminateProposalsListDocument } from '@vegaprotocol/proposals'; +import type { PositionsQuery } from '@vegaprotocol/positions'; +import { PositionsDocument } from '@vegaprotocol/positions'; + +const walletMock = { + pubKey: 'pubKey', + pubKeys: [{ publicKey: 'pubKey' }, { publicKey: 'secondPubKey' }], +}; +jest.mock('@vegaprotocol/wallet', () => ({ + ...jest.requireActual('@vegaprotocol/wallet'), + useVegaWallet: jest.fn(() => walletMock), +})); + +const proposalMock: MockedResponse = { + request: { + query: TerminateProposalsListDocument, + variables: undefined, + }, + result: { + data: { + proposalsConnection: { + edges: [ + { + node: { + id: 'first-id', + terms: { + closingDatetime: '2023-09-27T11:48:18Z', + enactmentDatetime: '2023-09-30T11:48:18', + change: { + __typename: 'UpdateMarketState', + updateType: '', + price: '', + market: { + id: 'market-1', + tradableInstrument: { + instrument: { + name: 'Market one', + }, + }, + }, + }, + }, + }, + }, + { + node: { + id: 'second-id', + terms: { + closingDatetime: '2023-09-27T11:48:18Z', + enactmentDatetime: '2023-10-01T11:48:18', + change: { + __typename: 'UpdateMarketState', + updateType: '', + price: '', + market: { + id: 'market-2', + tradableInstrument: { + instrument: { + name: 'Market two', + }, + }, + }, + }, + }, + }, + }, + ], + }, + } as unknown as TerminateProposalsListQuery, + }, +}; +const positionsMock: MockedResponse = { + request: { + query: PositionsDocument, + variables: { partyIds: ['pubKey', 'secondPubKey'] }, + }, + result: { + data: { + positions: { + edges: [ + { + node: { + market: { + id: 'market-1', + }, + party: { + id: 'pubKey', + }, + }, + }, + { + node: { + market: { + id: 'market-2', + }, + party: { + id: 'secondPubKey', + }, + }, + }, + ], + }, + } as unknown as PositionsQuery, + }, +}; +const mocks: MockedResponse[] = [proposalMock, positionsMock]; + +describe('MarketTerminationBanner', () => { + it('should be properly rendered', async () => { + render( + + + + ); + await waitFor(() => { + expect( + screen.getByTestId('termination-warning-banner-market-1') + ).toBeInTheDocument(); + }); + expect( + screen.getByTestId('termination-warning-banner-market-2') + ).toBeInTheDocument(); + }); +}); diff --git a/apps/trading/components/market-banner/market-termination-banner.tsx b/apps/trading/components/market-banner/market-termination-banner.tsx new file mode 100644 index 000000000..6cdda9fb3 --- /dev/null +++ b/apps/trading/components/market-banner/market-termination-banner.tsx @@ -0,0 +1,96 @@ +import { + ExternalLink, + Intent, + NotificationBanner, +} from '@vegaprotocol/ui-toolkit'; +import { useState } from 'react'; +import { useGetTerminationProposals } from '@vegaprotocol/proposals'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { t } from '@vegaprotocol/i18n'; +import { useDataProvider } from '@vegaprotocol/data-provider'; +import { positionsDataProvider } from '@vegaprotocol/positions'; +import { formatDateWithLocalTimezone } from '@vegaprotocol/utils'; +import { formatDuration, intervalToDuration } from 'date-fns'; + +export const MarketTerminationBanner = () => { + const [visible, setVisible] = useState(true); + const { pubKey, pubKeys } = useVegaWallet(); + + const partyIds = pubKeys?.map((item) => item.publicKey) || []; + const { data: positionsData } = useDataProvider({ + dataProvider: positionsDataProvider, + variables: { + partyIds, + }, + skip: !partyIds?.length, + }); + const marketsId = (positionsData || []).map((item) => item.market.id); + + const skip = !pubKey || !visible || !marketsId.length; + const proposalsData = useGetTerminationProposals({ skip }); + + const marketsMatched = (proposalsData || []) + .filter((item) => + marketsId.some( + (marketId) => + item.terms.change.__typename === 'UpdateMarketState' && + item.terms.change.market.id === marketId + ) + ) + .map((item) => ({ + enactmentDatetime: item.terms.enactmentDatetime, + name: + item.terms.change.__typename === 'UpdateMarketState' + ? item.terms.change.market.tradableInstrument.instrument.name + : '', + id: + item.terms.change.__typename === 'UpdateMarketState' + ? item.terms.change.market.id + : '', + })); + + if (visible && marketsMatched.length) { + return marketsMatched.map((item) => { + const dayMonthDate = formatDateWithLocalTimezone( + new Date(item.enactmentDatetime), + 'dd MMMM' + ); + const duration = intervalToDuration({ + start: new Date(), + end: new Date(item.enactmentDatetime), + }); + const formattedDuration = formatDuration(duration, { + format: ['days', 'hours'], + }); + return ( + <> + { + setVisible(false); + }} + data-testid={`termination-warning-banner-${item.id}`} + > +
+ {t('Trading on Market %s will stop on %s', [ + item.name, + dayMonthDate, + ])} +
+
+ {t('Market')}{' '} + + {item.name} + + {t( + 'will close to trading in %s. You will not be able to hold a position on this market after %s.', + [formattedDuration, dayMonthDate] + )} +
+
+ + ); + }); + } + return null; +}; diff --git a/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts b/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts index a8586bebb..120243f9b 100644 --- a/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts +++ b/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts @@ -261,4 +261,4 @@ export function useMarketInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions } export type MarketInfoQueryHookResult = ReturnType; export type MarketInfoLazyQueryHookResult = ReturnType; -export type MarketInfoQueryResult = Apollo.QueryResult; +export type MarketInfoQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql b/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql index 69a96a8cc..759988f64 100644 --- a/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql +++ b/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql @@ -1,3 +1,16 @@ +fragment UpdateMarketStateFields on UpdateMarketState { + market { + id + tradableInstrument { + instrument { + name + } + } + } + updateType + price +} + fragment NewMarketFields on NewMarket { instrument { name @@ -428,3 +441,36 @@ query SuccessorProposalsList { } } } + +fragment TerminateProposalsListFields on Proposal { + id + state + datetime + rejectionReason + terms { + closingDatetime + enactmentDatetime + change { + __typename + ... on UpdateMarketState { + ...UpdateMarketStateFields + } + } + } +} + +query TerminateProposalsList { + proposalsConnection(inState: STATE_PASSED) { + edges { + node { + ...TerminateProposalsListFields + } + } + } +} + +subscription TerminateLiveProposals { + proposals { + ...TerminateProposalsListFields + } +} diff --git a/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts b/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts index 238799d5d..686ec0b91 100644 --- a/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts +++ b/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts @@ -3,6 +3,8 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; +export type UpdateMarketStateFieldsFragment = { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } }; + export type NewMarketFieldsFragment = { __typename?: 'NewMarket', decimalPlaces: number, metadata?: Array | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | null }, riskParameters: { __typename?: 'LogNormalRiskModel', riskAversionParameter: number, tau: number, params: { __typename?: 'LogNormalModelParams', mu: number, r: number, sigma: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null }; export type UpdateMarketFieldsFragment = { __typename?: 'UpdateMarket', marketId: string, updateMarketConfiguration: { __typename?: 'UpdateMarketConfiguration', metadata?: Array | null, instrument: { __typename?: 'UpdateInstrumentConfiguration', code: string, product: { __typename?: 'UpdateFutureProduct', quoteName: string, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'UpdatePerpetualProduct', quoteName: string, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } }, priceMonitoringParameters: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, riskParameters: { __typename: 'UpdateMarketLogNormalRiskModel', logNormal?: { __typename?: 'LogNormalRiskModel', riskAversionParameter: number, tau: number, params: { __typename?: 'LogNormalModelParams', mu: number, r: number, sigma: number } } | null } | { __typename: 'UpdateMarketSimpleRiskModel', simple?: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } | null } } }; @@ -32,6 +34,18 @@ export type SuccessorProposalsListQueryVariables = Types.Exact<{ [key: string]: export type SuccessorProposalsListQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } } | null> | null } | null }; +export type TerminateProposalsListFieldsFragment = { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset' } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket' } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset' } | { __typename: 'UpdateMarket' } | { __typename: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } | { __typename: 'UpdateNetworkParameter' } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } }; + +export type TerminateProposalsListQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type TerminateProposalsListQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset' } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket' } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset' } | { __typename: 'UpdateMarket' } | { __typename: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } | { __typename: 'UpdateNetworkParameter' } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } } } | null> | null } | null }; + +export type TerminateLiveProposalsSubscriptionVariables = Types.Exact<{ [key: string]: never; }>; + + +export type TerminateLiveProposalsSubscription = { __typename?: 'Subscription', proposals: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset' } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket' } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset' } | { __typename: 'UpdateMarket' } | { __typename: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } | { __typename: 'UpdateNetworkParameter' } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } } }; + export const NewMarketFieldsFragmentDoc = gql` fragment NewMarketFields on NewMarket { instrument { @@ -437,6 +451,38 @@ export const SuccessorProposalListFieldsFragmentDoc = gql` } } ${NewMarketSuccessorFieldsFragmentDoc}`; +export const UpdateMarketStateFieldsFragmentDoc = gql` + fragment UpdateMarketStateFields on UpdateMarketState { + market { + id + tradableInstrument { + instrument { + name + } + } + } + updateType + price +} + `; +export const TerminateProposalsListFieldsFragmentDoc = gql` + fragment TerminateProposalsListFields on Proposal { + id + state + datetime + rejectionReason + terms { + closingDatetime + enactmentDatetime + change { + __typename + ... on UpdateMarketState { + ...UpdateMarketStateFields + } + } + } +} + ${UpdateMarketStateFieldsFragmentDoc}`; export const ProposalsListDocument = gql` query ProposalsList($proposalType: ProposalType, $inState: ProposalState) { proposalsConnection(proposalType: $proposalType, inState: $inState) { @@ -514,4 +560,71 @@ export function useSuccessorProposalsListLazyQuery(baseOptions?: Apollo.LazyQuer } export type SuccessorProposalsListQueryHookResult = ReturnType; export type SuccessorProposalsListLazyQueryHookResult = ReturnType; -export type SuccessorProposalsListQueryResult = Apollo.QueryResult; \ No newline at end of file +export type SuccessorProposalsListQueryResult = Apollo.QueryResult; +export const TerminateProposalsListDocument = gql` + query TerminateProposalsList { + proposalsConnection(inState: STATE_PASSED) { + edges { + node { + ...TerminateProposalsListFields + } + } + } +} + ${TerminateProposalsListFieldsFragmentDoc}`; + +/** + * __useTerminateProposalsListQuery__ + * + * To run a query within a React component, call `useTerminateProposalsListQuery` and pass it any options that fit your needs. + * When your component renders, `useTerminateProposalsListQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useTerminateProposalsListQuery({ + * variables: { + * }, + * }); + */ +export function useTerminateProposalsListQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(TerminateProposalsListDocument, options); + } +export function useTerminateProposalsListLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(TerminateProposalsListDocument, options); + } +export type TerminateProposalsListQueryHookResult = ReturnType; +export type TerminateProposalsListLazyQueryHookResult = ReturnType; +export type TerminateProposalsListQueryResult = Apollo.QueryResult; +export const TerminateLiveProposalsDocument = gql` + subscription TerminateLiveProposals { + proposals { + ...TerminateProposalsListFields + } +} + ${TerminateProposalsListFieldsFragmentDoc}`; + +/** + * __useTerminateLiveProposalsSubscription__ + * + * To run a query within a React component, call `useTerminateLiveProposalsSubscription` and pass it any options that fit your needs. + * When your component renders, `useTerminateLiveProposalsSubscription` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useTerminateLiveProposalsSubscription({ + * variables: { + * }, + * }); + */ +export function useTerminateLiveProposalsSubscription(baseOptions?: Apollo.SubscriptionHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSubscription(TerminateLiveProposalsDocument, options); + } +export type TerminateLiveProposalsSubscriptionHookResult = ReturnType; +export type TerminateLiveProposalsSubscriptionResult = Apollo.SubscriptionResult; \ No newline at end of file diff --git a/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx b/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx index c00cc18ff..d680051b9 100644 --- a/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx +++ b/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx @@ -1,10 +1,19 @@ import { makeDataProvider } from '@vegaprotocol/data-provider'; +import produce from 'immer'; import type { ProposalsListQuery, ProposalsListQueryVariables, ProposalListFieldsFragment, + TerminateLiveProposalsSubscription, + TerminateProposalsListFieldsFragment, + TerminateProposalsListQuery, } from './__generated__/Proposals'; -import { ProposalsListDocument } from './__generated__/Proposals'; +import { + ProposalsListDocument, + TerminateLiveProposalsDocument, + TerminateProposalsListDocument, +} from './__generated__/Proposals'; +import { removePaginationWrapper } from '@vegaprotocol/utils'; const getData = (responseData: ProposalsListQuery | null) => responseData?.proposalsConnection?.edges @@ -31,3 +40,41 @@ export const proposalsDataProvider = makeDataProvider< errorPolicyGuard: (errors) => errors.every((e) => e.message.match(/failed to get asset for ID/)), }); + +const update = ( + data: TerminateProposalsListFieldsFragment[] | null, + delta: TerminateProposalsListFieldsFragment +) => { + const updateData = produce(data || [], (draft) => { + const { id } = delta; + const index = draft.findIndex((item) => item.id === id); + if (index === -1) { + draft.unshift(delta); + } else { + const currNode = draft[index]; + draft[index] = { + ...currNode, + ...delta, + }; + } + }); + return updateData; +}; + +const getTerminateProposalsData = ( + responseData: TerminateProposalsListQuery | null +) => removePaginationWrapper(responseData?.proposalsConnection?.edges) || []; + +export const proposalTerminateDataProvider = makeDataProvider< + TerminateProposalsListQuery, + TerminateProposalsListFieldsFragment[], + TerminateLiveProposalsSubscription, + TerminateProposalsListFieldsFragment +>({ + query: TerminateProposalsListDocument, + subscriptionQuery: TerminateLiveProposalsDocument, + update, + getDelta: (subscriptionData: TerminateLiveProposalsSubscription) => + subscriptionData.proposals, + getData: getTerminateProposalsData, +}); diff --git a/libs/proposals/src/lib/proposals-hooks/index.ts b/libs/proposals/src/lib/proposals-hooks/index.ts index 744b0bc5f..ad06b3f4b 100644 --- a/libs/proposals/src/lib/proposals-hooks/index.ts +++ b/libs/proposals/src/lib/proposals-hooks/index.ts @@ -4,3 +4,4 @@ export * from './use-proposal-submit'; export * from './use-update-proposal'; export * from './use-update-network-paramaters-toasts'; export * from './use-successor-market-proposal-details'; +export * from './use-get-termination-proposals'; diff --git a/libs/proposals/src/lib/proposals-hooks/use-get-termination-proposals.ts b/libs/proposals/src/lib/proposals-hooks/use-get-termination-proposals.ts new file mode 100644 index 000000000..54b5d9d9f --- /dev/null +++ b/libs/proposals/src/lib/proposals-hooks/use-get-termination-proposals.ts @@ -0,0 +1,17 @@ +import { useDataProvider } from '@vegaprotocol/data-provider'; +import { proposalTerminateDataProvider } from '../proposals-data-provider'; + +export const useGetTerminationProposals = ({ + skip = false, +}: { + skip?: boolean; +}) => { + const { data } = useDataProvider({ + dataProvider: proposalTerminateDataProvider, + skip, + variables: undefined, + }); + return (data || []).filter( + (item) => item.terms.change.__typename === 'UpdateMarketState' + ); +}; diff --git a/libs/types/src/__generated__/types.ts b/libs/types/src/__generated__/types.ts index bf63dee41..273f0710f 100644 --- a/libs/types/src/__generated__/types.ts +++ b/libs/types/src/__generated__/types.ts @@ -4637,7 +4637,7 @@ export type ReferralSet = { * Referral set statistics for the latest or specific epoch. * If provided the results can be filtered for a specific referee */ - stats: ReferralSetStats; + stats?: Maybe; /** Timestamp as RFC3339Nano when the referral set was updated. */ updatedAt: Scalars['Timestamp']; };