From c55a15dc534bc260e8f1dd228c087ec86f82e6eb Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 2 Jun 2022 10:54:47 +0200 Subject: [PATCH] Extract Latency/API stats reusable chart (#7679) * Extract Latency/API stats reusable chart * Adjust --- package.json | 28 +-- packages/apps-config/package.json | 6 +- .../page-assets/src/Balances/useBalances.ts | 10 +- packages/page-contracts/package.json | 2 +- packages/page-council/src/useCounter.ts | 2 +- .../page-democracy/src/Execute/Scheduler.tsx | 4 +- .../src/Overview/TreasuryCell.tsx | 4 +- packages/page-explorer/src/Api/index.tsx | 42 ++-- packages/page-explorer/src/Latency/Chart.tsx | 48 ++++ packages/page-explorer/src/Latency/index.tsx | 81 +++---- packages/page-explorer/src/Latency/types.ts | 5 + packages/page-explorer/src/index.tsx | 2 +- packages/page-gilt/src/Overview/useInfo.ts | 4 +- packages/page-membership/src/useCounter.ts | 4 +- packages/page-nfts/src/useCollectionIds.ts | 4 +- .../page-society/src/Candidates/Candidate.tsx | 8 +- .../page-society/src/Overview/Defender.tsx | 4 +- packages/page-society/src/Suspended/index.tsx | 8 +- .../Account/InputValidationController.tsx | 8 +- packages/page-tech-comm/src/useCounter.ts | 4 +- .../page-treasury/src/Tips/TipEndorse.tsx | 4 +- packages/page-treasury/src/Tips/TipReason.tsx | 4 +- packages/page-treasury/src/useTipHashes.ts | 4 +- packages/react-api/package.json | 4 +- packages/react-hooks/src/types.ts | 3 +- packages/react-hooks/src/useBestHash.ts | 4 +- packages/react-hooks/src/useCall.ts | 15 +- packages/react-hooks/src/useCallMulti.ts | 5 +- .../react-hooks/src/useCollectiveMembers.ts | 4 +- packages/react-hooks/src/useIpfsFetch.ts | 12 +- packages/react-hooks/src/useProxies.ts | 33 +-- packages/react-hooks/src/useSudo.ts | 4 +- packages/react-query/src/Bonded.tsx | 8 +- packages/test-support/package.json | 2 +- yarn.lock | 216 +++++++++--------- 35 files changed, 317 insertions(+), 283 deletions(-) create mode 100644 packages/page-explorer/src/Latency/Chart.tsx diff --git a/package.json b/package.json index 69feb19628..9f28817c66 100644 --- a/package.json +++ b/package.json @@ -91,24 +91,24 @@ "webpack-cli": "^4.9.2" }, "resolutions": { - "@polkadot/api": "^8.6.3-8", - "@polkadot/api-augment": "^8.6.3-8", - "@polkadot/api-base": "^8.6.3-8", - "@polkadot/api-contract": "^8.6.3-8", - "@polkadot/api-derive": "^8.6.3-8", + "@polkadot/api": "^8.6.3-9", + "@polkadot/api-augment": "^8.6.3-9", + "@polkadot/api-base": "^8.6.3-9", + "@polkadot/api-contract": "^8.6.3-9", + "@polkadot/api-derive": "^8.6.3-9", "@polkadot/hw-ledger": "^9.3.2-1", "@polkadot/keyring": "^9.3.2-1", "@polkadot/networks": "^9.3.2-1", "@polkadot/phishing": "^0.13.51", - "@polkadot/rpc-augment": "^8.6.3-8", - "@polkadot/rpc-core": "^8.6.3-8", - "@polkadot/rpc-provider": "^8.6.3-8", - "@polkadot/types": "^8.6.3-8", - "@polkadot/types-augment": "^8.6.3-8", - "@polkadot/types-codec": "^8.6.3-8", - "@polkadot/types-create": "^8.6.3-8", - "@polkadot/types-known": "^8.6.3-8", - "@polkadot/types-support": "^8.6.3-8", + "@polkadot/rpc-augment": "^8.6.3-9", + "@polkadot/rpc-core": "^8.6.3-9", + "@polkadot/rpc-provider": "^8.6.3-9", + "@polkadot/types": "^8.6.3-9", + "@polkadot/types-augment": "^8.6.3-9", + "@polkadot/types-codec": "^8.6.3-9", + "@polkadot/types-create": "^8.6.3-9", + "@polkadot/types-known": "^8.6.3-9", + "@polkadot/types-support": "^8.6.3-9", "@polkadot/util": "^9.3.2-1", "@polkadot/util-crypto": "^9.3.2-1", "@polkadot/wasm-crypto": "^6.1.1", diff --git a/packages/apps-config/package.json b/packages/apps-config/package.json index 688c23c699..d966da8e3c 100644 --- a/packages/apps-config/package.json +++ b/packages/apps-config/package.json @@ -29,10 +29,10 @@ "@metaverse-network-sdk/type-definitions": "^0.0.1-13", "@parallel-finance/type-definitions": "1.7.1", "@phala/typedefs": "0.2.30", - "@polkadot/api": "^8.6.3-8", - "@polkadot/api-derive": "^8.6.3-8", + "@polkadot/api": "^8.6.3-9", + "@polkadot/api-derive": "^8.6.3-9", "@polkadot/networks": "^9.3.2-1", - "@polkadot/types": "^8.6.3-8", + "@polkadot/types": "^8.6.3-9", "@polkadot/util": "^9.3.2-1", "@polkadot/x-fetch": "^9.3.2-1", "@polymathnetwork/polymesh-types": "0.0.2", diff --git a/packages/page-assets/src/Balances/useBalances.ts b/packages/page-assets/src/Balances/useBalances.ts index 2e94d6c7d7..0b0f30a3e7 100644 --- a/packages/page-assets/src/Balances/useBalances.ts +++ b/packages/page-assets/src/Balances/useBalances.ts @@ -5,6 +5,8 @@ import type { PalletAssetsAssetAccount } from '@polkadot/types/lookup'; import type { Option } from '@polkadot/types-codec'; import type { BN } from '@polkadot/util'; +import { useMemo } from 'react'; + import { createNamedHook, useAccounts, useApi, useCall } from '@polkadot/react-hooks'; interface AccountResult { @@ -21,7 +23,7 @@ function isOptional (value: PalletAssetsAssetAccount | Option).isSome || (value as Option).isNone; } -const queryOptions = { +const OPTS = { transform: ([[params], accounts]: [[[BN, string][]], (PalletAssetsAssetAccount | Option)[]]): Result => ({ accounts: params .map(([, accountId], index) => { @@ -46,7 +48,11 @@ const queryOptions = { function useBalancesImpl (id?: BN | null): AccountResult[] | null { const { api } = useApi(); const { allAccounts } = useAccounts(); - const query = useCall(id && api.query.assets.account.multi, id && [allAccounts.map((a) => [id, a])], queryOptions); + const keys = useMemo( + () => [allAccounts.map((a) => [id, a])], + [allAccounts, id] + ); + const query = useCall(keys && api.query.assets.account.multi, keys, OPTS); return (query && id && (query.assetId === id) && query.accounts) || null; } diff --git a/packages/page-contracts/package.json b/packages/page-contracts/package.json index 39ebbf7c6b..1b427969c7 100644 --- a/packages/page-contracts/package.json +++ b/packages/page-contracts/package.json @@ -14,6 +14,6 @@ "version": "0.115.3-45", "dependencies": { "@babel/runtime": "^7.18.0", - "@polkadot/api-contract": "^8.6.3-8" + "@polkadot/api-contract": "^8.6.3-9" } } diff --git a/packages/page-council/src/useCounter.ts b/packages/page-council/src/useCounter.ts index e4aaa75cb2..2f795a6df6 100644 --- a/packages/page-council/src/useCounter.ts +++ b/packages/page-council/src/useCounter.ts @@ -6,7 +6,7 @@ import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types'; import { createNamedHook, useAccounts, useApi, useCall } from '@polkadot/react-hooks'; const transformCounter = { - transform: (motions: DeriveCollectiveProposal[]) => motions.filter(({ votes }): boolean => !!votes).length + transform: (motions: DeriveCollectiveProposal[]) => motions.filter(({ votes }) => !!votes).length }; function useCounterImpl (): number { diff --git a/packages/page-democracy/src/Execute/Scheduler.tsx b/packages/page-democracy/src/Execute/Scheduler.tsx index f71786e375..8b0a5f1e8f 100644 --- a/packages/page-democracy/src/Execute/Scheduler.tsx +++ b/packages/page-democracy/src/Execute/Scheduler.tsx @@ -18,7 +18,7 @@ interface Props { className?: string; } -const transformEntries = { +const OPT_SCHED = { transform: (entries: [{ args: [BlockNumber] }, Option[]][]): ScheduledExt[] => { return entries .filter(([, all]) => all.some((o) => o.isSome)) @@ -51,7 +51,7 @@ function Schedule ({ className = '' }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const bestNumber = useBestNumber(); - const items = useCall(api.query.scheduler.agenda.entries, undefined, transformEntries); + const items = useCall(api.query.scheduler.agenda.entries, undefined, OPT_SCHED); const filtered = useMemo( () => bestNumber && items && items.filter(({ blockNumber }) => blockNumber.gte(bestNumber)), diff --git a/packages/page-democracy/src/Overview/TreasuryCell.tsx b/packages/page-democracy/src/Overview/TreasuryCell.tsx index 718450f1c3..bda2412ace 100644 --- a/packages/page-democracy/src/Overview/TreasuryCell.tsx +++ b/packages/page-democracy/src/Overview/TreasuryCell.tsx @@ -36,7 +36,7 @@ interface ParamState { const DEFAULT_PARAMS: ParamState = { params: [], values: [] }; -const transformProposal = { +const OPT_PROP = { transform: (optProp: Option) => optProp.unwrapOr(null) }; @@ -44,7 +44,7 @@ function TreasuryCell ({ className = '', value }: Props): React.ReactElement value.unwrap()); - const proposal = useCall(api.query.treasury.proposals, [proposalId], transformProposal); + const proposal = useCall(api.query.treasury.proposals, [proposalId], OPT_PROP); const [{ params, values }, setExtracted] = useState(DEFAULT_PARAMS); useEffect((): void => { diff --git a/packages/page-explorer/src/Api/index.tsx b/packages/page-explorer/src/Api/index.tsx index ceb871da8f..c1ab70c8f6 100644 --- a/packages/page-explorer/src/Api/index.tsx +++ b/packages/page-explorer/src/Api/index.tsx @@ -6,9 +6,10 @@ import type { Stats } from '@polkadot/react-components/ApiStats/types'; import React, { useContext, useMemo } from 'react'; import styled from 'styled-components'; -import { ApiStatsContext, CardSummary, Chart, Spinner, SummaryBox } from '@polkadot/react-components'; +import { ApiStatsContext, CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; import { formatNumber } from '@polkadot/util'; +import Chart from '../Latency/Chart'; import { useTranslation } from '../translate'; interface Props { @@ -30,13 +31,6 @@ interface ChartInfo { const COLORS_BYTES = ['#00448c', '#008c44', '#acacac']; const COLORS_REQUESTS = ['#008c8c', '#00448c', '#8c4400', '#acacac']; -const OPTIONS = { - animation: { - duration: 0 - }, - aspectRatio: 6, - maintainAspectRatio: true -}; function getPoints (all: Stats[]): ChartInfo { const bytesChart: ChartContents = { @@ -124,26 +118,18 @@ function Api ({ className }: Props): React.ReactElement { ('total sub')}>{formatNumber(tSub)} -
-

{t('requests & subscriptions')}

- -
-
-

{t('bytes')}

- -
+ ('requests')} + value={requestsChart} + /> + ('transfer')} + value={bytesChart} + /> ); } diff --git a/packages/page-explorer/src/Latency/Chart.tsx b/packages/page-explorer/src/Latency/Chart.tsx new file mode 100644 index 0000000000..ae7131beff --- /dev/null +++ b/packages/page-explorer/src/Latency/Chart.tsx @@ -0,0 +1,48 @@ +// Copyright 2017-2022 @polkadot/app-explorer authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ChartContents } from './types'; + +import React from 'react'; +import styled from 'styled-components'; + +import { Chart } from '@polkadot/react-components'; + +interface Props { + className?: string; + colors: string[]; + legends: string[]; + title: string; + value: ChartContents; +} + +const OPTIONS = { + animation: { + duration: 0 + }, + aspectRatio: 6, + maintainAspectRatio: true +}; + +function ChartDisplay ({ className, colors, legends, title, value: { labels, values } }: Props): React.ReactElement { + return ( +
+

{title}

+ +
+ ); +} + +export default React.memo(styled(ChartDisplay)` + background: var(--bg-table); + border: 1px solid var(--border-table); + border-radius: 0.25rem; + margin-bottom: 1rem; + padding: 1rem 1.5rem; +`); diff --git a/packages/page-explorer/src/Latency/index.tsx b/packages/page-explorer/src/Latency/index.tsx index 4e5931aeeb..553499b4c2 100644 --- a/packages/page-explorer/src/Latency/index.tsx +++ b/packages/page-explorer/src/Latency/index.tsx @@ -1,26 +1,22 @@ // Copyright 2017-2022 @polkadot/app-explorer authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Detail } from './types'; +import type { ChartContents, Detail } from './types'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { CardSummary, Chart, Spinner, SummaryBox } from '@polkadot/react-components'; +import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components'; import { formatNumber } from '@polkadot/util'; import { useTranslation } from '../translate'; +import Chart from './Chart'; import useLatency from './useLatency'; interface Props { className?: string; } -interface ChartContents { - labels: string[]; - values: number[][]; -} - interface ChartInfo { blockLast: number; blocks: ChartContents; @@ -33,13 +29,6 @@ const COLORS_TIMES = ['#8c8c00', '#acacac']; const COLORS_BLOCKS = ['#008c8c', '#acacac']; const COLORS_EVENTS = ['#00448c', '#8c0044', '#acacac']; const COLORS_TXS = ['#448c00', '#acacac']; -const OPTIONS = { - animation: { - duration: 0 - }, - aspectRatio: 6, - maintainAspectRatio: true -}; function getPoints (details: Detail[], timeAvg: number): ChartInfo { const blocks: ChartContents = { @@ -144,46 +133,30 @@ function Latency ({ className }: Props): React.ReactElement | null { ('last')}>{formatTime(blockLast, 1)} -
-

{t('blocktimes (last {{num}} blocks)', { replace: { num: times.labels.length } })}

- -
-
-

{t('blocksize (last {{num}} blocks)', { replace: { num: blocks.labels.length } })}

- -
-
-

{t('extrinsics (last {{num}} blocks)', { replace: { num: extrinsics.labels.length } })}

- -
-
-

{t('events (last {{num}} blocks)', { replace: { num: events.labels.length } })}

- -
+ ('blocktimes (last {{num}} blocks)', { replace: { num: times.labels.length } })} + value={times} + /> + ('blocksize (last {{num}} blocks)', { replace: { num: blocks.labels.length } })} + value={blocks} + /> + ('extrinsics (last {{num}} blocks)', { replace: { num: extrinsics.labels.length } })} + value={extrinsics} + /> + ('events (last {{num}} blocks)', { replace: { num: events.labels.length } })} + value={events} + /> ); } diff --git a/packages/page-explorer/src/Latency/types.ts b/packages/page-explorer/src/Latency/types.ts index eef812198a..49df736746 100644 --- a/packages/page-explorer/src/Latency/types.ts +++ b/packages/page-explorer/src/Latency/types.ts @@ -28,3 +28,8 @@ export interface Result { timeMax: number; timeMin: number; } + +export interface ChartContents { + labels: string[]; + values: number[][]; +} diff --git a/packages/page-explorer/src/index.tsx b/packages/page-explorer/src/index.tsx index dc0fbc0599..3a30f5be0e 100644 --- a/packages/page-explorer/src/index.tsx +++ b/packages/page-explorer/src/index.tsx @@ -65,7 +65,7 @@ function createItemsRef (t: TFunction): TabItem[] { text: t('Node info') }, { - isHidden: true, + // isHidden: true, name: 'api', text: t('API stats') } diff --git a/packages/page-gilt/src/Overview/useInfo.ts b/packages/page-gilt/src/Overview/useInfo.ts index 8d2c29084d..bc29271ff9 100644 --- a/packages/page-gilt/src/Overview/useInfo.ts +++ b/packages/page-gilt/src/Overview/useInfo.ts @@ -14,7 +14,7 @@ interface State { info?: GiltInfo; } -const optGiltInfo = { +const OPT_GILT = { defaultValue: {} as GiltInfo, transform: ([activeTotal, queueTotals]: [ActiveGiltsTotal, [u32, BalanceOf][]]): GiltInfo => ({ activeIndex: activeTotal.index.isZero() @@ -32,7 +32,7 @@ function useInfoImpl (): State { const info = useCallMulti([ api.query.gilt.activeTotal, api.query.gilt.queueTotals - ], optGiltInfo); + ], OPT_GILT); // useEffect((): void => { // info.activeIndex && diff --git a/packages/page-membership/src/useCounter.ts b/packages/page-membership/src/useCounter.ts index b2daa89325..2dbb67f2bb 100644 --- a/packages/page-membership/src/useCounter.ts +++ b/packages/page-membership/src/useCounter.ts @@ -5,13 +5,13 @@ import type { Hash } from '@polkadot/types/interfaces'; import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks'; -const transformCounter = { +const OPT = { transform: (proposals: Hash[]) => proposals.length }; function useCounterImpl (): number { const { api, isApiReady } = useApi(); - const counter = useCall(isApiReady && api.query.membership?.proposals, undefined, transformCounter) || 0; + const counter = useCall(isApiReady && api.query.membership?.proposals, undefined, OPT) || 0; return counter; } diff --git a/packages/page-nfts/src/useCollectionIds.ts b/packages/page-nfts/src/useCollectionIds.ts index 175f68657e..d81755fa60 100644 --- a/packages/page-nfts/src/useCollectionIds.ts +++ b/packages/page-nfts/src/useCollectionIds.ts @@ -7,7 +7,7 @@ import type { EventRecord } from '@polkadot/types/interfaces'; import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@polkadot/react-hooks'; -const keyOptions = { +const OPT_KEYS = { transform: (keys: StorageKey<[u32]>[]): u32[] => keys .map(({ args: [id] }) => id) @@ -31,7 +31,7 @@ function filter (records: EventRecord[]): Changes { function useCollectionIdsImpl (): u32[] { const { api } = useApi(); - const startValue = useMapKeys(api.query.uniques.class, keyOptions); + const startValue = useMapKeys(api.query.uniques.class, OPT_KEYS); return useEventChanges([ api.events.uniques.Created, diff --git a/packages/page-society/src/Candidates/Candidate.tsx b/packages/page-society/src/Candidates/Candidate.tsx index 7694ba8623..539742c268 100644 --- a/packages/page-society/src/Candidates/Candidate.tsx +++ b/packages/page-society/src/Candidates/Candidate.tsx @@ -6,7 +6,7 @@ import type { Option } from '@polkadot/types'; import type { AccountId, SocietyVote } from '@polkadot/types/interfaces'; import type { VoteType } from '../types'; -import React from 'react'; +import React, { useMemo } from 'react'; import { AddressSmall } from '@polkadot/react-components'; import { useApi, useCall } from '@polkadot/react-hooks'; @@ -25,7 +25,11 @@ interface Props { function Candidate ({ allMembers, isMember, ownMembers, value: { accountId, kind, value } }: Props): React.ReactElement { const { api } = useApi(); - const votes = useCall(api.query.society.votes.multi, [allMembers.map((memberId): [AccountId, string] => [accountId, memberId])], { + const keys = useMemo( + () => [allMembers.map((memberId): [AccountId, string] => [accountId, memberId])], + [accountId, allMembers] + ); + const votes = useCall(api.query.society.votes.multi, keys, { transform: (voteOpts: Option[]): VoteType[] => voteOpts .map((voteOpt, index): [string, Option] => [allMembers[index], voteOpt]) diff --git a/packages/page-society/src/Overview/Defender.tsx b/packages/page-society/src/Overview/Defender.tsx index d3658f6ce5..48a8a3bbe1 100644 --- a/packages/page-society/src/Overview/Defender.tsx +++ b/packages/page-society/src/Overview/Defender.tsx @@ -21,7 +21,7 @@ interface Props { ownMembers: string[]; } -const transformVotes = { +const OPT_VOTES = { transform: (members: DeriveSocietyMember[]): VoteType[] => members .filter(({ vote }): boolean => !!vote) @@ -31,7 +31,7 @@ const transformVotes = { function Defender ({ className = '', info, isMember, ownMembers }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); - const votes = useCall(api.derive.society.members, undefined, transformVotes); + const votes = useCall(api.derive.society.members, undefined, OPT_VOTES); const headerRef = useRef([ [t('defender'), 'start'], diff --git a/packages/page-society/src/Suspended/index.tsx b/packages/page-society/src/Suspended/index.tsx index 32dd65c755..665e6f4f3d 100644 --- a/packages/page-society/src/Suspended/index.tsx +++ b/packages/page-society/src/Suspended/index.tsx @@ -24,7 +24,7 @@ interface CandidateSuspend { bid: PalletSocietyBidKind; } -const optExtractCandidates = { +const OPT_CAN = { transform: (entries: [StorageKey<[AccountId]>, Option>][]): CandidateSuspend[] => entries .filter(([{ args: [accountId] }, opt]) => opt.isSome && accountId) @@ -36,7 +36,7 @@ const optExtractCandidates = { .sort((a, b) => a.balance.cmp(b.balance)) }; -const optExtractAccounts = { +const OPT_ACC = { transform: (keys: StorageKey<[AccountId]>[]): AccountId[] => keys .map(({ args: [accountId] }) => accountId) @@ -46,8 +46,8 @@ const optExtractAccounts = { function Suspended ({ className }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); - const candidates = useCall(api.query.society.suspendedCandidates.entries, undefined, optExtractCandidates); - const members = useCall(api.query.society.suspendedMembers.keys, undefined, optExtractAccounts); + const candidates = useCall(api.query.society.suspendedCandidates.entries, undefined, OPT_CAN); + const members = useCall(api.query.society.suspendedMembers.keys, undefined, OPT_ACC); const headerRef = useRef({ candidates: [ diff --git a/packages/page-staking/src/Actions/Account/InputValidationController.tsx b/packages/page-staking/src/Actions/Account/InputValidationController.tsx index 51ff6e172a..6a7885ead5 100644 --- a/packages/page-staking/src/Actions/Account/InputValidationController.tsx +++ b/packages/page-staking/src/Actions/Account/InputValidationController.tsx @@ -24,14 +24,14 @@ interface ErrorState { isFatal: boolean; } -const transformBonded = { +const OPT_BOND = { transform: (value: Option): string | null => value.isSome ? value.unwrap().toString() : null }; -const transformStash = { +const OPT_STASH = { transform: (value: Option): string | null => value.isSome ? value.unwrap().stash.toString() @@ -41,8 +41,8 @@ const transformStash = { function ValidateController ({ accountId, controllerId, defaultController, onError }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); - const bondedId = useCall(controllerId ? api.query.staking.bonded : null, [controllerId], transformBonded); - const stashId = useCall(controllerId ? api.query.staking.ledger : null, [controllerId], transformStash); + const bondedId = useCall(controllerId ? api.query.staking.bonded : null, [controllerId], OPT_BOND); + const stashId = useCall(controllerId ? api.query.staking.ledger : null, [controllerId], OPT_STASH); const allBalances = useCall(controllerId ? api.derive.balances?.all : null, [controllerId]); const [{ error, isFatal }, setError] = useState({ error: null, isFatal: false }); diff --git a/packages/page-tech-comm/src/useCounter.ts b/packages/page-tech-comm/src/useCounter.ts index 8c0f084da0..b76ee2db2e 100644 --- a/packages/page-tech-comm/src/useCounter.ts +++ b/packages/page-tech-comm/src/useCounter.ts @@ -5,13 +5,13 @@ import type { Hash } from '@polkadot/types/interfaces'; import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks'; -const transformCounter = { +const OPT = { transform: (proposals: Hash[]) => proposals.length }; function useCounterImpl (): number { const { api, isApiReady } = useApi(); - const counter = useCall(isApiReady && api.derive.technicalCommittee?.proposals, undefined, transformCounter) || 0; + const counter = useCall(isApiReady && api.derive.technicalCommittee?.proposals, undefined, OPT) || 0; return counter; } diff --git a/packages/page-treasury/src/Tips/TipEndorse.tsx b/packages/page-treasury/src/Tips/TipEndorse.tsx index 9459c27c2d..88dfaaaffc 100644 --- a/packages/page-treasury/src/Tips/TipEndorse.tsx +++ b/packages/page-treasury/src/Tips/TipEndorse.tsx @@ -21,7 +21,7 @@ interface Props { recipient: string; } -const transformBalance = { +const OPT = { transform: ({ freeBalance, reservedBalance }: DeriveBalancesAll): BN => freeBalance.add(reservedBalance) }; @@ -32,7 +32,7 @@ function TipEndorse ({ defaultId, hash, isMember, isTipped, median, members, rec const [isOpen, toggleOpen] = useToggle(); const [accountId, setAccountId] = useState(defaultId); const [value, setValue] = useState(); - const totalBalance = useCall(api.derive.balances?.all, [recipient], transformBalance); + const totalBalance = useCall(api.derive.balances?.all, [recipient], OPT); const tipTx = (api.tx.tips || api.tx.treasury).tip; diff --git a/packages/page-treasury/src/Tips/TipReason.tsx b/packages/page-treasury/src/Tips/TipReason.tsx index 1bdf562ad6..7c95598377 100644 --- a/packages/page-treasury/src/Tips/TipReason.tsx +++ b/packages/page-treasury/src/Tips/TipReason.tsx @@ -13,7 +13,7 @@ interface Props { hash: Hash; } -const transformTip = { +const OPT = { transform: (optBytes: Option) => optBytes.isSome ? hexToString(optBytes.unwrap().toHex()) @@ -22,7 +22,7 @@ const transformTip = { function TipReason ({ hash }: Props): React.ReactElement { const { api } = useApi(); - const reasonText = useCall((api.query.tips || api.query.treasury).reasons, [hash], transformTip); + const reasonText = useCall((api.query.tips || api.query.treasury).reasons, [hash], OPT); return ( {reasonText || hash.toHex()} diff --git a/packages/page-treasury/src/useTipHashes.ts b/packages/page-treasury/src/useTipHashes.ts index 78222d75d1..78d3a423b3 100644 --- a/packages/page-treasury/src/useTipHashes.ts +++ b/packages/page-treasury/src/useTipHashes.ts @@ -6,7 +6,7 @@ import type { Hash } from '@polkadot/types/interfaces'; import { createNamedHook, useApi, useEventTrigger, useMapKeys } from '@polkadot/react-hooks'; -const options = { +const OPT = { transform: (keys: StorageKey<[Hash]>[]): string[] => keys.map(({ args: [hash] }) => hash.toHex()) }; @@ -19,7 +19,7 @@ function useTipHashesImpl (): string[] | undefined { api.events.tips?.TipRetracted ]); - return useMapKeys((api.query.tips || api.query.treasury)?.tips, options, trigger.blockHash); + return useMapKeys((api.query.tips || api.query.treasury)?.tips, OPT, trigger.blockHash); } export default createNamedHook('useTipHashes', useTipHashesImpl); diff --git a/packages/react-api/package.json b/packages/react-api/package.json index 83711c41ed..2b22ff6198 100644 --- a/packages/react-api/package.json +++ b/packages/react-api/package.json @@ -14,10 +14,10 @@ "version": "0.115.3-45", "dependencies": { "@babel/runtime": "^7.18.0", - "@polkadot/api": "^8.6.3-8", + "@polkadot/api": "^8.6.3-9", "@polkadot/extension-compat-metamask": "^0.43.3-4", "@polkadot/extension-dapp": "^0.43.3-4", - "@polkadot/rpc-provider": "^8.6.3-8", + "@polkadot/rpc-provider": "^8.6.3-9", "fflate": "^0.7.3", "rxjs": "^7.5.5" } diff --git a/packages/react-hooks/src/types.ts b/packages/react-hooks/src/types.ts index 74d8ae9a08..4ed5979786 100644 --- a/packages/react-hooks/src/types.ts +++ b/packages/react-hooks/src/types.ts @@ -1,6 +1,7 @@ // Copyright 2017-2022 @polkadot/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ApiPromise } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/api-derive/types'; import type { DisplayedJudgement } from '@polkadot/react-components/types'; @@ -15,7 +16,7 @@ export type CallParams = [] | CallParam[]; export interface CallOptions { defaultValue?: T; paramMap?: (params: any) => CallParams; - transform?: (value: any) => T; + transform?: (value: any, api: ApiPromise) => T; withParams?: boolean; withParamsTransform?: boolean; } diff --git a/packages/react-hooks/src/useBestHash.ts b/packages/react-hooks/src/useBestHash.ts index b61e4e9e56..daa90f7f6b 100644 --- a/packages/react-hooks/src/useBestHash.ts +++ b/packages/react-hooks/src/useBestHash.ts @@ -7,14 +7,14 @@ import { createNamedHook } from './createNamedHook'; import { useApi } from './useApi'; import { useCall } from './useCall'; -const optCall = { +const OPT = { transform: (header: Header) => header.hash.toHex() }; function useBestHashImpl (): string | undefined { const { api } = useApi(); - return useCall(api.rpc.chain.subscribeNewHeads, undefined, optCall); + return useCall(api.rpc.chain.subscribeNewHeads, undefined, OPT); } export const useBestHash = createNamedHook('useBestHash', useBestHashImpl); diff --git a/packages/react-hooks/src/useCall.ts b/packages/react-hooks/src/useCall.ts index 4dff1bda46..5ed38a7f79 100644 --- a/packages/react-hooks/src/useCall.ts +++ b/packages/react-hooks/src/useCall.ts @@ -1,6 +1,7 @@ // Copyright 2017-2022 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { ApiPromise } from '@polkadot/api'; import type { PromiseResult, QueryableStorageEntry } from '@polkadot/api/types'; import type { StorageEntryTypeLatest } from '@polkadot/types/interfaces'; import type { AnyFunction, Codec } from '@polkadot/types/types'; @@ -11,6 +12,7 @@ import { useEffect, useRef, useState } from 'react'; import { isFunction, isNull, isUndefined, nextTick } from '@polkadot/util'; +import { useApi } from './useApi'; import { useIsMountedRef } from './useIsMountedRef'; type VoidFn = () => void; @@ -103,7 +105,7 @@ export function unsubscribe (tracker: TrackerRef): void { } // subscribe, trying to play nice with the browser threads -function subscribe (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn | undefined, params: CallParams, setValue: (value: any) => void, { transform = transformIdentity, withParams, withParamsTransform }: CallOptions = {}): void { +function subscribe (api: ApiPromise, mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn | undefined, params: CallParams, setValue: (value: any) => void, { transform = transformIdentity, withParams, withParamsTransform }: CallOptions = {}): void { const validParams = params.filter((p) => !isUndefined(p)); unsubscribe(tracker); @@ -125,10 +127,10 @@ function subscribe (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn try { setValue( withParams - ? [params, transform(value)] + ? [params, transform(value, api)] : withParamsTransform - ? transform([params, value]) - : transform(value) + ? transform([params, value], api) + : transform(value, api) ); } catch (error) { handleError(error as Error, tracker, fn); @@ -158,6 +160,7 @@ export function throwOnError (tracker: Tracker): void { // FIXME The typings here need some serious TLC // FIXME This is generic, we cannot really use createNamedHook export function useCall (fn: TrackFn | undefined | null | false, params?: CallParams | null, options?: CallOptions): T | undefined { + const { api } = useApi(); const mountedRef = useIsMountedRef(); const tracker = useRef({ error: null, fn: null, isActive: false, serialized: null, subscriber: null, type: 'useCall' }); const [value, setValue] = useState((options || {}).defaultValue); @@ -177,10 +180,10 @@ export function useCall (fn: TrackFn | undefined | null | false, params?: Ca tracker.current.fn = fn; tracker.current.serialized = serialized; - subscribe(mountedRef, tracker, fn, mappedParams, setValue, options); + subscribe(api, mountedRef, tracker, fn, mappedParams, setValue, options); } } - }, [fn, options, mountedRef, params]); + }, [api, fn, options, mountedRef, params]); // throwOnError(tracker.current); diff --git a/packages/react-hooks/src/useCallMulti.ts b/packages/react-hooks/src/useCallMulti.ts index daf4b4de84..86339bbe06 100644 --- a/packages/react-hooks/src/useCallMulti.ts +++ b/packages/react-hooks/src/useCallMulti.ts @@ -20,7 +20,7 @@ interface TrackerRef { interface CallOptions { defaultValue?: T; - transform?: (value: any) => T; + transform?: (value: any, api: ApiPromise) => T; } // subscribe, trying to play nice with the browser threads @@ -47,7 +47,8 @@ function subscribe (api: ApiPromise, mountedRef: MountedRef, tracker: Tracke included[index] ? value[++valueIndex] : undefined - ) + ), + api ) ); } catch (error) { diff --git a/packages/react-hooks/src/useCollectiveMembers.ts b/packages/react-hooks/src/useCollectiveMembers.ts index f671876d20..ba32f59391 100644 --- a/packages/react-hooks/src/useCollectiveMembers.ts +++ b/packages/react-hooks/src/useCollectiveMembers.ts @@ -16,7 +16,7 @@ interface Result { members: string[]; } -const transformMembers = { +const OPT = { transform: (accounts: AccountId[]) => accounts.map((accountId) => accountId.toString()) }; @@ -24,7 +24,7 @@ const transformMembers = { function useCollectiveMembersImpl (collective: CollectiveType): Result { const { api } = useApi(); const { allAccounts, hasAccounts } = useAccounts(); - const retrieved = useCall(hasAccounts && api.derive[collective]?.members, undefined, transformMembers); + const retrieved = useCall(hasAccounts && api.derive[collective]?.members, undefined, OPT); return useMemo( () => ({ diff --git a/packages/react-hooks/src/useIpfsFetch.ts b/packages/react-hooks/src/useIpfsFetch.ts index 54f0e6aec9..a8cc190968 100644 --- a/packages/react-hooks/src/useIpfsFetch.ts +++ b/packages/react-hooks/src/useIpfsFetch.ts @@ -11,6 +11,10 @@ import { useEffect, useMemo, useState } from 'react'; import { useIsMountedRef } from './useIsMountedRef'; +interface Options extends CallOptions { + transform?: (value: any) => T +} + function isCid (cid: string): boolean { return !!cid && (isIPFS.cid(cid) || isIPFS.base32cid(cid.toLowerCase())); } @@ -41,20 +45,20 @@ async function fetchIpfsData (ipfsHashes: string[]): Promise> return result; } -function postProcessData (ipfsData: Map, options?: CallOptions) { - if (!options?.transform) { +function postProcessData (ipfsData: Map, { transform }: Options = {}) { + if (!transform) { return ipfsData; } for (const [key, value] of ipfsData.entries()) { - ipfsData.set(key, options?.transform(value)); + ipfsData.set(key, transform(value)); } return ipfsData; } // FIXME This is generic, we cannot really use createNamedHook -export function useIpfsFetch (hashes: string[] | undefined, options?: CallOptions): Map | undefined { +export function useIpfsFetch (hashes: string[] | undefined, options?: Options): Map | undefined { const mountedRef = useIsMountedRef(); const [value, setValue] = useState | undefined>(); diff --git a/packages/react-hooks/src/useProxies.ts b/packages/react-hooks/src/useProxies.ts index 6377c7de51..935ab31d67 100644 --- a/packages/react-hooks/src/useProxies.ts +++ b/packages/react-hooks/src/useProxies.ts @@ -1,31 +1,34 @@ // Copyright 2017-2022 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { AccountId } from '@polkadot/types/interfaces'; -import { NodeRuntimeProxyType, PalletProxyProxyDefinition } from '@polkadot/types/lookup'; -import { BN } from '@polkadot/util'; +import type { ApiPromise } from '@polkadot/api'; +import type { AccountId } from '@polkadot/types/interfaces'; +import type { NodeRuntimeProxyType, PalletProxyProxyDefinition } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; import { createNamedHook } from './createNamedHook'; import { useAccounts } from './useAccounts'; import { useApi } from './useApi'; import { useCall } from './useCall'; +const OPTS = { + transform: (result: [([AccountId, NodeRuntimeProxyType] | PalletProxyProxyDefinition)[], BN][], api: ApiPromise): [PalletProxyProxyDefinition[], BN][] => + api.tx.proxy.addProxy.meta.args.length === 3 + ? result as [PalletProxyProxyDefinition[], BN][] + : (result as [[AccountId, NodeRuntimeProxyType][], BN][]).map(([arr, bn]): [PalletProxyProxyDefinition[], BN] => + [arr.map(([delegate, proxyType]): PalletProxyProxyDefinition => + api.createType('ProxyDefinition', { + delegate, + proxyType + })), bn] + ) +}; + function useProxiesImpl (): [PalletProxyProxyDefinition[], BN][] | undefined { const { api } = useApi(); const { allAccounts } = useAccounts(); - return useCall<[PalletProxyProxyDefinition[], BN][]>(api.query.proxy?.proxies.multi, [allAccounts], { - transform: (result: [([AccountId, NodeRuntimeProxyType] | PalletProxyProxyDefinition)[], BN][]): [PalletProxyProxyDefinition[], BN][] => - api.tx.proxy.addProxy.meta.args.length === 3 - ? result as [PalletProxyProxyDefinition[], BN][] - : (result as [[AccountId, NodeRuntimeProxyType][], BN][]).map(([arr, bn]): [PalletProxyProxyDefinition[], BN] => - [arr.map(([delegate, proxyType]): PalletProxyProxyDefinition => - api.createType('ProxyDefinition', { - delegate, - proxyType - })), bn] - ) - }); + return useCall<[PalletProxyProxyDefinition[], BN][]>(api.query.proxy?.proxies.multi, [allAccounts], OPTS); } export const useProxies = createNamedHook('useProxies', useProxiesImpl); diff --git a/packages/react-hooks/src/useSudo.ts b/packages/react-hooks/src/useSudo.ts index c765c3eeef..8a7f32dd40 100644 --- a/packages/react-hooks/src/useSudo.ts +++ b/packages/react-hooks/src/useSudo.ts @@ -11,14 +11,14 @@ import { useAccounts } from './useAccounts'; import { useApi } from './useApi'; import { useCall } from './useCall'; -const transformSudo = { +const OPT = { transform: (key: AccountId) => key.toString() }; function useSudoImpl (): UseSudo { const { api } = useApi(); const { allAccounts, hasAccounts } = useAccounts(); - const sudoKey = useCall(hasAccounts && api.query.sudo?.key, undefined, transformSudo); + const sudoKey = useCall(hasAccounts && api.query.sudo?.key, undefined, OPT); const [hasSudoKey, setHasSudoKey] = useState(false); useEffect((): void => { diff --git a/packages/react-query/src/Bonded.tsx b/packages/react-query/src/Bonded.tsx index 5b082cef0e..74a2f59aaf 100644 --- a/packages/react-query/src/Bonded.tsx +++ b/packages/react-query/src/Bonded.tsx @@ -17,18 +17,18 @@ interface Props { label?: React.ReactNode; } -const transformController = { +const OPT_C = { transform: (value: Option) => value.unwrapOr(null) }; -const transformLedger = { +const OPT_L = { transform: (value: Option) => value.unwrapOr(null) }; function BondedDisplay ({ children, className = '', label, params }: Props): React.ReactElement { const { api } = useApi(); - const controllerId = useCall(api.query.staking?.bonded, [params], transformController); - const stakingLedger = useCall(controllerId && api.query.staking?.ledger, [controllerId], transformLedger); + const controllerId = useCall(api.query.staking?.bonded, [params], OPT_C); + const stakingLedger = useCall(controllerId && api.query.staking?.ledger, [controllerId], OPT_L); return (