Commit 5f45c5e9 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Revised upload page.

parent 9ff15d7e
Pipeline #62965 passed with stages
in 29 minutes and 22 seconds
......@@ -68,7 +68,9 @@ class NavigationUnstyled extends React.Component {
}
static styles = theme => ({
root: {},
root: {
minWidth: 1024,
},
title: {
marginLeft: theme.spacing.unit,
flexGrow: 1,
......
......@@ -32,8 +32,7 @@ class DataTableToolbarUnStyled extends React.Component {
static styles = theme => ({
root: {
paddingLeft: theme.spacing.unit * 2,
paddingRight: theme.spacing.unit
paddingLeft: theme.spacing.unit * 3
},
selected: {
color: theme.palette.secondary.main
......
......@@ -217,10 +217,14 @@ class Api {
return upload
}
async getUnpublishedUploads() {
async getUploads(state, page, perPage) {
state = state || 'all'
page = page || 1
perPage = perPage || 10
this.onStartLoading()
return this.swagger()
.then(client => client.apis.uploads.get_uploads({state: 'unpublished', page: 1, per_page: 1000}))
.then(client => client.apis.uploads.get_uploads({state: state, page: page, per_page: perPage}))
.catch(handleApiError)
.then(response => ({
...response.body,
......@@ -233,20 +237,12 @@ class Api {
.finally(this.onFinishLoading)
}
async getUnpublishedUploads() {
return this.getUploads('unpublished', 1, 1000)
}
async getPublishedUploads(page, perPage) {
this.onStartLoading()
return this.swagger()
.then(client => client.apis.uploads.get_uploads({state: 'published', page: page || 1, per_page: perPage || 10}))
.catch(handleApiError)
.then(response => ({
...response.body,
results: response.body.results.map(uploadJson => {
const upload = new Upload(uploadJson, this)
upload.uploading = 100
return upload
})
}))
.finally(this.onFinishLoading)
return this.getUploads('published', 1, 10)
}
async archive(uploadId, calcId) {
......
......@@ -25,6 +25,8 @@ export class EntryListUnstyled extends React.Component {
domain: PropTypes.object.isRequired,
editable: PropTypes.bool,
columns: PropTypes.object,
title: PropTypes.string,
actions: PropTypes.element,
selectedColumns: PropTypes.arrayOf(PropTypes.string)
}
......@@ -38,9 +40,6 @@ export class EntryListUnstyled extends React.Component {
entryDetailsRow: {
paddingRight: theme.spacing.unit * 3
},
clickableRow: {
cursor: 'pointer'
}
})
state = {
......@@ -230,7 +229,7 @@ export class EntryListUnstyled extends React.Component {
}
render() {
const { classes, data, order, order_by, page, per_page, domain, editable } = this.props
const { classes, data, order, order_by, page, per_page, domain, editable, title, ...rest } = this.props
const { results, pagination: { total } } = data
const { selected } = this.state
......@@ -269,7 +268,7 @@ export class EntryListUnstyled extends React.Component {
return (
<div className={classes.root}>
<DataTable
title={`${total.toLocaleString()} ${domain.entryLabel}s`}
title={title || `${total.toLocaleString()} ${domain.entryLabel}s`}
selectActions={selectActions}
id={row => row.calc_id}
total={total}
......@@ -285,6 +284,7 @@ export class EntryListUnstyled extends React.Component {
onOrderChanged={(order, orderBy) => this.handleChange({order: order === 'asc' ? -1 : 1, order_by: orderBy})}
rows={per_page}
pagination={pagination}
{...rest}
/>
</div>
)
......
......@@ -3,7 +3,8 @@ import PropTypes from 'prop-types'
import { withStyles, ExpansionPanel, ExpansionPanelSummary, Typography,
ExpansionPanelDetails, Stepper, Step, StepLabel,
Checkbox, FormControlLabel, Tooltip,
CircularProgress} from '@material-ui/core'
CircularProgress,
IconButton} from '@material-ui/core'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import ReactJson from 'react-json-view'
import { compose } from 'recompose'
......@@ -12,17 +13,21 @@ import { withRouter } from 'react-router'
import { debug } from '../../config'
import EntryList, { EntryListUnstyled } from '../search/EntryList'
import { withDomain } from '../domains'
import DeleteIcon from '@material-ui/icons/Delete'
import PublishIcon from '@material-ui/icons/Publish'
import ConfirmDialog from './ConfirmDialog'
import PublishedIcon from '@material-ui/icons/Visibility'
import UnPublishedIcon from '@material-ui/icons/Lock'
import { withApi } from '../api'
class Upload extends React.Component {
static propTypes = {
classes: PropTypes.object.isRequired,
raiseError: PropTypes.func.isRequired,
api: PropTypes.func.isRequired,
upload: PropTypes.object.isRequired,
checked: PropTypes.bool,
onCheckboxChanged: PropTypes.func,
onDoesNotExist: PropTypes.func,
onPublished: PropTypes.func,
history: PropTypes.any.isRequired,
domain: PropTypes.object.isRequired,
}
......@@ -39,9 +44,6 @@ class Upload extends React.Component {
display: 'block',
overflowX: 'auto'
},
summary: {
overflowX: 'auto'
},
detailsContent: {
margin: theme.spacing.unit * 3
},
......@@ -75,7 +77,7 @@ class Upload extends React.Component {
whiteSpace: 'nowrap',
textAlign: 'right'
},
progress: {
icon: {
marginLeft: -theme.spacing.unit * 0.5,
width: theme.spacing.unit * 13 - 2,
alignItems: 'center',
......@@ -105,6 +107,10 @@ class Upload extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleDelete = this.handleDelete.bind(this)
this.handlePublishCancel = this.handlePublishCancel.bind(this)
this.handlePublishOpen = this.handlePublishOpen.bind(this)
this.handlePublishSubmit = this.handlePublishSubmit.bind(this)
}
componentDidUpdate(prevProps, prevState) {
......@@ -236,6 +242,38 @@ class Upload extends React.Component {
this.setState({params: {...this.state.params, ...changes}})
}
handleDelete() {
const { api, upload } = this.props
api.deleteUpload(upload.upload_id)
.then(() => this.update())
.catch(error => {
this.props.raiseError(error)
this.update()
})
}
handlePublishOpen() {
this.setState({showPublishDialog: true})
}
handlePublishSubmit(withEmbargo) {
const { api, upload } = this.props
api.publishUpload(upload.upload_id, withEmbargo)
.then(() => {
this.setState({showPublishDialog: false})
this.update()
})
.catch(error => {
this.props.raiseError(error)
this.setState({showPublishDialog: false})
this.update()
})
}
handlePublishCancel() {
this.setState({showPublishDialog: false})
}
onCheckboxChanged(_, checked) {
if (this.props.onCheckboxChanged) {
this.props.onCheckboxChanged(checked)
......@@ -400,7 +438,7 @@ class Upload extends React.Component {
renderCalcTable() {
const { classes } = this.props
const { columns } = this.state
const { columns, upload } = this.state
const { calcs, tasks_status, waiting } = this.state.upload
const { pagination, results } = calcs
......@@ -435,53 +473,68 @@ class Upload extends React.Component {
}))
}
const running = upload.tasks_running || upload.process_running
const actions = upload.published ? '' : <React.Fragment>
<IconButton>
<Tooltip title="Delete upload" disable={running} onClick={this.handleDelete}>
<DeleteIcon />
</Tooltip>
</IconButton>
<IconButton disable={running || tasks_status !== 'SUCCESS'} onClick={this.handlePublishOpen}>
<Tooltip title="Publish upload">
<PublishIcon />
</Tooltip>
</IconButton>
</React.Fragment>
return <EntryList
title={`Upload with ${data.pagination.total} detected entries`}
query={{upload_id: this.props.upload_id}}
columns={columns}
selectedColumns={Upload.defaultSelectedColumns}
editable
editable={tasks_status === 'SUCCESS'}
data={data}
onChange={this.handleChange}
actions={actions}
{...this.state.params}
/>
}
renderCheckBox() {
renderStatusIcon() {
const { classes } = this.props
const { upload } = this.state
if (upload.tasks_running || upload.process_running) {
return <div className={classes.progress}>
<CircularProgress size={32}/>
const render = (icon, tooltip) => (
<div className={classes.icon}>
<Tooltip title={tooltip}>
{icon}
</Tooltip>
</div>
} else if (!upload.published) {
return <FormControlLabel control={(
<Checkbox
checked={this.props.checked}
className={classes.checkbox}
onClickCapture={(e) => e.stopPropagation()}
onChange={this.onCheckboxChanged.bind(this)}
/>
)}/>
)
if (upload.tasks_running || upload.process_running) {
return render(<CircularProgress size={32}/>, '')
} else if (upload.published) {
return render(<PublishedIcon size={32} color="action"/>, 'This upload is published')
} else {
return ''
return render(<UnPublishedIcon size={32} color="primary"/>, 'This upload is not published yet, and only visible to you')
}
}
render() {
const { classes } = this.props
const { upload } = this.state
const { upload, showPublishDialog } = this.state
const { errors } = upload
if (this.state.upload) {
return (
<div className={classes.root}>
<ExpansionPanel>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon/>}
classes={{root: classes.summary}}>
{this.renderCheckBox()} {this.renderTitle()} {this.renderStepper()}
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>} >
{this.renderStatusIcon()}
{this.renderTitle()}
{this.renderStepper()}
</ExpansionPanelSummary>
<ExpansionPanelDetails style={{width: '100%'}} classes={{root: classes.details}}>
{errors && errors.length > 0
......@@ -496,6 +549,11 @@ class Upload extends React.Component {
</div> : ''}
</ExpansionPanelDetails>
</ExpansionPanel>
<ConfirmDialog
open={showPublishDialog}
onClose={this.handlePublishCancel}
onPublish={this.handlePublishSubmit}
/>
</div>
)
} else {
......@@ -504,4 +562,4 @@ class Upload extends React.Component {
}
}
export default compose(withRouter, withErrors, withDomain, withStyles(Upload.styles))(Upload)
export default compose(withRouter, withErrors, withApi(true, false), withDomain, withStyles(Upload.styles))(Upload)
......@@ -7,8 +7,8 @@ import Dropzone from 'react-dropzone'
import Upload from './Upload'
import { compose } from 'recompose'
import DeleteIcon from '@material-ui/icons/Delete'
import ReloadIcon from '@material-ui/icons/Cached'
import CheckIcon from '@material-ui/icons/Check'
import ReloadIcon from '@material-ui/icons/Cached'
import MoreIcon from '@material-ui/icons/MoreHoriz'
import ClipboardIcon from '@material-ui/icons/Assignment'
import ConfirmDialog from './ConfirmDialog'
......@@ -139,16 +139,15 @@ class Uploads extends React.Component {
marginRight: theme.spacing.unit,
overflow: 'hidden'
},
selectFormGroup: {
paddingLeft: theme.spacing.unit * 3
formGroup: {
paddingLeft: 0
},
selectLabel: {
uploadsLabel: {
flexGrow: 1,
paddingLeft: 0,
padding: theme.spacing.unit * 2
},
uploads: {
marginTop: theme.spacing.unit * 2
},
uploadsContainer: {
marginTop: theme.spacing.unit * 4
},
pagination: {
......@@ -156,18 +155,23 @@ class Uploads extends React.Component {
}
})
defaultData = {
results: [],
pagination: {
total: 0,
per_page: 10,
page: 1
}
}
state = {
unpublishedUploads: null,
publishedUploads: null,
publishedUploadsPage: 1,
publishedUploadsTotal: 0,
uploadCommand: {
upload_command: 'loading ...',
upload_tar_command: 'loading ...',
upload_progress_command: 'loading ...'
},
selectedUnpublishedUploads: [],
showPublishDialog: false
data: {...this.defaultData},
uploading: []
}
componentDidMount() {
......@@ -181,77 +185,33 @@ class Uploads extends React.Component {
})
}
update(publishedUploadsPage) {
this.props.api.getUnpublishedUploads()
.then(uploads => {
// const filteredUploads = uploads.filter(upload => !upload.is_state)
this.setState({unpublishedUploads: uploads.results, selectedUnpublishedUploads: []})
})
.catch(error => {
this.setState({unpublishedUploads: [], selectedUnpublishedUploads: []})
this.props.raiseError(error)
})
this.props.api.getPublishedUploads(publishedUploadsPage, publishedUploadsPageSize)
update(newPage) {
const { data: { pagination: { page, per_page }}} = this.state
this.props.api.getUploads('all', newPage || page, per_page)
.then(uploads => {
this.setState({
publishedUploads: uploads.results,
publishedUploadsTotal: uploads.pagination.total,
publishedUploadsPage: uploads.pagination.page})
})
.catch(error => {
this.setState({publishedUploads: []})
this.props.raiseError(error)
})
}
onDeleteClicked() {
Promise.all(this.state.selectedUnpublishedUploads.map(upload => this.props.api.deleteUpload(upload.upload_id)))
.then(() => this.update())
.catch(error => {
this.props.raiseError(error)
this.update()
})
}
onPublishClicked() {
this.setState({showPublishDialog: true})
}
onPublish(withEmbargo) {
Promise.all(this.state.selectedUnpublishedUploads
.map(upload => this.props.api.publishUpload(upload.upload_id, withEmbargo)))
.then(() => {
this.setState({showPublishDialog: false})
return this.update()
this.setState({data: uploads})
})
.catch(error => {
this.setState({data: {...this.defaultData}})
this.props.raiseError(error)
this.update()
})
}
sortedUnpublishedUploads(order) {
order = order || -1
return this.state.unpublishedUploads.concat()
.sort((a, b) => (a.gui_upload_id === b.gui_upload_id)
? 0
: ((a.gui_upload_id < b.gui_upload_id) ? -1 : 1) * order)
}
handleDoesNotExist(nonExistingUpload) {
this.setState({
unpublishedUploads: this.state.unpublishedUploads.filter(upload => upload !== nonExistingUpload)
})
// this.setState({
// unpublishedUploads: this.state.unpublishedUploads.filter(upload => upload !== nonExistingUpload)
// })
this.update()
}
handlePublished(publishedUpload) {
handlePublished() {
this.update()
}
onDrop(files, rejectedFiles) {
const upload = file => {
const upload = this.props.api.createUpload(file.name)
this.setState({unpublishedUploads: [...this.state.unpublishedUploads, upload]})
this.setState({uploading: [...this.state.uploading, upload]})
upload.uploadFile(file).catch(this.props.raiseError)
}
......@@ -261,129 +221,38 @@ class Uploads extends React.Component {
.forEach(upload)
}
onSelectionChanged(upload, checked) {
if (checked) {
this.setState({selectedUnpublishedUploads: [upload, ...this.state.selectedUnpublishedUploads]})
} else {
const selectedUnpublishedUploads = [...this.state.selectedUnpublishedUploads]
selectedUnpublishedUploads.splice(selectedUnpublishedUploads.indexOf(upload), 1)
this.setState({selectedUnpublishedUploads: selectedUnpublishedUploads})
}
}
onSelectionAllChanged(checked) {
if (checked) {
this.setState({selectedUnpublishedUploads: [...this.state.unpublishedUploads.filter(upload => !upload.tasks_running)]})
} else {
this.setState({selectedUnpublishedUploads: []})
}
}
renderPublishedUploads() {
renderUploads() {
const { classes } = this.props
const { publishedUploadsTotal, publishedUploadsPage, publishedUploads } = this.state
const { data: { results, pagination: { total, per_page, page }}, uploading } = this.state
if (!publishedUploads || publishedUploads.length === 0) {
if (total === 0) {
return ''
}
return (<div className={classes.uploadsContainer}>
<FormLabel className={classes.uploadsLabel}>Your published uploads: </FormLabel>
<div className={classes.uploads}>
<div>
{
publishedUploads.map(upload => (
<Upload key={upload.gui_upload_id} upload={upload}
checked={false}
onCheckboxChanged={checked => true}/>
))
}
{
(publishedUploadsTotal > publishedUploadsPageSize)
? <Pagination classes={{root: classes.pagination}}
limit={publishedUploadsPageSize}
offset={(publishedUploadsPage - 1) * publishedUploadsPageSize}
total={publishedUploadsTotal}
onClick={(_, offset) => this.update((offset / publishedUploadsPageSize) + 1)}
previousPageLabel={'prev'}
nextPageLabel={'next'}
/> : ''
}
</div>
</div>
</div>)
}
renderUnpublishedUploads() {
const { classes } = this.props
const { selectedUnpublishedUploads, showPublishDialog } = this.state
const unpublishedUploads = this.state.unpublishedUploads || []
const reloadButton = <Tooltip title="Reload uploads, e.g. after using the curl upload" >
<IconButton onClick={() => this.update()}><ReloadIcon /></IconButton>
</Tooltip>
return (<div className={classes.uploadsContainer}>
<div style={{width: '100%'}}>
{(unpublishedUploads.length === 0) ? ''
: <FormLabel className={classes.uploadsLabel}>Your unpublished uploads: </FormLabel>
}
<FormGroup className={classes.selectFormGroup} style={{alignItems: 'center'}}row>
{(unpublishedUploads.length === 0) ? <FormLabel label="all" style={{flexGrow: 1}}>You have currently no unpublished uploads</FormLabel>
: <FormControlLabel label="all" style={{flexGrow: 1}} control={(
<Checkbox
checked={selectedUnpublishedUploads.length === unpublishedUploads.length && unpublishedUploads.length !== 0}
onChange={(_, checked) => this.onSelectionAllChanged(checked)}
/>
)} />
}
{reloadButton}
<FormLabel classes={{root: classes.selectLabel}}>
{`selected uploads ${selectedUnpublishedUploads.length}/${unpublishedUploads.length}`}
</FormLabel>
<Tooltip title="Delete selected uploads" >
<div>
<IconButton
disabled={selectedUnpublishedUploads.length === 0}
onClick={this.onDeleteClicked.bind(this)}
>
<DeleteIcon />
</IconButton>
</div>
</Tooltip>
<Tooltip title="Publish selected uploads" >
<div>
<IconButton
disabled={selectedUnpublishedUploads.length === 0 || selectedUnpublishedUploads.some(upload => upload.tasks_status !== 'SUCCESS' || upload.total_calcs === 0)}
onClick={() => this.onPublishClicked()}>
<CheckIcon />
</IconButton>
</div>
</Tooltip>
<ConfirmDialog
open={showPublishDialog}
onClose={() => this.setState({showPublishDialog: false})}
onPublish={(withEmbargo) => this.onPublish(withEmbargo)}
/>
</FormGroup>
</div>