mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
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 <m@kretschmann.io>
This commit is contained in:
parent
4b4a926f89
commit
4ff84b0871
@ -8,7 +8,8 @@ import {
|
|||||||
OrderParams,
|
OrderParams,
|
||||||
ProviderComputeInitialize,
|
ProviderComputeInitialize,
|
||||||
ProviderFees,
|
ProviderFees,
|
||||||
ProviderInstance
|
ProviderInstance,
|
||||||
|
ProviderInitialize
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
@ -20,6 +21,26 @@ import {
|
|||||||
} from '../../app.config'
|
} from '../../app.config'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
async function initializeProvider(
|
||||||
|
asset: AssetExtended,
|
||||||
|
accountId: string,
|
||||||
|
providerFees?: ProviderFees
|
||||||
|
): Promise<ProviderInitialize> {
|
||||||
|
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 web3
|
||||||
* @param asset
|
* @param asset
|
||||||
@ -40,15 +61,11 @@ export async function order(
|
|||||||
const datatoken = new Datatoken(web3)
|
const datatoken = new Datatoken(web3)
|
||||||
const config = getOceanConfig(asset.chainId)
|
const config = getOceanConfig(asset.chainId)
|
||||||
|
|
||||||
const initializeData =
|
const initializeData = await initializeProvider(
|
||||||
!providerFees &&
|
asset,
|
||||||
(await ProviderInstance.initialize(
|
accountId,
|
||||||
asset.id,
|
providerFees
|
||||||
asset.services[0].id,
|
)
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
asset.services[0].serviceEndpoint
|
|
||||||
))
|
|
||||||
|
|
||||||
const orderParams = {
|
const orderParams = {
|
||||||
consumer: computeConsumerAddress || accountId,
|
consumer: computeConsumerAddress || accountId,
|
||||||
@ -130,15 +147,11 @@ export async function reuseOrder(
|
|||||||
providerFees?: ProviderFees
|
providerFees?: ProviderFees
|
||||||
): Promise<TransactionReceipt> {
|
): Promise<TransactionReceipt> {
|
||||||
const datatoken = new Datatoken(web3)
|
const datatoken = new Datatoken(web3)
|
||||||
const initializeData =
|
const initializeData = await initializeProvider(
|
||||||
!providerFees &&
|
asset,
|
||||||
(await ProviderInstance.initialize(
|
accountId,
|
||||||
asset.id,
|
providerFees
|
||||||
asset.services[0].id,
|
)
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
asset.services[0].serviceEndpoint
|
|
||||||
))
|
|
||||||
|
|
||||||
const tx = await datatoken.reuseOrder(
|
const tx = await datatoken.reuseOrder(
|
||||||
asset.accessDetails.datatoken.address,
|
asset.accessDetails.datatoken.address,
|
||||||
|
143
src/components/Asset/AssetActions/ButtonBuy/index.test.tsx
Normal file
143
src/components/Asset/AssetActions/ButtonBuy/index.test.tsx
Normal file
@ -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(<ButtonBuy {...downloadProps} isLoading />)
|
||||||
|
const button = screen.getByText('TEST')
|
||||||
|
expect(button).toContainHTML('<Loader')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TESTS FOR DOWNLOAD
|
||||||
|
it('Renders Buy button without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} />)
|
||||||
|
const button = screen.getByText('Buy for 1 day')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders Buy button without crashing when hasPreviousOrder=true', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} hasPreviousOrder />)
|
||||||
|
const button = screen.getByText('Download')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders retry button for download without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} retry />)
|
||||||
|
const button = screen.getByText('Retry')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders get button for free download without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} priceType="free" hasPreviousOrder />)
|
||||||
|
const button = screen.getByText('Download')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Get" button for free assets without crashing', () => {
|
||||||
|
render(<ButtonBuy {...downloadProps} priceType="free" />)
|
||||||
|
const button = screen.getByText('Get')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders Buy button without crashing', () => {
|
||||||
|
render(
|
||||||
|
<ButtonBuy
|
||||||
|
{...downloadProps}
|
||||||
|
assetTimeout="Forever"
|
||||||
|
isConsumable={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const button = screen.getByText('Buy')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TESTS FOR COMPUTE
|
||||||
|
it('Renders "Buy Compute Job" button for compute without crashing', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} />)
|
||||||
|
const button = screen.getByText('Buy Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Buy Compute Job" button for compute without crashing', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} hasDatatokenSelectedComputeAsset />)
|
||||||
|
const button = screen.getByText('Buy Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Start Compute Job" button', () => {
|
||||||
|
render(
|
||||||
|
<ButtonBuy
|
||||||
|
{...computeProps}
|
||||||
|
hasPreviousOrder
|
||||||
|
hasPreviousOrderSelectedComputeAsset
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const button = screen.getByText('Start Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Order Compute Job" button', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} priceType="free" hasProviderFee />)
|
||||||
|
const button = screen.getByText('Order Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "Order Compute Job" button', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} priceType="free" hasProviderFee />)
|
||||||
|
const button = screen.getByText('Order Compute Job')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Renders "retry" button for compute without crashing', () => {
|
||||||
|
render(<ButtonBuy {...computeProps} retry />)
|
||||||
|
const button = screen.getByText('Retry')
|
||||||
|
expect(button).toContainHTML('<button')
|
||||||
|
})
|
||||||
|
})
|
@ -3,7 +3,7 @@ import Button from '../../../@shared/atoms/Button'
|
|||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import Loader from '../../../@shared/atoms/Loader'
|
import Loader from '../../../@shared/atoms/Loader'
|
||||||
|
|
||||||
interface ButtonBuyProps {
|
export interface ButtonBuyProps {
|
||||||
action: 'download' | 'compute'
|
action: 'download' | 'compute'
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
hasPreviousOrder: boolean
|
hasPreviousOrder: boolean
|
||||||
@ -29,6 +29,7 @@ interface ButtonBuyProps {
|
|||||||
algorithmPriceType?: string
|
algorithmPriceType?: string
|
||||||
isAlgorithmConsumable?: boolean
|
isAlgorithmConsumable?: boolean
|
||||||
hasProviderFee?: boolean
|
hasProviderFee?: boolean
|
||||||
|
retry?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we need to take a look at these messages
|
// TODO: we need to take a look at these messages
|
||||||
@ -131,22 +132,24 @@ export default function ButtonBuy({
|
|||||||
priceType,
|
priceType,
|
||||||
algorithmPriceType,
|
algorithmPriceType,
|
||||||
isAlgorithmConsumable,
|
isAlgorithmConsumable,
|
||||||
hasProviderFee
|
hasProviderFee,
|
||||||
|
retry
|
||||||
}: ButtonBuyProps): ReactElement {
|
}: ButtonBuyProps): ReactElement {
|
||||||
const buttonText =
|
const buttonText = retry
|
||||||
action === 'download'
|
? 'Retry'
|
||||||
? hasPreviousOrder
|
: action === 'download'
|
||||||
? 'Download'
|
? hasPreviousOrder
|
||||||
: priceType === 'free'
|
? 'Download'
|
||||||
? 'Get'
|
: priceType === 'free'
|
||||||
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
|
? 'Get'
|
||||||
: hasPreviousOrder &&
|
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
|
||||||
hasPreviousOrderSelectedComputeAsset &&
|
: hasPreviousOrder &&
|
||||||
!hasProviderFee
|
hasPreviousOrderSelectedComputeAsset &&
|
||||||
? 'Start Compute Job'
|
!hasProviderFee
|
||||||
: priceType === 'free' && algorithmPriceType === 'free'
|
? 'Start Compute Job'
|
||||||
? 'Order Compute Job'
|
: priceType === 'free' && algorithmPriceType === 'free'
|
||||||
: `Buy Compute Job`
|
? 'Order Compute Job'
|
||||||
|
: `Buy Compute Job`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
|
@ -43,7 +43,8 @@ export default function FormStartCompute({
|
|||||||
datasetOrderPriceAndFees,
|
datasetOrderPriceAndFees,
|
||||||
algoOrderPriceAndFees,
|
algoOrderPriceAndFees,
|
||||||
providerFeeAmount,
|
providerFeeAmount,
|
||||||
validUntil
|
validUntil,
|
||||||
|
retry
|
||||||
}: {
|
}: {
|
||||||
algorithms: AssetSelectionAsset[]
|
algorithms: AssetSelectionAsset[]
|
||||||
ddoListAlgorithms: Asset[]
|
ddoListAlgorithms: Asset[]
|
||||||
@ -71,6 +72,7 @@ export default function FormStartCompute({
|
|||||||
algoOrderPriceAndFees?: OrderPriceAndFees
|
algoOrderPriceAndFees?: OrderPriceAndFees
|
||||||
providerFeeAmount?: string
|
providerFeeAmount?: string
|
||||||
validUntil?: string
|
validUntil?: string
|
||||||
|
retry: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { siteContent } = useMarketMetadata()
|
const { siteContent } = useMarketMetadata()
|
||||||
const { accountId, balance } = useWeb3()
|
const { accountId, balance } = useWeb3()
|
||||||
@ -294,6 +296,7 @@ export default function FormStartCompute({
|
|||||||
selectedAlgorithmAsset?.accessDetails?.isPurchasable
|
selectedAlgorithmAsset?.accessDetails?.isPurchasable
|
||||||
}
|
}
|
||||||
hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'}
|
hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'}
|
||||||
|
retry={retry}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
@ -97,6 +97,7 @@ export default function Compute({
|
|||||||
const [refetchJobs, setRefetchJobs] = useState(false)
|
const [refetchJobs, setRefetchJobs] = useState(false)
|
||||||
const [isLoadingJobs, setIsLoadingJobs] = useState(false)
|
const [isLoadingJobs, setIsLoadingJobs] = useState(false)
|
||||||
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
|
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
|
||||||
|
const [retry, setRetry] = useState<boolean>(false)
|
||||||
|
|
||||||
const hasDatatoken = Number(dtBalance) >= 1
|
const hasDatatoken = Number(dtBalance) >= 1
|
||||||
const isComputeButtonDisabled =
|
const isComputeButtonDisabled =
|
||||||
@ -291,7 +292,8 @@ export default function Compute({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newError = error
|
const newError = error
|
||||||
if (!newError) return
|
if (!newError) return
|
||||||
toast.error(newError)
|
const errorMsg = newError + '. Please retry.'
|
||||||
|
toast.error(errorMsg)
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
async function startJob(): Promise<void> {
|
async function startJob(): Promise<void> {
|
||||||
@ -386,6 +388,7 @@ export default function Compute({
|
|||||||
initPriceAndFees()
|
initPriceAndFees()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error.message)
|
setError(error.message)
|
||||||
|
setRetry(true)
|
||||||
LoggerInstance.error(`[compute] ${error.message} `)
|
LoggerInstance.error(`[compute] ${error.message} `)
|
||||||
} finally {
|
} finally {
|
||||||
setIsOrdering(false)
|
setIsOrdering(false)
|
||||||
@ -477,6 +480,7 @@ export default function Compute({
|
|||||||
algoOrderPriceAndFees={algoOrderPriceAndFees}
|
algoOrderPriceAndFees={algoOrderPriceAndFees}
|
||||||
providerFeeAmount={providerFeeAmount}
|
providerFeeAmount={providerFeeAmount}
|
||||||
validUntil={computeValidUntil}
|
validUntil={computeValidUntil}
|
||||||
|
retry={retry}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
)}
|
)}
|
||||||
|
@ -48,6 +48,7 @@ export default function Download({
|
|||||||
const [isOrderDisabled, setIsOrderDisabled] = useState(false)
|
const [isOrderDisabled, setIsOrderDisabled] = useState(false)
|
||||||
const [orderPriceAndFees, setOrderPriceAndFees] =
|
const [orderPriceAndFees, setOrderPriceAndFees] =
|
||||||
useState<OrderPriceAndFees>()
|
useState<OrderPriceAndFees>()
|
||||||
|
const [retry, setRetry] = useState<boolean>(false)
|
||||||
|
|
||||||
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
|
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
|
||||||
|
|
||||||
@ -155,9 +156,10 @@ export default function Download({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error)
|
LoggerInstance.error(error)
|
||||||
|
setRetry(true)
|
||||||
const message = isOwned
|
const message = isOwned
|
||||||
? 'Failed to download file!'
|
? '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)
|
toast.error(message)
|
||||||
}
|
}
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -181,6 +183,7 @@ export default function Download({
|
|||||||
isConsumable={asset.accessDetails?.isPurchasable}
|
isConsumable={asset.accessDetails?.isPurchasable}
|
||||||
isBalanceSufficient={isBalanceSufficient}
|
isBalanceSufficient={isBalanceSufficient}
|
||||||
consumableFeedback={consumableFeedback}
|
consumableFeedback={consumableFeedback}
|
||||||
|
retry={retry}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user