market/src/components/Asset/AssetActions/Compute/index.tsx

454 lines
15 KiB
TypeScript

import React, { useState, ReactElement, useEffect, useCallback } from 'react'
import {
Asset,
DDO,
PublisherTrustedAlgorithm,
FileMetadata
} from '@oceanprotocol/lib'
import { toast } from 'react-toastify'
import Price from '@shared/Price'
import FileIcon from '@shared/FileIcon'
import Alert from '@shared/atoms/Alert'
import { useWeb3 } from '@context/Web3'
import {
generateBaseQuery,
getFilterTerm,
queryMetadata
} from '@utils/aquarius'
import { Formik } from 'formik'
import { getInitialValues, validationSchema } from './_constants'
import axios from 'axios'
import FormStartComputeDataset from './FormComputeDataset'
import styles from './index.module.css'
import SuccessConfetti from '@shared/SuccessConfetti'
import { getServiceByName } from '@utils/ddo'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute'
import { getPreviousOrders } from '@utils/subgraph'
import AssetActionHistoryTable from '../AssetActionHistoryTable'
import ComputeJobs from '../../../Profile/History/ComputeJobs'
import { useCancelToken } from '@hooks/useCancelToken'
import { useIsMounted } from '@hooks/useIsMounted'
import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery'
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { AccessDetails } from 'src/@types/Price'
import { transformAssetToAssetSelection } from '@utils/assetConvertor'
import { useMarketMetadata } from '@context/MarketMetadata'
export default function Compute({
ddo,
accessDetails,
dtBalance,
file,
fileIsLoading,
isConsumable,
consumableFeedback
}: {
ddo: Asset
accessDetails: AccessDetails
dtBalance: string
file: FileMetadata
fileIsLoading?: boolean
isConsumable?: boolean
consumableFeedback?: string
}): ReactElement {
const { appConfig } = useMarketMetadata()
const { accountId } = useWeb3()
const [isJobStarting, setIsJobStarting] = useState(false)
const [error, setError] = useState<string>()
const [algorithmList, setAlgorithmList] = useState<AssetSelectionAsset[]>()
const [ddoAlgorithmList, setDdoAlgorithmList] = useState<Asset[]>()
const [selectedAlgorithmAsset, setSelectedAlgorithmAsset] = useState<Asset>()
const [hasAlgoAssetDatatoken, setHasAlgoAssetDatatoken] = useState<boolean>()
const [isPublished, setIsPublished] = useState(false)
const [hasPreviousDatasetOrder, setHasPreviousDatasetOrder] = useState(false)
const [previousDatasetOrderId, setPreviousDatasetOrderId] = useState<string>()
const [hasPreviousAlgorithmOrder, setHasPreviousAlgorithmOrder] =
useState(false)
const [algorithmDTBalance, setalgorithmDTBalance] = useState<string>()
const [algorithmConsumeDetails, setAlgorithmConsumeDetails] =
useState<AccessDetails>()
const [previousAlgorithmOrderId, setPreviousAlgorithmOrderId] =
useState<string>()
const [datasetTimeout, setDatasetTimeout] = useState<string>()
const [algorithmTimeout, setAlgorithmTimeout] = useState<string>()
const newCancelToken = useCancelToken()
const hasDatatoken = Number(dtBalance) >= 1
const isMounted = useIsMounted()
const [isConsumablePrice, setIsConsumablePrice] = useState(true)
const [isAlgoConsumablePrice, setIsAlgoConsumablePrice] = useState(true)
const isComputeButtonDisabled =
isJobStarting === true ||
file === null ||
(!hasPreviousDatasetOrder && !hasDatatoken && !isConsumablePrice) ||
(!hasPreviousAlgorithmOrder &&
!hasAlgoAssetDatatoken &&
!isAlgoConsumablePrice)
const service = ddo?.services[0]
const { timeout } = service
async function checkPreviousOrders(ddo: DDO) {
const { type } = ddo.metadata
const orderId = await getPreviousOrders(
ddo.services[0].datatokenAddress?.toLowerCase(),
accountId?.toLowerCase(),
timeout.toString()
)
if (!isMounted()) return
if (type === 'algorithm') {
setPreviousAlgorithmOrderId(orderId)
setHasPreviousAlgorithmOrder(!!orderId)
} else {
setPreviousDatasetOrderId(orderId)
setHasPreviousDatasetOrder(!!orderId)
}
}
async function checkAssetDTBalance(asset: DDO) {
// const AssetDtBalance = await ocean.datatokens.balance(
// asset.services[0].datatokenAddress,
// accountId
// )
// setalgorithmDTBalance(AssetDtBalance)
// setHasAlgoAssetDatatoken(Number(AssetDtBalance) >= 1)
}
function getQuerryString(
trustedAlgorithmList: PublisherTrustedAlgorithm[],
chainId?: number
): SearchQuery {
const algorithmDidList = trustedAlgorithmList.map((x) => x.did)
const baseParams = {
chainIds: [chainId],
sort: { sortBy: SortTermOptions.Created },
filters: [
getFilterTerm('service.attributes.main.type', 'algorithm'),
getFilterTerm('id', algorithmDidList)
]
} as BaseQueryParams
const query = generateBaseQuery(baseParams)
return query
}
async function getAlgorithmList(): Promise<AssetSelectionAsset[]> {
const source = axios.CancelToken.source()
const computeService = ddo.services[0]
let algorithmSelectionList: AssetSelectionAsset[]
if (
!computeService.compute ||
!computeService.compute.publisherTrustedAlgorithms ||
computeService.compute.publisherTrustedAlgorithms.length === 0
) {
algorithmSelectionList = []
} else {
const gueryResults = await queryMetadata(
getQuerryString(
computeService.compute.publisherTrustedAlgorithms,
ddo.chainId
),
source.token
)
setDdoAlgorithmList(gueryResults.results)
algorithmSelectionList = await transformAssetToAssetSelection(
computeService?.serviceEndpoint,
gueryResults.results,
[]
)
}
return algorithmSelectionList
}
const initMetadata = useCallback(async (ddo: Asset): Promise<void> => {
if (!ddo) return
const accessDetails = await getAccessDetails(
ddo.chainId,
ddo.services[0].datatokenAddress
)
setAlgorithmConsumeDetails(accessDetails)
}, [])
useEffect(() => {
if (!algorithmConsumeDetails) return
setIsAlgoConsumablePrice(algorithmConsumeDetails.isPurchasable)
}, [algorithmConsumeDetails])
useEffect(() => {
if (!accessDetails) return
setIsConsumablePrice(accessDetails.isPurchasable)
}, [accessDetails])
// useEffect(() => {
// setDatasetTimeout(secondsToString(timeout))
// }, [ddo])
useEffect(() => {
if (!ddo) return
getAlgorithmList().then((algorithms) => {
setAlgorithmList(algorithms)
})
}, [ddo])
useEffect(() => {
if (!accountId) return
checkPreviousOrders(ddo)
}, [ddo, accountId])
useEffect(() => {
if (!selectedAlgorithmAsset) return
initMetadata(selectedAlgorithmAsset)
const { timeout } = ddo.services[0]
// setAlgorithmTimeout(secondsToString(timeout))
if (accountId) {
if (getServiceByName(selectedAlgorithmAsset, 'access')) {
checkPreviousOrders(selectedAlgorithmAsset).then(() => {
if (
!hasPreviousAlgorithmOrder &&
getServiceByName(selectedAlgorithmAsset, 'compute')
) {
checkPreviousOrders(selectedAlgorithmAsset)
}
})
} else if (getServiceByName(selectedAlgorithmAsset, 'compute')) {
checkPreviousOrders(selectedAlgorithmAsset)
}
}
// ocean && checkAssetDTBalance(selectedAlgorithmAsset)
}, [ddo, selectedAlgorithmAsset, accountId, hasPreviousAlgorithmOrder])
// Output errors in toast UI
useEffect(() => {
const newError = error
if (!newError) return
toast.error(newError)
}, [error])
// async function startJob(algorithmId: string) {
// try {
// if (!ocean) return
// setIsJobStarting(true)
// setIsPublished(false)
// setError(undefined)
// const computeService = getServiceByName(ddo, 'compute')
// const serviceAlgo = getServiceByName(selectedAlgorithmAsset, 'access')
// ? getServiceByName(selectedAlgorithmAsset, 'access')
// : getServiceByName(selectedAlgorithmAsset, 'compute')
// const computeAlgorithm: ComputeAlgorithm = {
// did: selectedAlgorithmAsset.id,
// serviceIndex: serviceAlgo.index,
// dataToken: selectedAlgorithmAsset.services[0].datatokenAddress
// }
// const allowed = await ocean.compute.isOrderable(
// ddo.id,
// computeService.index,
// computeAlgorithm
// )
// LoggerInstance.log('[compute] Is data set orderable?', allowed)
// if (!allowed) {
// setError(
// 'Data set is not orderable in combination with selected algorithm.'
// )
// LoggerInstance.error(
// '[compute] Error starting compute job. Dataset is not orderable in combination with selected algorithm.'
// )
// return
// }
// if (!hasPreviousDatasetOrder && !hasDatatoken) {
// const tx = await buyDT('1', price, ddo)
// if (!tx) {
// setError('Error buying datatoken.')
// LoggerInstance.error('[compute] Error buying datatoken for data set ', ddo.id)
// return
// }
// }
// if (!hasPreviousAlgorithmOrder && !hasAlgoAssetDatatoken) {
// const tx = await buyDT('1', algorithmPrice, selectedAlgorithmAsset)
// if (!tx) {
// setError('Error buying datatoken.')
// LoggerInstance.error(
// '[compute] Error buying datatoken for algorithm ',
// selectedAlgorithmAsset.id
// )
// return
// }
// }
// // TODO: pricingError is always undefined even upon errors during buyDT for whatever reason.
// // So manually drop out above, but ideally could be replaced with this alone.
// if (pricingError) {
// setError(pricingError)
// return
// }
// const assetOrderId = hasPreviousDatasetOrder
// ? previousDatasetOrderId
// : await ocean.compute.orderAsset(
// accountId,
// ddo.id,
// computeService.index,
// computeAlgorithm,
// appConfig.marketFeeAddress,
// undefined,
// null,
// false
// )
// assetOrderId &&
// LoggerInstance.log(
// `[compute] Got ${
// hasPreviousDatasetOrder ? 'existing' : 'new'
// } order ID for dataset: `,
// assetOrderId
// )
// const algorithmAssetOrderId = hasPreviousAlgorithmOrder
// ? previousAlgorithmOrderId
// : await ocean.compute.orderAlgorithm(
// algorithmId,
// serviceAlgo.type,
// accountId,
// serviceAlgo.index,
// appConfig.marketFeeAddress,
// undefined,
// null,
// false
// )
// algorithmAssetOrderId &&
// LoggerInstance.log(
// `[compute] Got ${
// hasPreviousAlgorithmOrder ? 'existing' : 'new'
// } order ID for algorithm: `,
// algorithmAssetOrderId
// )
// if (!assetOrderId || !algorithmAssetOrderId) {
// setError('Error ordering assets.')
// return
// }
// computeAlgorithm.transferTxId = algorithmAssetOrderId
// LoggerInstance.log('[compute] Starting compute job.')
// const output: ComputeOutput = {
// publishAlgorithmLog: true,
// publishOutput: true
// }
// const response = await ocean.compute.start(
// ddo.id,
// assetOrderId,
// ddo.services[0].datatokenAddress,
// account,
// computeAlgorithm,
// output,
// `${computeService.index}`,
// computeService.type
// )
// if (!response) {
// setError('Error starting compute job.')
// return
// }
// LoggerInstance.log('[compute] Starting compute job response: ', response)
// await checkPreviousOrders(selectedAlgorithmAsset)
// await checkPreviousOrders(ddo)
// setIsPublished(true)
// } catch (error) {
// await checkPreviousOrders(selectedAlgorithmAsset)
// await checkPreviousOrders(ddo)
// setError('Failed to start job!')
// LoggerInstance.error('[compute] Failed to start job: ', error.message)
// } finally {
// setIsJobStarting(false)
// }
// }
return (
<>
<div className={styles.info}>
<FileIcon file={file} isLoading={fileIsLoading} small />
<Price accessDetails={accessDetails} conversion size="large" />
</div>
{ddo.metadata.type === 'algorithm' ? (
<>
<Alert
text="This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!"
state="info"
/>
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} asset={ddo} />
</>
) : (
<Formik
initialValues={getInitialValues()}
validateOnMount
validationSchema={validationSchema}
onSubmit={async (values) => {
// await startJob(values.algorithm)
}}
>
<FormStartComputeDataset
algorithms={algorithmList}
ddoListAlgorithms={ddoAlgorithmList}
setSelectedAlgorithm={setSelectedAlgorithmAsset}
isLoading={isJobStarting}
isComputeButtonDisabled={isComputeButtonDisabled}
hasPreviousOrder={hasPreviousDatasetOrder}
hasDatatoken={hasDatatoken}
dtBalance={dtBalance}
datasetLowPoolLiquidity={!isConsumablePrice}
assetType={ddo?.metadata.type}
assetTimeout={datasetTimeout}
hasPreviousOrderSelectedComputeAsset={hasPreviousAlgorithmOrder}
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
oceanSymbol={accessDetails?.baseToken?.symbol || ''}
dtSymbolSelectedComputeAsset={
selectedAlgorithmAsset?.datatokens[0]?.symbol
}
dtBalanceSelectedComputeAsset={algorithmDTBalance}
selectedComputeAssetLowPoolLiquidity={!isAlgoConsumablePrice}
selectedComputeAssetType="algorithm"
selectedComputeAssetTimeout={algorithmTimeout}
// lazy comment when removing pricingStepText
stepText={'pricingStepText' || 'Starting Compute Job...'}
algorithmConsumeDetails={algorithmConsumeDetails}
isConsumable={isConsumable}
consumableFeedback={consumableFeedback}
/>
</Formik>
)}
<footer className={styles.feedback}>
{isPublished && (
<SuccessConfetti success="Your job started successfully! Watch the progress below or on your profile." />
)}
</footer>
{accountId && accessDetails?.datatoken && (
<AssetActionHistoryTable title="Your Compute Jobs">
<ComputeJobs minimal />
</AssetActionHistoryTable>
)}
</>
)
}