mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Start compute job (#439)
* Wip start compute job * Wip select algorithm design * Asset selection form component, for start compute job (#442) * prototype AssetSelection * assetselection styling * typing "fix" * put back file info icon * AssetSelection styling in context * update start job method, fixed algo select, and fixed option typing * compute logic update * add has previous orders for algo asset * fixed search algorithm assets in start compute form * fixed lint errors * updated previous order for algo logic and compute flow * update use price hook and added buy DT for algo * display only alg of type exchange and sort by value * display only trusted algo for asset if field is set * added logic for allow all published algorithms or no algorithms allowed * asset selection style & spacing tweaks * refactor get algorithms for compute and edit compute * fixed form options and more refactoring * new ButtonBuy component * shared component between consume/compute * dealing with various states: loading, previous orders, help text output * effect dependencies * move error output into toast * formik data flow refactor * ditch custom field change handler * fix initialValues * typed form data & validation * fixes multiple form validation issues along the way * isInitialValid → validateOnMount * metadata display tweaks * error feedback tweaks * oler assets checks, confeti on succes job, market fee on order, removed algo compute logic * more startJob logging * feedback & messaging changes * metadata display * return all algos, fixed & dynamic priced ones * fix DOM nesting * messaging updates * copy tweaks * check algorithm previous history for both acces and compute sercive types * handle start compute error * extra checks on start compute response * styling tweaks, fix toast UI errors * AssetSelection: empty screen, tweak min/max height * fix FRE issues on start compute * check is ordarable before start compute job * logging tweaks * disable eslint no-unused-vars rule for some Apollo code blocks * fix metadata editing for compute assets * consider dataset timeout for compute too Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
6d14181d17
commit
18f2c99e78
@ -19,7 +19,7 @@
|
||||
"name": "files",
|
||||
"label": "File",
|
||||
"placeholder": "e.g. https://file.com/file.json",
|
||||
"help": "Please provide a URL to your algorith file. This URL will be stored encrypted after publishing.",
|
||||
"help": "Please provide a URL to your algorithm file. This URL will be stored encrypted after publishing.",
|
||||
"type": "files",
|
||||
"required": true
|
||||
},
|
||||
@ -27,23 +27,23 @@
|
||||
"name": "dockerImage",
|
||||
"label": "Docker Image",
|
||||
"placeholder": "e.g. python3.7",
|
||||
"help": "Please select a image to run your algorithm.",
|
||||
"help": "Please select an image to run your algorithm.",
|
||||
"type": "select",
|
||||
"options": ["node:pre-defined", "python:pre-defined", "custom image"],
|
||||
"options": ["node:latest", "python:latest", "custom image"],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"label": "Image URL",
|
||||
"placeholder": "e.g. oceanprotocol/algo_dockers or https://example.com/image_path",
|
||||
"help": "Provide the name of a public docker image or the full url if you have it hosted in a 3rd party repo",
|
||||
"help": "Provide the name of a public Docker image or the full url if you have it hosted in a 3rd party repo",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "containerTag",
|
||||
"label": "Docker Image Tag",
|
||||
"placeholder": "e.g. latest",
|
||||
"help": "Provide the tag for your docker image.",
|
||||
"help": "Provide the tag for your Docker image.",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
@ -58,13 +58,14 @@
|
||||
"label": "Algorithm Privacy",
|
||||
"type": "checkbox",
|
||||
"options": ["Keep my algorithm private"],
|
||||
"help": "By default, your algorithm can be downloaded for a fixed or dynamic price in addition to running in compute jobs. Enabling this option will prevent downloading, so your algorithm can only be run as part of a compute job on a data set.",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"label": "Author",
|
||||
"placeholder": "e.g. Jelly McJellyfish",
|
||||
"help": "Give proper attribution for your algorith.",
|
||||
"help": "Give proper attribution for your algorithm.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
|
@ -35,7 +35,7 @@
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "select",
|
||||
"options": ["Download"],
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
|
16
content/pages/startComputeDataset.json
Normal file
16
content/pages/startComputeDataset.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"form": {
|
||||
"success": "🎉 Your Compute job started. 🎉",
|
||||
"error": "Compute job could not be started.",
|
||||
"data": [
|
||||
{
|
||||
"name": "algorithm",
|
||||
"label": "Select an algorithm to start a compute job",
|
||||
"type": "assetSelection",
|
||||
"value": false,
|
||||
"options": [],
|
||||
"sortOptions": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
14
src/components/atoms/ButtonBuy.module.css
Normal file
14
src/components/atoms/ButtonBuy.module.css
Normal file
@ -0,0 +1,14 @@
|
||||
.actions {
|
||||
width: 100%;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--color-secondary);
|
||||
margin-top: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.help:not(:empty) {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
90
src/components/atoms/ButtonBuy.tsx
Normal file
90
src/components/atoms/ButtonBuy.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import React, { FormEvent, ReactElement } from 'react'
|
||||
import Button from './Button'
|
||||
import styles from './ButtonBuy.module.css'
|
||||
import Loader from './Loader'
|
||||
|
||||
interface ButtonBuyProps {
|
||||
action: 'download' | 'compute'
|
||||
disabled: boolean
|
||||
hasPreviousOrder: boolean
|
||||
hasDatatoken: boolean
|
||||
dtSymbol: string
|
||||
dtBalance: string
|
||||
isLoading: boolean
|
||||
assetTimeout: string
|
||||
onClick?: (e: FormEvent<HTMLButtonElement>) => void
|
||||
stepText?: string
|
||||
type?: 'submit'
|
||||
}
|
||||
|
||||
function getHelpText(
|
||||
token: {
|
||||
dtBalance: string
|
||||
dtSymbol: string
|
||||
},
|
||||
hasDatatoken: boolean,
|
||||
hasPreviousOrder: boolean,
|
||||
timeout: string
|
||||
) {
|
||||
const { dtBalance, dtSymbol } = token
|
||||
const assetTimeout = timeout === 'Forever' ? '' : ` for ${timeout}`
|
||||
const text = hasPreviousOrder
|
||||
? `You bought this data set already allowing you to use it without paying again${assetTimeout}.`
|
||||
: hasDatatoken
|
||||
? `You own ${dtBalance} ${dtSymbol} allowing you to use this data set by spending 1 ${dtSymbol}, but without paying OCEAN again.`
|
||||
: `For using this data set, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher and pool.`
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
export default function ButtonBuy({
|
||||
action,
|
||||
disabled,
|
||||
hasPreviousOrder,
|
||||
hasDatatoken,
|
||||
dtSymbol,
|
||||
dtBalance,
|
||||
onClick,
|
||||
assetTimeout,
|
||||
stepText,
|
||||
isLoading,
|
||||
type
|
||||
}: ButtonBuyProps): ReactElement {
|
||||
const buttonText =
|
||||
action === 'download'
|
||||
? hasPreviousOrder
|
||||
? 'Download'
|
||||
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
|
||||
: hasPreviousOrder
|
||||
? 'Start Compute Job'
|
||||
: `Buy Compute Job ${
|
||||
assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`
|
||||
}`
|
||||
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
{isLoading ? (
|
||||
<Loader message={stepText} />
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
style="primary"
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
<div className={styles.help}>
|
||||
{getHelpText(
|
||||
{ dtBalance, dtSymbol },
|
||||
hasDatatoken,
|
||||
hasPreviousOrder,
|
||||
assetTimeout
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
|
||||
.file.small {
|
||||
font-size: var(--font-size-mini);
|
||||
height: 5.75rem;
|
||||
height: 5.5rem;
|
||||
width: 4.5rem;
|
||||
padding: calc(var(--spacer) / 2) calc(var(--spacer) / 4);
|
||||
}
|
||||
|
@ -100,6 +100,12 @@
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.algorithmLabel {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
|
||||
.radio,
|
||||
.checkbox {
|
||||
composes: input;
|
||||
@ -140,16 +146,17 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.radio {
|
||||
.radio,
|
||||
.radio::after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.radio::after {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
background: var(--brand-white);
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.checkbox::after {
|
||||
|
@ -1,4 +1,5 @@
|
||||
.action {
|
||||
text-align: center;
|
||||
display: block;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
font-size: var(--font-size-small);
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
@ -18,8 +19,8 @@ div [class*='loaderWrap'] {
|
||||
.scroll {
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: calc(var(--spacer) / 4);
|
||||
min-height: 200px;
|
||||
max-height: 300px;
|
||||
min-height: fit-content;
|
||||
max-height: 50vh;
|
||||
position: relative;
|
||||
/* smooth overflow scrolling for pre-iOS 13 */
|
||||
overflow: auto;
|
||||
@ -30,7 +31,11 @@ div [class*='loaderWrap'] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4);
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -46,11 +51,10 @@ div [class*='loaderWrap'] {
|
||||
}
|
||||
|
||||
.input {
|
||||
align-self: flex-start;
|
||||
min-width: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.radio {
|
||||
@ -64,7 +68,7 @@ div [class*='loaderWrap'] {
|
||||
.title {
|
||||
font-size: var(--font-size-small);
|
||||
margin-top: calc(var(--spacer) / 12);
|
||||
margin-bottom: calc(var(--spacer) / 12);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
@ -89,11 +93,12 @@ div [class*='loaderWrap'] {
|
||||
.price {
|
||||
white-space: pre;
|
||||
font-size: var(--font-size-small) !important;
|
||||
padding-left: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: calc(var(--spacer) / 4);
|
||||
width: calc(100% - calc(var(--spacer) / 2));
|
||||
margin: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
|
||||
width: calc(100% - var(--spacer));
|
||||
}
|
||||
|
||||
.did {
|
||||
@ -102,4 +107,14 @@ div [class*='loaderWrap'] {
|
||||
display: block;
|
||||
text-align: left;
|
||||
color: var(--color-secondary);
|
||||
/* makes sure DotDotDot will kick in */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: var(--spacer) calc(var(--spacer) / 2);
|
||||
text-align: center;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
@ -17,6 +17,10 @@ export interface AssetSelectionAsset {
|
||||
checked: boolean
|
||||
}
|
||||
|
||||
function Empty() {
|
||||
return <div className={styles.empty}>No assets found.</div>
|
||||
}
|
||||
|
||||
export default function AssetSelection({
|
||||
assets,
|
||||
multiple,
|
||||
@ -52,7 +56,11 @@ export default function AssetSelection({
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div className={styles.scroll}>
|
||||
{assets ? (
|
||||
{!assets ? (
|
||||
<Loader />
|
||||
) : assets && !assets.length ? (
|
||||
<Empty />
|
||||
) : (
|
||||
assets
|
||||
.filter((asset: AssetSelectionAsset) =>
|
||||
searchValue !== ''
|
||||
@ -98,8 +106,6 @@ export default function AssetSelection({
|
||||
<PriceUnit price={asset.price} small className={styles.price} />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,178 +0,0 @@
|
||||
import React, { useState, ReactElement, ChangeEvent, useEffect } from 'react'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
import Price from '../../atoms/Price'
|
||||
import File from '../../atoms/File'
|
||||
import { computeOptions, useCompute } from '../../../hooks/useCompute'
|
||||
import styles from './Compute.module.css'
|
||||
import Input from '../../atoms/Input'
|
||||
import Alert from '../../atoms/Alert'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import checkPreviousOrder from '../../../utils/checkPreviousOrder'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { usePricing } from '../../../hooks/usePricing'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
|
||||
export default function Compute({
|
||||
ddo,
|
||||
isBalanceSufficient,
|
||||
dtBalance
|
||||
}: {
|
||||
ddo: DDO
|
||||
isBalanceSufficient: boolean
|
||||
dtBalance: string
|
||||
}): ReactElement {
|
||||
const { marketFeeAddress } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { type } = useAsset()
|
||||
const { compute, isLoading, computeStepText, computeError } = useCompute()
|
||||
const { buyDT, dtSymbol } = usePricing(ddo)
|
||||
const { price } = useAsset()
|
||||
const computeService = ddo.findServiceByType('compute')
|
||||
const metadataService = ddo.findServiceByType('metadata')
|
||||
|
||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||
const [, setError] = useState('')
|
||||
const [computeType, setComputeType] = useState('nodejs')
|
||||
const [computeContainer, setComputeContainer] = useState(
|
||||
computeOptions[0].value
|
||||
)
|
||||
const [algorithmRawCode, setAlgorithmRawCode] = useState('')
|
||||
const [isPublished, setIsPublished] = useState(false)
|
||||
const [file, setFile] = useState(null)
|
||||
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
||||
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
||||
const isComputeButtonDisabled =
|
||||
isJobStarting === true ||
|
||||
file === null ||
|
||||
computeType === '' ||
|
||||
!ocean ||
|
||||
!isBalanceSufficient
|
||||
const hasDatatoken = Number(dtBalance) >= 1
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
async function checkPreviousOrders() {
|
||||
const orderId = await checkPreviousOrder(ocean, accountId, ddo, 'compute')
|
||||
setPreviousOrderId(orderId)
|
||||
setHasPreviousOrder(!!orderId)
|
||||
}
|
||||
checkPreviousOrders()
|
||||
}, [ocean, ddo, accountId])
|
||||
|
||||
const handleSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
const comType = event.target.value
|
||||
setComputeType(comType)
|
||||
|
||||
const selectedComputeOption = computeOptions.find((x) => x.name === comType)
|
||||
if (selectedComputeOption !== undefined)
|
||||
setComputeContainer(selectedComputeOption.value)
|
||||
}
|
||||
|
||||
// const startJob = async () => {
|
||||
// try {
|
||||
// if (!ocean) return
|
||||
|
||||
// setIsJobStarting(true)
|
||||
// setIsPublished(false)
|
||||
// setError('')
|
||||
|
||||
// !hasPreviousOrder && !hasDatatoken && (await buyDT('1'))
|
||||
|
||||
// await compute(
|
||||
// ddo.id,
|
||||
// computeService,
|
||||
// ddo.dataToken,
|
||||
// algorithmRawCode,
|
||||
// computeContainer,
|
||||
// marketFeeAddress,
|
||||
// previousOrderId
|
||||
// )
|
||||
|
||||
// setHasPreviousOrder(true)
|
||||
// setIsPublished(true)
|
||||
// setFile(null)
|
||||
// } catch (error) {
|
||||
// setError('Failed to start job!')
|
||||
// Logger.error(error.message)
|
||||
// } finally {
|
||||
// setIsJobStarting(false)
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.info}>
|
||||
<div className={styles.filewrapper}>
|
||||
<File file={metadataService.attributes.main.files[0]} small />
|
||||
</div>
|
||||
<div className={styles.pricewrapper}>
|
||||
<Price price={price} conversion />
|
||||
{hasDatatoken && (
|
||||
<div className={styles.hasTokens}>
|
||||
You own {dtBalance} {dtSymbol} allowing you to use this data set
|
||||
without paying again.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{type === 'algorithm' ? (
|
||||
<Input
|
||||
type="select"
|
||||
name="data"
|
||||
label="Select dataset for the algorithm"
|
||||
placeholder=""
|
||||
size="small"
|
||||
value="dataset-1"
|
||||
options={['dataset-1', 'dataset-2', 'dataset-3'].map((x) => x)}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
type="select"
|
||||
name="algorithm"
|
||||
label="Select image to run the algorithm"
|
||||
placeholder=""
|
||||
size="small"
|
||||
value={computeType}
|
||||
options={computeOptions.map((x) => x.name)}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={styles.actions}>
|
||||
{isLoading ? (
|
||||
<Loader message={computeStepText} />
|
||||
) : (
|
||||
<Alert text="Compute is coming back at a later stage." state="info" />
|
||||
// <Button
|
||||
// style="primary"
|
||||
// onClick={() => startJob()}
|
||||
// disabled={isComputeButtonDisabled}
|
||||
// >
|
||||
// {hasDatatoken || hasPreviousOrder ? 'Start job' : 'Buy'}
|
||||
// </Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer className={styles.feedback}>
|
||||
{computeError !== undefined && (
|
||||
<Alert text={computeError} state="error" />
|
||||
)}
|
||||
{isPublished && (
|
||||
<Alert
|
||||
title="Your job started!"
|
||||
text="Watch the progress in the history page."
|
||||
state="success"
|
||||
/>
|
||||
)}
|
||||
<Web3Feedback isBalanceSufficient={isBalanceSufficient} />
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
.form {
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin-top: -0.8rem;
|
||||
}
|
||||
|
||||
.form > div > label,
|
||||
[class*='ButtonBuy-module--actions'] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form > div > label {
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.form [class*='AssetSelection-module--selection'] {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.actions a,
|
||||
.actions button {
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import styles from './FormComputeDataset.module.css'
|
||||
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||
import Input from '../../../atoms/Input'
|
||||
import { FormFieldProps } from '../../../../@types/Form'
|
||||
import { useStaticQuery, graphql } from 'gatsby'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||
import ButtonBuy from '../../../atoms/ButtonBuy'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query StartComputeDatasetQuery {
|
||||
content: allFile(
|
||||
filter: { relativePath: { eq: "pages/startComputeDataset.json" } }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
childPagesJson {
|
||||
description
|
||||
form {
|
||||
success
|
||||
successAction
|
||||
error
|
||||
data {
|
||||
name
|
||||
label
|
||||
help
|
||||
type
|
||||
required
|
||||
sortOptions
|
||||
options
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function FormStartCompute({
|
||||
algorithms,
|
||||
ddoListAlgorithms,
|
||||
setSelectedAlgorithm,
|
||||
isLoading,
|
||||
isComputeButtonDisabled,
|
||||
hasPreviousOrder,
|
||||
hasDatatoken,
|
||||
dtSymbol,
|
||||
dtBalance,
|
||||
stepText,
|
||||
datasetTimeout
|
||||
}: {
|
||||
algorithms: AssetSelectionAsset[]
|
||||
ddoListAlgorithms: DDO[]
|
||||
setSelectedAlgorithm: React.Dispatch<React.SetStateAction<DDO>>
|
||||
isLoading: boolean
|
||||
isComputeButtonDisabled: boolean
|
||||
hasPreviousOrder: boolean
|
||||
hasDatatoken: boolean
|
||||
dtSymbol: string
|
||||
dtBalance: string
|
||||
stepText: string
|
||||
datasetTimeout: string
|
||||
}): ReactElement {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childPagesJson
|
||||
|
||||
const {
|
||||
isValid,
|
||||
values
|
||||
}: FormikContextType<{ algorithm: string }> = useFormikContext()
|
||||
|
||||
function getAlgorithmAsset(algorithmId: string): DDO {
|
||||
let assetDdo = null
|
||||
ddoListAlgorithms.forEach((ddo: DDO) => {
|
||||
if (ddo.id === algorithmId) assetDdo = ddo
|
||||
})
|
||||
return assetDdo
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!values.algorithm) return
|
||||
setSelectedAlgorithm(getAlgorithmAsset(values.algorithm))
|
||||
}, [values.algorithm])
|
||||
|
||||
return (
|
||||
<Form className={styles.form}>
|
||||
{content.form.data.map((field: FormFieldProps) => (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={algorithms}
|
||||
component={Input}
|
||||
/>
|
||||
))}
|
||||
|
||||
<ButtonBuy
|
||||
action="compute"
|
||||
disabled={isComputeButtonDisabled || !isValid}
|
||||
hasPreviousOrder={hasPreviousOrder}
|
||||
hasDatatoken={hasDatatoken}
|
||||
dtSymbol={dtSymbol}
|
||||
dtBalance={dtBalance}
|
||||
stepText={stepText}
|
||||
isLoading={isLoading}
|
||||
type="submit"
|
||||
assetTimeout={datasetTimeout}
|
||||
/>
|
||||
</Form>
|
||||
)
|
||||
}
|
@ -21,8 +21,9 @@
|
||||
|
||||
.feedback {
|
||||
width: 100%;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.help {
|
||||
composes: help from './index.module.css';
|
||||
composes: help from '../index.module.css';
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import Compute from './Compute'
|
||||
import ddo from '../../../../tests/unit/__fixtures__/ddo'
|
||||
import Compute from '.'
|
||||
import ddo from '../../../../../tests/unit/__fixtures__/ddo'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
export default {
|
||||
@ -13,5 +13,10 @@ export default {
|
||||
}
|
||||
|
||||
export const Default = (): ReactElement => (
|
||||
<Compute ddo={ddo as DDO} dtBalance="1" isBalanceSufficient />
|
||||
<Compute
|
||||
ddo={ddo as DDO}
|
||||
dtBalance="1"
|
||||
isBalanceSufficient
|
||||
file={ddo.service[0].attributes.main.files[0]}
|
||||
/>
|
||||
)
|
462
src/components/organisms/AssetActions/Compute/index.tsx
Normal file
462
src/components/organisms/AssetActions/Compute/index.tsx
Normal file
@ -0,0 +1,462 @@
|
||||
import React, { useState, ReactElement, useEffect, useCallback } from 'react'
|
||||
import {
|
||||
DDO,
|
||||
File as FileMetadata,
|
||||
Logger,
|
||||
ServiceType,
|
||||
publisherTrustedAlgorithm,
|
||||
BestPrice
|
||||
} from '@oceanprotocol/lib'
|
||||
import { toast } from 'react-toastify'
|
||||
import Price from '../../../atoms/Price'
|
||||
import File from '../../../atoms/File'
|
||||
import Alert from '../../../atoms/Alert'
|
||||
import Web3Feedback from '../../../molecules/Wallet/Feedback'
|
||||
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
|
||||
import checkPreviousOrder from '../../../../utils/checkPreviousOrder'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { usePricing } from '../../../../hooks/usePricing'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import {
|
||||
queryMetadata,
|
||||
transformDDOToAssetSelection
|
||||
} from '../../../../utils/aquarius'
|
||||
import { Formik } from 'formik'
|
||||
import {
|
||||
getInitialValues,
|
||||
validationSchema
|
||||
} from '../../../../models/FormStartComputeDataset'
|
||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||
import { SearchQuery } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import axios from 'axios'
|
||||
import FormStartComputeDataset from './FormComputeDataset'
|
||||
import styles from './index.module.css'
|
||||
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { FrePrice } from '../../../../@types/apollo/FrePrice'
|
||||
import { PoolPrice } from '../../../../@types/apollo/PoolPrice'
|
||||
import { secondsToString } from '../../../../utils/metadata'
|
||||
|
||||
const SuccessAction = () => (
|
||||
<Button style="text" to="/history" size="small">
|
||||
Go to history →
|
||||
</Button>
|
||||
)
|
||||
|
||||
const freQuery = gql`
|
||||
query AlgorithmFrePrice($datatoken: String) {
|
||||
fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
|
||||
rate
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
const poolQuery = gql`
|
||||
query AlgorithmPoolPrice($datatoken: String) {
|
||||
pools(where: { datatokenAddress: $datatoken }) {
|
||||
spotPrice
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Compute({
|
||||
ddo,
|
||||
isBalanceSufficient,
|
||||
dtBalance,
|
||||
file
|
||||
}: {
|
||||
ddo: DDO
|
||||
isBalanceSufficient: boolean
|
||||
dtBalance: string
|
||||
file: FileMetadata
|
||||
}): ReactElement {
|
||||
const { marketFeeAddress } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, account, config } = useOcean()
|
||||
const { price, type } = useAsset()
|
||||
const { buyDT, pricingError, pricingStepText } = usePricing()
|
||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
const [algorithmList, setAlgorithmList] = useState<AssetSelectionAsset[]>()
|
||||
const [ddoAlgorithmList, setDdoAlgorithmList] = useState<DDO[]>()
|
||||
const [selectedAlgorithmAsset, setSelectedAlgorithmAsset] = useState<DDO>()
|
||||
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 [algorithmPrice, setAlgorithmPrice] = useState<BestPrice>()
|
||||
const [variables, setVariables] = useState({})
|
||||
const [
|
||||
previousAlgorithmOrderId,
|
||||
setPreviousAlgorithmOrderId
|
||||
] = useState<string>()
|
||||
const [datasetTimeout, setDatasetTimeout] = useState<string>()
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const {
|
||||
refetch: refetchFre,
|
||||
startPolling: startPollingFre,
|
||||
data: frePrice
|
||||
} = useQuery<FrePrice>(freQuery, {
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
const {
|
||||
refetch: refetchPool,
|
||||
startPolling: startPollingPool,
|
||||
data: poolPrice
|
||||
} = useQuery<PoolPrice>(poolQuery, {
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const isComputeButtonDisabled =
|
||||
isJobStarting === true || file === null || !ocean || !isBalanceSufficient
|
||||
const hasDatatoken = Number(dtBalance) >= 1
|
||||
|
||||
async function checkPreviousOrders(ddo: DDO, serviceType: ServiceType) {
|
||||
const orderId = await checkPreviousOrder(ocean, accountId, ddo, serviceType)
|
||||
const assetType = ddo.findServiceByType('metadata').attributes.main.type
|
||||
if (assetType === 'algorithm') {
|
||||
setPreviousAlgorithmOrderId(orderId)
|
||||
setHasPreviousAlgorithmOrder(!!orderId)
|
||||
} else {
|
||||
setPreviousDatasetOrderId(orderId)
|
||||
setHasPreviousDatasetOrder(!!orderId)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAssetDTBalance(asset: DDO) {
|
||||
const AssetDtBalance = await ocean.datatokens.balance(
|
||||
asset.dataToken,
|
||||
accountId
|
||||
)
|
||||
setHasAlgoAssetDatatoken(Number(AssetDtBalance) >= 1)
|
||||
}
|
||||
|
||||
function getQuerryString(
|
||||
trustedAlgorithmList: publisherTrustedAlgorithm[]
|
||||
): SearchQuery {
|
||||
let algoQuerry = ''
|
||||
trustedAlgorithmList.forEach((trusteAlgo) => {
|
||||
algoQuerry += `id:"${trusteAlgo.did}" OR `
|
||||
})
|
||||
if (trustedAlgorithmList.length > 1) {
|
||||
algoQuerry = algoQuerry.substring(0, algoQuerry.length - 3)
|
||||
}
|
||||
const algorithmQuery =
|
||||
trustedAlgorithmList.length > 0 ? `(${algoQuerry}) AND` : ``
|
||||
const query = {
|
||||
page: 1,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `${algorithmQuery} service.attributes.main.type:algorithm -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
async function getAlgorithmList(): Promise<AssetSelectionAsset[]> {
|
||||
const source = axios.CancelToken.source()
|
||||
const computeService = ddo.findServiceByType('compute')
|
||||
let algorithmSelectionList: AssetSelectionAsset[]
|
||||
if (
|
||||
!computeService.attributes.main.privacy ||
|
||||
!computeService.attributes.main.privacy.publisherTrustedAlgorithms ||
|
||||
(computeService.attributes.main.privacy.publisherTrustedAlgorithms
|
||||
.length === 0 &&
|
||||
!computeService.attributes.main.privacy.allowAllPublishedAlgorithms)
|
||||
) {
|
||||
algorithmSelectionList = []
|
||||
} else {
|
||||
const gueryResults = await queryMetadata(
|
||||
getQuerryString(
|
||||
computeService.attributes.main.privacy.publisherTrustedAlgorithms
|
||||
),
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
setDdoAlgorithmList(gueryResults.results)
|
||||
algorithmSelectionList = await transformDDOToAssetSelection(
|
||||
gueryResults.results,
|
||||
config.metadataCacheUri,
|
||||
[]
|
||||
)
|
||||
}
|
||||
return algorithmSelectionList
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { timeout } = (
|
||||
ddo.findServiceByType('access') || ddo.findServiceByType('compute')
|
||||
).attributes.main
|
||||
setDatasetTimeout(secondsToString(timeout))
|
||||
}, [ddo])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!frePrice ||
|
||||
frePrice.fixedRateExchanges.length === 0 ||
|
||||
algorithmPrice.type !== 'exchange'
|
||||
)
|
||||
return
|
||||
setAlgorithmPrice((prevState) => ({
|
||||
...prevState,
|
||||
value: frePrice.fixedRateExchanges[0].rate,
|
||||
address: frePrice.fixedRateExchanges[0].id
|
||||
}))
|
||||
}, [frePrice])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!poolPrice ||
|
||||
poolPrice.pools.length === 0 ||
|
||||
algorithmPrice.type !== 'pool'
|
||||
)
|
||||
return
|
||||
setAlgorithmPrice((prevState) => ({
|
||||
...prevState,
|
||||
value: poolPrice.pools[0].spotPrice
|
||||
}))
|
||||
}, [poolPrice])
|
||||
|
||||
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
|
||||
if (!ddo) return
|
||||
setAlgorithmPrice(ddo.price)
|
||||
setVariables({ datatoken: ddo?.dataToken.toLowerCase() })
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
getAlgorithmList().then((algorithms) => {
|
||||
setAlgorithmList(algorithms)
|
||||
})
|
||||
}, [ddo])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId) return
|
||||
checkPreviousOrders(ddo, 'compute')
|
||||
}, [ocean, ddo, accountId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean || !accountId || !selectedAlgorithmAsset) return
|
||||
|
||||
if (selectedAlgorithmAsset.findServiceByType('access')) {
|
||||
checkPreviousOrders(selectedAlgorithmAsset, 'access').then(() => {
|
||||
if (
|
||||
!hasPreviousAlgorithmOrder &&
|
||||
selectedAlgorithmAsset.findServiceByType('compute')
|
||||
) {
|
||||
checkPreviousOrders(selectedAlgorithmAsset, 'compute')
|
||||
}
|
||||
})
|
||||
} else if (selectedAlgorithmAsset.findServiceByType('compute')) {
|
||||
checkPreviousOrders(selectedAlgorithmAsset, 'compute')
|
||||
}
|
||||
checkAssetDTBalance(selectedAlgorithmAsset)
|
||||
initMetadata(selectedAlgorithmAsset)
|
||||
}, [selectedAlgorithmAsset, ocean, accountId, hasPreviousAlgorithmOrder])
|
||||
|
||||
// Output errors in toast UI
|
||||
useEffect(() => {
|
||||
const newError = error || pricingError
|
||||
if (!newError) return
|
||||
toast.error(newError)
|
||||
}, [error, pricingError])
|
||||
|
||||
async function startJob(algorithmId: string) {
|
||||
try {
|
||||
if (!ocean) return
|
||||
|
||||
setIsJobStarting(true)
|
||||
setIsPublished(false)
|
||||
setError(undefined)
|
||||
|
||||
const computeService = ddo.findServiceByType('compute')
|
||||
const serviceAlgo = selectedAlgorithmAsset.findServiceByType('access')
|
||||
? selectedAlgorithmAsset.findServiceByType('access')
|
||||
: selectedAlgorithmAsset.findServiceByType('compute')
|
||||
|
||||
const allowed = await ocean.compute.isOrderable(
|
||||
ddo.id,
|
||||
computeService.index,
|
||||
selectedAlgorithmAsset.id
|
||||
)
|
||||
Logger.log('[compute] Is data set orderable?', allowed)
|
||||
|
||||
if (!allowed) {
|
||||
setError(
|
||||
'Data set is not orderable in combination with selected algorithm.'
|
||||
)
|
||||
Logger.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.')
|
||||
Logger.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.')
|
||||
Logger.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,
|
||||
undefined,
|
||||
undefined,
|
||||
marketFeeAddress
|
||||
)
|
||||
|
||||
assetOrderId &&
|
||||
Logger.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,
|
||||
marketFeeAddress
|
||||
)
|
||||
|
||||
algorithmAssetOrderId &&
|
||||
Logger.log(
|
||||
`[compute] Got ${
|
||||
hasPreviousAlgorithmOrder ? 'existing' : 'new'
|
||||
} order ID for algorithm: `,
|
||||
algorithmAssetOrderId
|
||||
)
|
||||
|
||||
if (!assetOrderId || !algorithmAssetOrderId) {
|
||||
setError('Error ordering assets.')
|
||||
return
|
||||
}
|
||||
|
||||
Logger.log('[compute] Starting compute job.')
|
||||
|
||||
const output = {}
|
||||
const response = await ocean.compute.start(
|
||||
ddo.id,
|
||||
assetOrderId,
|
||||
ddo.dataToken,
|
||||
account,
|
||||
algorithmId,
|
||||
undefined,
|
||||
output,
|
||||
`${computeService.index}`,
|
||||
computeService.type,
|
||||
algorithmAssetOrderId,
|
||||
selectedAlgorithmAsset.dataToken
|
||||
)
|
||||
|
||||
if (
|
||||
!response ||
|
||||
response.status !== 10 ||
|
||||
response.statusText !== 'Job started'
|
||||
) {
|
||||
setError('Error starting compute job.')
|
||||
return
|
||||
}
|
||||
|
||||
Logger.log('[compute] Starting compute job response: ', response)
|
||||
|
||||
setHasPreviousDatasetOrder(true)
|
||||
setIsPublished(true)
|
||||
} catch (error) {
|
||||
setError('Failed to start job!')
|
||||
Logger.error('[compute] Failed to start job: ', error.message)
|
||||
} finally {
|
||||
setIsJobStarting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.info}>
|
||||
<File file={file} small />
|
||||
<Price price={(ddo as DDO).price} conversion />
|
||||
</div>
|
||||
|
||||
{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"
|
||||
/>
|
||||
) : (
|
||||
<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 || hasPreviousAlgorithmOrder
|
||||
}
|
||||
hasDatatoken={hasDatatoken}
|
||||
dtSymbol={ddo.dataTokenInfo?.symbol}
|
||||
dtBalance={dtBalance}
|
||||
stepText={pricingStepText || 'Starting Compute Job...'}
|
||||
datasetTimeout={datasetTimeout}
|
||||
/>
|
||||
</Formik>
|
||||
)}
|
||||
|
||||
<footer className={styles.feedback}>
|
||||
{isPublished && (
|
||||
<SuccessConfetti
|
||||
success="Your job started successfully! Watch the progress on the history page."
|
||||
action={<SuccessAction />}
|
||||
/>
|
||||
)}
|
||||
<Web3Feedback isBalanceSufficient={isBalanceSufficient} />
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
@ -12,19 +12,6 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
width: 100%;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.help {
|
||||
composes: help from './index.module.css';
|
||||
}
|
||||
|
||||
.help:not(:empty) {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.feedback {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { File as FileMetadata, DDO } from '@oceanprotocol/lib'
|
||||
import Button from '../../atoms/Button'
|
||||
import File from '../../atoms/File'
|
||||
import Price from '../../atoms/Price'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
import styles from './Consume.module.css'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
import { secondsToString } from '../../../utils/metadata'
|
||||
@ -17,6 +15,7 @@ import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { usePricing } from '../../../hooks/usePricing'
|
||||
import { useConsume } from '../../../hooks/useConsume'
|
||||
import ButtonBuy from '../../atoms/ButtonBuy'
|
||||
|
||||
const previousOrderQuery = gql`
|
||||
query PreviousOrder($id: String!, $account: String!) {
|
||||
@ -32,26 +31,6 @@ const previousOrderQuery = gql`
|
||||
}
|
||||
`
|
||||
|
||||
function getHelpText(
|
||||
token: {
|
||||
dtBalance: string
|
||||
dtSymbol: string
|
||||
},
|
||||
hasDatatoken: boolean,
|
||||
hasPreviousOrder: boolean,
|
||||
timeout: string
|
||||
) {
|
||||
const { dtBalance, dtSymbol } = token
|
||||
const assetTimeout = timeout === 'Forever' ? '' : ` for ${timeout}`
|
||||
const text = hasPreviousOrder
|
||||
? `You bought this data set already allowing you to download it without paying again${assetTimeout}.`
|
||||
: hasDatatoken
|
||||
? `You own ${dtBalance} ${dtSymbol} allowing you to use this data set by spending 1 ${dtSymbol}, but without paying OCEAN again.`
|
||||
: `For using this data set, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher and pool.`
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
export default function Consume({
|
||||
ddo,
|
||||
file,
|
||||
@ -68,10 +47,13 @@ export default function Consume({
|
||||
const { marketFeeAddress } = useSiteMetadata()
|
||||
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
||||
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
||||
const { isInPurgatory, price, type } = useAsset()
|
||||
const { buyDT, pricingStepText, pricingError, pricingIsLoading } = usePricing(
|
||||
ddo
|
||||
)
|
||||
const { isInPurgatory, price } = useAsset()
|
||||
const {
|
||||
buyDT,
|
||||
pricingStepText,
|
||||
pricingError,
|
||||
pricingIsLoading
|
||||
} = usePricing()
|
||||
const { consumeStepText, consume, consumeError } = useConsume()
|
||||
const [isDisabled, setIsDisabled] = useState(true)
|
||||
const [hasDatatoken, setHasDatatoken] = useState(false)
|
||||
@ -144,7 +126,7 @@ export default function Consume({
|
||||
])
|
||||
|
||||
async function handleConsume() {
|
||||
!hasPreviousOrder && !hasDatatoken && (await buyDT('1', price))
|
||||
!hasPreviousOrder && !hasDatatoken && (await buyDT('1', price, ddo))
|
||||
await consume(
|
||||
ddo.id,
|
||||
ddo.dataToken,
|
||||
@ -162,29 +144,18 @@ export default function Consume({
|
||||
}, [consumeError, pricingError])
|
||||
|
||||
const PurchaseButton = () => (
|
||||
<div className={styles.actions}>
|
||||
{consumeStepText || pricingIsLoading ? (
|
||||
<Loader message={consumeStepText || pricingStepText} />
|
||||
) : (
|
||||
<>
|
||||
<Button style="primary" onClick={handleConsume} disabled={isDisabled}>
|
||||
{hasPreviousOrder
|
||||
? 'Download'
|
||||
: `Buy ${
|
||||
assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`
|
||||
}`}
|
||||
</Button>
|
||||
<div className={styles.help}>
|
||||
{getHelpText(
|
||||
{ dtBalance, dtSymbol: ddo.dataTokenInfo.symbol },
|
||||
hasDatatoken,
|
||||
hasPreviousOrder,
|
||||
assetTimeout
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ButtonBuy
|
||||
action="download"
|
||||
disabled={isDisabled}
|
||||
hasPreviousOrder={hasPreviousOrder}
|
||||
hasDatatoken={hasDatatoken}
|
||||
dtSymbol={ddo.dataTokenInfo?.symbol}
|
||||
dtBalance={dtBalance}
|
||||
onClick={handleConsume}
|
||||
assetTimeout={assetTimeout}
|
||||
stepText={consumeStepText || pricingStepText}
|
||||
isLoading={pricingIsLoading}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -8,9 +8,14 @@ import { FormFieldProps } from '../../../../@types/Form'
|
||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './FormEditMetadata.module.css'
|
||||
import { getAlgorithmsForAssetSelection } from '../../../../utils/aquarius'
|
||||
import {
|
||||
queryMetadata,
|
||||
transformDDOToAssetSelection
|
||||
} from '../../../../utils/aquarius'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { ComputePrivacyForm } from '../../../../models/FormEditComputeDataset'
|
||||
import { publisherTrustedAlgorithm as PublisherTrustedAlgorithm } from '@oceanprotocol/lib'
|
||||
import axios from 'axios'
|
||||
|
||||
export default function FormEditComputeDataset({
|
||||
data,
|
||||
@ -34,11 +39,34 @@ export default function FormEditComputeDataset({
|
||||
'compute'
|
||||
).attributes.main.privacy
|
||||
|
||||
useEffect(() => {
|
||||
getAlgorithmsForAssetSelection(
|
||||
async function getAlgorithmList(
|
||||
publisherTrustedAlgorithms: PublisherTrustedAlgorithm[]
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
const source = axios.CancelToken.source()
|
||||
const query = {
|
||||
page: 1,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `service.attributes.main.type:algorithm -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
const querryResult = await queryMetadata(
|
||||
query,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const algorithmSelectionList = await transformDDOToAssetSelection(
|
||||
querryResult.results,
|
||||
config.metadataCacheUri,
|
||||
publisherTrustedAlgorithms
|
||||
).then((algorithms) => {
|
||||
)
|
||||
return algorithmSelectionList
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAlgorithmList(publisherTrustedAlgorithms).then((algorithms) => {
|
||||
setAllAlgorithms(algorithms)
|
||||
})
|
||||
}, [config.metadataCacheUri, publisherTrustedAlgorithms])
|
||||
|
@ -89,7 +89,9 @@ export default function Edit({
|
||||
}
|
||||
let ddoEditedTimeout = ddoEditedMetdata
|
||||
if (timeoutStringValue !== values.timeout) {
|
||||
const service = ddoEditedMetdata.findServiceByType('access')
|
||||
const service =
|
||||
ddoEditedMetdata.findServiceByType('access') ||
|
||||
ddoEditedMetdata.findServiceByType('compute')
|
||||
const timeout = mapTimeoutStringToSeconds(values.timeout)
|
||||
ddoEditedTimeout = await ocean.assets.editServiceTimeout(
|
||||
ddoEditedMetdata,
|
||||
|
@ -4,9 +4,3 @@
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--color-secondary);
|
||||
margin-top: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ export default function AssetActions(): ReactElement {
|
||||
ddo={ddo}
|
||||
dtBalance={dtBalance}
|
||||
isBalanceSufficient={isBalanceSufficient}
|
||||
file={metadata?.main.files[0]}
|
||||
/>
|
||||
) : (
|
||||
<Consume
|
||||
|
@ -2,7 +2,7 @@
|
||||
margin-top: var(--spacer);
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
||||
}
|
||||
|
||||
.metaFull code {
|
||||
|
@ -8,11 +8,11 @@
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: var(--font-size-mini);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.typeAndDate {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
margin-bottom: calc(var(--spacer) / 12);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@ -21,5 +21,5 @@
|
||||
padding-right: calc(var(--spacer) / 3.5);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
width: auto;
|
||||
font-size: var(--font-size-mini);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
@ -15,21 +15,6 @@ export default function MetaMain(): ReactElement {
|
||||
|
||||
return (
|
||||
<aside className={styles.meta}>
|
||||
<p>
|
||||
<ExplorerLink
|
||||
networkId={networkId}
|
||||
path={
|
||||
networkId === 137
|
||||
? `tokens/${ddo?.dataToken}`
|
||||
: `token/${ddo?.dataToken}`
|
||||
}
|
||||
>
|
||||
{`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`}
|
||||
</ExplorerLink>
|
||||
</p>
|
||||
<div>
|
||||
Published By <Publisher account={owner} />
|
||||
</div>
|
||||
<div className={styles.typeAndDate}>
|
||||
<AssetType
|
||||
type={type}
|
||||
@ -46,6 +31,19 @@ export default function MetaMain(): ReactElement {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<ExplorerLink
|
||||
networkId={networkId}
|
||||
path={
|
||||
networkId === 137
|
||||
? `tokens/${ddo?.dataToken}`
|
||||
: `token/${ddo?.dataToken}`
|
||||
}
|
||||
>
|
||||
{`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`}
|
||||
</ExplorerLink>
|
||||
</p>
|
||||
Published By <Publisher account={owner} />
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Conversion from '../../../../atoms/Price/Conversion'
|
||||
import { useField } from 'formik'
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import Input from '../../../../atoms/Input'
|
||||
import styles from './Price.module.css'
|
||||
import Error from './Error'
|
||||
@ -16,7 +16,23 @@ export default function Price({
|
||||
firstPrice?: string
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField('price')
|
||||
const { dtName, dtSymbol } = usePricing(ddo)
|
||||
const { getDTName, getDTSymbol } = usePricing()
|
||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
||||
const [dtName, setDtName] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
async function setDatatokenSymbol(ddo: DDO) {
|
||||
const dtSymbol = await getDTSymbol(ddo)
|
||||
setDtSymbol(dtSymbol)
|
||||
}
|
||||
async function setDatatokenName(ddo: DDO) {
|
||||
const dtName = await getDTName(ddo)
|
||||
setDtName(dtName)
|
||||
}
|
||||
setDatatokenSymbol(ddo)
|
||||
setDatatokenName(ddo)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.price}>
|
||||
|
@ -62,7 +62,7 @@ export default function Pricing({ ddo }: { ddo: DDO }): ReactElement {
|
||||
pricingIsLoading,
|
||||
pricingError,
|
||||
pricingStepText
|
||||
} = usePricing(ddo)
|
||||
} = usePricing()
|
||||
|
||||
const hasFeedback = pricingIsLoading || typeof success !== 'undefined'
|
||||
|
||||
@ -74,7 +74,7 @@ export default function Pricing({ ddo }: { ddo: DDO }): ReactElement {
|
||||
swapFee: `${values.swapFee / 100}`
|
||||
}
|
||||
|
||||
const tx = await createPricing(priceOptions)
|
||||
const tx = await createPricing(priceOptions, ddo)
|
||||
|
||||
// Pricing failed
|
||||
if (!tx || pricingError) {
|
||||
|
@ -72,13 +72,13 @@ export default function FormPublish(): ReactElement {
|
||||
|
||||
function handleImageSelectChange(imageSelected: string) {
|
||||
switch (imageSelected) {
|
||||
case 'node:pre-defined': {
|
||||
case 'node:latest': {
|
||||
setFieldValue('image', 'node')
|
||||
setFieldValue('containerTag', '10')
|
||||
setFieldValue('containerTag', 'latest')
|
||||
setFieldValue('entrypoint', 'node $ALGO')
|
||||
break
|
||||
}
|
||||
case 'python:pre-defined': {
|
||||
case 'python:latest': {
|
||||
setFieldValue('image', 'oceanprotocol/algo_dockers')
|
||||
setFieldValue('containerTag', 'python-panda')
|
||||
setFieldValue('entrypoint', 'python $ALGO')
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { MetadataCache, Logger } from '@oceanprotocol/lib'
|
||||
import queryString from 'query-string'
|
||||
import { TypeOf } from 'yup'
|
||||
|
||||
export const SortTermOptions = {
|
||||
Liquidity: 'liquidity',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DDO, Logger, BestPrice } from '@oceanprotocol/lib'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
import { Decimal } from 'decimal.js'
|
||||
import { getFirstPoolPrice } from '../utils/dtUtils'
|
||||
@ -24,16 +24,21 @@ interface PriceOptions {
|
||||
}
|
||||
|
||||
interface UsePricing {
|
||||
dtSymbol?: string
|
||||
dtName?: string
|
||||
getDTSymbol: (ddo: DDO) => Promise<string>
|
||||
getDTName: (ddo: DDO) => Promise<string>
|
||||
createPricing: (
|
||||
priceOptions: PriceOptions
|
||||
priceOptions: PriceOptions,
|
||||
ddo: DDO
|
||||
) => Promise<TransactionReceipt | string | void>
|
||||
sellDT: (dtAmount: number | string) => Promise<TransactionReceipt | void>
|
||||
mint: (tokensToMint: string) => Promise<TransactionReceipt | void>
|
||||
sellDT: (
|
||||
dtAmount: number | string,
|
||||
ddo: DDO
|
||||
) => Promise<TransactionReceipt | void>
|
||||
mint: (tokensToMint: string, ddo: DDO) => Promise<TransactionReceipt | void>
|
||||
buyDT: (
|
||||
dtAmount: number | string,
|
||||
price: BestPrice
|
||||
price: BestPrice,
|
||||
ddo: DDO
|
||||
) => Promise<TransactionReceipt | void>
|
||||
pricingStep?: number
|
||||
pricingStepText?: string
|
||||
@ -41,38 +46,37 @@ interface UsePricing {
|
||||
pricingIsLoading: boolean
|
||||
}
|
||||
|
||||
function usePricing(ddo: DDO): UsePricing {
|
||||
function usePricing(): UsePricing {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, config } = useOcean()
|
||||
const [pricingIsLoading, setPricingIsLoading] = useState(false)
|
||||
const [pricingStep, setPricingStep] = useState<number>()
|
||||
const [pricingStepText, setPricingStepText] = useState<string>()
|
||||
const [pricingError, setPricingError] = useState<string>()
|
||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
||||
const [dtName, setDtName] = useState<string>()
|
||||
|
||||
async function getDTSymbol(ddo: DDO): Promise<string> {
|
||||
if (!ocean || !accountId) return
|
||||
const { dataToken, dataTokenInfo } = ddo
|
||||
|
||||
// Get Datatoken info, from DDO first, then from chain
|
||||
useEffect(() => {
|
||||
if (!dataToken) return
|
||||
|
||||
async function init() {
|
||||
const dtSymbol = dataTokenInfo
|
||||
return dataTokenInfo
|
||||
? dataTokenInfo.symbol
|
||||
: await ocean?.datatokens.getSymbol(dataToken)
|
||||
setDtSymbol(dtSymbol)
|
||||
}
|
||||
|
||||
const dtName = dataTokenInfo
|
||||
async function getDTName(ddo: DDO): Promise<string> {
|
||||
if (!ocean || !accountId) return
|
||||
const { dataToken, dataTokenInfo } = ddo
|
||||
return dataTokenInfo
|
||||
? dataTokenInfo.name
|
||||
: await ocean?.datatokens.getName(dataToken)
|
||||
setDtName(dtName)
|
||||
}
|
||||
init()
|
||||
}, [ocean, dataToken, dataTokenInfo])
|
||||
|
||||
// Helper for setting steps & feedback for all flows
|
||||
function setStep(index: number, type: 'pool' | 'exchange' | 'buy' | 'sell') {
|
||||
async function setStep(
|
||||
index: number,
|
||||
type: 'pool' | 'exchange' | 'buy' | 'sell',
|
||||
ddo: DDO
|
||||
) {
|
||||
const dtSymbol = await getDTSymbol(ddo)
|
||||
setPricingStep(index)
|
||||
if (!dtSymbol) return
|
||||
|
||||
@ -97,8 +101,10 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
}
|
||||
|
||||
async function mint(
|
||||
tokensToMint: string
|
||||
tokensToMint: string,
|
||||
ddo: DDO
|
||||
): Promise<TransactionReceipt | void> {
|
||||
const { dataToken } = ddo
|
||||
Logger.log('mint function', dataToken, accountId)
|
||||
const balance = new Decimal(
|
||||
await ocean.datatokens.balance(dataToken, accountId)
|
||||
@ -117,7 +123,8 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
|
||||
async function buyDT(
|
||||
dtAmount: number | string,
|
||||
price: BestPrice
|
||||
price: BestPrice,
|
||||
ddo: DDO
|
||||
): Promise<TransactionReceipt | void> {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
@ -126,14 +133,14 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
try {
|
||||
setPricingIsLoading(true)
|
||||
setPricingError(undefined)
|
||||
setStep(1, 'buy')
|
||||
setStep(1, 'buy', ddo)
|
||||
|
||||
Logger.log('Price found for buying', price)
|
||||
switch (price?.type) {
|
||||
case 'pool': {
|
||||
const oceanAmmount = new Decimal(price.value).times(1.05).toString()
|
||||
const maxPrice = new Decimal(price.value).times(2).toString()
|
||||
setStep(2, 'buy')
|
||||
setStep(2, 'buy', ddo)
|
||||
Logger.log('Buying token from pool', price, accountId, price)
|
||||
tx = await ocean.pool.buyDT(
|
||||
accountId,
|
||||
@ -142,7 +149,7 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
oceanAmmount,
|
||||
maxPrice
|
||||
)
|
||||
setStep(3, 'buy')
|
||||
setStep(3, 'buy', ddo)
|
||||
Logger.log('DT buy response', tx)
|
||||
break
|
||||
}
|
||||
@ -162,13 +169,13 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
`${price.value}`,
|
||||
accountId
|
||||
)
|
||||
setStep(2, 'buy')
|
||||
setStep(2, 'buy', ddo)
|
||||
tx = await ocean.fixedRateExchange.buyDT(
|
||||
price.address,
|
||||
`${dtAmount}`,
|
||||
accountId
|
||||
)
|
||||
setStep(3, 'buy')
|
||||
setStep(3, 'buy', ddo)
|
||||
Logger.log('DT exchange buy response', tx)
|
||||
break
|
||||
}
|
||||
@ -177,7 +184,7 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
setPricingError(error.message)
|
||||
Logger.error(error)
|
||||
} finally {
|
||||
setStep(0, 'buy')
|
||||
setStep(0, 'buy', ddo)
|
||||
setPricingStepText(undefined)
|
||||
setPricingIsLoading(false)
|
||||
}
|
||||
@ -186,7 +193,8 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
}
|
||||
|
||||
async function sellDT(
|
||||
dtAmount: number | string
|
||||
dtAmount: number | string,
|
||||
ddo: DDO
|
||||
): Promise<TransactionReceipt | void> {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
@ -196,13 +204,14 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
}
|
||||
|
||||
try {
|
||||
const { dataToken } = ddo
|
||||
setPricingIsLoading(true)
|
||||
setPricingError(undefined)
|
||||
setStep(1, 'sell')
|
||||
setStep(1, 'sell', ddo)
|
||||
const pool = await getFirstPoolPrice(ocean, dataToken)
|
||||
if (!pool || pool.value === 0) return
|
||||
const price = new Decimal(pool.value).times(0.95).toString()
|
||||
setStep(2, 'sell')
|
||||
setStep(2, 'sell', ddo)
|
||||
Logger.log('Selling token to pool', pool, accountId, price)
|
||||
const tx = await ocean.pool.sellDT(
|
||||
accountId,
|
||||
@ -210,22 +219,26 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
`${dtAmount}`,
|
||||
price
|
||||
)
|
||||
setStep(3, 'sell')
|
||||
setStep(3, 'sell', ddo)
|
||||
Logger.log('DT sell response', tx)
|
||||
return tx
|
||||
} catch (error) {
|
||||
setPricingError(error.message)
|
||||
Logger.error(error)
|
||||
} finally {
|
||||
setStep(0, 'sell')
|
||||
setStep(0, 'sell', ddo)
|
||||
setPricingStepText(undefined)
|
||||
setPricingIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function createPricing(
|
||||
priceOptions: PriceOptions
|
||||
priceOptions: PriceOptions,
|
||||
ddo: DDO
|
||||
): Promise<TransactionReceipt | void> {
|
||||
const { dataToken } = ddo
|
||||
const dtSymbol = await getDTSymbol(ddo)
|
||||
|
||||
if (!ocean || !accountId || !dtSymbol) return
|
||||
|
||||
const {
|
||||
@ -247,12 +260,12 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
setPricingIsLoading(true)
|
||||
setPricingError(undefined)
|
||||
|
||||
setStep(99, 'pool')
|
||||
setStep(99, 'pool', ddo)
|
||||
|
||||
try {
|
||||
// if fixedPrice set dt to max amount
|
||||
if (!isPool) dtAmount = 1000
|
||||
await mint(`${dtAmount}`)
|
||||
await mint(`${dtAmount}`, ddo)
|
||||
|
||||
// dtAmount for fixed price is set to max
|
||||
const tx = isPool
|
||||
@ -265,10 +278,10 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
`${oceanAmount}`,
|
||||
swapFee
|
||||
)
|
||||
.next((step: number) => setStep(step, 'pool'))
|
||||
.next((step: number) => setStep(step, 'pool', ddo))
|
||||
: await ocean.fixedRateExchange
|
||||
.create(dataToken, `${price}`, accountId, `${dtAmount}`)
|
||||
.next((step: number) => setStep(step, 'exchange'))
|
||||
.next((step: number) => setStep(step, 'exchange', ddo))
|
||||
await sleep(20000)
|
||||
return tx
|
||||
} catch (error) {
|
||||
@ -282,8 +295,8 @@ function usePricing(ddo: DDO): UsePricing {
|
||||
}
|
||||
|
||||
return {
|
||||
dtSymbol,
|
||||
dtName,
|
||||
getDTSymbol,
|
||||
getDTName,
|
||||
createPricing,
|
||||
buyDT,
|
||||
sellDT,
|
||||
|
@ -11,7 +11,7 @@ export const validationSchema: Yup.SchemaOf<MetadataPublishFormAlgorithm> = Yup.
|
||||
description: Yup.string().min(10).required('Required'),
|
||||
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||
dockerImage: Yup.string()
|
||||
.matches(/node:pre-defined|python:pre-defined|custom image/g, {
|
||||
.matches(/node:latest|python:latest|custom image/g, {
|
||||
excludeEmptyString: true
|
||||
})
|
||||
.required('Required'),
|
||||
@ -30,9 +30,9 @@ export const validationSchema: Yup.SchemaOf<MetadataPublishFormAlgorithm> = Yup.
|
||||
export const initialValues: Partial<MetadataPublishFormAlgorithm> = {
|
||||
name: '',
|
||||
author: '',
|
||||
dockerImage: 'node:pre-defined',
|
||||
dockerImage: 'node:latest',
|
||||
image: 'node',
|
||||
containerTag: '10',
|
||||
containerTag: 'latest',
|
||||
entrypoint: 'node $ALGO',
|
||||
files: '',
|
||||
description: '',
|
||||
|
13
src/models/FormStartComputeDataset.ts
Normal file
13
src/models/FormStartComputeDataset.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as Yup from 'yup'
|
||||
|
||||
export const validationSchema: Yup.SchemaOf<{
|
||||
algorithm: string
|
||||
}> = Yup.object().shape({
|
||||
algorithm: Yup.string().required('Required')
|
||||
})
|
||||
|
||||
export function getInitialValues(): { algorithm: string } {
|
||||
return {
|
||||
algorithm: undefined
|
||||
}
|
||||
}
|
@ -74,6 +74,7 @@ function AssetProvider({
|
||||
const [type, setType] = useState<MetadataMain['type']>()
|
||||
const [variables, setVariables] = useState({})
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const {
|
||||
refetch: refetchFre,
|
||||
startPolling: startPollingFre,
|
||||
@ -90,6 +91,7 @@ function AssetProvider({
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
// this is not working as expected, thus we need to fetch both pool and fre
|
||||
// useEffect(() => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
Config,
|
||||
DDO,
|
||||
DID,
|
||||
Logger,
|
||||
publisherTrustedAlgorithm as PublisherTrustedAlgorithm
|
||||
} from '@oceanprotocol/lib/'
|
||||
|
||||
import {
|
||||
QueryResult,
|
||||
SearchQuery
|
||||
@ -12,7 +12,6 @@ import {
|
||||
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
|
||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
import web3 from 'web3'
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
||||
|
||||
// TODO: import directly from ocean.js somehow.
|
||||
// Transforming Aquarius' direct response is needed for getting actual DDOs
|
||||
@ -107,33 +106,17 @@ export async function getAssetsNames(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAlgorithmsForAssetSelection(
|
||||
export async function transformDDOToAssetSelection(
|
||||
ddoList: DDO[],
|
||||
metadataCacheUri: string,
|
||||
selectedAlgorithms?: PublisherTrustedAlgorithm[]
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
const query = {
|
||||
page: 1,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `(service.attributes.main.type:algorithm) -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
const source = axios.CancelToken.source()
|
||||
const didList: string[] = []
|
||||
const priceList: any = {}
|
||||
const result = await queryMetadata(
|
||||
query as any,
|
||||
metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
result?.results?.forEach((ddo: DDO) => {
|
||||
const did: string = web3.utils
|
||||
.toChecksumAddress(ddo.dataToken)
|
||||
.replace('0x', 'did:op:')
|
||||
didList.push(did)
|
||||
priceList[did] = ddo.price.value
|
||||
ddoList.forEach((ddo: DDO) => {
|
||||
didList.push(ddo.id)
|
||||
priceList[ddo.id] = ddo.price.value
|
||||
})
|
||||
const ddoNames = await getAssetsNames(didList, metadataCacheUri, source.token)
|
||||
const algorithmList: AssetSelectionAsset[] = []
|
||||
|
Loading…
Reference in New Issue
Block a user