Skip to content

Commit

Permalink
deposit: push code to zenodo
Browse files Browse the repository at this point in the history
 * Closes #841.

Signed-off-by: Ioannis Tsanaktsidis <[email protected]>
  • Loading branch information
ioannistsanaktsidis committed Apr 16, 2019
1 parent 5b9cbd7 commit 3332290
Showing 8 changed files with 223 additions and 29 deletions.
7 changes: 6 additions & 1 deletion cap/config.py
Original file line number Diff line number Diff line change
@@ -638,7 +638,7 @@ def _(x):
GITLAB_OAUTH_ACCESS_TOKEN = os.environ.get(
'APP_GITLAB_OAUTH_ACCESS_TOKEN', None)

# Reana server url
# Reana access token
# ================
REANA_ACCESS_TOKEN = {
'ATLAS': os.environ.get(
@@ -650,3 +650,8 @@ def _(x):
'LHCb': os.environ.get(
'APP_REANA_LHCb_ACCESS_TOKEN', None)
}

# Zenodo
# ======
ZENODO_SERVER_URL = os.environ.get('APP_ZENODO_SERVER_URL', None)
ZENODO_ACCESS_TOKEN = os.environ.get('APP_ZENODO_ACCESS_TOKEN', None)
5 changes: 4 additions & 1 deletion cap/jsonschemas/options/deposits/records/lhcb-v0.0.1.json
Original file line number Diff line number Diff line change
@@ -54,7 +54,10 @@
},
"gitlab_links": {
"items": {
"ui:field": "CapFiles"
"ui:field": "CapFiles",
"ui:options": {
"zenodo": true
}
}
},
"ui:object": "accordionObjectField",
5 changes: 5 additions & 0 deletions cap/modules/zenodo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-

"""CAP Zenodo."""

from __future__ import absolute_import, print_function
63 changes: 63 additions & 0 deletions cap/modules/zenodo/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
# This file is part of CERN Analysis Preservation Framework.
# Copyright (C) 2018 CERN.
#
# CERN Analysis Preservation Framework is free software; you can redistribute
# it and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# CERN Analysis Preservation Framework is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CERN Analysis Preservation Framework; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.


"""CAP Zenodo views."""

import requests

from flask import Blueprint, current_app, jsonify
from invenio_files_rest.models import FileInstance, ObjectVersion


zenodo_bp = Blueprint('cap_zenodo',
__name__,
url_prefix='/zenodo'
)


@zenodo_bp.route('/<bucket_id>/<filename>')
def upload_to_zenodo(bucket_id, filename):
"""Upload code to zenodo."""
zenodo_server_url = current_app.config.get('ZENODO_SERVER_URL')
params = {"access_token": current_app.config.get(
'ZENODO_ACCESS_TOKEN')}
filename = filename + '.tar.gz'

r = requests.post(zenodo_server_url,
params=params, json={},
)

file_obj = ObjectVersion.get(bucket_id, filename)
file = FileInstance.get(file_obj.file_id)

bucket_url = r.json()['links']['bucket']
with open(file.uri, 'rb') as fp:
response = requests.put(
bucket_url + '/{}'.format(filename),
data=fp,
params=params,
)

return jsonify({"status": response.status_code})
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@
'Flask-Cache>=0.13.1',
'Flask-Debugtoolbar>=0.10.1',
# CAP specific libraries
'PyGithub>=1.35',
'PyGithub>=1.43.2',
'python-gitlab>=1.0.2',

# Pinned libraries
@@ -143,6 +143,7 @@
'cap_lhcb = cap.modules.experiments.views.lhcb:lhcb_bp',
'cap_cms = cap.modules.experiments.views.cms:cms_bp',
'cap_reana = cap.modules.reana.views:reana_bp',
'cap_zenodo = cap.modules.zenodo.views:zenodo_bp',
'invenio_oauthclient = invenio_oauthclient.views.client:blueprint',
],
'invenio_celery.tasks': [
42 changes: 42 additions & 0 deletions ui/src/actions/drafts.js
Original file line number Diff line number Diff line change
@@ -57,6 +57,10 @@ export const DELETE_FILE_REQUEST = "DELETE_FILE_REQUEST";
export const DELETE_FILE_SUCCESS = "DELETE_FILE_SUCCESS";
export const DELETE_FILE_ERROR = "DELETE_FILE_ERROR";

export const UPLOAD_TO_ZENODO_REQUEST = "UPLOAD_TO_ZENODO_REQUEST";
export const UPLOAD_TO_ZENODO_SUCCESS = "UPLOAD_TO_ZENODO_SUCCESS";
export const UPLOAD_TO_ZENODO_ERROR = "UPLOAD_TO_ZENODO_ERROR";

export const EDIT_PUBLISHED_REQUEST = "EDIT_PUBLISHED_REQUEST";
export const EDIT_PUBLISHED_SUCCESS = "EDIT_PUBLISHED_SUCCESS";
export const EDIT_PUBLISHED_ERROR = "EDIT_PUBLISHED_ERROR";
@@ -320,12 +324,34 @@ export function permissionsItemError(error) {
};
}


export function clearErrorSuccess() {
return {
type: CLEAR_ERROR_SUCCESS
};
}

export function uploadToZenodoRequest() {
return {
type: UPLOAD_TO_ZENODO_REQUEST
};
}

export function uploadToZenodoSuccess(element_id, status) {
return {
type: UPLOAD_TO_ZENODO_SUCCESS,
element_id,
status
};
}

export function uploadToZenodoError(error) {
return {
type: UPLOAD_TO_ZENODO_ERROR,
error
};
}

// [TOFIX] Plug validation action if needed.
// export function validate(data, schema) {
// return dispatch => {
@@ -717,6 +743,22 @@ export function handlePermissions(draft_id, type, email, action, operation) {
};
}

export function uploadToZenodo(element_id, bucket_id, filename) {
return dispatch => {
dispatch(uploadToZenodoRequest());
let file = filename.split("/").pop();
let uri = `/api/zenodo/${bucket_id}/${file}`;
axios
.get(uri)
.then(response => {
dispatch(uploadToZenodoSuccess(element_id, response.status));
})
.catch(error => {
dispatch(uploadToZenodoError(error));
});
};
}

function _get_permissions_data(type, email, action, operation) {
return [
{
75 changes: 62 additions & 13 deletions ui/src/components/drafts/form/themes/grommet/fields/CapFiles.js
Original file line number Diff line number Diff line change
@@ -5,17 +5,27 @@ import { connect } from "react-redux";

import Box from "grommet/components/Box";
import Anchor from "grommet/components/Anchor";
import Button from "grommet/components/Button"

import Edit from "grommet/components/icons/base/FormEdit";
import { toggleFilemanagerLayer } from "../../../../../../actions/drafts";
import CloudUploadIcon from "grommet/components/icons/base/CloudUpload";
import {
toggleFilemanagerLayer,
uploadToZenodo
} from "../../../../../../actions/drafts";

import Status from "grommet/components/icons/Status";

class CapFile extends React.Component {
constructor(props) {
super(props);

let isZenodo = props.uiSchema["ui:options"]
? props.uiSchema["ui:options"]["zenodo"]
: null;
this.state = {
layerActive: false,
selected: {}
selected: {},
isZenodo: isZenodo
};
}

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

render() {
let bucket = this.props.links ? this.props.links.get("bucket") : null;
let bucket_id = bucket ? bucket.split("/").pop() : null;

return (
<Box
pad="small"
@@ -54,13 +67,35 @@ class CapFile extends React.Component {
wrap={false}
>
{this.props.formData ? (
<React.Fragment>
<span>{this.props.formData}</span>
<Anchor
icon={<Edit />}
onClick={this._toggleFileManager.bind(this)}
/>
</React.Fragment>
<Box>
<Box direction="row">
<Box pad="small">{this.props.formData}</Box>
<Anchor
icon={<Edit />}
onClick={this._toggleFileManager.bind(this)}
/>
</Box>
{this.state.isZenodo ? (
<Box direction="row">
<Button
icon={<CloudUploadIcon />}
label="Upload to zenodo"
onClick={() => {
this.props.uploadToZenodo(
this.props.idSchema.$id,
bucket_id,
this.props.formData
);
}}
/>
{this.props.zenodoId == 200 ? (
<Box pad="small">
<Status value="ok" />
</Box>
) : null}
</Box>
) : null}
</Box>
) : (
<React.Fragment>
<Anchor
@@ -82,17 +117,31 @@ CapFile.propTypes = {
onChange: PropTypes.func,
properties: PropTypes.object,
toggleFilemanagerLayer: PropTypes.func,
formData: PropTypes.object
formData: PropTypes.object,
uploadToZenodo: PropTypes.func,
links: PropTypes.object,
zenodo: PropTypes.object,
uiSchema: PropTypes.object,
idSchema: PropTypes.object
};

function mapStateToProps(state, props) {
return {
links: state.drafts.getIn(["current_item", "links"]),
zenodoId: state.drafts.getIn(["zenodo", props.idSchema.$id, "status"])
};
}

function mapDispatchToProps(dispatch) {
return {
toggleFilemanagerLayer: (selectable = false, action) =>
dispatch(toggleFilemanagerLayer(selectable, action))
dispatch(toggleFilemanagerLayer(selectable, action)),
uploadToZenodo: (element_id, bucket_id, filename) =>
dispatch(uploadToZenodo(element_id, bucket_id, filename))
};
}

export default connect(
null,
mapStateToProps,
mapDispatchToProps
)(CapFile);
52 changes: 39 additions & 13 deletions ui/src/reducers/drafts.js
Original file line number Diff line number Diff line change
@@ -53,7 +53,10 @@ import {
GENERAL_TITLE_CHANGED,
GENERAL_TITLE_REQUEST,
GENERAL_TITLE_SUCCESS,
GENERAL_TITLE_ERROR
GENERAL_TITLE_ERROR,
UPLOAD_TO_ZENODO_REQUEST,
UPLOAD_TO_ZENODO_SUCCESS,
UPLOAD_TO_ZENODO_ERROR
} from "../actions/drafts";

const initialState = Map({
@@ -93,7 +96,8 @@ const initialState = Map({
error: null,
links: null,
permissions: []
})
}),
zenodo: Map({})
});

export default function draftsReducer(state = initialState, action) {
@@ -218,17 +222,26 @@ export default function draftsReducer(state = initialState, action) {
msg: "Error while updating.."
});
case INIT_FORM:
return state.set(
"current_item",
Map({
id: null,
data: null,
loading: false,
error: null,
links: null,
files: Map({})
})
);
return state
.set(
"current_item",
Map({
id: null,
data: null,
loading: false,
error: null,
links: null,
files: Map({})
})
)
.set(
"zenodo",
Map({
loading: false,
error: null,
status: null
})
);
case UPLOAD_FILE_REQUEST:
return state.setIn(["current_item", "files", action.filename], {
key: action.filename,
@@ -323,6 +336,19 @@ export default function draftsReducer(state = initialState, action) {
.setIn(["current_item", "error"], action.error.response.data);
case CLEAR_ERROR_SUCCESS:
return state.setIn(["current_item", "error"], null);
case UPLOAD_TO_ZENODO_REQUEST:
return state
.setIn(["zenodo", "loading"], true)
.setIn(["zenodo", "error"], false);
case UPLOAD_TO_ZENODO_SUCCESS:
return state.setIn(
["zenodo", action.element_id, "status"],
action.status
);
case UPLOAD_TO_ZENODO_ERROR:
return state
.setIn(["zenodo", "loading"], false)
.setIn(["zenodo", "error"], action.error);
case FORM_DATA_CHANGE:
return state.setIn(["current_item", "formData"], action.data);
case GENERAL_TITLE_CHANGED:

0 comments on commit 3332290

Please sign in to comment.