Commit 559ad23b authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added PID resolve. Fixes #212

parent 8f5308d2
Pipeline #54985 passed with stages
in 33 minutes and 10 seconds
...@@ -28,6 +28,7 @@ import packageJson from '../../package.json' ...@@ -28,6 +28,7 @@ import packageJson from '../../package.json'
import { Cookies, withCookies } from 'react-cookie' import { Cookies, withCookies } from 'react-cookie'
import Markdown from './Markdown' import Markdown from './Markdown'
import {help as uploadHelp, default as Uploads} from './uploads/Uploads' import {help as uploadHelp, default as Uploads} from './uploads/Uploads'
import ResolvePID from './entry/ResolvePID';
export class VersionMismatch extends Error { export class VersionMismatch extends Error {
constructor(msg) { constructor(msg) {
...@@ -49,6 +50,7 @@ const toolbarThemes = { ...@@ -49,6 +50,7 @@ const toolbarThemes = {
'/': genTheme, '/': genTheme,
'/search': repoTheme, '/search': repoTheme,
'/uploads': repoTheme, '/uploads': repoTheme,
'/entry': repoTheme,
'/metainfo': archiveTheme '/metainfo': archiveTheme
} }
...@@ -388,9 +390,9 @@ export default class App extends React.Component { ...@@ -388,9 +390,9 @@ export default class App extends React.Component {
path: '/search', path: '/search',
render: props => <SearchPage {...props} /> render: props => <SearchPage {...props} />
}, },
'searchEntry': { 'entry': {
path: '/search/:uploadId/:calcId', path: '/entry/id/:uploadId/:calcId',
key: (props) => `searchEntry/${props.match.params.uploadId}/${props.match.params.uploadId}`, key: (props) => `entry/${props.match.params.uploadId}/${props.match.params.uploadId}`,
render: props => { render: props => {
const { match, ...rest } = props const { match, ...rest } = props
if (match && match.params.uploadId && match.params.calcId) { if (match && match.params.uploadId && match.params.calcId) {
...@@ -400,24 +402,24 @@ export default class App extends React.Component { ...@@ -400,24 +402,24 @@ export default class App extends React.Component {
} }
} }
}, },
'uploads': { 'entry_pid': {
exact: true, path: '/entry/pid/:pid',
singleton: true, key: (props) => `entry/pid/${props.match.params.pid}`,
path: '/uploads',
render: props => <Uploads {...props} />
},
'uploadedEntry': {
path: '/uploads/:uploadId/:calcId',
key: (props) => `uploadedEntry/${props.match.params.uploadId}/${props.match.params.uploadId}`,
render: props => { render: props => {
const { match, ...rest } = props const { match, ...rest } = props
if (match && match.params.uploadId && match.params.calcId) { if (match && match.params.pid) {
return (<Calc {...rest} uploadId={match.params.uploadId} calcId={match.params.calcId} />) return (<ResolvePID {...rest} pid={match.params.pid} />)
} else { } else {
return '' return ''
} }
} }
}, },
'uploads': {
exact: true,
singleton: true,
path: '/uploads',
render: props => <Uploads {...props} />
},
'metainfo': { 'metainfo': {
exact: true, exact: true,
path: '/metainfo', path: '/metainfo',
......
...@@ -171,6 +171,7 @@ class Api { ...@@ -171,6 +171,7 @@ class Api {
this.onStartLoading = () => null this.onStartLoading = () => null
this.onFinishLoading = () => null this.onFinishLoading = () => null
this.isLoggedIn = true && user
user = user || {} user = user || {}
this.auth_headers = { this.auth_headers = {
'X-Token': user.token 'X-Token': user.token
...@@ -290,6 +291,15 @@ class Api { ...@@ -290,6 +291,15 @@ class Api {
.finally(this.onFinishLoading) .finally(this.onFinishLoading)
} }
async resolvePid(pid) {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.repo.resolve_pid({pid: pid}))
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
async search(search) { async search(search) {
this.onStartLoading() this.onStartLoading()
return this.swaggerPromise return this.swaggerPromise
......
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Typography } from '@material-ui/core'
import { compose } from 'recompose'
import { withApi } from '../api'
import { withRouter } from 'react-router'
class ResovlePID extends React.Component {
static styles = theme => ({
root: {
padding: theme.spacing.unit * 3
}
})
static propTypes = {
classes: PropTypes.object.isRequired,
pid: PropTypes.string.isRequired,
api: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
raiseError: PropTypes.func.isRequired
}
state = {
doesNotExist: false
}
componentDidMount() {
const { pid, api, history } = this.props
api.resolvePid(pid).then(entry => {
history.push(`/entry/id/${entry.upload_id}/${entry.calc_id}`)
}).catch(error => {
this.setState({calcData: null})
if (error.name === 'DoesNotExist') {
this.setState({doesNotExist: true})
} else {
this.props.raiseError(error)
}
})
}
render() {
const { classes, api } = this.props
const { doesNotExist } = this.state
let message = 'loading ...'
if (doesNotExist) {
if (api.isLoggedIn) {
message = `
This URL points to an entry that either does not exist, or that you are not
authorized to see.`
} else {
message = `
This URL points to an entry that either does not exist, or that is not
publically visibile. Please login; you might be authorized to view it.`
}
}
return (
<Typography className={classes.root}>{message}</Typography>
)
}
}
export default compose(withRouter, withApi(false), withStyles(ResovlePID.styles))(ResovlePID)
...@@ -63,7 +63,7 @@ class SearchResultListUnstyled extends React.Component { ...@@ -63,7 +63,7 @@ class SearchResultListUnstyled extends React.Component {
} }
handleClickCalc(calc) { handleClickCalc(calc) {
this.props.history.push(`/search/${calc.upload_id}/${calc.calc_id}`) this.props.history.push(`/entry/id/${calc.upload_id}/${calc.calc_id}`)
} }
handleChangePage = (event, page) => { handleChangePage = (event, page) => {
......
...@@ -390,7 +390,7 @@ class Upload extends React.Component { ...@@ -390,7 +390,7 @@ class Upload extends React.Component {
const processed = tasks_status === 'FAILURE' || tasks_status === 'SUCCESS' const processed = tasks_status === 'FAILURE' || tasks_status === 'SUCCESS'
const row = ( const row = (
<TableRow key={index} hover={processed} <TableRow key={index} hover={processed}
onClick={() => this.props.history.push(`/uploads/${upload_id}/${calc_id}`)} onClick={() => this.props.history.push(`/entry/id/${upload_id}/${calc_id}`)}
className={processed ? classes.clickableRow : null} > className={processed ? classes.clickableRow : null} >
<TableCell> <TableCell>
......
...@@ -24,7 +24,7 @@ from elasticsearch_dsl import Q ...@@ -24,7 +24,7 @@ from elasticsearch_dsl import Q
from elasticsearch.exceptions import NotFoundError from elasticsearch.exceptions import NotFoundError
import datetime import datetime
from nomad import search from nomad import search, utils
from .app import api, rfc3339DateTime from .app import api, rfc3339DateTime
from .auth import login_if_available from .auth import login_if_available
...@@ -87,6 +87,11 @@ repo_calcs_model = api.model('RepoCalculations', { ...@@ -87,6 +87,11 @@ repo_calcs_model = api.model('RepoCalculations', {
}) })
repo_calc_id_model = api.model('RepoCalculationId', {
'upload_id': fields.String(), 'calc_id': fields.String()
})
def add_common_parameters(request_parser): def add_common_parameters(request_parser):
request_parser.add_argument( request_parser.add_argument(
'owner', type=str, 'owner', type=str,
...@@ -353,3 +358,26 @@ class RepoQuantityResource(Resource): ...@@ -353,3 +358,26 @@ class RepoQuantityResource(Resource):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
abort(400, 'Given quantity does not exist: %s' % str(e)) abort(400, 'Given quantity does not exist: %s' % str(e))
@ns.route('/pid/<int:pid>')
class RepoPidResource(Resource):
@api.doc('resolve_pid')
@api.response(404, 'Entry with PID does not exist')
@api.marshal_with(repo_calc_id_model, skip_none=True, code=200, description='Entry resolved')
@login_if_available
def get(self, pid: int):
q = _create_owner_query()
results = search.entry_search(q, page=1, per_page=1, search_parameters=dict(pid=pid))
total = results['pagination']['total']
if total == 1:
return dict(
upload_id=results['results'][0]['upload_id'],
calc_id=results['results'][0]['calc_id'])
elif total == 0:
abort(404, 'Entry with PID %d does not exist' % pid)
else:
utils.get_logger(__name__).error('Two entries for the same pid', pid=pid)
return dict(
upload_id=results['results'][0]['upload_id'],
calc_id=results['results'][0]['calc_id'])
...@@ -656,18 +656,20 @@ class TestRepo(): ...@@ -656,18 +656,20 @@ class TestRepo():
search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True) search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True)
calc_with_metadata.update( calc_with_metadata.update(
calc_id='2', uploader=other_test_user.to_popo(), published=True, with_embargo=False, calc_id='2', uploader=other_test_user.to_popo(), published=True,
upload_time=today - datetime.timedelta(days=5)) with_embargo=False, pid=2, upload_time=today - datetime.timedelta(days=5))
calc_with_metadata.update( calc_with_metadata.update(
atoms=['Fe'], comment='this is a specific word', formula='AAA', basis_set='zzz') atoms=['Fe'], comment='this is a specific word', formula='AAA', basis_set='zzz')
search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True) search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True)
calc_with_metadata.update( calc_with_metadata.update(
calc_id='3', uploader=other_test_user.to_popo(), published=False, with_embargo=False) calc_id='3', uploader=other_test_user.to_popo(), published=False,
with_embargo=False, pid=3)
search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True) search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True)
calc_with_metadata.update( calc_with_metadata.update(
calc_id='4', uploader=other_test_user.to_popo(), published=True, with_embargo=True) calc_id='4', uploader=other_test_user.to_popo(), published=True,
with_embargo=True, pid=4)
search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True) search.Entry.from_calc_with_metadata(calc_with_metadata).save(refresh=True)
def assert_search(self, rv: Any, number_of_calcs: int) -> dict: def assert_search(self, rv: Any, number_of_calcs: int) -> dict:
...@@ -935,6 +937,21 @@ class TestRepo(): ...@@ -935,6 +937,21 @@ class TestRepo():
assert after != quantity['after'] assert after != quantity['after']
after = quantity['after'] after = quantity['after']
@pytest.mark.parametrize('pid, with_login, success', [
(2, True, True), (2, False, True),
(3, True, True), (3, False, False),
(4, True, True), (4, False, False)])
def test_resolve_pid(
self, client, example_elastic_calcs, other_test_user_auth, pid, with_login,
success, no_warn):
rv = client.get(
'/repo/pid/%d' % pid,
headers=other_test_user_auth if with_login else {})
assert rv.status_code == 200 if success else 404
if success:
assert json.loads(rv.data)['calc_id'] == '%d' % pid
assert json.loads(rv.data)['upload_id'] == '0'
class TestRaw(UploadFilesBasedTests): class TestRaw(UploadFilesBasedTests):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment