From 4ff84b0871c90c9ad02ad8b6faef5eb2190094a0 Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Fri, 11 Nov 2022 12:54:21 +0300 Subject: [PATCH] Handle provider error & showing Retry button (#1770) * adding error handling for button onCLick * Updating butoon to show retry & updating toast message * Logging provider error * Removing unused imports * Using early return when provider fees are present to prevent unneccessary nesting * Adding retry logic for compute * Removing duplicate teast message * Adding tests for rendering Buy button * Additional tests for BuyButton * Refactoring BuyButton tests for download * Fix failing tests * Refactoring compute tests * Refactoring tests Co-authored-by: Matthias Kretschmann --- src/@utils/order.ts | 51 ++++--- .../AssetActions/ButtonBuy/index.test.tsx | 143 ++++++++++++++++++ .../Asset/AssetActions/ButtonBuy/index.tsx | 35 +++-- .../Compute/FormComputeDataset.tsx | 5 +- .../Asset/AssetActions/Compute/index.tsx | 6 +- .../Asset/AssetActions/Download.tsx | 5 +- 6 files changed, 207 insertions(+), 38 deletions(-) create mode 100644 src/components/Asset/AssetActions/ButtonBuy/index.test.tsx diff --git a/src/@utils/order.ts b/src/@utils/order.ts index 9dc189c61..3f9337d77 100644 --- a/src/@utils/order.ts +++ b/src/@utils/order.ts @@ -8,7 +8,8 @@ import { OrderParams, ProviderComputeInitialize, ProviderFees, - ProviderInstance + ProviderInstance, + ProviderInitialize } from '@oceanprotocol/lib' import Web3 from 'web3' import { getOceanConfig } from './ocean' @@ -20,6 +21,26 @@ import { } from '../../app.config' import { toast } from 'react-toastify' +async function initializeProvider( + asset: AssetExtended, + accountId: string, + providerFees?: ProviderFees +): Promise { + if (providerFees) return + try { + const provider = await ProviderInstance.initialize( + asset.id, + asset.services[0].id, + 0, + accountId, + asset.services[0].serviceEndpoint + ) + return provider + } catch (error) { + LoggerInstance.log('[Initialize Provider] Error:', error) + } +} + /** * @param web3 * @param asset @@ -40,15 +61,11 @@ export async function order( const datatoken = new Datatoken(web3) const config = getOceanConfig(asset.chainId) - const initializeData = - !providerFees && - (await ProviderInstance.initialize( - asset.id, - asset.services[0].id, - 0, - accountId, - asset.services[0].serviceEndpoint - )) + const initializeData = await initializeProvider( + asset, + accountId, + providerFees + ) const orderParams = { consumer: computeConsumerAddress || accountId, @@ -130,15 +147,11 @@ export async function reuseOrder( providerFees?: ProviderFees ): Promise { const datatoken = new Datatoken(web3) - const initializeData = - !providerFees && - (await ProviderInstance.initialize( - asset.id, - asset.services[0].id, - 0, - accountId, - asset.services[0].serviceEndpoint - )) + const initializeData = await initializeProvider( + asset, + accountId, + providerFees + ) const tx = await datatoken.reuseOrder( asset.accessDetails.datatoken.address, diff --git a/src/components/Asset/AssetActions/ButtonBuy/index.test.tsx b/src/components/Asset/AssetActions/ButtonBuy/index.test.tsx new file mode 100644 index 000000000..43ef5c433 --- /dev/null +++ b/src/components/Asset/AssetActions/ButtonBuy/index.test.tsx @@ -0,0 +1,143 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import ButtonBuy, { ButtonBuyProps } from './' + +const downloadProps: ButtonBuyProps = { + action: 'download', + disabled: false, + hasPreviousOrder: false, + hasDatatoken: false, + btSymbol: 'btSymbol', + dtSymbol: 'dtSymbol', + dtBalance: '100000000000', + assetTimeout: '1 day', + assetType: 'Dataset', + stepText: 'TEST', + priceType: 'fixed', + isConsumable: true, + isBalanceSufficient: true, + consumableFeedback: 'TEST: consumableFeedback' +} + +const computeProps: ButtonBuyProps = { + action: 'compute', + disabled: false, + hasPreviousOrder: false, + hasDatatoken: true, + btSymbol: 'btSymbol', + dtSymbol: 'dtSymbol', + dtBalance: '100000000000', + assetTimeout: '1 day', + assetType: 'algorithm', + hasPreviousOrderSelectedComputeAsset: false, + hasDatatokenSelectedComputeAsset: true, + dtSymbolSelectedComputeAsset: 'dtSymbol', + dtBalanceSelectedComputeAsset: 'dtBalance', + selectedComputeAssetType: 'selectedComputeAssetType', + stepText: ' ', + isLoading: false, + type: 'submit', + priceType: 'fixed', + algorithmPriceType: 'free', + isBalanceSufficient: true, + isConsumable: true, + consumableFeedback: 'consumableFeedback', + isAlgorithmConsumable: true, + hasProviderFee: false, + retry: false +} + +describe('Asset/AssetActions/ButtonBuy', () => { + // TESTS FOR LOADING + it('Renders Buy button without crashing', () => { + render() + const button = screen.getByText('TEST') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Buy for 1 day') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Download') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Retry') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Download') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Get') + expect(button).toContainHTML(' { + render( + + ) + const button = screen.getByText('Buy') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Buy Compute Job') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Buy Compute Job') + expect(button).toContainHTML(' { + render( + + ) + const button = screen.getByText('Start Compute Job') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Order Compute Job') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Order Compute Job') + expect(button).toContainHTML(' { + render() + const button = screen.getByText('Retry') + expect(button).toContainHTML(' diff --git a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx index abe7b3398..18b37b2ba 100644 --- a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx +++ b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx @@ -43,7 +43,8 @@ export default function FormStartCompute({ datasetOrderPriceAndFees, algoOrderPriceAndFees, providerFeeAmount, - validUntil + validUntil, + retry }: { algorithms: AssetSelectionAsset[] ddoListAlgorithms: Asset[] @@ -71,6 +72,7 @@ export default function FormStartCompute({ algoOrderPriceAndFees?: OrderPriceAndFees providerFeeAmount?: string validUntil?: string + retry: boolean }): ReactElement { const { siteContent } = useMarketMetadata() const { accountId, balance } = useWeb3() @@ -294,6 +296,7 @@ export default function FormStartCompute({ selectedAlgorithmAsset?.accessDetails?.isPurchasable } hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'} + retry={retry} /> ) diff --git a/src/components/Asset/AssetActions/Compute/index.tsx b/src/components/Asset/AssetActions/Compute/index.tsx index 13e2533cf..4407473eb 100644 --- a/src/components/Asset/AssetActions/Compute/index.tsx +++ b/src/components/Asset/AssetActions/Compute/index.tsx @@ -97,6 +97,7 @@ export default function Compute({ const [refetchJobs, setRefetchJobs] = useState(false) const [isLoadingJobs, setIsLoadingJobs] = useState(false) const [jobs, setJobs] = useState([]) + const [retry, setRetry] = useState(false) const hasDatatoken = Number(dtBalance) >= 1 const isComputeButtonDisabled = @@ -291,7 +292,8 @@ export default function Compute({ useEffect(() => { const newError = error if (!newError) return - toast.error(newError) + const errorMsg = newError + '. Please retry.' + toast.error(errorMsg) }, [error]) async function startJob(): Promise { @@ -386,6 +388,7 @@ export default function Compute({ initPriceAndFees() } catch (error) { setError(error.message) + setRetry(true) LoggerInstance.error(`[compute] ${error.message} `) } finally { setIsOrdering(false) @@ -477,6 +480,7 @@ export default function Compute({ algoOrderPriceAndFees={algoOrderPriceAndFees} providerFeeAmount={providerFeeAmount} validUntil={computeValidUntil} + retry={retry} /> )} diff --git a/src/components/Asset/AssetActions/Download.tsx b/src/components/Asset/AssetActions/Download.tsx index 61a31dc3c..1bcbb360b 100644 --- a/src/components/Asset/AssetActions/Download.tsx +++ b/src/components/Asset/AssetActions/Download.tsx @@ -48,6 +48,7 @@ export default function Download({ const [isOrderDisabled, setIsOrderDisabled] = useState(false) const [orderPriceAndFees, setOrderPriceAndFees] = useState() + const [retry, setRetry] = useState(false) const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED' @@ -155,9 +156,10 @@ export default function Download({ } } catch (error) { LoggerInstance.error(error) + setRetry(true) const message = isOwned ? 'Failed to download file!' - : 'An error occurred. Check console for more information.' + : 'An error occurred, please retry. Check console for more information.' toast.error(message) } setIsLoading(false) @@ -181,6 +183,7 @@ export default function Download({ isConsumable={asset.accessDetails?.isPurchasable} isBalanceSufficient={isBalanceSufficient} consumableFeedback={consumableFeedback} + retry={retry} /> )