Commit 3842da5c authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Adopted gui to new upload/calc api. Added sorting, and real pagination.

parent b29d3911
...@@ -62,22 +62,30 @@ class Upload { ...@@ -62,22 +62,30 @@ class Upload {
_assignFromJson(uploadJson, created) { _assignFromJson(uploadJson, created) {
Object.assign(this, uploadJson) Object.assign(this, uploadJson)
if (this.proc.current_task_name !== this.proc.task_names[0]) { if (this.current_task !== this.tasks[0]) {
this.uploading = 100 this.uploading = 100
} else if (!created && this.uploading === null) { } else if (!created && this.uploading === null) {
// if data came from server during a normal get (not create) and its still uploading // if data came from server during a normal get (not create) and its still uploading
// and the uploading is also not controlled locally then it ought to be a failure/abort // and the uploading is also not controlled locally then it ought to be a failure/abort
this.proc.status = 'FAILURE' this.status = 'FAILURE'
this.is_ready = true this.completed = true
this.proc.errors = ['upload failed, probably aborted'] this.errors = ['upload failed, probably aborted']
} }
} }
update() { get(page, perPage, orderBy, order) {
if (!page) page = 1
if (!perPage) perPage = 5
if (!orderBy) orderBy = 'mainfile'
if (!order) order = 'desc'
order = order === 'desc' ? -1 : 1
if (this.uploading !== null && this.uploading !== 100) { if (this.uploading !== null && this.uploading !== 100) {
return new Promise(resolve => resolve(this)) return new Promise(resolve => resolve(this))
} else { } else {
return fetch(`${apiBase}/uploads/${this.upload_id}`) const qparams = `page=${page}&per_page=${perPage}&order_by=${orderBy}&order=${order}`
return fetch(`${apiBase}/uploads/${this.upload_id}?${qparams}`)
.catch(networkError) .catch(networkError)
.then(handleResponseErrors) .then(handleResponseErrors)
.then(response => response.json()) .then(response => response.json())
...@@ -148,7 +156,7 @@ async function getMetaInfo() { ...@@ -148,7 +156,7 @@ async function getMetaInfo() {
if (cachedMetaInfo) { if (cachedMetaInfo) {
return cachedMetaInfo return cachedMetaInfo
} else { } else {
const loadMetaInfo = async (path) => { const loadMetaInfo = async(path) => {
return fetch(`${appStaticBase}/metainfo/meta_info/nomad_meta_info/${path}`) return fetch(`${appStaticBase}/metainfo/meta_info/nomad_meta_info/${path}`)
.catch(networkError) .catch(networkError)
.then(handleResponseErrors) .then(handleResponseErrors)
......
...@@ -10,32 +10,33 @@ import Link from 'react-router-dom/Link' ...@@ -10,32 +10,33 @@ import Link from 'react-router-dom/Link'
class CalcLink extends React.Component { class CalcLink extends React.Component {
static propTypes = { static propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
uploadHash: PropTypes.string.isRequired, calcId: PropTypes.string,
calcHash: PropTypes.string.isRequired uploadHash: PropTypes.string,
calcHash: PropTypes.string,
disabled: PropTypes.bool
} }
static styles = theme => ({ static styles = theme => ({
root: { root: {
overflow: 'hidden', overflow: 'hidden',
whiteSpace: 'nowrap', whiteSpace: 'nowrap'
textAlign: 'right'
} }
}); });
render() { render() {
const { uploadHash, calcHash, classes } = this.props const { uploadHash, calcHash, classes, calcId, disabled } = this.props
const archiveId = `${uploadHash}/${calcHash}` const id = calcId || `${uploadHash}/${calcHash}`
return ( return (
<div className={classes.root}> <div className={classes.root}>
<MuiThemeProvider theme={repoTheme}> <MuiThemeProvider theme={repoTheme}>
<IconButton color="primary" component={Link} to={`/repo/${archiveId}`}><RepoIcon /></IconButton> <IconButton color="primary" component={Link} to={`/repo/${id}`} disabled={disabled}><RepoIcon /></IconButton>
</MuiThemeProvider> </MuiThemeProvider>
<MuiThemeProvider theme={archiveTheme}> <MuiThemeProvider theme={archiveTheme}>
<IconButton color="primary" component={Link} to={`/archive/${archiveId}`}><ArchiveIcon /></IconButton> <IconButton color="primary" component={Link} to={`/archive/${id}`} disabled={disabled}><ArchiveIcon /></IconButton>
</MuiThemeProvider> </MuiThemeProvider>
<MuiThemeProvider theme={encTheme}> <MuiThemeProvider theme={encTheme}>
<IconButton color="primary" component={Link} to={`/enc/${archiveId}`}><EncIcon /></IconButton> <IconButton color="primary" component={Link} to={`/enc/${id}`} disabled={disabled}><EncIcon /></IconButton>
</MuiThemeProvider> </MuiThemeProvider>
</div> </div>
) )
......
...@@ -3,7 +3,9 @@ import PropTypes from 'prop-types' ...@@ -3,7 +3,9 @@ import PropTypes from 'prop-types'
import { withStyles, ExpansionPanel, ExpansionPanelSummary, Typography, import { withStyles, ExpansionPanel, ExpansionPanelSummary, Typography,
ExpansionPanelDetails, Stepper, Step, StepLabel, Table, TableRow, TableCell, TableBody, ExpansionPanelDetails, Stepper, Step, StepLabel, Table, TableRow, TableCell, TableBody,
Checkbox, FormControlLabel, TablePagination, TableHead, Tooltip, Checkbox, FormControlLabel, TablePagination, TableHead, Tooltip,
CircularProgress} from '@material-ui/core' CircularProgress,
LinearProgress,
TableSortLabel} from '@material-ui/core'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore' import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
import CalcLinks from './CalcLinks' import CalcLinks from './CalcLinks'
...@@ -64,38 +66,56 @@ class Upload extends React.Component { ...@@ -64,38 +66,56 @@ class Upload extends React.Component {
state = { state = {
upload: this.props.upload, upload: this.props.upload,
page: 1, params: {
rowsPerPage: 5 page: 1,
perPage: 5,
orderBy: 'mainfile',
order: 'desc'
},
loading: true, // its loading data from the server and the user should know about it
updating: true // it is still not complete and contineusly looking for updates
} }
updateUpload() { update(params) {
window.setTimeout(() => { const {page, perPage, orderBy, order} = params
this.state.upload.update() this.setState({loading: true})
.then(upload => { this.state.upload.get(page, perPage, orderBy, order)
console.assert(upload.proc, 'Uploads always must have a proc') .then(upload => {
this.setState({upload: upload}) const continueUpdating = upload.status !== 'SUCCESS' && upload.status !== 'FAILURE' && !upload.is_stale
if (upload.proc.status !== 'SUCCESS' && upload.proc.status !== 'FAILURE' && !upload.proc.is_stale) { this.setState({upload: upload, loading: false, params: params, updating: continueUpdating})
this.updateUpload() if (continueUpdating) {
} window.setTimeout(() => {
}) if (!this.state.loading) {
.catch(error => { this.update(this.state.params)
this.setState({upload: null}) }
this.props.raiseError(error) }, 500)
}) }
}, 500) })
.catch(error => {
this.setState({loading: false, ...params})
this.props.raiseError(error)
})
} }
componentDidMount() { componentDidMount() {
this.updateUpload() this.update(this.state.params)
} }
handleChangePage = (_, page) => { handleChangePage = (_, page) => {
this.setState({page: page + 1}) this.update({...this.state.params, page: page + 1})
} }
handleChangeRowsPerPage = event => { handleChangeRowsPerPage = event => {
const rowsPerPage = event.target.value const perPage = event.target.value
this.setState({rowsPerPage: rowsPerPage}) this.update({...this.state.params, perPage: perPage})
}
handleSort(orderBy) {
let order = 'desc'
if (this.state.params.orderBy === orderBy && this.state.params.order === 'desc') {
order = 'asc'
}
this.update({...this.state.params, orderBy: orderBy, order: order})
} }
onCheckboxChanged(_, checked) { onCheckboxChanged(_, checked) {
...@@ -123,16 +143,16 @@ class Upload extends React.Component { ...@@ -123,16 +143,16 @@ class Upload extends React.Component {
renderStepper() { renderStepper() {
const { classes } = this.props const { classes } = this.props
const { upload } = this.state const { upload } = this.state
const { calc_procs, task_names, current_task_name, status, errors } = upload.proc const { calcs, tasks, current_task, status, errors } = upload
let activeStep = task_names.indexOf(current_task_name) let activeStep = tasks.indexOf(current_task)
activeStep += (status === 'SUCCESS') ? 1 : 0 activeStep += (status === 'SUCCESS') ? 1 : 0
const labelPropsFactories = { const labelPropsFactories = {
uploading: (props) => { uploading: (props) => {
props.children = 'uploading' props.children = 'uploading'
const { uploading } = upload const { uploading } = upload
if (upload.proc.status !== 'FAILURE') { if (upload.status !== 'FAILURE') {
props.optional = ( props.optional = (
<Typography variant="caption"> <Typography variant="caption">
{uploading || 0}% {uploading || 0}%
...@@ -142,7 +162,7 @@ class Upload extends React.Component { ...@@ -142,7 +162,7 @@ class Upload extends React.Component {
}, },
extracting: (props) => { extracting: (props) => {
props.children = 'extracting' props.children = 'extracting'
if (current_task_name === 'extracting') { if (current_task === 'extracting') {
props.optional = ( props.optional = (
<Typography variant="caption"> <Typography variant="caption">
be patient be patient
...@@ -152,20 +172,20 @@ class Upload extends React.Component { ...@@ -152,20 +172,20 @@ class Upload extends React.Component {
}, },
parse_all: (props) => { parse_all: (props) => {
props.children = 'parse' props.children = 'parse'
if (calc_procs.length > 0) { if (calcs && calcs.pagination.total > 0) {
const failures = calc_procs.filter(calcProc => calcProc.status === 'FAILURE') const { total, successes, failures } = calcs.pagination
if (failures.length) {
if (failures) {
props.error = true props.error = true
props.optional = ( props.optional = (
<Typography variant="caption" color="error"> <Typography variant="caption" color="error">
{calc_procs.filter(p => p.status === 'SUCCESS').length}/{calc_procs.length} {successes + failures}/{total}, {failures} failed
, {failures.length} failed
</Typography> </Typography>
) )
} else { } else {
props.optional = ( props.optional = (
<Typography variant="caption"> <Typography variant="caption">
{calc_procs.filter(p => p.status === 'SUCCESS').length}/{calc_procs.length} {successes + failures}/{total}
</Typography> </Typography>
) )
} }
...@@ -180,7 +200,7 @@ class Upload extends React.Component { ...@@ -180,7 +200,7 @@ class Upload extends React.Component {
return ( return (
<Stepper activeStep={activeStep} classes={{root: classes.stepper}}> <Stepper activeStep={activeStep} classes={{root: classes.stepper}}>
{task_names.map((label, index) => { {tasks.map((label, index) => {
const labelProps = { const labelProps = {
children: label, children: label,
error: activeStep === index && status === 'FAILURE' error: activeStep === index && status === 'FAILURE'
...@@ -211,14 +231,15 @@ class Upload extends React.Component { ...@@ -211,14 +231,15 @@ class Upload extends React.Component {
renderCalcTable() { renderCalcTable() {
const { classes } = this.props const { classes } = this.props
const { page, rowsPerPage } = this.state const { page, perPage, orderBy, order } = this.state.params
const { calc_procs, status, upload_hash } = this.state.upload.proc const { calcs, status } = this.state.upload
const { pagination, results } = calcs
if (calc_procs.length === 0) { if (pagination.total === 0) {
if (this.state.upload.is_ready) { if (this.state.upload.completed) {
return ( return (
<Typography className={classes.detailsContent}> <Typography className={classes.detailsContent}>
{status === 'SUCCESS' ? 'No calculcations found.' : 'There are errors and no calculations to show.'} {status === 'SUCCESS' ? 'No calculcations found.' : 'No calculations to show.'}
</Typography> </Typography>
) )
} else { } else {
...@@ -230,8 +251,8 @@ class Upload extends React.Component { ...@@ -230,8 +251,8 @@ class Upload extends React.Component {
} }
} }
const renderRow = (calcProc, index) => { const renderRow = (calc, index) => {
const { mainfile, calc_hash, parser_name, task_names, current_task_name, status, errors } = calcProc const { mainfile, archive_id, parser, tasks, current_task, status, errors } = calc
const color = status === 'FAILURE' ? 'error' : 'default' const color = status === 'FAILURE' ? 'error' : 'default'
const row = ( const row = (
<TableRow key={index}> <TableRow key={index}>
...@@ -240,28 +261,32 @@ class Upload extends React.Component { ...@@ -240,28 +261,32 @@ class Upload extends React.Component {
{mainfile} {mainfile}
</Typography> </Typography>
<Typography variant="caption" color={color}> <Typography variant="caption" color={color}>
{calc_hash} {archive_id}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography color={color}> <Typography color={color}>
{parser_name.replace('parsers/', '')} {parser.replace('parsers/', '')}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography color={color}> <Typography color={color}>
{current_task_name} {current_task}
</Typography> </Typography>
<Typography variant="caption" color={color}> <Typography variant="caption" color={color}>
task&nbsp; task&nbsp;
<b> <b>
[{task_names.indexOf(current_task_name) + 1}/{task_names.length}] [{tasks.indexOf(current_task) + 1}/{tasks.length}]
</b> </b>
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
{status === 'SUCCESS' <Typography color={color}>
? <CalcLinks uploadHash={upload_hash} calcHash={calc_hash} /> : ''} {status.toLowerCase()}
</Typography>
</TableCell>
<TableCell>
<CalcLinks calcId={archive_id} disabled={status !== 'SUCCESS'} />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
...@@ -277,21 +302,45 @@ class Upload extends React.Component { ...@@ -277,21 +302,45 @@ class Upload extends React.Component {
} }
} }
const total = calc_procs.length const total = pagination.total
const emptyRows = rowsPerPage - Math.min(rowsPerPage, total - (page - 1) * rowsPerPage) const emptyRows = perPage - Math.min(perPage, total - (page - 1) * perPage)
const columns = [
{ id: 'mainfile', sort: true, label: 'mainfile' },
{ id: 'parser', sort: true, label: 'code' },
{ id: 'task', sort: false, label: 'task' },
{ id: 'status', sort: true, label: 'status' },
{ id: 'links', sort: false, label: 'links' }
]
return ( return (
<Table> <Table>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>mainfile</TableCell> {columns.map(column => (
<TableCell>code</TableCell> <TableCell key={column.id}>
<TableCell>task</TableCell> {column.sort
<TableCell></TableCell> ? <Tooltip
title="Sort"
placement={'bottom-start'}
enterDelay={300}
>
<TableSortLabel
active={orderBy === column.id}
direction={order}
onClick={() => this.handleSort(column.id)}
>
{column.label}
</TableSortLabel>
</Tooltip>
: column.label
}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{calc_procs.slice((page - 1) * rowsPerPage, page * rowsPerPage).map(renderRow)} {results.map(renderRow)}
{emptyRows > 0 && ( {emptyRows > 0 && (
<TableRow style={{ height: 57 * emptyRows }}> <TableRow style={{ height: 57 * emptyRows }}>
<TableCell colSpan={6} /> <TableCell colSpan={6} />
...@@ -300,7 +349,7 @@ class Upload extends React.Component { ...@@ -300,7 +349,7 @@ class Upload extends React.Component {
<TableRow> <TableRow>
<TablePagination <TablePagination
count={total} count={total}
rowsPerPage={rowsPerPage} rowsPerPage={perPage}
page={page - 1} page={page - 1}
onChangePage={this.handleChangePage} onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage} onChangeRowsPerPage={this.handleChangeRowsPerPage}
...@@ -320,7 +369,7 @@ class Upload extends React.Component { ...@@ -320,7 +369,7 @@ class Upload extends React.Component {
<ExpansionPanel> <ExpansionPanel>
<ExpansionPanelSummary <ExpansionPanelSummary
expandIcon={<ExpandMoreIcon/>} classes={{root: classes.summary}}> expandIcon={<ExpandMoreIcon/>} classes={{root: classes.summary}}>
{!upload.is_ready {!upload.completed
? <div className={classes.progress}> ? <div className={classes.progress}>
<CircularProgress size={32}/> <CircularProgress size={32}/>
</div> </div>
...@@ -336,11 +385,12 @@ class Upload extends React.Component { ...@@ -336,11 +385,12 @@ class Upload extends React.Component {
{this.renderTitle()} {this.renderStepper()} {this.renderTitle()} {this.renderStepper()}
</ExpansionPanelSummary> </ExpansionPanelSummary>
<ExpansionPanelDetails style={{width: '100%'}} classes={{root: classes.details}}> <ExpansionPanelDetails style={{width: '100%'}} classes={{root: classes.details}}>
{this.renderCalcTable()} {upload.calcs ? this.renderCalcTable() : ''}
{debug {debug
? <div className={classes.detailsContent}> ? <div className={classes.detailsContent}>
<ReactJson src={upload} enableClipboard={false} collapsed={0} /> <ReactJson src={upload} enableClipboard={false} collapsed={0} />
</div> : ''} </div> : ''}
{this.state.loading && !this.state.updating ? <LinearProgress/> : ''}
</ExpansionPanelDetails> </ExpansionPanelDetails>
</ExpansionPanel> </ExpansionPanel>
) )
......
...@@ -5,7 +5,7 @@ from elasticsearch.exceptions import NotFoundError ...@@ -5,7 +5,7 @@ from elasticsearch.exceptions import NotFoundError
from nomad import config, files from nomad import config, files
from nomad.utils import get_logger, create_uuid from nomad.utils import get_logger, create_uuid
from nomad.processing import Upload, Calc, NotAllowedDuringProcessing from nomad.processing import Upload, Calc, NotAllowedDuringProcessing, SUCCESS, FAILURE
from nomad.repo import RepoCalc from nomad.repo import RepoCalc
from nomad.user import me from nomad.user import me
...@@ -205,9 +205,13 @@ class UploadRes(Resource): ...@@ -205,9 +205,13 @@ class UploadRes(Resource):
except KeyError: except KeyError:
abort(404, message='Upload with id %s does not exist.' % upload_id) abort(404, message='Upload with id %s does not exist.' % upload_id)
page = int(request.args.get('page', 1)) try:
per_page = int(request.args.get('per_page', 10)) page = int(request.args.get('page', 1))
order_by = str(request.args.get('order_by', 'mainfile')) per_page = int(request.args.get('per_page', 10))
order_by = str(request.args.get('order_by', 'mainfile'))
order = int(str(request.args.get('order', -1)))
except Exception:
abort(400, message='invalid pagination or ordering')
try: try:
assert page >= 1 assert page >= 1
...@@ -215,14 +219,20 @@ class UploadRes(Resource): ...@@ -215,14 +219,20 @@ class UploadRes(Resource):
except AssertionError: except AssertionError:
abort(400, message='invalid pagination') abort(400, message='invalid pagination')
if order_by not in ['mainfile', 'status']: if order_by not in ['mainfile', 'status', 'parser']:
abort(400, message='invalid order_by field %s' % order_by) abort(400, message='invalid order_by field %s' % order_by)
order_by = ('-%s' if order == -1 else '+%s') % order_by
all_calcs = Calc.objects(upload_id=upload_id) all_calcs = Calc.objects(upload_id=upload_id)
total = all_calcs.count() total = all_calcs.count()
successes = Calc.objects(upload_id=upload_id, status=SUCCESS).count()
failures = Calc.objects(upload_id=upload_id, status=FAILURE).count()
calcs = all_calcs[(page - 1) * per_page:page * per_page].order_by(order_by) calcs = all_calcs[(page - 1) * per_page:page * per_page].order_by(order_by)
result['calcs'] = { result['calcs'] = {
'pagination': dict(total=total, page=page, per_page=per_page), 'pagination': dict(
total=total, page=page, per_page=per_page,
successes=successes, failures=failures),
'results': [calc