Merge branch 'main' into fix/issue583-start-second-job
This commit is contained in:
commit
e620ee2a47
|
@ -28,8 +28,8 @@
|
|||
"label": "Docker Image",
|
||||
"placeholder": "e.g. python3.7",
|
||||
"help": "Please select an image to run your algorithm.",
|
||||
"type": "select",
|
||||
"options": ["node:latest", "python:latest", "custom image"],
|
||||
"type": "boxSelection",
|
||||
"options": [],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "select",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true
|
||||
},
|
||||
|
|
|
@ -4,6 +4,9 @@ import styles from './InputElement.module.css'
|
|||
import { InputProps } from '.'
|
||||
import FilesInput from '../../molecules/FormFields/FilesInput'
|
||||
import Terms from '../../molecules/FormFields/Terms'
|
||||
import BoxSelection, {
|
||||
BoxSelectionOption
|
||||
} from '../../molecules/FormFields/BoxSelection'
|
||||
import Datatoken from '../../molecules/FormFields/Datatoken'
|
||||
import classNames from 'classnames/bind'
|
||||
import AssetSelection, {
|
||||
|
@ -91,7 +94,7 @@ export default function InputElement({
|
|||
id={slugify(option)}
|
||||
type={type}
|
||||
name={name}
|
||||
checked={props.defaultChecked}
|
||||
defaultChecked={props.defaultChecked}
|
||||
{...props}
|
||||
/>
|
||||
<label className={styles.radioLabel} htmlFor={slugify(option)}>
|
||||
|
@ -125,6 +128,15 @@ export default function InputElement({
|
|||
return <Datatoken name={name} {...field} {...props} />
|
||||
case 'terms':
|
||||
return <Terms name={name} options={options} {...field} {...props} />
|
||||
case 'boxSelection':
|
||||
return (
|
||||
<BoxSelection
|
||||
name={name}
|
||||
options={(options as unknown) as BoxSelectionOption[]}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return prefix || postfix ? (
|
||||
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>
|
||||
|
|
|
@ -2,9 +2,10 @@ import AssetTeaser from '../molecules/AssetTeaser'
|
|||
import * as React from 'react'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import ddo from '../../../tests/unit/__fixtures__/ddo'
|
||||
import { AssetListPrices } from '../../utils/subgraph'
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Asset Teaser'
|
||||
}
|
||||
|
||||
export const Default = () => <AssetTeaser ddo={ddo as DDO} />
|
||||
export const Default = () => <AssetTeaser ddo={ddo as DDO} price={undefined} />
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Link } from 'gatsby'
|
|||
import Dotdotdot from 'react-dotdotdot'
|
||||
import Price from '../atoms/Price'
|
||||
import styles from './AssetTeaser.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { DDO, BestPrice } from '@oceanprotocol/lib'
|
||||
import removeMarkdown from 'remove-markdown'
|
||||
import Publisher from '../atoms/Publisher'
|
||||
import Time from '../atoms/Time'
|
||||
|
@ -11,9 +11,13 @@ import AssetType from '../atoms/AssetType'
|
|||
|
||||
declare type AssetTeaserProps = {
|
||||
ddo: DDO
|
||||
price: BestPrice
|
||||
}
|
||||
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
||||
ddo,
|
||||
price
|
||||
}: AssetTeaserProps) => {
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
const { name, type } = attributes.main
|
||||
const { dataTokenInfo } = ddo
|
||||
|
@ -47,7 +51,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
|||
</div>
|
||||
|
||||
<footer className={styles.foot}>
|
||||
<Price price={ddo.price} small />
|
||||
<Price price={price} small />
|
||||
<p className={styles.date}>
|
||||
<Time date={ddo?.created} relative />
|
||||
</p>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
.boxSelectionsWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0 calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boxSelection {
|
||||
display: block;
|
||||
flex: 1 1 0px;
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
|
||||
calc(var(--spacer) / 4) calc(var(--spacer) / 2) !important;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper input[type='radio'] {
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input[type='radio']:checked + label {
|
||||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.boxSelection svg {
|
||||
width: var(--font-size-h4);
|
||||
height: var(--font-size-h4);
|
||||
fill: currentColor;
|
||||
margin-bottom: calc(var(--spacer) / 5);
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
color: var(--color-secondary);
|
||||
font-weight: normal;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import React, { ChangeEvent } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import styles from './BoxSelection.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface BoxSelectionOption {
|
||||
name: string
|
||||
checked: boolean
|
||||
title: JSX.Element | string
|
||||
icon?: JSX.Element
|
||||
text?: JSX.Element | string
|
||||
}
|
||||
|
||||
export default function BoxSelection({
|
||||
name,
|
||||
options,
|
||||
disabled,
|
||||
handleChange,
|
||||
...props
|
||||
}: {
|
||||
name: string
|
||||
options: BoxSelectionOption[]
|
||||
disabled?: boolean
|
||||
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
|
||||
}): JSX.Element {
|
||||
const styleClassesWrapper = cx({
|
||||
boxSelectionsWrapper: true,
|
||||
[styles.disabled]: disabled
|
||||
})
|
||||
|
||||
const styleClassesInput = cx({
|
||||
input: true,
|
||||
radio: true
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styleClassesWrapper}>
|
||||
{!options ? (
|
||||
<Loader />
|
||||
) : (
|
||||
options.map((value: BoxSelectionOption) => (
|
||||
<div key={value.name}>
|
||||
<input
|
||||
id={value.name}
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
defaultChecked={value.checked}
|
||||
onChange={(event) => handleChange(event)}
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
value={value.name}
|
||||
name={name}
|
||||
/>
|
||||
<label
|
||||
className={`${styles.boxSelection} ${styles.label}`}
|
||||
htmlFor={value.name}
|
||||
title={value.name}
|
||||
>
|
||||
{value.icon}
|
||||
<span className={styles.title}>{value.title}</span>
|
||||
{value.text}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -30,3 +30,7 @@
|
|||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.appearances div[class*='boxSelectionsWrapper'] {
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { DarkMode } from 'use-dark-mode'
|
||||
import Button from '../../atoms/Button'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import styles from './Appearance.module.css'
|
||||
import { ReactComponent as Moon } from '../../../images/moon.svg'
|
||||
import { ReactComponent as Sun } from '../../../images/sun.svg'
|
||||
|
||||
const buttons = ['Light', 'Dark']
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Appearance.module.css'
|
||||
|
||||
export default function Appearance({
|
||||
darkMode
|
||||
}: {
|
||||
darkMode: DarkMode
|
||||
}): ReactElement {
|
||||
return (
|
||||
<li>
|
||||
<Label htmlFor="">Appearance</Label>
|
||||
<div className={styles.buttons}>
|
||||
{buttons.map((button) => {
|
||||
const isDark = button === 'Dark'
|
||||
const selected =
|
||||
(isDark && darkMode.value) || (!isDark && !darkMode.value)
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'Light',
|
||||
checked: !darkMode.value,
|
||||
title: 'Light',
|
||||
icon: <Sun />
|
||||
},
|
||||
{
|
||||
name: 'Dark',
|
||||
checked: darkMode.value,
|
||||
title: 'Dark',
|
||||
icon: <Moon />
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={button}
|
||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
||||
size="small"
|
||||
style="text"
|
||||
onClick={() => (isDark ? darkMode.enable() : darkMode.disable())}
|
||||
>
|
||||
{isDark ? <Moon /> : <Sun />}
|
||||
{button}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
event.target.value === 'Dark' ? darkMode.enable() : darkMode.disable()
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={styles.appearances}>
|
||||
<Label htmlFor="">Appearance</Label>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="appearanceMode"
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<FormHelp>Defaults to your OS setting, select to override.</FormHelp>
|
||||
</li>
|
||||
)
|
||||
|
|
|
@ -17,3 +17,14 @@
|
|||
.selected {
|
||||
composes: selected from './Appearance.module.css';
|
||||
}
|
||||
|
||||
.chains div[class*='boxSelectionsWrapper'] {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) / 4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.chains label[class*='boxSelection'] {
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4) !important;
|
||||
}
|
||||
|
|
|
@ -1,53 +1,58 @@
|
|||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
import Button from '../../atoms/Button'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Chain.module.css'
|
||||
|
||||
export default function Chain(): ReactElement {
|
||||
const { web3 } = useWeb3()
|
||||
const { config, connect } = useOcean()
|
||||
|
||||
async function connectOcean(networkName: string) {
|
||||
const config = getOceanConfig(networkName)
|
||||
async function connectOcean(event: ChangeEvent<HTMLInputElement>) {
|
||||
const config = getOceanConfig(event.target.value)
|
||||
await connect(config)
|
||||
}
|
||||
|
||||
const chains = [
|
||||
{ name: 'ETH', oceanConfig: 'mainnet', label: 'Mainnet' },
|
||||
{ name: 'Polygon/Matic', oceanConfig: 'polygon', label: 'Mainnet' },
|
||||
{ name: 'Moonbase Alpha', oceanConfig: 'moonbeamalpha', label: 'Testnet' }
|
||||
function isNetworkSelected(oceanConfig: string) {
|
||||
return (config as ConfigHelperConfig).network === oceanConfig
|
||||
}
|
||||
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'mainnet',
|
||||
checked: isNetworkSelected('mainnet'),
|
||||
title: 'ETH',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'polygon',
|
||||
checked: isNetworkSelected('polygon'),
|
||||
title: 'Polygon/Matic',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'moonbeamalpha',
|
||||
checked: isNetworkSelected('moonbeamalpha'),
|
||||
title: 'Moonbase Alpha',
|
||||
text: 'Testnet'
|
||||
}
|
||||
]
|
||||
|
||||
// TODO: to fully solve https://github.com/oceanprotocol/market/issues/432
|
||||
// there are more considerations for users with a wallet connected (wallet network vs. setting network).
|
||||
// For now, only show the setting for non-wallet users.
|
||||
return !web3 ? (
|
||||
<li>
|
||||
<li className={styles.chains}>
|
||||
<Label htmlFor="">Chain</Label>
|
||||
<div className={styles.buttons}>
|
||||
{chains.map((button) => {
|
||||
const selected =
|
||||
(config as ConfigHelperConfig).network === button.oceanConfig
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={button.name}
|
||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
||||
size="small"
|
||||
style="text"
|
||||
onClick={() => connectOcean(button.oceanConfig)}
|
||||
>
|
||||
{button.name}
|
||||
<span>{button.label}</span>
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="chain"
|
||||
handleChange={connectOcean}
|
||||
/>
|
||||
<FormHelp>Switch the data source for the interface.</FormHelp>
|
||||
</li>
|
||||
) : null
|
||||
|
|
|
@ -16,3 +16,9 @@
|
|||
font-size: var(--font-size-small);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
import AssetTeaser from '../molecules/AssetTeaser'
|
||||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Pagination from '../molecules/Pagination'
|
||||
import styles from './AssetList.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import classNames from 'classnames/bind'
|
||||
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
|
||||
import Loader from '../atoms/Loader'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
function LoaderArea() {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
<Loader />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
declare type AssetListProps = {
|
||||
assets: DDO[]
|
||||
showPagination: boolean
|
||||
page?: number
|
||||
totalPages?: number
|
||||
isLoading?: boolean
|
||||
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
||||
className?: string
|
||||
}
|
||||
|
@ -21,9 +32,22 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||
showPagination,
|
||||
page,
|
||||
totalPages,
|
||||
isLoading,
|
||||
onPageChange,
|
||||
className
|
||||
}) => {
|
||||
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!assets) return
|
||||
isLoading && setLoading(true)
|
||||
getAssetsBestPrices(assets).then((asset) => {
|
||||
setAssetWithPrices(asset)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [assets])
|
||||
|
||||
// // This changes the page field inside the query
|
||||
function handlePageChange(selected: number) {
|
||||
onPageChange(selected + 1)
|
||||
|
@ -34,11 +58,19 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||
[className]: className
|
||||
})
|
||||
|
||||
return (
|
||||
return assetsWithPrices &&
|
||||
!loading &&
|
||||
(isLoading === undefined || isLoading === false) ? (
|
||||
<>
|
||||
<div className={styleClasses}>
|
||||
{assets.length > 0 ? (
|
||||
assets.map((ddo) => <AssetTeaser ddo={ddo} key={ddo.id} />)
|
||||
{assetsWithPrices.length > 0 ? (
|
||||
assetsWithPrices.map((assetWithPrice) => (
|
||||
<AssetTeaser
|
||||
ddo={assetWithPrice.ddo}
|
||||
price={assetWithPrice.price}
|
||||
key={assetWithPrice.ddo.id}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className={styles.empty}>No results found.</div>
|
||||
)}
|
||||
|
@ -52,6 +84,8 @@ const AssetList: React.FC<AssetListProps> = ({
|
|||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<LoaderArea />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,3 +2,25 @@
|
|||
text-transform: uppercase;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.computeTableContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refresh,
|
||||
.refresh:first-child {
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.refresh svg {
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-right: calc(var(--spacer) / 6);
|
||||
margin-bottom: -0.1rem;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { DDO, Logger, Service, Provider } from '@oceanprotocol/lib'
|
|||
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import Table from '../../../atoms/Table'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
|
@ -13,8 +14,9 @@ import { queryMetadata } from '../../../../utils/aquarius'
|
|||
import axios, { CancelToken } from 'axios'
|
||||
import { ComputeOrders } from '../../../../@types/apollo/ComputeOrders'
|
||||
import Details from './Details'
|
||||
import styles from './index.module.css'
|
||||
import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
|
||||
import { ReactComponent as Refresh } from '../../../../images/refresh.svg'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const getComputeOrders = gql`
|
||||
query ComputeOrders($user: String!) {
|
||||
|
@ -99,7 +101,7 @@ async function getAssetMetadata(
|
|||
export default function ComputeJobs(): ReactElement {
|
||||
const { ocean, account, config } = useOcean()
|
||||
const { accountId } = useWeb3()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
|
||||
const { data } = useQuery<ComputeOrders>(getComputeOrders, {
|
||||
variables: {
|
||||
|
@ -107,139 +109,158 @@ export default function ComputeJobs(): ReactElement {
|
|||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (data === undefined || !config?.metadataCacheUri) return
|
||||
async function getJobs() {
|
||||
if (!ocean || !account) return
|
||||
|
||||
async function getJobs() {
|
||||
if (!ocean || !account) return
|
||||
setIsLoading(true)
|
||||
|
||||
setIsLoading(true)
|
||||
const dtList = []
|
||||
const computeJobs: ComputeJobMetaData[] = []
|
||||
for (let i = 0; i < data.tokenOrders.length; i++) {
|
||||
dtList.push(data.tokenOrders[i].datatokenId.address)
|
||||
}
|
||||
const queryDtList = JSON.stringify(dtList)
|
||||
.replace(/,/g, ' ')
|
||||
.replace(/"/g, '')
|
||||
.replace(/(\[|\])/g, '')
|
||||
|
||||
const dtList = []
|
||||
const computeJobs: ComputeJobMetaData[] = []
|
||||
try {
|
||||
const source = axios.CancelToken.source()
|
||||
const assets = await getAssetMetadata(
|
||||
queryDtList,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const providers: Provider[] = []
|
||||
const serviceEndpoints: string[] = []
|
||||
for (let i = 0; i < data.tokenOrders.length; i++) {
|
||||
dtList.push(data.tokenOrders[i].datatokenId.address)
|
||||
try {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(data.tokenOrders[i].datatokenId.address)
|
||||
.replace('0x', 'did:op:')
|
||||
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
|
||||
if (!ddo) continue
|
||||
|
||||
const service = ddo.service.filter(
|
||||
(x: Service) => x.index === data.tokenOrders[i].serviceId
|
||||
)[0]
|
||||
|
||||
if (!service || service.type !== 'compute') continue
|
||||
const { serviceEndpoint } = service
|
||||
|
||||
const wasProviderQueried =
|
||||
serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0
|
||||
|
||||
if (wasProviderQueried) continue
|
||||
serviceEndpoints.push(serviceEndpoint)
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
}
|
||||
const queryDtList = JSON.stringify(dtList)
|
||||
.replace(/,/g, ' ')
|
||||
.replace(/"/g, '')
|
||||
.replace(/(\[|\])/g, '')
|
||||
|
||||
try {
|
||||
const source = axios.CancelToken.source()
|
||||
const assets = await getAssetMetadata(
|
||||
queryDtList,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const providers: Provider[] = []
|
||||
const serviceEndpoints: string[] = []
|
||||
for (let i = 0; i < data.tokenOrders.length; i++) {
|
||||
try {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(data.tokenOrders[i].datatokenId.address)
|
||||
.replace('0x', 'did:op:')
|
||||
for (let i = 0; i < serviceEndpoints.length; i++) {
|
||||
const instanceConfig = {
|
||||
config,
|
||||
web3: config.web3Provider,
|
||||
logger: Logger,
|
||||
ocean: ocean
|
||||
}
|
||||
const provider = await Provider.getInstance(instanceConfig)
|
||||
await provider.setBaseUrl(serviceEndpoints[i])
|
||||
const hasSameCompute =
|
||||
providers.filter(
|
||||
(x) => x.computeAddress === provider.computeAddress
|
||||
).length > 0
|
||||
if (!hasSameCompute) providers.push(provider)
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
try {
|
||||
const providerComputeJobs = (await providers[i].computeStatus(
|
||||
'',
|
||||
account,
|
||||
undefined,
|
||||
undefined,
|
||||
false
|
||||
)) as ComputeJob[]
|
||||
|
||||
// means the provider uri is not good, so we ignore it and move on
|
||||
if (!providerComputeJobs) continue
|
||||
providerComputeJobs.sort((a, b) => {
|
||||
if (a.dateCreated > b.dateCreated) {
|
||||
return -1
|
||||
}
|
||||
if (a.dateCreated < b.dateCreated) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
for (let j = 0; j < providerComputeJobs.length; j++) {
|
||||
const job = providerComputeJobs[j]
|
||||
const did = job.inputDID[0]
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
|
||||
if (!ddo) continue
|
||||
|
||||
const service = ddo.service.filter(
|
||||
(x: Service) => x.index === data.tokenOrders[i].serviceId
|
||||
const serviceMetadata = ddo.service.filter(
|
||||
(x: Service) => x.type === 'metadata'
|
||||
)[0]
|
||||
|
||||
if (!service || service.type !== 'compute') continue
|
||||
const { serviceEndpoint } = service
|
||||
|
||||
const wasProviderQueried =
|
||||
serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0
|
||||
|
||||
if (wasProviderQueried) continue
|
||||
serviceEndpoints.push(serviceEndpoint)
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (let i = 0; i < serviceEndpoints.length; i++) {
|
||||
const instanceConfig = {
|
||||
config,
|
||||
web3: config.web3Provider,
|
||||
logger: Logger,
|
||||
ocean: ocean
|
||||
const compJob: ComputeJobMetaData = {
|
||||
...job,
|
||||
assetName: serviceMetadata.attributes.main.name,
|
||||
assetDtSymbol: ddo.dataTokenInfo.symbol
|
||||
}
|
||||
const provider = await Provider.getInstance(instanceConfig)
|
||||
await provider.setBaseUrl(serviceEndpoints[i])
|
||||
const hasSameCompute =
|
||||
providers.filter(
|
||||
(x) => x.computeAddress === provider.computeAddress
|
||||
).length > 0
|
||||
if (!hasSameCompute) providers.push(provider)
|
||||
computeJobs.push(compJob)
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
try {
|
||||
const providerComputeJobs = (await providers[i].computeStatus(
|
||||
'',
|
||||
account,
|
||||
undefined,
|
||||
undefined,
|
||||
false
|
||||
)) as ComputeJob[]
|
||||
|
||||
// means the provider uri is not good, so we ignore it and move on
|
||||
if (!providerComputeJobs) continue
|
||||
providerComputeJobs.sort((a, b) => {
|
||||
if (a.dateCreated > b.dateCreated) {
|
||||
return -1
|
||||
}
|
||||
if (a.dateCreated < b.dateCreated) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
for (let j = 0; j < providerComputeJobs.length; j++) {
|
||||
const job = providerComputeJobs[j]
|
||||
const did = job.inputDID[0]
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
|
||||
if (!ddo) continue
|
||||
const serviceMetadata = ddo.service.filter(
|
||||
(x: Service) => x.type === 'metadata'
|
||||
)[0]
|
||||
|
||||
const compJob: ComputeJobMetaData = {
|
||||
...job,
|
||||
assetName: serviceMetadata.attributes.main.name,
|
||||
assetDtSymbol: ddo.dataTokenInfo.symbol
|
||||
}
|
||||
computeJobs.push(compJob)
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
}
|
||||
setJobs(computeJobs)
|
||||
} catch (error) {
|
||||
Logger.log(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
setJobs(computeJobs)
|
||||
} catch (error) {
|
||||
Logger.log(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (data === undefined || !config?.metadataCacheUri) {
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
getJobs()
|
||||
}, [ocean, account, data, config?.metadataCacheUri])
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={jobs}
|
||||
isLoading={isLoading}
|
||||
defaultSortField="row.dateCreated"
|
||||
defaultSortAsc={false}
|
||||
/>
|
||||
<>
|
||||
{jobs.length > 0 && (
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
title="Refresh compute jobs"
|
||||
onClick={() => getJobs()}
|
||||
disabled={isLoading}
|
||||
className={styles.refresh}
|
||||
>
|
||||
<Refresh />
|
||||
Refresh
|
||||
</Button>
|
||||
)}
|
||||
<Table
|
||||
columns={columns}
|
||||
data={jobs}
|
||||
isLoading={isLoading}
|
||||
defaultSortField="row.dateCreated"
|
||||
defaultSortAsc={false}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import AssetList from '../../organisms/AssetList'
|
||||
import axios from 'axios'
|
||||
import { queryMetadata } from '../../../utils/aquarius'
|
||||
|
@ -48,14 +47,13 @@ export default function PublishedList(): ReactElement {
|
|||
getPublished()
|
||||
}, [accountId, page, config.metadataCacheUri])
|
||||
|
||||
return isLoading ? (
|
||||
<Loader />
|
||||
) : accountId && queryResult ? (
|
||||
return accountId ? (
|
||||
<AssetList
|
||||
assets={queryResult.results}
|
||||
assets={queryResult?.results}
|
||||
isLoading={isLoading}
|
||||
showPagination
|
||||
page={queryResult.page}
|
||||
totalPages={queryResult.totalPages}
|
||||
page={queryResult?.page}
|
||||
totalPages={queryResult?.totalPages}
|
||||
onPageChange={(newPage) => {
|
||||
setPage(newPage)
|
||||
}}
|
||||
|
|
|
@ -16,9 +16,3 @@
|
|||
.section [class*='button'] {
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import Container from '../atoms/Container'
|
||||
import Loader from '../atoms/Loader'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import Button from '../atoms/Button'
|
||||
import Bookmarks from '../molecules/Bookmarks'
|
||||
|
@ -36,14 +35,6 @@ const queryLatest = {
|
|||
sort: { created: -1 }
|
||||
}
|
||||
|
||||
function LoaderArea() {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
<Loader />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SectionQueryResult({
|
||||
title,
|
||||
query,
|
||||
|
@ -55,7 +46,6 @@ function SectionQueryResult({
|
|||
}) {
|
||||
const { config } = useOcean()
|
||||
const [result, setResult] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.metadataCacheUri) return
|
||||
|
@ -69,7 +59,6 @@ function SectionQueryResult({
|
|||
source.token
|
||||
)
|
||||
setResult(result)
|
||||
setLoading(false)
|
||||
}
|
||||
init()
|
||||
|
||||
|
@ -81,11 +70,7 @@ function SectionQueryResult({
|
|||
return (
|
||||
<section className={styles.section}>
|
||||
<h3>{title}</h3>
|
||||
{loading ? (
|
||||
<LoaderArea />
|
||||
) : (
|
||||
result && <AssetList assets={result.results} showPagination={false} />
|
||||
)}
|
||||
<AssetList assets={result?.results} showPagination={false} />
|
||||
{action && action}
|
||||
</section>
|
||||
)
|
||||
|
|
|
@ -14,7 +14,6 @@ import Button from '../../atoms/Button'
|
|||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
|
||||
|
||||
import stylesIndex from './index.module.css'
|
||||
|
||||
const query = graphql`
|
||||
|
@ -63,6 +62,24 @@ export default function FormPublish(): ReactElement {
|
|||
initialValues.dockerImage
|
||||
)
|
||||
|
||||
const dockerImageOptions = [
|
||||
{
|
||||
name: 'node:latest',
|
||||
title: 'node:latest',
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
name: 'python:latest',
|
||||
title: 'python:latest',
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
name: 'custom image',
|
||||
title: 'custom image',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
|
||||
// reset form validation on every mount
|
||||
useEffect(() => {
|
||||
setErrors({})
|
||||
|
@ -135,6 +152,11 @@ export default function FormPublish(): ReactElement {
|
|||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection'
|
||||
? dockerImageOptions
|
||||
: field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
|
|
|
@ -7,6 +7,8 @@ import { FormContent, FormFieldProps } from '../../../@types/Form'
|
|||
import { MetadataPublishFormDataset } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { ReactComponent as Download } from '../../../images/download.svg'
|
||||
import { ReactComponent as Compute } from '../../../images/compute.svg'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './FormPublish.module.css'
|
||||
|
||||
|
@ -61,6 +63,19 @@ export default function FormPublish(): ReactElement {
|
|||
// setSubmitting(false)
|
||||
}, [setErrors, setTouched])
|
||||
|
||||
const accessTypeOptions = [
|
||||
{
|
||||
name: 'Download',
|
||||
title: 'Download',
|
||||
icon: <Download />
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
title: 'Compute',
|
||||
icon: <Compute />
|
||||
}
|
||||
]
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in
|
||||
function handleFieldChange(
|
||||
|
@ -94,6 +109,9 @@ export default function FormPublish(): ReactElement {
|
|||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection' ? accessTypeOptions : field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import styles from './filterPrice.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import {
|
||||
addExistingParamsToUrl,
|
||||
FilterByPriceOptions,
|
||||
FilterByTypeOptions
|
||||
} from './utils'
|
||||
import Button from '../../atoms/Button'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const clearFilters = [{ display: 'Clear', value: '' }]
|
||||
|
||||
const priceFilterItems = [
|
||||
{ display: 'fixed price', value: FilterByPriceOptions.Fixed },
|
||||
{ display: 'dynamic price', value: FilterByPriceOptions.Dynamic }
|
||||
]
|
||||
|
||||
const serviceFilterItems = [
|
||||
{ display: 'data sets', value: FilterByTypeOptions.Data },
|
||||
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm }
|
||||
]
|
||||
|
||||
export default function FilterPrice({
|
||||
priceType,
|
||||
serviceType,
|
||||
setPriceType,
|
||||
setServiceType
|
||||
}: {
|
||||
priceType: string
|
||||
setPriceType: React.Dispatch<React.SetStateAction<string>>
|
||||
serviceType: string
|
||||
setServiceType: React.Dispatch<React.SetStateAction<string>>
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [priceSelections, setPriceSelections] = useState<string[]>([])
|
||||
const [serviceSelections, setServiceSelections] = useState<string[]>([])
|
||||
|
||||
async function applyPriceFilter(filterBy: string) {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'priceType')
|
||||
if (filterBy) {
|
||||
urlLocation = `${urlLocation}&priceType=${filterBy}`
|
||||
}
|
||||
setPriceType(filterBy)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
async function applyServiceFilter(filterBy: string) {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
|
||||
if (filterBy && location.search.indexOf('&serviceType') === -1) {
|
||||
urlLocation = `${urlLocation}&serviceType=${filterBy}`
|
||||
}
|
||||
setServiceType(filterBy)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
async function handleSelectedFilter(isSelected: boolean, value: string) {
|
||||
if (
|
||||
value === FilterByPriceOptions.Fixed ||
|
||||
value === FilterByPriceOptions.Dynamic
|
||||
) {
|
||||
if (isSelected) {
|
||||
if (priceSelections.length > 1) {
|
||||
// both selected -> select the other one
|
||||
const otherValue = priceFilterItems.find((p) => p.value !== value)
|
||||
.value
|
||||
await applyPriceFilter(otherValue)
|
||||
} else {
|
||||
// only the current one selected -> deselect it
|
||||
await applyPriceFilter(undefined)
|
||||
}
|
||||
} else {
|
||||
if (priceSelections.length > 0) {
|
||||
// one already selected -> both selected
|
||||
await applyPriceFilter(FilterByPriceOptions.All)
|
||||
setPriceSelections(priceFilterItems.map((p) => p.value))
|
||||
} else {
|
||||
// none selected -> select
|
||||
await applyPriceFilter(value)
|
||||
setPriceSelections([value])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isSelected) {
|
||||
if (serviceSelections.length > 1) {
|
||||
const otherValue = serviceFilterItems.find((p) => p.value !== value)
|
||||
.value
|
||||
await applyServiceFilter(otherValue)
|
||||
setServiceSelections([otherValue])
|
||||
} else {
|
||||
await applyServiceFilter(undefined)
|
||||
}
|
||||
} else {
|
||||
if (serviceSelections.length) {
|
||||
await applyServiceFilter(undefined)
|
||||
setServiceSelections(serviceFilterItems.map((p) => p.value))
|
||||
} else {
|
||||
await applyServiceFilter(value)
|
||||
setServiceSelections([value])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyClearFilter() {
|
||||
let urlLocation = await addExistingParamsToUrl(
|
||||
location,
|
||||
'priceType',
|
||||
'serviceType'
|
||||
)
|
||||
|
||||
urlLocation = `${urlLocation}`
|
||||
|
||||
setServiceSelections([])
|
||||
setPriceSelections([])
|
||||
|
||||
setPriceType(undefined)
|
||||
setServiceType(undefined)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.filterList}>
|
||||
{priceFilterItems.map((e, index) => {
|
||||
const isPriceSelected =
|
||||
e.value === priceType || priceSelections.includes(e.value)
|
||||
const selectFilter = cx({
|
||||
[styles.selected]: isPriceSelected,
|
||||
[styles.filter]: true
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={selectFilter}
|
||||
onClick={async () => {
|
||||
handleSelectedFilter(isPriceSelected, e.value)
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
{serviceFilterItems.map((e, index) => {
|
||||
const isServiceSelected =
|
||||
e.value === serviceType || serviceSelections.includes(e.value)
|
||||
const selectFilter = cx({
|
||||
[styles.selected]: isServiceSelected,
|
||||
[styles.filter]: true
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={selectFilter}
|
||||
onClick={async () => {
|
||||
handleSelectedFilter(isServiceSelected, e.value)
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
{clearFilters.map((e, index) => {
|
||||
const showClear =
|
||||
priceSelections.length > 0 || serviceSelections.length > 0
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={showClear ? styles.showClear : styles.hideClear}
|
||||
onClick={async () => {
|
||||
applyClearFilter()
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import React, { ReactElement, useState } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import styles from './filterService.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import { addExistingParamsToUrl, FilterByTypeOptions } from './utils'
|
||||
import Button from '../../atoms/Button'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const clearFilters = [{ display: 'Clear', value: '' }]
|
||||
|
||||
const serviceFilterItems = [
|
||||
{ display: 'data sets', value: FilterByTypeOptions.Data },
|
||||
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm }
|
||||
]
|
||||
|
||||
export default function FilterPrice({
|
||||
serviceType,
|
||||
setServiceType
|
||||
}: {
|
||||
serviceType: string
|
||||
setServiceType: React.Dispatch<React.SetStateAction<string>>
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const [serviceSelections, setServiceSelections] = useState<string[]>([])
|
||||
|
||||
async function applyServiceFilter(filterBy: string) {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
|
||||
if (filterBy && location.search.indexOf('&serviceType') === -1) {
|
||||
urlLocation = `${urlLocation}&serviceType=${filterBy}`
|
||||
}
|
||||
setServiceType(filterBy)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
async function handleSelectedFilter(isSelected: boolean, value: string) {
|
||||
if (isSelected) {
|
||||
if (serviceSelections.length > 1) {
|
||||
const otherValue = serviceFilterItems.find((p) => p.value !== value)
|
||||
.value
|
||||
await applyServiceFilter(otherValue)
|
||||
setServiceSelections([otherValue])
|
||||
} else {
|
||||
await applyServiceFilter(undefined)
|
||||
}
|
||||
} else {
|
||||
if (serviceSelections.length) {
|
||||
await applyServiceFilter(undefined)
|
||||
setServiceSelections(serviceFilterItems.map((p) => p.value))
|
||||
} else {
|
||||
await applyServiceFilter(value)
|
||||
setServiceSelections([value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyClearFilter() {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
|
||||
|
||||
urlLocation = `${urlLocation}`
|
||||
|
||||
setServiceSelections([])
|
||||
setServiceType(undefined)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.filterList}>
|
||||
{serviceFilterItems.map((e, index) => {
|
||||
const isServiceSelected =
|
||||
e.value === serviceType || serviceSelections.includes(e.value)
|
||||
const selectFilter = cx({
|
||||
[styles.selected]: isServiceSelected,
|
||||
[styles.filter]: true
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={selectFilter}
|
||||
onClick={async () => {
|
||||
handleSelectedFilter(isServiceSelected, e.value)
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
{clearFilters.map((e, index) => {
|
||||
const showClear = serviceSelections.length > 0
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={showClear ? styles.showClear : styles.hideClear}
|
||||
onClick={async () => {
|
||||
applyClearFilter()
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -4,7 +4,7 @@ import SearchBar from '../../molecules/SearchBar'
|
|||
import AssetList from '../../organisms/AssetList'
|
||||
import styles from './index.module.css'
|
||||
import queryString from 'query-string'
|
||||
import PriceFilter from './filterPrice'
|
||||
import ServiceFilter from './filterService'
|
||||
import Sort from './sort'
|
||||
import { getResults } from './utils'
|
||||
import { navigate } from 'gatsby'
|
||||
|
@ -21,19 +21,9 @@ export default function SearchPage({
|
|||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const parsed = queryString.parse(location.search)
|
||||
const {
|
||||
text,
|
||||
owner,
|
||||
tags,
|
||||
page,
|
||||
sort,
|
||||
sortOrder,
|
||||
priceType,
|
||||
serviceType
|
||||
} = parsed
|
||||
const { text, owner, tags, page, sort, sortOrder, serviceType } = parsed
|
||||
const [queryResult, setQueryResult] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const [price, setPriceType] = useState<string>(priceType as string)
|
||||
const [service, setServiceType] = useState<string>(serviceType as string)
|
||||
const [sortType, setSortType] = useState<string>(sort as string)
|
||||
const [sortDirection, setSortDirection] = useState<string>(
|
||||
|
@ -58,7 +48,6 @@ export default function SearchPage({
|
|||
tags,
|
||||
sort,
|
||||
page,
|
||||
priceType,
|
||||
serviceType,
|
||||
sortOrder,
|
||||
config.metadataCacheUri
|
||||
|
@ -80,10 +69,8 @@ export default function SearchPage({
|
|||
<SearchBar initialValue={(text || owner) as string} />
|
||||
)}
|
||||
<div className={styles.row}>
|
||||
<PriceFilter
|
||||
priceType={price}
|
||||
<ServiceFilter
|
||||
serviceType={service}
|
||||
setPriceType={setPriceType}
|
||||
setServiceType={setServiceType}
|
||||
/>
|
||||
<Sort
|
||||
|
@ -91,25 +78,18 @@ export default function SearchPage({
|
|||
sortDirection={sortDirection}
|
||||
setSortType={setSortType}
|
||||
setSortDirection={setSortDirection}
|
||||
setPriceType={setPriceType}
|
||||
setServiceType={setServiceType}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.results}>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : queryResult ? (
|
||||
<AssetList
|
||||
assets={queryResult.results}
|
||||
showPagination
|
||||
page={queryResult.page}
|
||||
totalPages={queryResult.totalPages}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<AssetList
|
||||
assets={queryResult?.results}
|
||||
showPagination
|
||||
isLoading={loading}
|
||||
page={queryResult?.page}
|
||||
totalPages={queryResult?.totalPages}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -3,9 +3,7 @@ import { useNavigate } from '@reach/router'
|
|||
import {
|
||||
addExistingParamsToUrl,
|
||||
SortTermOptions,
|
||||
SortValueOptions,
|
||||
FilterByPriceOptions,
|
||||
FilterByTypeOptions
|
||||
SortValueOptions
|
||||
} from './utils'
|
||||
import Button from '../../atoms/Button'
|
||||
import styles from './sort.module.css'
|
||||
|
@ -13,26 +11,18 @@ import classNames from 'classnames/bind'
|
|||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const sortItems = [
|
||||
{ display: 'Published', value: SortTermOptions.Created },
|
||||
{ display: 'Liquidity', value: SortTermOptions.Liquidity },
|
||||
{ display: 'Price', value: SortTermOptions.Price }
|
||||
]
|
||||
const sortItems = [{ display: 'Published', value: SortTermOptions.Created }]
|
||||
|
||||
export default function Sort({
|
||||
sortType,
|
||||
setSortType,
|
||||
sortDirection,
|
||||
setSortDirection,
|
||||
setPriceType,
|
||||
setServiceType
|
||||
setSortDirection
|
||||
}: {
|
||||
sortType: string
|
||||
setSortType: React.Dispatch<React.SetStateAction<string>>
|
||||
sortDirection: string
|
||||
setSortDirection: React.Dispatch<React.SetStateAction<string>>
|
||||
setPriceType: React.Dispatch<React.SetStateAction<string>>
|
||||
setServiceType: React.Dispatch<React.SetStateAction<string>>
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const directionArrow = String.fromCharCode(
|
||||
|
@ -41,12 +31,7 @@ export default function Sort({
|
|||
async function sortResults(sortBy?: string, direction?: string) {
|
||||
let urlLocation: string
|
||||
if (sortBy) {
|
||||
urlLocation = await addExistingParamsToUrl(location, 'sort', 'priceType')
|
||||
urlLocation = `${urlLocation}&sort=${sortBy}`
|
||||
if (sortBy === SortTermOptions.Liquidity) {
|
||||
urlLocation = `${urlLocation}&priceType=${FilterByPriceOptions.Dynamic}`
|
||||
setPriceType(FilterByPriceOptions.Dynamic)
|
||||
}
|
||||
setSortType(sortBy)
|
||||
} else if (direction) {
|
||||
urlLocation = await addExistingParamsToUrl(location, 'sortOrder')
|
||||
|
|
|
@ -6,8 +6,6 @@ import { MetadataCache, Logger } from '@oceanprotocol/lib'
|
|||
import queryString from 'query-string'
|
||||
|
||||
export const SortTermOptions = {
|
||||
Liquidity: 'liquidity',
|
||||
Price: 'price',
|
||||
Created: 'created'
|
||||
} as const
|
||||
type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions]
|
||||
|
@ -25,36 +23,12 @@ export const SortValueOptions = {
|
|||
} as const
|
||||
type SortValueOptions = typeof SortValueOptions[keyof typeof SortValueOptions]
|
||||
|
||||
export const FilterByPriceOptions = {
|
||||
Fixed: 'exchange',
|
||||
Dynamic: 'pool',
|
||||
All: 'all'
|
||||
} as const
|
||||
type FilterByPriceOptions = typeof FilterByPriceOptions[keyof typeof FilterByPriceOptions]
|
||||
|
||||
export const FilterByTypeOptions = {
|
||||
Data: 'dataset',
|
||||
Algorithm: 'algorithm'
|
||||
} as const
|
||||
type FilterByTypeOptions = typeof FilterByTypeOptions[keyof typeof FilterByTypeOptions]
|
||||
|
||||
function addPriceFilterToQuery(sortTerm: string, priceFilter: string): string {
|
||||
if (priceFilter === FilterByPriceOptions.All) {
|
||||
sortTerm = priceFilter
|
||||
? sortTerm === ''
|
||||
? `(price.type:${FilterByPriceOptions.Fixed} OR price.type:${FilterByPriceOptions.Dynamic})`
|
||||
: `${sortTerm} AND (price.type:${FilterByPriceOptions.Dynamic} OR price.type:${FilterByPriceOptions.Fixed})`
|
||||
: sortTerm
|
||||
} else {
|
||||
sortTerm = priceFilter
|
||||
? sortTerm === ''
|
||||
? `price.type:${priceFilter}`
|
||||
: `${sortTerm} AND price.type:${priceFilter}`
|
||||
: sortTerm
|
||||
}
|
||||
return sortTerm
|
||||
}
|
||||
|
||||
function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
|
||||
sortTerm = typeFilter
|
||||
? sortTerm === ''
|
||||
|
@ -64,13 +38,8 @@ function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
|
|||
return sortTerm
|
||||
}
|
||||
|
||||
function getSortType(sortParam: string): string {
|
||||
const sortTerm =
|
||||
sortParam === SortTermOptions.Liquidity
|
||||
? SortElasticTerm.Liquidity
|
||||
: sortParam === SortTermOptions.Price
|
||||
? SortElasticTerm.Price
|
||||
: SortTermOptions.Created
|
||||
function getSortType(): string {
|
||||
const sortTerm = SortTermOptions.Created
|
||||
return sortTerm
|
||||
}
|
||||
|
||||
|
@ -83,10 +52,9 @@ export function getSearchQuery(
|
|||
offset?: string,
|
||||
sort?: string,
|
||||
sortOrder?: string,
|
||||
priceType?: string,
|
||||
serviceType?: string
|
||||
): SearchQuery {
|
||||
const sortTerm = getSortType(sort)
|
||||
const sortTerm = getSortType()
|
||||
const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1
|
||||
let searchTerm = owner
|
||||
? `(publicKey.owner:${owner})`
|
||||
|
@ -98,7 +66,6 @@ export function getSearchQuery(
|
|||
`(service.attributes.additionalInformation.categories:\"${categories}\")`
|
||||
: text || ''
|
||||
searchTerm = addTypeFilterToQuery(searchTerm, serviceType)
|
||||
searchTerm = addPriceFilterToQuery(searchTerm, priceType)
|
||||
|
||||
return {
|
||||
page: Number(page) || 1,
|
||||
|
@ -136,7 +103,6 @@ export async function getResults(
|
|||
offset?: string
|
||||
sort?: string
|
||||
sortOrder?: string
|
||||
priceType?: string
|
||||
serviceType?: string
|
||||
},
|
||||
metadataCacheUri: string
|
||||
|
@ -150,10 +116,10 @@ export async function getResults(
|
|||
categories,
|
||||
sort,
|
||||
sortOrder,
|
||||
priceType,
|
||||
serviceType
|
||||
} = params
|
||||
const metadataCache = new MetadataCache(metadataCacheUri, Logger)
|
||||
|
||||
const searchQuery = getSearchQuery(
|
||||
text,
|
||||
owner,
|
||||
|
@ -163,11 +129,10 @@ export async function getResults(
|
|||
offset,
|
||||
sort,
|
||||
sortOrder,
|
||||
priceType,
|
||||
serviceType
|
||||
)
|
||||
const queryResult = await metadataCache.queryMetadata(searchQuery)
|
||||
|
||||
const queryResult = await metadataCache.queryMetadata(searchQuery)
|
||||
return queryResult
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
|
||||
import { PriceList, getAssetPrices } from './subgraph'
|
||||
import { PriceList, getAssetsPriceList } from './subgraph'
|
||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
|
||||
// TODO: import directly from ocean.js somehow.
|
||||
|
@ -113,7 +113,7 @@ export async function transformDDOToAssetSelection(
|
|||
): Promise<AssetSelectionAsset[]> {
|
||||
const source = axios.CancelToken.source()
|
||||
const didList: string[] = []
|
||||
const priceList: PriceList = await getAssetPrices(ddoList)
|
||||
const priceList: PriceList = await getAssetsPriceList(ddoList)
|
||||
const symbolList: any = {}
|
||||
for (const ddo of ddoList) {
|
||||
didList.push(ddo.id)
|
||||
|
|
|
@ -1,12 +1,30 @@
|
|||
import { gql, DocumentNode, ApolloQueryResult } from '@apollo/client'
|
||||
import { DDO, BestPrice } from '@oceanprotocol/lib'
|
||||
import { getApolloClientInstance } from '../providers/ApolloClientProvider'
|
||||
import {
|
||||
AssetsPoolPrice,
|
||||
AssetsPoolPrice_pools as AssetsPoolPricePools
|
||||
} from '../@types/apollo/AssetsPoolPrice'
|
||||
import {
|
||||
AssetsFrePrice,
|
||||
AssetsFrePrice_fixedRateExchanges as AssetsFrePriceFixedRateExchanges
|
||||
} from '../@types/apollo/AssetsFrePrice'
|
||||
import { AssetPreviousOrder } from '../@types/apollo/AssetPreviousOrder'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
export interface PriceList {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface AssetListPrices {
|
||||
ddo: DDO
|
||||
price: BestPrice
|
||||
}
|
||||
|
||||
interface DidAndDatatokenMap {
|
||||
[name: string]: string
|
||||
}
|
||||
|
||||
const FreQuery = gql`
|
||||
query AssetsFrePrice($datatoken_in: [String!]) {
|
||||
fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) {
|
||||
|
@ -36,6 +54,8 @@ const PoolQuery = gql`
|
|||
spotPrice
|
||||
consumePrice
|
||||
datatokenAddress
|
||||
datatokenReserve
|
||||
oceanReserve
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -93,7 +113,7 @@ export async function getPreviousOrders(
|
|||
id: id,
|
||||
account: account
|
||||
}
|
||||
const fetchedPreviousOrders: any = await fetchData(
|
||||
const fetchedPreviousOrders: ApolloQueryResult<AssetPreviousOrder> = await fetchData(
|
||||
PreviousOrderQuery,
|
||||
variables
|
||||
)
|
||||
|
@ -112,74 +132,32 @@ export async function getPreviousOrders(
|
|||
}
|
||||
}
|
||||
|
||||
export async function getAssetPrices(assets: DDO[]): Promise<PriceList> {
|
||||
const priceList: PriceList = {}
|
||||
const didDTMap: any = {}
|
||||
const dataTokenList: string[] = []
|
||||
|
||||
for (const ddo of assets) {
|
||||
didDTMap[ddo?.dataToken.toLowerCase()] = ddo.id
|
||||
dataTokenList.push(ddo?.dataToken.toLowerCase())
|
||||
}
|
||||
const freVariables = {
|
||||
datatoken_in: dataTokenList
|
||||
}
|
||||
const poolVariables = {
|
||||
datatokenAddress_in: dataTokenList
|
||||
}
|
||||
const poolPriceResponse: any = await fetchData(PoolQuery, poolVariables)
|
||||
for (const poolPrice of poolPriceResponse.data?.pools) {
|
||||
priceList[didDTMap[poolPrice.datatokenAddress]] =
|
||||
poolPrice.consumePrice === '-1'
|
||||
? poolPrice.spotPrice
|
||||
: poolPrice.consumePrice
|
||||
}
|
||||
const frePriceResponse: any = await fetchData(FreQuery, freVariables)
|
||||
for (const frePrice of frePriceResponse.data?.fixedRateExchanges) {
|
||||
priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate
|
||||
}
|
||||
return priceList
|
||||
}
|
||||
|
||||
export async function getPrice(asset: DDO): Promise<BestPrice> {
|
||||
const freVariables = {
|
||||
datatoken: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
|
||||
const poolVariables = {
|
||||
datatokenAddress: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
|
||||
const poolPriceResponse: any = await fetchData(
|
||||
AssetPoolPriceQuerry,
|
||||
poolVariables
|
||||
)
|
||||
const frePriceResponse: any = await fetchData(AssetFreQuery, freVariables)
|
||||
if (poolPriceResponse.data?.pools.length > 0) {
|
||||
function transformPriceToBestPrice(
|
||||
frePrice: AssetsFrePriceFixedRateExchanges[],
|
||||
poolPrice: AssetsPoolPricePools[]
|
||||
) {
|
||||
if (poolPrice?.length > 0) {
|
||||
const price: BestPrice = {
|
||||
type: 'pool',
|
||||
address: poolPriceResponse.data?.pools[0]?.id,
|
||||
address: poolPrice[0]?.id,
|
||||
value:
|
||||
poolPriceResponse.data?.pools[0]?.consumePrice === '-1'
|
||||
? poolPriceResponse.data?.pools[0]?.spotPrice
|
||||
: poolPriceResponse.data?.pools[0]?.consumePrice,
|
||||
ocean: poolPriceResponse.data?.pools[0]?.oceanReserve,
|
||||
datatoken: poolPriceResponse.data?.pools[0]?.datatokenReserve,
|
||||
pools: [poolPriceResponse.data?.pools[0]?.id],
|
||||
isConsumable:
|
||||
poolPriceResponse.data?.pools[0]?.consumePrice === '-1'
|
||||
? 'false'
|
||||
: 'true'
|
||||
poolPrice[0]?.consumePrice === '-1'
|
||||
? poolPrice[0]?.spotPrice
|
||||
: poolPrice[0]?.consumePrice,
|
||||
ocean: poolPrice[0]?.oceanReserve,
|
||||
datatoken: poolPrice[0]?.datatokenReserve,
|
||||
pools: [poolPrice[0]?.id],
|
||||
isConsumable: poolPrice[0]?.consumePrice === '-1' ? 'false' : 'true'
|
||||
}
|
||||
return price
|
||||
} else if (frePriceResponse.data?.fixedRateExchanges.length > 0) {
|
||||
} else if (frePrice?.length > 0) {
|
||||
// TODO Hacky hack, temporary™: set isConsumable to true for fre assets.
|
||||
// isConsumable: 'true'
|
||||
const price: BestPrice = {
|
||||
type: 'exchange',
|
||||
value: frePriceResponse.data?.fixedRateExchanges[0]?.rate,
|
||||
address: frePriceResponse.data?.fixedRateExchanges[0]?.id,
|
||||
exchange_id: frePriceResponse.data?.fixedRateExchanges[0]?.id,
|
||||
value: frePrice[0]?.rate,
|
||||
address: frePrice[0]?.id,
|
||||
exchange_id: frePrice[0]?.id,
|
||||
ocean: 0,
|
||||
datatoken: 0,
|
||||
pools: [],
|
||||
|
@ -200,3 +178,123 @@ export async function getPrice(asset: DDO): Promise<BestPrice> {
|
|||
return price
|
||||
}
|
||||
}
|
||||
|
||||
async function getAssetsPoolsExchangesAndDatatokenMap(
|
||||
assets: DDO[]
|
||||
): Promise<
|
||||
[
|
||||
ApolloQueryResult<AssetsPoolPrice>,
|
||||
ApolloQueryResult<AssetsFrePrice>,
|
||||
DidAndDatatokenMap
|
||||
]
|
||||
> {
|
||||
const didDTMap: DidAndDatatokenMap = {}
|
||||
const dataTokenList: string[] = []
|
||||
|
||||
for (const ddo of assets) {
|
||||
didDTMap[ddo?.dataToken.toLowerCase()] = ddo.id
|
||||
dataTokenList.push(ddo?.dataToken.toLowerCase())
|
||||
}
|
||||
const freVariables = {
|
||||
datatoken_in: dataTokenList
|
||||
}
|
||||
const poolVariables = {
|
||||
datatokenAddress_in: dataTokenList
|
||||
}
|
||||
|
||||
const poolPriceResponse: ApolloQueryResult<AssetsPoolPrice> = await fetchData(
|
||||
PoolQuery,
|
||||
poolVariables
|
||||
)
|
||||
const frePriceResponse: ApolloQueryResult<AssetsFrePrice> = await fetchData(
|
||||
FreQuery,
|
||||
freVariables
|
||||
)
|
||||
|
||||
return [poolPriceResponse, frePriceResponse, didDTMap]
|
||||
}
|
||||
|
||||
export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
|
||||
const priceList: PriceList = {}
|
||||
|
||||
const values: [
|
||||
ApolloQueryResult<AssetsPoolPrice>,
|
||||
ApolloQueryResult<AssetsFrePrice>,
|
||||
DidAndDatatokenMap
|
||||
] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
|
||||
const poolPriceResponse = values[0]
|
||||
const frePriceResponse = values[1]
|
||||
const didDTMap: DidAndDatatokenMap = values[2]
|
||||
|
||||
for (const poolPrice of poolPriceResponse.data?.pools) {
|
||||
priceList[didDTMap[poolPrice.datatokenAddress]] =
|
||||
poolPrice.consumePrice === '-1'
|
||||
? poolPrice.spotPrice
|
||||
: poolPrice.consumePrice
|
||||
}
|
||||
for (const frePrice of frePriceResponse.data?.fixedRateExchanges) {
|
||||
priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate
|
||||
}
|
||||
return priceList
|
||||
}
|
||||
|
||||
export async function getPrice(asset: DDO): Promise<BestPrice> {
|
||||
const freVariables = {
|
||||
datatoken: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
|
||||
const poolVariables = {
|
||||
datatokenAddress: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
|
||||
const poolPriceResponse: ApolloQueryResult<AssetsPoolPrice> = await fetchData(
|
||||
AssetPoolPriceQuerry,
|
||||
poolVariables
|
||||
)
|
||||
const frePriceResponse: ApolloQueryResult<AssetsFrePrice> = await fetchData(
|
||||
AssetFreQuery,
|
||||
freVariables
|
||||
)
|
||||
|
||||
const bestPrice: BestPrice = transformPriceToBestPrice(
|
||||
frePriceResponse.data.fixedRateExchanges,
|
||||
poolPriceResponse.data.pools
|
||||
)
|
||||
|
||||
return bestPrice
|
||||
}
|
||||
|
||||
export async function getAssetsBestPrices(
|
||||
assets: DDO[]
|
||||
): Promise<AssetListPrices[]> {
|
||||
const assetsWithPrice: AssetListPrices[] = []
|
||||
|
||||
const values: [
|
||||
ApolloQueryResult<AssetsPoolPrice>,
|
||||
ApolloQueryResult<AssetsFrePrice>,
|
||||
DidAndDatatokenMap
|
||||
] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
|
||||
const poolPriceResponse = values[0]
|
||||
const frePriceResponse = values[1]
|
||||
|
||||
for (const ddo of assets) {
|
||||
const dataToken = ddo.dataToken.toLowerCase()
|
||||
const poolPrice: AssetsPoolPricePools[] = []
|
||||
const frePrice: AssetsFrePriceFixedRateExchanges[] = []
|
||||
const pool = poolPriceResponse.data?.pools.find(
|
||||
(pool: any) => pool.datatokenAddress === dataToken
|
||||
)
|
||||
pool && poolPrice.push(pool)
|
||||
const fre = frePriceResponse.data?.fixedRateExchanges.find(
|
||||
(fre: any) => fre.datatoken.address === dataToken
|
||||
)
|
||||
fre && frePrice.push(fre)
|
||||
const bestPrice = transformPriceToBestPrice(frePrice, poolPrice)
|
||||
assetsWithPrice.push({
|
||||
ddo: ddo,
|
||||
price: bestPrice
|
||||
})
|
||||
}
|
||||
|
||||
return assetsWithPrice
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue