1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge branch 'main' into feature/calica-integration

This commit is contained in:
mihaisc 2023-01-18 15:53:05 +02:00
commit 87d68667a0
41 changed files with 7975 additions and 649 deletions

View File

@ -0,0 +1,91 @@
import { Asset } from '@oceanprotocol/lib'
export const algorithmAquarius: Asset = {
'@context': ['https://w3id.org/did/v1'],
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
version: '4.1.0',
chainId: 1,
metadata: {
created: '2022-09-29T11:30:26Z',
updated: '2022-09-29T11:30:26Z',
type: 'algorithm',
name: 'algorithmTestitest',
description: 'This is an algorithm test.',
links: ['https://www.oceanprotocol.com/sample'],
tags: [
'trading',
'defi',
'algorithm',
'algorithmic-crypto-trading',
'algo-trading',
'trading-strategy',
'cryptocurrency',
'crypto'
],
author: 'Test User',
license: 'https://market.oceanprotocol.com/terms',
additionalInformation: {
termsAndConditions: true
},
algorithm: {
language: 'json',
version: '0.1',
container: {
entrypoint: 'python $algo',
tag: 'latest',
image: 'https://docker.com/test.img',
checksum: ''
}
}
},
services: [
{
id: 'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1',
type: 'access',
files:
'0x04022ef1afafe340f41b261ef721b8dd55dee094975cc70330803d760beef38871948ce572ff1c533d56cda2665749ed2eb8283e243ec5ee19011f510b6b263b2da0af537e3f1fdff7ddd90fa26c7a4761a6d26928bc1348a302634012aac7998e92c84456ab73e9a847120c44ebda15781787e8c382391b2eaefc8b8d36998f3998d1c4647f4f7bb28f4278093c1d231f66e78f81452049443b9e540aeb42ebbdc1b748c024eb10218532814736e241efa1c2a687685b4e2ea7a877685aa0ea325d1a8cf765d1b423b32d81ec3c3e22fc9c15c6b9b71f2862edaec4e4cf7c3a638ffc0ecb88ede3cabb511d4780543a53c001a95f42de1877796e13c997b57bc671507e92198934b4ea7c2e6554993388421253e8c2f10458dec872a7ebfa71b6e77ed359222c93261ba252028c5da06ccf8defcd529885b2125816325a47e23728b513',
datatokenAddress: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
serviceEndpoint: 'https://v4.provider.goerli.oceanprotocol.com',
timeout: 604800
}
],
event: {
tx: '0x3e07a75c1cc5d4146222a93ab4319144e60ecca3ebfb8b15f1ff339d6f479dc9',
block: 7680195,
from: '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
contract: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
datetime: '2022-09-29T11:31:12'
},
nft: {
address: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
name: 'Ocean Data NFT',
symbol: 'OCEAN-NFT',
state: 0,
tokenURI:
'data:application/json;base64,eyJuYW1lIjoiT2NlYW4gRGF0YSBORlQiLCJzeW1ib2wiOiJPQ0VBTi1ORlQiLCJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYW4gYXNzZXQgaW4gdGhlIE9jZWFuIFByb3RvY29sIHY0IGVjb3N5c3RlbS5cblxuVmlldyBvbiBPY2VhbiBNYXJrZXQ6IGh0dHBzOi8vbWFya2V0Lm9jZWFucHJvdG9jb2wuY29tL2Fzc2V0L2RpZDpvcDo2NjU0YjA3OTM3NjViMjY5Njk2Y2VjOGQyZjBkMDc3ZDliYmNkZDNjNGYwMzNkOTQxYWI5Njg0ZThhZDA2NjMwIiwiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9tYXJrZXQub2NlYW5wcm90b2NvbC5jb20vYXNzZXQvZGlkOm9wOjY2NTRiMDc5Mzc2NWIyNjk2OTZjZWM4ZDJmMGQwNzdkOWJiY2RkM2M0ZjAzM2Q5NDFhYjk2ODRlOGFkMDY2MzAiLCJiYWNrZ3JvdW5kX2NvbG9yIjoiMTQxNDE0IiwiaW1hZ2VfZGF0YSI6ImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0Nzdmcgdmlld0JveD0nMCAwIDk5IDk5JyBmaWxsPSd1bmRlZmluZWQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyclM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5Mjc3JyBkPSdNMCw5OUwwLDI5QzksMjUgMTksMjIgMjksMjFDMzgsMTkgNDksMTkgNjEsMjFDNzIsMjIgODUsMjUgOTksMjlMOTksOTlaJy8lM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5MmJiJyBkPSdNMCw5OUwwLDU1QzgsNDkgMTcsNDQgMjgsNDNDMzgsNDEgNTAsNDIgNjMsNDNDNzUsNDMgODcsNDIgOTksNDJMOTksOTlaJyUzRSUzQy9wYXRoJTNFJTNDcGF0aCBmaWxsPSclMjNmZjQwOTJmZicgZD0nTTAsOTlMMCw2OEMxMSw2NiAyMiw2NSAzMiw2N0M0MSw2OCA1MCw3MyA2MSw3NkM3MSw3OCA4NSw3OCA5OSw3OUw5OSw5OVonJTNFJTNDL3BhdGglM0UlM0Mvc3ZnJTNFIn0=',
owner: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
created: '2022-09-29T11:31:12'
},
datatokens: [
{
address: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
name: 'Stupendous Orca Token',
symbol: 'STUORC-59',
serviceId:
'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1'
}
],
stats: {
orders: 22,
price: {
value: 3231343254,
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
tokenSymbol: 'OCEAN'
}
},
purgatory: {
state: false,
reason: ''
}
}

View File

@ -1,6 +1,6 @@
import { Asset } from '@oceanprotocol/lib'
export const assetAquarius: Asset = {
export const datasetAquarius: Asset = {
'@context': ['https://w3id.org/did/v1'],
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
@ -12,7 +12,16 @@ export const assetAquarius: Asset = {
type: 'dataset',
name: 'Testitest',
description: 'This is a test.',
tags: [],
tags: [
'trading',
'defi',
'algorithm',
'algorithmic-crypto-trading',
'algo-trading',
'trading-strategy',
'cryptocurrency',
'crypto'
],
author: 'Test User',
license: 'https://market.oceanprotocol.com/terms',
additionalInformation: {

View File

@ -1,7 +1,7 @@
import { assetAquarius } from './assetAquarius'
import { datasetAquarius } from './datasetAquarius'
export const asset: AssetExtended = {
...assetAquarius,
...datasetAquarius,
accessDetails: {
templateId: 1,
publisherMarketOrderFee: '0',

View File

@ -5,6 +5,5 @@ export default {
chainIds: [5, 1, 137, 56, 1285, 246],
bookmarks: [],
privacyPolicySlug: '/privacy/en',
showPPC: true,
infiniteApproval: false
showPPC: true
}

View File

@ -1,3 +1,3 @@
import { assets } from '../../__fixtures__/assetsWithAccessDetails'
import { assets } from '../../__fixtures__/datasetsWithAccessDetails'
export const getAccessDetailsForAssets = jest.fn().mockResolvedValue(assets)

View File

@ -1,7 +1,7 @@
import marketMetadata from '../__fixtures__/marketMetadata'
import userPreferences from '../__fixtures__/userPreferences'
import web3 from '../__fixtures__/web3'
import { asset } from '../__fixtures__/assetWithAccessDetails'
import { asset } from '../__fixtures__/datasetWithAccessDetails'
jest.mock('../../src/@context/MarketMetadata', () => ({
useMarketMetadata: () => marketMetadata

View File

@ -32,42 +32,45 @@
"label": "File",
"prominentHelp": false,
"type": "tabs",
"fields": [{
"value": "ipfs",
"title": "IPFS",
"label": "CID",
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
"help": "This CID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "arweave",
"title": "Arweave",
"label": "Transaction ID",
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
"help": "This Transaction ID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "url",
"title": "URL",
"label": "File",
"placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
}],
"fields": [
{
"value": "ipfs",
"title": "IPFS",
"label": "CID",
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
"help": "This CID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "arweave",
"title": "Arweave",
"label": "Transaction ID",
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
"help": "This Transaction ID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "url",
"title": "URL",
"label": "File",
"placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
}
],
"sortOptions": false,
"required": true
},{
},
{
"name": "links",
"label": "Sample file",
"prominentHelp": false,
@ -83,7 +86,8 @@
"prominentHelp": true,
"type": "files",
"required": false
}],
}
],
"required": false
},

View File

@ -24,8 +24,7 @@
},
"approval": {
"tooltips": {
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.",
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences."
}
}
}

View File

@ -106,58 +106,62 @@
"label": "File",
"prominentHelp": false,
"type": "tabs",
"fields": [{
"value": "ipfs",
"title": "IPFS",
"label": "CID",
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
"help": "This CID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "arweave",
"title": "Arweave",
"label": "Transaction ID",
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
"help": "This Transaction ID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "url",
"title": "URL",
"label": "File",
"placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
}],
"fields": [
{
"value": "ipfs",
"title": "IPFS",
"label": "CID",
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
"help": "This CID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "arweave",
"title": "Arweave",
"label": "Transaction ID",
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
"help": "This Transaction ID will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
},
{
"value": "url",
"title": "URL",
"label": "File",
"placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true
}
],
"sortOptions": false,
"required": true
},{
},
{
"name": "links",
"label": "Sample file",
"prominentHelp": false,
"type": "tabs",
"fields": [
{
"value": "url",
"title": "URL",
"label": "File",
"placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": false
}],
"value": "url",
"title": "URL",
"label": "File",
"placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": false
}
],
"required": false
},
{

7949
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0",
"@oceanprotocol/lib": "^2.5.2",
"@oceanprotocol/lib": "^2.6.0",
"@oceanprotocol/typographies": "^0.1.0",
"@oceanprotocol/use-dark-mode": "^2.4.3",
"@tippyjs/react": "^4.2.6",
@ -55,9 +55,9 @@
"react-dotdotdot": "^1.3.1",
"react-modal": "^3.16.1",
"react-paginate": "^8.1.4",
"react-select": "^5.6.1",
"react-select": "^5.7.0",
"react-spring": "^9.5.5",
"react-tabs": "^5.1.0",
"react-tabs": "^6.0.0",
"react-toastify": "^9.1.1",
"remark": "^14.0.2",
"remark-gfm": "^3.0.1",
@ -71,14 +71,14 @@
"yup": "^0.32.11"
},
"devDependencies": {
"@storybook/addon-essentials": "^6.5.13",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13",
"@svgr/webpack": "^6.5.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@types/jest": "^29.2.3",
"@types/jest": "^29.2.5",
"@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4",
"@types/node": "^18.8.5",
@ -103,7 +103,7 @@
"husky": "^8.0.2",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.8.0",
"prettier": "^2.8.1",
"pretty-quick": "^3.1.3",
"process": "^0.11.10",
"serve": "^14.1.2",

View File

@ -21,6 +21,7 @@ import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean'
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { useIsMounted } from '@hooks/useIsMounted'
import { useMarketMetadata } from './MarketMetadata'
import { isValidDid } from '@utils/ddo'
export interface AssetProviderValue {
isInPurgatory: boolean
@ -68,6 +69,13 @@ function AssetProvider({
const fetchAsset = useCallback(
async (token?: CancelToken) => {
if (!did) return
const isDid = isValidDid(did)
if (!isDid) {
setError(`The url is not for a valid DID`)
LoggerInstance.error(`[asset] Not a valid DID`)
return
}
LoggerInstance.log('[asset] Fetching asset...')
setLoading(true)

View File

@ -24,8 +24,6 @@ interface UserPreferencesValue {
removeBookmark: (did: string) => void
setPrivacyPolicySlug: (slug: string) => void
setShowPPC: (value: boolean) => void
infiniteApproval: boolean
setInfiniteApproval: (value: boolean) => void
locale: string
}
@ -73,10 +71,6 @@ function UserPreferencesProvider({
localStorage?.showPPC !== false
)
const [infiniteApproval, setInfiniteApproval] = useState(
localStorage?.infiniteApproval || false
)
// Write values to localStorage on change
useEffect(() => {
setLocalStorage({
@ -85,18 +79,9 @@ function UserPreferencesProvider({
currency,
bookmarks,
privacyPolicySlug,
showPPC,
infiniteApproval
showPPC
})
}, [
chainIds,
debug,
currency,
bookmarks,
privacyPolicySlug,
showPPC,
infiniteApproval
])
}, [chainIds, debug, currency, bookmarks, privacyPolicySlug, showPPC])
// Set ocean.js log levels, default: Error
useEffect(() => {
@ -152,8 +137,6 @@ function UserPreferencesProvider({
bookmarks,
privacyPolicySlug,
showPPC,
infiniteApproval,
setInfiniteApproval,
setChainIds,
setDebug,
setCurrency,

View File

@ -22,6 +22,7 @@ export function getNetworkType(network: EthereumListsChain): string {
export function getNetworkDisplayName(data: EthereumListsChain): string {
let displayName
if (!data) return 'Unknown'
switch (data.chainId) {
case 137:

View File

@ -1,5 +1,10 @@
import { Asset, DDO, Service } from '@oceanprotocol/lib'
export function isValidDid(did: string): boolean {
const regex = /did:op:[A-Za-z0-9]{64}/
return regex.test(did)
}
export function getServiceByName(
ddo: Asset | DDO,
name: 'access' | 'compute'

View File

@ -1,4 +1,4 @@
import { sanitizeUrl } from '.'
import { sanitizeUrl, isGoogleUrl } from '.'
describe('@utils/url', () => {
test('sanitizeUrl', () => {
@ -7,3 +7,20 @@ describe('@utils/url', () => {
expect(sanitizeUrl('ftp://example.com')).toBe('about:blank')
})
})
describe('isGoogleUrl', () => {
it('should return true if the url is a google domain', () => {
expect(isGoogleUrl('https://google.com')).toBe(true)
expect(isGoogleUrl('https://drive.google.com')).toBe(true)
expect(isGoogleUrl('https://docs.google.com')).toBe(true)
expect(isGoogleUrl('https://sheets.google.com')).toBe(true)
expect(isGoogleUrl('https://meet.google.com')).toBe(true)
expect(isGoogleUrl('https://calendar.google.com')).toBe(true)
})
it('should return false if the url is not a google domain', () => {
expect(isGoogleUrl('https://google.test.com')).toBe(false)
expect(isGoogleUrl('https://drive.gloogle.com')).toBe(false)
expect(isGoogleUrl('https://drive.google.test.com')).toBe(false)
expect(isGoogleUrl('https://google.com.test.com')).toBe(false)
})
})

View File

@ -3,3 +3,10 @@ export function sanitizeUrl(url: string) {
const isAllowedUrlScheme = u.startsWith('http://') || u.startsWith('https://')
return isAllowedUrlScheme ? url : 'about:blank'
}
// check if the url is a google domain
export const isGoogleUrl = (url: string): boolean => {
if (!url) return
const googleUrl = new URL(url)
return googleUrl.hostname.endsWith('google.com')
}

View File

@ -1,6 +1,7 @@
import { isCID } from '@utils/ipfs'
import isUrl from 'is-url-superb'
import * as Yup from 'yup'
import { isGoogleUrl } from './url/index'
export function testLinks(isEdit?: boolean) {
return Yup.string().test((value, context) => {
@ -28,7 +29,7 @@ export function testLinks(isEdit?: boolean) {
validField = true
}
// if the url has google drive, we need to block the user from submit
if (value?.toString().includes('drive.google')) {
if (isGoogleUrl(value?.toString())) {
validField = false
errorMessage =
'Google Drive is not a supported hosting service. Please use an alternative.'

View File

@ -3,7 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react'
import MarketMetadataProvider from '@context/MarketMetadata'
import { UserPreferencesProvider } from '@context/UserPreferences'
import AssetList, { AssetListProps } from '.'
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails'
import { assets } from '../../../../.jest/__fixtures__/datasetsWithAccessDetails'
export default {
title: 'Component/@shared/AssetList',

View File

@ -1,7 +1,7 @@
import { render, screen, fireEvent } from '@testing-library/react'
import React from 'react'
import AssetList from './index'
import { assetAquarius } from '../../../../.jest/__fixtures__/assetAquarius'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
describe('@shared/AssetList', () => {
it('renders without crashing', async () => {
@ -9,7 +9,7 @@ describe('@shared/AssetList', () => {
render(
<AssetList
assets={[assetAquarius]}
assets={[datasetAquarius]}
showPagination
page={1}
totalPages={10}

View File

@ -1,7 +1,7 @@
import React from 'react'
import testRender from '../../../../.jest/testRender'
import AssetTeaser from './index'
import { asset } from '../../../../.jest/__fixtures__/assetWithAccessDetails'
import { asset } from '../../../../.jest/__fixtures__/datasetWithAccessDetails'
describe('@shared/AssetTeaser', () => {
testRender(<AssetTeaser asset={asset} />)

View File

@ -40,6 +40,9 @@ export default function FileIcon({
? filesize(Number(file.contentLength)).toString()
: ''}
</li>
<li>
{file.type === 'smartcontract' ? 'smart\ncontract' : file.type}
</li>
</>
) : (
<li className={styles.empty}>No file info available</li>

View File

@ -6,6 +6,7 @@ import { InputProps } from '@shared/FormInput'
import { getFileInfo } from '@utils/provider'
import { LoggerInstance } from '@oceanprotocol/lib'
import { useAsset } from '@context/Asset'
import { isGoogleUrl } from '@utils/url/index'
export default function FilesInput(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name)
@ -26,7 +27,7 @@ export default function FilesInput(props: InputProps): ReactElement {
setIsLoading(true)
// TODO: handled on provider
if (url.includes('drive.google')) {
if (isGoogleUrl(url)) {
throw Error(
'Google Drive is not a supported hosting service. Please use an alternative.'
)

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Price from './index'
import { asset } from '../../../../.jest/__fixtures__/assetWithAccessDetails'
import { asset } from '../../../../.jest/__fixtures__/datasetWithAccessDetails'
import prices from '../../../../.jest/__fixtures__/prices'
jest.mock('../../../@context/Prices', () => ({

View File

@ -43,7 +43,7 @@ export default function Tooltip(props: TippyProps): ReactElement {
<Tippy
interactive
interactiveBorder={5}
zIndex={1}
zIndex={3}
trigger={trigger || 'mouseenter focus'}
disabled={disabled || null}
placement={placement || 'auto'}

View File

@ -23,14 +23,14 @@ const computeProps: ButtonBuyProps = {
action: 'compute',
disabled: false,
hasPreviousOrder: false,
hasDatatoken: true,
hasDatatoken: false,
btSymbol: 'btSymbol',
dtSymbol: 'dtSymbol',
dtBalance: '100000000000',
assetTimeout: '1 day',
assetType: 'algorithm',
hasPreviousOrderSelectedComputeAsset: false,
hasDatatokenSelectedComputeAsset: true,
hasDatatokenSelectedComputeAsset: false,
dtSymbolSelectedComputeAsset: 'dtSymbol',
dtBalanceSelectedComputeAsset: 'dtBalance',
selectedComputeAssetType: 'selectedComputeAssetType',
@ -78,6 +78,11 @@ describe('Asset/AssetActions/ButtonBuy', () => {
render(<ButtonBuy {...downloadProps} priceType="free" hasPreviousOrder />)
const button = screen.getByText('Download')
expect(button).toContainHTML('<button')
expect(
screen.getByText(
'This Dataset is free to use. Please note that network gas fees still apply, even when using free assets.'
)
).toBeInTheDocument()
})
it('Renders "Get" button for free assets without crashing', () => {
@ -105,6 +110,33 @@ describe('Asset/AssetActions/ButtonBuy', () => {
expect(button).toContainHTML('<button')
})
it('Renders correct message for fixed-priced compute asset with free algorithm', () => {
render(<ButtonBuy {...computeProps} />)
expect(
screen.getByText(
'To use this algorithm, you will buy 1 dtSymbol and immediately send it back to the publisher. Connect to the correct network to interact with this asset. The C2D resources required to start the job are available, no payment is required for them. Please note that network gas fees still apply, even when using free assets.'
)
).toBeInTheDocument()
})
it('Renders correct message for free compute asset with free algorithm', () => {
render(<ButtonBuy {...computeProps} priceType="free" />)
expect(
screen.getByText(
'This algorithm is free to use. Connect to the correct network to interact with this asset. The C2D resources required to start the job are available, no payment is required for them. Please note that network gas fees still apply, even when using free assets.'
)
).toBeInTheDocument()
})
it('Renders correct message for free compute asset with free algorithm', () => {
render(<ButtonBuy {...computeProps} algorithmPriceType="fixed" />)
expect(
screen.getByText(
'To use this algorithm, you will buy 1 dtSymbol and immediately send it back to the publisher. Connect to the correct network to interact with this asset. The C2D resources required to start the job are available, no payment is required for them.'
)
).toBeInTheDocument()
})
it('Renders "Buy Compute Job" button for compute without crashing', () => {
render(<ButtonBuy {...computeProps} hasDatatokenSelectedComputeAsset />)
const button = screen.getByText('Buy Compute Job')

View File

@ -46,7 +46,8 @@ function getConsumeHelpText(
isBalanceSufficient: boolean,
consumableFeedback: string,
isSupportedOceanNetwork: boolean,
web3: Web3
web3: Web3,
priceType: string
) {
const text =
isConsumable === false
@ -57,7 +58,9 @@ function getConsumeHelpText(
? `You own ${dtBalance} ${dtSymbol} allowing you to use this dataset by spending 1 ${dtSymbol}, but without paying ${btSymbol} again.`
: isBalanceSufficient === false
? `You do not have enough ${btSymbol} in your wallet to purchase this asset.`
: `For using this ${assetType}, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher.`
: priceType === 'free'
? `This ${assetType} is free to use.`
: `To use this ${assetType}, you will buy 1 ${dtSymbol} and immediately send it back to the publisher.`
return text
}
@ -71,7 +74,8 @@ function getAlgoHelpText(
hasDatatokenSelectedComputeAsset: boolean,
isBalanceSufficient: boolean,
isSupportedOceanNetwork: boolean,
web3: Web3
web3: Web3,
algorithmPriceType: string
) {
const text =
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
@ -86,7 +90,9 @@ function getAlgoHelpText(
? `Connect to the correct network to interact with this asset.`
: isBalanceSufficient === false
? ''
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.`
: algorithmPriceType === 'free'
? `Additionally, the selected ${selectedComputeAssetType} is free to use.`
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and send it back to the publisher.`
return text
}
@ -99,6 +105,8 @@ function getComputeAssetHelpText(
isConsumable: boolean,
consumableFeedback: string,
isBalanceSufficient: boolean,
algorithmPriceType: string,
priceType: string,
hasPreviousOrderSelectedComputeAsset?: boolean,
hasDatatokenSelectedComputeAsset?: boolean,
assetType?: string,
@ -121,7 +129,8 @@ function getComputeAssetHelpText(
isBalanceSufficient,
consumableFeedback,
isSupportedOceanNetwork,
web3
web3,
priceType
)
const computeAlgoHelpText = getAlgoHelpText(
@ -134,13 +143,17 @@ function getComputeAssetHelpText(
hasDatatokenSelectedComputeAsset,
isBalanceSufficient,
isSupportedOceanNetwork,
web3
web3,
algorithmPriceType
)
const providerFeeHelpText = hasProviderFee
? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
: 'C2D resources required to start the job are available, no payment required for those fees.'
const computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
: 'The C2D resources required to start the job are available, no payment is required for them.'
let computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
computeHelpText = computeHelpText.replace(/^\s+/, '')
console.log('computeHelpText', computeHelpText)
return computeHelpText
}
@ -190,6 +203,53 @@ export default function ButtonBuy({
? 'Order Compute Job'
: `Buy Compute Job`
function message(): string {
let message = ''
if (action === 'download') {
message = getConsumeHelpText(
btSymbol,
dtBalance,
dtSymbol,
hasDatatoken,
hasPreviousOrder,
assetType,
isConsumable,
isBalanceSufficient,
consumableFeedback,
isSupportedOceanNetwork,
web3,
priceType
)
} else {
message = getComputeAssetHelpText(
hasPreviousOrder,
hasDatatoken,
btSymbol,
dtSymbol,
dtBalance,
isConsumable,
consumableFeedback,
isBalanceSufficient,
algorithmPriceType,
priceType,
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset,
assetType,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetType,
isAlgorithmConsumable,
isSupportedOceanNetwork,
web3,
hasProviderFee
)
}
if (priceType === 'free' || algorithmPriceType === 'free') {
message +=
' Please note that network gas fees still apply, even when using free assets.'
}
return message
}
return (
<div className={styles.actions}>
{isLoading ? (
@ -205,42 +265,7 @@ export default function ButtonBuy({
>
{buttonText}
</Button>
<div className={styles.help}>
{action === 'download'
? getConsumeHelpText(
btSymbol,
dtBalance,
dtSymbol,
hasDatatoken,
hasPreviousOrder,
assetType,
isConsumable,
isBalanceSufficient,
consumableFeedback,
isSupportedOceanNetwork,
web3
)
: getComputeAssetHelpText(
hasPreviousOrder,
hasDatatoken,
btSymbol,
dtSymbol,
dtBalance,
isConsumable,
consumableFeedback,
isBalanceSufficient,
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset,
assetType,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetType,
isAlgorithmConsumable,
isSupportedOceanNetwork,
web3,
hasProviderFee
)}
</div>
<div className={styles.help}>{message()}</div>
</>
)}
</div>

View File

@ -33,6 +33,7 @@ export default function FormStartCompute({
hasDatatokenSelectedComputeAsset,
datasetSymbol,
algorithmSymbol,
providerFeesSymbol,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetType,
@ -61,6 +62,7 @@ export default function FormStartCompute({
hasDatatokenSelectedComputeAsset?: boolean
datasetSymbol?: string
algorithmSymbol?: string
providerFeesSymbol?: string
dtSymbolSelectedComputeAsset?: string
dtBalanceSelectedComputeAsset?: string
selectedComputeAssetType?: string
@ -145,7 +147,7 @@ export default function FormStartCompute({
? new Decimal(providerFeeAmount).toDecimalPlaces(MAX_DECIMALS)
: new Decimal(0)
if (algorithmSymbol === 'OCEAN') {
if (algorithmSymbol === providerFeesSymbol) {
let sum = providerFees.add(priceAlgo)
totalPrices.push({
value: sum.toDecimalPlaces(MAX_DECIMALS).toString(),
@ -161,7 +163,7 @@ export default function FormStartCompute({
})
}
} else {
if (datasetSymbol === 'OCEAN') {
if (datasetSymbol === providerFeesSymbol) {
const sum = providerFees.add(priceDataset)
totalPrices.push({
value: sum.toDecimalPlaces(MAX_DECIMALS).toString(),
@ -179,7 +181,7 @@ export default function FormStartCompute({
})
totalPrices.push({
value: providerFees.toDecimalPlaces(MAX_DECIMALS).toString(),
symbol: 'OCEAN'
symbol: providerFeesSymbol
})
} else {
totalPrices.push({
@ -188,7 +190,7 @@ export default function FormStartCompute({
})
totalPrices.push({
value: providerFees.toDecimalPlaces(MAX_DECIMALS).toString(),
symbol: 'OCEAN'
symbol: providerFeesSymbol
})
totalPrices.push({
value: priceAlgo.toDecimalPlaces(MAX_DECIMALS).toString(),
@ -211,7 +213,8 @@ export default function FormStartCompute({
datasetOrderPrice,
algoOrderPrice,
algorithmSymbol,
datasetSymbol
datasetSymbol,
providerFeesSymbol
])
useEffect(() => {
@ -263,6 +266,7 @@ export default function FormStartCompute({
datasetOrderPrice={datasetOrderPrice}
algoOrderPrice={algoOrderPrice}
providerFeeAmount={providerFeeAmount}
providerFeesSymbol={providerFeesSymbol}
validUntil={validUntil}
totalPrices={totalPrices}
/>

View File

@ -20,6 +20,7 @@ interface PriceOutputProps {
datasetOrderPrice?: string
algoOrderPrice?: string
providerFeeAmount?: string
providerFeesSymbol?: string
validUntil?: string
totalPrices?: totalPriceMap[]
}
@ -84,6 +85,7 @@ export default function PriceOutput({
datasetOrderPrice,
algoOrderPrice,
providerFeeAmount,
providerFeesSymbol,
validUntil,
totalPrices
}: PriceOutputProps): ReactElement {
@ -134,7 +136,7 @@ export default function PriceOutput({
<Row
price={providerFeeAmount} // initializeCompute.provider fee amount
timeout={`${validUntil} seconds`} // valid until value
symbol={'OCEAN'} // we assume that provider fees will always be in OCEAN token
symbol={providerFeesSymbol} // we assume that provider fees will always be in OCEAN token
sign="+"
type="C2D RESOURCES"
/>

View File

@ -12,7 +12,8 @@ import {
ComputeAlgorithm,
ComputeOutput,
ProviderComputeInitializeResults,
unitsToAmount
unitsToAmount,
minAbi
} from '@oceanprotocol/lib'
import { toast } from 'react-toastify'
import Price from '@shared/Price'
@ -61,7 +62,7 @@ export default function Compute({
fileIsLoading?: boolean
consumableFeedback?: string
}): ReactElement {
const { accountId, web3, isSupportedOceanNetwork } = useWeb3()
const { accountId, web3, isSupportedOceanNetwork, networkId } = useWeb3()
const { chainIds } = useUserPreferences()
const { isAssetNetwork } = useAsset()
@ -90,6 +91,7 @@ export default function Compute({
const [initializedProviderResponse, setInitializedProviderResponse] =
useState<ProviderComputeInitializeResults>()
const [providerFeeAmount, setProviderFeeAmount] = useState<string>('0')
const [providerFeesSymbol, setProviderFeesSymbol] = useState<string>('OCEAN')
const [computeValidUntil, setComputeValidUntil] = useState<string>('0')
const [datasetOrderPriceAndFees, setDatasetOrderPriceAndFees] =
useState<OrderPriceAndFees>()
@ -160,6 +162,18 @@ export default function Compute({
setProviderFeeAmount(feeAmount)
const datatoken = new Datatoken(
await getDummyWeb3(asset?.chainId),
null,
null,
minAbi
)
setProviderFeesSymbol(
await datatoken.getSymbol(
initializedProvider?.datasets?.[0]?.providerFee?.providerFeeToken
)
)
const computeDuration = (
parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) -
Math.floor(Date.now() / 1000)
@ -466,11 +480,15 @@ export default function Compute({
assetTimeout={secondsToString(asset?.services[0].timeout)}
hasPreviousOrderSelectedComputeAsset={!!validAlgorithmOrderTx}
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
datasetSymbol={asset?.accessDetails?.baseToken?.symbol || 'OCEAN'}
datasetSymbol={
asset?.accessDetails?.baseToken?.symbol ||
(asset?.chainId === 137 ? 'mOCEAN' : 'OCEAN')
}
algorithmSymbol={
selectedAlgorithmAsset?.accessDetails?.baseToken?.symbol ||
'OCEAN'
(selectedAlgorithmAsset?.chainId === 137 ? 'mOCEAN' : 'OCEAN')
}
providerFeesSymbol={providerFeesSymbol}
dtSymbolSelectedComputeAsset={
selectedAlgorithmAsset?.datatokens[0]?.symbol
}

View File

@ -0,0 +1,11 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Bookmark from './Bookmark'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
describe('src/components/Asset/AssetContent/Bookmark.tsx', () => {
it('renders Add Bookmark button', () => {
render(<Bookmark did={datasetAquarius.id} />)
expect(screen.getByTitle('Add Bookmark')).toBeInTheDocument()
})
})

View File

@ -0,0 +1,17 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import MetaFull from './MetaFull'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
import { algorithmAquarius } from '../../../../.jest/__fixtures__/algorithmAquarius'
describe('src/components/Asset/AssetContent/MetaFull.tsx', () => {
it('renders metadata', () => {
render(<MetaFull ddo={datasetAquarius} />)
expect(screen.getByText('Owner')).toBeInTheDocument()
})
it('renders metadata for an algorithm', () => {
render(<MetaFull ddo={algorithmAquarius} />)
expect(screen.getByText('Docker Image')).toBeInTheDocument()
})
})

View File

@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import MetaSecondary from './MetaSecondary'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
import { algorithmAquarius } from '../../../../.jest/__fixtures__/algorithmAquarius'
describe('src/components/Asset/AssetContent/MetaSecondary.tsx', () => {
it('renders tags', () => {
render(<MetaSecondary ddo={datasetAquarius} />)
expect(
screen.getByText(datasetAquarius.metadata.tags[0])
).toBeInTheDocument()
})
it('renders download sample button', () => {
render(<MetaSecondary ddo={algorithmAquarius} />)
expect(screen.getByText('Sample Data')).toBeInTheDocument()
})
})

View File

@ -179,7 +179,7 @@ export default function Edit({
initialValues={getInitialValues(
asset?.metadata,
asset?.services[0]?.timeout,
asset?.accessDetails?.price,
asset?.accessDetails?.price || '0',
paymentCollector
)}
validationSchema={validationSchema}

View File

@ -6,6 +6,7 @@ import { useAsset } from '@context/Asset'
import { FormPublishData } from '@components/Publish/_types'
import { getFileInfo } from '@utils/provider'
import { getFieldContent } from '@utils/form'
import { isGoogleUrl } from '@utils/url'
export function checkIfTimeoutInPredefinedValues(
timeout: string,
@ -65,7 +66,7 @@ export default function FormEditMetadata({
getFileInfo(asset.metadata.links[0], providerUrl, 'url').then(
(checkedFile) => {
// set valid false if url is using google drive
if (asset.metadata.links[0].includes('drive.google')) {
if (isGoogleUrl(asset.metadata.links[0])) {
setFieldValue('links', [
{
url: asset.metadata.links[0],

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import RelatedAssets from '.'
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails'
import { assets } from '../../../../.jest/__fixtures__/datasetsWithAccessDetails'
import { queryMetadata } from '../../../@utils/aquarius'
// import * as userPreferencesMock from '../../../@context/UserPreferences'

View File

@ -1,21 +0,0 @@
import React, { ReactElement } from 'react'
import { useUserPreferences } from '@context/UserPreferences'
import Input from '@shared/FormInput'
export default function TokenApproval(): ReactElement {
const { infiniteApproval, setInfiniteApproval } = useUserPreferences()
return (
<li>
<Input
label="Token Approvals"
help="Use infinite amount when approving tokens in _Use_."
name="infiniteApproval"
type="checkbox"
options={['Allow infinite amount']}
defaultChecked={infiniteApproval === true}
onChange={() => setInfiniteApproval(!infiniteApproval)}
/>
</li>
)
}

View File

@ -7,7 +7,6 @@ import Debug from './Debug'
import Caret from '@images/caret.svg'
import useDarkMode from '@oceanprotocol/use-dark-mode'
import Appearance from './Appearance'
import TokenApproval from './TokenApproval'
import { useMarketMetadata } from '@context/MarketMetadata'
export default function UserPreferences(): ReactElement {
@ -20,7 +19,6 @@ export default function UserPreferences(): ReactElement {
content={
<ul className={styles.preferencesDetails}>
<Currency />
<TokenApproval />
<Appearance darkMode={darkMode} />
<Debug />
</ul>

View File

@ -3,7 +3,7 @@ import React from 'react'
import MostViews from '.'
import axios from 'axios'
import { queryMetadata } from '@utils/aquarius'
import { assetAquarius } from '../../../../.jest/__fixtures__/assetAquarius'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
jest.mock('axios')
jest.mock('@utils/aquarius')
@ -12,7 +12,7 @@ const axiosMock = axios as jest.Mocked<typeof axios>
const queryMetadataMock = queryMetadata as jest.Mock
const queryMetadataBaseReturn: PagedAssets = {
results: [assetAquarius],
results: [datasetAquarius],
page: 1,
totalPages: 1,
totalResults: 1,
@ -27,7 +27,7 @@ describe('components/Home/MostViews', () => {
it('renders without crashing', async () => {
axiosMock.get.mockImplementation(() =>
Promise.resolve({
data: [{ count: 666, did: assetAquarius.id }]
data: [{ count: 666, did: datasetAquarius.id }]
})
)
queryMetadataMock.mockResolvedValue(queryMetadataBaseReturn)

View File

@ -14,25 +14,9 @@ export default function Page404(): ReactElement {
<>
<Head>
<style type="text/css">{`
body {
background: transparent url(${fishfail}) center bottom no-repeat !important;
background-size: cover !important;
min-height: 100vh;
}
main {
text-align: center;
}
header *,
footer *,
main * {
color: var(--brand-white) !important;
}
header svg path {
fill: var(--brand-white) !important;
}
`}</style>
</Head>
<Page