diff --git a/docs/.DS_Store b/docs/.DS_Store index 560ae6fbf457a39fcfc8c2f24862414f0648c4c3..efcf6d48ca6cbe1f32bb3708f624e8a39f3a1ada 100644 Binary files a/docs/.DS_Store and b/docs/.DS_Store differ diff --git a/gui/package.json b/gui/package.json index a13d638e39d008ccea91741f198347b6e2b0c2ac..b59efaf9ba76643868ac3ada3c895ad2bfa4237a 100644 --- a/gui/package.json +++ b/gui/package.json @@ -35,6 +35,7 @@ "eslint": "^4.19.1", "eslint-config-standard": "^11.0.0", "eslint-plugin-import": "^2.14.0", + "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^3.7.0", "eslint-plugin-react": "^7.11.1", "eslint-plugin-standard": "^3.1.0", diff --git a/gui/src/api.js b/gui/src/api.js index 9b33b4474e11d4ad0fa013d8b566376befa4093b..45cdab8ca3b2c8dd50a7e90f36e210bd7731d8c8 100644 --- a/gui/src/api.js +++ b/gui/src/api.js @@ -18,31 +18,47 @@ const swaggerPromise = Swagger(`${apiBase}/swagger.json`, { } }) -const networkError = (e) => { - console.log(e) - throw Error('Network related error, cannot reach API: ' + e) -} - -const handleJsonErrors = (e) => { - console.log(e) - throw Error('API return unexpected data format.') +export class DoesNotExist extends Error { + constructor(msg) { + super(msg) + this.name = 'DoesNotExist' + } } -const handleResponseErrors = (response) => { - if (!response.ok) { - return response.json() - .catch(() => { - throw Error(`API error (${response.status}): ${response.statusText}`) - }).then(data => { - throw Error(`API error (${response.status}): ${data.message}`) - }) +const handleApiError = (e) => { + if (e.response) { + const body = e.response.body + const message = (body && body.message) ? body.message : e.response.statusText + if (e.response.status === 404) { + throw new DoesNotExist(message) + } else { + throw Error(`API error (${e.response.status}): ${message}`) + } + } else { + throw Error('Network related error, cannot reach API: ' + e) } - return response } +const upload_to_gui_ids = {} +let gui_upload_id_counter = 0 + class Upload { constructor(json) { - this.uploading = 0 + // Cannot use upload_id as key in GUI, because uploads don't have an upload_id + // before upload is completed + if (json.upload_id) { + // instance from the API + this.gui_upload_id = upload_to_gui_ids[json.upload_id] + if (this.gui_upload_id === undefined) { + // never seen in the GUI, needs a GUI id + this.gui_upload_id = gui_upload_id_counter++ + upload_to_gui_ids[json.upload_id] = this.gui_upload_id + console.log('new gui ui') + } + } else { + // new instance, not from the API + this.gui_upload_id = gui_upload_id_counter++ + } Object.assign(this, json) } @@ -65,13 +81,14 @@ class Upload { } ) if (uploadRequest.error) { - networkError(uploadRequest.error) + handleApiError(uploadRequest.error) } if (uploadRequest.aborted) { throw Error('User abort') } this.uploading = 100 this.upload_id = uploadRequest.response.upload_id + upload_to_gui_ids[this.upload_id] = this.gui_upload_id } return uploadFileWithProgress() @@ -84,14 +101,13 @@ class Upload { } else { if (this.upload_id) { return swaggerPromise.then(client => client.apis.uploads.get_upload({ - upload_id: this.upload_id, - page: page || 1, - per_page: perPage || 5, - order_by: orderBy || 'mainfile', - order: order || -1 - })) - .catch(networkError) - .then(handleResponseErrors) + upload_id: this.upload_id, + page: page || 1, + per_page: perPage || 5, + order_by: orderBy || 'mainfile', + order: order || -1 + })) + .catch(handleApiError) .then(response => response.body) .then(uploadJson => { Object.assign(this, uploadJson) @@ -107,7 +123,7 @@ class Upload { function createUpload(name) { return new Upload({ name: name, - tasks: ['uploading'], + tasks: ['uploading', 'extract', 'parse_all', 'cleanup'], current_task: 'uploading', uploading: 0, create_time: new Date() @@ -117,8 +133,7 @@ function createUpload(name) { async function getUploads() { const client = await swaggerPromise return client.apis.uploads.get_uploads() - .catch(networkError) - .then(handleResponseErrors) + .catch(handleApiError) .then(response => response.body.map(uploadJson => { const upload = new Upload(uploadJson) upload.uploading = 100 @@ -129,11 +144,10 @@ async function getUploads() { async function archive(uploadId, calcId) { const client = await swaggerPromise return client.apis.archive.get_archive_calc({ - upload_id: uploadId, - calc_id: calcId - }) - .catch(networkError) - .then(handleResponseErrors) + upload_id: uploadId, + calc_id: calcId + }) + .catch(handleApiError) .then(response => response.body) } @@ -143,61 +157,47 @@ async function calcProcLog(uploadId, calcId) { upload_id: uploadId, calc_id: calcId }) - .catch(networkError) - .then(response => { - if (!response.ok) { - if (response.status === 404) { - return '' - } else { - return handleResponseErrors(response) - } - } else { - return response.text - } - }) + .catch(handleApiError) + .then(response => response.text) } async function repo(uploadId, calcId) { const client = await swaggerPromise return client.apis.repo.get_repo_calc({ - upload_id: uploadId, - calc_id: calcId - }) - .catch(networkError) - .then(handleResponseErrors) + upload_id: uploadId, + calc_id: calcId + }) + .catch(handleApiError) .then(response => response.body) } async function repoAll(page, perPage, owner) { const client = await swaggerPromise return client.apis.repo.get_calcs({ - page: page, - per_page: perPage, - ower: owner || 'all' - }) - .catch(networkError) - .then(handleResponseErrors) + page: page, + per_page: perPage, + ower: owner || 'all' + }) + .catch(handleApiError) .then(response => response.body) } async function deleteUpload(uploadId) { const client = await swaggerPromise return client.apis.uploads.delete_upload({upload_id: uploadId}) - .catch(networkError) - .then(handleResponseErrors) + .catch(handleApiError) .then(response => response.body) } async function commitUpload(uploadId) { const client = await swaggerPromise return client.apis.uploads.exec_upload_command({ - upload_id: uploadId, - payload: { - operation: 'commit' - } - }) - .catch(networkError) - .then(handleResponseErrors) + upload_id: uploadId, + payload: { + operation: 'commit' + } + }) + .catch(handleApiError) .then(response => response.body) } @@ -208,11 +208,11 @@ async function getMetaInfo() { return cachedMetaInfo } else { const loadMetaInfo = async(path) => { - return fetch(`${apiBase}/archive/metainfo/${path}`) - .catch(networkError) - .then(handleResponseErrors) - .then(response => response.json()) - .catch(handleJsonErrors) + const client = await swaggerPromise + console.log(path) + return client.apis.archive.get_metainfo({metainfo_path: path}) + .catch(handleApiError) + .then(response => response.body) .then(data => { if (!cachedMetaInfo) { cachedMetaInfo = { @@ -243,8 +243,7 @@ async function getMetaInfo() { async function getUploadCommand() { const client = await swaggerPromise return client.apis.uploads.get_upload_command() - .catch(networkError) - .then(handleResponseErrors) + .catch(handleApiError) .then(response => response.body.upload_command) } diff --git a/gui/src/components/Upload.js b/gui/src/components/Upload.js index cb8b261961bf52e4d557bbfb2100fc9020d39079..c9462d57c8cd7fb8b70348287f679931cf53b0df 100644 --- a/gui/src/components/Upload.js +++ b/gui/src/components/Upload.js @@ -20,7 +20,8 @@ class Upload extends React.Component { raiseError: PropTypes.func.isRequired, upload: PropTypes.object.isRequired, checked: PropTypes.bool, - onCheckboxChanged: PropTypes.func + onCheckboxChanged: PropTypes.func, + onDoesNotExist: PropTypes.func } static styles = theme => ({ @@ -77,7 +78,7 @@ class Upload extends React.Component { params: { page: 1, perPage: 5, - orderBy: 'status', + orderBy: 'tasks_status', order: 'asc' }, archiveLogs: null, // { uploadId, calcId } ids of archive to show logs for @@ -96,8 +97,9 @@ class Upload extends React.Component { this.setState({loading: true}) this.state.upload.get(page, perPage, orderBy, order === 'asc' ? 1 : -1) .then(upload => { + const {tasks_running, process_running, current_task} = upload if (!this._unmounted) { - const continueUpdating = upload.status !== 'SUCCESS' && upload.status !== 'FAILURE' && !upload.is_stale + const continueUpdating = tasks_running || process_running || current_task === 'uploading' this.setState({upload: upload, loading: false, params: params, updating: continueUpdating}) if (continueUpdating) { window.setTimeout(() => { @@ -111,7 +113,11 @@ class Upload extends React.Component { .catch(error => { if (!this._unmounted) { this.setState({loading: false, ...params}) - this.props.raiseError(error) + if (error.name === 'DoesNotExist') { + this.props.onDoesNotExist() + } else { + this.props.raiseError(error) + } } }) } @@ -120,6 +126,12 @@ class Upload extends React.Component { this.update(this.state.params) } + componentDidUpdate(prevProps) { + if (!prevProps.upload.process_running && this.props.upload.process_running) { + this.update(this.state.params) + } + } + componentWillUnmount() { this._unmounted = true } @@ -169,73 +181,119 @@ class Upload extends React.Component { renderStepper() { const { classes } = this.props const { upload } = this.state - const { calcs, tasks, current_task, status, errors } = upload - - let activeStep = tasks.indexOf(current_task) - activeStep += (status === 'SUCCESS') ? 1 : 0 + const { calcs, tasks, current_task, tasks_running, tasks_status, process_running, current_process, errors } = upload + + // map tasks [ uploading, extracting, parse_all, cleanup ] to steps + const steps = [ 'upload', 'process', 'commit' ] + let step = null + const task_index = tasks.indexOf(current_task) + if (task_index === 0) { + step = 'upload' + } else if (task_index > 0 && tasks_running) { + step = 'process' + } else { + step = 'commit' + } + const stepIndex = steps.indexOf(step) const labelPropsFactories = { - uploading: (props) => { - props.children = 'uploading' - const { uploading } = upload - if (upload.status !== 'FAILURE') { - props.optional = ( - <Typography variant="caption"> - {uploading === 100 && current_task === tasks[0] ? 'waiting for processing ...' : `${uploading || 0}%`} - </Typography> - ) + upload: (props) => { + if (step === 'upload') { + props.children = 'uploading' + const { uploading } = upload + if (upload.tasks_status !== 'FAILURE') { + props.optional = ( + <Typography variant="caption"> + {`${uploading || 0}%`} + </Typography> + ) + } + } else { + props.children = 'uploaded' } }, - extracting: (props) => { - props.children = 'extracting' + process: (props) => { + props.error = tasks_status === 'FAILURE' + + const processIndex = steps.indexOf('process') + if (stepIndex <= processIndex) { + props.children = 'processing' + } else { + props.children = 'processed' + } + if (current_task === 'extracting') { + props.children = 'extracting' props.optional = ( <Typography variant="caption"> be patient </Typography> ) + } else if (current_task === 'parse_all') { + props.children = 'parsing' } - }, - parse_all: (props) => { - props.children = 'parse' - if (!calcs) { - props.optional = ( - <Typography variant="caption" > - loading... - </Typography> - ) - } else if (calcs.pagination.total > 0) { - const { total, successes, failures } = calcs.pagination - if (failures) { - props.error = true + if (stepIndex >= processIndex) { + if (!calcs) { props.optional = ( - <Typography variant="caption" color="error"> - {successes + failures}/{total}, {failures} failed + <Typography variant="caption" > + matching... </Typography> ) - } else { + } else if (calcs.pagination.total > 0) { + const { total, successes, failures } = calcs.pagination + if (failures) { + props.error = true + props.optional = ( + <Typography variant="caption" color="error"> + {successes + failures}/{total}, {failures} failed + </Typography> + ) + } else { + props.optional = ( + <Typography variant="caption"> + {successes + failures}/{total} + </Typography> + ) + } + } else if (tasks_status === 'SUCCESS') { + props.error = true props.optional = ( - <Typography variant="caption"> - {successes + failures}/{total} - </Typography> + <Typography variant="caption" color="error">No calculations found.</Typography> ) } - } else if (status === 'SUCCESS') { - props.error = true + } + + if (tasks_status === 'FAILURE') { props.optional = ( - <Typography variant="caption" color="error">No calculations found.</Typography> + <Typography variant="caption" color="error"> + {errors.join(' ')} + </Typography> ) } + }, + commit: (props) => { + props.children = 'inspect' + + if (process_running) { + if (current_process === 'commit_upload') { + props.children = 'approved' + props.optional = <Typography variant="caption">moving data ...</Typography> + } else if (current_process === 'delete_upload') { + props.children = 'declined' + props.optional = <Typography variant="caption">deleting data ...</Typography> + } + } else { + props.optional = <Typography variant="caption">commit or delete</Typography> + } } } return ( - <Stepper activeStep={activeStep} classes={{root: classes.stepper}}> - {tasks.map((label, index) => { + <Stepper activeStep={steps.indexOf(step)} classes={{root: classes.stepper}}> + {steps.map((label, index) => { const labelProps = { - children: label, - error: activeStep === index && status === 'FAILURE' + children: label } const labelPropsFactory = labelPropsFactories[label] @@ -243,14 +301,6 @@ class Upload extends React.Component { labelPropsFactory(labelProps) } - if (labelProps.error && status === 'FAILURE') { - labelProps.optional = ( - <Typography variant="caption" color="error"> - {errors.join(' ')} - </Typography> - ) - } - return ( <Step key={label}> <StepLabel {...labelProps} /> @@ -264,14 +314,14 @@ class Upload extends React.Component { renderCalcTable() { const { classes } = this.props const { page, perPage, orderBy, order } = this.state.params - const { calcs, status, waiting } = this.state.upload + const { calcs, tasks_status, waiting } = this.state.upload const { pagination, results } = calcs if (pagination.total === 0) { - if (this.state.upload.completed) { + if (!this.state.upload.tasks_running) { return ( <Typography className={classes.detailsContent}> - {status === 'SUCCESS' ? 'No calculcations found.' : 'No calculations to show.'} + {tasks_status === 'SUCCESS' ? 'No calculcations found.' : 'No calculations to show.'} </Typography> ) } else { @@ -292,8 +342,8 @@ class Upload extends React.Component { } const renderRow = (calc, index) => { - const { mainfile, calc_id, upload_id, parser, tasks, current_task, status, errors } = calc - const color = status === 'FAILURE' ? 'error' : 'default' + const { mainfile, calc_id, upload_id, parser, tasks, current_task, tasks_status, errors } = calc + const color = tasks_status === 'FAILURE' ? 'error' : 'default' const row = ( <TableRow key={index}> <TableCell> @@ -322,22 +372,20 @@ class Upload extends React.Component { </TableCell> <TableCell> <Typography color={color}> - {(status === 'SUCCESS' || status === 'FAILURE') - ? - <a className={classes.logLink} href="#logs" onClick={() => this.setState({archiveLogs: { uploadId: upload_id, calcId: calc_id }})}> - {status.toLowerCase()} - </a> - : status.toLowerCase() + {(tasks_status === 'SUCCESS' || tasks_status === 'FAILURE') + ? <a className={classes.logLink} href="#logs" onClick={() => this.setState({archiveLogs: { uploadId: upload_id, calcId: calc_id }})}> + {tasks_status.toLowerCase()} + </a> : tasks_status.toLowerCase() } </Typography> </TableCell> <TableCell> - <CalcLinks uploadId={upload_id} calcId={calc_id} disabled={status !== 'SUCCESS'} /> + <CalcLinks uploadId={upload_id} calcId={calc_id} disabled={tasks_status !== 'SUCCESS'} /> </TableCell> </TableRow> ) - if (status === 'FAILURE') { + if (tasks_status === 'FAILURE') { return ( <Tooltip key={calc_id} title={errors.map((error, index) => (<p key={`${calc_id}-${index}`}>{error}</p>))}> {row} @@ -355,7 +403,7 @@ class Upload extends React.Component { { 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: 'tasks_status', sort: true, label: 'status' }, { id: 'links', sort: false, label: 'links' } ] @@ -434,7 +482,7 @@ class Upload extends React.Component { <ExpansionPanel> <ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>} classes={{root: classes.summary}}> - {!(upload.completed || upload.waiting) + {(upload.tasks_running || upload.process_running) ? <div className={classes.progress}> <CircularProgress size={32}/> </div> diff --git a/gui/src/components/Uploads.js b/gui/src/components/Uploads.js index bc44b2733ff5f89be6cfbc70283c64ea412f9448..b87cbdebfc8f3e5037326dcac9aa17b38194a5fd 100644 --- a/gui/src/components/Uploads.js +++ b/gui/src/components/Uploads.js @@ -119,6 +119,17 @@ class Uploads extends React.Component { }) } + sortedUploads() { + return this.state.uploads.concat() + .sort((a, b) => (a.gui_upload_id === b.gui_upload_id) ? 0 : ((a.gui_upload_id < b.gui_upload_id) ? -1 : 1)) + } + + handleDoesNotExist(nonExistingUupload) { + this.setState({ + uploads: this.state.uploads.filter(upload => upload !== nonExistingUupload) + }) + } + onDrop(files) { files.forEach(file => { const upload = api.createUpload(file.name) @@ -139,7 +150,7 @@ class Uploads extends React.Component { onSelectionAllChanged(checked) { if (checked) { - this.setState({selectedUploads: [...this.state.uploads.filter(upload => upload.completed)]}) + this.setState({selectedUploads: [...this.state.uploads.filter(upload => !upload.tasks_running)]}) } else { this.setState({selectedUploads: []}) } @@ -181,9 +192,10 @@ class Uploads extends React.Component { </FormGroup> </div> <div className={classes.uploads}> - {this.state.uploads.map((upload) => ( - <Upload key={upload.upload_id} upload={upload} + {this.sortedUploads().map(upload => ( + <Upload key={upload.gui_upload_id} upload={upload} checked={selectedUploads.indexOf(upload) !== -1} + onDoesNotExist={() => this.handleDoesNotExist(upload)} onCheckboxChanged={checked => this.onSelectionChanged(upload, checked)}/> ))} </div> diff --git a/gui/yarn.lock b/gui/yarn.lock index a9a7488e76af6fafc8ed785b2993ba874e196770..093f3836afa8d753f3821f1fa95db8f00f60a296 100644 --- a/gui/yarn.lock +++ b/gui/yarn.lock @@ -2660,6 +2660,13 @@ eslint-module-utils@^2.1.1, eslint-module-utils@^2.2.0: debug "^2.6.8" pkg-dir "^1.0.0" +eslint-plugin-es@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6" + dependencies: + eslint-utils "^1.3.0" + regexpp "^2.0.1" + eslint-plugin-flowtype@2.39.1: version "2.39.1" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.39.1.tgz#b5624622a0388bcd969f4351131232dcb9649cd5" @@ -2708,6 +2715,17 @@ eslint-plugin-jsx-a11y@5.1.1: emoji-regex "^6.1.0" jsx-ast-utils "^1.4.0" +eslint-plugin-node@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz#55ae3560022863d141fa7a11799532340a685964" + dependencies: + eslint-plugin-es "^1.3.1" + eslint-utils "^1.3.1" + ignore "^5.0.2" + minimatch "^3.0.4" + resolve "^1.8.1" + semver "^5.5.0" + eslint-plugin-promise@^3.7.0: version "3.8.0" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz#65ebf27a845e3c1e9d6f6a5622ddd3801694b621" @@ -2749,6 +2767,10 @@ eslint-scope@^3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-utils@^1.3.0, eslint-utils@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" + eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" @@ -3823,6 +3845,10 @@ ignore@^3.3.3: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" +ignore@^5.0.2: + version "5.0.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.4.tgz#33168af4a21e99b00c5d41cbadb6a6cb49903a45" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -5654,7 +5680,7 @@ path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" -path-parse@^1.0.5: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -6570,6 +6596,10 @@ regexpp@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -6734,6 +6764,12 @@ resolve@^1.3.2, resolve@^1.5.0, resolve@^1.6.0: dependencies: path-parse "^1.0.5" +resolve@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06" + dependencies: + path-parse "^1.0.6" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -6848,6 +6884,10 @@ semver-diff@^2.0.0: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" +semver@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + send@0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" diff --git a/nomad/api/archive.py b/nomad/api/archive.py index d8ed55af863e9d5e2fd5d80979d4d092ccaa86ef..75af737f66c41da3fe1e0a306d7b6c4b09614503 100644 --- a/nomad/api/archive.py +++ b/nomad/api/archive.py @@ -102,7 +102,7 @@ class ArchiveCalcResource(Resource): @ns.route('/metainfo/<string:metainfo_path>') -@api.doc(params=dict(metainfo_path='A path or metainfo definition file name.')) +@api.doc(params=dict(nomad_metainfo_path='A path or metainfo definition file name.')) class MetainfoResource(Resource): @api.doc('get_metainfo') @api.response(404, 'The metainfo does not exist') @@ -113,13 +113,13 @@ class MetainfoResource(Resource): """ try: file_dir = os.path.dirname(os.path.abspath(nomad_meta_info.__file__)) - meta_info_path = os.path.normpath(os.path.join(file_dir, metainfo_path.strip())) + metainfo_file = os.path.normpath(os.path.join(file_dir, metainfo_path.strip())) rv = send_file( - meta_info_path, + metainfo_file, mimetype='application/json', as_attachment=True, - attachment_filename=os.path.basename(metainfo_path)) + attachment_filename=os.path.basename(metainfo_file)) return rv except FileNotFoundError: diff --git a/nomad/api/upload.py b/nomad/api/upload.py index 15861751f7e6efa83c214a9d076345bad6d8f0b1..3ae085d06dceb16659c0c70daf78da8d8aa44d54 100644 --- a/nomad/api/upload.py +++ b/nomad/api/upload.py @@ -41,7 +41,7 @@ ns = api.namespace( proc_model = api.model('Processing', { 'tasks': fields.List(fields.String), 'current_task': fields.String, - 'tasks_completed': fields.Boolean, + 'tasks_running': fields.Boolean, 'tasks_status': fields.String, 'errors': fields.List(fields.String), 'warnings': fields.List(fields.String), @@ -297,7 +297,7 @@ class UploadResource(Resource): if upload.user_id != str(g.user.user_id) and not g.user.is_admin: abort(401, message='Upload with id %s does not belong to you.' % upload_id) - if not upload.tasks_completed: + if upload.tasks_running: abort(400, message='The upload is not processed yet') try: @@ -350,7 +350,7 @@ class UploadResource(Resource): break if operation == 'commit': - if not upload.tasks_completed: + if upload.tasks_running: abort(400, message='The upload is not processed yet') try: upload.metadata = metadata diff --git a/nomad/client.py b/nomad/client.py index 5dce848741ca6188e0be4cde045beb1c62f6dee7..f8c29d0c6f423b98f1d434a4dc36d04771066f2a 100644 --- a/nomad/client.py +++ b/nomad/client.py @@ -327,12 +327,13 @@ def worker(): @run.command(help='Run the nomad development api.') -def api(): +@click.option('--debug', help='Does run flask in debug.', is_flag=True) +def api(debug: bool): config.service = 'nomad_api' from nomad import infrastructure from nomad.api.__main__ import run_dev_server infrastructure.setup() - run_dev_server(debug=True, port=8000) + run_dev_server(debug=debug, port=8000) @cli.command(help='Runs tests and linting. Useful before commit code.') diff --git a/nomad/processing/base.py b/nomad/processing/base.py index 311a695adca0974dce4eed4e11e0acc5ab79c511..21b4f109755e832f0796564e0c5ad5761fb3725f 100644 --- a/nomad/processing/base.py +++ b/nomad/processing/base.py @@ -53,7 +53,7 @@ RUNNING = 'RUNNING' FAILURE = 'FAILURE' SUCCESS = 'SUCCESS' -PROCESS_CALLED = 'CALLD' +PROCESS_CALLED = 'CALLED' PROCESS_RUNNING = 'RUNNING' PROCESS_COMPLETED = 'COMPLETED' @@ -130,9 +130,9 @@ class Proc(Document, metaclass=ProcMetaclass): process_status = StringField(default=None) @property - def tasks_completed(self) -> bool: + def tasks_running(self) -> bool: """ Returns True of the process has failed or succeeded. """ - return self.tasks_status in [SUCCESS, FAILURE] + return self.tasks_status not in [SUCCESS, FAILURE] @property def process_running(self) -> bool: @@ -194,7 +194,7 @@ class Proc(Document, metaclass=ProcMetaclass): def fail(self, *errors, log_level=logging.ERROR, **kwargs): """ Allows to fail the process. Takes strings or exceptions as args. """ - assert not self.tasks_completed, 'Cannot fail a completed process.' + assert self.tasks_running, 'Cannot fail a completed process.' failed_with_exception = False @@ -219,7 +219,7 @@ class Proc(Document, metaclass=ProcMetaclass): def warning(self, *warnings, log_level=logging.WARNING, **kwargs): """ Allows to save warnings. Takes strings or exceptions as args. """ - assert not self.tasks_completed + assert self.tasks_running logger = self.get_logger(**kwargs) @@ -266,7 +266,7 @@ class Proc(Document, metaclass=ProcMetaclass): Reloads the process constantly until it sees a completed process. Should be used with care as it can block indefinitely. Just intended for testing purposes. """ - while not self.tasks_completed: + while self.tasks_running: time.sleep(interval) self.reload() @@ -397,7 +397,7 @@ def task(func): except Exception as e: self.fail(e) - if self.__class__.tasks[-1] == self.current_task and not self.tasks_completed: + if self.__class__.tasks[-1] == self.current_task and self.tasks_running: self._complete() setattr(wrapper, '__task_name', func.__name__) diff --git a/nomad/processing/data.py b/nomad/processing/data.py index 98f7b651944df094e38bbabffad8ba40f82d0218..a3c2998fce58fcfa61fcebc8e7e992ac24ec7b0d 100644 --- a/nomad/processing/data.py +++ b/nomad/processing/data.py @@ -346,6 +346,8 @@ class Upload(Chord, datamodel.Upload): with utils.timer( logger, 'staged upload deleted', step='delete', upload_size=self.upload_files.size): + import time + time.sleep(10) self.upload_files.delete() self.delete() diff --git a/tests/processing/test_data.py b/tests/processing/test_data.py index 224b3363241ebf515762a24927d752e62b06637b..85c4fcecdc5461b56cd40593f0043a1b7b474f2f 100644 --- a/tests/processing/test_data.py +++ b/tests/processing/test_data.py @@ -82,7 +82,7 @@ def processed_upload(uploaded_id, test_user, worker, no_warn) -> Upload: def assert_processing(upload: Upload): - assert upload.tasks_completed + assert not upload.tasks_running assert upload.current_task == 'cleanup' assert upload.upload_id is not None assert len(upload.errors) == 0 @@ -127,7 +127,7 @@ def test_processing_with_warning(uploaded_id_with_warning, worker, test_user): def test_process_non_existing(worker, test_user, with_error): upload = run_processing('__does_not_exist', test_user) - assert upload.tasks_completed + assert not upload.tasks_running assert upload.current_task == 'extracting' assert upload.tasks_status == 'FAILURE' assert len(upload.errors) > 0 @@ -154,7 +154,7 @@ def test_task_failure(monkeypatch, uploaded_id, worker, task, test_user, with_er # run the test upload = run_processing(uploaded_id, test_user) - assert upload.tasks_completed + assert not upload.tasks_running if task != 'parsing': assert upload.tasks_status == 'FAILURE' diff --git a/tests/test_api.py b/tests/test_api.py index 80ccb0508bac58ca90c70ab700e1908a52470f59..e81fde6de2fefce2bc86efc7b5d2cc62f43d8e83 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -188,7 +188,7 @@ class TestUploads: assert rv.status_code == 200 upload = self.assert_upload(rv.data) assert 'upload_time' in upload - if upload['tasks_completed']: + if not upload['tasks_running']: break assert len(upload['tasks']) == 4