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

Merge pull request #101 from oceanprotocol/feature/history-compute

compute fix, compute history
This commit is contained in:
Matthias Kretschmann 2020-10-22 16:25:50 +02:00 committed by GitHub
commit 28ad41218e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 530 additions and 111 deletions

53
package-lock.json generated
View File

@ -4457,17 +4457,17 @@
"integrity": "sha512-j4PEZSVtKSqxDYMVh/hd5vk088Bg6a6QkrUMTXN9Q6OIFAMfHM235f1AxaakNrEyK0FKMD908KuJEdfFLRn9Hw=="
},
"@oceanprotocol/contracts": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-0.5.6.tgz",
"integrity": "sha512-LING+GvW37I0L40rZdPCZ1SvcZurDSGGhT0WOVPNO8oyh2C3bXModDBNE4+gCFa8pTbQBOc4ot1/Zoj9PfT/zA=="
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-0.5.7.tgz",
"integrity": "sha512-p0oOHXr60hXZuLNsQ/PsOQtCfia79thm7MjPxTrnnBvD+csJoHzARYMB0IFj/KTw6U5vLXODgjJAn8x6QksLwg=="
},
"@oceanprotocol/lib": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-0.7.3.tgz",
"integrity": "sha512-P+ImSOtNz2iDmYXVWutviBwxfFSY/zql362K+FZz3NQt4wYaMrCIiPALR2IF3/9v2FA2g9itTC7eGvWO7bfxqw==",
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-0.7.5.tgz",
"integrity": "sha512-frqMwfsqhyQ7+wfY6ak7+vmrbMVXDd7DuYdXHL2gyzom9uqcLLWhmWFNWo/1hOh750GhZ5d8BT4iX0O9P1JPTw==",
"requires": {
"@ethereum-navigator/navigator": "^0.5.0",
"@oceanprotocol/contracts": "^0.5.6",
"@oceanprotocol/contracts": "^0.5.7",
"decimal.js": "^10.2.0",
"fs": "0.0.1-security",
"lzma": "^2.3.2",
@ -8134,6 +8134,15 @@
"@types/react": "*"
}
},
"@types/react-modal": {
"version": "3.10.6",
"resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.10.6.tgz",
"integrity": "sha512-XpshhwVYir1TRZ2HS5EfmNotJjB8UEC2IkT3omNtiQzROOXSzVLz5xsjwEpACP8U+PctkpfZepX+WT5oDf0a9g==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-paginate": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@types/react-paginate/-/react-paginate-6.2.1.tgz",
@ -16480,6 +16489,11 @@
}
}
},
"exenv": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
},
"exif-parser": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
@ -27781,11 +27795,6 @@
"tslib": "^1.10.0"
}
},
"no-scroll": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/no-scroll/-/no-scroll-2.1.1.tgz",
"integrity": "sha512-YTzGAJOo/B6hkodeT5SKKHpOhAzjMfkUCCXjLJwjWk2F4/InIg+HbdH9kmT7bKpleDuqLZDTRy2OdNtAj0IVyQ=="
},
"node-abi": {
"version": "2.19.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.19.1.tgz",
@ -30997,6 +31006,17 @@
}
}
},
"react-modal": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
"integrity": "sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==",
"requires": {
"exenv": "^1.2.0",
"prop-types": "^15.5.10",
"react-lifecycles-compat": "^3.0.0",
"warning": "^4.0.3"
}
},
"react-onclickoutside": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz",
@ -31071,15 +31091,6 @@
"tslib": "^1.0.0"
}
},
"react-responsive-modal": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-responsive-modal/-/react-responsive-modal-5.1.1.tgz",
"integrity": "sha512-DjNBoVWfiP+5KCvpqFKhjKPVpPUYjpieCU/F3nlBU4OPImiZyGdM7l8iDjTIEti0lAt02vbuwmAaVLf3z1Mrqw==",
"requires": {
"classnames": "^2.2.6",
"no-scroll": "^2.1.1"
}
},
"react-side-effect": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz",

View File

@ -22,7 +22,7 @@
"@coingecko/cryptoformat": "^0.4.2",
"@loadable/component": "5.13.1",
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/lib": "^0.7.3",
"@oceanprotocol/lib": "^0.7.5",
"@oceanprotocol/react": "^0.3.5",
"@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0",
@ -66,8 +66,8 @@
"react-dropzone": "^11.2.0",
"react-helmet": "^6.1.0",
"react-markdown": "^5.0.1",
"react-modal": "^3.11.2",
"react-paginate": "^6.5.0",
"react-responsive-modal": "^5.1.1",
"react-spring": "^8.0.27",
"react-tabs": "^3.1.1",
"react-toastify": "^6.0.9",
@ -94,6 +94,7 @@
"@types/react": "^16.9.53",
"@types/react-datepicker": "^3.1.1",
"@types/react-helmet": "^6.1.0",
"@types/react-modal": "^3.10.6",
"@types/react-paginate": "^6.2.1",
"@types/react-tabs": "^2.3.2",
"@types/remove-markdown": "^0.1.1",

11
src/@types/ComputeJobMetaData.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
export interface ComputeJobMetaData {
jobId: string
did: string
dateCreated: string
dateFinished: string
assetName: string
status: number
statusText: string
algorithmLogUrl: string
resultsUrls: string[]
}

View File

@ -1,6 +0,0 @@
.customOverlay {
}
.customModal {
border-radius: 15px;
margin: auto;
}

View File

@ -1,35 +0,0 @@
import React, { ReactNode, ReactElement } from 'react'
import styles from './BaseDialog.module.css'
import { Modal } from 'react-responsive-modal'
export default function BaseDialog({
open,
title,
onClose,
children,
disableClose,
actions,
...other
}: {
open: boolean
title: string
onClose: () => void
children: ReactNode
disableClose?: boolean
actions?: any
}): ReactElement {
return (
<Modal
open={open}
onClose={onClose}
classNames={{
overlay: styles.customOverlay,
modal: styles.customModal
}}
{...other}
>
<h2>{title}</h2>
<div>{children}</div>
</Modal>
)
}

View File

@ -0,0 +1,96 @@
/* prevent background scrolling */
:global(.ReactModal__Body--open) {
overflow: hidden;
}
.modalOverlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(3px);
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
animation: fadeIn 0.2s ease-out backwards;
z-index: 5;
}
.modal {
composes: box from './Box.module.css';
padding: var(--spacer);
margin: var(--spacer) auto;
max-width: var(--break-point--small);
position: relative;
animation: moveUp 0.2s ease-out backwards;
}
.modal:focus {
outline: 0;
}
@media (min-width: 40rem) {
.modal {
padding: calc(var(--spacer) * 2);
}
}
.header {
padding-bottom: var(--spacer);
}
.title {
font-size: var(--font-size-h3);
margin: 0;
}
@media (min-width: 40rem) {
.title {
font-size: var(--font-size-h2);
}
}
.description {
margin: 0;
margin-top: var(--spacer);
}
.close {
position: absolute;
cursor: pointer;
background: none;
border: 0;
box-shadow: none;
outline: 0;
top: calc(var(--spacer) / 4);
right: calc(var(--spacer) / 2);
font-size: var(--font-size-h2);
color: var(--brand-grey);
}
.close:hover,
.close:focus {
opacity: 0.7;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes moveUp {
from {
transform: translate3d(0, 1rem, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}

View File

@ -0,0 +1,17 @@
import React from 'react'
import { render } from '@testing-library/react'
import Modal from './Modal'
import ReactModal from 'react-modal'
describe('Modal', () => {
it('renders without crashing', () => {
ReactModal.setAppElement(document.createElement('div'))
render(
<Modal title="Hello" isOpen onToggleModal={() => null}>
Hello
</Modal>
)
expect(document.querySelector('.ReactModalPortal')).toBeInTheDocument()
})
})

View File

@ -0,0 +1,41 @@
import React, { ReactElement, ReactNode } from 'react'
import ReactModal from 'react-modal'
import styles from './Modal.module.css'
if (process.env.NODE_ENV !== 'test') ReactModal.setAppElement('#___gatsby')
export interface ModalProps extends ReactModal.Props {
title: string
onToggleModal: () => void
children: ReactNode
}
export default function Modal({
title,
onToggleModal,
children,
...props
}: ModalProps): ReactElement {
return (
<ReactModal
contentLabel={title}
className={styles.modal}
overlayClassName={styles.modalOverlay}
{...props}
>
<button
className={styles.close}
onClick={onToggleModal}
data-testid="closeModal"
>
&times;
</button>
<header className={styles.header}>
<h2 className={styles.title}>{title}</h2>
</header>
{children}
</ReactModal>
)
}

View File

@ -0,0 +1,32 @@
import React, { ReactElement } from 'react'
import DataTable, { IDataTableProps } from 'react-data-table-component'
import Loader from './Loader'
import styles from './Table.module.css'
interface TableProps extends IDataTableProps {
isLoading?: boolean
}
function Empty(): ReactElement {
return <div className={styles.empty}>No results found</div>
}
export default function Table({
data,
columns,
isLoading
}: TableProps): ReactElement {
return (
<DataTable
columns={columns}
data={data}
className={styles.table}
noHeader
pagination={data?.length >= 9}
paginationPerPage={10}
noDataComponent={<Empty />}
progressPending={isLoading}
progressComponent={<Loader />}
/>
)
}

View File

@ -3,15 +3,19 @@ import { format, formatDistance } from 'date-fns'
export default function Time({
date,
relative
relative,
isUnix
}: {
date: string
relative?: boolean
isUnix?: boolean
}): ReactElement {
const dateNew = new Date(date)
const dateNew = isUnix ? new Date(Number(date) * 1000) : new Date(date)
const dateIso = dateNew.toISOString()
return (
return !date ? (
<></>
) : (
<time
title={relative ? format(dateNew, 'MMMM d, yyyy') : undefined}
dateTime={dateIso}

View File

@ -39,11 +39,9 @@ export default function Compute({
const [isJobStarting, setIsJobStarting] = useState(false)
const [, setError] = useState('')
const [computeType, setComputeType] = useState('nodejs')
const [computeContainer, setComputeContainer] = useState({
entrypoint: '',
image: '',
tag: ''
})
const [computeContainer, setComputeContainer] = useState(
computeOptions[0].value
)
const [algorithmRawCode, setAlgorithmRawCode] = useState('')
const [isPublished, setIsPublished] = useState(false)
const [file, setFile] = useState(null)
@ -130,17 +128,20 @@ export default function Compute({
<Dropzone multiple={false} handleOnDrop={onDrop} />
<div className={styles.actions}>
<Button
style="primary"
onClick={() => startJob()}
disabled={isComputeButtonDisabled}
>
{hasDatatoken ? 'Start job' : 'Buy'}
</Button>
{isLoading ? (
<Loader message={computeStepText} />
) : (
<Button
style="primary"
onClick={() => startJob()}
disabled={isComputeButtonDisabled}
>
{hasDatatoken ? 'Start job' : 'Buy'}
</Button>
)}
</div>
<footer className={styles.feedback}>
{isLoading && <Loader message={computeStepText} />}
{computeError !== undefined && (
<Alert text={computeError} state="error" />
)}

View File

@ -0,0 +1,3 @@
.title {
margin-bottom: calc(var(--spacer) / 4);
}

View File

@ -0,0 +1,99 @@
import { Logger } from '@oceanprotocol/lib'
import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../../atoms/Loader'
import Modal from '../../atoms/Modal'
import { ComputeJobMetaData } from '../../../@types/ComputeJobMetaData'
import Time from '../../atoms/Time'
import shortid from 'shortid'
import styles from './ComputeDetails.module.css'
import { Status } from './ComputeJobs'
import { ListItem } from '../../atoms/Lists'
export default function ComputeDetailsModal({
computeJob,
isOpen,
onToggleModal
}: {
computeJob: ComputeJobMetaData
isOpen: boolean
onToggleModal: () => void
}): ReactElement {
const { ocean, status, account } = useOcean()
const [isLoading, setIsLoading] = useState(false)
const isFinished = computeJob.dateFinished !== null
useEffect(() => {
async function getDetails() {
if (!account || !ocean || !computeJob || !isOpen || !isFinished) return
try {
setIsLoading(true)
const job = await ocean.compute.status(
account,
computeJob.did,
computeJob.jobId
)
if (job && job.length > 0) {
computeJob.algorithmLogUrl = job[0].algorithmLogUrl
// hack because ComputeJob returns resultsUrl instead of resultsUrls, issue created already
computeJob.resultsUrls =
(job[0] as any).resultsUrl !== '' ? (job[0] as any).resultsUrl : []
}
} catch (error) {
Logger.error(error.message)
} finally {
setIsLoading(false)
}
}
getDetails()
}, [ocean, status, account, isOpen, computeJob, isFinished])
return (
<Modal
title="Compute job details"
isOpen={isOpen}
onToggleModal={onToggleModal}
>
<h3 className={styles.title}>{computeJob.assetName}</h3>
<p>
Created on <Time date={computeJob.dateCreated} isUnix />
{computeJob.dateFinished && (
<>
<br />
Finished on <Time date={computeJob.dateFinished} isUnix />
</>
)}
</p>
<Status>{computeJob.statusText}</Status>
{isFinished &&
(isLoading ? (
<Loader />
) : (
<>
<ul>
<ListItem>
<a
href={computeJob.algorithmLogUrl}
target="_blank"
rel="noreferrer"
>
View Log
</a>
</ListItem>
{computeJob.resultsUrls?.map((url, i) => (
<ListItem key={shortid.generate()}>
<a href={url} target="_blank" rel="noreferrer">
View Result {i}
</a>
</ListItem>
))}
</ul>
</>
))}
</Modal>
)
}

View File

@ -0,0 +1,4 @@
.status {
text-transform: uppercase;
color: var(--color-secondary);
}

View File

@ -0,0 +1,141 @@
import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react'
import Time from '../../atoms/Time'
import styles from './ComputeJobs.module.css'
import Button from '../../atoms/Button'
import ComputeDetails from './ComputeDetails'
import { ComputeJobMetaData } from '../../../@types/ComputeJobMetaData'
import { Link } from 'gatsby'
import { Logger } from '@oceanprotocol/lib'
import Dotdotdot from 'react-dotdotdot'
import Table from '../../atoms/Table'
function DetailsButton({ row }: { row: ComputeJobMetaData }): ReactElement {
const [isDialogOpen, setIsDialogOpen] = useState(false)
return (
<>
<Button style="text" size="small" onClick={() => setIsDialogOpen(true)}>
Show Details
</Button>
<ComputeDetails
computeJob={row}
isOpen={isDialogOpen}
onToggleModal={() => setIsDialogOpen(false)}
/>
</>
)
}
export function Status({ children }: { children: string }): ReactElement {
return <div className={styles.status}>{children}</div>
}
const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: ComputeJobMetaData) {
return (
<Dotdotdot clamp={2}>
<Link to={`/asset/${row.did}`}>{row.assetName}</Link>
</Dotdotdot>
)
}
},
{
name: 'Created',
selector: function getTimeRow(row: ComputeJobMetaData) {
return <Time date={row.dateCreated} isUnix relative />
}
},
{
name: 'Finished',
selector: function getTimeRow(row: ComputeJobMetaData) {
return <Time date={row.dateFinished} isUnix />
}
},
{
name: 'Status',
selector: function getStatus(row: ComputeJobMetaData) {
return <Status>{row.statusText}</Status>
}
},
{
name: 'Actions',
selector: function getActions(row: ComputeJobMetaData) {
return <DetailsButton row={row} />
}
}
]
export default function ComputeJobs(): ReactElement {
const { ocean, account } = useOcean()
const [jobs, setJobs] = useState<ComputeJobMetaData[]>()
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
async function getTitle(did: string) {
const ddo = await ocean.metadatacache.retrieveDDO(did)
const metadata = ddo.findServiceByType('metadata')
return metadata.attributes.main.name
}
async function getJobs() {
if (!ocean || !account) return
setIsLoading(true)
try {
const orderHistory = await ocean.assets.getOrderHistory(
account,
'compute',
100
)
const jobs: ComputeJobMetaData[] = []
for (let i = 0; i < orderHistory.length; i++) {
const assetName = await getTitle(orderHistory[i].did)
const computeJob = await ocean.compute.status(
account,
orderHistory[i].did,
undefined,
orderHistory[i].transactionHash,
false
)
computeJob.forEach((item) => {
jobs.push({
did: orderHistory[i].did,
jobId: item.jobId,
dateCreated: item.dateCreated,
dateFinished: item.dateFinished,
assetName: assetName,
status: item.status,
statusText: item.statusText,
algorithmLogUrl: '',
resultsUrls: []
})
})
}
const jobsSorted = jobs.sort((a, b) => {
if (a.dateCreated > b.dateCreated) return -1
if (a.dateCreated < b.dateCreated) return 1
return 0
})
setJobs(jobsSorted)
} catch (error) {
Logger.log(error.message)
} finally {
setIsLoading(false)
}
}
getJobs()
}, [ocean, account])
return (
<Table
columns={columns}
data={jobs}
isLoading={isLoading}
defaultSortField="row.dateCreated"
defaultSortAsc={false}
/>
)
}

View File

@ -2,18 +2,18 @@ import { PoolTransaction } from '@oceanprotocol/lib/dist/node/balancer/OceanPool
import { useMetadata, useOcean } from '@oceanprotocol/react'
import { Link } from 'gatsby'
import React, { ReactElement, useEffect, useState } from 'react'
import DataTable from 'react-data-table-component'
import EtherscanLink from '../../atoms/EtherscanLink'
import Time from '../../atoms/Time'
import styles from './PoolTransactions.module.css'
import Dotdotdot from 'react-dotdotdot'
import Table from '../../atoms/Table'
function AssetTitle({ did }: { did: string }): ReactElement {
const { title } = useMetadata(did)
return <Link to={`/asset/${did}`}>{title || did}</Link>
}
function Empty() {
return <div className={styles.empty}>No results found</div>
return (
<Dotdotdot clamp={2}>
<Link to={`/asset/${did}`}>{title || did}</Link>
</Dotdotdot>
)
}
function Title({ row }: { row: PoolTransaction }) {
@ -60,9 +60,7 @@ const columns = [
{
name: 'Time',
selector: function getTimeRow(row: PoolTransaction) {
return (
<Time date={new Date(row.timestamp * 1000).toUTCString()} relative />
)
return <Time date={row.timestamp.toString()} relative isUnix />
}
}
]
@ -70,33 +68,24 @@ const columns = [
export default function PoolTransactions(): ReactElement {
const { ocean, accountId } = useOcean()
const [logs, setLogs] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
async function getLogs() {
if (!ocean || !accountId) return
setIsLoading(true)
const logs = await ocean.pool.getAllPoolLogs(accountId)
setLogs(
// sort logs by date, newest first
logs.sort((a, b) => {
if (a.timestamp > b.timestamp) return -1
if (a.timestamp < b.timestamp) return 1
return 0
})
)
// sort logs by date, newest first
const logsSorted = logs.sort((a, b) => {
if (a.timestamp > b.timestamp) return -1
if (a.timestamp < b.timestamp) return 1
return 0
})
setLogs(logsSorted)
setIsLoading(false)
}
getLogs()
}, [ocean, accountId])
return (
<DataTable
columns={columns}
data={logs}
className={styles.table}
noHeader
pagination={logs?.length >= 19}
paginationPerPage={20}
noDataComponent={<Empty />}
/>
)
return <Table columns={columns} data={logs} isLoading={isLoading} />
}

View File

@ -1,4 +1,5 @@
import React, { ReactElement, ReactNode } from 'react'
import ComputeJobs from './ComputeJobs'
import styles from './index.module.css'
import PoolTransactions from './PoolTransactions'
import PublishedList from './PublishedList'
@ -14,7 +15,7 @@ const sections = [
},
{
title: 'Compute Jobs',
component: 'Coming Soon...'
component: <ComputeJobs />
}
]

View File

@ -19,6 +19,15 @@ export function NetworkMonitor(): ReactElement {
// add metadataCacheUri only when defined
...(metadataCacheUri && { metadataCacheUri })
}
if (chainId === '8996') {
newConfig.factoryAddress = '0x312213d6f6b5FCF9F56B7B8946A6C727Bf4Bc21f'
newConfig.poolFactoryAddress =
'0xF9E633CBeEB2A474D3Fe22261046C99e805beeC4'
newConfig.fixedRateExchangeAddress =
'0xefdcb16b16C7842ec27c6fdCf56adc316B9B29B8'
newConfig.metadataContractAddress =
'0xEBe77E16736359Bf0F9013F6017242a5971cAE76'
}
try {
await connect(newConfig)