diff --git a/plugin.cmake b/plugin.cmake index 9322d41f..7e296e32 100644 --- a/plugin.cmake +++ b/plugin.cmake @@ -21,5 +21,6 @@ add_python_test(dataone_register plugins/wholetale/test_cn_switch.txt plugins/wholetale/dataone_register_test01.json ) +add_python_test(integration PLUGIN wholetale) add_python_style_test(python_static_analysis_wholetale "${PROJECT_SOURCE_DIR}/plugins/wholetale/server") diff --git a/plugin_tests/integration_test.py b/plugin_tests/integration_test.py new file mode 100644 index 00000000..0694ffb2 --- /dev/null +++ b/plugin_tests/integration_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from tests import base + + +def setUpModule(): + base.enabledPlugins.append('wholetale') + base.startServer() + + +def tearDownModule(): + base.stopServer() + + +class IntegrationTestCase(base.TestCase): + + def testDataverseIntegration(self): + resp = self.request( + '/integration/dataverse', method='GET', + params={'fileId': 'blah', 'siteUrl': 'https://dataverse.someplace'}) + self.assertStatus(resp, 400) + self.assertEqual(resp.json, { + 'message': 'Invalid fileId (should be integer)', + 'type': 'rest' + }) + + resp = self.request( + '/integration/dataverse', method='GET', + params={'fileId': '1234', 'siteUrl': 'definitely not a URL'}) + self.assertStatus(resp, 400) + self.assertEqual(resp.json, { + 'message': 'Not a valid URL: siteUrl', + 'type': 'rest' + }) + + resp = self.request( + '/integration/dataverse', method='GET', + params={'fileId': '1234', 'siteUrl': 'https://dataverse.someplace'}) + self.assertStatus(resp, 303) + self.assertEqual( + resp.headers['Location'], + 'https://dashboard.wholetale.org/compose?url=' + 'https%3A%2F%2Fdataverse.someplace%2Fapi%2Faccess%2Fdatafile%2F1234%2F' + ) diff --git a/server/__init__.py b/server/__init__.py index 4eed1136..6fa21f66 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -24,6 +24,7 @@ from .rest.dataset import Dataset from .rest.recipe import Recipe from .rest.image import Image +from .rest.integration import Integration from .rest.repository import Repository from .rest.publish import Publish from .rest.harvester import listImportedData @@ -359,6 +360,7 @@ def load(info): events.bind('model.user.save.created', 'wholetale', addDefaultFolders) info['apiRoot'].repository = Repository() info['apiRoot'].publish = Publish() + info['apiRoot'].integration = Integration() info['apiRoot'].folder.route('GET', ('registered',), listImportedData) info['apiRoot'].folder.route('GET', (':id', 'listing'), listFolder) info['apiRoot'].folder.route('GET', (':id', 'dataset'), getDataSet) diff --git a/server/rest/integration.py b/server/rest/integration.py new file mode 100644 index 00000000..d726a83d --- /dev/null +++ b/server/rest/integration.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import cherrypy +import validators +from urllib.parse import urlparse, urlunparse, urlencode +from girder.api import access +from girder.api.describe import Description, autoDescribeRoute +from girder.api.rest import Resource, RestException, setResponseHeader + + +class Integration(Resource): + + def __init__(self): + super(Integration, self).__init__() + self.resourceName = 'integration' + + self.route('GET', ('dataverse',), self.dataverseExternalTools) + + @access.public + @autoDescribeRoute( + Description('Convert external tools request and bounce it to the dashboard.') + .param('fileId', 'The Dataverse database ID of a file the external tool has ' + 'been launched on.', required=True) + .param('siteUrl', 'The URL of the Dataverse installation that hosts the file ' + 'with the fileId above', required=True) + .param('apiToken', 'The Dataverse API token of the user launching the external' + ' tool, if available.', required=False) + .notes('apiToken is currently ignored.') + ) + def dataverseExternalTools(self, fileId, siteUrl, apiToken): + if not validators.url(siteUrl): + raise RestException('Not a valid URL: siteUrl') + try: + fileId = int(fileId) + except (TypeError, ValueError): + raise RestException('Invalid fileId (should be integer)') + + site = urlparse(siteUrl) + url = '{scheme}://{netloc}/api/access/datafile/{fileId}/'.format( + scheme=site.scheme, netloc=site.netloc, fileId=fileId + ) + + # TODO: Make base url a plugin setting, defaulting to dashboard. + dashboard_url = os.environ.get('DASHBOARD_URL', 'https://dashboard.wholetale.org') + location = urlunparse( + urlparse(dashboard_url)._replace( + path='/compose', + query=urlencode({'url': url})) + ) + setResponseHeader('Location', location) + cherrypy.response.status = 303