mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Edit compute dataset (#417)
* WIP * created form for editing compute privacy * used editComputePrivacy method * select and update trusted algorithm * display and select multiple trusted algorithms * fixed update when trusted algorithm list not changed * code refactoring * moved separator inside condition * moved functions and interface from EditComputeDataset component * moved algorithmOptions to parent component * used AssetSelection to display algorithms * use AssetSelection to select trusted algorithms * getAlgorithmsOptions function review * review fixes * removed unused imports * merge fixes * AssetSelection style & usability tweaks * use custom radio & checkbox styles * add simple search for name & DID * spacing adjustments * copy updates, remove raw algo input, hardcode allowRawAlgorithm * copy * AssetSelection usability tweaks * make rows clickable * tweak layout, style and markup * use formik set function to update values * sorted algorithm list, added checked field * sort assetSelection list on user select * fix getAlgorithmsForAssetSelection breaking on empty responses * form debug output * another empty publisherTrustedAlgorithms fix * created separate algorithms state for the form, sort list on edit * refactor * use Formik functionality wherever possible * unify transforming form data to final data * fix form debug transformation * fix form submit, fix defaultChecked * refactor * use Formik functionality wherever possible * unify transforming form data to final data * fix form debug transformation * fix form submit, fix defaultChecked * disable assetSelection when allowAllAlgorithms is true * added loader to AssetSelection * changed allowAllAlgorithms to allowAllPublishedAlgorithms * fixed lint error * updated transformComputeFormToServiceComputePrivacy * lint fix * modify publish defaults Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
2e9db9d170
commit
977a38e118
27
content/pages/editComputeDataset.json
Normal file
27
content/pages/editComputeDataset.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"description": "Only selected algorithms are allowed to run on this data set. Updating these settings will create an on-chain transaction you have to approve in your wallet.",
|
||||
"form": {
|
||||
"title": "Set allowed algorithms",
|
||||
"success": "🎉 Successfully updated. 🎉",
|
||||
"successAction": "Close",
|
||||
"error": "Updating DDO failed.",
|
||||
"data": [
|
||||
{
|
||||
"name": "allowAllPublishedAlgorithms",
|
||||
"label": "All Algorithms",
|
||||
"help": "Allow any published algorithm to run on this data set.",
|
||||
"type": "checkbox",
|
||||
"options": ["Allow any published algorithm"]
|
||||
},
|
||||
{
|
||||
"name": "publisherTrustedAlgorithms",
|
||||
"label": "Selected Algorithms",
|
||||
"help": "Choose one or multiple algorithms you trust to allow them to run on this data set.",
|
||||
"type": "assetSelectionMultiple",
|
||||
"multiple": true,
|
||||
"options": [],
|
||||
"sortOptions": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -3625,9 +3625,9 @@
|
||||
"integrity": "sha512-6GrBk1jy+zxjDjh2SPra06etrqdp8CB6RaZaTq2OQpK8dA2Dq91hqCbj+6eb21MlU8bDY3/atnxax9rgPgsxkA=="
|
||||
},
|
||||
"@oceanprotocol/lib": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-0.12.0.tgz",
|
||||
"integrity": "sha512-bREJhiyQ1LlFdLY0WoZbelfH27R7PLi0pY+c3TiX3fYvDShfp5NCYkq0B8Wf4FjxUxd4BMJREwRNdOS416RYVA==",
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-0.12.1.tgz",
|
||||
"integrity": "sha512-Cw4d4Di6GnL1gfZcYqemnKh1Agpc66AemIJANfbJpO08KkgiLEZhhMYsYQZzha+zQD9Jr4ZuzE/ZlYDr6S0o6w==",
|
||||
"requires": {
|
||||
"@ethereum-navigator/navigator": "^0.5.2",
|
||||
"@oceanprotocol/contracts": "^0.5.10",
|
||||
@ -16410,7 +16410,7 @@
|
||||
}
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1ce6a1d64235fabe2aaf827fd606def55693508f",
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "^4.11.8",
|
||||
|
@ -27,7 +27,7 @@
|
||||
"@coingecko/cryptoformat": "^0.4.2",
|
||||
"@loadable/component": "^5.14.1",
|
||||
"@oceanprotocol/art": "^3.0.0",
|
||||
"@oceanprotocol/lib": "^0.12.0",
|
||||
"@oceanprotocol/lib": "^0.12.1",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@portis/web3": "^3.0.3",
|
||||
"@sindresorhus/slugify": "^1.0.0",
|
||||
|
4
src/@types/ComputeDataset.ts
Normal file
4
src/@types/ComputeDataset.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface AlgorithmOption {
|
||||
did: string
|
||||
name: string
|
||||
}
|
5
src/@types/Form.d.ts
vendored
5
src/@types/Form.d.ts
vendored
@ -1,10 +1,13 @@
|
||||
import { AssetSelectionAsset } from '../../molecules/FormFields/AssetSelection'
|
||||
|
||||
export interface FormFieldProps {
|
||||
label: string
|
||||
name: string
|
||||
type?: string
|
||||
options?: string[]
|
||||
options?: string[] | AssetSelectionAsset[]
|
||||
sortOptions?: boolean
|
||||
required?: boolean
|
||||
multiple?: boolean
|
||||
disabled?: boolean
|
||||
help?: string
|
||||
placeholder?: string
|
||||
|
@ -14,13 +14,14 @@ const cx = classNames.bind(styles)
|
||||
|
||||
const DefaultInput = ({
|
||||
size,
|
||||
className,
|
||||
prefix,
|
||||
postfix,
|
||||
additionalComponent,
|
||||
...props
|
||||
}: InputProps) => (
|
||||
<input
|
||||
className={cx({ input: true, [size]: size })}
|
||||
className={cx({ input: true, [size]: size, [className]: className })}
|
||||
id={props.name}
|
||||
{...props}
|
||||
/>
|
||||
@ -36,13 +37,14 @@ export default function InputElement({
|
||||
size,
|
||||
field,
|
||||
label,
|
||||
multiple,
|
||||
disabled,
|
||||
help,
|
||||
form,
|
||||
additionalComponent,
|
||||
...props
|
||||
}: InputProps): ReactElement {
|
||||
const styleClasses = cx({ select: true, [size]: size })
|
||||
|
||||
switch (type) {
|
||||
case 'select': {
|
||||
const sortedOptions =
|
||||
@ -50,7 +52,12 @@ export default function InputElement({
|
||||
? options
|
||||
: options.sort((a: string, b: string) => a.localeCompare(b))
|
||||
return (
|
||||
<select id={name} className={styleClasses} {...props}>
|
||||
<select
|
||||
id={name}
|
||||
className={styleClasses}
|
||||
{...props}
|
||||
multiple={multiple}
|
||||
>
|
||||
{field !== undefined && field.value === '' && (
|
||||
<option value="">---</option>
|
||||
)}
|
||||
@ -106,6 +113,7 @@ export default function InputElement({
|
||||
<AssetSelection
|
||||
assets={(options as unknown) as AssetSelectionAsset[]}
|
||||
multiple
|
||||
disabled={disabled}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
@ -126,6 +134,7 @@ export default function InputElement({
|
||||
name={name}
|
||||
type={type || 'text'}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
{postfix && (
|
||||
@ -137,6 +146,7 @@ export default function InputElement({
|
||||
name={name}
|
||||
type={type || 'text'}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
@ -41,6 +41,7 @@ export interface InputProps {
|
||||
step?: string
|
||||
defaultChecked?: boolean
|
||||
size?: 'mini' | 'small' | 'large' | 'default'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Input(props: Partial<InputProps>): ReactElement {
|
||||
|
@ -1,12 +1,26 @@
|
||||
.selection {
|
||||
padding: calc(var(--spacer) / 4);
|
||||
padding: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--background-highlight);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
div [class*='loaderWrap'] {
|
||||
margin: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.scroll {
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: calc(var(--spacer) / 4);
|
||||
min-height: 200px;
|
||||
max-height: 300px;
|
||||
position: relative;
|
||||
/* smooth overflow scrolling for pre-iOS 13 */
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
@ -14,44 +28,78 @@
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-top: calc(var(--spacer) / 4);
|
||||
padding-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.row > div {
|
||||
width: 100%;
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: calc(var(--spacer) / 10);
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input {
|
||||
align-self: flex-start;
|
||||
min-width: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.radio {
|
||||
composes: radio from '../../atoms/Input/InputElement.module.css';
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
composes: checkbox from '../../atoms/Input/InputElement.module.css';
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0;
|
||||
margin-top: calc(var(--spacer) / 12);
|
||||
margin-bottom: calc(var(--spacer) / 12);
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
display: inline-block;
|
||||
margin-left: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.link svg {
|
||||
margin: 0;
|
||||
fill: var(--color-primary);
|
||||
width: 0.7em;
|
||||
height: 0.7em;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
gap: var(--spacer);
|
||||
justify-content: space-between;
|
||||
margin-top: calc(var(--spacer) / 12);
|
||||
}
|
||||
|
||||
.price {
|
||||
display: inline-block;
|
||||
white-space: pre;
|
||||
font-size: var(--font-size-small) !important;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: calc(var(--spacer) / 4);
|
||||
width: calc(100% - calc(var(--spacer) / 2));
|
||||
}
|
||||
|
||||
.did {
|
||||
padding: 0;
|
||||
font-size: var(--font-size-mini);
|
||||
display: block;
|
||||
text-align: left;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
@ -1,58 +1,107 @@
|
||||
import React from 'react'
|
||||
import React, { ChangeEvent, useState } from 'react'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import slugify from 'slugify'
|
||||
import classNames from 'classnames/bind'
|
||||
import PriceUnit from '../../atoms/Price/PriceUnit'
|
||||
import { ReactComponent as External } from '../../../images/external.svg'
|
||||
import styles from './AssetSelection.module.css'
|
||||
import InputElement from '../../atoms/Input/InputElement'
|
||||
import Loader from '../../atoms/Loader'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface AssetSelectionAsset {
|
||||
did: string
|
||||
name: string
|
||||
price: string
|
||||
checked: boolean
|
||||
}
|
||||
|
||||
export default function AssetSelection({
|
||||
assets,
|
||||
multiple,
|
||||
disabled,
|
||||
...props
|
||||
}: {
|
||||
assets: AssetSelectionAsset[]
|
||||
multiple?: boolean
|
||||
disabled?: boolean
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className={styles.selection}>
|
||||
{assets.map((asset: AssetSelectionAsset) => (
|
||||
<div className={styles.row} key={asset.did}>
|
||||
<input
|
||||
id={slugify(asset.name)}
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
value={asset.did}
|
||||
className={styles.input}
|
||||
{...props}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<label
|
||||
className={styles.label}
|
||||
htmlFor={slugify(asset.name)}
|
||||
title={asset.name}
|
||||
>
|
||||
<Dotdotdot clamp={1} tagName="h3" className={styles.title}>
|
||||
{asset.name}
|
||||
</Dotdotdot>
|
||||
<PriceUnit price={asset.price} small className={styles.price} />
|
||||
</label>
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
|
||||
<a
|
||||
className={styles.link}
|
||||
href={`/asset/${asset.did}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<External />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
const styleClassesInput = cx({
|
||||
input: true,
|
||||
[styles.checkbox]: multiple,
|
||||
[styles.radio]: !multiple
|
||||
})
|
||||
|
||||
function handleSearchInput(e: ChangeEvent<HTMLInputElement>) {
|
||||
setSearchValue(e.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.selection} ${disabled ? styles.disabled : ''}`}>
|
||||
<InputElement
|
||||
type="search"
|
||||
name="search"
|
||||
size="small"
|
||||
placeholder="Search by title or DID..."
|
||||
value={searchValue}
|
||||
onChange={handleSearchInput}
|
||||
className={styles.search}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div className={styles.scroll}>
|
||||
{assets ? (
|
||||
assets
|
||||
.filter((asset: AssetSelectionAsset) =>
|
||||
searchValue !== ''
|
||||
? asset.name.toLowerCase().includes(searchValue) ||
|
||||
asset.did.includes(searchValue)
|
||||
: asset
|
||||
)
|
||||
.map((asset: AssetSelectionAsset) => (
|
||||
<div className={styles.row} key={asset.did}>
|
||||
<input
|
||||
id={slugify(asset.did)}
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
className={styleClassesInput}
|
||||
defaultChecked={asset.checked}
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
value={asset.did}
|
||||
/>
|
||||
<label
|
||||
className={styles.label}
|
||||
htmlFor={slugify(asset.did)}
|
||||
title={asset.name}
|
||||
>
|
||||
<h3 className={styles.title}>
|
||||
<Dotdotdot clamp={1} tagName="span">
|
||||
{asset.name}
|
||||
</Dotdotdot>
|
||||
<a
|
||||
className={styles.link}
|
||||
href={`/asset/${asset.did}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<External />
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<Dotdotdot clamp={1} tagName="code" className={styles.did}>
|
||||
{asset.did}
|
||||
</Dotdotdot>
|
||||
</label>
|
||||
|
||||
<PriceUnit price={asset.price} small className={styles.price} />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { DDO, ServiceComputePrivacy } from '@oceanprotocol/lib'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { ComputePrivacyForm } from '../../../../models/FormEditComputeDataset'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { transformComputeFormToServiceComputePrivacy } from '../../../../utils/compute'
|
||||
import DebugOutput from '../../../atoms/DebugOutput'
|
||||
|
||||
export default function DebugEditCompute({
|
||||
values,
|
||||
ddo
|
||||
}: {
|
||||
values: ComputePrivacyForm
|
||||
ddo: DDO
|
||||
}): ReactElement {
|
||||
const { ocean } = useOcean()
|
||||
const [
|
||||
formTransformed,
|
||||
setFormTransformed
|
||||
] = useState<ServiceComputePrivacy>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ocean) return
|
||||
|
||||
async function transformValues() {
|
||||
const privacy = await transformComputeFormToServiceComputePrivacy(
|
||||
values,
|
||||
ocean
|
||||
)
|
||||
setFormTransformed(privacy)
|
||||
}
|
||||
transformValues()
|
||||
}, [values, ddo, ocean])
|
||||
|
||||
return (
|
||||
<>
|
||||
<DebugOutput title="Collected Form Values" output={values} />
|
||||
<DebugOutput title="Transformed Form Values" output={formTransformed} />
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { Formik } from 'formik'
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import {
|
||||
validationSchema,
|
||||
getInitialValues,
|
||||
ComputePrivacyForm
|
||||
} from '../../../../models/FormEditComputeDataset'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import FormEditComputeDataset from './FormEditComputeDataset'
|
||||
import { Logger, ServiceComputePrivacy } from '@oceanprotocol/lib'
|
||||
import MetadataFeedback from '../../../molecules/MetadataFeedback'
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import DebugEditCompute from './DebugEditCompute'
|
||||
import styles from './index.module.css'
|
||||
import { transformComputeFormToServiceComputePrivacy } from '../../../../utils/compute'
|
||||
|
||||
const contentQuery = graphql`
|
||||
query EditComputeDataQuery {
|
||||
content: allFile(
|
||||
filter: { relativePath: { eq: "pages/editComputeDataset.json" } }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
childPagesJson {
|
||||
description
|
||||
form {
|
||||
title
|
||||
success
|
||||
successAction
|
||||
error
|
||||
data {
|
||||
name
|
||||
placeholder
|
||||
label
|
||||
help
|
||||
type
|
||||
required
|
||||
sortOptions
|
||||
options
|
||||
multiple
|
||||
rows
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function EditComputeDataset({
|
||||
setShowEdit
|
||||
}: {
|
||||
setShowEdit: (show: boolean) => void
|
||||
}): ReactElement {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childPagesJson
|
||||
|
||||
const { debug } = useUserPreferences()
|
||||
const { ocean } = useOcean()
|
||||
const { accountId } = useWeb3()
|
||||
const { ddo, refreshDdo } = useAsset()
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
const hasFeedback = error || success
|
||||
|
||||
async function handleSubmit(
|
||||
values: ComputePrivacyForm,
|
||||
resetForm: () => void
|
||||
) {
|
||||
try {
|
||||
const privacy = await transformComputeFormToServiceComputePrivacy(
|
||||
values,
|
||||
ocean
|
||||
)
|
||||
|
||||
const ddoEditedComputePrivacy = await ocean.compute.editComputePrivacy(
|
||||
ddo,
|
||||
1,
|
||||
privacy as ServiceComputePrivacy
|
||||
)
|
||||
|
||||
if (!ddoEditedComputePrivacy) {
|
||||
setError(content.form.error)
|
||||
Logger.error(content.form.error)
|
||||
return
|
||||
}
|
||||
|
||||
const storedddo = await ocean.assets.updateMetadata(
|
||||
ddoEditedComputePrivacy,
|
||||
accountId
|
||||
)
|
||||
if (!storedddo) {
|
||||
setError(content.form.error)
|
||||
Logger.error(content.form.error)
|
||||
return
|
||||
} else {
|
||||
// Edit succeeded
|
||||
setSuccess(content.form.success)
|
||||
resetForm()
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
setError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={getInitialValues(
|
||||
ddo.findServiceByType('compute').attributes.main.privacy
|
||||
)}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
// move user's focus to top of screen
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||
// kick off editing
|
||||
await handleSubmit(values, resetForm)
|
||||
}}
|
||||
>
|
||||
{({ values, isSubmitting }) =>
|
||||
isSubmitting || hasFeedback ? (
|
||||
<MetadataFeedback
|
||||
title="Updating Data Set"
|
||||
error={error}
|
||||
success={success}
|
||||
setError={setError}
|
||||
successAction={{
|
||||
name: content.form.successAction,
|
||||
onClick: async () => {
|
||||
await refreshDdo()
|
||||
setShowEdit(false)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<p className={styles.description}>{content.description}</p>
|
||||
<article className={styles.grid}>
|
||||
<FormEditComputeDataset
|
||||
title={content.form.title}
|
||||
data={content.form.data}
|
||||
setShowEdit={setShowEdit}
|
||||
/>
|
||||
</article>
|
||||
|
||||
{debug === true && (
|
||||
<div className={styles.grid}>
|
||||
<DebugEditCompute values={values} ddo={ddo} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Formik>
|
||||
)
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||
import Button from '../../../atoms/Button'
|
||||
import Input from '../../../atoms/Input'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
import { FormFieldProps } from '../../../../@types/Form'
|
||||
import { AssetSelectionAsset } from '../../../molecules/FormFields/AssetSelection'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './FormEditMetadata.module.css'
|
||||
import { getAlgorithmsForAssetSelection } from '../../../../utils/aquarius'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { ComputePrivacyForm } from '../../../../models/FormEditComputeDataset'
|
||||
|
||||
export default function FormEditComputeDataset({
|
||||
data,
|
||||
title,
|
||||
setShowEdit
|
||||
}: {
|
||||
data: FormFieldProps[]
|
||||
title: string
|
||||
setShowEdit: (show: boolean) => void
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, config } = useOcean()
|
||||
const { ddo } = useAsset()
|
||||
const {
|
||||
isValid,
|
||||
values
|
||||
}: FormikContextType<ComputePrivacyForm> = useFormikContext()
|
||||
const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
|
||||
|
||||
const { publisherTrustedAlgorithms } = ddo?.findServiceByType(
|
||||
'compute'
|
||||
).attributes.main.privacy
|
||||
|
||||
useEffect(() => {
|
||||
getAlgorithmsForAssetSelection(
|
||||
config.metadataCacheUri,
|
||||
publisherTrustedAlgorithms
|
||||
).then((algorithms) => {
|
||||
setAllAlgorithms(algorithms)
|
||||
})
|
||||
}, [config.metadataCacheUri, publisherTrustedAlgorithms])
|
||||
|
||||
return (
|
||||
<Form className={styles.form}>
|
||||
<h3 className={stylesIndex.title}>{title}</h3>
|
||||
{data.map((field: FormFieldProps) => (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.name === 'publisherTrustedAlgorithms'
|
||||
? allAlgorithms
|
||||
: field.options
|
||||
}
|
||||
disabled={
|
||||
field.name === 'publisherTrustedAlgorithms'
|
||||
? values.allowAllPublishedAlgorithms
|
||||
: false
|
||||
}
|
||||
component={Input}
|
||||
/>
|
||||
))}
|
||||
<footer className={styles.actions}>
|
||||
<Button style="primary" disabled={!ocean || !accountId || !isValid}>
|
||||
Submit
|
||||
</Button>
|
||||
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</footer>
|
||||
</Form>
|
||||
)
|
||||
}
|
@ -22,3 +22,7 @@
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-right: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
select[multiple] {
|
||||
height: 130px;
|
||||
}
|
||||
|
@ -8,3 +8,14 @@
|
||||
margin-top: -1.5rem;
|
||||
max-width: 50rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-large);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: calc(var(--spacer) / 2);
|
||||
margin-top: -1rem;
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||
import { MetadataPreview } from '../../../molecules/MetadataPreview'
|
||||
import Debug from './Debug'
|
||||
import Debug from './DebugEditMetadata'
|
||||
import Web3Feedback from '../../../molecules/Wallet/Feedback'
|
||||
import FormEditMetadata from './FormEditMetadata'
|
||||
import { mapTimeoutStringToSeconds } from '../../../../utils/metadata'
|
||||
@ -64,6 +64,9 @@ export default function Edit({
|
||||
const [success, setSuccess] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const [timeoutStringValue, setTimeoutStringValue] = useState<string>()
|
||||
const timeout = ddo.findServiceByType('access')
|
||||
? ddo.findServiceByType('access').attributes.main.timeout
|
||||
: ddo.findServiceByType('compute').attributes.main.timeout
|
||||
|
||||
const hasFeedback = error || success
|
||||
|
||||
@ -122,10 +125,7 @@ export default function Edit({
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={getInitialValues(
|
||||
metadata,
|
||||
ddo.findServiceByType('access').attributes.main.timeout
|
||||
)}
|
||||
initialValues={getInitialValues(metadata, timeout)}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={async (values, { resetForm }) => {
|
||||
// move user's focus to top of screen
|
||||
|
@ -42,3 +42,7 @@
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { graphql, useStaticQuery } from 'gatsby'
|
||||
import Markdown from '../../atoms/Markdown'
|
||||
import MetaFull from './MetaFull'
|
||||
import MetaSecondary from './MetaSecondary'
|
||||
import styles from './index.module.css'
|
||||
import AssetActions from '../AssetActions'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import Pricing from './Pricing'
|
||||
@ -12,10 +11,12 @@ import { useAsset } from '../../../providers/Asset'
|
||||
import Alert from '../../atoms/Alert'
|
||||
import Button from '../../atoms/Button'
|
||||
import Edit from '../AssetActions/Edit'
|
||||
import EditComputeDataset from '../AssetActions/Edit/EditComputeDataset'
|
||||
import DebugOutput from '../../atoms/DebugOutput'
|
||||
import MetaMain from './MetaMain'
|
||||
import EditHistory from './EditHistory'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export interface AssetContentProps {
|
||||
path?: string
|
||||
@ -46,7 +47,9 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
const { owner, isInPurgatory, purgatoryData } = useAsset()
|
||||
const [showPricing, setShowPricing] = useState(false)
|
||||
const [showEdit, setShowEdit] = useState<boolean>()
|
||||
const [showEditCompute, setShowEditCompute] = useState<boolean>()
|
||||
const { ddo, price, metadata } = useAsset()
|
||||
|
||||
const isOwner = accountId === owner
|
||||
|
||||
useEffect(() => {
|
||||
@ -60,8 +63,15 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
setShowEdit(true)
|
||||
}
|
||||
|
||||
function handleEditComputeButton() {
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||
setShowEditCompute(true)
|
||||
}
|
||||
|
||||
return showEdit ? (
|
||||
<Edit setShowEdit={setShowEdit} />
|
||||
) : showEditCompute ? (
|
||||
<EditComputeDataset setShowEdit={setShowEditCompute} />
|
||||
) : (
|
||||
<article className={styles.grid}>
|
||||
<div>
|
||||
@ -91,6 +101,18 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
<Button style="text" size="small" onClick={handleEditButton}>
|
||||
Edit Metadata
|
||||
</Button>
|
||||
{ddo.findServiceByType('compute') && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditComputeButton}
|
||||
>
|
||||
Edit Compute Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -113,8 +113,9 @@ function usePublish(): UsePublish {
|
||||
servers
|
||||
)
|
||||
const origComputePrivacy: ServiceComputePrivacy = {
|
||||
allowRawAlgorithm: true,
|
||||
allowRawAlgorithm: false,
|
||||
allowNetworkAccess: false,
|
||||
allowAllPublishedAlgorithms: false,
|
||||
publisherTrustedAlgorithms: []
|
||||
}
|
||||
const computeService = ocean.compute.createComputeService(
|
||||
|
30
src/models/FormEditComputeDataset.ts
Normal file
30
src/models/FormEditComputeDataset.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ServiceComputePrivacy } from '@oceanprotocol/lib'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
export interface ComputePrivacyForm {
|
||||
allowAllPublishedAlgorithms: boolean
|
||||
publisherTrustedAlgorithms: string[]
|
||||
}
|
||||
|
||||
export const validationSchema: Yup.SchemaOf<ComputePrivacyForm> = Yup.object().shape(
|
||||
{
|
||||
allowAllPublishedAlgorithms: Yup.boolean().nullable(),
|
||||
publisherTrustedAlgorithms: Yup.array().nullable()
|
||||
}
|
||||
)
|
||||
|
||||
export function getInitialValues(
|
||||
compute: ServiceComputePrivacy
|
||||
): ComputePrivacyForm {
|
||||
// TODO: ocean.js needs allowAllAlgoritms setting
|
||||
const { allowAllPublishedAlgorithms, publisherTrustedAlgorithms } = compute
|
||||
|
||||
const publisherTrustedAlgorithmsForForm = (
|
||||
publisherTrustedAlgorithms || []
|
||||
).map((algo) => algo.did)
|
||||
|
||||
return {
|
||||
allowAllPublishedAlgorithms,
|
||||
publisherTrustedAlgorithms: publisherTrustedAlgorithmsForForm
|
||||
}
|
||||
}
|
@ -1,9 +1,18 @@
|
||||
import { DDO, DID, Logger } from '@oceanprotocol/lib'
|
||||
import {
|
||||
Config,
|
||||
DDO,
|
||||
DID,
|
||||
Logger,
|
||||
publisherTrustedAlgorithm as PublisherTrustedAlgorithm
|
||||
} from '@oceanprotocol/lib/'
|
||||
import {
|
||||
QueryResult,
|
||||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
|
||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
import web3 from 'web3'
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
||||
|
||||
// TODO: import directly from ocean.js somehow.
|
||||
// Transforming Aquarius' direct response is needed for getting actual DDOs
|
||||
@ -97,3 +106,57 @@ export async function getAssetsNames(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAlgorithmsForAssetSelection(
|
||||
metadataCacheUri: string,
|
||||
selectedAlgorithms?: PublisherTrustedAlgorithm[]
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
const query = {
|
||||
page: 1,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `(service.attributes.main.type:algorithm) -isInPurgatory:true`
|
||||
}
|
||||
},
|
||||
sort: { created: -1 }
|
||||
}
|
||||
const source = axios.CancelToken.source()
|
||||
const didList: string[] = []
|
||||
const priceList: any = {}
|
||||
const result = await queryMetadata(
|
||||
query as any,
|
||||
metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
result?.results?.forEach((ddo: DDO) => {
|
||||
const did: string = web3.utils
|
||||
.toChecksumAddress(ddo.dataToken)
|
||||
.replace('0x', 'did:op:')
|
||||
didList.push(did)
|
||||
priceList[did] = ddo.price.value
|
||||
})
|
||||
const ddoNames = await getAssetsNames(didList, metadataCacheUri, source.token)
|
||||
const algorithmList: AssetSelectionAsset[] = []
|
||||
didList?.forEach((did: string) => {
|
||||
let selected = false
|
||||
selectedAlgorithms?.forEach((algorithm: PublisherTrustedAlgorithm) => {
|
||||
if (algorithm.did === did) {
|
||||
selected = true
|
||||
}
|
||||
})
|
||||
selected
|
||||
? algorithmList.unshift({
|
||||
did: did,
|
||||
name: ddoNames[did],
|
||||
price: priceList[did],
|
||||
checked: selected
|
||||
})
|
||||
: algorithmList.push({
|
||||
did: did,
|
||||
name: ddoNames[did],
|
||||
price: priceList[did],
|
||||
checked: selected
|
||||
})
|
||||
})
|
||||
return algorithmList
|
||||
}
|
||||
|
41
src/utils/compute.ts
Normal file
41
src/utils/compute.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {
|
||||
DDO,
|
||||
Ocean,
|
||||
ServiceComputePrivacy,
|
||||
publisherTrustedAlgorithm as PublisherTrustedAlgorithm
|
||||
} from '@oceanprotocol/lib'
|
||||
import { ComputePrivacyForm } from '../models/FormEditComputeDataset'
|
||||
|
||||
export async function createTrustedAlgorithmList(
|
||||
selectedAlgorithms: string[], // list of DIDs
|
||||
ocean: Ocean
|
||||
): Promise<PublisherTrustedAlgorithm[]> {
|
||||
const trustedAlgorithms = []
|
||||
|
||||
for (const selectedAlgorithm of selectedAlgorithms) {
|
||||
const trustedAlgorithm = await ocean.compute.createPublisherTrustedAlgorithmfromDID(
|
||||
selectedAlgorithm
|
||||
)
|
||||
trustedAlgorithms.push(trustedAlgorithm)
|
||||
}
|
||||
return trustedAlgorithms
|
||||
}
|
||||
|
||||
export async function transformComputeFormToServiceComputePrivacy(
|
||||
values: ComputePrivacyForm,
|
||||
ocean: Ocean
|
||||
): Promise<ServiceComputePrivacy> {
|
||||
const { allowAllPublishedAlgorithms } = values
|
||||
const publisherTrustedAlgorithms = values.allowAllPublishedAlgorithms
|
||||
? []
|
||||
: await createTrustedAlgorithmList(values.publisherTrustedAlgorithms, ocean)
|
||||
|
||||
const privacy: ServiceComputePrivacy = {
|
||||
allowNetworkAccess: false,
|
||||
allowRawAlgorithm: false,
|
||||
allowAllPublishedAlgorithms,
|
||||
publisherTrustedAlgorithms
|
||||
}
|
||||
|
||||
return privacy
|
||||
}
|
Loading…
Reference in New Issue
Block a user