mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Show the list of data sets an algorithm is allowed to run on (#579)
* WIP * UI changes * get and display datasets on both compute and consume * new component for datasets that algorithm can run compute job on * AssetSelection className refactor * added internal link to AssetSelection * show loading page when changing asset * Component and asset title UI update * created new component for dataset list * removed unnecessary changes * updated link margin * prettier fix * merge fix * another fix Co-authored-by: Norbi <katunanorbert@gmai.com> Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
e8033687fb
commit
3a4851132d
5
package-lock.json
generated
5
package-lock.json
generated
@ -65001,11 +65001,6 @@
|
|||||||
"clsx": "^1.1.1"
|
"clsx": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reactjs-popup": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-G5jTXL2JkClKAYAdqedf+K9QvbNsFWvdbrXW1/vWiyanuCU/d7DtQzQux+uKOz2HeNVRsFQHvs7abs0Z7VLAhg=="
|
|
||||||
},
|
|
||||||
"read": {
|
"read": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
|
||||||
|
59
src/components/molecules/AssetComputeList.module.css
Normal file
59
src/components/molecules/AssetComputeList.module.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
.display {
|
||||||
|
composes: selection from './FormFields/AssetSelection.module.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.display [class*='loaderWrap'] {
|
||||||
|
margin: calc(var(--spacer) / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
composes: scroll from './FormFields/AssetSelection.module.css';
|
||||||
|
margin-top: 0;
|
||||||
|
border-top: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
composes: row from './FormFields/AssetSelection.module.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row:hover {
|
||||||
|
background-color: var(--background-content);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
composes: title from './FormFields/AssetSelection.module.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
composes: price from './FormFields/AssetSelection.module.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.price [class*='symbol'] {
|
||||||
|
font-size: calc(var(--font-size-small) / 1.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.did {
|
||||||
|
composes: did from './FormFields/AssetSelection.module.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
composes: empty from './FormFields/AssetSelection.module.css';
|
||||||
|
}
|
49
src/components/molecules/AssetComputeList.tsx
Normal file
49
src/components/molecules/AssetComputeList.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Dotdotdot from 'react-dotdotdot'
|
||||||
|
import { Link } from 'gatsby'
|
||||||
|
import PriceUnit from '../atoms/Price/PriceUnit'
|
||||||
|
import Loader from '../atoms/Loader'
|
||||||
|
import styles from './AssetComputeList.module.css'
|
||||||
|
import { AssetSelectionAsset } from './FormFields/AssetSelection'
|
||||||
|
|
||||||
|
function Empty() {
|
||||||
|
return <div className={styles.empty}>No assets found.</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AssetComputeSelection({
|
||||||
|
assets
|
||||||
|
}: {
|
||||||
|
assets: AssetSelectionAsset[]
|
||||||
|
}): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div className={styles.display}>
|
||||||
|
<div className={styles.scroll}>
|
||||||
|
{!assets ? (
|
||||||
|
<Loader />
|
||||||
|
) : assets && !assets.length ? (
|
||||||
|
<Empty />
|
||||||
|
) : (
|
||||||
|
assets.map((asset: AssetSelectionAsset) => (
|
||||||
|
<Link
|
||||||
|
to={`/asset/${asset.did}`}
|
||||||
|
className={styles.row}
|
||||||
|
key={asset.did}
|
||||||
|
>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<h3 className={styles.title}>
|
||||||
|
<Dotdotdot clamp={1} tagName="span">
|
||||||
|
{asset.name}
|
||||||
|
</Dotdotdot>
|
||||||
|
</h3>
|
||||||
|
<Dotdotdot clamp={1} tagName="code" className={styles.did}>
|
||||||
|
{asset.symbol} | {asset.did}
|
||||||
|
</Dotdotdot>
|
||||||
|
</div>
|
||||||
|
<PriceUnit price={asset.price} small className={styles.price} />
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -29,7 +29,6 @@ import {
|
|||||||
ComputeAlgorithm,
|
ComputeAlgorithm,
|
||||||
ComputeOutput
|
ComputeOutput
|
||||||
} from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
|
} from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
|
||||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
|
||||||
import { SearchQuery } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
import { SearchQuery } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import FormStartComputeDataset from './FormComputeDataset'
|
import FormStartComputeDataset from './FormComputeDataset'
|
||||||
@ -37,6 +36,8 @@ import styles from './index.module.css'
|
|||||||
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
||||||
import Button from '../../../atoms/Button'
|
import Button from '../../../atoms/Button'
|
||||||
import { secondsToString } from '../../../../utils/metadata'
|
import { secondsToString } from '../../../../utils/metadata'
|
||||||
|
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||||
|
import AlgorithmDatasetsListForCompute from '../../AssetContent/AlgorithmDatasetsListForCompute'
|
||||||
import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
|
import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
|
||||||
|
|
||||||
const SuccessAction = () => (
|
const SuccessAction = () => (
|
||||||
@ -377,10 +378,13 @@ export default function Compute({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{type === 'algorithm' ? (
|
{type === 'algorithm' ? (
|
||||||
|
<>
|
||||||
<Alert
|
<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!"
|
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"
|
state="info"
|
||||||
/>
|
/>
|
||||||
|
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} />
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={getInitialValues()}
|
initialValues={getInitialValues()}
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
.info {
|
.info {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: auto;
|
||||||
|
margin-left: -2rem;
|
||||||
|
margin-right: -2rem;
|
||||||
|
padding: 0 calc(var(--spacer)) 0 calc(var(--spacer));
|
||||||
}
|
}
|
||||||
|
|
||||||
.filewrapper {
|
.filewrapper {
|
||||||
|
@ -16,6 +16,7 @@ import { useWeb3 } from '../../../providers/Web3'
|
|||||||
import { usePricing } from '../../../hooks/usePricing'
|
import { usePricing } from '../../../hooks/usePricing'
|
||||||
import { useConsume } from '../../../hooks/useConsume'
|
import { useConsume } from '../../../hooks/useConsume'
|
||||||
import ButtonBuy from '../../atoms/ButtonBuy'
|
import ButtonBuy from '../../atoms/ButtonBuy'
|
||||||
|
import AlgorithmDatasetsListForCompute from '../AssetContent/AlgorithmDatasetsListForCompute'
|
||||||
|
|
||||||
const previousOrderQuery = gql`
|
const previousOrderQuery = gql`
|
||||||
query PreviousOrder($id: String!, $account: String!) {
|
query PreviousOrder($id: String!, $account: String!) {
|
||||||
@ -168,6 +169,9 @@ export default function Consume({
|
|||||||
{!isInPurgatory && <PurchaseButton />}
|
{!isInPurgatory && <PurchaseButton />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{type === 'algorithm' && (
|
||||||
|
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} />
|
||||||
|
)}
|
||||||
<footer className={styles.feedback}>
|
<footer className={styles.feedback}>
|
||||||
<Web3Feedback isBalanceSufficient={isBalanceSufficient} />
|
<Web3Feedback isBalanceSufficient={isBalanceSufficient} />
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
.datasetsContainer {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: auto;
|
||||||
|
margin-left: -2rem;
|
||||||
|
margin-right: -2rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
margin-top: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.datasetsContainer div[class*='AssetSelection-module--selection'] {
|
||||||
|
width: 100%;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datasetsContainer .text {
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
margin-top: calc(var(--spacer) / 2);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--font-color-text);
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
font-family: var(--font-family-heading);
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
|
import styles from './AlgorithmDatasetsListForCompute.module.css'
|
||||||
|
import { getAlgorithmDatasetsForCompute } from '../../../utils/aquarius'
|
||||||
|
import { AssetSelectionAsset } from '../../molecules/FormFields/AssetSelection'
|
||||||
|
import AssetComputeList from '../../molecules/AssetComputeList'
|
||||||
|
import { useOcean } from '../../../providers/Ocean'
|
||||||
|
import { useAsset } from '../../../providers/Asset'
|
||||||
|
|
||||||
|
export default function AlgorithmDatasetsListForCompute({
|
||||||
|
algorithmDid
|
||||||
|
}: {
|
||||||
|
algorithmDid: string
|
||||||
|
}): ReactElement {
|
||||||
|
const { config } = useOcean()
|
||||||
|
const { type } = useAsset()
|
||||||
|
const [datasetsForCompute, setDatasetsForCompute] =
|
||||||
|
useState<AssetSelectionAsset[]>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function getDatasetsAllowedForCompute() {
|
||||||
|
const datasets = await getAlgorithmDatasetsForCompute(
|
||||||
|
algorithmDid,
|
||||||
|
config.metadataCacheUri
|
||||||
|
)
|
||||||
|
setDatasetsForCompute(datasets)
|
||||||
|
}
|
||||||
|
type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||||
|
}, [type])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.datasetsContainer}>
|
||||||
|
<h3 className={styles.text}>Datasets algorithm is allowed to run on</h3>
|
||||||
|
<AssetComputeList assets={datasetsForCompute} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -6,12 +6,10 @@ import { useAsset } from '../../../providers/Asset'
|
|||||||
|
|
||||||
export default function MetaFull(): ReactElement {
|
export default function MetaFull(): ReactElement {
|
||||||
const { ddo, metadata, isInPurgatory, type } = useAsset()
|
const { ddo, metadata, isInPurgatory, type } = useAsset()
|
||||||
|
const { algorithm } = ddo.findServiceByType('metadata').attributes.main
|
||||||
|
|
||||||
function DockerImage() {
|
function DockerImage() {
|
||||||
const algorithmContainer =
|
const { image, tag } = algorithm.container
|
||||||
ddo.findServiceByType('metadata').attributes.main.algorithm.container
|
|
||||||
const { image } = algorithmContainer
|
|
||||||
const { tag } = algorithmContainer
|
|
||||||
return <span>{`${image}:${tag}`}</span>
|
return <span>{`${image}:${tag}`}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +23,7 @@ export default function MetaFull(): ReactElement {
|
|||||||
content={<Publisher account={ddo?.publicKey[0].owner} />}
|
content={<Publisher account={ddo?.publicKey[0].owner} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{type === 'algorithm' && (
|
{type === 'algorithm' && algorithm && (
|
||||||
<MetaItem title="Docker Image" content={<DockerImage />} />
|
<MetaItem title="Docker Image" content={<DockerImage />} />
|
||||||
)}
|
)}
|
||||||
<MetaItem title="DID" content={<code>{ddo?.id}</code>} />
|
<MetaItem title="DID" content={<code>{ddo?.id}</code>} />
|
||||||
|
@ -11,7 +11,7 @@ export default function PageTemplateAssetDetails({
|
|||||||
}: {
|
}: {
|
||||||
uri: string
|
uri: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { ddo, title, error, isInPurgatory } = useAsset()
|
const { ddo, title, error, isInPurgatory, loading } = useAsset()
|
||||||
const [pageTitle, setPageTitle] = useState<string>()
|
const [pageTitle, setPageTitle] = useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -23,7 +23,7 @@ export default function PageTemplateAssetDetails({
|
|||||||
setPageTitle(isInPurgatory ? '' : title)
|
setPageTitle(isInPurgatory ? '' : title)
|
||||||
}, [ddo, error, isInPurgatory, title])
|
}, [ddo, error, isInPurgatory, title])
|
||||||
|
|
||||||
return ddo && pageTitle !== undefined ? (
|
return ddo && pageTitle !== undefined && !loading ? (
|
||||||
<>
|
<>
|
||||||
<Page title={pageTitle} uri={uri}>
|
<Page title={pageTitle} uri={uri}>
|
||||||
<Router basepath="/asset">
|
<Router basepath="/asset">
|
||||||
|
@ -28,6 +28,7 @@ interface AssetProviderValue {
|
|||||||
type: MetadataMain['type'] | undefined
|
type: MetadataMain['type'] | undefined
|
||||||
error?: string
|
error?: string
|
||||||
refreshInterval: number
|
refreshInterval: number
|
||||||
|
loading: boolean
|
||||||
refreshDdo: (token?: CancelToken) => Promise<void>
|
refreshDdo: (token?: CancelToken) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +54,11 @@ function AssetProvider({
|
|||||||
const [owner, setOwner] = useState<string>()
|
const [owner, setOwner] = useState<string>()
|
||||||
const [error, setError] = useState<string>()
|
const [error, setError] = useState<string>()
|
||||||
const [type, setType] = useState<MetadataMain['type']>()
|
const [type, setType] = useState<MetadataMain['type']>()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
const fetchDdo = async (token?: CancelToken) => {
|
const fetchDdo = async (token?: CancelToken) => {
|
||||||
Logger.log('[asset] Init asset, get DDO')
|
Logger.log('[asset] Init asset, get DDO')
|
||||||
|
setLoading(true)
|
||||||
const ddo = await retrieveDDO(
|
const ddo = await retrieveDDO(
|
||||||
asset as string,
|
asset as string,
|
||||||
config.metadataCacheUri,
|
config.metadataCacheUri,
|
||||||
@ -69,13 +72,16 @@ function AssetProvider({
|
|||||||
} else {
|
} else {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
}
|
}
|
||||||
|
setLoading(false)
|
||||||
return ddo
|
return ddo
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshDdo = async (token?: CancelToken) => {
|
const refreshDdo = async (token?: CancelToken) => {
|
||||||
|
setLoading(true)
|
||||||
const ddo = await fetchDdo(token)
|
const ddo = await fetchDdo(token)
|
||||||
Logger.debug('[asset] Got DDO', ddo)
|
Logger.debug('[asset] Got DDO', ddo)
|
||||||
setDDO(ddo)
|
setDDO(ddo)
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -116,7 +122,7 @@ function AssetProvider({
|
|||||||
|
|
||||||
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
|
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
|
||||||
if (!ddo) return
|
if (!ddo) return
|
||||||
|
setLoading(true)
|
||||||
const returnedPrice = await getPrice(ddo)
|
const returnedPrice = await getPrice(ddo)
|
||||||
setPrice({ ...returnedPrice })
|
setPrice({ ...returnedPrice })
|
||||||
|
|
||||||
@ -130,6 +136,7 @@ function AssetProvider({
|
|||||||
|
|
||||||
setIsInPurgatory(ddo.isInPurgatory === 'true')
|
setIsInPurgatory(ddo.isInPurgatory === 'true')
|
||||||
await setPurgatory(ddo.id)
|
await setPurgatory(ddo.id)
|
||||||
|
setLoading(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -152,6 +159,7 @@ function AssetProvider({
|
|||||||
isInPurgatory,
|
isInPurgatory,
|
||||||
purgatoryData,
|
purgatoryData,
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
|
loading,
|
||||||
refreshDdo
|
refreshDdo
|
||||||
} as AssetProviderValue
|
} as AssetProviderValue
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,17 @@ import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSel
|
|||||||
import { PriceList, getAssetsPriceList } from './subgraph'
|
import { PriceList, getAssetsPriceList } from './subgraph'
|
||||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||||
|
|
||||||
|
function getQueryForAlgorithmDatasets(algorithmDid: string) {
|
||||||
|
return {
|
||||||
|
query: {
|
||||||
|
query_string: {
|
||||||
|
query: `service.attributes.main.privacy.publisherTrustedAlgorithms.did:${algorithmDid}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sort: { created: -1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: import directly from ocean.js somehow.
|
// TODO: import directly from ocean.js somehow.
|
||||||
// Transforming Aquarius' direct response is needed for getting actual DDOs
|
// Transforming Aquarius' direct response is needed for getting actual DDOs
|
||||||
// and not just strings of DDOs. For now, taken from
|
// and not just strings of DDOs. For now, taken from
|
||||||
@ -148,3 +159,33 @@ export async function transformDDOToAssetSelection(
|
|||||||
})
|
})
|
||||||
return algorithmList
|
return algorithmList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAlgorithmDatasetsForCompute(
|
||||||
|
algorithmId: string,
|
||||||
|
metadataCacheUri: string
|
||||||
|
): Promise<AssetSelectionAsset[]> {
|
||||||
|
const source = axios.CancelToken.source()
|
||||||
|
const computeDatasets = await queryMetadata(
|
||||||
|
getQueryForAlgorithmDatasets(algorithmId),
|
||||||
|
metadataCacheUri,
|
||||||
|
source.token
|
||||||
|
)
|
||||||
|
const computeDatasetsForCurrentAlgorithm: DDO[] = []
|
||||||
|
computeDatasets.results.forEach((data: DDO) => {
|
||||||
|
const algorithm = data
|
||||||
|
.findServiceByType('compute')
|
||||||
|
.attributes.main.privacy.publisherTrustedAlgorithms.find(
|
||||||
|
(algo) => algo.did === algorithmId
|
||||||
|
)
|
||||||
|
algorithm && computeDatasetsForCurrentAlgorithm.push(data)
|
||||||
|
})
|
||||||
|
if (computeDatasetsForCurrentAlgorithm.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const datasets = await transformDDOToAssetSelection(
|
||||||
|
computeDatasetsForCurrentAlgorithm,
|
||||||
|
metadataCacheUri,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
return datasets
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user