From 42ce07777677114f289e273082436b0bc45a0522 Mon Sep 17 00:00:00 2001 From: Chris Maniewski Date: Thu, 1 Aug 2024 16:30:24 +0200 Subject: [PATCH] fix: use paging to fail all pending user transactions --- scripts/create-data.js | 17 +++-- .../partials/GroupedTransactionContent.tsx | 5 +- src/graphql/generated.ts | 13 +++- src/graphql/queries/transactions.graphql | 9 ++- src/state/transactionState.ts | 66 ++++++++++++------- 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/scripts/create-data.js b/scripts/create-data.js index 3bb00169106..f0ed7968e32 100644 --- a/scripts/create-data.js +++ b/scripts/create-data.js @@ -264,6 +264,7 @@ const addTxToDb = async ({ hash, methodName, params, + status, userAddress, }) => { const txGroup = { @@ -288,7 +289,7 @@ const addTxToDb = async ({ hash, methodContext: null, methodName, - status: 'SUCCEEDED', + status, title: null, titleValues: null, params: JSON.stringify(params), @@ -510,6 +511,7 @@ const mintTokens = async ( hash: mintTokens.hash, methodName: 'mintTokens', params: [amount], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -528,6 +530,7 @@ const mintTokens = async ( hash: claimColonyFunds.hash, methodName: 'claimColonyFunds', params: [amount], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -658,6 +661,7 @@ const createColony = async ( hash: colonyDeploymentTransaction.transactionHash, methodName: 'createColonyForFrontend', params, + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -772,6 +776,7 @@ const createColony = async ( hash: subdomainTransactions.transactionHash, methodName: 'addDomain(uint256,uint256,uint256)', params: [rootDomainId], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -827,6 +832,7 @@ const createColony = async ( hash: setOwnerTransaction.transactionHash, methodName: 'setOwner', params: [colonyAddress], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -879,6 +885,7 @@ const createColony = async ( hash: installExtensionsTx.transactionHash, methodName: 'multicall.installExtensions', params: [], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -980,6 +987,7 @@ const createColony = async ( hash: setExtensionRolesTx.transactionHash, methodName: 'setUserRoles', params: [], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -1016,6 +1024,7 @@ const createColony = async ( hash: stakedExpenditureInitTx.transactionHash, methodName: 'initialise', params: [stakeFraction], + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -1207,6 +1216,7 @@ const transferFundsBetweenPots = async ( methodName: 'moveFundsBetweenPots(uint256,uint256,uint256,uint256,uint256,uint256,address)', params, + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -1320,6 +1330,7 @@ const userPayments = async ( hash: oneTxPaymentTransaction.transactionHash, methodName: 'makePaymentFundedFromDomain', params, + status: 'SUCCEEDED', userAddress: signerOrWallet.address, }); @@ -1654,9 +1665,7 @@ const createUserAndColonyData = async () => { } } - const colonies = Object.keys(availableColonies).map( - (colonyAddress) => availableColonies[colonyAddress], - ); + const colonies = Object.values(availableColonies); const coloniesTokens = colonies .map(({ colonyName, tokenAddress }) => { if (colonyName !== 'Planet Express') { diff --git a/src/components/common/Extensions/UserHub/partials/TransactionsTab/partials/GroupedTransactionContent.tsx b/src/components/common/Extensions/UserHub/partials/TransactionsTab/partials/GroupedTransactionContent.tsx index 2dffce7d7a4..2c2186bd515 100644 --- a/src/components/common/Extensions/UserHub/partials/TransactionsTab/partials/GroupedTransactionContent.tsx +++ b/src/components/common/Extensions/UserHub/partials/TransactionsTab/partials/GroupedTransactionContent.tsx @@ -2,6 +2,7 @@ import clsx from 'clsx'; import React, { type FC } from 'react'; import { defineMessages, FormattedMessage } from 'react-intl'; +import { TX_RETRY_TIMEOUT } from '~state/transactionState.ts'; import { formatText } from '~utils/intl.ts'; import NotificationBanner from '~v5/shared/NotificationBanner/index.ts'; @@ -13,8 +14,6 @@ import transactionsItemClasses from './TransactionsItem/TransactionsItem.styles. import TransactionStatus from './TransactionStatus.tsx'; import { shortErrorMessage } from './utils.ts'; -const TX_RETRY_TIMEOUT = 1000 * 60 * 10; - const displayName = 'common.Extensions.UserHub.partials.TransactionsTab.partials.GroupedTransactionContent'; @@ -78,7 +77,7 @@ const GroupedTransactionContent: FC = ({ // Whether a retry of the transaction is possible const retryable = - createdAt.valueOf() > new Date().valueOf() - TX_RETRY_TIMEOUT; + createdAt.valueOf() > Date.now() - TX_RETRY_TIMEOUT * 60 * 1000; return (
  • ; }>; -export type GetPendingTransactionsQuery = { __typename?: 'Query', getTransactionsByUserAndStatus?: { __typename?: 'ModelTransactionConnection', items: Array<{ __typename?: 'Transaction', id: string } | null> } | null }; +export type GetPendingTransactionsQuery = { __typename?: 'Query', getTransactionsByUserAndStatus?: { __typename?: 'ModelTransactionConnection', nextToken?: string | null, items: Array<{ __typename?: 'Transaction', id: string } | null> } | null }; export type GetUserByAddressQueryVariables = Exact<{ address: Scalars['ID']; @@ -13330,11 +13331,16 @@ export type GetTransactionQueryHookResult = ReturnType; export type GetTransactionQueryResult = Apollo.QueryResult; export const GetPendingTransactionsDocument = gql` - query GetPendingTransactions($userAddress: ID!) { - getTransactionsByUserAndStatus(from: {eq: $userAddress}, status: PENDING) { + query GetPendingTransactions($userAddress: ID!, $nextToken: String) { + getTransactionsByUserAndStatus( + from: {eq: $userAddress} + status: PENDING + nextToken: $nextToken + ) { items { id } + nextToken } } `; @@ -13352,6 +13358,7 @@ export const GetPendingTransactionsDocument = gql` * const { data, loading, error } = useGetPendingTransactionsQuery({ * variables: { * userAddress: // value for 'userAddress' + * nextToken: // value for 'nextToken' * }, * }); */ diff --git a/src/graphql/queries/transactions.graphql b/src/graphql/queries/transactions.graphql index 447c404c3b0..8b9f00b0c41 100644 --- a/src/graphql/queries/transactions.graphql +++ b/src/graphql/queries/transactions.graphql @@ -27,10 +27,15 @@ query GetTransaction($id: ID!) { } } -query GetPendingTransactions($userAddress: ID!) { - getTransactionsByUserAndStatus(from: { eq: $userAddress }, status: PENDING) { +query GetPendingTransactions($userAddress: ID!, $nextToken: String) { + getTransactionsByUserAndStatus( + from: { eq: $userAddress } + status: PENDING + nextToken: $nextToken + ) { items { id } + nextToken } } diff --git a/src/state/transactionState.ts b/src/state/transactionState.ts index 1ea8a6f0bac..44a80587000 100644 --- a/src/state/transactionState.ts +++ b/src/state/transactionState.ts @@ -1,4 +1,8 @@ -import { type MutationOptions, type FetchPolicy } from '@apollo/client'; +import { + type MutationOptions, + type FetchPolicy, + type FetchResult, +} from '@apollo/client'; import { type ClientTypeTokens } from '@colony/colony-js'; import { utils } from 'ethers'; import { useMemo } from 'react'; @@ -38,6 +42,8 @@ import { notNull } from '~utils/arrays/index.ts'; import { filter, groupBy, mapValues, orderBy } from '~utils/lodash.ts'; export const TX_PAGE_SIZE = 20; +// In minutes +export const TX_RETRY_TIMEOUT = 10; export const convertTransactionType = ({ context, @@ -535,31 +541,41 @@ export const failPendingTransactions = async () => { const userAddress = utils.getAddress(wallet.address); const apollo = getContext(ContextModule.ApolloClient); - const { data } = await apollo.query< - GetPendingTransactionsQuery, - GetPendingTransactionsQueryVariables - >({ - query: GetPendingTransactionsDocument, - variables: { - userAddress, - }, - }); - - const promises = data.getTransactionsByUserAndStatus?.items - .filter(notNull) - .map((tx) => { - return updateTransaction( - { - id: tx.id, - status: TransactionStatus.Failed, - }, - // Optimisitc response, for quick UI updates - { - id: tx.id, - status: TransactionStatus.Failed, - }, - ); + let nextToken: string | null | undefined; + const promises: Promise>[] = []; + + do { + // This is a serial operiation (https://eslint.org/docs/latest/rules/no-await-in-loop#when-not-to-use-it) + // eslint-disable-next-line no-await-in-loop + const { data } = await apollo.query< + GetPendingTransactionsQuery, + GetPendingTransactionsQueryVariables + >({ + query: GetPendingTransactionsDocument, + variables: { + nextToken, + userAddress, + }, }); + nextToken = data?.getTransactionsByUserAndStatus?.nextToken; + + Array.prototype.push.apply( + promises, + data.getTransactionsByUserAndStatus?.items.filter(notNull).map((tx) => { + return updateTransaction( + { + id: tx.id, + status: TransactionStatus.Failed, + }, + // Optimisitc response, for quick UI updates + { + id: tx.id, + status: TransactionStatus.Failed, + }, + ); + }), + ); + } while (nextToken); if (!promises) { return;