Skip to content

Commit 67e99c3

Browse files
deposit: push code to zenodo
* Closes #841. Signed-off-by: Ioannis Tsanaktsidis <[email protected]>
1 parent a0f014f commit 67e99c3

File tree

8 files changed

+223
-29
lines changed

8 files changed

+223
-29
lines changed

cap/config.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ def _(x):
638638
GITLAB_OAUTH_ACCESS_TOKEN = os.environ.get(
639639
'APP_GITLAB_OAUTH_ACCESS_TOKEN', None)
640640

641-
# Reana server url
641+
# Reana access token
642642
# ================
643643
REANA_ACCESS_TOKEN = {
644644
'ATLAS': os.environ.get(
@@ -650,3 +650,8 @@ def _(x):
650650
'LHCb': os.environ.get(
651651
'APP_REANA_LHCb_ACCESS_TOKEN', None)
652652
}
653+
654+
# Zenodo
655+
# ======
656+
ZENODO_SERVER_URL = os.environ.get('APP_ZENODO_SERVER_URL', None)
657+
ZENODO_ACCESS_TOKEN = os.environ.get('APP_ZENODO_ACCESS_TOKEN', None)

cap/jsonschemas/options/deposits/records/lhcb-v0.0.1.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@
5454
},
5555
"gitlab_links": {
5656
"items": {
57-
"ui:field": "CapFiles"
57+
"ui:field": "CapFiles",
58+
"ui:options": {
59+
"zenodo": true
60+
}
5861
}
5962
},
6063
"ui:object": "accordionObjectField",

cap/modules/zenodo/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""CAP Zenodo."""
4+
5+
from __future__ import absolute_import, print_function

cap/modules/zenodo/views.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# This file is part of CERN Analysis Preservation Framework.
4+
# Copyright (C) 2018 CERN.
5+
#
6+
# CERN Analysis Preservation Framework is free software; you can redistribute
7+
# it and/or modify it under the terms of the GNU General Public License as
8+
# published by the Free Software Foundation; either version 2 of the
9+
# License, or (at your option) any later version.
10+
#
11+
# CERN Analysis Preservation Framework is distributed in the hope that it will
12+
# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
# General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with CERN Analysis Preservation Framework; if not, write to the
18+
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
19+
# MA 02111-1307, USA.
20+
#
21+
# In applying this license, CERN does not
22+
# waive the privileges and immunities granted to it by virtue of its status
23+
# as an Intergovernmental Organization or submit itself to any jurisdiction.
24+
25+
26+
"""CAP Zenodo views."""
27+
28+
import requests
29+
30+
from flask import Blueprint, current_app, jsonify
31+
from invenio_files_rest.models import FileInstance, ObjectVersion
32+
33+
34+
zenodo_bp = Blueprint('cap_zenodo',
35+
__name__,
36+
url_prefix='/zenodo'
37+
)
38+
39+
40+
@zenodo_bp.route('/<bucket_id>/<filename>')
41+
def upload_to_zenodo(bucket_id, filename):
42+
"""Upload code to zenodo."""
43+
zenodo_server_url = current_app.config.get('ZENODO_SERVER_URL')
44+
params = {"access_token": current_app.config.get(
45+
'ZENODO_ACCESS_TOKEN')}
46+
filename = filename + '.tar.gz'
47+
48+
r = requests.post(zenodo_server_url,
49+
params=params, json={},
50+
)
51+
52+
file_obj = ObjectVersion.get(bucket_id, filename)
53+
file = FileInstance.get(file_obj.file_id)
54+
55+
bucket_url = r.json()['links']['bucket']
56+
with open(file.uri, 'rb') as fp:
57+
response = requests.put(
58+
bucket_url + '/{}'.format(filename),
59+
data=fp,
60+
params=params,
61+
)
62+
63+
return jsonify({"status": response.status_code})

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
'Flask-Cache>=0.13.1',
6565
'Flask-Debugtoolbar>=0.10.1',
6666
# CAP specific libraries
67-
'PyGithub>=1.35',
67+
'PyGithub>=1.43.2',
6868
'python-gitlab>=1.0.2',
6969

7070
# Pinned libraries
@@ -143,6 +143,7 @@
143143
'cap_lhcb = cap.modules.experiments.views.lhcb:lhcb_bp',
144144
'cap_cms = cap.modules.experiments.views.cms:cms_bp',
145145
'cap_reana = cap.modules.reana.views:reana_bp',
146+
'cap_zenodo = cap.modules.zenodo.views:zenodo_bp',
146147
'invenio_oauthclient = invenio_oauthclient.views.client:blueprint',
147148
],
148149
'invenio_celery.tasks': [

ui/src/actions/drafts.js

+42
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export const DELETE_FILE_REQUEST = "DELETE_FILE_REQUEST";
5757
export const DELETE_FILE_SUCCESS = "DELETE_FILE_SUCCESS";
5858
export const DELETE_FILE_ERROR = "DELETE_FILE_ERROR";
5959

60+
export const UPLOAD_TO_ZENODO_REQUEST = "UPLOAD_TO_ZENODO_REQUEST";
61+
export const UPLOAD_TO_ZENODO_SUCCESS = "UPLOAD_TO_ZENODO_SUCCESS";
62+
export const UPLOAD_TO_ZENODO_ERROR = "UPLOAD_TO_ZENODO_ERROR";
63+
6064
export const EDIT_PUBLISHED_REQUEST = "EDIT_PUBLISHED_REQUEST";
6165
export const EDIT_PUBLISHED_SUCCESS = "EDIT_PUBLISHED_SUCCESS";
6266
export const EDIT_PUBLISHED_ERROR = "EDIT_PUBLISHED_ERROR";
@@ -320,12 +324,34 @@ export function permissionsItemError(error) {
320324
};
321325
}
322326

327+
323328
export function clearErrorSuccess() {
324329
return {
325330
type: CLEAR_ERROR_SUCCESS
326331
};
327332
}
328333

334+
export function uploadToZenodoRequest() {
335+
return {
336+
type: UPLOAD_TO_ZENODO_REQUEST
337+
};
338+
}
339+
340+
export function uploadToZenodoSuccess(element_id, status) {
341+
return {
342+
type: UPLOAD_TO_ZENODO_SUCCESS,
343+
element_id,
344+
status
345+
};
346+
}
347+
348+
export function uploadToZenodoError(error) {
349+
return {
350+
type: UPLOAD_TO_ZENODO_ERROR,
351+
error
352+
};
353+
}
354+
329355
// [TOFIX] Plug validation action if needed.
330356
// export function validate(data, schema) {
331357
// return dispatch => {
@@ -717,6 +743,22 @@ export function handlePermissions(draft_id, type, email, action, operation) {
717743
};
718744
}
719745

746+
export function uploadToZenodo(element_id, bucket_id, filename) {
747+
return dispatch => {
748+
dispatch(uploadToZenodoRequest());
749+
let file = filename.split("/").pop();
750+
let uri = `/api/zenodo/${bucket_id}/${file}`;
751+
axios
752+
.get(uri)
753+
.then(response => {
754+
dispatch(uploadToZenodoSuccess(element_id, response.status));
755+
})
756+
.catch(error => {
757+
dispatch(uploadToZenodoError(error));
758+
});
759+
};
760+
}
761+
720762
function _get_permissions_data(type, email, action, operation) {
721763
return [
722764
{

ui/src/components/drafts/form/themes/grommet/fields/CapFiles.js

+62-13
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@ import { connect } from "react-redux";
55

66
import Box from "grommet/components/Box";
77
import Anchor from "grommet/components/Anchor";
8+
import Button from "grommet/components/Button"
89

910
import Edit from "grommet/components/icons/base/FormEdit";
10-
import { toggleFilemanagerLayer } from "../../../../../../actions/drafts";
11+
import CloudUploadIcon from "grommet/components/icons/base/CloudUpload";
12+
import {
13+
toggleFilemanagerLayer,
14+
uploadToZenodo
15+
} from "../../../../../../actions/drafts";
16+
17+
import Status from "grommet/components/icons/Status";
1118

1219
class CapFile extends React.Component {
1320
constructor(props) {
1421
super(props);
15-
22+
let isZenodo = props.uiSchema["ui:options"]
23+
? props.uiSchema["ui:options"]["zenodo"]
24+
: null;
1625
this.state = {
1726
layerActive: false,
18-
selected: {}
27+
selected: {},
28+
isZenodo: isZenodo
1929
};
2030
}
2131

@@ -43,6 +53,9 @@ class CapFile extends React.Component {
4353
}
4454

4555
render() {
56+
let bucket = this.props.links ? this.props.links.get("bucket") : null;
57+
let bucket_id = bucket ? bucket.split("/").pop() : null;
58+
4659
return (
4760
<Box
4861
pad="small"
@@ -54,13 +67,35 @@ class CapFile extends React.Component {
5467
wrap={false}
5568
>
5669
{this.props.formData ? (
57-
<React.Fragment>
58-
<span>{this.props.formData}</span>
59-
<Anchor
60-
icon={<Edit />}
61-
onClick={this._toggleFileManager.bind(this)}
62-
/>
63-
</React.Fragment>
70+
<Box>
71+
<Box direction="row">
72+
<Box pad="small">{this.props.formData}</Box>
73+
<Anchor
74+
icon={<Edit />}
75+
onClick={this._toggleFileManager.bind(this)}
76+
/>
77+
</Box>
78+
{this.state.isZenodo ? (
79+
<Box direction="row">
80+
<Button
81+
icon={<CloudUploadIcon />}
82+
label="Upload to zenodo"
83+
onClick={() => {
84+
this.props.uploadToZenodo(
85+
this.props.idSchema.$id,
86+
bucket_id,
87+
this.props.formData
88+
);
89+
}}
90+
/>
91+
{this.props.zenodoId == 200 ? (
92+
<Box pad="small">
93+
<Status value="ok" />
94+
</Box>
95+
) : null}
96+
</Box>
97+
) : null}
98+
</Box>
6499
) : (
65100
<React.Fragment>
66101
<Anchor
@@ -82,17 +117,31 @@ CapFile.propTypes = {
82117
onChange: PropTypes.func,
83118
properties: PropTypes.object,
84119
toggleFilemanagerLayer: PropTypes.func,
85-
formData: PropTypes.object
120+
formData: PropTypes.object,
121+
uploadToZenodo: PropTypes.func,
122+
links: PropTypes.object,
123+
zenodo: PropTypes.object,
124+
uiSchema: PropTypes.object,
125+
idSchema: PropTypes.object
86126
};
87127

128+
function mapStateToProps(state, props) {
129+
return {
130+
links: state.drafts.getIn(["current_item", "links"]),
131+
zenodoId: state.drafts.getIn(["zenodo", props.idSchema.$id, "status"])
132+
};
133+
}
134+
88135
function mapDispatchToProps(dispatch) {
89136
return {
90137
toggleFilemanagerLayer: (selectable = false, action) =>
91-
dispatch(toggleFilemanagerLayer(selectable, action))
138+
dispatch(toggleFilemanagerLayer(selectable, action)),
139+
uploadToZenodo: (element_id, bucket_id, filename) =>
140+
dispatch(uploadToZenodo(element_id, bucket_id, filename))
92141
};
93142
}
94143

95144
export default connect(
96-
null,
145+
mapStateToProps,
97146
mapDispatchToProps
98147
)(CapFile);

ui/src/reducers/drafts.js

+39-13
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ import {
5353
GENERAL_TITLE_CHANGED,
5454
GENERAL_TITLE_REQUEST,
5555
GENERAL_TITLE_SUCCESS,
56-
GENERAL_TITLE_ERROR
56+
GENERAL_TITLE_ERROR,
57+
UPLOAD_TO_ZENODO_REQUEST,
58+
UPLOAD_TO_ZENODO_SUCCESS,
59+
UPLOAD_TO_ZENODO_ERROR
5760
} from "../actions/drafts";
5861

5962
const initialState = Map({
@@ -93,7 +96,8 @@ const initialState = Map({
9396
error: null,
9497
links: null,
9598
permissions: []
96-
})
99+
}),
100+
zenodo: Map({})
97101
});
98102
// IMPORTANT: Note that with Redux, state should NEVER be changed.
99103
// State is considered immutable. Instead,
@@ -222,17 +226,26 @@ export default function depositReducer(state = initialState, action) {
222226
msg: "Error while updating.."
223227
});
224228
case INIT_FORM:
225-
return state.set(
226-
"current_item",
227-
Map({
228-
id: null,
229-
data: null,
230-
loading: false,
231-
error: null,
232-
links: null,
233-
files: Map({})
234-
})
235-
);
229+
return state
230+
.set(
231+
"current_item",
232+
Map({
233+
id: null,
234+
data: null,
235+
loading: false,
236+
error: null,
237+
links: null,
238+
files: Map({})
239+
})
240+
)
241+
.set(
242+
"zenodo",
243+
Map({
244+
loading: false,
245+
error: null,
246+
status: null
247+
})
248+
);
236249
case UPLOAD_FILE_REQUEST:
237250
return state.setIn(["current_item", "files", action.filename], {
238251
key: action.filename,
@@ -327,6 +340,19 @@ export default function depositReducer(state = initialState, action) {
327340
.setIn(["current_item", "error"], action.error.response.data);
328341
case CLEAR_ERROR_SUCCESS:
329342
return state.setIn(["current_item", "error"], null);
343+
case UPLOAD_TO_ZENODO_REQUEST:
344+
return state
345+
.setIn(["zenodo", "loading"], true)
346+
.setIn(["zenodo", "error"], false);
347+
case UPLOAD_TO_ZENODO_SUCCESS:
348+
return state.setIn(
349+
["zenodo", action.element_id, "status"],
350+
action.status
351+
);
352+
case UPLOAD_TO_ZENODO_ERROR:
353+
return state
354+
.setIn(["zenodo", "loading"], false)
355+
.setIn(["zenodo", "error"], action.error);
330356
case FORM_DATA_CHANGE:
331357
return state.setIn(["current_item", "formData"], action.data);
332358
case GENERAL_TITLE_CHANGED:

0 commit comments

Comments
 (0)