From 6ed92c0bfd68c8b4c5aef349f138936fc49dbcf0 Mon Sep 17 00:00:00 2001 From: Norbi <37236152+KatunaNorbert@users.noreply.github.com> Date: Tue, 15 Jun 2021 14:30:01 +0300 Subject: [PATCH 1/9] covert timeout before display (#682) Co-authored-by: Norbi --- src/components/organisms/AssetActions/Consume.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/AssetActions/Consume.tsx b/src/components/organisms/AssetActions/Consume.tsx index 2918d1bdf..3dc526a7a 100644 --- a/src/components/organisms/AssetActions/Consume.tsx +++ b/src/components/organisms/AssetActions/Consume.tsx @@ -156,7 +156,7 @@ export default function Consume({ dtSymbol={ddo.dataTokenInfo?.symbol} dtBalance={dtBalance} onClick={handleConsume} - assetTimeout={assetTimeout} + assetTimeout={secondsToString(parseInt(assetTimeout))} assetType={type} stepText={consumeStepText || pricingStepText} isLoading={pricingIsLoading || isLoading} From eb8c6afb62095334da352405c9c53125781a2c7b Mon Sep 17 00:00:00 2001 From: claudiaHash <49017601+claudiaHash@users.noreply.github.com> Date: Tue, 15 Jun 2021 16:46:49 +0300 Subject: [PATCH 2/9] Search issues fix (#641) * search by addresses * search by asset id * logs deleted * search query updated * search query updated * search terms differentiated * asset id hack * id search hack removed * restore lock file Signed-off-by: mihaisc * remove SearchQuery return type Signed-off-by: mihaisc * refine query, add relevance sort Signed-off-by: mihaisc * remove old comments Signed-off-by: mihaisc * remove white spaces from search term endings * fix filter by type * wip on filter with empty search text * sort by relevance fix * linting errors fix * lint fixes * comment sort by relevance Signed-off-by: mihaisc * lint Signed-off-by: mihaisc * search by owner and tags fix * lint err fix * fix search Signed-off-by: mihaisc Co-authored-by: claudia.holhos Co-authored-by: mihaisc --- package-lock.json | 2 + src/components/molecules/SearchBar.tsx | 12 +- .../templates/Search/filterService.tsx | 4 +- src/components/templates/Search/index.tsx | 4 +- src/components/templates/Search/sort.tsx | 8 +- src/components/templates/Search/utils.ts | 130 ++++++++++++------ 6 files changed, 108 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 744c08cc1..95f89b505 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43525,6 +43525,7 @@ "node-abort-controller": "^2.0.0", "save-file": "^2.3.1", "uuid": "^8.3.2", + "web3": "^1.3.5", "web3-eth-contract": "^1.3.6" } }, @@ -43599,6 +43600,7 @@ "integrity": "sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw==", "dev": true, "requires": { + "@oclif/config": "^1.15.1", "@oclif/errors": "^1.3.3", "@oclif/parser": "^3.8.3", "@oclif/plugin-help": "^3", diff --git a/src/components/molecules/SearchBar.tsx b/src/components/molecules/SearchBar.tsx index 78e65a95e..1dfb2c4d7 100644 --- a/src/components/molecules/SearchBar.tsx +++ b/src/components/molecules/SearchBar.tsx @@ -23,7 +23,11 @@ export default function SearchBar({ e.preventDefault() if (value === '') value = ' ' const urlEncodedValue = encodeURIComponent(value) - const url = await addExistingParamsToUrl(location, 'text') + const url = await addExistingParamsToUrl(location, [ + 'text', + 'owner', + 'tags' + ]) navigate(`${url}&text=${urlEncodedValue}`) } @@ -31,7 +35,11 @@ export default function SearchBar({ const searchParams = new URLSearchParams(window.location.href) const text = searchParams.get('text') if (text !== ('' || undefined || null)) { - const url = await addExistingParamsToUrl(location, 'text') + const url = await addExistingParamsToUrl(location, [ + 'text', + 'owner', + 'tags' + ]) navigate(`${url}&text=%20`) } } diff --git a/src/components/templates/Search/filterService.tsx b/src/components/templates/Search/filterService.tsx index 64f59b1c4..168c35fe2 100644 --- a/src/components/templates/Search/filterService.tsx +++ b/src/components/templates/Search/filterService.tsx @@ -25,7 +25,7 @@ export default function FilterPrice({ const [serviceSelections, setServiceSelections] = useState([]) async function applyServiceFilter(filterBy: string) { - let urlLocation = await addExistingParamsToUrl(location, 'serviceType') + let urlLocation = await addExistingParamsToUrl(location, ['serviceType']) if (filterBy && location.search.indexOf('&serviceType') === -1) { urlLocation = `${urlLocation}&serviceType=${filterBy}` } @@ -59,7 +59,7 @@ export default function FilterPrice({ } async function applyClearFilter() { - let urlLocation = await addExistingParamsToUrl(location, 'serviceType') + let urlLocation = await addExistingParamsToUrl(location, ['serviceType']) urlLocation = `${urlLocation}` diff --git a/src/components/templates/Search/index.tsx b/src/components/templates/Search/index.tsx index 61d7f01d6..a4d581453 100644 --- a/src/components/templates/Search/index.tsx +++ b/src/components/templates/Search/index.tsx @@ -10,7 +10,6 @@ import Sort from './sort' import { getResults } from './utils' import { navigate } from 'gatsby' import { updateQueryStringParameter } from '../../../utils' -import Loader from '../../atoms/Loader' import { useOcean } from '../../../providers/Ocean' export default function SearchPage({ @@ -33,7 +32,6 @@ export default function SearchPage({ useEffect(() => { if (!config?.metadataCacheUri) return - async function initSearch() { setLoading(true) setTotalResults(undefined) @@ -67,7 +65,7 @@ export default function SearchPage({ <>
- {(text || owner) && ( + {(text || owner || tags) && ( )}
diff --git a/src/components/templates/Search/sort.tsx b/src/components/templates/Search/sort.tsx index 41f2c67d1..09a1d3139 100644 --- a/src/components/templates/Search/sort.tsx +++ b/src/components/templates/Search/sort.tsx @@ -11,7 +11,10 @@ import classNames from 'classnames/bind' const cx = classNames.bind(styles) -const sortItems = [{ display: 'Published', value: SortTermOptions.Created }] +const sortItems = [ + // { display: 'Relevance', value: SortTermOptions.Relevance }, + { display: 'Published', value: SortTermOptions.Created } +] export default function Sort({ sortType, @@ -31,10 +34,11 @@ export default function Sort({ async function sortResults(sortBy?: string, direction?: string) { let urlLocation: string if (sortBy) { + urlLocation = await addExistingParamsToUrl(location, ['sort']) urlLocation = `${urlLocation}&sort=${sortBy}` setSortType(sortBy) } else if (direction) { - urlLocation = await addExistingParamsToUrl(location, 'sortOrder') + urlLocation = await addExistingParamsToUrl(location, ['sortOrder']) urlLocation = `${urlLocation}&sortOrder=${direction}` setSortDirection(direction) } diff --git a/src/components/templates/Search/utils.ts b/src/components/templates/Search/utils.ts index 424c8bbe8..2d4cfb9e1 100644 --- a/src/components/templates/Search/utils.ts +++ b/src/components/templates/Search/utils.ts @@ -1,12 +1,10 @@ -import { - SearchQuery, - QueryResult -} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' +import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' import { MetadataCache, Logger } from '@oceanprotocol/lib' import queryString from 'query-string' export const SortTermOptions = { - Created: 'created' + Created: 'created', + Relevance: '_score' } as const type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions] @@ -39,8 +37,11 @@ function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string { return sortTerm } -function getSortType(): string { - const sortTerm = SortTermOptions.Created +function getSortType(sortParam: string): string { + const sortTerm = + sortParam === SortTermOptions.Created + ? SortTermOptions.Created + : SortTermOptions.Relevance return sortTerm } @@ -54,9 +55,11 @@ export function getSearchQuery( sort?: string, sortOrder?: string, serviceType?: string -): SearchQuery { - const sortTerm = getSortType() +): any { + const sortTerm = getSortType(sort) const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1 + const emptySearchTerm = text === undefined || text === '' + let searchTerm = owner ? `(publicKey.owner:${owner})` : tags @@ -67,41 +70,84 @@ export function getSearchQuery( `(service.attributes.additionalInformation.categories:\"${categories}\")` : text || '' - // HACK: resolves the case sensitivity related to dataTokenInfo.symbol - searchTerm = '*' + searchTerm.toUpperCase() + '*' + searchTerm = searchTerm.trim() + let modifiedSearchTerm = searchTerm.split(' ').join(' OR ').trim() + modifiedSearchTerm = addTypeFilterToQuery(modifiedSearchTerm, serviceType) searchTerm = addTypeFilterToQuery(searchTerm, serviceType) + const prefixedSearchTerm = + emptySearchTerm && searchTerm + ? searchTerm + : !emptySearchTerm && searchTerm + ? '*' + searchTerm + '*' + : '**' return { page: Number(page) || 1, offset: Number(offset) || 21, query: { - query_string: { - query: `${searchTerm} -isInPurgatory:true`, - fields: [ - 'dataTokenInfo.name', - 'dataTokenInfo.symbol', - 'service.attributes.main.name', - 'service.attributes.main.author', - 'service.attributes.additionalInformation.description' - ], - default_operator: 'AND' + bool: { + must: [ + { + bool: { + should: [ + { + query_string: { + query: `${modifiedSearchTerm}`, + fields: [ + 'id', + 'publicKey.owner', + 'dataToken', + 'dataTokenInfo.name', + 'dataTokenInfo.symbol', + 'service.attributes.main.name^10', + 'service.attributes.main.author', + 'service.attributes.additionalInformation.description', + 'service.attributes.additionalInformation.tags' + ], + minimum_should_match: '2<75%', + phrase_slop: 2, + boost: 5 + } + }, + { + match_phrase: { + content: { + query: `${searchTerm}`, + boost: 10 + } + } + }, + { + query_string: { + query: `${prefixedSearchTerm}`, + fields: [ + 'id', + 'publicKey.owner', + 'dataToken', + 'dataTokenInfo.name', + 'dataTokenInfo.symbol', + 'service.attributes.main.name', + 'service.attributes.main.author', + 'service.attributes.additionalInformation.description', + 'service.attributes.additionalInformation.tags' + ], + default_operator: 'AND' + } + } + ] + } + }, + { + term: { + isInPurgatory: false + } + } + ] } - // ...(owner && { 'publicKey.owner': [owner] }), - // ...(tags && { tags: [tags] }), - // ...(categories && { categories: [categories] }) }, sort: { [sortTerm]: sortValue } - - // Something in ocean.js is weird when using 'tags: [tag]' - // which is the only way the query actually returns desired results. - // But it doesn't follow 'SearchQuery' interface so we have to assign - // it here. - // } as SearchQuery - - // And the next hack, - // nativeSearch is not implmeneted on ocean.js typings } } @@ -123,9 +169,9 @@ export async function getResults( text, owner, tags, + categories, page, offset, - categories, sort, sortOrder, serviceType @@ -143,25 +189,20 @@ export async function getResults( sortOrder, serviceType ) - const queryResult = await metadataCache.queryMetadata(searchQuery) return queryResult } export async function addExistingParamsToUrl( location: Location, - excludedParam: string, - secondExcludedParam?: string + excludedParams: string[] ): Promise { const parsed = queryString.parse(location.search) let urlLocation = '/search?' if (Object.keys(parsed).length > 0) { for (const querryParam in parsed) { - if ( - querryParam !== excludedParam && - querryParam !== secondExcludedParam - ) { - if (querryParam === 'page' && excludedParam === 'text') { + if (!excludedParams.includes(querryParam)) { + if (querryParam === 'page' && excludedParams.includes('text')) { Logger.log('remove page when starting a new search') } else { const value = parsed[querryParam] @@ -170,7 +211,10 @@ export async function addExistingParamsToUrl( } } } else { - urlLocation = `${urlLocation}sort=${SortTermOptions.Created}&sortOrder=${SortValueOptions.Descending}&` + // sort should be relevance when fixed in aqua + urlLocation = `${urlLocation}sort=${encodeURIComponent( + SortTermOptions.Created + )}&sortOrder=${SortValueOptions.Descending}&` } urlLocation = urlLocation.slice(0, -1) return urlLocation From e02babf2c2fd80ae67e686fb026406bc4dfcfac3 Mon Sep 17 00:00:00 2001 From: Kris Liew <39853992+krisliew@users.noreply.github.com> Date: Wed, 16 Jun 2021 09:32:11 +0800 Subject: [PATCH 3/9] [EPIC] Free Pricing (#681) * Free Pricing Option at create Pricing (#621) * Free Pricing Option + env var toggle * Create Pricing step msg * Default 'allowFreePricing' to true temp for review * Fix price 0 on free tab * Attempt fix useSiteMetadata * Fix linting * Feature/free price support consume compute (#654) * Update fetch free price * Feedback change UI remove 0's * update button msg && fix * compute algorithm list show 'Free' instead of '0' * updateMetadata() v3 workaround solution for free pricing (#677) * compute algorithm list show 'Free' instead of '0' * workaround editMetaData free price * utils function for compute & download * `allowFreePricing` default to false --- .env.example | 1 + apollo.config.js | 3 +- app.config.js | 3 +- content/price.json | 4 + src/components/atoms/ButtonBuy.tsx | 10 ++- src/components/atoms/Price/PriceUnit.tsx | 23 +++-- src/components/atoms/Price/index.tsx | 2 +- .../molecules/FormFields/AssetSelection.tsx | 7 +- .../Compute/FormComputeDataset.tsx | 2 + .../organisms/AssetActions/Consume.tsx | 1 + .../AssetActions/Edit/EditComputeDataset.tsx | 24 ++++- .../organisms/AssetActions/Edit/index.tsx | 22 +++++ .../organisms/AssetActions/index.tsx | 1 + .../Pricing/FormPricing/Free.module.css | 3 + .../AssetContent/Pricing/FormPricing/Free.tsx | 21 +++++ .../Pricing/FormPricing/Price.tsx | 36 +++++--- .../Pricing/FormPricing/index.module.css | 5 ++ .../Pricing/FormPricing/index.tsx | 8 ++ .../organisms/AssetContent/Pricing/index.tsx | 4 + src/hooks/usePricing.ts | 53 +++++++++-- src/hooks/useSiteMetadata.ts | 2 + src/models/FormPricing.ts | 2 +- src/utils/feedback.ts | 20 +++++ src/utils/freePrice.ts | 37 ++++++++ src/utils/subgraph.ts | 88 +++++++++++++++++-- 25 files changed, 341 insertions(+), 41 deletions(-) create mode 100644 src/components/organisms/AssetContent/Pricing/FormPricing/Free.module.css create mode 100644 src/components/organisms/AssetContent/Pricing/FormPricing/Free.tsx create mode 100644 src/utils/freePrice.ts diff --git a/.env.example b/.env.example index 91f93b378..a44ef8bbb 100644 --- a/.env.example +++ b/.env.example @@ -11,5 +11,6 @@ GATSBY_NETWORK="rinkeby" #GATSBY_PORTIS_ID="xxx" #GATSBY_ALLOW_FIXED_PRICING="true" #GATSBY_ALLOW_DYNAMIC_PRICING="true" +#GATSBY_ALLOW_FREE_PRICING="true" #GATSBY_ALLOW_ADVANCED_SETTINGS="true" #GATSBY_CREDENTIAL_TYPE="address" diff --git a/apollo.config.js b/apollo.config.js index 8c4500450..3ca33ccad 100644 --- a/apollo.config.js +++ b/apollo.config.js @@ -2,8 +2,7 @@ module.exports = { client: { service: { name: 'ocean', - url: - 'https://subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph', + url: 'https://subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph', // optional disable SSL validation check skipSSLValidation: true } diff --git a/app.config.js b/app.config.js index 129d2076f..f24dab256 100644 --- a/app.config.js +++ b/app.config.js @@ -41,10 +41,11 @@ module.exports = { // Wallets portisId: process.env.GATSBY_PORTIS_ID || 'xxx', - // Used to show or hide the fixed and dynamic price options + // Used to show or hide the fixed, dynamic or free price options // tab to publishers during the price creation. allowFixedPricing: process.env.GATSBY_ALLOW_FIXED_PRICING || 'true', allowDynamicPricing: process.env.GATSBY_ALLOW_DYNAMIC_PRICING || 'true', + allowFreePricing: process.env.GATSBY_ALLOW_FREE_PRICING || 'false', // Used to show or hide advanced settings button in asset details page allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false', diff --git a/content/price.json b/content/price.json index 4c3f278b0..517d74a75 100644 --- a/content/price.json +++ b/content/price.json @@ -21,6 +21,10 @@ "communityFee": "Explain community fee...", "marketplaceFee": "Explain marketplace fee..." } + }, + "free": { + "title": "Free", + "info": "Set your data set as free. The datatoken for this data set will be given for free via creating a faucet." } }, "pool": { diff --git a/src/components/atoms/ButtonBuy.tsx b/src/components/atoms/ButtonBuy.tsx index 6bdcec09e..50acd944d 100644 --- a/src/components/atoms/ButtonBuy.tsx +++ b/src/components/atoms/ButtonBuy.tsx @@ -21,6 +21,8 @@ interface ButtonBuyProps { onClick?: (e: FormEvent) => void stepText?: string type?: 'submit' + priceType?: string + algorithmPriceType?: string } function getConsumeHelpText( @@ -87,15 +89,21 @@ export default function ButtonBuy({ onClick, stepText, isLoading, - type + type, + priceType, + algorithmPriceType }: ButtonBuyProps): ReactElement { const buttonText = action === 'download' ? hasPreviousOrder ? 'Download' + : priceType === 'free' + ? 'Get' : `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}` : hasPreviousOrder && hasPreviousOrderSelectedComputeAsset ? 'Start Compute Job' + : priceType === 'free' && algorithmPriceType === 'free' + ? 'Order Compute Job' : `Buy Compute Job` return ( diff --git a/src/components/atoms/Price/PriceUnit.tsx b/src/components/atoms/Price/PriceUnit.tsx index 7d9e3bccd..a1f3bf54f 100644 --- a/src/components/atoms/Price/PriceUnit.tsx +++ b/src/components/atoms/Price/PriceUnit.tsx @@ -42,15 +42,20 @@ export default function PriceUnit({ return (
-
- {Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '} - {symbol || 'OCEAN'} - {type && type === 'pool' && ( - - )} -
- - {conversion && } + {type && type === 'free' ? ( +
Free
+ ) : ( + <> +
+ {Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '} + {symbol || 'OCEAN'} + {type && type === 'pool' && ( + + )} +
+ {conversion && } + + )}
) } diff --git a/src/components/atoms/Price/index.tsx b/src/components/atoms/Price/index.tsx index 18082436f..940aca870 100644 --- a/src/components/atoms/Price/index.tsx +++ b/src/components/atoms/Price/index.tsx @@ -16,7 +16,7 @@ export default function Price({ small?: boolean conversion?: boolean }): ReactElement { - return price?.value ? ( + return price?.value || price?.type === 'free' ? ( - +
)) )} diff --git a/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx b/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx index 8bae8b2eb..36906a061 100644 --- a/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx +++ b/src/components/organisms/AssetActions/Compute/FormComputeDataset.tsx @@ -166,6 +166,8 @@ export default function FormStartCompute({ stepText={stepText} isLoading={isLoading} type="submit" + priceType={price?.type} + algorithmPriceType={algorithmPrice?.type} /> ) diff --git a/src/components/organisms/AssetActions/Consume.tsx b/src/components/organisms/AssetActions/Consume.tsx index 3dc526a7a..73055be6c 100644 --- a/src/components/organisms/AssetActions/Consume.tsx +++ b/src/components/organisms/AssetActions/Consume.tsx @@ -160,6 +160,7 @@ export default function Consume({ assetType={type} stepText={consumeStepText || pricingStepText} isLoading={pricingIsLoading || isLoading} + priceType={price?.type} /> ) diff --git a/src/components/organisms/AssetActions/Edit/EditComputeDataset.tsx b/src/components/organisms/AssetActions/Edit/EditComputeDataset.tsx index 8af2bbe37..1c65aaa70 100644 --- a/src/components/organisms/AssetActions/Edit/EditComputeDataset.tsx +++ b/src/components/organisms/AssetActions/Edit/EditComputeDataset.tsx @@ -16,6 +16,10 @@ import { useUserPreferences } from '../../../../providers/UserPreferences' import DebugEditCompute from './DebugEditCompute' import styles from './index.module.css' import { transformComputeFormToServiceComputePrivacy } from '../../../../utils/compute' +import { + setMinterToDispenser, + setMinterToPublisher +} from '../../../../utils/freePrice' const contentQuery = graphql` query EditComputeDataQuery { @@ -62,7 +66,7 @@ export default function EditComputeDataset({ const { debug } = useUserPreferences() const { ocean } = useOcean() const { accountId } = useWeb3() - const { ddo, refreshDdo } = useAsset() + const { ddo, refreshDdo, price } = useAsset() const [success, setSuccess] = useState() const [error, setError] = useState() @@ -73,6 +77,15 @@ export default function EditComputeDataset({ resetForm: () => void ) { try { + if (price.type === 'free') { + const tx = await setMinterToPublisher( + ocean, + ddo.dataToken, + accountId, + setError + ) + if (!tx) return + } const privacy = await transformComputeFormToServiceComputePrivacy( values, ocean @@ -99,6 +112,15 @@ export default function EditComputeDataset({ Logger.error(content.form.error) return } else { + if (price.type === 'free') { + const tx = await setMinterToDispenser( + ocean, + ddo.dataToken, + accountId, + setError + ) + if (!tx) return + } // Edit succeeded setSuccess(content.form.success) resetForm() diff --git a/src/components/organisms/AssetActions/Edit/index.tsx b/src/components/organisms/AssetActions/Edit/index.tsx index dfafa3a6f..a513e4854 100644 --- a/src/components/organisms/AssetActions/Edit/index.tsx +++ b/src/components/organisms/AssetActions/Edit/index.tsx @@ -18,6 +18,10 @@ import MetadataFeedback from '../../../molecules/MetadataFeedback' import { graphql, useStaticQuery } from 'gatsby' import { useWeb3 } from '../../../../providers/Web3' import { useOcean } from '../../../../providers/Ocean' +import { + setMinterToDispenser, + setMinterToPublisher +} from '../../../../utils/freePrice' const contentQuery = graphql` query EditMetadataQuery { @@ -88,6 +92,15 @@ export default function Edit({ resetForm: () => void ) { try { + if (price.type === 'free') { + const tx = await setMinterToPublisher( + ocean, + ddo.dataToken, + accountId, + setError + ) + if (!tx) return + } // Construct new DDO with new values const ddoEditedMetdata = await ocean.assets.editMetadata(ddo, { title: values.name, @@ -132,6 +145,15 @@ export default function Edit({ Logger.error(content.form.error) return } else { + if (price.type === 'free') { + const tx = await setMinterToDispenser( + ocean, + ddo.dataToken, + accountId, + setError + ) + if (!tx) return + } // Edit succeeded setSuccess(content.form.success) resetForm() diff --git a/src/components/organisms/AssetActions/index.tsx b/src/components/organisms/AssetActions/index.tsx index 03c916354..819cc4c55 100644 --- a/src/components/organisms/AssetActions/index.tsx +++ b/src/components/organisms/AssetActions/index.tsx @@ -65,6 +65,7 @@ export default function AssetActions(): ReactElement { // Check user balance against price useEffect(() => { + if (price?.type === 'free') setIsBalanceSufficient(true) if (!price?.value || !account || !balance?.ocean || !dtBalance) return setIsBalanceSufficient( diff --git a/src/components/organisms/AssetContent/Pricing/FormPricing/Free.module.css b/src/components/organisms/AssetContent/Pricing/FormPricing/Free.module.css new file mode 100644 index 000000000..5dcdab375 --- /dev/null +++ b/src/components/organisms/AssetContent/Pricing/FormPricing/Free.module.css @@ -0,0 +1,3 @@ +.free { + composes: content from './index.module.css'; +} diff --git a/src/components/organisms/AssetContent/Pricing/FormPricing/Free.tsx b/src/components/organisms/AssetContent/Pricing/FormPricing/Free.tsx new file mode 100644 index 000000000..70115699b --- /dev/null +++ b/src/components/organisms/AssetContent/Pricing/FormPricing/Free.tsx @@ -0,0 +1,21 @@ +import React, { ReactElement } from 'react' +import stylesIndex from './index.module.css' +import styles from './Free.module.css' +import FormHelp from '../../../../atoms/Input/Help' +import { DDO } from '@oceanprotocol/lib' +import Price from './Price' + +export default function Free({ + ddo, + content +}: { + ddo: DDO + content: any +}): ReactElement { + return ( +
+ {content.info} + +
+ ) +} diff --git a/src/components/organisms/AssetContent/Pricing/FormPricing/Price.tsx b/src/components/organisms/AssetContent/Pricing/FormPricing/Price.tsx index ad2669e7b..17576ceff 100644 --- a/src/components/organisms/AssetContent/Pricing/FormPricing/Price.tsx +++ b/src/components/organisms/AssetContent/Pricing/FormPricing/Price.tsx @@ -10,10 +10,12 @@ import usePricing from '../../../../../hooks/usePricing' export default function Price({ ddo, - firstPrice + firstPrice, + free }: { ddo: DDO firstPrice?: string + free?: boolean }): ReactElement { const [field, meta] = useField('price') const { getDTName, getDTSymbol } = usePricing() @@ -38,17 +40,27 @@ export default function Price({
- - } - /> + {free ? ( + + ) : ( + + } + /> + )}
diff --git a/src/components/organisms/AssetContent/Pricing/FormPricing/index.module.css b/src/components/organisms/AssetContent/Pricing/FormPricing/index.module.css index 22058c782..c8002d1f9 100644 --- a/src/components/organisms/AssetContent/Pricing/FormPricing/index.module.css +++ b/src/components/organisms/AssetContent/Pricing/FormPricing/index.module.css @@ -45,3 +45,8 @@ padding-left: var(--spacer); padding-right: var(--spacer); } + +.free { + text-align: center; + margin-bottom: calc(var(--spacer) / 1.5); +} diff --git a/src/components/organisms/AssetContent/Pricing/FormPricing/index.tsx b/src/components/organisms/AssetContent/Pricing/FormPricing/index.tsx index 64cb0755b..a64e84573 100644 --- a/src/components/organisms/AssetContent/Pricing/FormPricing/index.tsx +++ b/src/components/organisms/AssetContent/Pricing/FormPricing/index.tsx @@ -3,6 +3,7 @@ import styles from './index.module.css' import Tabs from '../../../../atoms/Tabs' import Fixed from './Fixed' import Dynamic from './Dynamic' +import Free from './Free' import { useFormikContext } from 'formik' import { useUserPreferences } from '../../../../../providers/UserPreferences' import { PriceOptionsMarket } from '../../../../../@types/MetaData' @@ -33,6 +34,7 @@ export default function FormPricing({ const type = tabName.toLowerCase() setFieldValue('type', type) type === 'fixed' && setFieldValue('dtAmount', 1000) + type === 'free' && price < 1 && setFieldValue('price', 1) } // Always update everything when price value changes @@ -57,6 +59,12 @@ export default function FormPricing({ title: content.dynamic.title, content: } + : undefined, + appConfig.allowFreePricing === 'true' + ? { + title: content.free.title, + content: + } : undefined ].filter((tab) => tab !== undefined) diff --git a/src/components/organisms/AssetContent/Pricing/index.tsx b/src/components/organisms/AssetContent/Pricing/index.tsx index c69552db3..b33aaafbb 100644 --- a/src/components/organisms/AssetContent/Pricing/index.tsx +++ b/src/components/organisms/AssetContent/Pricing/index.tsx @@ -40,6 +40,10 @@ const query = graphql` marketplaceFee } } + free { + title + info + } } } } diff --git a/src/hooks/usePricing.ts b/src/hooks/usePricing.ts index c01d1104b..cc956c279 100644 --- a/src/hooks/usePricing.ts +++ b/src/hooks/usePricing.ts @@ -5,7 +5,9 @@ import { Decimal } from 'decimal.js' import { getCreatePricingPoolFeedback, getCreatePricingExchangeFeedback, - getBuyDTFeedback + getBuyDTFeedback, + getCreateFreePricingFeedback, + getDispenseFeedback } from '../utils/feedback' import { sleep } from '../utils' @@ -16,7 +18,7 @@ interface PriceOptions { price: number dtAmount: number oceanAmount: number - type: 'fixed' | 'dynamic' | string + type: 'fixed' | 'dynamic' | 'free' | string weightOnDataToken: string swapFee: string } @@ -68,7 +70,7 @@ function usePricing(): UsePricing { // Helper for setting steps & feedback for all flows async function setStep( index: number, - type: 'pool' | 'exchange' | 'buy', + type: 'pool' | 'exchange' | 'free' | 'buy' | 'dispense', ddo: DDO ) { const dtSymbol = await getDTSymbol(ddo) @@ -84,9 +86,15 @@ function usePricing(): UsePricing { case 'exchange': messages = getCreatePricingExchangeFeedback(dtSymbol) break + case 'free': + messages = getCreateFreePricingFeedback(dtSymbol) + break case 'buy': messages = getBuyDTFeedback(dtSymbol) break + case 'dispense': + messages = getDispenseFeedback(dtSymbol) + break } setPricingStepText(messages[index]) @@ -180,6 +188,28 @@ function usePricing(): UsePricing { Logger.log('DT exchange buy response', tx) break } + case 'free': { + setStep(1, 'dispense', ddo) + const isDispensable = await ocean.OceanDispenser.isDispensable( + ddo.dataToken, + accountId, + '1' + ) + + if (!isDispensable) { + Logger.error(`Dispenser for ${ddo.dataToken} failed to dispense`) + return + } + + tx = await ocean.OceanDispenser.dispense( + ddo.dataToken, + accountId, + '1' + ) + setStep(2, 'dispense', ddo) + Logger.log('DT dispense response', tx) + break + } } } catch (error) { setPricingError(error.message) @@ -219,9 +249,14 @@ function usePricing(): UsePricing { setStep(99, 'pool', ddo) try { - // if fixedPrice set dt to max amount - if (!isPool) dtAmount = 1000 - await mint(`${dtAmount}`, ddo) + if (type === 'free') { + setStep(99, 'free', ddo) + await ocean.OceanDispenser.activate(dataToken, '1', '1', accountId) + } else { + // if fixedPrice set dt to max amount + if (!isPool) dtAmount = 1000 + await mint(`${dtAmount}`, ddo) + } // dtAmount for fixed price is set to max const tx = isPool @@ -235,9 +270,13 @@ function usePricing(): UsePricing { swapFee ) .next((step: number) => setStep(step, 'pool', ddo)) - : await ocean.fixedRateExchange + : type === 'fixed' + ? await ocean.fixedRateExchange .create(dataToken, `${price}`, accountId, `${dtAmount}`) .next((step: number) => setStep(step, 'exchange', ddo)) + : await ocean.OceanDispenser.makeMinter(dataToken, accountId).next( + (step: number) => setStep(step, 'free', ddo) + ) await sleep(20000) return tx } catch (error) { diff --git a/src/hooks/useSiteMetadata.ts b/src/hooks/useSiteMetadata.ts index 30ed596bd..b33ed34b1 100644 --- a/src/hooks/useSiteMetadata.ts +++ b/src/hooks/useSiteMetadata.ts @@ -27,6 +27,7 @@ interface UseSiteMetadata { portisId: string allowFixedPricing: string allowDynamicPricing: string + allowFreePricing: string allowAdvancedSettings: string credentialType: string } @@ -61,6 +62,7 @@ const query = graphql` portisId allowFixedPricing allowDynamicPricing + allowFreePricing allowAdvancedSettings credentialType } diff --git a/src/models/FormPricing.ts b/src/models/FormPricing.ts index 8ad89189d..c9ad3aac5 100644 --- a/src/models/FormPricing.ts +++ b/src/models/FormPricing.ts @@ -13,7 +13,7 @@ export const validationSchema: Yup.SchemaOf = .min(21, (param) => `Must be more or equal to ${param.min}`) .required('Required'), type: Yup.string() - .matches(/fixed|dynamic/g, { excludeEmptyString: true }) + .matches(/fixed|dynamic|free/g, { excludeEmptyString: true }) .required('Required'), weightOnDataToken: Yup.string().required('Required'), weightOnOcean: Yup.string().required('Required'), diff --git a/src/utils/feedback.ts b/src/utils/feedback.ts index 511f4c7b2..ab440966e 100644 --- a/src/utils/feedback.ts +++ b/src/utils/feedback.ts @@ -52,6 +52,17 @@ export function getCreatePricingExchangeFeedback(dtSymbol: string): { } } +export function getCreateFreePricingFeedback(dtSymbol: string): { + [key: number]: string +} { + return { + 99: `Creating ${dtSymbol} faucet...`, + 0: 'Setting faucet as minter ...', + 1: 'Approving minter...', + 2: 'Faucet created.' + } +} + export function getBuyDTFeedback(dtSymbol: string): { [key: number]: string } { return { 1: '1/3 Approving OCEAN ...', @@ -67,3 +78,12 @@ export function getSellDTFeedback(dtSymbol: string): { [key: number]: string } { 3: `3/3 ${dtSymbol} sold.` } } + +export function getDispenseFeedback(dtSymbol: string): { + [key: number]: string +} { + return { + 1: `1/2 Requesting ${dtSymbol}...`, + 2: `2/2 Received ${dtSymbol}.` + } +} diff --git a/src/utils/freePrice.ts b/src/utils/freePrice.ts new file mode 100644 index 000000000..68e6de649 --- /dev/null +++ b/src/utils/freePrice.ts @@ -0,0 +1,37 @@ +import { Logger, Ocean } from '@oceanprotocol/lib' + +export async function setMinterToPublisher( + ocean: Ocean, + dataTokenAddress: string, + accountId: string, + setError: (msg: string) => void +): Promise { + // free pricing v3 workaround part1 + const response = await ocean.OceanDispenser.cancelMinter( + dataTokenAddress, + accountId + ) + if (!response) { + setError('Updating DDO failed.') + Logger.error('Failed at cancelMinter') + } + return response +} + +export async function setMinterToDispenser( + ocean: Ocean, + dataTokenAddress: string, + accountId: string, + setError: (msg: string) => void +): Promise { + // free pricing v3 workaround part2 + const response = await ocean.OceanDispenser.makeMinter( + dataTokenAddress, + accountId + ) + if (!response) { + setError('Updating DDO failed.') + Logger.error('Failed at makeMinter') + } + return response +} diff --git a/src/utils/subgraph.ts b/src/utils/subgraph.ts index c5408ee53..115f8c432 100644 --- a/src/utils/subgraph.ts +++ b/src/utils/subgraph.ts @@ -10,6 +10,10 @@ import { AssetsFrePrice_fixedRateExchanges as AssetsFrePriceFixedRateExchanges } from '../@types/apollo/AssetsFrePrice' import { AssetPreviousOrder } from '../@types/apollo/AssetPreviousOrder' +import { + AssetsFreePrice, + AssetsFreePrice_dispensers as AssetFreePriceDispenser +} from '../@types/apollo/AssetsFreePrice' import web3 from 'web3' export interface PriceList { @@ -25,6 +29,36 @@ interface DidAndDatatokenMap { [name: string]: string } +const FreeQuery = gql` + query AssetsFreePrice($datatoken_in: [String!]) { + dispensers(orderBy: id, where: { datatoken_in: $datatoken_in }) { + datatoken { + id + address + } + } + } +` + +const AssetFreeQuery = gql` + query AssetFreePrice($datatoken: String) { + dispensers(orderBy: id, where: { datatoken: $datatoken }) { + active + owner { + id + } + minterApproved + isTrueMinter + maxTokens + maxBalance + balance + datatoken { + id + } + } + } +` + const FreQuery = gql` query AssetsFrePrice($datatoken_in: [String!]) { fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) { @@ -146,7 +180,8 @@ export async function getPreviousOrders( function transformPriceToBestPrice( frePrice: AssetsFrePriceFixedRateExchanges[], - poolPrice: AssetsPoolPricePools[] + poolPrice: AssetsPoolPricePools[], + freePrice: AssetFreePriceDispenser[] ) { if (poolPrice?.length > 0) { const price: BestPrice = { @@ -176,6 +211,18 @@ function transformPriceToBestPrice( isConsumable: 'true' } return price + } else if (freePrice?.length > 0) { + const price: BestPrice = { + type: 'free', + value: 0, + address: freePrice[0]?.datatoken.id, + exchange_id: '', + ocean: 0, + datatoken: 0, + pools: [], + isConsumable: 'true' + } + return price } else { const price: BestPrice = { type: '', @@ -197,6 +244,7 @@ async function getAssetsPoolsExchangesAndDatatokenMap( [ ApolloQueryResult, ApolloQueryResult, + ApolloQueryResult, DidAndDatatokenMap ] > { @@ -214,6 +262,10 @@ async function getAssetsPoolsExchangesAndDatatokenMap( datatokenAddress_in: dataTokenList } + const freeVariables = { + datatoken_in: dataTokenList + } + const poolPriceResponse: ApolloQueryResult = await fetchData( PoolQuery, poolVariables @@ -223,7 +275,12 @@ async function getAssetsPoolsExchangesAndDatatokenMap( freVariables ) - return [poolPriceResponse, frePriceResponse, didDTMap] + const freePriceResponse: ApolloQueryResult = await fetchData( + FreeQuery, + freeVariables + ) + + return [poolPriceResponse, frePriceResponse, freePriceResponse, didDTMap] } export async function getAssetsPriceList(assets: DDO[]): Promise { @@ -232,11 +289,13 @@ export async function getAssetsPriceList(assets: DDO[]): Promise { const values: [ ApolloQueryResult, ApolloQueryResult, + ApolloQueryResult, DidAndDatatokenMap ] = await getAssetsPoolsExchangesAndDatatokenMap(assets) const poolPriceResponse = values[0] const frePriceResponse = values[1] - const didDTMap: DidAndDatatokenMap = values[2] + const freePriceResponse = values[2] + const didDTMap: DidAndDatatokenMap = values[3] for (const poolPrice of poolPriceResponse.data?.pools) { priceList[didDTMap[poolPrice.datatokenAddress]] = @@ -247,6 +306,9 @@ export async function getAssetsPriceList(assets: DDO[]): Promise { for (const frePrice of frePriceResponse.data?.fixedRateExchanges) { priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate } + for (const freePrice of freePriceResponse.data?.dispensers) { + priceList[didDTMap[freePrice.datatoken?.address]] = '0' + } return priceList } @@ -259,6 +321,10 @@ export async function getPrice(asset: DDO): Promise { datatokenAddress: asset?.dataToken.toLowerCase() } + const freeVariables = { + datatoken: asset?.dataToken.toLowerCase() + } + const poolPriceResponse: ApolloQueryResult = await fetchData( AssetPoolPriceQuerry, poolVariables @@ -267,10 +333,15 @@ export async function getPrice(asset: DDO): Promise { AssetFreQuery, freVariables ) + const freePriceResponse: ApolloQueryResult = await fetchData( + AssetFreeQuery, + freeVariables + ) const bestPrice: BestPrice = transformPriceToBestPrice( frePriceResponse.data.fixedRateExchanges, - poolPriceResponse.data.pools + poolPriceResponse.data.pools, + freePriceResponse.data.dispensers ) return bestPrice @@ -284,15 +355,18 @@ export async function getAssetsBestPrices( const values: [ ApolloQueryResult, ApolloQueryResult, + ApolloQueryResult, DidAndDatatokenMap ] = await getAssetsPoolsExchangesAndDatatokenMap(assets) const poolPriceResponse = values[0] const frePriceResponse = values[1] + const freePriceResponse = values[2] for (const ddo of assets) { const dataToken = ddo.dataToken.toLowerCase() const poolPrice: AssetsPoolPricePools[] = [] const frePrice: AssetsFrePriceFixedRateExchanges[] = [] + const freePrice: AssetFreePriceDispenser[] = [] const pool = poolPriceResponse.data?.pools.find( (pool: any) => pool.datatokenAddress === dataToken ) @@ -301,7 +375,11 @@ export async function getAssetsBestPrices( (fre: any) => fre.datatoken.address === dataToken ) fre && frePrice.push(fre) - const bestPrice = transformPriceToBestPrice(frePrice, poolPrice) + const free = freePriceResponse.data?.dispensers.find( + (free: any) => free.datatoken.address === dataToken + ) + free && freePrice.push(free) + const bestPrice = transformPriceToBestPrice(frePrice, poolPrice, freePrice) assetsWithPrice.push({ ddo: ddo, price: bestPrice From 04d505da42f84839e752b4ffa5d4f3943f563faf Mon Sep 17 00:00:00 2001 From: mihaisc Date: Wed, 16 Jun 2021 19:49:40 +0300 Subject: [PATCH 4/9] remove fileInfo check, cleanup (#689) Signed-off-by: mihaisc --- .../organisms/AssetActions/index.tsx | 41 ++++++++++--------- src/utils/cleanupContentType.ts | 4 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/components/organisms/AssetActions/index.tsx b/src/components/organisms/AssetActions/index.tsx index 819cc4c55..ed5bec4d5 100644 --- a/src/components/organisms/AssetActions/index.tsx +++ b/src/components/organisms/AssetActions/index.tsx @@ -11,7 +11,7 @@ import Trade from './Trade' import { useAsset } from '../../../providers/Asset' import { useOcean } from '../../../providers/Ocean' import { useWeb3 } from '../../../providers/Web3' -import { getFileInfo } from '../../../utils/provider' +import { fileinfo, getFileInfo } from '../../../utils/provider' import axios from 'axios' export default function AssetActions(): ReactElement { @@ -26,24 +26,27 @@ export default function AssetActions(): ReactElement { const isCompute = Boolean(ddo?.findServiceByType('compute')) useEffect(() => { - if (!config) return - const source = axios.CancelToken.source() - async function initFileInfo() { - setFileIsLoading(true) - try { - const fileInfo = await getFileInfo( - DID.parse(`${ddo.id}`), - config.providerUri, - source.token - ) - setFileMetadata(fileInfo.data[0]) - } catch (error) { - Logger.error(error.message) - } finally { - setFileIsLoading(false) - } - } - initFileInfo() + const { attributes } = ddo.findServiceByType('metadata') + setFileMetadata(attributes.main.files[0]) + // !!!!! do not remove this, we will enable this again after fileInfo endpoint is fixed !!! + // if (!config) return + // const source = axios.CancelToken.source() + // async function initFileInfo() { + // setFileIsLoading(true) + // try { + // const fileInfo = await getFileInfo( + // DID.parse(`${ddo.id}`), + // config.providerUri, + // source.token + // ) + // setFileMetadata(fileInfo.data[0]) + // } catch (error) { + // Logger.error(error.message) + // } finally { + // setFileIsLoading(false) + // } + // } + // initFileInfo() }, [config, ddo.id]) // Get and set user DT balance diff --git a/src/utils/cleanupContentType.ts b/src/utils/cleanupContentType.ts index 6e1060c52..66cd920d4 100644 --- a/src/utils/cleanupContentType.ts +++ b/src/utils/cleanupContentType.ts @@ -1,6 +1,8 @@ const cleanupContentType = (contentType: string): string => { + // strip away the `charset=utf-8` + const contentSplit = contentType.split(';')[0] // strip away the 'application/' part - const contentTypeSplit = contentType.split('/')[1] + const contentTypeSplit = contentSplit.split('/')[1] if (!contentTypeSplit) return contentType From 00bce63b8dab776089175c24b88922f6d246e2ca Mon Sep 17 00:00:00 2001 From: Jamie Hewitt Date: Tue, 22 Jun 2021 13:16:27 +0300 Subject: [PATCH 5/9] Adding advanced documentation (#687) * Creating advance documentation file * adding descriptions of advance features * fixing small issues * update RBAC instructions * update RBAC instructions * integrating feedback * reorder example env vars, document in place * integrating feedback * additional instructions on RBAC settings * fixing contents links Co-authored-by: Matthias Kretschmann --- .env.example | 20 +++++++++++++++----- README.md | 11 +++++++++++ docs/advancedSettings.md | 29 +++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 docs/advancedSettings.md diff --git a/.env.example b/.env.example index a44ef8bbb..a9835e185 100644 --- a/.env.example +++ b/.env.example @@ -2,15 +2,25 @@ # "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha" GATSBY_NETWORK="rinkeby" -## Define a GATSBY_RBAC_URL to implement permission based restrictions -#GATSBY_RBAC_URL="http://localhost:3000" - #GATSBY_INFURA_PROJECT_ID="xxx" #GATSBY_MARKET_FEE_ADDRESS="0xxx" -#GATSBY_ANALYTICS_ID="xxx" #GATSBY_PORTIS_ID="xxx" + + +# +# ADVANCED SETTINGS +# + +# Toggle pricing options presented during price creation #GATSBY_ALLOW_FIXED_PRICING="true" #GATSBY_ALLOW_DYNAMIC_PRICING="true" -#GATSBY_ALLOW_FREE_PRICING="true" +#GATSBY_ALLOW_FREE_PRICING="false" + +# Define RBAC server URL to implement permission based restrictions +#GATSBY_RBAC_URL="http://localhost:3000" + +# Enables another asset editing button holder further advanced settings #GATSBY_ALLOW_ADVANCED_SETTINGS="true" + +# Allow/Deny Lists #GATSBY_CREDENTIAL_TYPE="address" diff --git a/README.md b/README.md index c7b73fc41..4ffc0f81f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - [⬆️ Deployment](#️-deployment) - [💖 Contributing](#-contributing) - [🍴 Forking](#-forking) +- [💻 Advanced Features](#-advanced-features) - [🏛 License](#-license) ## 🏄 Get Started @@ -374,6 +375,16 @@ Additionally, we would also advise that your retain the text saying "Powered by Everything else is made open according to the apache2 license. We look forward to seeing your data marketplace! +## 💻 Advanced Features + +Ocean Market also includes a number of advanced features that are suitable for an enterprise data market, such as: + +- Role based access control +- Allow and deny lists +- Free pricing + +[See our seperate guide on advanced features](docs/advancedSettings.md) + ## 🏛 License ```text diff --git a/docs/advancedSettings.md b/docs/advancedSettings.md new file mode 100644 index 000000000..572cab143 --- /dev/null +++ b/docs/advancedSettings.md @@ -0,0 +1,29 @@ +# Advanced Settings + +**Table of Contents** + +- [Role based Access Control](#rbac-settings) +- [Allow and Deny lists](#allow-and-deny-list-settings) +- [Free Pricing](#free-pricing-settings) + +## RBAC settings + +- Setup and host the Ocean role based access control (RBAC) server. Follow the instructions in the [RBAC repository](https://github.com/oceanprotocol/RBAC-Server) +- The RBAC server can store roles in [Keycloak](https://www.keycloak.org/) or a json file. +- In your .env file, set the value of the `GATSBY_RBAC_URL` environmental variable to the URL of the Ocean RBAC server that you have hosted, e.g. `GATSBY_RBAC_URL= "http://localhost:3000"` +- Users of your marketplace will now require the correct role ("user", "consumer", "publisher") to access features in your marketplace. The market will check the role that has been allocated to the user based on the address that they have connected to the market with. +- The following features have been wrapped in the `Permission` component and will be restricted once the `GATSBY_RBAC_URL` has been defined: + - Viewing or searching datasets requires the user to have permison to `browse` + - Purchasing or trading a datatoken, or adding liquidity to a pool require the user to have permison to `consume` + - Publishing a dataset requires the user to have permison to `publish` +- You can change the permission resrictions by either removing the `Permission` component or passing in a different eventType prop e.g. ``. + +## Allow and Deny List Settings + +- To enable allow and deny lists you need to add the following environmental variable to your .env file: `GATSBY_ALLOW_ADVANCED_SETTINGS="true"` +- Publishers in your market will now have the ability to restrict who can consume their datasets. + +## Free Pricing Settings + +- To allow publishers to set pricing as "Free" you need to add the following environmental variable to your .env file: `GATSBY_ALLOW_FREE_PRICING="true"` +- This allocates the datatokens to the [dispenser contract](https://github.com/oceanprotocol/contracts/blob/main/contracts/dispenser/Dispenser.sol) which dispenses data tokens to users for free. Publishers in your market will now be able to offer their datasets to users for free (excluding gas costs). From 31ee1aab986d6fef61356d13b2671e5615aaf195 Mon Sep 17 00:00:00 2001 From: Norbi <37236152+KatunaNorbert@users.noreply.github.com> Date: Fri, 25 Jun 2021 11:34:39 +0300 Subject: [PATCH 6/9] Pool statistics graph shows two sets of data when switching tabs (#696) * removed compose previous data with new data * removed unused states * fix refetch multiple times for same block number Co-authored-by: Norbi --- src/components/organisms/AssetActions/Pool/Graph.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/organisms/AssetActions/Pool/Graph.tsx b/src/components/organisms/AssetActions/Pool/Graph.tsx index b13e1fc3d..a20275d26 100644 --- a/src/components/organisms/AssetActions/Pool/Graph.tsx +++ b/src/components/organisms/AssetActions/Pool/Graph.tsx @@ -123,11 +123,10 @@ export default function Graph(): ReactElement { const { price } = useAsset() - const [lastBlock, setLastBlock] = useState(0) + const [lastBlock, setLastBlock] = useState(0) const [priceHistory, setPriceHistory] = useState([]) const [liquidityHistory, setLiquidityHistory] = useState([]) const [timestamps, setTimestamps] = useState([]) - const [isLoading, setIsLoading] = useState(true) const [graphData, setGraphData] = useState() @@ -156,7 +155,6 @@ export default function Graph(): ReactElement { return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}` }) ] - setTimestamps(latestTimestamps) const latestLiquidtyHistory = [ @@ -170,17 +168,20 @@ export default function Graph(): ReactElement { ...priceHistory, ...data.poolTransactions.map((item) => item.spotPrice) ] + setPriceHistory(latestPriceHistory) if (data.poolTransactions.length > 0) { + const newBlock = + data.poolTransactions[data.poolTransactions.length - 1].block + if (newBlock === lastBlock) return setLastBlock( data.poolTransactions[data.poolTransactions.length - 1].block ) refetch() } else { - setIsLoading(false) setGraphData({ - labels: timestamps.slice(0), + labels: latestTimestamps.slice(0), datasets: [ { ...lineStyle, @@ -194,6 +195,7 @@ export default function Graph(): ReactElement { } ] }) + setIsLoading(false) } }, [data, graphType]) From c81a986261fa77b810d051f37423f3d1122851f3 Mon Sep 17 00:00:00 2001 From: Norbi <37236152+KatunaNorbert@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:47:56 +0300 Subject: [PATCH 7/9] Fix refetch data token orders (#700) * refetch token orders inside getJobs * removed logs * don't use Time component for Finished column if date is undefined * removed logs Co-authored-by: Norbi --- src/components/pages/History/ComputeJobs/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/pages/History/ComputeJobs/index.tsx b/src/components/pages/History/ComputeJobs/index.tsx index 765a48171..688bf3f28 100644 --- a/src/components/pages/History/ComputeJobs/index.tsx +++ b/src/components/pages/History/ComputeJobs/index.tsx @@ -60,7 +60,11 @@ const columns = [ { name: 'Finished', selector: function getTimeRow(row: ComputeJobMetaData) { - return