diff --git a/package-lock.json b/package-lock.json index 6be48043e..744c08cc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65001,11 +65001,6 @@ "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": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", diff --git a/src/components/molecules/AssetComputeList.module.css b/src/components/molecules/AssetComputeList.module.css new file mode 100644 index 000000000..bf304f55e --- /dev/null +++ b/src/components/molecules/AssetComputeList.module.css @@ -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'; +} diff --git a/src/components/molecules/AssetComputeList.tsx b/src/components/molecules/AssetComputeList.tsx new file mode 100644 index 000000000..e5f4419d2 --- /dev/null +++ b/src/components/molecules/AssetComputeList.tsx @@ -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
No assets found.
+} + +export default function AssetComputeSelection({ + assets +}: { + assets: AssetSelectionAsset[] +}): JSX.Element { + return ( +
+
+ {!assets ? ( + + ) : assets && !assets.length ? ( + + ) : ( + assets.map((asset: AssetSelectionAsset) => ( + +
+

+ + {asset.name} + +

+ + {asset.symbol} | {asset.did} + +
+ + + )) + )} +
+
+ ) +} diff --git a/src/components/organisms/AssetActions/Compute/index.tsx b/src/components/organisms/AssetActions/Compute/index.tsx index 9f5fc8ee2..71a101fb5 100644 --- a/src/components/organisms/AssetActions/Compute/index.tsx +++ b/src/components/organisms/AssetActions/Compute/index.tsx @@ -29,7 +29,6 @@ import { ComputeAlgorithm, ComputeOutput } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute' -import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection' import { SearchQuery } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' import axios from 'axios' import FormStartComputeDataset from './FormComputeDataset' @@ -37,6 +36,8 @@ import styles from './index.module.css' import SuccessConfetti from '../../../atoms/SuccessConfetti' import Button from '../../../atoms/Button' import { secondsToString } from '../../../../utils/metadata' +import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection' +import AlgorithmDatasetsListForCompute from '../../AssetContent/AlgorithmDatasetsListForCompute' import { getPreviousOrders, getPrice } from '../../../../utils/subgraph' const SuccessAction = () => ( @@ -377,10 +378,13 @@ export default function Compute({ {type === 'algorithm' ? ( - + <> + + + ) : ( } + {type === 'algorithm' && ( + + )}
diff --git a/src/components/organisms/AssetContent/AlgorithmDatasetsListForCompute.module.css b/src/components/organisms/AssetContent/AlgorithmDatasetsListForCompute.module.css new file mode 100644 index 000000000..54cd6f2c1 --- /dev/null +++ b/src/components/organisms/AssetContent/AlgorithmDatasetsListForCompute.module.css @@ -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); +} diff --git a/src/components/organisms/AssetContent/AlgorithmDatasetsListForCompute.tsx b/src/components/organisms/AssetContent/AlgorithmDatasetsListForCompute.tsx new file mode 100644 index 000000000..7531ef390 --- /dev/null +++ b/src/components/organisms/AssetContent/AlgorithmDatasetsListForCompute.tsx @@ -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() + + useEffect(() => { + async function getDatasetsAllowedForCompute() { + const datasets = await getAlgorithmDatasetsForCompute( + algorithmDid, + config.metadataCacheUri + ) + setDatasetsForCompute(datasets) + } + type === 'algorithm' && getDatasetsAllowedForCompute() + }, [type]) + + return ( +
+

Datasets algorithm is allowed to run on

+ +
+ ) +} diff --git a/src/components/organisms/AssetContent/MetaFull.tsx b/src/components/organisms/AssetContent/MetaFull.tsx index 93f6d2bf5..233cc4357 100644 --- a/src/components/organisms/AssetContent/MetaFull.tsx +++ b/src/components/organisms/AssetContent/MetaFull.tsx @@ -6,12 +6,10 @@ import { useAsset } from '../../../providers/Asset' export default function MetaFull(): ReactElement { const { ddo, metadata, isInPurgatory, type } = useAsset() + const { algorithm } = ddo.findServiceByType('metadata').attributes.main function DockerImage() { - const algorithmContainer = - ddo.findServiceByType('metadata').attributes.main.algorithm.container - const { image } = algorithmContainer - const { tag } = algorithmContainer + const { image, tag } = algorithm.container return {`${image}:${tag}`} } @@ -25,7 +23,7 @@ export default function MetaFull(): ReactElement { content={} /> - {type === 'algorithm' && ( + {type === 'algorithm' && algorithm && ( } /> )} {ddo?.id}} /> diff --git a/src/components/templates/PageAssetDetails.tsx b/src/components/templates/PageAssetDetails.tsx index 9f7773cc4..a48361210 100644 --- a/src/components/templates/PageAssetDetails.tsx +++ b/src/components/templates/PageAssetDetails.tsx @@ -11,7 +11,7 @@ export default function PageTemplateAssetDetails({ }: { uri: string }): ReactElement { - const { ddo, title, error, isInPurgatory } = useAsset() + const { ddo, title, error, isInPurgatory, loading } = useAsset() const [pageTitle, setPageTitle] = useState() useEffect(() => { @@ -23,7 +23,7 @@ export default function PageTemplateAssetDetails({ setPageTitle(isInPurgatory ? '' : title) }, [ddo, error, isInPurgatory, title]) - return ddo && pageTitle !== undefined ? ( + return ddo && pageTitle !== undefined && !loading ? ( <> diff --git a/src/providers/Asset.tsx b/src/providers/Asset.tsx index f49bf6ef2..885fc5cde 100644 --- a/src/providers/Asset.tsx +++ b/src/providers/Asset.tsx @@ -28,6 +28,7 @@ interface AssetProviderValue { type: MetadataMain['type'] | undefined error?: string refreshInterval: number + loading: boolean refreshDdo: (token?: CancelToken) => Promise } @@ -53,9 +54,11 @@ function AssetProvider({ const [owner, setOwner] = useState() const [error, setError] = useState() const [type, setType] = useState() + const [loading, setLoading] = useState(false) const fetchDdo = async (token?: CancelToken) => { Logger.log('[asset] Init asset, get DDO') + setLoading(true) const ddo = await retrieveDDO( asset as string, config.metadataCacheUri, @@ -69,13 +72,16 @@ function AssetProvider({ } else { setError(undefined) } + setLoading(false) return ddo } const refreshDdo = async (token?: CancelToken) => { + setLoading(true) const ddo = await fetchDdo(token) Logger.debug('[asset] Got DDO', ddo) setDDO(ddo) + setLoading(false) } // @@ -116,7 +122,7 @@ function AssetProvider({ const initMetadata = useCallback(async (ddo: DDO): Promise => { if (!ddo) return - + setLoading(true) const returnedPrice = await getPrice(ddo) setPrice({ ...returnedPrice }) @@ -130,6 +136,7 @@ function AssetProvider({ setIsInPurgatory(ddo.isInPurgatory === 'true') await setPurgatory(ddo.id) + setLoading(false) }, []) useEffect(() => { @@ -152,6 +159,7 @@ function AssetProvider({ isInPurgatory, purgatoryData, refreshInterval, + loading, refreshDdo } as AssetProviderValue } diff --git a/src/utils/aquarius.ts b/src/utils/aquarius.ts index db8b09d83..d8f2c57c0 100644 --- a/src/utils/aquarius.ts +++ b/src/utils/aquarius.ts @@ -13,6 +13,17 @@ import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSel import { PriceList, getAssetsPriceList } from './subgraph' 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. // Transforming Aquarius' direct response is needed for getting actual DDOs // and not just strings of DDOs. For now, taken from @@ -148,3 +159,33 @@ export async function transformDDOToAssetSelection( }) return algorithmList } + +export async function getAlgorithmDatasetsForCompute( + algorithmId: string, + metadataCacheUri: string +): Promise { + 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 +}