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

Handle asset price in teaser (#596)

* used price from subgraph for asset teasers

* moved loading component inside AssetList

* replaced any with proper types inside subgraph utils

* fixed loading component displayed when empty assets or prices list

* show loading component when refetching data on sort and filter

* get each asset price before displaying the component, loading changes

* refactoring functions for getting asset prices

Co-authored-by: Norbi <katunanorbert@gmai.com>
This commit is contained in:
Norbi 2021-05-17 17:08:15 +03:00 committed by GitHub
parent c2d03f94ac
commit 65194696d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 227 additions and 112 deletions

View File

@ -2,9 +2,10 @@ import AssetTeaser from '../molecules/AssetTeaser'
import * as React from 'react' import * as React from 'react'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import ddo from '../../../tests/unit/__fixtures__/ddo' import ddo from '../../../tests/unit/__fixtures__/ddo'
import { AssetListPrices } from '../../utils/subgraph'
export default { export default {
title: 'Molecules/Asset Teaser' title: 'Molecules/Asset Teaser'
} }
export const Default = () => <AssetTeaser ddo={ddo as DDO} /> export const Default = () => <AssetTeaser ddo={ddo as DDO} price={undefined} />

View File

@ -3,7 +3,7 @@ import { Link } from 'gatsby'
import Dotdotdot from 'react-dotdotdot' import Dotdotdot from 'react-dotdotdot'
import Price from '../atoms/Price' import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css' import styles from './AssetTeaser.module.css'
import { DDO } from '@oceanprotocol/lib' import { DDO, BestPrice } from '@oceanprotocol/lib'
import removeMarkdown from 'remove-markdown' import removeMarkdown from 'remove-markdown'
import Publisher from '../atoms/Publisher' import Publisher from '../atoms/Publisher'
import Time from '../atoms/Time' import Time from '../atoms/Time'
@ -11,9 +11,13 @@ import AssetType from '../atoms/AssetType'
declare type AssetTeaserProps = { declare type AssetTeaserProps = {
ddo: DDO 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 { attributes } = ddo.findServiceByType('metadata')
const { name, type } = attributes.main const { name, type } = attributes.main
const { dataTokenInfo } = ddo const { dataTokenInfo } = ddo
@ -47,7 +51,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
</div> </div>
<footer className={styles.foot}> <footer className={styles.foot}>
<Price price={ddo.price} small /> <Price price={price} small />
<p className={styles.date}> <p className={styles.date}>
<Time date={ddo?.created} relative /> <Time date={ddo?.created} relative />
</p> </p>

View File

@ -16,3 +16,9 @@
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-style: italic; font-style: italic;
} }
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,17 +1,28 @@
import AssetTeaser from '../molecules/AssetTeaser' import AssetTeaser from '../molecules/AssetTeaser'
import React from 'react' import React, { useEffect, useState } from 'react'
import Pagination from '../molecules/Pagination' import Pagination from '../molecules/Pagination'
import styles from './AssetList.module.css' import styles from './AssetList.module.css'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
import Loader from '../atoms/Loader'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
declare type AssetListProps = { declare type AssetListProps = {
assets: DDO[] assets: DDO[]
showPagination: boolean showPagination: boolean
page?: number page?: number
totalPages?: number totalPages?: number
isLoading?: boolean
onPageChange?: React.Dispatch<React.SetStateAction<number>> onPageChange?: React.Dispatch<React.SetStateAction<number>>
className?: string className?: string
} }
@ -21,9 +32,22 @@ const AssetList: React.FC<AssetListProps> = ({
showPagination, showPagination,
page, page,
totalPages, totalPages,
isLoading,
onPageChange, onPageChange,
className 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 // // This changes the page field inside the query
function handlePageChange(selected: number) { function handlePageChange(selected: number) {
onPageChange(selected + 1) onPageChange(selected + 1)
@ -34,11 +58,19 @@ const AssetList: React.FC<AssetListProps> = ({
[className]: className [className]: className
}) })
return ( return assetsWithPrices &&
!loading &&
(isLoading === undefined || isLoading === false) ? (
<> <>
<div className={styleClasses}> <div className={styleClasses}>
{assets.length > 0 ? ( {assetsWithPrices.length > 0 ? (
assets.map((ddo) => <AssetTeaser ddo={ddo} key={ddo.id} />) assetsWithPrices.map((assetWithPrice) => (
<AssetTeaser
ddo={assetWithPrice.ddo}
price={assetWithPrice.price}
key={assetWithPrice.ddo.id}
/>
))
) : ( ) : (
<div className={styles.empty}>No results found.</div> <div className={styles.empty}>No results found.</div>
)} )}
@ -52,6 +84,8 @@ const AssetList: React.FC<AssetListProps> = ({
/> />
)} )}
</> </>
) : (
<LoaderArea />
) )
} }

View File

@ -1,7 +1,6 @@
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../../atoms/Loader'
import AssetList from '../../organisms/AssetList' import AssetList from '../../organisms/AssetList'
import axios from 'axios' import axios from 'axios'
import { queryMetadata } from '../../../utils/aquarius' import { queryMetadata } from '../../../utils/aquarius'
@ -48,14 +47,13 @@ export default function PublishedList(): ReactElement {
getPublished() getPublished()
}, [accountId, page, config.metadataCacheUri]) }, [accountId, page, config.metadataCacheUri])
return isLoading ? ( return accountId ? (
<Loader />
) : accountId && queryResult ? (
<AssetList <AssetList
assets={queryResult.results} assets={queryResult?.results}
isLoading={isLoading}
showPagination showPagination
page={queryResult.page} page={queryResult?.page}
totalPages={queryResult.totalPages} totalPages={queryResult?.totalPages}
onPageChange={(newPage) => { onPageChange={(newPage) => {
setPage(newPage) setPage(newPage)
}} }}

View File

@ -16,9 +16,3 @@
.section [class*='button'] { .section [class*='button'] {
margin-top: var(--spacer); margin-top: var(--spacer);
} }
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -7,7 +7,6 @@ import {
SearchQuery SearchQuery
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import Container from '../atoms/Container' import Container from '../atoms/Container'
import Loader from '../atoms/Loader'
import { useOcean } from '../../providers/Ocean' import { useOcean } from '../../providers/Ocean'
import Button from '../atoms/Button' import Button from '../atoms/Button'
import Bookmarks from '../molecules/Bookmarks' import Bookmarks from '../molecules/Bookmarks'
@ -36,14 +35,6 @@ const queryLatest = {
sort: { created: -1 } sort: { created: -1 }
} }
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
function SectionQueryResult({ function SectionQueryResult({
title, title,
query, query,
@ -55,7 +46,6 @@ function SectionQueryResult({
}) { }) {
const { config } = useOcean() const { config } = useOcean()
const [result, setResult] = useState<QueryResult>() const [result, setResult] = useState<QueryResult>()
const [loading, setLoading] = useState(true)
useEffect(() => { useEffect(() => {
if (!config?.metadataCacheUri) return if (!config?.metadataCacheUri) return
@ -69,7 +59,6 @@ function SectionQueryResult({
source.token source.token
) )
setResult(result) setResult(result)
setLoading(false)
} }
init() init()
@ -81,11 +70,7 @@ function SectionQueryResult({
return ( return (
<section className={styles.section}> <section className={styles.section}>
<h3>{title}</h3> <h3>{title}</h3>
{loading ? ( <AssetList assets={result?.results} showPagination={false} />
<LoaderArea />
) : (
result && <AssetList assets={result.results} showPagination={false} />
)}
{action && action} {action && action}
</section> </section>
) )

View File

@ -82,19 +82,14 @@ export default function SearchPage({
</div> </div>
</div> </div>
<div className={styles.results}> <div className={styles.results}>
{loading ? ( <AssetList
<Loader /> assets={queryResult?.results}
) : queryResult ? ( showPagination
<AssetList isLoading={loading}
assets={queryResult.results} page={queryResult?.page}
showPagination totalPages={queryResult?.totalPages}
page={queryResult.page} onPageChange={setPage}
totalPages={queryResult.totalPages} />
onPageChange={setPage}
/>
) : (
''
)}
</div> </div>
</> </>
) )

View File

@ -10,7 +10,7 @@ import {
SearchQuery SearchQuery
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection' import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
import { PriceList, getAssetPrices } from './subgraph' import { PriceList, getAssetsPriceList } from './subgraph'
import axios, { CancelToken, AxiosResponse } from 'axios' import axios, { CancelToken, AxiosResponse } from 'axios'
// TODO: import directly from ocean.js somehow. // TODO: import directly from ocean.js somehow.
@ -113,7 +113,7 @@ export async function transformDDOToAssetSelection(
): Promise<AssetSelectionAsset[]> { ): Promise<AssetSelectionAsset[]> {
const source = axios.CancelToken.source() const source = axios.CancelToken.source()
const didList: string[] = [] const didList: string[] = []
const priceList: PriceList = await getAssetPrices(ddoList) const priceList: PriceList = await getAssetsPriceList(ddoList)
const symbolList: any = {} const symbolList: any = {}
for (const ddo of ddoList) { for (const ddo of ddoList) {
didList.push(ddo.id) didList.push(ddo.id)

View File

@ -1,12 +1,30 @@
import { gql, DocumentNode, ApolloQueryResult } from '@apollo/client' import { gql, DocumentNode, ApolloQueryResult } from '@apollo/client'
import { DDO, BestPrice } from '@oceanprotocol/lib' import { DDO, BestPrice } from '@oceanprotocol/lib'
import { getApolloClientInstance } from '../providers/ApolloClientProvider' 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' import BigNumber from 'bignumber.js'
export interface PriceList { export interface PriceList {
[key: string]: string [key: string]: string
} }
export interface AssetListPrices {
ddo: DDO
price: BestPrice
}
interface DidAndDatatokenMap {
[name: string]: string
}
const FreQuery = gql` const FreQuery = gql`
query AssetsFrePrice($datatoken_in: [String!]) { query AssetsFrePrice($datatoken_in: [String!]) {
fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) { fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) {
@ -36,6 +54,8 @@ const PoolQuery = gql`
spotPrice spotPrice
consumePrice consumePrice
datatokenAddress datatokenAddress
datatokenReserve
oceanReserve
} }
} }
` `
@ -93,7 +113,7 @@ export async function getPreviousOrders(
id: id, id: id,
account: account account: account
} }
const fetchedPreviousOrders: any = await fetchData( const fetchedPreviousOrders: ApolloQueryResult<AssetPreviousOrder> = await fetchData(
PreviousOrderQuery, PreviousOrderQuery,
variables variables
) )
@ -113,74 +133,32 @@ export async function getPreviousOrders(
} }
} }
export async function getAssetPrices(assets: DDO[]): Promise<PriceList> { function transformPriceToBestPrice(
const priceList: PriceList = {} frePrice: AssetsFrePriceFixedRateExchanges[],
const didDTMap: any = {} poolPrice: AssetsPoolPricePools[]
const dataTokenList: string[] = [] ) {
if (poolPrice?.length > 0) {
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) {
const price: BestPrice = { const price: BestPrice = {
type: 'pool', type: 'pool',
address: poolPriceResponse.data?.pools[0]?.id, address: poolPrice[0]?.id,
value: value:
poolPriceResponse.data?.pools[0]?.consumePrice === '-1' poolPrice[0]?.consumePrice === '-1'
? poolPriceResponse.data?.pools[0]?.spotPrice ? poolPrice[0]?.spotPrice
: poolPriceResponse.data?.pools[0]?.consumePrice, : poolPrice[0]?.consumePrice,
ocean: poolPriceResponse.data?.pools[0]?.oceanReserve, ocean: poolPrice[0]?.oceanReserve,
datatoken: poolPriceResponse.data?.pools[0]?.datatokenReserve, datatoken: poolPrice[0]?.datatokenReserve,
pools: [poolPriceResponse.data?.pools[0]?.id], pools: [poolPrice[0]?.id],
isConsumable: isConsumable: poolPrice[0]?.consumePrice === '-1' ? 'false' : 'true'
poolPriceResponse.data?.pools[0]?.consumePrice === '-1'
? 'false'
: 'true'
} }
return price 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. // TODO Hacky hack, temporary™: set isConsumable to true for fre assets.
// isConsumable: 'true' // isConsumable: 'true'
const price: BestPrice = { const price: BestPrice = {
type: 'exchange', type: 'exchange',
value: frePriceResponse.data?.fixedRateExchanges[0]?.rate, value: frePrice[0]?.rate,
address: frePriceResponse.data?.fixedRateExchanges[0]?.id, address: frePrice[0]?.id,
exchange_id: frePriceResponse.data?.fixedRateExchanges[0]?.id, exchange_id: frePrice[0]?.id,
ocean: 0, ocean: 0,
datatoken: 0, datatoken: 0,
pools: [], pools: [],
@ -201,3 +179,123 @@ export async function getPrice(asset: DDO): Promise<BestPrice> {
return price 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
}