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
09f53cc9
Commit
09f53cc9
authored
Jan 25, 2022
by
Markus Scheidgen
Browse files
Minor upload gui fixes.
#709
parent
6faa1770
Changes
7
Hide whitespace changes
Inline
Side-by-side
gui/src/components/api.js
View file @
09f53cc9
...
...
@@ -52,8 +52,15 @@ export class ApiError extends Error {
}
}
export
class
ApiRequestError
extends
Error
{
constructor
(
msg
)
{
super
(
msg
)
this
.
name
=
'
BadRequest
'
}
}
function
handleApiError
(
e
)
{
if
(
e
.
name
===
'
CannotReachApi
'
||
e
.
name
===
'
NotAuthorized
'
||
e
.
name
===
'
DoesNotExist
'
)
{
if
(
e
.
name
===
'
CannotReachApi
'
||
e
.
name
===
'
NotAuthorized
'
||
e
.
name
===
'
DoesNotExist
'
||
e
.
name
===
'
BadRequest
'
)
{
throw
e
}
...
...
@@ -66,6 +73,8 @@ function handleApiError(e) {
error
=
new
DoesNotExist
(
errorMessage
)
}
else
if
(
e
.
response
.
status
===
401
)
{
error
=
new
NotAuthorized
(
errorMessage
)
}
else
if
(
e
.
response
.
status
===
400
)
{
error
=
new
ApiRequestError
(
errorMessage
)
}
else
if
(
e
.
response
.
status
===
502
)
{
error
=
new
ApiError
(
errorMessage
)
}
else
{
...
...
gui/src/components/dataset/DatasetsPage.js
View file @
09f53cc9
...
...
@@ -17,7 +17,7 @@
*/
import
React
,
{
useCallback
,
useContext
,
useEffect
,
useState
,
useMemo
}
from
'
react
'
import
PropTypes
from
'
prop-types
'
import
{
Paper
,
IconButton
,
Tooltip
,
DialogContent
,
Button
,
Dialog
}
from
'
@material-ui/core
'
import
{
Paper
,
IconButton
,
Tooltip
,
DialogContent
,
Button
,
Dialog
,
DialogTitle
}
from
'
@material-ui/core
'
import
{
useApi
,
withLoginRequired
}
from
'
../api
'
import
Page
from
'
../Page
'
import
{
useErrors
}
from
'
../errors
'
...
...
@@ -67,33 +67,33 @@ const DatasetActions = React.memo(function VisitDatasetAction({data}) {
const
{
api
}
=
useApi
()
const
{
raiseError
}
=
useErrors
()
const
{
refresh
}
=
useContext
(
PageContext
)
const
[
openConfirmDialog
,
setOpenConfirmDialog
]
=
useState
(
false
)
const
[
openConfirmDeleteDialog
,
setOpenConfirmDeleteDialog
]
=
useState
(
false
)
const
[
openConfirmDoiDialog
,
setOpenConfirmDoiDialog
]
=
useState
(
false
)
const
handleDelete
=
useCallback
(()
=>
{
setOpenConfirmDeleteDialog
(
false
)
api
.
delete
(
`/datasets/
${
data
.
dataset_id
}
`
)
.
then
(
refresh
).
catch
(
raiseError
)
},
[
api
,
raiseError
,
data
.
dataset_id
,
refresh
])
},
[
api
,
raiseError
,
data
.
dataset_id
,
refresh
,
setOpenConfirmDeleteDialog
])
const
handleAssignDoi
=
useCallback
(()
=>
{
setOpenConfirmDoiDialog
(
false
)
api
.
post
(
`/datasets/
${
data
.
dataset_id
}
/action/doi`
)
.
then
(
refresh
).
catch
(
raiseError
)
},
[
api
,
raiseError
,
data
.
dataset_id
,
refresh
])
const
onConfirm
=
()
=>
{
setOpenConfirmDialog
(
true
)
}
.
then
(
refresh
)
.
catch
(
raiseError
)
},
[
api
,
raiseError
,
data
.
dataset_id
,
refresh
,
setOpenConfirmDoiDialog
])
return
<
React
.
Fragment
>
<
Tooltip
title
=
"
Assign a DOI
"
>
<
span
>
<
IconButton
onClick
=
{
handleAssignDoi
}
disabled
=
{
!!
data
.
doi
}
>
<
IconButton
onClick
=
{
()
=>
setOpenConfirmDoiDialog
(
true
)
}
disabled
=
{
!!
data
.
doi
}
>
<
DOIIcon
/>
<
/IconButton
>
<
/span
>
<
/Tooltip
>
<
Tooltip
title
=
{(
data
.
doi
?
'
The dataset cannot be deleted. A DOI has been assigned to the dataset.
'
:
'
Delete the dataset
'
)}
>
<
span
>
<
IconButton
onClick
=
{
onConfirm
}
disabled
=
{
!!
data
.
doi
}
style
=
{{
pointerEvents
:
'
auto
'
}}
>
<
IconButton
onClick
=
{
()
=>
setOpenConfirmDeleteDialog
(
true
)
}
disabled
=
{
!!
data
.
doi
}
style
=
{{
pointerEvents
:
'
auto
'
}}
>
<
DeleteIcon
/>
<
/IconButton
>
<
/span
>
...
...
@@ -104,7 +104,7 @@ const DatasetActions = React.memo(function VisitDatasetAction({data}) {
<
/DatasetButton
>
<
/Tooltip
>
<
Dialog
open
=
{
openConfirmDialog
}
open
=
{
openConfirmD
eleteD
ialog
}
aria
-
describedby
=
"
alert-dialog-description
"
>
<
DialogContent
>
...
...
@@ -113,10 +113,26 @@ const DatasetActions = React.memo(function VisitDatasetAction({data}) {
<
/DialogContentText
>
<
/DialogContent
>
<
DialogActions
>
<
Button
onClick
=
{()
=>
setOpenConfirmDialog
(
false
)}
autoFocus
>
Cancel
<
/Button
>
<
Button
onClick
=
{()
=>
setOpenConfirmD
eleteD
ialog
(
false
)}
autoFocus
>
Cancel
<
/Button
>
<
Button
onClick
=
{
handleDelete
}
>
Delete
<
/Button
>
<
/DialogActions
>
<
/Dialog
>
<
Dialog
open
=
{
openConfirmDoiDialog
}
onClose
=
{()
=>
setOpenConfirmDoiDialog
(
false
)}
>
<
DialogTitle
>
Confirm
that
you
want
to
assign
a
DOI
<
/DialogTitle
>
<
DialogContent
>
<
DialogContentText
>
Assigning
a
DOI
is
permanent
.
You
will
not
be
able
to
remove
entries
from
datasets
with
a
DOI
.
You
cannot
delete
datasets
with
a
DOI
.
<
/DialogContentText
>
<
/DialogContent
>
<
DialogActions
>
<
Button
onClick
=
{()
=>
setOpenConfirmDoiDialog
(
false
)}
autoFocus
>
Cancel
<
/Button
>
<
Button
onClick
=
{
handleAssignDoi
}
>
Assign
DOI
<
/Button
>
<
/DialogActions
>
<
/Dialog
>
<
/React.Fragment
>
})
DatasetActions
.
propTypes
=
{
...
...
gui/src/components/errors.js
View file @
09f53cc9
...
...
@@ -56,7 +56,9 @@ class ErrorSnacksUnstyled extends React.Component {
onError
(
error
)
{
let
errorStr
=
'
Unexpected error. Please try again and let us know, if this error keeps happening.
'
if
(
error
instanceof
Error
)
{
if
(
error
.
name
===
'
CannotReachApi
'
)
{
if
(
error
.
name
===
'
BadRequest
'
)
{
errorStr
=
error
.
message
}
else
if
(
error
.
name
===
'
CannotReachApi
'
)
{
errorStr
=
'
Cannot reach NOMAD, please try again later.
'
}
else
if
(
error
.
name
===
'
NotAuthorized
'
)
{
errorStr
=
error
.
message
...
...
gui/src/components/uploads/UploadPage.js
View file @
09f53cc9
...
...
@@ -224,7 +224,7 @@ function PublishUpload({upload, onPublish}) {
<
Button
style
=
{{
height
:
32
,
minWith
:
100
}}
size
=
"
small
"
variant
=
"
contained
"
onClick
=
{()
=>
handlePublish
()}
color
=
"
primary
"
autoFocus
onClick
=
{()
=>
handlePublish
()}
color
=
"
primary
"
disabled
=
{
upload
.
process_running
}
>
{
embargo
>
0
?
'
Publish with embargo
'
:
'
Publish
'
}
...
...
@@ -499,7 +499,7 @@ function UploadPage() {
<
/Dialog
>
<
/Grid
>
<
/Grid
>
<
Stepper
classes
=
{{
root
:
classes
.
stepper
}}
orientation
=
"
vertical
"
nonLinear
>
<
Stepper
classes
=
{{
root
:
classes
.
stepper
}}
orientation
=
"
vertical
"
>
<
Step
expanded
active
=
{
false
}
>
<
StepLabel
>
Prepare
and
upload
your
files
<
/StepLabel
>
<
StepContent
>
...
...
@@ -529,7 +529,7 @@ function UploadPage() {
<
FilesBrower
className
=
{
classes
.
stepContent
}
uploadId
=
{
uploadId
}
disabled
=
{
isProcessing
||
deleteClicked
}
/
>
<
/StepContent
>
<
/Step
>
<
Step
expanded
=
{
!
isEmpty
}
>
<
Step
expanded
=
{
!
isEmpty
}
active
=
{
false
}
>
<
StepLabel
>
Process
data
<
/StepLabel
>
<
StepContent
>
<
ProcessingStatus
data
=
{
data
}
/
>
...
...
@@ -540,7 +540,7 @@ function UploadPage() {
onPaginationChanged
=
{
setPagination
}
/
>
<
/StepContent
>
<
/Step
>
{(
isAuthenticated
&&
isWriter
)
&&
<
Step
expanded
=
{
!
isEmpty
}
>
{(
isAuthenticated
&&
isWriter
)
&&
<
Step
expanded
=
{
!
isEmpty
}
active
=
{
false
}
>
<
StepLabel
>
Edit
author
metadata
<
/StepLabel
>
<
StepContent
>
<
Typography
className
=
{
classes
.
stepContent
}
>
...
...
@@ -556,7 +556,7 @@ function UploadPage() {
{
!
isEmpty
&&
<
EditMetaDataDialog
selectedEntries
=
{{
'
upload_id
'
:
upload
.
upload_id
}}
/>
}
<
/StepContent
>
<
/Step>
}
{(
isAuthenticated
&&
isWriter
)
&&
<
Step
expanded
=
{
!
isEmpty
}
>
{(
isAuthenticated
&&
isWriter
)
&&
<
Step
expanded
=
{
!
isEmpty
}
active
=
{
false
}
>
<
StepLabel
>
Publish
<
/StepLabel
>
<
StepContent
>
{
isPublished
&&
<
Typography
className
=
{
classes
.
stepContent
}
>
...
...
gui/src/components/uploads/UploadsPage.js
View file @
09f53cc9
...
...
@@ -253,7 +253,7 @@ function UploadsPage() {
page_size
:
10
,
page
:
1
,
order_by
:
'
upload_create_time
'
,
order
:
'
a
sc
'
order
:
'
de
sc
'
})
const
fetchData
=
useCallback
(()
=>
{
...
...
nomad/app/v1/routers/datasets.py
View file @
09f53cc9
...
...
@@ -24,19 +24,19 @@ from pydantic import BaseModel, Field, validator
from
datetime
import
datetime
import
enum
from
nomad
import
utils
,
datamodel
,
processing
from
nomad
import
utils
,
datamodel
,
processing
,
config
from
nomad.metainfo.elasticsearch_extension
import
entry_type
from
nomad.utils
import
strip
,
create_uuid
from
nomad.datamodel
import
Dataset
as
DatasetDefinitionCls
from
nomad.doi
import
DOI
from
nomad.search
import
update_by_query
from
nomad.search
import
search
,
update_by_query
from
.auth
import
create_user_dependency
from
.entries
import
_do_exaustive_search
from
..utils
import
create_responses
,
parameter_dependency_from_model
from
..models
import
(
Pagination
,
PaginationResponse
,
Query
,
HTTPExceptionModel
,
User
,
Direction
,
Owner
,
Any_
)
Pagination
,
PaginationResponse
,
MetadataPagination
,
Query
,
HTTPExceptionModel
,
User
,
Direction
,
Owner
,
Any_
)
router
=
APIRouter
()
...
...
@@ -76,6 +76,20 @@ _dataset_is_fixed_response = status.HTTP_400_BAD_REQUEST, {
The dataset already has a DOI and cannot be changed anymore.
'''
)}
_dataset_has_unpublished_contents
=
status
.
HTTP_400_BAD_REQUEST
,
{
'model'
:
HTTPExceptionModel
,
'description'
:
strip
(
'''
The dataset has unpublished contents. No DOI can be assigned at the moment.
Publish the dataset contents first.
'''
)}
_dataset_is_empty
=
status
.
HTTP_400_BAD_REQUEST
,
{
'model'
:
HTTPExceptionModel
,
'description'
:
strip
(
'''
The dataset is empty. No DOI can be assigned at this moment. Add some published
contents to the dataset first.
'''
)}
Dataset
=
datamodel
.
Dataset
.
m_def
.
a_pydantic
.
model
...
...
@@ -336,7 +350,9 @@ async def delete_dataset(
'/{dataset_id}/action/doi'
,
tags
=
[
default_tag
],
summary
=
'Assign a DOI to a dataset'
,
response_model
=
DatasetResponse
,
responses
=
create_responses
(
_bad_id_response
,
_dataset_is_fixed_response
,
_bad_user_response
),
responses
=
create_responses
(
_bad_id_response
,
_dataset_is_fixed_response
,
_dataset_has_unpublished_contents
,
_bad_user_response
,
_dataset_is_empty
),
response_model_exclude_unset
=
True
,
response_model_exclude_none
=
True
)
async
def
assign_doi
(
...
...
@@ -362,6 +378,27 @@ async def assign_doi(
status_code
=
_bad_user_response
[
0
],
detail
=
_bad_user_response
[
1
][
'description'
])
response
=
search
(
owner
=
'admin'
,
query
=
{
'datasets.dataset_id'
:
dataset_id
},
pagination
=
MetadataPagination
(
page_size
=
0
),
user_id
=
config
.
services
.
admin_user_id
)
if
response
.
pagination
.
total
==
0
:
raise
HTTPException
(
status_code
=
_dataset_is_empty
[
0
],
detail
=
_dataset_is_empty
[
1
][
'description'
])
response
=
search
(
owner
=
'admin'
,
query
=
{
'datasets.dataset_id'
:
dataset_id
,
'published'
:
False
},
pagination
=
MetadataPagination
(
page_size
=
0
),
user_id
=
config
.
services
.
admin_user_id
)
if
response
.
pagination
.
total
>
0
:
raise
HTTPException
(
status_code
=
_dataset_has_unpublished_contents
[
0
],
detail
=
_dataset_has_unpublished_contents
[
1
][
'description'
])
doi
=
DOI
.
create
(
title
=
'NOMAD dataset: %s'
%
dataset
.
dataset_name
,
user
=
user
)
doi
.
create_draft
()
doi
.
make_findable
()
...
...
tests/app/v1/routers/test_datasets.py
View file @
09f53cc9
...
...
@@ -43,13 +43,14 @@ to assert for certain aspects in the responses.
'''
def
create_dataset
(
**
kwargs
):
dataset
=
Dataset
(
dataset_create_time
=
datetime
.
now
(),
dataset_modified_time
=
datetime
.
now
(),
**
kwargs
)
dataset
.
m_get_annotations
(
'mongo'
).
save
()
return
dataset
@
pytest
.
fixture
(
scope
=
'function'
)
def
data
(
elastic
,
raw_files
,
mongo
,
test_user
,
other_test_user
):
def
create_dataset
(
**
kwargs
):
dataset
=
Dataset
(
dataset_create_time
=
datetime
.
now
(),
dataset_modified_time
=
datetime
.
now
(),
**
kwargs
)
dataset
.
m_get_annotations
(
'mongo'
).
save
()
return
dataset
data
=
ExampleData
(
main_author
=
test_user
)
data
.
create_upload
(
upload_id
=
'upload_1'
,
published
=
True
)
data
.
create_entry
(
...
...
@@ -276,9 +277,28 @@ def test_delete_dataset(client, data, test_user_auth, other_test_user_auth, data
pytest
.
param
(
'dataset_1'
,
'test_user'
,
200
,
id
=
'plain'
),
pytest
.
param
(
'dataset_1'
,
None
,
401
,
id
=
'no-user'
),
pytest
.
param
(
'dataset_1'
,
'other_test_user'
,
401
,
id
=
'wrong-user'
),
pytest
.
param
(
'dataset_doi'
,
'test_user'
,
400
,
id
=
'with-doi'
)
pytest
.
param
(
'dataset_doi'
,
'test_user'
,
400
,
id
=
'with-doi'
),
pytest
.
param
(
'unpublished'
,
'test_user'
,
400
,
id
=
'unpublished'
),
pytest
.
param
(
'empty'
,
'test_user'
,
400
,
id
=
'empty'
)
])
def
test_assign_doi_dataset
(
client
,
data
,
test_user
,
test_user_auth
,
other_test_user_auth
,
dataset_id
,
user
,
status_code
):
more_data
=
ExampleData
(
main_author
=
test_user
)
more_data
.
create_upload
(
upload_id
=
'unpublished'
,
published
=
False
)
more_data
.
create_entry
(
upload_id
=
'unpublished'
,
entry_id
=
'unpublished'
,
mainfile
=
'test_content/1/mainfile.json'
,
datasets
=
[
create_dataset
(
dataset_id
=
'unpublished'
,
user_id
=
test_user
.
user_id
,
dataset_name
=
'test unpublished entries'
,
dataset_type
=
'owned'
)])
create_dataset
(
dataset_id
=
'empty'
,
user_id
=
test_user
.
user_id
,
dataset_name
=
'test empty dataset'
,
dataset_type
=
'owned'
)
more_data
.
save
(
with_files
=
False
)
auth
=
None
if
user
==
'test_user'
:
auth
=
test_user_auth
...
...
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