From c95d3e9d4e0910ad851d0d72cf015331e122bee7 Mon Sep 17 00:00:00 2001 From: Ioannis Tsanaktsidis Date: Fri, 14 Sep 2018 14:24:28 +0200 Subject: [PATCH] deposit: push code to zenodo * Closes #841. Signed-off-by: Ioannis Tsanaktsidis --- cap/config.py | 10 +- .../options/deposits/records/lhcb-v0.0.1.json | 7 +- cap/modules/zenodo/__init__.py | 5 + cap/modules/zenodo/views.py | 62 ++++++++++++ setup.py | 3 +- ui/src/actions/drafts.js | 42 +++++++++ .../form/themes/grommet/fields/CapFiles.js | 94 ++++++++++++++----- ui/src/reducers/drafts.js | 50 +++++++--- 8 files changed, 233 insertions(+), 40 deletions(-) create mode 100644 cap/modules/zenodo/__init__.py create mode 100644 cap/modules/zenodo/views.py diff --git a/cap/config.py b/cap/config.py index fb0d36e3c0..b2c623df1b 100644 --- a/cap/config.py +++ b/cap/config.py @@ -444,9 +444,9 @@ def _(x): # Update CERN OAuth handlers - due to REST - mostly only redirect urls # and error flashing CERN_REMOTE_APP.update(dict( - authorized_handler=authorized_signup_handler, - disconnect_handler=disconnect_handler, - )) + authorized_handler=authorized_signup_handler, + disconnect_handler=disconnect_handler, +)) CERN_REMOTE_APP['signup_handler']['view'] = signup_handler @@ -603,3 +603,7 @@ def _(x): REANA_CLIENT_TOKEN = os.environ.get( 'APP_REANA_CLIENT_TOKEN', None) + +# Zenodo +# ====== +ZENODO_ACCESS_TOKEN = os.environ.get('APP_ZENODO_ACCESS_TOKEN', None) diff --git a/cap/jsonschemas/options/deposits/records/lhcb-v0.0.1.json b/cap/jsonschemas/options/deposits/records/lhcb-v0.0.1.json index 4df2d8a892..1b3ace8bfd 100644 --- a/cap/jsonschemas/options/deposits/records/lhcb-v0.0.1.json +++ b/cap/jsonschemas/options/deposits/records/lhcb-v0.0.1.json @@ -54,7 +54,10 @@ }, "gitlab_links": { "items": { - "ui:field": "CapFiles" + "ui:field": "CapFiles", + "ui:options": { + "zenodo": true + } } }, "ui:object": "accordionObjectField", @@ -247,4 +250,4 @@ } }, "is_deposit": false -} \ No newline at end of file +} diff --git a/cap/modules/zenodo/__init__.py b/cap/modules/zenodo/__init__.py new file mode 100644 index 0000000000..502a593b85 --- /dev/null +++ b/cap/modules/zenodo/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +"""CAP Zenodo.""" + +from __future__ import absolute_import, print_function diff --git a/cap/modules/zenodo/views.py b/cap/modules/zenodo/views.py new file mode 100644 index 0000000000..2cffe28598 --- /dev/null +++ b/cap/modules/zenodo/views.py @@ -0,0 +1,62 @@ +# -*- 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('//') +def upload_to_zenodo(bucket_id, filename): + """Upload code to zenodo.""" + params = {"access_token": current_app.config.get( + 'ZENODO_ACCESS_TOKEN', '')} + filename = filename + '.tar.gz' + + r = requests.post('https://sandbox.zenodo.org/api/deposit/depositions', + 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}) diff --git a/setup.py b/setup.py index 3d7612bcef..fc32df0e13 100644 --- a/setup.py +++ b/setup.py @@ -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 @@ -134,6 +134,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': [ diff --git a/ui/src/actions/drafts.js b/ui/src/actions/drafts.js index e3b3e935f1..b093795b7b 100644 --- a/ui/src/actions/drafts.js +++ b/ui/src/actions/drafts.js @@ -51,6 +51,10 @@ export const UPLOAD_FILE_REQUEST = "UPLOAD_FILE_REQUEST"; export const UPLOAD_FILE_SUCCESS = "UPLOAD_FILE_SUCCESS"; export const UPLOAD_FILE_ERROR = "UPLOAD_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"; @@ -298,12 +302,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 => { @@ -603,6 +629,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 [ { diff --git a/ui/src/components/drafts/form/themes/grommet/fields/CapFiles.js b/ui/src/components/drafts/form/themes/grommet/fields/CapFiles.js index 416f5b43fb..2ed5d51f52 100644 --- a/ui/src/components/drafts/form/themes/grommet/fields/CapFiles.js +++ b/ui/src/components/drafts/form/themes/grommet/fields/CapFiles.js @@ -3,18 +3,27 @@ import PropTypes from "prop-types"; import { connect } from "react-redux"; -import { Box, Anchor } from "grommet"; +import { Box, Anchor, Button } from "grommet"; 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 }; } @@ -42,6 +51,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 ( {this.props.formData ? ( - - {this.props.formData} - } - onClick={this._toggleFileManager.bind(this)} - /> - + + + {this.props.formData} + } + onClick={this._toggleFileManager.bind(this)} + /> + + {this.state.isZenodo ? ( + +