feat: view proposed market change (#2189)
This commit is contained in:
parent
642bc8072b
commit
246c07f355
@ -1,4 +1,9 @@
|
|||||||
|
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||||
import { Schema } from '@vegaprotocol/types';
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
generateProposals,
|
||||||
|
marketUpdateProposal,
|
||||||
|
} from '../support/mocks/generate-proposals';
|
||||||
|
|
||||||
const marketSummaryBlock = 'header-summary';
|
const marketSummaryBlock = 'header-summary';
|
||||||
const marketExpiry = 'market-expiry';
|
const marketExpiry = 'market-expiry';
|
||||||
@ -12,6 +17,42 @@ const priceChangeValue = 'price-change';
|
|||||||
const itemHeader = 'item-header';
|
const itemHeader = 'item-header';
|
||||||
const itemValue = 'item-value';
|
const itemValue = 'item-value';
|
||||||
|
|
||||||
|
describe('Market proposal notification', { tags: '@smoke' }, () => {
|
||||||
|
before(() => {
|
||||||
|
cy.mockTradingPage(
|
||||||
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
|
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||||
|
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
|
||||||
|
);
|
||||||
|
cy.mockGQL((req) => {
|
||||||
|
aliasQuery(
|
||||||
|
req,
|
||||||
|
'ProposalsList',
|
||||||
|
generateProposals([marketUpdateProposal])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cy.mockGQLSubscription();
|
||||||
|
cy.visit('/#/markets/market-0');
|
||||||
|
cy.wait('@MarketData');
|
||||||
|
cy.getByTestId(marketSummaryBlock).should('be.visible');
|
||||||
|
});
|
||||||
|
it('should display market proposal notification if proposal found', () => {
|
||||||
|
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||||
|
cy.getByTestId('market-proposal-notification').should(
|
||||||
|
'contain.text',
|
||||||
|
'Changes have been proposed for this market'
|
||||||
|
);
|
||||||
|
cy.getByTestId('market-proposal-notification').within(() => {
|
||||||
|
cy.getByTestId('external-link').should(
|
||||||
|
'have.attr',
|
||||||
|
'href',
|
||||||
|
'https://stagnet3.token.vega.xyz/governance/123'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Market trading page', () => {
|
describe('Market trading page', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,7 @@ import { Last24hPriceChange } from '../../components/last-24h-price-change';
|
|||||||
import { MarketMarkPrice } from '../../components/market-mark-price';
|
import { MarketMarkPrice } from '../../components/market-mark-price';
|
||||||
import { MarketTradingModeComponent } from '../../components/market-trading-mode';
|
import { MarketTradingModeComponent } from '../../components/market-trading-mode';
|
||||||
import { Last24hVolume } from '../../components/last-24h-volume';
|
import { Last24hVolume } from '../../components/last-24h-volume';
|
||||||
|
import { MarketProposalNotification } from '@vegaprotocol/governance';
|
||||||
|
|
||||||
const NO_MARKET = t('No market');
|
const NO_MARKET = t('No market');
|
||||||
|
|
||||||
@ -177,6 +178,7 @@ export const TradeMarketHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
</HeaderStat>
|
</HeaderStat>
|
||||||
) : null}
|
) : null}
|
||||||
|
<MarketProposalNotification marketId={market?.id} />
|
||||||
</Header>
|
</Header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ interface TradeMarketHeaderProps {
|
|||||||
|
|
||||||
export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
||||||
return (
|
return (
|
||||||
<header className="w-screen xl:px-4 pt-4 border-b border-default">
|
<header className="w-screen xl:px-4 pt-3 border-b border-default">
|
||||||
<div className="xl:flex xl:gap-4 items-start">
|
<div className="xl:flex xl:gap-4 items-start">
|
||||||
<div className="mb-4 xl:mb-0 px-4 xl:px-0">{title}</div>
|
<div className="mb-4 xl:mb-0 px-4 xl:px-0">{title}</div>
|
||||||
<div
|
<div
|
||||||
@ -43,7 +43,7 @@ export const HeaderStat = ({
|
|||||||
testId?: string;
|
testId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const itemClass =
|
const itemClass =
|
||||||
'min-w-min w-[120px] whitespace-nowrap pb-3 px-4 border-l border-default';
|
'min-w-min w-[120px] whitespace-nowrap pb-3 px-4 border-l border-default mt-1';
|
||||||
const itemHeading = 'text-neutral-500 dark:text-neutral-400';
|
const itemHeading = 'text-neutral-500 dark:text-neutral-400';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -144,7 +144,7 @@ export const SelectMarketPopover = ({
|
|||||||
onCellClick: OnCellClickHandler;
|
onCellClick: OnCellClickHandler;
|
||||||
}) => {
|
}) => {
|
||||||
const triggerClasses =
|
const triggerClasses =
|
||||||
'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300';
|
'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300 mt-1';
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { data, loading: marketsLoading } = useMarketList();
|
const { data, loading: marketsLoading } = useMarketList();
|
||||||
|
@ -3,6 +3,7 @@ import { useAssetsDataProvider } from './assets-data-provider';
|
|||||||
import { Button, Dialog, Icon, Splash } from '@vegaprotocol/ui-toolkit';
|
import { Button, Dialog, Icon, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
import { AssetDetailsTable } from './asset-details-table';
|
import { AssetDetailsTable } from './asset-details-table';
|
||||||
|
import { AssetProposalNotification } from '@vegaprotocol/governance';
|
||||||
|
|
||||||
export type AssetDetailsDialogStore = {
|
export type AssetDetailsDialogStore = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -49,6 +50,7 @@ export const AssetDetailsDialog = ({
|
|||||||
|
|
||||||
const content = asset ? (
|
const content = asset ? (
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
|
<AssetProposalNotification assetId={asset.id} />
|
||||||
<AssetDetailsTable asset={asset} />
|
<AssetDetailsTable asset={asset} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useUpdateProposal } from '../lib';
|
||||||
|
|
||||||
|
type AssetProposalNotificationProps = {
|
||||||
|
assetId?: string;
|
||||||
|
};
|
||||||
|
export const AssetProposalNotification = ({
|
||||||
|
assetId,
|
||||||
|
}: AssetProposalNotificationProps) => {
|
||||||
|
const tokenLink = useLinks(DApp.Token);
|
||||||
|
const { data: proposal } = useUpdateProposal({
|
||||||
|
id: assetId,
|
||||||
|
proposalType: Schema.ProposalType.TYPE_UPDATE_ASSET,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (proposal) {
|
||||||
|
const proposalLink = tokenLink(
|
||||||
|
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
intent={Intent.Warning}
|
||||||
|
message={t('Changes have been proposed for this asset')}
|
||||||
|
testId="asset-proposal-notification"
|
||||||
|
>
|
||||||
|
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
2
libs/governance/src/components/index.ts
Normal file
2
libs/governance/src/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './asset-proposal-notification';
|
||||||
|
export * from './market-proposal-notification';
|
@ -0,0 +1,35 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||||
|
import { useUpdateProposal } from '../lib';
|
||||||
|
|
||||||
|
type MarketProposalNotificationProps = {
|
||||||
|
marketId?: string;
|
||||||
|
};
|
||||||
|
export const MarketProposalNotification = ({
|
||||||
|
marketId,
|
||||||
|
}: MarketProposalNotificationProps) => {
|
||||||
|
const tokenLink = useLinks(DApp.Token);
|
||||||
|
const { data: proposal } = useUpdateProposal({
|
||||||
|
id: marketId,
|
||||||
|
proposalType: Schema.ProposalType.TYPE_UPDATE_MARKET,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (proposal) {
|
||||||
|
const proposalLink = tokenLink(
|
||||||
|
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
intent={Intent.Warning}
|
||||||
|
message={t('Changes have been proposed for this market')}
|
||||||
|
testId="market-proposal-notification"
|
||||||
|
>
|
||||||
|
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
@ -1,2 +1,3 @@
|
|||||||
export * from './lib';
|
export * from './lib';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export * from './components';
|
||||||
|
@ -12,6 +12,63 @@ fragment NewMarketFields on NewMarket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment UpdateMarketFields on UpdateMarket {
|
||||||
|
marketId
|
||||||
|
updateMarketConfiguration {
|
||||||
|
instrument {
|
||||||
|
code
|
||||||
|
product {
|
||||||
|
quoteName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
priceMonitoringParameters {
|
||||||
|
triggers {
|
||||||
|
horizonSecs
|
||||||
|
probability
|
||||||
|
auctionExtensionSecs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liquidityMonitoringParameters {
|
||||||
|
targetStakeParameters {
|
||||||
|
timeWindow
|
||||||
|
scalingFactor
|
||||||
|
}
|
||||||
|
triggeringRatio
|
||||||
|
}
|
||||||
|
riskParameters {
|
||||||
|
__typename
|
||||||
|
... on UpdateMarketSimpleRiskModel {
|
||||||
|
simple {
|
||||||
|
factorLong
|
||||||
|
factorShort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on UpdateMarketLogNormalRiskModel {
|
||||||
|
logNormal {
|
||||||
|
riskAversionParameter
|
||||||
|
tau
|
||||||
|
params {
|
||||||
|
mu
|
||||||
|
r
|
||||||
|
sigma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment UpdateAssetFields on UpdateAsset {
|
||||||
|
assetId
|
||||||
|
quantum
|
||||||
|
source {
|
||||||
|
... on UpdateERC20 {
|
||||||
|
lifetimeLimit
|
||||||
|
withdrawThreshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fragment ProposalListFields on Proposal {
|
fragment ProposalListFields on Proposal {
|
||||||
id
|
id
|
||||||
reference
|
reference
|
||||||
@ -36,6 +93,12 @@ fragment ProposalListFields on Proposal {
|
|||||||
... on NewMarket {
|
... on NewMarket {
|
||||||
...NewMarketFields
|
...NewMarketFields
|
||||||
}
|
}
|
||||||
|
... on UpdateMarket {
|
||||||
|
...UpdateMarketFields
|
||||||
|
}
|
||||||
|
... on UpdateAsset {
|
||||||
|
...UpdateAssetFields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,11 @@ import * as Apollo from '@apollo/client';
|
|||||||
const defaultOptions = {} as const;
|
const defaultOptions = {} as const;
|
||||||
export type NewMarketFieldsFragment = { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', code: string, name: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string } } | null } };
|
export type NewMarketFieldsFragment = { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', code: string, name: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string } } | null } };
|
||||||
|
|
||||||
export type ProposalListFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string } }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', code: string, name: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string } } | null } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateNetworkParameter' } } };
|
export type UpdateMarketFieldsFragment = { __typename?: 'UpdateMarket', marketId: string, updateMarketConfiguration: { __typename?: 'UpdateMarketConfiguration', instrument: { __typename?: 'UpdateInstrumentConfiguration', code: string, product: { __typename?: 'UpdateFutureProduct', quoteName: string } }, priceMonitoringParameters: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, 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 } } };
|
||||||
|
|
||||||
|
export type UpdateAssetFieldsFragment = { __typename?: 'UpdateAsset', assetId: string, quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } };
|
||||||
|
|
||||||
|
export type ProposalListFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string } }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', code: string, name: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string } } | null } } | { __typename?: 'UpdateAsset', assetId: string, quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string, updateMarketConfiguration: { __typename?: 'UpdateMarketConfiguration', instrument: { __typename?: 'UpdateInstrumentConfiguration', code: string, product: { __typename?: 'UpdateFutureProduct', quoteName: string } }, priceMonitoringParameters: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, 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 } } } | { __typename?: 'UpdateNetworkParameter' } } };
|
||||||
|
|
||||||
export type ProposalsListQueryVariables = Types.Exact<{
|
export type ProposalsListQueryVariables = Types.Exact<{
|
||||||
proposalType?: Types.InputMaybe<Types.ProposalType>;
|
proposalType?: Types.InputMaybe<Types.ProposalType>;
|
||||||
@ -13,7 +17,7 @@ export type ProposalsListQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ProposalsListQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string } }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', code: string, name: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string } } | null } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateNetworkParameter' } } } } | null> | null } | null };
|
export type ProposalsListQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string } }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', code: string, name: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string } } | null } } | { __typename?: 'UpdateAsset', assetId: string, quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string, updateMarketConfiguration: { __typename?: 'UpdateMarketConfiguration', instrument: { __typename?: 'UpdateInstrumentConfiguration', code: string, product: { __typename?: 'UpdateFutureProduct', quoteName: string } }, priceMonitoringParameters: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, 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 } } } | { __typename?: 'UpdateNetworkParameter' } } } } | null> | null } | null };
|
||||||
|
|
||||||
export const NewMarketFieldsFragmentDoc = gql`
|
export const NewMarketFieldsFragmentDoc = gql`
|
||||||
fragment NewMarketFields on NewMarket {
|
fragment NewMarketFields on NewMarket {
|
||||||
@ -30,6 +34,65 @@ export const NewMarketFieldsFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const UpdateMarketFieldsFragmentDoc = gql`
|
||||||
|
fragment UpdateMarketFields on UpdateMarket {
|
||||||
|
marketId
|
||||||
|
updateMarketConfiguration {
|
||||||
|
instrument {
|
||||||
|
code
|
||||||
|
product {
|
||||||
|
quoteName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
priceMonitoringParameters {
|
||||||
|
triggers {
|
||||||
|
horizonSecs
|
||||||
|
probability
|
||||||
|
auctionExtensionSecs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liquidityMonitoringParameters {
|
||||||
|
targetStakeParameters {
|
||||||
|
timeWindow
|
||||||
|
scalingFactor
|
||||||
|
}
|
||||||
|
triggeringRatio
|
||||||
|
}
|
||||||
|
riskParameters {
|
||||||
|
__typename
|
||||||
|
... on UpdateMarketSimpleRiskModel {
|
||||||
|
simple {
|
||||||
|
factorLong
|
||||||
|
factorShort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on UpdateMarketLogNormalRiskModel {
|
||||||
|
logNormal {
|
||||||
|
riskAversionParameter
|
||||||
|
tau
|
||||||
|
params {
|
||||||
|
mu
|
||||||
|
r
|
||||||
|
sigma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const UpdateAssetFieldsFragmentDoc = gql`
|
||||||
|
fragment UpdateAssetFields on UpdateAsset {
|
||||||
|
assetId
|
||||||
|
quantum
|
||||||
|
source {
|
||||||
|
... on UpdateERC20 {
|
||||||
|
lifetimeLimit
|
||||||
|
withdrawThreshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const ProposalListFieldsFragmentDoc = gql`
|
export const ProposalListFieldsFragmentDoc = gql`
|
||||||
fragment ProposalListFields on Proposal {
|
fragment ProposalListFields on Proposal {
|
||||||
id
|
id
|
||||||
@ -55,10 +118,18 @@ export const ProposalListFieldsFragmentDoc = gql`
|
|||||||
... on NewMarket {
|
... on NewMarket {
|
||||||
...NewMarketFields
|
...NewMarketFields
|
||||||
}
|
}
|
||||||
|
... on UpdateMarket {
|
||||||
|
...UpdateMarketFields
|
||||||
|
}
|
||||||
|
... on UpdateAsset {
|
||||||
|
...UpdateAssetFields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${NewMarketFieldsFragmentDoc}`;
|
${NewMarketFieldsFragmentDoc}
|
||||||
|
${UpdateMarketFieldsFragmentDoc}
|
||||||
|
${UpdateAssetFieldsFragmentDoc}`;
|
||||||
export const ProposalsListDocument = gql`
|
export const ProposalsListDocument = gql`
|
||||||
query ProposalsList($proposalType: ProposalType, $inState: ProposalState) {
|
query ProposalsList($proposalType: ProposalType, $inState: ProposalState) {
|
||||||
proposalsConnection(proposalType: $proposalType, inState: $inState) {
|
proposalsConnection(proposalType: $proposalType, inState: $inState) {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from './__generated__/Proposal';
|
export * from './__generated__/Proposal';
|
||||||
export * from './use-proposal-event';
|
export * from './use-proposal-event';
|
||||||
export * from './use-proposal-submit';
|
export * from './use-proposal-submit';
|
||||||
|
export * from './use-update-proposal';
|
||||||
|
@ -0,0 +1,319 @@
|
|||||||
|
/* eslint-disable jest/no-conditional-expect */
|
||||||
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ProposalListFieldsFragment,
|
||||||
|
UpdateAssetFieldsFragment,
|
||||||
|
UpdateMarketFieldsFragment,
|
||||||
|
} from '../proposals-data-provider';
|
||||||
|
import {
|
||||||
|
isChangeProposed,
|
||||||
|
UpdateAssetFields,
|
||||||
|
UpdateMarketFields,
|
||||||
|
useUpdateProposal,
|
||||||
|
} from './use-update-proposal';
|
||||||
|
|
||||||
|
const generateUpdateAssetProposal = (
|
||||||
|
id: string,
|
||||||
|
quantum = '',
|
||||||
|
lifetimeLimit = '',
|
||||||
|
withdrawThreshold = ''
|
||||||
|
): ProposalListFieldsFragment => ({
|
||||||
|
reference: '',
|
||||||
|
state: Schema.ProposalState.STATE_OPEN,
|
||||||
|
datetime: '',
|
||||||
|
votes: {
|
||||||
|
__typename: undefined,
|
||||||
|
yes: {
|
||||||
|
__typename: undefined,
|
||||||
|
totalTokens: '',
|
||||||
|
totalNumber: '',
|
||||||
|
totalWeight: '',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
__typename: undefined,
|
||||||
|
totalTokens: '',
|
||||||
|
totalNumber: '',
|
||||||
|
totalWeight: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
terms: {
|
||||||
|
__typename: 'ProposalTerms',
|
||||||
|
closingDatetime: '',
|
||||||
|
enactmentDatetime: undefined,
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateAsset',
|
||||||
|
assetId: id,
|
||||||
|
quantum,
|
||||||
|
source: {
|
||||||
|
__typename: 'UpdateERC20',
|
||||||
|
lifetimeLimit,
|
||||||
|
withdrawThreshold,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateRiskParameters = (
|
||||||
|
type:
|
||||||
|
| 'UpdateMarketLogNormalRiskModel'
|
||||||
|
| 'UpdateMarketSimpleRiskModel' = 'UpdateMarketLogNormalRiskModel'
|
||||||
|
): RiskParameters => {
|
||||||
|
if (type === 'UpdateMarketSimpleRiskModel')
|
||||||
|
return {
|
||||||
|
__typename: 'UpdateMarketSimpleRiskModel',
|
||||||
|
simple: {
|
||||||
|
__typename: 'SimpleRiskModelParams',
|
||||||
|
factorLong: 0,
|
||||||
|
factorShort: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
__typename: 'UpdateMarketLogNormalRiskModel',
|
||||||
|
logNormal: {
|
||||||
|
__typename: 'LogNormalRiskModel',
|
||||||
|
params: {
|
||||||
|
__typename: 'LogNormalModelParams',
|
||||||
|
mu: 0,
|
||||||
|
r: 0,
|
||||||
|
sigma: 0,
|
||||||
|
},
|
||||||
|
riskAversionParameter: 0,
|
||||||
|
tau: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUpdateMarketProposal = (
|
||||||
|
id: string,
|
||||||
|
code = '',
|
||||||
|
quoteName = '',
|
||||||
|
priceMonitoring = false,
|
||||||
|
liquidityMonitoring = false,
|
||||||
|
riskParameters = false,
|
||||||
|
riskParametersType:
|
||||||
|
| 'UpdateMarketLogNormalRiskModel'
|
||||||
|
| 'UpdateMarketSimpleRiskModel' = 'UpdateMarketLogNormalRiskModel'
|
||||||
|
): ProposalListFieldsFragment => ({
|
||||||
|
reference: '',
|
||||||
|
state: Schema.ProposalState.STATE_OPEN,
|
||||||
|
datetime: '',
|
||||||
|
votes: {
|
||||||
|
__typename: undefined,
|
||||||
|
yes: {
|
||||||
|
__typename: undefined,
|
||||||
|
totalTokens: '',
|
||||||
|
totalNumber: '',
|
||||||
|
totalWeight: '',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
__typename: undefined,
|
||||||
|
totalTokens: '',
|
||||||
|
totalNumber: '',
|
||||||
|
totalWeight: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
terms: {
|
||||||
|
__typename: 'ProposalTerms',
|
||||||
|
closingDatetime: '',
|
||||||
|
enactmentDatetime: undefined,
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
marketId: id,
|
||||||
|
updateMarketConfiguration: {
|
||||||
|
__typename: undefined,
|
||||||
|
instrument: {
|
||||||
|
__typename:
|
||||||
|
code.length > 0 || quoteName.length > 0
|
||||||
|
? 'UpdateInstrumentConfiguration'
|
||||||
|
: undefined,
|
||||||
|
code,
|
||||||
|
product: {
|
||||||
|
__typename:
|
||||||
|
quoteName.length > 0 ? 'UpdateFutureProduct' : undefined,
|
||||||
|
quoteName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
priceMonitoringParameters: {
|
||||||
|
__typename: priceMonitoring ? 'PriceMonitoringParameters' : undefined,
|
||||||
|
triggers: priceMonitoring
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
auctionExtensionSecs: 1,
|
||||||
|
horizonSecs: 2,
|
||||||
|
probability: 3,
|
||||||
|
__typename: 'PriceMonitoringTrigger',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
},
|
||||||
|
liquidityMonitoringParameters: {
|
||||||
|
__typename: liquidityMonitoring
|
||||||
|
? 'LiquidityMonitoringParameters'
|
||||||
|
: undefined,
|
||||||
|
triggeringRatio: 0,
|
||||||
|
targetStakeParameters: {
|
||||||
|
__typename: undefined,
|
||||||
|
scalingFactor: 0,
|
||||||
|
timeWindow: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
riskParameters: riskParameters
|
||||||
|
? generateRiskParameters(riskParametersType)
|
||||||
|
: {
|
||||||
|
__typename: riskParametersType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockDataProviderData: {
|
||||||
|
data: ProposalListFieldsFragment[];
|
||||||
|
error: Error | undefined;
|
||||||
|
loading: boolean;
|
||||||
|
} = {
|
||||||
|
data: [
|
||||||
|
generateUpdateMarketProposal('123'),
|
||||||
|
generateUpdateAssetProposal('456'),
|
||||||
|
],
|
||||||
|
error: undefined,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDataProvider = jest.fn(() => {
|
||||||
|
return mockDataProviderData;
|
||||||
|
});
|
||||||
|
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||||
|
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||||
|
useDataProvider: jest.fn((args) => mockDataProvider()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useUpdateProposal', () => {
|
||||||
|
it('returns update proposal for a given asset', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useUpdateProposal({
|
||||||
|
id: '456',
|
||||||
|
proposalType: Schema.ProposalType.TYPE_UPDATE_ASSET,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const change = result.current.data?.terms
|
||||||
|
.change as UpdateAssetFieldsFragment;
|
||||||
|
expect(change.__typename).toEqual('UpdateAsset');
|
||||||
|
expect(change.assetId).toEqual('456');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns update proposal for a given market', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useUpdateProposal({
|
||||||
|
id: '123',
|
||||||
|
proposalType: Schema.ProposalType.TYPE_UPDATE_MARKET,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const change = result.current.data?.terms
|
||||||
|
.change as UpdateMarketFieldsFragment;
|
||||||
|
expect(change.__typename).toEqual('UpdateMarket');
|
||||||
|
expect(change.marketId).toEqual('123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return a proposal if not found', () => {
|
||||||
|
const { result } = renderHook(() =>
|
||||||
|
useUpdateProposal({
|
||||||
|
id: '789',
|
||||||
|
proposalType: Schema.ProposalType.TYPE_UPDATE_MARKET,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.current.data).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isChangeProposed', () => {
|
||||||
|
it('returns false if a change for the specified asset field is not proposed', () => {
|
||||||
|
const proposal = generateUpdateAssetProposal('123');
|
||||||
|
expect(isChangeProposed(proposal, UpdateAssetFields.Quantum)).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateAssetFields.LifetimeLimit)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateAssetFields.WithdrawThreshold)
|
||||||
|
).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if a change for the specified asset field is proposed', () => {
|
||||||
|
const proposal = generateUpdateAssetProposal('123', '100', '100', '100');
|
||||||
|
expect(isChangeProposed(proposal, UpdateAssetFields.Quantum)).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateAssetFields.LifetimeLimit)
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateAssetFields.WithdrawThreshold)
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if a change for the specified market field is not proposed', () => {
|
||||||
|
const proposal = generateUpdateMarketProposal('123');
|
||||||
|
expect(isChangeProposed(proposal, UpdateMarketFields.Code)).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.QuoteName)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.PriceMonitoring)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.LiquidityMonitoring)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.RiskParameters)
|
||||||
|
).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if a change for the specified market field is proposed', () => {
|
||||||
|
const proposal = generateUpdateMarketProposal(
|
||||||
|
'123',
|
||||||
|
'ABCDEF',
|
||||||
|
'qABCDEFq',
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(isChangeProposed(proposal, UpdateMarketFields.Code)).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.QuoteName)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.PriceMonitoring)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.LiquidityMonitoring)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
isChangeProposed(proposal, UpdateMarketFields.RiskParameters)
|
||||||
|
).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
203
libs/governance/src/lib/proposals-hooks/use-update-proposal.ts
Normal file
203
libs/governance/src/lib/proposals-hooks/use-update-proposal.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import { proposalsListDataProvider } from '..';
|
||||||
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import first from 'lodash/first';
|
||||||
|
import type { ProposalListFieldsFragment } from '..';
|
||||||
|
|
||||||
|
type UseUpdateProposalProps = {
|
||||||
|
id?: string;
|
||||||
|
proposalType:
|
||||||
|
| Schema.ProposalType.TYPE_UPDATE_ASSET
|
||||||
|
| Schema.ProposalType.TYPE_UPDATE_MARKET;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseUpdateProposal = {
|
||||||
|
data: ProposalListFieldsFragment | undefined;
|
||||||
|
loading: boolean;
|
||||||
|
error: Error | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeCondition = {
|
||||||
|
[Schema.ProposalType.TYPE_UPDATE_ASSET]: (
|
||||||
|
id: string,
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => change.__typename === 'UpdateAsset' && change.assetId === id,
|
||||||
|
[Schema.ProposalType.TYPE_UPDATE_MARKET]: (
|
||||||
|
id: string,
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => change.__typename === 'UpdateMarket' && change.marketId === id,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateProposal = ({
|
||||||
|
id,
|
||||||
|
proposalType,
|
||||||
|
}: UseUpdateProposalProps): UseUpdateProposal => {
|
||||||
|
const variables = useMemo(
|
||||||
|
() => ({
|
||||||
|
proposalType,
|
||||||
|
skipUpdates: true,
|
||||||
|
}),
|
||||||
|
[proposalType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, loading, error } = useDataProvider({
|
||||||
|
dataProvider: proposalsListDataProvider,
|
||||||
|
variables,
|
||||||
|
});
|
||||||
|
|
||||||
|
const proposal = id
|
||||||
|
? first(
|
||||||
|
(data || []).filter(
|
||||||
|
(proposal) =>
|
||||||
|
[
|
||||||
|
Schema.ProposalState.STATE_OPEN,
|
||||||
|
Schema.ProposalState.STATE_PASSED,
|
||||||
|
Schema.ProposalState.STATE_WAITING_FOR_NODE_VOTE,
|
||||||
|
].includes(proposal.state) &&
|
||||||
|
changeCondition[proposalType](id, proposal.terms.change)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return { data: proposal, loading, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum UpdateMarketFields {
|
||||||
|
Code,
|
||||||
|
QuoteName,
|
||||||
|
PriceMonitoring,
|
||||||
|
LiquidityMonitoring,
|
||||||
|
RiskParameters,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UpdateAssetFields {
|
||||||
|
Quantum,
|
||||||
|
LifetimeLimit,
|
||||||
|
WithdrawThreshold,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateProposalField = UpdateAssetFields | UpdateMarketFields;
|
||||||
|
|
||||||
|
const fieldGetters = {
|
||||||
|
[UpdateMarketFields.Code]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateMarket') {
|
||||||
|
const proposed =
|
||||||
|
change.updateMarketConfiguration.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.instrument.__typename !== undefined;
|
||||||
|
return (
|
||||||
|
proposed && change.updateMarketConfiguration.instrument.code.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateMarketFields.QuoteName]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateMarket') {
|
||||||
|
const proposed =
|
||||||
|
change.updateMarketConfiguration.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.instrument.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.instrument.product.__typename !==
|
||||||
|
undefined;
|
||||||
|
return (
|
||||||
|
proposed &&
|
||||||
|
change.updateMarketConfiguration.instrument.product.quoteName.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateMarketFields.PriceMonitoring]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateMarket') {
|
||||||
|
const proposed =
|
||||||
|
change.updateMarketConfiguration.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.priceMonitoringParameters
|
||||||
|
.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.priceMonitoringParameters.triggers
|
||||||
|
?.length;
|
||||||
|
return proposed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateMarketFields.LiquidityMonitoring]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateMarket') {
|
||||||
|
const proposed =
|
||||||
|
change.updateMarketConfiguration.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.liquidityMonitoringParameters
|
||||||
|
.__typename !== undefined;
|
||||||
|
return proposed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateMarketFields.RiskParameters]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateMarket') {
|
||||||
|
const proposed =
|
||||||
|
change.updateMarketConfiguration.__typename !== undefined &&
|
||||||
|
change.updateMarketConfiguration.riskParameters.__typename !==
|
||||||
|
undefined;
|
||||||
|
const log =
|
||||||
|
change.updateMarketConfiguration.riskParameters.__typename ===
|
||||||
|
'UpdateMarketLogNormalRiskModel' &&
|
||||||
|
change.updateMarketConfiguration.riskParameters.logNormal !== undefined;
|
||||||
|
const simple =
|
||||||
|
change.updateMarketConfiguration.riskParameters.__typename ===
|
||||||
|
'UpdateMarketSimpleRiskModel' &&
|
||||||
|
change.updateMarketConfiguration.riskParameters.simple !== undefined;
|
||||||
|
return proposed && (log || simple);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateAssetFields.Quantum]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateAsset') {
|
||||||
|
const proposed = change.quantum.length > 0;
|
||||||
|
return proposed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateAssetFields.LifetimeLimit]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateAsset') {
|
||||||
|
const proposed =
|
||||||
|
change.source.__typename === 'UpdateERC20' &&
|
||||||
|
change.source.lifetimeLimit.length > 0;
|
||||||
|
return proposed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[UpdateAssetFields.WithdrawThreshold]: (
|
||||||
|
change: ProposalListFieldsFragment['terms']['change']
|
||||||
|
) => {
|
||||||
|
if (change.__typename === 'UpdateAsset') {
|
||||||
|
const proposed =
|
||||||
|
change.source.__typename === 'UpdateERC20' &&
|
||||||
|
change.source.withdrawThreshold.length > 0;
|
||||||
|
return proposed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isChangeProposed = (
|
||||||
|
proposal: ProposalListFieldsFragment | undefined,
|
||||||
|
field: UpdateProposalField
|
||||||
|
) => {
|
||||||
|
if (proposal) {
|
||||||
|
return (
|
||||||
|
(proposal.terms.change.__typename === 'UpdateAsset' ||
|
||||||
|
proposal.terms.change.__typename === 'UpdateMarket') &&
|
||||||
|
fieldGetters[field](proposal.terms.change)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
@ -31,6 +31,7 @@ import { marketInfoDataProvider } from './market-info-data-provider';
|
|||||||
import { TokenLinks } from '@vegaprotocol/react-helpers';
|
import { TokenLinks } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
import type { MarketInfoQuery } from './__generated___/MarketInfo';
|
import type { MarketInfoQuery } from './__generated___/MarketInfo';
|
||||||
|
import { MarketProposalNotification } from '@vegaprotocol/governance';
|
||||||
|
|
||||||
export interface InfoProps {
|
export interface InfoProps {
|
||||||
market: MarketInfoQuery['market'];
|
market: MarketInfoQuery['market'];
|
||||||
@ -405,6 +406,7 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
<Accordion panels={marketDataPanels} />
|
<Accordion panels={marketDataPanels} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
<MarketProposalNotification marketId={market.id} />
|
||||||
<p className={headerClassName}>{t('Market specification')}</p>
|
<p className={headerClassName}>{t('Market specification')}</p>
|
||||||
<Accordion panels={marketSpecPanels} />
|
<Accordion panels={marketSpecPanels} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,3 +38,4 @@ export * from './vega-icons';
|
|||||||
export * from './vega-logo';
|
export * from './vega-logo';
|
||||||
export * from './traffic-light';
|
export * from './traffic-light';
|
||||||
export * from './toast';
|
export * from './toast';
|
||||||
|
export * from './notification';
|
||||||
|
1
libs/ui-toolkit/src/components/notification/index.ts
Normal file
1
libs/ui-toolkit/src/components/notification/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './notification';
|
@ -0,0 +1,57 @@
|
|||||||
|
import type { Meta, Story } from '@storybook/react';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
import { ExternalLink, Link } from '../link';
|
||||||
|
import { Notification } from './notification';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Notification,
|
||||||
|
title: 'Notification',
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story = ({ intent, message, children }) => (
|
||||||
|
<div className="flex">
|
||||||
|
<Notification intent={intent} message={message}>
|
||||||
|
{children}
|
||||||
|
</Notification>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
message: 'Exercitationem doloremque neque laborum incidunt consectetur amet',
|
||||||
|
children: (
|
||||||
|
<div className="flex space-x-1">
|
||||||
|
<Link>Action</Link>
|
||||||
|
<ExternalLink>External action</ExternalLink>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
...props,
|
||||||
|
intent: Intent.None,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
||||||
|
Primary.args = {
|
||||||
|
...props,
|
||||||
|
intent: Intent.Primary,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Success = Template.bind({});
|
||||||
|
Success.args = {
|
||||||
|
...props,
|
||||||
|
intent: Intent.Success,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Warning = Template.bind({});
|
||||||
|
Warning.args = {
|
||||||
|
...props,
|
||||||
|
intent: Intent.Warning,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Danger = Template.bind({});
|
||||||
|
Danger.args = {
|
||||||
|
...props,
|
||||||
|
intent: Intent.Danger,
|
||||||
|
};
|
71
libs/ui-toolkit/src/components/notification/notification.tsx
Normal file
71
libs/ui-toolkit/src/components/notification/notification.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import type { IconName } from '@blueprintjs/icons';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
|
type NotificationProps = {
|
||||||
|
intent: Intent;
|
||||||
|
message: string;
|
||||||
|
testId?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIcon = (intent: Intent): IconName => {
|
||||||
|
const mapping = {
|
||||||
|
[Intent.None]: IconNames.HELP,
|
||||||
|
[Intent.Primary]: IconNames.INFO_SIGN,
|
||||||
|
[Intent.Success]: IconNames.TICK_CIRCLE,
|
||||||
|
[Intent.Warning]: IconNames.WARNING_SIGN,
|
||||||
|
[Intent.Danger]: IconNames.ERROR,
|
||||||
|
};
|
||||||
|
return mapping[intent] as IconName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Notification = ({
|
||||||
|
intent,
|
||||||
|
message,
|
||||||
|
testId,
|
||||||
|
children,
|
||||||
|
}: NotificationProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-testid={testId || 'notification'}
|
||||||
|
className={classNames(
|
||||||
|
{
|
||||||
|
'border-gray-700 dark:border-gray-300': intent === Intent.None,
|
||||||
|
'border-vega-blue': intent === Intent.Primary,
|
||||||
|
'border-vega-green-dark dark:border-vega-green':
|
||||||
|
intent === Intent.Success,
|
||||||
|
'border-yellow-500': intent === Intent.Warning,
|
||||||
|
'border-vega-pink': intent === Intent.Danger,
|
||||||
|
},
|
||||||
|
'border rounded px-3 py-1 text-xs mb-1 mr-1'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
{
|
||||||
|
'text-gray-700 dark:text-gray-300': intent === Intent.None,
|
||||||
|
'text-vega-blue': intent === Intent.Primary,
|
||||||
|
'text-vega-green-dark dark:text-vega-green':
|
||||||
|
intent === Intent.Success,
|
||||||
|
'text-yellow-600 dark:text-yellow-500': intent === Intent.Warning,
|
||||||
|
'text-vega-pink': intent === Intent.Danger,
|
||||||
|
},
|
||||||
|
'flex items-start'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon size={3} className="mr-1 mt-[2px]" name={getIcon(intent)} />
|
||||||
|
<span
|
||||||
|
title={message}
|
||||||
|
className="whitespace-nowrap overflow-hidden text-ellipsis"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user