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

restored "Publishers with most sales" section (#1540)

* updated subgraph query

* rework AccountList

* fix missing 'a' elem in dom (<Link> is not rendering correct markup)

* restore PublishersWithMostSales section

* removed unnecessary params in AssetList

* use Blockies for all profile.image

* changed logic on getTopAssetsPublishers

* added aggregations op to getTopAssetsPublishers method

* fix build issues

* improve logic & added correct total sales on profile

* removed complex logic to unify query

* typography & markup cleanup

Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
EnzoVezzaro 2022-07-05 14:49:35 +02:00 committed by GitHub
parent 2d2ad78f89
commit 42956a3441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 177 additions and 118 deletions

View File

@ -9,6 +9,7 @@ interface BaseQueryParams {
nestedQuery?: any nestedQuery?: any
esPaginationOptions?: EsPaginationOptions esPaginationOptions?: EsPaginationOptions
sortOptions?: SortOptions sortOptions?: SortOptions
aggs?: any
filters?: FilterTerm[] filters?: FilterTerm[]
ignorePurgatory?: boolean ignorePurgatory?: boolean
} }

View File

@ -3,4 +3,5 @@ interface PagedAssets {
page: number page: number
totalPages: number totalPages: number
totalResults: number totalResults: number
aggregations: any
} }

View File

@ -5,7 +5,8 @@ export enum SortDirectionOptions {
export enum SortTermOptions { export enum SortTermOptions {
Created = 'metadata.created', Created = 'metadata.created',
Relevance = '_score' Relevance = '_score',
Stats = 'stats.orders'
} }
// Note: could not figure out how to get `enum` to be ambiant // Note: could not figure out how to get `enum` to be ambiant
@ -43,5 +44,6 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
query: any query: any
sort?: { [jsonPath: string]: SortDirectionOptions } sort?: { [jsonPath: string]: SortDirectionOptions }
aggs?: any
} }
} }

View File

@ -55,6 +55,10 @@ export function generateBaseQuery(
} }
} as SearchQuery } as SearchQuery
if (baseQueryParams.aggs !== undefined) {
generatedQuery.aggs = baseQueryParams.aggs
}
if (baseQueryParams.sortOptions !== undefined) if (baseQueryParams.sortOptions !== undefined)
generatedQuery.sort = { generatedQuery.sort = {
[baseQueryParams.sortOptions.sortBy]: [baseQueryParams.sortOptions.sortBy]:
@ -74,12 +78,15 @@ export function transformQueryResult(
results: [], results: [],
page: 0, page: 0,
totalPages: 0, totalPages: 0,
totalResults: 0 totalResults: 0,
aggregations: []
} }
result.results = (queryResult.hits.hits || []).map( result.results = (queryResult.hits.hits || []).map(
(hit) => hit._source as Asset (hit) => hit._source as Asset
) )
result.aggregations = queryResult.aggregations
result.totalResults = queryResult.hits.total.value result.totalResults = queryResult.hits.total.value
result.totalPages = result.totalPages =
result.totalResults / size < 1 result.totalResults / size < 1
@ -307,6 +314,13 @@ export async function getPublishedAssets(
sortBy: SortTermOptions.Created, sortBy: SortTermOptions.Created,
sortDirection: SortDirectionOptions.Descending sortDirection: SortDirectionOptions.Descending
}, },
aggs: {
totalOrders: {
sum: {
field: SortTermOptions.Stats
}
}
},
esPaginationOptions: { esPaginationOptions: {
from: (Number(page) - 1 || 0) * 9, from: (Number(page) - 1 || 0) * 9,
size: 9 size: 9
@ -314,6 +328,62 @@ export async function getPublishedAssets(
} as BaseQueryParams } as BaseQueryParams
const query = generateBaseQuery(baseQueryParams) const query = generateBaseQuery(baseQueryParams)
try {
const result = await queryMetadata(query, cancelToken)
return result
} catch (error) {
if (axios.isCancel(error)) {
LoggerInstance.log(error.message)
} else {
LoggerInstance.error(error.message)
}
}
}
export async function getTopPublishers(
chainIds: number[],
cancelToken: CancelToken,
page?: number,
type?: string,
accesType?: string
): Promise<PagedAssets> {
const filters: FilterTerm[] = []
accesType !== undefined &&
filters.push(getFilterTerm('services.type', accesType))
type !== undefined && filters.push(getFilterTerm('metadata.type', type))
const baseQueryParams = {
chainIds,
filters,
sortOptions: {
sortBy: SortTermOptions.Created,
sortDirection: SortDirectionOptions.Descending
},
aggs: {
topPublishers: {
terms: {
field: 'nft.owner.keyword',
order: { totalSales: 'desc' }
},
aggs: {
totalSales: {
sum: {
field: SortTermOptions.Stats
}
}
}
}
},
esPaginationOptions: {
from: (Number(page) - 1 || 0) * 9,
size: 9
}
} as BaseQueryParams
const query = generateBaseQuery(baseQueryParams)
try { try {
const result = await queryMetadata(query, cancelToken) const result = await queryMetadata(query, cancelToken)
return result return result

View File

@ -18,7 +18,7 @@ import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
import { calcSingleOutGivenPoolIn } from './pool' import { calcSingleOutGivenPoolIn } from './pool'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { MAX_DECIMALS } from './constants' import { MAX_DECIMALS } from './constants'
import { getPublishedAssets, getTopPublishers } from '@utils/aquarius'
export interface UserLiquidity { export interface UserLiquidity {
price: string price: string
oceanBalance: string oceanBalance: string
@ -172,19 +172,11 @@ const UserSalesQuery = gql`
} }
` `
// TODO: figure out some way to get this
const TopSalesQuery = gql` const TopSalesQuery = gql`
query TopSalesQuery { query TopSalesQuery {
users( users(first: 20, orderBy: totalSales, orderDirection: desc) {
first: 20
orderBy: sharesOwned
orderDirection: desc
where: { tokenBalancesOwned_not: "0" }
) {
id id
tokenBalancesOwned { totalSales
value
}
} }
} }
` `
@ -419,20 +411,10 @@ export async function getUserSales(
accountId: string, accountId: string,
chainIds: number[] chainIds: number[]
): Promise<number> { ): Promise<number> {
const variables = { user: accountId?.toLowerCase() }
try { try {
const userSales = await fetchDataForMultipleChains( const result = await getPublishedAssets(accountId, chainIds, null)
UserSalesQuery, const { totalOrders } = result.aggregations
variables, return totalOrders.value
chainIds
)
let salesSum = 0
for (let i = 0; i < userSales.length; i++) {
if (userSales[i].users.length > 0) {
salesSum += parseInt(userSales[i].users[0].totalSales)
}
}
return salesSum
} catch (error) { } catch (error) {
LoggerInstance.error('Error getUserSales', error.message) LoggerInstance.error('Error getUserSales', error.message)
} }
@ -442,33 +424,19 @@ export async function getTopAssetsPublishers(
chainIds: number[], chainIds: number[],
nrItems = 9 nrItems = 9
): Promise<AccountTeaserVM[]> { ): Promise<AccountTeaserVM[]> {
const publisherSales: AccountTeaserVM[] = [] const publishers: AccountTeaserVM[] = []
for (const chain of chainIds) { const result = await getTopPublishers(chainIds, null)
const queryContext = getQueryContext(Number(chain)) const { topPublishers } = result.aggregations
const fetchedUsers: OperationResult<UsersSalesList> = await fetchData(
TopSalesQuery, for (let i = 0; i < topPublishers.buckets.length; i++) {
null, publishers.push({
queryContext address: topPublishers.buckets[i].key,
) nrSales: parseInt(topPublishers.buckets[i].totalSales.value)
for (let i = 0; i < fetchedUsers.data.users.length; i++) { })
const publishersIndex = publisherSales.findIndex(
(user) => fetchedUsers.data.users[i].id === user.address
)
if (publishersIndex === -1) {
const publisher: AccountTeaserVM = {
address: fetchedUsers.data.users[i].id,
nrSales: fetchedUsers.data.users[i].totalSales
}
publisherSales.push(publisher)
} else {
publisherSales[publishersIndex].nrSales +=
publisherSales[publishersIndex].nrSales
}
}
} }
publisherSales.sort((a, b) => b.nrSales - a.nrSales) publishers.sort((a, b) => b.nrSales - a.nrSales)
return publisherSales.slice(0, nrItems) return publishers.slice(0, nrItems)
} }

View File

@ -1,5 +1,5 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './AssetList.module.css' import styles from './index.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import Loader from '../atoms/Loader' import Loader from '../atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
@ -29,7 +29,7 @@ export default function AccountList({
const { chainIds } = useUserPreferences() const { chainIds } = useUserPreferences()
const styleClasses = cx({ const styleClasses = cx({
assetList: true, accountList: true,
[className]: className [className]: className
}) })

View File

@ -0,0 +1,24 @@
.accountList {
display: grid;
grid-template-columns: 1fr;
gap: calc(var(--spacer) / 2);
}
@media screen and (min-width: 25rem) {
.accountList {
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
gap: var(--spacer);
}
}
.empty {
color: var(--color-secondary);
font-size: var(--font-size-small);
font-style: italic;
}
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,46 +1,39 @@
.blockies { .blockies {
aspect-ratio: 1/1; aspect-ratio: 1/1;
width: 13%; width: calc(var(--font-size-large) * 2) !important;
height: 13%; height: calc(var(--font-size-large) * 2) !important;
border-radius: 50%; border-radius: 50%;
margin-left: 0; margin-left: 0;
margin-right: calc(var(--spacer) / 4); margin-right: calc(var(--spacer) / 3);
} }
.teaser { .teaser {
max-width: 40rem;
height: 100%;
}
.link {
composes: box from '../atoms/Box.module.css'; composes: box from '../atoms/Box.module.css';
padding: calc(var(--spacer) / 2) !important; padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2);
font-size: var(--font-size-mini);
height: 90%;
color: var(--color-secondary); color: var(--color-secondary);
position: relative; position: relative;
display: flex;
align-items: center;
}
.link span {
font-size: var(--font-size-large);
margin-right: calc(var(--spacer) / 3);
}
.name {
margin-bottom: 0;
font-size: var(--font-size-base) !important;
padding-top: calc(var(--spacer) / 4);
}
.header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
} }
.sales { .place {
font-size: small; font-size: var(--font-size-large);
margin-top: -5px !important; margin-right: calc(var(--spacer) / 2);
margin-bottom: clac(var(--spacer) / 2); }
.name {
margin-bottom: 0;
font-size: var(--font-size-base);
padding-top: calc(var(--spacer) / 5);
color: var(--font-color-text);
}
.sales {
margin: 0;
}
.sales span {
font-weight: var(--font-weight-bold);
color: var(--font-color-text);
} }

View File

@ -33,34 +33,26 @@ export default function AccountTeaser({
}, [accountTeaserVM, newCancelToken]) }, [accountTeaserVM, newCancelToken])
return ( return (
<article className={styles.teaser}>
<Link href={`/profile/${accountTeaserVM.address}`}> <Link href={`/profile/${accountTeaserVM.address}`}>
<header className={styles.header}> <a className={styles.teaser}>
{place && <span>{place}</span>} {place && <span className={styles.place}>{place}</span>}
{profile?.image ? (
<img src={profile.image} className={styles.blockies} />
) : (
<Blockies <Blockies
accountId={accountTeaserVM.address} accountId={accountTeaserVM.address}
className={styles.blockies} className={styles.blockies}
image={profile?.image}
/> />
)}
<div> <div>
<Dotdotdot clamp={3}> <Dotdotdot tagName="h4" clamp={2} className={styles.name}>
<h3 className={styles.name}>
{profile?.name {profile?.name
? profile?.name ? profile?.name
: accountTruncate(accountTeaserVM.address)} : accountTruncate(accountTeaserVM.address)}
</h3>
</Dotdotdot> </Dotdotdot>
<p className={styles.sales}> <p className={styles.sales}>
{`${accountTeaserVM.nrSales} ${ <span>{accountTeaserVM.nrSales}</span>
accountTeaserVM.nrSales === 1 ? 'sale' : 'sales' {`${accountTeaserVM.nrSales === 1 ? ' sale' : ' sales'}`}
}`}
</p> </p>
</div> </div>
</header> </a>
</Link> </Link>
</article>
) )
} }

View File

@ -5,11 +5,13 @@ import styles from './index.module.css'
export interface BlockiesProps { export interface BlockiesProps {
accountId: string accountId: string
className?: string className?: string
image?: string
} }
export default function Blockies({ export default function Blockies({
accountId, accountId,
className className,
image
}: BlockiesProps): ReactElement { }: BlockiesProps): ReactElement {
if (!accountId) return null if (!accountId) return null
@ -18,7 +20,7 @@ export default function Blockies({
return ( return (
<img <img
className={`${className || ''} ${styles.blockies} `} className={`${className || ''} ${styles.blockies} `}
src={blockies} src={image || blockies}
alt="Blockies" alt="Blockies"
aria-hidden="true" aria-hidden="true"
/> />

View File

@ -1,8 +1,9 @@
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
import { LoggerInstance } from '@oceanprotocol/lib'
import AccountList from '@shared/AccountList/AccountList' import AccountList from '@shared/AccountList/AccountList'
import { getTopAssetsPublishers } from '@utils/subgraph' import { getTopAssetsPublishers } from '@utils/subgraph'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import styles from './Home.module.css' import styles from './index.module.css'
export default function PublishersWithMostSales({ export default function PublishersWithMostSales({
title, title,
@ -17,18 +18,19 @@ export default function PublishersWithMostSales({
useEffect(() => { useEffect(() => {
async function init() { async function init() {
setLoading(true)
if (chainIds.length === 0) { if (chainIds.length === 0) {
const result: AccountTeaserVM[] = [] const result: AccountTeaserVM[] = []
setResult(result) setResult(result)
setLoading(false) setLoading(false)
} else { } else {
try { try {
setLoading(true)
const publishers = await getTopAssetsPublishers(chainIds) const publishers = await getTopAssetsPublishers(chainIds)
setResult(publishers) setResult(publishers)
setLoading(false) setLoading(false)
} catch (error) { } catch (error) {
// Logger.error(error.message) LoggerInstance.error(error.message)
setLoading(false)
} }
} }
} }

View File

@ -14,6 +14,7 @@ import styles from './index.module.css'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { SortTermOptions } from '../../@types/aquarius/SearchQuery' import { SortTermOptions } from '../../@types/aquarius/SearchQuery'
import PublishersWithMostSales from './PublishersWithMostSales'
async function getQueryHighest( async function getQueryHighest(
chainIds: number[] chainIds: number[]
@ -66,7 +67,8 @@ function SectionQueryResult({
results: [], results: [],
page: 0, page: 0,
totalPages: 0, totalPages: 0,
totalResults: 0 totalResults: 0,
aggregations: undefined
} }
setResult(result) setResult(result)
setLoading(false) setLoading(false)
@ -153,6 +155,8 @@ export default function HomePage(): ReactElement {
</Button> </Button>
} }
/> />
<PublishersWithMostSales title="Publishers With Most Sales" />
</> </>
) )
} }