takuseno/minerva

View on GitHub
front/components/Header.js

Summary

Maintainability
A
0 mins
Test Coverage
import 'react-sweet-progress/lib/style.css'
import '../styles/header.scss'
import { Link, useLocation } from 'react-router-dom'
import React, { useContext, useState } from 'react'
import { CpuIcon } from '@primer/octicons-react'
import { GlobalContext } from '../context'
import { Progress } from 'react-sweet-progress'
import { Range } from 'immutable'

const JobItem = (props) => {
  const { experiment, project } = props
  const { metrics } = experiment

  const totalEpoch = experiment.config.n_epochs
  const currentEpoch = metrics.td_error ? metrics.td_error.length : 0
  const progress = currentEpoch / totalEpoch

  return (
    <div className='job-item'>
      <div className='job-progress'>
        <Progress
          type='circle'
          percent={100.0 * progress}
          strokeWidth='10'
          width='35'
          status='active'
          theme={{
            active: {
              symbol: `${Math.round(100.0 * progress).toString()}%`,
              color: '#3498db'
            }
          }}
        />
      </div>
      <div className='job-description'>
        <p className='project-name'>Project: {project.name}</p>
        <p className='experiment-name'>{experiment.name}</p>
      </div>
    </div>
  )
}

const JobList = (props) => props.jobs.map((experiment) => {
  const { projects } = props
  const { projectId } = experiment
  const project = projects.find((p) => p.id === projectId)
  return (
    <li key={experiment.id}>
      <JobItem experiment={experiment} project={project} />
    </li>
  )
})

const JobFloatPanel = (props) => {
  const { projects } = props
  const cpuJobs = props.status.cpu.jobs
  const totalGPU = props.status.gpu.total
  const gpuJobs = props.status.gpu.jobs
  return (
    <div className='job-list'>
      <div className='jobs'>
        <p className='device-name'>CPU</p>
        {cpuJobs.length === 0 &&
          <p className='empty-message'>No Jobs</p>}
        {cpuJobs.length > 0 &&
          <ul>
            <JobList projects={projects} jobs={cpuJobs} />
          </ul>}
      </div>
      {Range(0, totalGPU).toJS().map((i) => (
        <div key={i} className='jobs'>
          <p className='device-name'>GPU:{i}</p>
          {(gpuJobs[i] === undefined || gpuJobs[i].length === 0) &&
            <p className='empty-message'>No Jobs</p>}
          {gpuJobs[i] !== undefined && gpuJobs[i].length > 0 &&
            <ul>
              <JobList projects={projects} jobs={gpuJobs[i]} />
            </ul>}
        </div>
      ))}
    </div>
  )
}

export const Header = () => {
  const { status, projects } = useContext(GlobalContext)
  const [isJobListOpen, setIsJobListOpen] = useState(false)
  const location = useLocation()
  return (
    <div className='header'>
      <div className='logo'>
        <span>MINERVA</span>
      </div>
      <div className='links'>
        {['projects', 'datasets'].map((link) => {
          const isActive = location.pathname.split('/')[1] === link
          const className = isActive ? 'link active' : 'link'
          const to = `/${link}`
          return (
            <Link key={link} to={to} className={className}>
              <span>{link.toUpperCase()}</span>
            </Link>
          )
        })}
      </div>
      <div className='popups'>
        <div className='button'>
          <div onClick={() => setIsJobListOpen(!isJobListOpen)}>
            <CpuIcon size='large' />
          </div>
          {isJobListOpen && status.cpu !== undefined &&
            <JobFloatPanel status={status} projects={projects} />}
        </div>
      </div>
    </div>
  )
}