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

Job details (#528)

* refactor

* refactor job details

* styling, cleanup, get algo name

* output dataset & algo dt symbol

* more styling and job metadata output

* history background tweak

* human numbers, edge case fixes
This commit is contained in:
Matthias Kretschmann 2021-04-16 10:12:53 +02:00 committed by GitHub
parent 55b09220b4
commit 93ca8e3879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 332 additions and 206 deletions

View File

@ -1,11 +1,6 @@
export interface ComputeJobMetaData {
jobId: string
did: string
dateCreated: string
dateFinished: string
import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
export interface ComputeJobMetaData extends ComputeJob {
assetName: string
status: number
statusText: string
algorithmLogUrl: string
resultsUrls: string[]
assetDtSymbol: string
}

View File

@ -5,6 +5,7 @@
margin: 0;
display: inline-block;
width: fit-content;
min-width: 7rem;
padding: calc(var(--spacer) / 3) var(--spacer);
font-size: var(--font-size-base);
font-family: var(--font-family-base);

View File

@ -5,10 +5,6 @@
list-style-position: inside;
}
.item span {
color: var(--brand-grey-dark);
}
.ulItem {
list-style-type: square;
}

View File

@ -1,5 +1,7 @@
.loaderWrap {
display: flex;
align-items: center;
justify-content: center;
}
.loader {

View File

@ -4,9 +4,9 @@ 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'
import styles from './AssetSelection.module.css'
const cx = classNames.bind(styles)

View File

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

View File

@ -1,109 +0,0 @@
import { Logger } from '@oceanprotocol/lib'
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'
import { useOcean } from '../../../providers/Ocean'
export default function ComputeDetailsModal({
computeJob,
isOpen,
onToggleModal
}: {
computeJob: ComputeJobMetaData
isOpen: boolean
onToggleModal: () => void
}): ReactElement {
const { ocean, 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,
undefined,
undefined,
computeJob.jobId
)
if (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, account, isOpen, computeJob, isFinished])
return (
<Modal
title="Compute job details"
isOpen={isOpen}
onToggleModal={onToggleModal}
>
<h3 className={styles.title}>{computeJob.assetName}</h3>
<p>
Created <Time date={computeJob.dateCreated} isUnix relative />
{computeJob.dateFinished && (
<>
<br />
Finished <Time date={computeJob.dateFinished} isUnix relative />
</>
)}
</p>
<Status>{computeJob.statusText}</Status>
{isFinished &&
(isLoading ? (
<Loader />
) : (
<>
<ul>
<ListItem>
{computeJob.algorithmLogUrl ? (
<a
href={computeJob.algorithmLogUrl}
target="_blank"
rel="noreferrer"
>
View Log
</a>
) : (
'No logs found'
)}
</ListItem>
{computeJob.resultsUrls?.map((url, i) =>
url ? (
<ListItem key={shortid.generate()}>
<a href={url} target="_blank" rel="noreferrer">
View Result {i}
</a>
</ListItem>
) : (
'No results found.'
)
)}
</ul>
</>
))}
</Modal>
)
}

View File

@ -0,0 +1,72 @@
.main {
margin-bottom: var(--spacer);
}
.main > div:first-child {
margin-bottom: calc(var(--spacer) / 2);
}
.asset {
composes: box from '../../../atoms/Box.module.css';
box-shadow: none;
padding: calc(var(--spacer) / 2);
margin-bottom: calc(var(--spacer) / 2);
}
.asset + .asset {
margin-left: var(--spacer);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
position: relative;
overflow: visible;
}
.asset + .asset:before {
content: '';
display: block;
position: absolute;
left: -1px;
top: -1.15rem;
bottom: 3px;
width: 1px;
background-color: var(--border-color);
}
.asset p {
margin: 0;
}
.asset code {
padding: 0;
}
.assetTitle {
margin-bottom: 0;
font-size: var(--font-size-base);
color: var(--font-color-text);
}
.assetLink {
display: inline-block;
margin-left: calc(var(--spacer) / 8);
}
.assetLink svg {
margin: 0;
fill: var(--color-primary);
width: 0.6em;
height: 0.6em;
}
.assetMeta,
.assetMeta code {
color: var(--color-secondary);
font-size: var(--font-size-small);
}
.meta {
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
margin-top: calc(var(--spacer) * 1.5);
}

View File

@ -0,0 +1,119 @@
import React, { ReactElement, useEffect, useState } from 'react'
import axios from 'axios'
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
import Time from '../../../atoms/Time'
import Button from '../../../atoms/Button'
import Modal from '../../../atoms/Modal'
import MetaItem from '../../../organisms/AssetContent/MetaItem'
import { ReactComponent as External } from '../../../../images/external.svg'
import { retrieveDDO } from '../../../../utils/aquarius'
import { useOcean } from '../../../../providers/Ocean'
import Results from './Results'
import styles from './Details.module.css'
function Asset({
title,
symbol,
did
}: {
title: string
symbol: string
did: string
}) {
return (
<div className={styles.asset}>
<h3 className={styles.assetTitle}>
{title}{' '}
<a
className={styles.assetLink}
href={`/asset/${did}`}
target="_blank"
rel="noreferrer"
>
<External />
</a>
</h3>
<p className={styles.assetMeta}>
{symbol} | <code>{did}</code>
</p>
</div>
)
}
function DetailsAssets({ job }: { job: ComputeJobMetaData }) {
const { config } = useOcean()
const [algoName, setAlgoName] = useState<string>()
const [algoDtSymbol, setAlgoDtSymbol] = useState<string>()
useEffect(() => {
async function getAlgoMetadata() {
const source = axios.CancelToken.source()
const ddo = await retrieveDDO(
job.algoDID,
config.metadataCacheUri,
source.token
)
setAlgoDtSymbol(ddo.dataTokenInfo.symbol)
const { attributes } = ddo.findServiceByType('metadata')
setAlgoName(attributes?.main.name)
}
getAlgoMetadata()
}, [config?.metadataCacheUri, job.algoDID])
return (
<>
<Asset
title={job.assetName}
symbol={job.assetDtSymbol}
did={job.inputDID[0]}
/>
<Asset title={algoName} symbol={algoDtSymbol} did={job.algoDID} />
</>
)
}
export default function Details({
job
}: {
job: ComputeJobMetaData
}): ReactElement {
const [isDialogOpen, setIsDialogOpen] = useState(false)
return (
<>
<Button style="text" size="small" onClick={() => setIsDialogOpen(true)}>
Show Details
</Button>
<Modal
title={job.statusText}
isOpen={isDialogOpen}
onToggleModal={() => setIsDialogOpen(false)}
>
<DetailsAssets job={job} />
<Results job={job} />
<div className={styles.meta}>
<MetaItem
title="Created"
content={<Time date={job.dateCreated} isUnix relative />}
/>
{job.dateFinished && (
<MetaItem
title="Finished"
content={<Time date={job.dateFinished} isUnix relative />}
/>
)}
<MetaItem title="Job ID" content={<code>{job.jobId}</code>} />
{job.resultsDid && (
<MetaItem
title="Published Results DID"
content={<code>{job.resultsDid}</code>}
/>
)}
</div>
</Modal>
</>
)
}

View File

@ -0,0 +1,4 @@
.results {
composes: asset from './Details.module.css';
border-bottom-left-radius: var(--border-radius) !important;
}

View File

@ -0,0 +1,90 @@
import { Logger } from '@oceanprotocol/lib'
import React, { ReactElement, useState } from 'react'
import Loader from '../../../atoms/Loader'
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
import { ListItem } from '../../../atoms/Lists'
import Button from '../../../atoms/Button'
import { useOcean } from '../../../../providers/Ocean'
import styles from './Results.module.css'
export default function Results({
job
}: {
job: ComputeJobMetaData
}): ReactElement {
const { ocean, account } = useOcean()
const [isLoading, setIsLoading] = useState(false)
const [hasFetched, setHasFetched] = useState(false)
const isFinished = job.dateFinished !== null
async function getResults() {
if (!account || !ocean || !job) return
try {
setIsLoading(true)
const jobStatus = await ocean.compute.status(
account,
job.did,
undefined,
undefined,
job.jobId
)
if (jobStatus?.length > 0) {
job.algorithmLogUrl = jobStatus[0].algorithmLogUrl
job.resultsUrl = jobStatus[0].resultsUrl
}
} catch (error) {
Logger.error(error.message)
} finally {
setIsLoading(false)
setHasFetched(true)
}
}
return (
<div className={styles.results}>
{hasFetched ? (
<ul>
<ListItem>
{job.algorithmLogUrl ? (
<a href={job.algorithmLogUrl} target="_blank" rel="noreferrer">
View Log
</a>
) : (
'No logs found.'
)}
</ListItem>
{job.resultsUrl &&
Array.isArray(job.resultsUrl) &&
job.resultsUrl.map((url, i) =>
url ? (
<ListItem key={job.jobId}>
<a href={url} target="_blank" rel="noreferrer">
View Result {i + 1}
</a>
</ListItem>
) : (
<ListItem>No results found.</ListItem>
)
)}
</ul>
) : (
<Button
style="primary"
size="small"
onClick={() => getResults()}
disabled={isLoading || !isFinished}
>
{isLoading ? (
<Loader />
) : !isFinished ? (
'Waiting for results...'
) : (
'Get Results'
)}
</Button>
)}
</div>
)
}

View File

@ -1,21 +1,20 @@
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 { DDO, Logger, ServiceCommon, ServiceCompute } from '@oceanprotocol/lib'
import Dotdotdot from 'react-dotdotdot'
import Table from '../../atoms/Table'
import { useOcean } from '../../../providers/Ocean'
import { gql, useQuery } from '@apollo/client'
import { useWeb3 } from '../../../providers/Web3'
import { queryMetadata } from '../../../utils/aquarius'
import axios, { CancelToken } from 'axios'
import { ComputeOrders } from '../../../@types/apollo/ComputeOrders'
import web3 from 'web3'
import AssetTitle from '../../molecules/AssetListTitle'
import Time from '../../../atoms/Time'
import { Link } from 'gatsby'
import { DDO, Logger, Service, ServiceCompute } from '@oceanprotocol/lib'
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
import Dotdotdot from 'react-dotdotdot'
import Table from '../../../atoms/Table'
import { useOcean } from '../../../../providers/Ocean'
import { gql, useQuery } from '@apollo/client'
import { useWeb3 } from '../../../../providers/Web3'
import { queryMetadata } from '../../../../utils/aquarius'
import axios, { CancelToken } from 'axios'
import { ComputeOrders } from '../../../../@types/apollo/ComputeOrders'
import Details from './Details'
import styles from './index.module.css'
const getComputeOrders = gql`
query ComputeOrders($user: String!) {
tokenOrders(
@ -33,22 +32,6 @@ const getComputeOrders = gql`
}
}
`
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>
@ -57,36 +40,36 @@ export function Status({ children }: { children: string }): ReactElement {
const columns = [
{
name: 'Data Set',
selector: function getAssetRow(row: ComputeAsset) {
selector: function getAssetRow(row: ComputeJobMetaData) {
return (
<Dotdotdot clamp={2}>
<Link to={`/asset/${row.did}`}>{row.assetName}</Link>
<Link to={`/asset/${row.inputDID[0]}`}>{row.assetName}</Link>
</Dotdotdot>
)
}
},
{
name: 'Created',
selector: function getTimeRow(row: ComputeAsset) {
selector: function getTimeRow(row: ComputeJobMetaData) {
return <Time date={row.dateCreated} isUnix relative />
}
},
{
name: 'Finished',
selector: function getTimeRow(row: ComputeAsset) {
selector: function getTimeRow(row: ComputeJobMetaData) {
return <Time date={row.dateFinished} isUnix relative />
}
},
{
name: 'Status',
selector: function getStatus(row: ComputeAsset) {
selector: function getStatus(row: ComputeJobMetaData) {
return <Status>{row.statusText}</Status>
}
},
{
name: 'Actions',
selector: function getActions(row: ComputeAsset) {
return <DetailsButton row={row} />
selector: function getActions(row: ComputeJobMetaData) {
return <Details job={row} />
}
}
]
@ -94,8 +77,7 @@ const columns = [
async function getAssetMetadata(
queryDtList: string,
metadataCacheUri: string,
cancelToken: CancelToken,
timestamps: number[]
cancelToken: CancelToken
): Promise<DDO[]> {
const queryDid = {
page: 1,
@ -113,18 +95,11 @@ async function getAssetMetadata(
return result.results
}
interface ComputeAsset extends ComputeJobMetaData {
did: string
assetName: string
timestamp: number
type: string
}
export default function ComputeJobs(): ReactElement {
const { ocean, account, config } = useOcean()
const { accountId } = useWeb3()
const [isLoading, setIsLoading] = useState(false)
const [jobs, setJobs] = useState<ComputeAsset[]>([])
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
const { data } = useQuery<ComputeOrders>(getComputeOrders, {
variables: {
user: accountId?.toLowerCase()
@ -140,11 +115,9 @@ export default function ComputeJobs(): ReactElement {
setIsLoading(true)
const dtList = []
const dtTimestamps = []
const computeJobs: ComputeAsset[] = []
const computeJobs: ComputeJobMetaData[] = []
for (let i = 0; i < data.tokenOrders.length; i++) {
dtList.push(data.tokenOrders[i].datatokenId.address)
dtTimestamps.push(data.tokenOrders[i].timestamp)
}
const queryDtList = JSON.stringify(dtList)
@ -157,8 +130,7 @@ export default function ComputeJobs(): ReactElement {
const assets = await getAssetMetadata(
queryDtList,
config.metadataCacheUri,
source.token,
dtTimestamps
source.token
)
const providers: ServiceCompute[] = []
@ -173,7 +145,7 @@ export default function ComputeJobs(): ReactElement {
if (!ddo) continue
const service = ddo.service.filter(
(x: ServiceCommon) => x.index === data.tokenOrders[i].serviceId
(x: Service) => x.index === data.tokenOrders[i].serviceId
)[0]
if (!service || service.type !== 'compute') continue
@ -188,7 +160,7 @@ export default function ComputeJobs(): ReactElement {
providers.push(service as ServiceCompute)
// eslint-disable-next-line no-empty
} catch (err) {
console.log(err)
Logger.error(err.message)
}
}
@ -213,34 +185,25 @@ export default function ComputeJobs(): ReactElement {
})
for (let j = 0; j < computeJob.length; j++) {
const job = computeJob[j]
const did = job.inputDID[0]
const ddo = assets.filter((x) => x.id === did)[0]
const ddo = assets.filter((x) => x.id === job.inputDID[0])[0]
if (!ddo) continue
const serviceMetadata = ddo.service.filter(
(x: any) => x.type === 'metadata'
(x: Service) => x.type === 'metadata'
)[0]
const compJob = {
did: did,
jobId: job.jobId,
dateCreated: job.dateCreated,
dateFinished: job.dateFinished,
const compJob: ComputeJobMetaData = {
...job,
assetName: serviceMetadata.attributes.main.name,
status: job.status,
statusText: job.statusText,
algorithmLogUrl: '',
resultsUrls: [],
timestamp: data.tokenOrders[i].timestamp,
type: ''
} as ComputeAsset
assetDtSymbol: ddo.dataTokenInfo.symbol
}
computeJobs.push(compJob)
}
}
setJobs(computeJobs)
} catch (error) {
Logger.log(error.message)
Logger.error(error.message)
} finally {
setIsLoading(false)
}

View File

@ -18,11 +18,7 @@
.content {
margin-top: var(--spacer);
background-color: var(--background-content) !important;
}
.tabs div[class*='tabs'] {
background-color: var(--background-content);
background-color: var(--background-body);
}
.tabs ul[class*='tabList'] {

View File

@ -1,11 +1,11 @@
import React, { ReactElement } from 'react'
import ComputeJobs from './ComputeJobs'
import styles from './index.module.css'
import Tabs from '../../atoms/Tabs'
import PoolShares from './PoolShares'
import PoolTransactions from '../../molecules/PoolTransactions'
import PublishedList from './PublishedList'
import Downloads from './Downloads'
import Tabs from '../../atoms/Tabs'
import ComputeJobs from './ComputeJobs'
import styles from './index.module.css'
const tabs = [
{