From 567345e7aae3e08a5fcbab2398b8f437eca03e1a Mon Sep 17 00:00:00 2001 From: Edd Date: Thu, 1 Feb 2024 15:37:22 +0000 Subject: [PATCH] feat(explorer): extend proposal summary to summarise batch items --- .../src/app/components/links/hash.tsx | 2 +- .../src/app/components/links/index.ts | 1 + .../network-parameter-link.tsx | 30 ++++ .../txs/details/proposal/batch-item.spec.tsx | 168 ++++++++++++++++++ .../txs/details/proposal/batch-item.tsx | 87 +++++++++ .../txs/details/proposal/summary.tsx | 21 ++- .../txs/details/tx-batch-proposal.tsx | 77 ++------ 7 files changed, 323 insertions(+), 63 deletions(-) create mode 100644 apps/explorer/src/app/components/links/network-parameter-link/network-parameter-link.tsx create mode 100644 apps/explorer/src/app/components/txs/details/proposal/batch-item.spec.tsx create mode 100644 apps/explorer/src/app/components/txs/details/proposal/batch-item.tsx diff --git a/apps/explorer/src/app/components/links/hash.tsx b/apps/explorer/src/app/components/links/hash.tsx index 3fd073aa7..f2f96c900 100644 --- a/apps/explorer/src/app/components/links/hash.tsx +++ b/apps/explorer/src/app/components/links/hash.tsx @@ -1,4 +1,4 @@ -export type HashProps = { +export type HashProps = React.HTMLProps & { text: string; truncate?: boolean; }; diff --git a/apps/explorer/src/app/components/links/index.ts b/apps/explorer/src/app/components/links/index.ts index ec0211d43..e835eba87 100644 --- a/apps/explorer/src/app/components/links/index.ts +++ b/apps/explorer/src/app/components/links/index.ts @@ -2,4 +2,5 @@ export { default as BlockLink } from './block-link/block-link'; export { default as PartyLink } from './party-link/party-link'; export { default as NodeLink } from './node-link/node-link'; export { default as MarketLink } from './market-link/market-link'; +export { default as NetworkParameterLink } from './network-parameter-link/network-parameter-link'; export * from './asset-link/asset-link'; diff --git a/apps/explorer/src/app/components/links/network-parameter-link/network-parameter-link.tsx b/apps/explorer/src/app/components/links/network-parameter-link/network-parameter-link.tsx new file mode 100644 index 000000000..2b8bbc54c --- /dev/null +++ b/apps/explorer/src/app/components/links/network-parameter-link/network-parameter-link.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Routes } from '../../../routes/route-names'; +import { Link } from 'react-router-dom'; + +import type { ComponentProps } from 'react'; +import Hash from '../hash'; + +export type NetworkParameterLinkProps = Partial> & { + parameter: string; +}; + +/** + * Links a given network parameter to the relevant page and anchor on the page + */ +const NetworkParameterLink = ({ + parameter, + ...props +}: NetworkParameterLinkProps) => { + return ( + + + + ); +}; + +export default NetworkParameterLink; diff --git a/apps/explorer/src/app/components/txs/details/proposal/batch-item.spec.tsx b/apps/explorer/src/app/components/txs/details/proposal/batch-item.spec.tsx new file mode 100644 index 000000000..1d017444a --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/proposal/batch-item.spec.tsx @@ -0,0 +1,168 @@ +import { render, screen } from '@testing-library/react'; +import { BatchItem } from './batch-item'; +import { MemoryRouter } from 'react-router-dom'; +import { MockedProvider } from '@apollo/client/testing'; + +describe('BatchItem', () => { + it('Renders "New spot market"', () => { + const item = { + newSpotMarket: {}, + }; + render(); + expect(screen.getByText('New spot market')).toBeInTheDocument(); + }); + + it('Renders "New transfer"', () => { + const item = { + newTransfer: {}, + }; + render(); + expect(screen.getByText('New transfer')).toBeInTheDocument(); + }); + + it('Renders "Update asset" with assetId', () => { + const item = { + updateAsset: { + assetId: 'asset123', + }, + }; + render( + + + + + + ); + expect(screen.getByText('Update asset')).toBeInTheDocument(); + expect(screen.getByText('asset123')).toBeInTheDocument(); + }); + + it('Renders "Update asset" even if assetId is not set', () => { + const item = { + updateAsset: { + assetId: undefined, + }, + }; + render(); + expect(screen.getByText('Update asset')).toBeInTheDocument(); + }); + + it('Renders "Update market state" with marketId', () => { + const item = { + updateMarketState: { + changes: { + marketId: 'market123', + }, + }, + }; + render( + + + + + + ); + expect(screen.getByText('Update market state')).toBeInTheDocument(); + expect(screen.getByText('market123')).toBeInTheDocument(); + }); + + it('Renders "Update market state" even if marketId is not set', () => { + const item = { + updateMarketState: { + changes: { + marketId: undefined, + }, + }, + }; + render( + + + + + + ); + expect(screen.getByText('Update market state')).toBeInTheDocument(); + }); + + it('Renders "Update network parameter" with parameter', () => { + const item = { + updateNetworkParameter: { + changes: { + key: 'parameter123', + }, + }, + }; + render( + + + + + + ); + expect(screen.getByText('Update network parameter')).toBeInTheDocument(); + expect(screen.getByText('parameter123')).toBeInTheDocument(); + }); + + it('Renders "Update network parameter" even if parameter is not set', () => { + const item = { + updateNetworkParameter: { + changes: { + key: undefined, + }, + }, + }; + render(); + expect(screen.getByText('Update network parameter')).toBeInTheDocument(); + }); + + it('Renders "Update referral program"', () => { + const item = { + updateReferralProgram: {}, + }; + render(); + expect(screen.getByText('Update referral program')).toBeInTheDocument(); + }); + + it('Renders "Update spot market" with marketId', () => { + const item = { + updateSpotMarket: { + marketId: 'market123', + }, + }; + render( + + + + + + ); + expect(screen.getByText('Update spot market')).toBeInTheDocument(); + expect(screen.getByText('market123')).toBeInTheDocument(); + }); + + it('Renders "Update spot market" even if marketId is not set', () => { + const item = { + updateSpotMarket: { + marketId: undefined, + }, + }; + render( + + + + + + ); + expect(screen.getByText('Update spot market')).toBeInTheDocument(); + }); + + it('Renders "Update volume discount program"', () => { + const item = { + updateVolumeDiscountProgram: {}, + }; + render(); + expect( + screen.getByText('Update volume discount program') + ).toBeInTheDocument(); + }); +}); diff --git a/apps/explorer/src/app/components/txs/details/proposal/batch-item.tsx b/apps/explorer/src/app/components/txs/details/proposal/batch-item.tsx new file mode 100644 index 000000000..31a875ad6 --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/proposal/batch-item.tsx @@ -0,0 +1,87 @@ +import { t } from '@vegaprotocol/i18n'; +import { AssetLink, MarketLink, NetworkParameterLink } from '../../../links'; +import type { components } from '../../../../../types/explorer'; +import Hash from '../../../links/hash'; + +type Item = components['schemas']['vegaBatchProposalTermsChange']; + +export interface BatchItemProps { + item: Item; +} + +/** + * Produces a one line summary for an item in a batch proposal. Could + * easily be adapted to summarise individual proposals, but there is no + * place for that yet. + * + * Details (like IDs) should be shown and linked if available, but handled + * if not available. This is adequate as the ProposalSummary component contains + * a JSON viewer for the full proposal. + */ +export const BatchItem = ({ item }: BatchItemProps) => { + if (item.cancelTransfer) { + const transferId = item?.cancelTransfer?.changes?.transferId || false; + return ( + + {t('Cancel transfer')}  + {transferId && ( + + )} + + ); + } else if (item.newFreeform) { + return {t('New freeform')}; + } else if (item.newMarket) { + return {t('New market')}; + } else if (item.newSpotMarket) { + return {t('New spot market')}; + } else if (item.newTransfer) { + return {t('New transfer')}; + } else if (item.updateAsset) { + const assetId = item?.updateAsset?.assetId || false; + return ( + + {t('Update asset')} + {assetId && } + + ); + } else if (item.updateMarket) { + const marketId = item?.updateMarket?.marketId || false; + return ( + + {t('Update market')}{' '} + {marketId && } + + ); + } else if (item.updateMarketState) { + const marketId = item?.updateMarketState?.changes?.marketId || false; + return ( + + {t('Update market state')} + {marketId && } + + ); + } else if (item.updateNetworkParameter) { + const param = item?.updateNetworkParameter?.changes?.key || false; + return ( + + {t('Update network parameter')} + {param && } + + ); + } else if (item.updateReferralProgram) { + return {t('Update referral program')}; + } else if (item.updateSpotMarket) { + const marketId = item?.updateSpotMarket?.marketId || ''; + return ( + + {t('Update spot market')} + + + ); + } else if (item.updateVolumeDiscountProgram) { + return {t('Update volume discount program')}; + } + + return {t('Unknown proposal type')}; +}; diff --git a/apps/explorer/src/app/components/txs/details/proposal/summary.tsx b/apps/explorer/src/app/components/txs/details/proposal/summary.tsx index 48c71cc6c..0972e3d07 100644 --- a/apps/explorer/src/app/components/txs/details/proposal/summary.tsx +++ b/apps/explorer/src/app/components/txs/details/proposal/summary.tsx @@ -1,6 +1,4 @@ -import type { ProposalTerms } from '../tx-proposal'; import { useState } from 'react'; -import type { components } from '../../../../../types/explorer'; import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog'; import ProposalLink from '../../../links/proposal-link/proposal-link'; import truncate from 'lodash/truncate'; @@ -9,7 +7,12 @@ import ReactMarkdown from 'react-markdown'; import { ProposalDate } from './proposal-date'; import { t } from '@vegaprotocol/i18n'; +import type { ProposalTerms } from '../tx-proposal'; +import type { components } from '../../../../../types/explorer'; +import { BatchItem } from './batch-item'; + type Rationale = components['schemas']['vegaProposalRationale']; +type Batch = components['schemas']['v1BatchProposalSubmissionTerms']['changes']; type ProposalTermsDialog = { open: boolean; @@ -21,6 +24,7 @@ interface ProposalSummaryProps { id: string; rationale?: Rationale; terms?: ProposalTerms; + batch?: Batch; } /** @@ -31,6 +35,7 @@ export const ProposalSummary = ({ id, rationale, terms, + batch, }: ProposalSummaryProps) => { const [dialog, setDialog] = useState({ open: false, @@ -72,6 +77,18 @@ export const ProposalSummary = ({ )} + {batch && ( +
+

{t('Changes')}

+
    + {batch.map((change, index) => ( +
  1. + +
  2. + ))} +
+
+ )}