Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
31c6a1f7
Commit
31c6a1f7
authored
Nov 12, 2019
by
Markus Scheidgen
Browse files
Assign URL to DOIs and resolve those in the GUI.
parent
8dd5e944
Pipeline
#63519
passed with stages
in 16 minutes and 16 seconds
Changes
14
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
gui/src/components/App.js
View file @
31c6a1f7
...
...
@@ -35,6 +35,7 @@ import { capitalize } from '../utils'
import
{
amber
}
from
'
@material-ui/core/colors
'
import
KeepState
from
'
./KeepState
'
import
{
help
as
userdataHelp
,
default
as
UserdataPage
}
from
'
./UserdataPage
'
import
ResolveDOI
from
'
./dataset/ResolveDOI
'
export
class
VersionMismatch
extends
Error
{
constructor
(
msg
)
{
...
...
@@ -466,6 +467,18 @@ export default class App extends React.Component {
}
}
},
'
dataset_doi
'
: {
path:
'
/
dataset
/
doi
/
:
doi
*
'
,
key: (props) => `dataset/doi/${props.match.params.doi}`,
render: props => {
const { match, ...rest } = props
if (match && match.params.doi) {
return (<ResolveDOI {...rest} doi={match.params.doi} />)
} else {
return
''
}
}
},
'
uploads
'
: {
exact: true,
singleton: true,
...
...
gui/src/components/DatasetPage.js
View file @
31c6a1f7
...
...
@@ -6,8 +6,8 @@ import { withErrors } from './errors'
import
{
withApi
,
DoesNotExist
}
from
'
./api
'
import
Search
from
'
./search/Search
'
import
SearchContext
from
'
./search/SearchContext
'
import
{
Typography
,
Link
}
from
'
@material-ui/core
'
import
{
DatasetActions
}
from
'
./search/DatasetList
'
import
{
Typography
}
from
'
@material-ui/core
'
import
{
DatasetActions
,
DOI
}
from
'
./search/DatasetList
'
import
{
withRouter
}
from
'
react-router
'
export
const
help
=
`
...
...
@@ -97,7 +97,7 @@ class DatasetPage extends React.Component {
<
div
className
=
{
classes
.
description
}
>
<
Typography
variant
=
"
h4
"
>
{
dataset
.
name
||
'
loading ...
'
}
<
/Typography
>
<
Typography
>
dataset
{
dataset
.
doi
?
<
span
>
,
with
DOI
<
Link
href
=
{
dataset
.
doi
}
>
{
dataset
.
doi
}
<
/Link
></
span
>
:
''
}
dataset
{
dataset
.
doi
?
<
span
>
,
with
DOI
<
DOI
doi
=
{
dataset
.
doi
}
/
></
span
>
:
''
}
<
/Typography
>
<
/div
>
...
...
gui/src/components/EditUserMetadataDialog.js
View file @
31c6a1f7
...
...
@@ -320,7 +320,8 @@ class EditUserMetadataDialogUnstyled extends React.Component {
raiseError
:
PropTypes
.
func
.
isRequired
,
user
:
PropTypes
.
object
,
onEditComplete
:
PropTypes
.
func
,
disabled
:
PropTypes
.
bool
disabled
:
PropTypes
.
bool
,
title
:
PropTypes
.
string
}
static
styles
=
theme
=>
({
...
...
@@ -478,7 +479,7 @@ class EditUserMetadataDialogUnstyled extends React.Component {
}
render
()
{
const
{
classes
,
buttonProps
,
total
,
api
,
user
,
example
,
disabled
}
=
this
.
props
const
{
classes
,
buttonProps
,
total
,
api
,
user
,
example
,
disabled
,
title
}
=
this
.
props
const
{
open
,
actions
,
verified
,
submitting
}
=
this
.
state
const
dialogEnabled
=
user
&&
example
.
uploader
&&
example
.
uploader
.
user_id
===
user
.
sub
&&
!
disabled
...
...
@@ -511,7 +512,7 @@ class EditUserMetadataDialogUnstyled extends React.Component {
return
(
<
React
.
Fragment
>
<
IconButton
{...(
buttonProps
||
{})}
onClick
=
{
this
.
handleButtonClick
}
disabled
=
{
!
dialogEnabled
}
>
<
Tooltip
title
=
{
`Edit user metadata
${
dialogEnabled
?
''
:
'
. You can only edit your data.
'
}
`
}
>
<
Tooltip
title
=
{
title
||
`Edit user metadata
${
dialogEnabled
?
''
:
'
. You can only edit your data.
'
}
`
}
>
<
EditIcon
/>
<
/Tooltip
>
<
/IconButton
>
...
...
gui/src/components/Quantity.js
View file @
31c6a1f7
...
...
@@ -32,8 +32,7 @@ class Quantity extends React.Component {
},
valueAction
:
{},
valueActionButton
:
{
padding
:
2
,
paddingButtom
:
3
padding
:
4
},
valueActionIcon
:
{
fontSize
:
16
...
...
gui/src/components/api.js
View file @
31c6a1f7
...
...
@@ -333,13 +333,23 @@ class Api {
async
resolvePid
(
pid
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
repo
.
resolve_pid
({
pid
:
pid
}))
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
.
finally
(
this
.
onFinishLoading
)
}
async
resolveDoi
(
doi
)
{
this
.
onStartLoading
()
console
.
log
(
doi
)
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
datasets
.
resolve_doi
({
doi
:
doi
}))
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
.
finally
(
this
.
onFinishLoading
)
}
async
search
(
search
)
{
this
.
onStartLoading
()
return
this
.
swagger
()
...
...
gui/src/components/dataset/ResolveDOI.js
0 → 100644
View file @
31c6a1f7
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
ResolveDOI
extends
React
.
Component
{
static
styles
=
theme
=>
({
root
:
{
padding
:
theme
.
spacing
.
unit
*
3
}
})
static
propTypes
=
{
classes
:
PropTypes
.
object
.
isRequired
,
doi
:
PropTypes
.
string
.
isRequired
,
api
:
PropTypes
.
object
.
isRequired
,
history
:
PropTypes
.
object
.
isRequired
,
raiseError
:
PropTypes
.
func
.
isRequired
}
update
()
{
const
{
doi
,
api
,
history
,
raiseError
}
=
this
.
props
api
.
resolveDoi
(
doi
).
then
(
dataset
=>
{
history
.
push
(
`/dataset/id/
${
dataset
.
dataset_id
}
`
)
}).
catch
(
raiseError
)
}
componentDidMount
()
{
this
.
update
()
}
componentDidUpdate
(
prevProps
)
{
if
(
prevProps
.
doi
!==
this
.
props
.
doi
||
prevProps
.
api
!==
this
.
props
.
api
)
{
this
.
update
()
}
}
render
()
{
const
{
classes
}
=
this
.
props
return
(
<
Typography
className
=
{
classes
.
root
}
>
loading
...
<
/Typography
>
)
}
}
export
default
compose
(
withRouter
,
withApi
(
false
),
withStyles
(
ResolveDOI
.
styles
))(
ResolveDOI
)
gui/src/components/entry/RepoEntryView.js
View file @
31c6a1f7
...
...
@@ -7,6 +7,7 @@ import ApiDialogButton from '../ApiDialogButton'
import
Quantity
from
'
../Quantity
'
import
{
withDomain
}
from
'
../domains
'
import
{
Link
as
RouterLink
}
from
'
react-router-dom
'
import
{
DOI
}
from
'
../search/DatasetList
'
class
RepoEntryView
extends
React
.
Component
{
static
styles
=
theme
=>
({
...
...
@@ -120,7 +121,7 @@ class RepoEntryView extends React.Component {
{(
calcData
.
datasets
||
[]).
map
(
ds
=>
(
<
Typography
key
=
{
ds
.
id
}
>
<
Link
component
=
{
RouterLink
}
to
=
{
`/dataset/id/
${
ds
.
id
}
`
}
>
{
ds
.
name
}
<
/Link
>
{
ds
.
doi
?
<
span
>&
nbsp
;
(
<
Link
href
=
{
ds
.
doi
}
>
{
ds
.
doi
}
<
/Link
>
)
</
span
>
:
''
}
{
ds
.
doi
?
<
span
>&
nbsp
;
(
<
DOI
doi
=
{
ds
.
doi
}
/
>
)
</
span
>
:
''
}
<
/Typography>
))
}
<
/div
>
<
/Quantity
>
...
...
gui/src/components/search/DatasetList.js
View file @
31c6a1f7
import
React
from
'
react
'
import
PropTypes
from
'
prop-types
'
import
{
withStyles
,
TableCell
,
Toolbar
,
IconButton
,
FormGroup
,
Tooltip
}
from
'
@material-ui/core
'
import
{
withStyles
,
TableCell
,
Toolbar
,
IconButton
,
FormGroup
,
Tooltip
,
Link
}
from
'
@material-ui/core
'
import
{
compose
}
from
'
recompose
'
import
{
withRouter
}
from
'
react-router
'
import
{
withDomain
}
from
'
../domains
'
...
...
@@ -13,6 +13,42 @@ import DeleteIcon from '@material-ui/icons/Delete'
import
{
withApi
}
from
'
../api
'
import
EditUserMetadataDialog
from
'
../EditUserMetadataDialog
'
import
DownloadButton
from
'
../DownloadButton
'
import
ClipboardIcon
from
'
@material-ui/icons/Assignment
'
import
{
CopyToClipboard
}
from
'
react-copy-to-clipboard
'
class
DOIUnstyled
extends
React
.
Component
{
static
propTypes
=
{
doi
:
PropTypes
.
string
.
isRequired
}
static
styles
=
theme
=>
({
root
:
{
display
:
'
inline-flex
'
,
alignItems
:
'
center
'
,
flexDirection
:
'
row
'
,
flexWrap
:
'
nowrap
'
}
})
render
()
{
const
{
classes
,
doi
}
=
this
.
props
const
url
=
`https://dx.doi.org/
${
doi
}
`
return
<
span
className
=
{
classes
.
root
}
>
<
Link
href
=
{
url
}
>
{
doi
}
<
/Link
>
<
CopyToClipboard
text
=
{
url
}
onCopy
=
{()
=>
null
}
>
<
Tooltip
title
=
{
`Copy DOI to clipboard`
}
>
<
IconButton
style
=
{{
margin
:
3
,
marginRight
:
0
,
padding
:
4
}}
>
<
ClipboardIcon
style
=
{{
fontSize
:
16
}}
/
>
<
/IconButton
>
<
/Tooltip
>
<
/CopyToClipboard
>
<
/span
>
}
}
export
const
DOI
=
withStyles
(
DOIUnstyled
.
styles
)(
DOIUnstyled
)
class
DatasetActionsUnstyled
extends
React
.
Component
{
static
propTypes
=
{
...
...
@@ -102,6 +138,7 @@ class DatasetActionsUnstyled extends React.Component {
<
/IconButton
>
<
/Tooltip>
}
{
editable
&&
<
EditUserMetadataDialog
title
=
"
Edit metadata of all dataset entries
"
example
=
{
dataset
.
example
}
query
=
{
query
}
total
=
{
dataset
.
total
}
onEditComplete
=
{
this
.
handleEdit
}
/>
}
...
...
@@ -160,7 +197,7 @@ class DatasetListUnstyled extends React.Component {
},
DOI
:
{
label
:
'
Dataset DOI
'
,
render
:
(
dataset
)
=>
dataset
.
doi
render
:
(
dataset
)
=>
dataset
.
doi
&&
<
DOI
doi
=
{
dataset
.
doi
}
/
>
},
entries
:
{
label
:
'
Entries
'
,
...
...
nomad/app/api/dataset.py
View file @
31c6a1f7
...
...
@@ -138,7 +138,11 @@ class DatasetResource(Resource):
# set the DOI
doi
=
DOI
.
create
(
title
=
'NOMAD dataset: %s'
%
result
.
name
,
user
=
g
.
user
)
doi
.
create_draft
()
doi
.
make_findable
()
result
.
doi
=
doi
.
doi
result
.
m_x
(
'me'
).
save
()
if
doi
.
state
!=
'findable'
:
logger
.
warning
(
...
...
@@ -176,3 +180,17 @@ class DatasetResource(Resource):
result
.
m_x
(
'me'
).
delete
()
return
result
@
ns
.
route
(
'/doi/<path:doi>'
)
class
RepoPidResource
(
Resource
):
@
api
.
doc
(
'resolve_doi'
)
@
api
.
response
(
404
,
'Dataset with DOI does not exist'
)
@
api
.
marshal_with
(
dataset_model
,
skip_none
=
True
,
code
=
200
,
description
=
'DOI resolved'
)
@
authenticate
()
def
get
(
self
,
doi
:
str
):
dataset_me
=
Dataset
.
m_def
.
m_x
(
'me'
).
objects
(
doi
=
doi
).
first
()
if
dataset_me
is
None
:
abort
(
404
,
'Dataset with DOI %s does not exist'
%
doi
)
return
dataset_me
,
200
nomad/app/api/upload.py
View file @
31c6a1f7
...
...
@@ -320,7 +320,7 @@ class UploadListResource(Resource):
Thanks for uploading your data to nomad.
Go back to %s and press reload to see the progress on your upload and publish your data.
'''
%
upload
.
gui_url
,
'''
%
config
.
gui_url
()
,
200
,
{
'Content-Type'
:
'text/plain; charset=utf-8'
})
return
upload
,
200
...
...
nomad/config.py
View file @
31c6a1f7
...
...
@@ -158,13 +158,12 @@ def api_url(ssl: bool = True):
services
.
api_base_path
.
strip
(
'/'
))
migration_source_db
=
NomadConfig
(
host
=
'db-repository.nomad.esc'
,
port
=
5432
,
dbname
=
'nomad_prod'
,
user
=
'nomadlab'
,
password
=
'*'
)
def
gui_url
():
base
=
api_url
(
True
)[:
-
3
]
if
base
.
endswith
(
'/'
):
base
=
base
[:
-
1
]
return
'%s/gui'
%
base
mail
=
NomadConfig
(
enabled
=
False
,
...
...
nomad/doi.py
View file @
31c6a1f7
...
...
@@ -76,6 +76,7 @@ class DOI(Document):
doi
.
doi_url
=
'%s/doi/%s'
%
(
config
.
datacite
.
mds_host
,
doi_str
)
doi
.
state
=
'created'
doi
.
create_time
=
create_time
doi
.
url
=
'%s/dataset/doi/%s'
%
(
config
.
gui_url
(),
doi_str
)
affiliation
=
''
if
user
.
affiliation
is
not
None
:
...
...
nomad/processing/data.py
View file @
31c6a1f7
...
...
@@ -841,13 +841,6 @@ class Upload(Proc):
self
.
joined
=
False
super
().
reset
()
@
property
def
gui_url
(
self
):
base
=
config
.
api_url
()[:
-
3
]
if
base
.
endswith
(
'/'
):
base
=
base
[:
-
1
]
return
'%s/gui/uploads/'
%
base
def
_cleanup_after_processing
(
self
):
# send email about process finish
user
=
self
.
uploader
...
...
@@ -857,7 +850,7 @@ class Upload(Proc):
''
,
'your data %suploaded at %s has completed processing.'
%
(
'"%s" '
%
self
.
name
if
self
.
name
else
''
,
self
.
upload_time
.
isoformat
()),
# pylint: disable=no-member
'You can review your data on your upload page: %s'
%
self
.
gui_url
,
'You can review your data on your upload page: %s'
%
config
.
gui_url
()
,
''
,
'If you encouter any issues with your upload, please let us know and replay to this email.'
,
''
,
...
...
tests/app/test_api.py
View file @
31c6a1f7
...
...
@@ -1512,7 +1512,6 @@ class TestDataset:
search
.
refresh
()
def
test_delete_dataset
(
self
,
api
,
test_user_auth
,
example_dataset_with_entry
):
# delete dataset
rv
=
api
.
delete
(
'/datasets/ds1'
,
headers
=
test_user_auth
)
assert
rv
.
status_code
==
200
data
=
json
.
loads
(
rv
.
data
)
...
...
@@ -1525,9 +1524,14 @@ class TestDataset:
assert
rv
.
status_code
==
400
def
test_assign_doi
(
self
,
api
,
test_user_auth
,
example_dataset_with_entry
):
# assign doi
rv
=
api
.
post
(
'/datasets/ds1'
,
headers
=
test_user_auth
)
assert
rv
.
status_code
==
200
data
=
json
.
loads
(
rv
.
data
)
self
.
assert_dataset
(
data
,
name
=
'ds1'
,
doi
=
True
)
self
.
assert_dataset_entry
(
api
,
'1'
,
True
,
True
,
headers
=
test_user_auth
)
def
test_resolve_doi
(
self
,
api
,
example_dataset_with_entry
):
rv
=
api
.
get
(
'/datasets/doi/test_doi'
)
assert
rv
.
status_code
==
200
data
=
json
.
loads
(
rv
.
data
)
self
.
assert_dataset
(
data
,
name
=
'ds2'
,
doi
=
True
)
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment