diff --git a/content/pages/privacy/de.md b/content/pages/privacy/de.md index 5f6b2228c..36ff1ad21 100644 --- a/content/pages/privacy/de.md +++ b/content/pages/privacy/de.md @@ -235,7 +235,6 @@ Wenn du Fragen zu Purgatory hast oder wenn du kein GitHub-Konto besitzt und möc Du hast das Recht, deine personenbezogenen Daten in einem strukturierten, gängigen und maschinenlesbaren Format zu erhalten. Zusätzlich hast du das Recht, diese Daten ungehindert an einen anderen Verantwortlichen zu übermitteln, sofern die in Art. 20 DSGVO definierten Voraussetzungen zutreffen. Sie können von Ihrem Recht auf Datenübertragbarkeit Gebrauch machen, indem Sie sich mit uns in Verbindung setzen. - ## 7.6 Widerspruchsrecht (Art. 21 GDPR) Du hast das Recht, aus Gründen, die sich aus deiner besonderen Situation ergeben, gegen die Verarbeitung deiner personenbezogenen Daten Widerspruch einzulegen, wenn wir die Verarbeitung auf die Wahrung unseres berechtigten Interessens stützen (Art. 6(1)(f) GDPR). Wenn du Widerspruch einlegst, werden wir deine personenbezogenen Daten nicht mehr verarbeiten, es sei denn, wir können zwingende schutzwürdige Gründe für die Verarbeitung nachweisen, die deine Rechte, Freiheiten und Interessen überwiegen, oder wenn die Verarbeitung zur Begründung, Ausübung oder Verteidigung von Rechtsansprüchen erforderlich ist. diff --git a/content/pages/privacy/fr.md b/content/pages/privacy/fr.md index 37b625e88..f21bb26b3 100644 --- a/content/pages/privacy/fr.md +++ b/content/pages/privacy/fr.md @@ -235,7 +235,6 @@ Si vous avez des questions sur Purgatory ou si vous n'avez pas de compte Git Vous avez le droit de recevoir vos données personnelles dans un format structuré, couramment utilisé et lisible par machine. En outre, vous avez le droit de transmettre ces données à un autre responsable du traitement sans entrave, lorsque les fondements juridiques définis à l'article 20 du RGPD s'appliquent. Vous pouvez faire usage de votre droit à la portabilité des données en nous contactant. - ## 7.6 Droit d'opposition (art. 21 du RGPD) Pour des motifs liés à votre situation particulière, vous avez le droit de vous opposer au traitement de vos données personnelles lorsque nous avons basé le traitement sur des intérêts légitimes (art. 6(1)(f) du RGPD). Si vous vous y opposez, Ocean ne traitera plus vos données personnelles à moins que nous puissions démontrer des motifs légitimes impérieux pour le traitement, outrepassant vos droits, libertés et intérêts, ou si le traitement est nécessaire pour établir, exercer ou défendre des réclamations légales. diff --git a/src/components/molecules/Bookmarks.tsx b/src/components/molecules/Bookmarks.tsx index 3a45ee59f..2cb0baf62 100644 --- a/src/components/molecules/Bookmarks.tsx +++ b/src/components/molecules/Bookmarks.tsx @@ -1,5 +1,5 @@ import { useUserPreferences } from '../../providers/UserPreferences' -import React, { ReactElement, useEffect, useState } from 'react' +import React, { ReactElement, useCallback, useEffect, useState } from 'react' import Table from '../atoms/Table' import { Logger } from '@oceanprotocol/lib' import Price from '../atoms/Price' @@ -7,22 +7,9 @@ import Tooltip from '../atoms/Tooltip' import AssetTitle from './AssetListTitle' import { retrieveDDOListByDIDs } from '../../utils/aquarius' import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph' -import axios, { CancelToken } from 'axios' import { useSiteMetadata } from '../../hooks/useSiteMetadata' import { useCancelToken } from '../../hooks/useCancelToken' - -async function getAssetsBookmarked( - bookmarks: string[], - chainIds: number[], - cancelToken: CancelToken -) { - try { - const result = await retrieveDDOListByDIDs(bookmarks, chainIds, cancelToken) - return result - } catch (error) { - Logger.error(error.message) - } -} +import { CancelToken } from 'axios' const columns = [ { @@ -62,6 +49,27 @@ export default function Bookmarks(): ReactElement { const [isLoading, setIsLoading] = useState() const { chainIds } = useUserPreferences() const newCancelToken = useCancelToken() + + const getAssetsBookmarked = useCallback( + async ( + bookmarks: string[], + chainIds: number[], + cancelToken: CancelToken + ) => { + try { + const result = await retrieveDDOListByDIDs( + bookmarks, + chainIds, + cancelToken + ) + return result + } catch (error) { + Logger.error(error.message) + } + }, + [] + ) + useEffect(() => { if (!appConfig?.metadataCacheUri || bookmarks === []) return @@ -90,7 +98,13 @@ export default function Bookmarks(): ReactElement { setIsLoading(false) } init() - }, [appConfig?.metadataCacheUri, bookmarks, chainIds, newCancelToken]) + }, [ + appConfig?.metadataCacheUri, + bookmarks, + chainIds, + getAssetsBookmarked, + newCancelToken + ]) return ( ( - } - /> + {queryLatest && ( + + All data sets and algorithms → + + } + /> + )} ) diff --git a/src/components/pages/Profile/Header/Stats.tsx b/src/components/pages/Profile/Header/Stats.tsx index e75a02820..7f22b9b27 100644 --- a/src/components/pages/Profile/Header/Stats.tsx +++ b/src/components/pages/Profile/Header/Stats.tsx @@ -5,7 +5,6 @@ import { useUserPreferences } from '../../../../providers/UserPreferences' import { getAccountLiquidityInOwnAssets, getAssetsBestPrices, - getUserSales, UserLiquidity, calculateUserLiquidity } from '../../../../utils/subgraph' diff --git a/src/components/pages/Profile/History/Downloads.tsx b/src/components/pages/Profile/History/Downloads.tsx index ef06b9f01..c381d463a 100644 --- a/src/components/pages/Profile/History/Downloads.tsx +++ b/src/components/pages/Profile/History/Downloads.tsx @@ -4,8 +4,7 @@ import Time from '../../../atoms/Time' import AssetTitle from '../../../molecules/AssetListTitle' import NetworkName from '../../../atoms/NetworkName' import { useProfile } from '../../../../providers/Profile' -import { DownloadedAsset } from '../../../../utils/aquarius' - +import { DownloadedAsset } from '../../../../models/aquarius/DownloadedAsset' const columns = [ { name: 'Data Set', diff --git a/src/components/pages/Profile/History/PoolShares.tsx b/src/components/pages/Profile/History/PoolShares.tsx index 46220452a..279874ee2 100644 --- a/src/components/pages/Profile/History/PoolShares.tsx +++ b/src/components/pages/Profile/History/PoolShares.tsx @@ -64,9 +64,9 @@ function Liquidity({ row, type }: { row: Asset; type: string }) { price = isValidNumber(row.poolShare.poolId.oceanReserve) && isValidNumber(row.poolShare.poolId.datatokenReserve) && - isValidNumber(row.poolShare.poolId.consumePrice) + isValidNumber(row.poolShare.poolId.spotPrice) ? new Decimal(row.poolShare.poolId.datatokenReserve) - .mul(new Decimal(row.poolShare.poolId.consumePrice)) + .mul(new Decimal(row.poolShare.poolId.spotPrice)) .plus(row.poolShare.poolId.oceanReserve) .toString() : '0' @@ -148,6 +148,7 @@ async function getPoolSharesAssets( didList.push(did) } const ddoList = await retrieveDDOListByDIDs(didList, chainIds, cancelToken) + for (let i = 0; i < data.length; i++) { const userLiquidity = calculateUserLiquidity(data[i]) assetList.push({ @@ -176,9 +177,11 @@ export default function PoolShares({ const isMounted = useIsMounted() const fetchPoolSharesAssets = useCallback( - async (cancelToken: CancelToken) => { - if (!poolShares || isPoolSharesLoading || !isMounted()) return - + async ( + chainIds: number[], + poolShares: PoolShare[], + cancelToken: CancelToken + ) => { try { const assets = await getPoolSharesAssets( poolShares, @@ -192,18 +195,23 @@ export default function PoolShares({ setLoading(false) } }, - [poolShares, isPoolSharesLoading, isMounted] + [] ) - + // do not add chainIds,dataFetchInterval to effect dep useEffect(() => { const cancelToken = newCancelToken() async function init() { setLoading(true) - await fetchPoolSharesAssets(cancelToken) + + if (!poolShares || isPoolSharesLoading || !chainIds || !isMounted()) + return + await fetchPoolSharesAssets(chainIds, poolShares, cancelToken) + setLoading(false) if (dataFetchInterval) return + const interval = setInterval(async () => { - await fetchPoolSharesAssets(cancelToken) + await fetchPoolSharesAssets(chainIds, poolShares, cancelToken) }, REFETCH_INTERVAL) setDataFetchInterval(interval) } @@ -212,7 +220,13 @@ export default function PoolShares({ return () => { clearInterval(dataFetchInterval) } - }, [dataFetchInterval, fetchPoolSharesAssets, newCancelToken]) + }, [ + fetchPoolSharesAssets, + isPoolSharesLoading, + newCancelToken, + poolShares, + isMounted + ]) return accountId ? (
() + const [queryResult, setQueryResult] = useState() const [isLoading, setIsLoading] = useState(false) const [page, setPage] = useState(1) - const [service, setServiceType] = useState('dataset OR algorithm') - const [access, setAccsesType] = useState('access OR compute') + const [service, setServiceType] = useState() + const [access, setAccsesType] = useState() const newCancelToken = useCancelToken() - async function getPublished() { - try { - setIsLoading(true) - const result = await getPublishedAssets( - accountId, - chainIds, - newCancelToken(), - page, - service, - access - ) - setQueryResult(result) - } catch (error) { - Logger.error(error.message) - } finally { - setIsLoading(false) - } - } + const getPublished = useCallback( + async (accountId, chainIds, page, service, access, cancelToken) => { + try { + setIsLoading(true) + const result = await getPublishedAssets( + accountId.toLowerCase(), + chainIds, + cancelToken, + page, + service, + access + ) + setQueryResult(result) + } catch (error) { + Logger.error(error.message) + } finally { + setIsLoading(false) + } + }, + [] + ) useEffect(() => { - async function fetchPublishedAssets() { - await getPublished() - } - if (page !== 1) { - setPage(1) - } else { - fetchPublishedAssets() - } - }, [service, access]) + if (queryResult && queryResult.totalPages < page) setPage(1) + }, [page, queryResult]) useEffect(() => { if (!accountId) return - async function fetchPublishedAssets() { - await getPublished() - } - fetchPublishedAssets() - }, [accountId, page, appConfig.metadataCacheUri, chainIds, newCancelToken]) + + getPublished(accountId, chainIds, page, service, access, newCancelToken()) + }, [ + accountId, + page, + appConfig.metadataCacheUri, + chainIds, + newCancelToken, + getPublished, + service, + access + ]) return accountId ? ( <> diff --git a/src/components/templates/Search/Filters.tsx b/src/components/templates/Search/Filters.tsx index f28ef4de5..cc631eae3 100644 --- a/src/components/templates/Search/Filters.tsx +++ b/src/components/templates/Search/Filters.tsx @@ -1,13 +1,13 @@ import React, { ReactElement, useState } from 'react' import { useNavigate } from '@reach/router' import classNames from 'classnames/bind' -import { - addExistingParamsToUrl, - FilterByAccessOptions, - FilterByTypeOptions -} from './utils' +import { addExistingParamsToUrl } from './utils' import Button from '../../atoms/Button' import styles from './Filters.module.css' +import { + FilterByAccessOptions, + FilterByTypeOptions +} from '../../../models/SortAndFilters' const cx = classNames.bind(styles) diff --git a/src/components/templates/Search/index.tsx b/src/components/templates/Search/index.tsx index ac78f1c81..c572a1b6a 100644 --- a/src/components/templates/Search/index.tsx +++ b/src/components/templates/Search/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useState, useEffect } from 'react' +import React, { ReactElement, useState, useEffect, useCallback } from 'react' import Permission from '../../organisms/Permission' import AssetList from '../../organisms/AssetList' import queryString from 'query-string' @@ -10,6 +10,7 @@ import { updateQueryStringParameter } from '../../../utils' import { useUserPreferences } from '../../../providers/UserPreferences' import { useCancelToken } from '../../../hooks/useCancelToken' import styles from './index.module.css' +import { PagedAssets } from '../../../models/PagedAssets' export default function SearchPage({ location, @@ -20,54 +21,61 @@ export default function SearchPage({ setTotalResults: (totalResults: number) => void setTotalPagesNumber: (totalPagesNumber: number) => void }): ReactElement { - const parsed = queryString.parse(location.search) - const { text, owner, tags, page, sort, sortOrder, serviceType, accessType } = - parsed + const [parsed, setParsed] = useState>() const { chainIds } = useUserPreferences() - const [queryResult, setQueryResult] = useState() + const [queryResult, setQueryResult] = useState() const [loading, setLoading] = useState() - const [service, setServiceType] = useState(serviceType as string) - const [access, setAccessType] = useState(accessType as string) - const [sortType, setSortType] = useState(sort as string) - const [sortDirection, setSortDirection] = useState( - sortOrder as string - ) + const [serviceType, setServiceType] = useState() + const [accessType, setAccessType] = useState() + const [sortType, setSortType] = useState() + const [sortDirection, setSortDirection] = useState() const newCancelToken = useCancelToken() - async function fetchAssets() { - setLoading(true) - setTotalResults(undefined) - const queryResult = await getResults(parsed, chainIds, newCancelToken()) + useEffect(() => { + const parsed = queryString.parse(location.search) + const { sort, sortOrder, serviceType, accessType } = parsed + setParsed(parsed) + setServiceType(serviceType as string) + setAccessType(accessType as string) + setSortDirection(sortOrder as string) + setSortType(sort as string) + }, [location]) - setQueryResult(queryResult) - setTotalResults(queryResult.totalResults) - setTotalPagesNumber(queryResult.totalPages) - setLoading(false) - } + const updatePage = useCallback( + (page: number) => { + const { pathname, search } = location + const newUrl = updateQueryStringParameter( + pathname + search, + 'page', + `${page}` + ) + return navigate(newUrl) + }, + [location] + ) - function setPage(page: number) { - const newUrl = updateQueryStringParameter( - location.pathname + location.search, - 'page', - `${page}` - ) - return navigate(newUrl) - } + const fetchAssets = useCallback( + async (parsed: queryString.ParsedQuery, chainIds: number[]) => { + setLoading(true) + setTotalResults(undefined) + const queryResult = await getResults(parsed, chainIds, newCancelToken()) + setQueryResult(queryResult) + setTotalResults(queryResult.totalResults) + setTotalPagesNumber(queryResult.totalPages) + setLoading(false) + }, + [newCancelToken, setTotalPagesNumber, setTotalResults] + ) + useEffect(() => { + if (!parsed || !queryResult) return + const { page } = parsed + if (queryResult.totalPages < Number(page)) updatePage(1) + }, [parsed, queryResult, updatePage]) useEffect(() => { - async function initSearch() { - await fetchAssets() - } - initSearch() - }, [text, owner, tags, sort, page, sortOrder, chainIds, newCancelToken]) - - useEffect(() => { - if (page !== '1') { - setPage(1) - } else { - fetchAssets() - } - }, [serviceType, accessType]) + if (!parsed || !chainIds) return + fetchAssets(parsed, chainIds) + }, [parsed, chainIds, newCancelToken, fetchAssets]) return ( @@ -75,8 +83,8 @@ export default function SearchPage({
diff --git a/src/components/templates/Search/sort.tsx b/src/components/templates/Search/sort.tsx index cf7b59d5a..c706958c8 100644 --- a/src/components/templates/Search/sort.tsx +++ b/src/components/templates/Search/sort.tsx @@ -1,13 +1,13 @@ import React, { ReactElement } from 'react' import { useNavigate } from '@reach/router' -import { - addExistingParamsToUrl, - SortTermOptions, - SortValueOptions -} from './utils' +import { addExistingParamsToUrl } from './utils' import Button from '../../atoms/Button' import styles from './sort.module.css' import classNames from 'classnames/bind' +import { + SortDirectionOptions, + SortTermOptions +} from '../../../models/SortAndFilters' const cx = classNames.bind(styles) @@ -29,7 +29,7 @@ export default function Sort({ }): ReactElement { const navigate = useNavigate() const directionArrow = String.fromCharCode( - sortDirection === SortValueOptions.Ascending ? 9650 : 9660 + sortDirection === SortDirectionOptions.Ascending ? 9650 : 9660 ) async function sortResults(sortBy?: string, direction?: string) { let urlLocation: string @@ -46,10 +46,10 @@ export default function Sort({ } function handleSortButtonClick(value: string) { if (value === sortType) { - if (sortDirection === SortValueOptions.Descending) { - sortResults(null, SortValueOptions.Ascending) + if (sortDirection === SortDirectionOptions.Descending) { + sortResults(null, SortDirectionOptions.Ascending) } else { - sortResults(null, SortValueOptions.Descending) + sortResults(null, SortDirectionOptions.Descending) } } else { sortResults(value, null) diff --git a/src/components/templates/Search/utils.ts b/src/components/templates/Search/utils.ts index ca5711d99..5d9be868c 100644 --- a/src/components/templates/Search/utils.ts +++ b/src/components/templates/Search/utils.ts @@ -1,51 +1,18 @@ import { Logger } from '@oceanprotocol/lib' import { - queryMetadata, - transformChainIdsListToQuery + generateBaseQuery, + getFilterTerm, + queryMetadata } from '../../../utils/aquarius' import queryString from 'query-string' import { CancelToken } from 'axios' - -export const SortTermOptions = { - Created: 'created', - Relevance: '_score' -} as const -type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions] - -export const SortElasticTerm = { - Liquidity: 'price.ocean', - Price: 'price.value', - Created: 'created' -} as const -type SortElasticTerm = typeof SortElasticTerm[keyof typeof SortElasticTerm] - -export const SortValueOptions = { - Ascending: 'asc', - Descending: 'desc' -} as const -type SortValueOptions = typeof SortValueOptions[keyof typeof SortValueOptions] - -export const FilterByTypeOptions = { - Data: 'dataset', - Algorithm: 'algorithm' -} as const -type FilterByTypeOptions = - typeof FilterByTypeOptions[keyof typeof FilterByTypeOptions] - -export const FilterByAccessOptions = { - Download: 'access', - Compute: 'compute' -} -type FilterByAccessOptions = - typeof FilterByAccessOptions[keyof typeof FilterByAccessOptions] - -function getSortType(sortParam: string): string { - const sortTerm = - sortParam === SortTermOptions.Created - ? SortTermOptions.Created - : SortTermOptions.Relevance - return sortTerm -} +import { BaseQueryParams } from '../../../models/aquarius/BaseQueryParams' +import { SearchQuery } from '../../../models/aquarius/SearchQuery' +import { FilterTerm } from '../../../models/aquarius/FilterTerm' +import { + SortDirectionOptions, + SortTermOptions +} from '../../../models/SortAndFilters' export function escapeESReservedChars(text: string): string { return text?.replace(/([!*+\-=<>&|()\\[\]{}^~?:\\/"])/g, '\\$1') @@ -60,10 +27,10 @@ export function getSearchQuery( page?: string, offset?: string, sort?: string, - sortOrder?: string, + sortDirection?: string, serviceType?: string, accessType?: string -): any { +): SearchQuery { text = escapeESReservedChars(text) const emptySearchTerm = text === undefined || text === '' @@ -98,81 +65,70 @@ export function getSearchQuery( 'service.attributes.additionalInformation.description', 'service.attributes.additionalInformation.tags' ] - return { - from: (Number(page) - 1 || 0) * (Number(offset) || 21), - size: Number(offset) || 21, - query: { - bool: { - must: [ - { - bool: { - should: [ - { - query_string: { - query: `${modifiedSearchTerm}`, - fields: searchFields, - minimum_should_match: '2<75%', - phrase_slop: 2, - boost: 5 - } - }, - { - query_string: { - query: `${noSpaceSearchTerm}*`, - fields: searchFields, - boost: 5, - lenient: true - } - }, - { - match_phrase: { - content: { - query: `${searchTerm}`, - boost: 10 - } - } - }, - { - query_string: { - query: `${prefixedSearchTerm}`, - fields: searchFields, - default_operator: 'AND' - } + + const nestedQuery = { + must: [ + { + bool: { + should: [ + { + query_string: { + query: `${modifiedSearchTerm}`, + fields: searchFields, + minimum_should_match: '2<75%', + phrase_slop: 2, + boost: 5 + } + }, + { + query_string: { + query: `${noSpaceSearchTerm}*`, + fields: searchFields, + boost: 5, + lenient: true + } + }, + { + match_phrase: { + content: { + query: `${searchTerm}`, + boost: 10 } - ] + } + }, + { + query_string: { + query: `${prefixedSearchTerm}`, + fields: searchFields, + default_operator: 'AND' + } } - }, - { - match: { - 'service.attributes.main.type': - serviceType === undefined - ? 'dataset OR algorithm' - : `${serviceType}` - } - }, - { - match: { - 'service.type': - accessType === undefined ? 'access OR compute' : `${accessType}` - } - }, - { - query_string: { - query: `${transformChainIdsListToQuery(chainIds)}` - } - }, - { - term: { - isInPurgatory: false - } - } - ] + ] + } } - }, - sort: { - [sort]: sortOrder - } + ] } + + const filters: FilterTerm[] = [] + accessType !== undefined && + filters.push(getFilterTerm('service.type', accessType)) + serviceType !== undefined && + filters.push(getFilterTerm('service.attributes.main.type', serviceType)) + + const baseQueryParams = { + chainIds, + nestedQuery, + esPaginationOptions: { + from: (Number(page) - 1 || 0) * (Number(offset) || 21), + size: Number(offset) || 21 + }, + sortOptions: { sortBy: sort, sortDirection: sortDirection }, + filters + } as BaseQueryParams + + const query = generateBaseQuery(baseQueryParams) + + return query } export async function getResults( @@ -242,7 +198,7 @@ export async function addExistingParamsToUrl( // sort should be relevance when fixed in aqua urlLocation = `${urlLocation}sort=${encodeURIComponent( SortTermOptions.Relevance - )}&sortOrder=${SortValueOptions.Descending}&` + )}&sortOrder=${SortDirectionOptions.Descending}&` } urlLocation = urlLocation.slice(0, -1) return urlLocation diff --git a/src/models/PagedAssets.ts b/src/models/PagedAssets.ts new file mode 100644 index 000000000..9ad191858 --- /dev/null +++ b/src/models/PagedAssets.ts @@ -0,0 +1,8 @@ +import { DDO } from '@oceanprotocol/lib' + +export interface PagedAssets { + results: DDO[] + page: number + totalPages: number + totalResults: number +} diff --git a/src/models/SortAndFilters.ts b/src/models/SortAndFilters.ts new file mode 100644 index 000000000..18bc0ba35 --- /dev/null +++ b/src/models/SortAndFilters.ts @@ -0,0 +1,26 @@ +export enum SortDirectionOptions { + Ascending = 'asc', + Descending = 'desc' +} + +export enum SortTermOptions { + Created = 'created', + Relevance = '_score' +} + +export enum FilterByTypeOptions { + Data = 'dataset', + Algorithm = 'algorithm' +} + +export enum FilterByAccessOptions { + Download = 'access', + Compute = 'compute' +} + +export interface SortOptions { + sortBy: SortTermOptions + sortDirection?: SortDirectionOptions +} + +export type Filters = FilterByTypeOptions | FilterByAccessOptions diff --git a/src/models/aquarius/BaseQueryParams.ts b/src/models/aquarius/BaseQueryParams.ts new file mode 100644 index 000000000..0546dc13e --- /dev/null +++ b/src/models/aquarius/BaseQueryParams.ts @@ -0,0 +1,12 @@ +import { SortOptions } from '../SortAndFilters' +import { EsPaginationOptions } from './EsPaginationOptions' +import { FilterTerm } from './FilterTerm' + +export interface BaseQueryParams { + chainIds: number[] + nestedQuery?: any + esPaginationOptions?: EsPaginationOptions + sortOptions?: SortOptions + filters?: FilterTerm[] + ignorePurgatory?: boolean +} diff --git a/src/models/aquarius/DownloadedAsset.ts b/src/models/aquarius/DownloadedAsset.ts new file mode 100644 index 000000000..79ebbe041 --- /dev/null +++ b/src/models/aquarius/DownloadedAsset.ts @@ -0,0 +1,8 @@ +import { DDO } from '@oceanprotocol/lib' + +export interface DownloadedAsset { + dtSymbol: string + timestamp: number + networkId: number + ddo: DDO +} diff --git a/src/models/aquarius/EsPaginationOptions.ts b/src/models/aquarius/EsPaginationOptions.ts new file mode 100644 index 000000000..caa93ed2e --- /dev/null +++ b/src/models/aquarius/EsPaginationOptions.ts @@ -0,0 +1,4 @@ +export interface EsPaginationOptions { + from?: number + size?: number +} diff --git a/src/models/aquarius/FilterTerm.ts b/src/models/aquarius/FilterTerm.ts new file mode 100644 index 000000000..7e2944690 --- /dev/null +++ b/src/models/aquarius/FilterTerm.ts @@ -0,0 +1,5 @@ +export interface FilterTerm { + [property: string]: { + [property: string]: string | number | boolean | number[] | string[] + } +} diff --git a/src/models/aquarius/SearchQuery.ts b/src/models/aquarius/SearchQuery.ts new file mode 100644 index 000000000..b6eb68902 --- /dev/null +++ b/src/models/aquarius/SearchQuery.ts @@ -0,0 +1,8 @@ +import { SortDirectionOptions } from '../SortAndFilters' +export interface SearchQuery { + from?: number + size?: number + // eslint-disable-next-line @typescript-eslint/no-explicit-any + query: any + sort?: { [jsonPath: string]: SortDirectionOptions } +} diff --git a/src/models/aquarius/SearchResponse.ts b/src/models/aquarius/SearchResponse.ts new file mode 100644 index 000000000..eef2fdfee --- /dev/null +++ b/src/models/aquarius/SearchResponse.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable camelcase */ +import { DDO } from '@oceanprotocol/lib' + +export interface Explanation { + value: number + description: string + details: Explanation[] +} +export interface ShardsResponse { + total: number + successful: number + failed: number + skipped: number +} + +export interface SearchResponse { + took: number + timed_out: boolean + _scroll_id?: string | undefined + _shards: ShardsResponse + hits: { + total: number + max_score: number + hits: Array<{ + _index: string + _type: string + _id: string + _score: number + _source: DDO + _version?: number | undefined + _explanation?: Explanation | undefined + fields?: any + highlight?: any + inner_hits?: any + matched_queries?: string[] | undefined + sort?: string[] | undefined + }> + } + aggregations?: any +} diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 01cbcd553..ea8c96b8c 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -52,8 +52,8 @@ export default function PageGatsbySearch(props: PageProps): ReactElement { > setTotalResults(totalResults)} - setTotalPagesNumber={(totalPages) => setTotalPagesNumber(totalPages)} + setTotalResults={setTotalResults} + setTotalPagesNumber={setTotalPagesNumber} /> ) diff --git a/src/providers/Profile.tsx b/src/providers/Profile.tsx index 8380d17bd..026c63d50 100644 --- a/src/providers/Profile.tsx +++ b/src/providers/Profile.tsx @@ -15,11 +15,7 @@ import { import { useUserPreferences } from './UserPreferences' import { PoolShares_poolShares as PoolShare } from '../@types/apollo/PoolShares' import { DDO, Logger } from '@oceanprotocol/lib' -import { - DownloadedAsset, - getDownloadAssets, - getPublishedAssets -} from '../utils/aquarius' +import { getDownloadAssets, getPublishedAssets } from '../utils/aquarius' import { useSiteMetadata } from '../hooks/useSiteMetadata' import { Profile } from '../models/Profile' import { accountTruncate } from '../utils/web3' @@ -27,6 +23,7 @@ import axios, { CancelToken } from 'axios' import ethereumAddress from 'ethereum-address' import get3BoxProfile from '../utils/profile' import web3 from 'web3' +import { DownloadedAsset } from '../models/aquarius/DownloadedAsset' interface ProfileProviderValue { profile: Profile @@ -134,31 +131,34 @@ function ProfileProvider({ const [isPoolSharesLoading, setIsPoolSharesLoading] = useState(false) const [poolSharesInterval, setPoolSharesInterval] = useState() - const fetchPoolShares = useCallback(async () => { - if (!accountId || !chainIds || !isEthAddress) return + const fetchPoolShares = useCallback( + async (accountId, chainIds, isEthAddress) => { + if (!accountId || !chainIds || !isEthAddress) return - try { - setIsPoolSharesLoading(true) - const poolShares = await getPoolSharesData(accountId, chainIds) - setPoolShares(poolShares) - Logger.log( - `[profile] Fetched ${poolShares.length} pool shares.`, - poolShares - ) - } catch (error) { - Logger.error('Error fetching pool shares: ', error.message) - } finally { - setIsPoolSharesLoading(false) - } - }, [accountId, chainIds, isEthAddress]) + try { + setIsPoolSharesLoading(true) + const poolShares = await getPoolSharesData(accountId, chainIds) + setPoolShares(poolShares) + Logger.log( + `[profile] Fetched ${poolShares.length} pool shares.`, + poolShares + ) + } catch (error) { + Logger.error('Error fetching pool shares: ', error.message) + } finally { + setIsPoolSharesLoading(false) + } + }, + [] + ) useEffect(() => { async function init() { - await fetchPoolShares() + await fetchPoolShares(accountId, chainIds, isEthAddress) if (poolSharesInterval) return const interval = setInterval(async () => { - await fetchPoolShares() + await fetchPoolShares(accountId, chainIds, isEthAddress) }, refreshInterval) setPoolSharesInterval(interval) } @@ -167,7 +167,7 @@ function ProfileProvider({ return () => { clearInterval(poolSharesInterval) } - }, [poolSharesInterval, fetchPoolShares]) + }, [poolSharesInterval, fetchPoolShares, accountId, chainIds, isEthAddress]) // // PUBLISHED ASSETS diff --git a/src/utils/aquarius.ts b/src/utils/aquarius.ts index af5c3cc3d..3d4b04469 100644 --- a/src/utils/aquarius.ts +++ b/src/utils/aquarius.ts @@ -10,49 +10,70 @@ import { PriceList, getAssetsPriceList } from './subgraph' import axios, { CancelToken, AxiosResponse } from 'axios' import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData' import { metadataCacheUri } from '../../app.config' - -export interface DownloadedAsset { - dtSymbol: string - timestamp: number - networkId: number - ddo: DDO -} +import { DownloadedAsset } from '../models/aquarius/DownloadedAsset' +import { SearchQuery } from '../models/aquarius/SearchQuery' +import { SearchResponse } from '../models/aquarius/SearchResponse' +import { PagedAssets } from '../models/PagedAssets' +import { SortDirectionOptions, SortTermOptions } from '../models/SortAndFilters' +import { FilterTerm } from '../models/aquarius/FilterTerm' +import { BaseQueryParams } from '../models/aquarius/BaseQueryParams' export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476 -function getQueryForAlgorithmDatasets(algorithmDid: string, chainId?: number) { +/** + * @param filterField the name of the actual field from the ddo schema e.g. 'id','service.attributes.main.type' + * @param value the value of the filter + * @returns json structure of the es filter + */ +export function getFilterTerm( + filterField: string, + value: string | number | boolean | number[] | string[] +): FilterTerm { + const isArray = Array.isArray(value) return { - query: { - bool: { - must: [ - { - match: { - 'service.attributes.main.privacy.publisherTrustedAlgorithms.did': - algorithmDid - } - }, - { - query_string: { - query: `chainId:${chainId}` - } - } - ] - } - }, - sort: { created: 'desc' } + [isArray ? 'terms' : 'term']: { + [filterField]: value + } } } -// 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 -// https://github.com/oceanprotocol/ocean.js/blob/main/src/metadatacache/MetadataCache.ts#L361-L375 +export function generateBaseQuery( + baseQueryParams: BaseQueryParams +): SearchQuery { + const generatedQuery = { + from: baseQueryParams.esPaginationOptions?.from || 0, + size: baseQueryParams.esPaginationOptions?.size || 1000, + query: { + bool: { + ...baseQueryParams.nestedQuery, + filter: [ + ...(baseQueryParams.filters || []), + getFilterTerm('chainId', baseQueryParams.chainIds), + getFilterTerm('_index', 'aquarius'), + ...(baseQueryParams.ignorePurgatory + ? [] + : [getFilterTerm('isInPurgatory', 'false')]) + ] + } + } + } as SearchQuery + + if (baseQueryParams.sortOptions !== undefined) + generatedQuery.sort = { + [baseQueryParams.sortOptions.sortBy]: + baseQueryParams.sortOptions.sortDirection || + SortDirectionOptions.Descending + } + + return generatedQuery +} + export function transformQueryResult( - queryResult: any, + queryResult: SearchResponse, from = 0, size = 21 -): any { - const result: any = { +): PagedAssets { + const result: PagedAssets = { results: [], page: 0, totalPages: 0, @@ -60,7 +81,7 @@ export function transformQueryResult( } result.results = (queryResult.hits.hits || []).map( - (hit: any) => new DDO(hit._source as DDO) + (hit) => new DDO(hit._source as DDO) ) result.totalResults = queryResult.hits.total result.totalPages = @@ -72,31 +93,12 @@ export function transformQueryResult( return result } -export function transformChainIdsListToQuery(chainIds: number[]): string { - let chainQuery = '' - chainIds.forEach((chainId) => { - chainQuery += `chainId:${chainId} OR ` - }) - chainQuery = chainQuery.slice(0, chainQuery.length - 4) - return chainQuery -} - -export function transformDIDListToQuery(didList: string[] | DID[]): string { - let chainQuery = '' - const regex = new RegExp('(:)', 'g') - didList.forEach((did: any) => { - chainQuery += `id:${did.replace(regex, '\\:')} OR ` - }) - chainQuery = chainQuery.slice(0, chainQuery.length - 4) - return chainQuery -} - export async function queryMetadata( - query: any, + query: SearchQuery, cancelToken: CancelToken -): Promise { +): Promise { try { - const response: AxiosResponse = await axios.post( + const response: AxiosResponse = await axios.post( `${metadataCacheUri}/api/v1/aquarius/assets/query`, { ...query }, { cancelToken } @@ -155,6 +157,53 @@ export async function getAssetsNames( } } +export async function getAssetsFromDidList( + didList: string[], + chainIds: number[], + cancelToken: CancelToken +): Promise { + try { + if (!(didList.length > 0)) return + + const baseParams = { + chainIds: chainIds, + filters: [getFilterTerm('id', didList)], + ignorePurgatory: true + } as BaseQueryParams + const query = generateBaseQuery(baseParams) + + const queryResult = await queryMetadata(query, cancelToken) + return queryResult + } catch (error) { + Logger.error(error.message) + } +} + +export async function retrieveDDOListByDIDs( + didList: string[], + chainIds: number[], + cancelToken: CancelToken +): Promise { + try { + if (didList?.length === 0 || chainIds?.length === 0) return [] + const orderedDDOListByDIDList: DDO[] = [] + const baseQueryparams = { + chainIds, + filters: [getFilterTerm('id', didList)], + ignorePurgatory: true + } as BaseQueryParams + const query = generateBaseQuery(baseQueryparams) + const result = await queryMetadata(query, cancelToken) + didList.forEach((did: string | DID) => { + const ddo: DDO = result.results.find((ddo: DDO) => ddo.id === did) + orderedDDOListByDIDList.push(ddo) + }) + return orderedDDOListByDIDList + } catch (error) { + Logger.error(error.message) + } +} + export async function transformDDOToAssetSelection( datasetProviderEndpoint: string, ddoList: DDO[], @@ -212,60 +261,34 @@ export async function getAlgorithmDatasetsForCompute( datasetChainId?: number, cancelToken?: CancelToken ): Promise { - const computeDatasets = await queryMetadata( - getQueryForAlgorithmDatasets(algorithmId, datasetChainId), - cancelToken - ) - const computeDatasetsForCurrentAlgorithm: DDO[] = [] - computeDatasets.results.forEach((data: DDO) => { - const algorithm = data - .findServiceByType('compute') - .attributes.main.privacy.publisherTrustedAlgorithms.find( - (algo) => algo.did === algorithmId + const baseQueryParams = { + chainIds: [datasetChainId], + filters: [ + getFilterTerm( + 'service.attributes.main.privacy.publisherTrustedAlgorithms.did', + algorithmId ) - algorithm && computeDatasetsForCurrentAlgorithm.push(data) - }) - if (computeDatasetsForCurrentAlgorithm.length === 0) { - return [] - } + ], + sortOptions: { + sortBy: SortTermOptions.Created, + sortDirection: SortDirectionOptions.Descending + } + } as BaseQueryParams + + const query = generateBaseQuery(baseQueryParams) + const computeDatasets = await queryMetadata(query, cancelToken) + + if (computeDatasets.totalResults === 0) return [] + const datasets = await transformDDOToAssetSelection( datasetProviderUri, - computeDatasetsForCurrentAlgorithm, + computeDatasets.results, [], cancelToken ) return datasets } -export async function retrieveDDOListByDIDs( - didList: string[] | DID[], - chainIds: number[], - cancelToken: CancelToken -): Promise { - try { - if (didList?.length === 0 || chainIds?.length === 0) return [] - const orderedDDOListByDIDList: DDO[] = [] - const query = { - size: didList.length, - query: { - query_string: { - query: `(${transformDIDListToQuery( - didList - )}) AND (${transformChainIdsListToQuery(chainIds)})` - } - } - } - const result = await queryMetadata(query, cancelToken) - didList.forEach((did: string | DID) => { - const ddo: DDO = result.results.find((ddo: DDO) => ddo.id === did) - orderedDDOListByDIDList.push(ddo) - }) - return orderedDDOListByDIDList - } catch (error) { - Logger.error(error.message) - } -} - export async function getPublishedAssets( accountId: string, chainIds: number[], @@ -273,26 +296,33 @@ export async function getPublishedAssets( page?: number, type?: string, accesType?: string -): Promise { +): Promise { if (!accountId) return - type = type || 'dataset OR algorithm' - accesType = accesType || 'access OR compute' + const filters: FilterTerm[] = [] - const queryPublishedAssets = { - from: (Number(page) - 1 || 0) * (Number(9) || 21), - size: Number(9) || 21, - query: { - query_string: { - query: `(publicKey.owner:${accountId}) AND (service.attributes.main.type:${type}) AND (service.type:${accesType}) AND (${transformChainIdsListToQuery( - chainIds - )})` - } + filters.push(getFilterTerm('publicKey.owner', accountId.toLowerCase())) + accesType !== undefined && + filters.push(getFilterTerm('service.type', accesType)) + type !== undefined && + filters.push(getFilterTerm('service.attributes.main.type', type)) + + const baseQueryParams = { + chainIds, + filters, + sortOptions: { + sortBy: SortTermOptions.Created, + sortDirection: SortDirectionOptions.Descending }, - sort: { created: 'desc' } - } + esPaginationOptions: { + from: (Number(page) - 1 || 0) * 9, + size: 9 + } + } as BaseQueryParams + + const query = generateBaseQuery(baseQueryParams) try { - const result = await queryMetadata(queryPublishedAssets, cancelToken) + const result = await queryMetadata(query, cancelToken) return result } catch (error) { if (axios.isCancel(error)) { @@ -309,38 +339,34 @@ export async function getDownloadAssets( chainIds: number[], cancelToken: CancelToken ): Promise { - const downloadedAssets: DownloadedAsset[] = [] - try { - const queryResult = await retrieveDDOListByDIDs( - didList, + const baseQueryparams = { chainIds, - cancelToken - ) - const ddoList = queryResult + filters: [ + getFilterTerm('id', didList), + getFilterTerm('service.type', 'access') + ] + } as BaseQueryParams + const query = generateBaseQuery(baseQueryparams) + const result = await queryMetadata(query, cancelToken) - for (let i = 0; i < tokenOrders?.length; i++) { - const ddo = ddoList.filter( - (ddo: { dataToken: string }) => - tokenOrders[i].datatokenId.address.toLowerCase() === - ddo.dataToken.toLowerCase() - )[0] + const downloadedAssets: DownloadedAsset[] = result.results + .map((ddo) => { + const order = tokenOrders.find( + ({ datatokenId }) => + datatokenId?.address.toLowerCase() === ddo.dataToken.toLowerCase() + ) - // make sure we are only pushing download orders - if (ddo.service[1].type !== 'access') continue - - downloadedAssets.push({ - ddo, - networkId: ddo.chainId, - dtSymbol: tokenOrders[i].datatokenId.symbol, - timestamp: tokenOrders[i].timestamp + return { + ddo, + networkId: ddo.chainId, + dtSymbol: order?.datatokenId?.symbol, + timestamp: order?.timestamp + } }) - } + .sort((a, b) => b.timestamp - a.timestamp) - const sortedOrders = downloadedAssets.sort( - (a, b) => b.timestamp - a.timestamp - ) - return sortedOrders + return downloadedAssets } catch (error) { Logger.error(error.message) } diff --git a/src/utils/compute.ts b/src/utils/compute.ts index b260ddef3..ae986452f 100644 --- a/src/utils/compute.ts +++ b/src/utils/compute.ts @@ -12,12 +12,13 @@ import { import { ComputePrivacyForm } from '../models/FormEditComputeDataset' import web3 from 'web3' import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute' -import axios, { CancelToken } from 'axios' +import { CancelToken } from 'axios' import { gql } from 'urql' import { ComputeJobMetaData } from '../@types/ComputeJobMetaData' -import { transformChainIdsListToQuery, queryMetadata } from './aquarius' +import { queryMetadata, getFilterTerm, generateBaseQuery } from './aquarius' import { fetchDataForMultipleChains } from './subgraph' import { OrdersData_tokenOrders_datatokenId as OrdersDatatoken } from '../@types/apollo/OrdersData' +import { BaseQueryParams } from '../models/aquarius/BaseQueryParams' const getComputeOrders = gql` query ComputeOrders($user: String!) { @@ -72,22 +73,22 @@ interface ComputeResults { } async function getAssetMetadata( - queryDtList: string, + queryDtList: string[], cancelToken: CancelToken, chainIds: number[] ): Promise { - const queryDid = { - query: { - query_string: { - query: `(${queryDtList}) AND (${transformChainIdsListToQuery( - chainIds - )}) AND service.attributes.main.type:dataset AND service.type:compute`, - fields: ['dataToken'] - } - } - } + const baseQueryparams = { + chainIds, + filters: [ + getFilterTerm('dataToken', queryDtList), + getFilterTerm('service.type', 'compute'), + getFilterTerm('service.attributes.main.type', 'dataset') + ], + ignorePurgatory: true + } as BaseQueryParams + const query = generateBaseQuery(baseQueryparams) + const result = await queryMetadata(query, cancelToken) - const result = await queryMetadata(queryDid, cancelToken) return result.results } @@ -206,18 +207,14 @@ async function getJobs( return computeJobs } -function getDtList(data: TokenOrder[]) { +function getDtList(data: TokenOrder[]): string[] { const dtList = [] for (let i = 0; i < data.length; i++) { dtList.push(data[i].datatokenId.address) } - const queryDtList = JSON.stringify(dtList) - .replace(/,/g, ' ') - .replace(/"/g, '') - .replace(/(\[|\])/g, '') - return queryDtList + return dtList } export async function getComputeJobs( @@ -225,7 +222,8 @@ export async function getComputeJobs( config: Config, ocean: Ocean, account: Account, - ddo?: DDO + ddo?: DDO, + token?: CancelToken ): Promise { const assetDTAddress = ddo?.dataTokenInfo?.address let computeResult: ComputeResults = { @@ -262,10 +260,9 @@ export async function getComputeJobs( data = data.sort((a, b) => b.timestamp - a.timestamp) const queryDtList = getDtList(data) - if (queryDtList === '') return + if (!queryDtList) return - const source = axios.CancelToken.source() - const assets = await getAssetMetadata(queryDtList, source.token, chainIds) + const assets = await getAssetMetadata(queryDtList, token, chainIds) const serviceEndpoints = getServiceEndpoints(data, assets) const providers: Provider[] = await getProviders( serviceEndpoints, diff --git a/src/utils/subgraph.ts b/src/utils/subgraph.ts index a4d732b5d..4658b0076 100644 --- a/src/utils/subgraph.ts +++ b/src/utils/subgraph.ts @@ -590,10 +590,10 @@ export async function getAssetsBestPrices( return assetsWithPrice } -export async function getHighestLiquidityDIDs( +export async function getHighestLiquidityDatatokens( chainIds: number[] -): Promise<[string, number]> { - const didList: string[] = [] +): Promise { + const dtList: string[] = [] let highestLiquidityAssets: HighestLiquidityAssetsPool[] = [] for (const chain of chainIds) { const queryContext = getQueryContext(Number(chain)) @@ -603,22 +603,12 @@ export async function getHighestLiquidityDIDs( fetchedPools.data.pools ) } - highestLiquidityAssets - .sort((a, b) => a.oceanReserve - b.oceanReserve) - .reverse() + highestLiquidityAssets.sort((a, b) => b.oceanReserve - a.oceanReserve) for (let i = 0; i < highestLiquidityAssets.length; i++) { if (!highestLiquidityAssets[i].datatokenAddress) continue - const did = web3.utils - .toChecksumAddress(highestLiquidityAssets[i].datatokenAddress) - .replace('0x', 'did:op:') - didList.push(did) + dtList.push(highestLiquidityAssets[i].datatokenAddress) } - const searchDids = JSON.stringify(didList) - .replace(/,/g, ' ') - .replace(/"/g, '') - .replace(/(\[|\])/g, '') - .replace(/(did:op:)/g, '0x') - return [searchDids, didList.length] + return dtList } export function calculateUserLiquidity(poolShare: PoolShare): number { @@ -628,7 +618,7 @@ export function calculateUserLiquidity(poolShare: PoolShare): number { const datatokens = (poolShare.balance / poolShare.poolId.totalShares) * poolShare.poolId.datatokenReserve - const totalLiquidity = ocean + datatokens * poolShare.poolId.consumePrice + const totalLiquidity = ocean + datatokens * poolShare.poolId.spotPrice return totalLiquidity } @@ -648,6 +638,7 @@ export async function getAccountLiquidityInOwnAssets( ) let totalLiquidity = 0 let totalOceanLiquidity = 0 + for (const result of results) { for (const poolShare of result.poolShares) { const userShare = poolShare.balance / poolShare.poolId.totalShares