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

Merge branch 'main' into fix/issue-validation-g-drive-links

This commit is contained in:
EnzoVezzaro 2022-11-07 17:15:25 -04:00
commit ceb055af36
11 changed files with 254 additions and 35 deletions

View File

@ -0,0 +1,20 @@
import marketMetadata from '../__fixtures__/marketMetadata'
import userPreferences from '../__fixtures__/userPreferences'
import web3 from '../__fixtures__/web3'
import { asset } from '../__fixtures__/assetWithAccessDetails'
jest.mock('../../src/@context/MarketMetadata', () => ({
useMarketMetadata: () => marketMetadata
}))
jest.mock('../../src/@context/UserPreferences', () => ({
useUserPreferences: () => userPreferences
}))
jest.mock('../../src/@context/Web3', () => ({
useWeb3: () => web3
}))
jest.mock('../../../@context/Asset', () => ({
useAsset: () => ({ asset })
}))

View File

@ -1,23 +1,3 @@
import '@testing-library/jest-dom/extend-expect' import '@testing-library/jest-dom/extend-expect'
import './__mocks__/matchMedia' import './__mocks__/matchMedia'
import './__mocks__/hooksMocks'
import marketMetadata from './__fixtures__/marketMetadata'
import userPreferences from './__fixtures__/userPreferences'
import web3 from './__fixtures__/web3'
import { asset } from './__fixtures__/assetWithAccessDetails'
jest.mock('../../src/@context/MarketMetadata', () => ({
useMarketMetadata: () => marketMetadata
}))
jest.mock('../../src/@context/UserPreferences', () => ({
useUserPreferences: () => userPreferences
}))
jest.mock('../../src/@context/Web3', () => ({
useWeb3: () => web3
}))
jest.mock('../../../@context/Asset', () => ({
useAsset: () => ({ asset })
}))

2
package-lock.json generated
View File

@ -97,7 +97,7 @@
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"engines": { "engines": {
"node": ">=14" "node": "16"
} }
}, },
"node_modules/@adobe/css-tools": { "node_modules/@adobe/css-tools": {

View File

@ -24,6 +24,8 @@ export declare type AssetListProps = {
onPageChange?: React.Dispatch<React.SetStateAction<number>> onPageChange?: React.Dispatch<React.SetStateAction<number>>
className?: string className?: string
noPublisher?: boolean noPublisher?: boolean
noDescription?: boolean
noPrice?: boolean
} }
export default function AssetList({ export default function AssetList({
@ -34,7 +36,9 @@ export default function AssetList({
isLoading, isLoading,
onPageChange, onPageChange,
className, className,
noPublisher noPublisher,
noDescription,
noPrice
}: AssetListProps): ReactElement { }: AssetListProps): ReactElement {
const { accountId } = useWeb3() const { accountId } = useWeb3()
const [assetsWithPrices, setAssetsWithPrices] = const [assetsWithPrices, setAssetsWithPrices] =
@ -74,6 +78,8 @@ export default function AssetList({
asset={assetWithPrice} asset={assetWithPrice}
key={assetWithPrice.id} key={assetWithPrice.id}
noPublisher={noPublisher} noPublisher={noPublisher}
noDescription={noDescription}
noPrice={noPrice}
/> />
)) ))
) : ( ) : (

View File

@ -21,7 +21,7 @@
} }
.detailLine { .detailLine {
margin-bottom: calc(var(--spacer) / 2); margin-bottom: calc(var(--spacer) / 4);
} }
.content { .content {
@ -43,8 +43,12 @@
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.price {
margin-top: calc(var(--spacer) / 12);
}
.footer { .footer {
margin-top: calc(var(--spacer) / 4); margin-top: calc(var(--spacer) / 12);
} }
.typeLabel { .typeLabel {

View File

@ -14,11 +14,15 @@ import { useUserPreferences } from '@context/UserPreferences'
export declare type AssetTeaserProps = { export declare type AssetTeaserProps = {
asset: AssetExtended asset: AssetExtended
noPublisher?: boolean noPublisher?: boolean
noDescription?: boolean
noPrice?: boolean
} }
export default function AssetTeaser({ export default function AssetTeaser({
asset, asset,
noPublisher noPublisher,
noDescription,
noPrice
}: AssetTeaserProps): ReactElement { }: AssetTeaserProps): ReactElement {
const { name, type, description } = asset.metadata const { name, type, description } = asset.metadata
const { datatokens } = asset const { datatokens } = asset
@ -53,16 +57,23 @@ export default function AssetTeaser({
</Dotdotdot> </Dotdotdot>
{!noPublisher && <Publisher account={owner} minimal />} {!noPublisher && <Publisher account={owner} minimal />}
</header> </header>
{!noDescription && (
<div className={styles.content}> <div className={styles.content}>
<Dotdotdot tagName="p" clamp={3}> <Dotdotdot tagName="p" clamp={3}>
{removeMarkdown(description?.substring(0, 300) || '')} {removeMarkdown(description?.substring(0, 300) || '')}
</Dotdotdot> </Dotdotdot>
</div> </div>
)}
{!noPrice && (
<div className={styles.price}>
{isUnsupportedPricing || !asset.services.length ? ( {isUnsupportedPricing || !asset.services.length ? (
<strong>No pricing schema available</strong> <strong>No pricing schema available</strong>
) : ( ) : (
<Price accessDetails={asset.accessDetails} size="small" /> <Price accessDetails={asset.accessDetails} size="small" />
)} )}
</div>
)}
<footer className={styles.footer}> <footer className={styles.footer}>
{allocated && allocated > 0 ? ( {allocated && allocated > 0 ? (
<span className={styles.typeLabel}> <span className={styles.typeLabel}>

View File

@ -15,6 +15,7 @@ import NetworkName from '@shared/NetworkName'
import content from '../../../../content/purgatory.json' import content from '../../../../content/purgatory.json'
import Web3 from 'web3' import Web3 from 'web3'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import RelatedAssets from '../RelatedAssets'
export default function AssetContent({ export default function AssetContent({
asset asset
@ -78,6 +79,7 @@ export default function AssetContent({
</Button> </Button>
</div> </div>
)} )}
<RelatedAssets />
</div> </div>
</article> </article>
</> </>

View File

@ -0,0 +1,33 @@
import { SortTermOptions } from '../../../@types/aquarius/SearchQuery'
export function generateQuery(
chainIds: number[],
nftAddress: string,
size: number,
tags?: string[],
owner?: string
) {
return {
chainIds,
esPaginationOptions: {
size
},
nestedQuery: {
must_not: {
term: { 'nftAddress.keyword': nftAddress }
}
},
filters: [
tags && {
terms: { 'metadata.tags.keyword': tags }
},
owner && { term: { 'nft.owner.keyword': owner } }
],
sort: {
'stats.orders': 'desc'
},
sortOptions: {
sortBy: SortTermOptions.Orders
} as SortOptions
} as BaseQueryParams
}

View File

@ -0,0 +1,8 @@
.section {
margin-top: calc(var(--spacer) * 2);
}
.section > h3 {
font-size: var(--font-size-large);
color: var(--color-secondary);
}

View File

@ -0,0 +1,67 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import RelatedAssets from '.'
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails'
import { queryMetadata } from '../../../@utils/aquarius'
// import * as userPreferencesMock from '../../../@context/UserPreferences'
jest.mock('../../../@utils/aquarius')
// jest.mock('../../src/@context/UserPreferences', () => ({
// useUserPreferences: () => ({ chainIds: [1, 2, 3] })
// }))
const queryMetadataBaseReturn: PagedAssets = {
results: assets,
page: 1,
totalPages: 1,
totalResults: 10,
aggregations: {}
}
describe('Asset/RelatedAssets', () => {
beforeAll(() => jest.resetAllMocks())
it('renders with more than 4 queryMetadata() results', async () => {
;(queryMetadata as jest.Mock).mockReturnValue(queryMetadataBaseReturn)
render(<RelatedAssets />)
await screen.findByText(assets[0].metadata.name)
})
it('renders with 4 queryMetadata() results', async () => {
;(queryMetadata as jest.Mock).mockReturnValue({
...queryMetadataBaseReturn,
results: assets.slice(0, 4),
totalResults: 4
})
render(<RelatedAssets />)
await screen.findByText(assets[0].metadata.name)
})
// TODO: figure out how to overwrite already mocked module
// it('does nothing when no chainIds selected', async () => {
// jest
// .spyOn(userPreferencesMock, 'useUserPreferences')
// .mockReturnValue({ chainIds: [] } as any)
// render(<RelatedAssets />)
// await screen.findByText('No results found')
// })
it('catches queryMetadata errors', async () => {
;(queryMetadata as jest.Mock).mockImplementation(() => {
throw new Error('Hello error')
})
// prevent console error from showing up in test log
const originalError = console.error
console.error = jest.fn()
try {
render(<RelatedAssets />)
} catch (error) {
expect(error).toEqual({ message: 'Hello error' })
}
console.error = originalError
})
})

View File

@ -0,0 +1,88 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { generateBaseQuery, queryMetadata } from '@utils/aquarius'
import { useUserPreferences } from '@context/UserPreferences'
import { useAsset } from '@context/Asset'
import styles from './index.module.css'
import { useCancelToken } from '@hooks/useCancelToken'
import AssetList from '@shared/AssetList'
import { generateQuery } from './_utils'
export default function RelatedAssets(): ReactElement {
const { asset } = useAsset()
const { chainIds } = useUserPreferences()
const newCancelToken = useCancelToken()
const [relatedAssets, setRelatedAssets] = useState<Asset[]>()
const [isLoading, setIsLoading] = useState<boolean>()
useEffect(() => {
if (
!chainIds?.length ||
!asset?.nftAddress ||
!asset?.nft ||
!asset?.metadata
) {
setIsLoading(false)
return
}
async function getAssets() {
setIsLoading(true)
try {
const tagQuery = generateBaseQuery(
generateQuery(chainIds, asset.nftAddress, 4, asset.metadata.tags)
)
const tagResults = (await queryMetadata(tagQuery, newCancelToken()))
?.results
if (tagResults.length === 4) {
setRelatedAssets(tagResults)
} else {
const ownerQuery = generateBaseQuery(
generateQuery(
chainIds,
asset.nftAddress,
4 - tagResults.length,
null,
asset.nft.owner
)
)
const ownerResults = (
await queryMetadata(ownerQuery, newCancelToken())
)?.results
// combine both results, and filter out duplicates
// stolen from: https://stackoverflow.com/a/70326769/733677
const bothResults = tagResults.concat(
ownerResults.filter(
(asset2) => !tagResults.find((asset1) => asset1.id === asset2.id)
)
)
setRelatedAssets(bothResults)
}
} catch (error) {
LoggerInstance.error(error.message)
} finally {
setIsLoading(false)
}
}
getAssets()
}, [chainIds, asset, newCancelToken])
return (
<section className={styles.section}>
<h3>Related Assets</h3>
<AssetList
assets={relatedAssets}
showPagination={false}
isLoading={isLoading}
noDescription
noPublisher
noPrice
/>
</section>
)
}