diff --git a/.gitignore b/.gitignore index 1f518e762f8..2904bc578f2 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ scripts/installer/default.config # ignore UI testing related files tests/node_modules tests/package-lock.json +venv diff --git a/conf/docker-aio/run-test-suite.sh b/conf/docker-aio/run-test-suite.sh index 3027577b230..c1fca242389 100755 --- a/conf/docker-aio/run-test-suite.sh +++ b/conf/docker-aio/run-test-suite.sh @@ -8,4 +8,4 @@ fi # Please note the "dataverse.test.baseurl" is set to run for "all-in-one" Docker environment. # TODO: Rather than hard-coding the list of "IT" classes here, add a profile to pom.xml. -mvn test -Dtest=DataversesIT,DatasetsIT,SwordIT,AdminIT,BuiltinUsersIT,UsersIT,UtilIT,ConfirmEmailIT,FileMetadataIT,FilesIT,SearchIT,InReviewWorkflowIT,HarvestingServerIT,MoveIT,MakeDataCountApiIT,FileTypeDetectionIT,EditDDIIT -Ddataverse.test.baseurl=$dvurl +mvn test -Dtest=DataversesIT,DatasetsIT,SwordIT,AdminIT,BuiltinUsersIT,UsersIT,UtilIT,ConfirmEmailIT,FileMetadataIT,FilesIT,SearchIT,InReviewWorkflowIT,HarvestingServerIT,MoveIT,MakeDataCountApiIT,FileTypeDetectionIT,EditDDIIT,ExternalToolsIT -Ddataverse.test.baseurl=$dvurl diff --git a/doc/sphinx-guides/source/_static/admin/dataverse-external-tools.tsv b/doc/sphinx-guides/source/_static/admin/dataverse-external-tools.tsv new file mode 100644 index 00000000000..0d251202c22 --- /dev/null +++ b/doc/sphinx-guides/source/_static/admin/dataverse-external-tools.tsv @@ -0,0 +1,5 @@ +TwoRavens explore file A system of interlocking statistical tools for data exploration, analysis, and meta-analysis: http://2ra.vn. See the :doc:`/user/data-exploration/tworavens` section of the User Guide for more information on TwoRavens from the user perspective and the :doc:`/installation/r-rapache-tworavens` section of the Installation Guide. +Data Explorer explore file A GUI which lists the variables in a tabular data file allowing searching, charting and cross tabulation analysis. See the README.md file at https://github.com/scholarsportal/Dataverse-Data-Explorer for the instructions on adding Data Explorer to your Dataverse; and the :doc:`/installation/prerequisites` section of the Installation Guide for the instructions on how to set up **basic R configuration required** (specifically, Dataverse uses R to generate .prep metadata files that are needed to run Data Explorer). +Whole Tale explore file A platform for the creation of reproducible research packages that allows users to launch containerized interactive analysis environments based on popular tools such as Jupyter and RStudio. Using this integration, Dataverse users can launch Jupyter and RStudio environments to analyze published datasets. For more information, see the `Whole Tale User Guide `_. +File Previewers explore file A set of tools that display the content of files - including audio, html, `Hypothes.is `_ annotations, images, PDF, text, video - allowing them to be viewed without downloading. The previewers can be run directly from github.io, so the only required step is using the Dataverse API to register the ones you want to use. Documentation, including how to optionally brand the previewers, and an invitation to contribute through github are in the README.md file. https://github.com/QualitativeDataRepository/dataverse-previewers +Data Curation Tool configure file A GUI for curating data by adding labels, groups, weights and other details to assist with informed reuse. See the README.md file at https://github.com/scholarsportal/Dataverse-Data-Curation-Tool for the installation instructions. diff --git a/doc/sphinx-guides/source/_static/installation/files/root/external-tools/dynamicDatasetTool.json b/doc/sphinx-guides/source/_static/installation/files/root/external-tools/dynamicDatasetTool.json new file mode 100644 index 00000000000..2a9a888dddf --- /dev/null +++ b/doc/sphinx-guides/source/_static/installation/files/root/external-tools/dynamicDatasetTool.json @@ -0,0 +1,17 @@ +{ + "displayName": "Dynamic Dataset Tool", + "description": "Dazzles! Dizzying!", + "scope": "dataset", + "type": "explore", + "toolUrl": "https://dynamicdatasettool.com/v2", + "toolParameters": { + "queryParameters": [ + { + "PID": "{datasetPid}" + }, + { + "apiToken": "{apiToken}" + } + ] + } +} diff --git a/doc/sphinx-guides/source/_static/installation/files/root/external-tools/awesomeTool.json b/doc/sphinx-guides/source/_static/installation/files/root/external-tools/fabulousFileTool.json similarity index 59% rename from doc/sphinx-guides/source/_static/installation/files/root/external-tools/awesomeTool.json rename to doc/sphinx-guides/source/_static/installation/files/root/external-tools/fabulousFileTool.json index c3b87a3c258..e1dc0475d28 100644 --- a/doc/sphinx-guides/source/_static/installation/files/root/external-tools/awesomeTool.json +++ b/doc/sphinx-guides/source/_static/installation/files/root/external-tools/fabulousFileTool.json @@ -1,9 +1,10 @@ { - "displayName": "Awesome Tool", - "description": "The most awesome tool.", + "displayName": "Fabulous File Tool", + "description": "Fabulous Fun for Files!", + "scope": "file", "type": "explore", + "toolUrl": "https://fabulousfiletool.com", "contentType": "text/tab-separated-values", - "toolUrl": "https://awesometool.com", "toolParameters": { "queryParameters": [ { diff --git a/doc/sphinx-guides/source/admin/external-tools.rst b/doc/sphinx-guides/source/admin/external-tools.rst new file mode 100644 index 00000000000..6054334e4a6 --- /dev/null +++ b/doc/sphinx-guides/source/admin/external-tools.rst @@ -0,0 +1,103 @@ +External Tools +============== + +External tools can provide additional features that are not part of Dataverse itself, such as data exploration. + +.. contents:: |toctitle| + :local: + +.. _inventory-of-external-tools: + +Inventory of External Tools +--------------------------- + +.. csv-table:: + :header: "Tool", "Type", "Scope", "Description" + :widths: 20, 10, 5, 65 + :delim: tab + :file: ../_static/admin/dataverse-external-tools.tsv + +.. _managing-external-tools: + +Managing External Tools +----------------------- + +Adding External Tools to Dataverse ++++++++++++++++++++++++++++++++++++ + +To add an external tool to your installation of Dataverse you must first download a JSON file for that tool, which we refer to as a "manifest". It should look something like this: + +.. literalinclude:: ../_static/installation/files/root/external-tools/fabulousFileTool.json + +Go to :ref:`inventory-of-external-tools` and download a JSON manifest for one of the tools by following links in the description to installation instructions. + +In the curl command below, replace the placeholder "fabulousFileTool.json" placeholder for the actual name of the JSON file you downloaded. + +.. code-block:: bash + + curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools --upload-file fabulousFileTool.json + +Listing All External Tools in Dataverse ++++++++++++++++++++++++++++++++++++++++ + +To list all the external tools that are available in Dataverse: + +.. code-block:: bash + + curl http://localhost:8080/api/admin/externalTools + +Showing an External Tool in Dataverse ++++++++++++++++++++++++++++++++++++++ + +To show one of the external tools that are available in Dataverse, pass its database id: + +.. code-block:: bash + + export TOOL_ID=1 + curl http://localhost:8080/api/admin/externalTools/$TOOL_ID + +Removing an External Tool From Dataverse +++++++++++++++++++++++++++++++++++++++++ + +Assuming the external tool database id is "1", remove it with the following command: + +.. code-block:: bash + + export TOOL_ID=1 + curl -X DELETE http://localhost:8080/api/admin/externalTools/$TOOL_ID + +.. _testing-external-tools: + +Testing External Tools +---------------------- + +Once you have added an external tool to your installation of Dataverse, you will probably want to test it to make sure it is functioning properly. + +File Level Explore Tools +++++++++++++++++++++++++ + +File level explore tools are specific to the file type (content type or MIME type) of the file. For example, there is a tool for exploring PDF files in the "File Previewers" set of tools. + +An "Explore" button will appear (on both the dataset page and the file landing page) for files that match the type that the tool has been built for. When there are multiple explore tools for a filetype, the button becomes a dropdown. + +File Level Configure Tools +++++++++++++++++++++++++++ + +File level configure tools are only available when you log in and have write access to the file. The file type determines if a configure tool is available. For example, a configure tool may only be available for tabular files. + +Dataset Level Explore Tools ++++++++++++++++++++++++++++ + +When a dataset level explore tool is added, an "Explore" button on the dataset page will appear. This button becomes a drop down when there are multiple tools. + +Dataset Level Configure Tools ++++++++++++++++++++++++++++++ + +Configure tools at the dataset level are not currently supported. No button appears in the GUI if you add this type of tool. + +Writing Your Own External Tool +------------------------------ + +If you plan to write a external tool, see the :doc:`/api/external-tools` section of the API Guide. + +If you have an idea for an external tool, please let the Dataverse community know by posting about it on the dataverse-community mailing list: https://groups.google.com/forum/#!forum/dataverse-community diff --git a/doc/sphinx-guides/source/admin/index.rst b/doc/sphinx-guides/source/admin/index.rst index 670f315c5a5..39b4f5748d3 100755 --- a/doc/sphinx-guides/source/admin/index.rst +++ b/doc/sphinx-guides/source/admin/index.rst @@ -13,6 +13,7 @@ This guide documents the functionality only available to superusers (such as "da .. toctree:: dashboard + external-tools harvestclients harvestserver metadatacustomization diff --git a/doc/sphinx-guides/source/admin/integrations.rst b/doc/sphinx-guides/source/admin/integrations.rst index c7924cd7c20..62a3121ed25 100644 --- a/doc/sphinx-guides/source/admin/integrations.rst +++ b/doc/sphinx-guides/source/admin/integrations.rst @@ -49,14 +49,14 @@ Data Explorer Data Explorer is a GUI which lists the variables in a tabular data file allowing searching, charting and cross tabulation analysis. -For installation instructions, see the :doc:`/installation/external-tools` section of the Installation Guide. +For installation instructions, see the :doc:`external-tools` section. TwoRavens/Zelig +++++++++++++++ TwoRavens is a web application for tabular data exploration and statistical analysis with Zelig. -For installation instructions, see the :doc:`/installation/external-tools` section of the Installation Guide. +For installation instructions, see the :doc:`external-tools` section. WorldMap ++++++++ @@ -75,7 +75,7 @@ Whole Tale `Whole Tale `_ enables researchers to analyze data using popular tools including Jupyter and RStudio with the ultimate goal of supporting publishing of reproducible research packages. Users can `import data from Dataverse -`_ via identifier (e.g., DOI, URI, etc) or through the External Tools integration. For installation instructions, see the :doc:`/installation/external-tools` section of this Installation Guide or the `Integration `_ section of the Whole Tale User Guide. +`_ via identifier (e.g., DOI, URI, etc) or through the External Tools integration. For installation instructions, see the :doc:`external-tools` section or the `Integration `_ section of the Whole Tale User Guide. Discoverability --------------- diff --git a/doc/sphinx-guides/source/api/apps.rst b/doc/sphinx-guides/source/api/apps.rst index f9d8d4c9b02..a7bc1cb3a4c 100755 --- a/doc/sphinx-guides/source/api/apps.rst +++ b/doc/sphinx-guides/source/api/apps.rst @@ -1,7 +1,7 @@ Apps ==== -The introduction of Dataverse APIs has fostered the development of a variety of software applications that are listed in the :doc:`/admin/integrations` and :doc:`/admin/reporting-tools` sections of the Admin Guide and the :doc:`/installation/external-tools` section of the Installation Guide. +The introduction of Dataverse APIs has fostered the development of a variety of software applications that are listed in the :doc:`/admin/integrations`, :doc:`/admin/external-tools`, and :doc:`/admin/reporting-tools` sections of the Admin Guide. The apps below are open source and demonstrate how to use Dataverse APIs. Some of these apps are built on :doc:`/api/client-libraries` that are available for Dataverse APIs in Python, R, and Java. diff --git a/doc/sphinx-guides/source/api/external-tools.rst b/doc/sphinx-guides/source/api/external-tools.rst new file mode 100644 index 00000000000..7662200e061 --- /dev/null +++ b/doc/sphinx-guides/source/api/external-tools.rst @@ -0,0 +1,150 @@ +Building External Tools +======================= + +External tools can provide additional features that are not part of Dataverse itself, such as data exploration. Thank you for your interest in building an external tool for Dataverse! + +.. contents:: |toctitle| + :local: + +Introduction +------------ + +You can think of a external tool as **a glorified hyperlink** that opens a browser window in a new tab on some other website. The term "external" is used to indicate that the user has left the Dataverse web interface. For example, perhaps the user is looking at a dataset on https://demo.dataverse.org . They click "Explore" and are brought to https://fabulousfiletool.com?fileId=42&siteUrl=http://demo.dataverse.org + +The "other website" (fabulousfiletool.com in the example above) is probably part of the same ecosystem of scholarly publishing that Dataverse itself participates in. Sometimes the other website runs entirely in the browser. Sometimes the other website is a full blown server side web application like Dataverse itself. + +The possibilities for external tools are endless. Let's look at some examples to get your creative juices flowing. + +Examples of External Tools +-------------------------- + +Note: This is the same list that appears in the :doc:`/admin/external-tools` section of the Admin Guide. + +.. csv-table:: + :header: "Tool", "Type", "Scope", "Description" + :widths: 20, 10, 5, 65 + :delim: tab + :file: ../_static/admin/dataverse-external-tools.tsv + +How External Tools Are Presented to Users +----------------------------------------- + +In short, an external tool appears under an "Explore" or "Configure" button either on a dataset landing page or a file landing page. See also the :ref:`testing-external-tools` section of the Admin Guide for some perspective on how installations of Dataverse will expect to test your tool before announcing it to their users. + +Creating an External Tool Manifest +---------------------------------- + +External tools must be expressed in an external tool manifest file, a specific JSON format Dataverse requires. As the author of an external tool, you are expected to provide this JSON file and installation instructions on a web page for your tool. + +Examples of Manifests ++++++++++++++++++++++ + +Let's look at two examples of external tool manifests (one at the file level and one at the dataset level) before we dive into how they work. + +External Tools for Files +^^^^^^^^^^^^^^^^^^^^^^^^ + +:download:`fabulousFileTool.json <../_static/installation/files/root/external-tools/fabulousFileTool.json>` is a file level explore tool that operates on tabular files: + +.. literalinclude:: ../_static/installation/files/root/external-tools/fabulousFileTool.json + +External Tools for Datasets +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:download:`dynamicDatasetTool.json <../_static/installation/files/root/external-tools/dynamicDatasetTool.json>` is a dataset level explore tool: + +.. literalinclude:: ../_static/installation/files/root/external-tools/dynamicDatasetTool.json + +Terminology ++++++++++++ + +.. table:: + :widths: 20, 80 + + =========================== ========== + Term Definition + =========================== ========== + external tool manifest A **JSON file** the defines the URL constructed by Dataverse when users click "Explore" or "Configure" buttons. External tool makers are asked to host this JSON file on a website (no app store yet, sorry) and explain how to use install and use the tool. Examples include :download:`fabulousFileTool.json <../_static/installation/files/root/external-tools/fabulousFileTool.json>` and :download:`dynamicDatasetTool.json <../_static/installation/files/root/external-tools/dynamicDatasetTool.json>` as well as the real world examples above such as Data Explorer. + + displayName The **name** of the tool in the Dataverse web interface. For example, "Data Explorer". + + description The **description** of the tool, which appears in a popup (for configure tools only) so the user who clicked the tool can learn about the tool before being redirected the tool in a new tab in their browser. HTML is supported. + + scope Whether the external tool appears and operates at the **file** level or the **dataset** level. Note that a file level tool much also specify the type of file it operates on (see "contentType" below). + + type Whether the external tool is an **explore** tool or a **configure** tool. Configure tools require an API token because they make changes to data files (files within datasets). Configure tools are currently not supported at the dataset level (no "Configure" button appears in the GUI for datasets). + + toolUrl The **base URL** of the tool before query parameters are added. + + contentType File level tools operate on a specific **file type** (content type or MIME type). Dataset level tools do not use contentType. + + toolParameters **Query parameters** are supported and described below. + + queryParameters **Key/value combinations** that can be appended to the toolUrl. For example, once substitution takes place (described below) the user may be redirected to ``https://fabulousfiletool.com?fileId=42&siteUrl=http://demo.dataverse.org``. + + query parameter keys An **arbitrary string** to associate with a value that is populated with a reserved word (described below). As the author of the tool, you have control over what "key" you would like to be passed to your tool. For example, if you want to have your tool receive and operate on the query parameter "dataverseFileId=42" instead of just "fileId=42", that's fine. + + query parameter values A **mechanism for substituting reserved words with dynamic content**. For example, in your manifest file, you can use a reserved word (described below) such as ``{fileId}`` to pass a file's database id to your tool in a query parameter. Your tool might receive this query parameter as "fileId=42". + + reserved words A **set of strings surrounded by curly braces** such as ``{fileId}`` or ``{datasetId}`` that will be inserted into query parameters. See the table below for a complete list. + =========================== ========== + +Reserved Words +++++++++++++++ + +.. table:: + :widths: 15, 15, 70 + + =========================== ========== =========== + Reserved word Status Description + =========================== ========== =========== + ``{siteUrl}`` optional The URL of the Dataverse installation from which the tool was launched. For example, ``https://demo.dataverse.org``. + + ``{fileId}`` depends The database ID of a file the user clicks "Explore" or "Configure" on. For example, ``42``. This reserved word is **required for file level tools** unless you use ``{filePid}`` instead. + + ``{filePid}`` depends The Persistent ID (DOI or Handle) of a file the user clicks "Explore" or "Configure" on. For example, ``doi:10.7910/DVN/TJCLKP/3VSTKY``. Note that not all installations of Dataverse have Persistent IDs (PIDs) enabled at the file level. This reserved word is **required for file level tools** unless you use ``{fileId}`` instead. + + ``{apiToken}`` optional The Dataverse API token of the user launching the external tool, if available. Please note that API tokens should be treated with the same care as a password. For example, ``f3465b0c-f830-4bc7-879f-06c0745a5a5c``. + + ``{datasetId}`` depends The database ID of the dataset. For example, ``42``. This reseved word is **required for dataset level tools** unless you use ``{datasetPid}`` instead. + + ``{datasetPid}`` depends The Persistent ID (DOI or Handle) of the dataset. For example, ``doi:10.7910/DVN/TJCLKP``. This reseved word is **required for dataset level tools** unless you use ``{datasetId}`` instead. + + ``{datasetVersion}`` optional The friendly version number ( or \:draft ) of the dataset version the tool is being launched from. For example, ``1.0`` or ``:draft``. + =========================== ========== =========== + +Using Example Manifests to Get Started +++++++++++++++++++++++++++++++++++++++ + +Again, you can use :download:`fabulousFileTool.json <../_static/installation/files/root/external-tools/fabulousFileTool.json>` or :download:`dynamicDatasetTool.json <../_static/installation/files/root/external-tools/dynamicDatasetTool.json>` as a starting point for your own manifest file. + +Testing Your External Tool +-------------------------- + +As the author of an external tool, you are not expected to learn how to install and operate Dataverse. There's a very good chance your tool can be added to a server Dataverse developers use for testing if you reach out on any of the channels listed under :ref:`getting-help-developers` in the Developer Guide. + +By all means, if you'd like to install Dataverse yourself, a number of developer-centric options are available. For example, there's a script to spin up Dataverse on EC2 at https://github.com/IQSS/dataverse-sample-data . The process for using curl to add your external tool to a Dataverse installation is documented under :ref:`managing-external-tools` in the Admin Guide. + +Spreading the Word About Your External Tool +------------------------------------------- + +Adding Your Tool to the Inventory of External Tools ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Once you've gotten your tool working, please make a pull request to update the list of tools above! You are also welcome to download :download:`dataverse-external-tools.tsv <../_static/admin/dataverse-external-tools.tsv>`, add your tool to the TSV file, create and issue at https://github.com/IQSS/dataverse/issues , and then upload your TSV file there. + +Unless your tool runs entirely in a browser, you may have integrated server-side software with Dataverse. If so, please double check that your software is listed in the :doc:`/admin/integrations` section of the Admin Guide and if not, please open an issue or pull request to add it. Thanks! + +If you've thought to yourself that there ought to be an app store for Dataverse external tools, you're not alone. Please see https://github.com/IQSS/dataverse/issues/5688 :) + +Demoing Your External Tool +++++++++++++++++++++++++++ + +https://demo.dataverse.org is the place to play around with Dataverse and your tool can be included. Please email support@dataverse.org to start the conversation about adding your tool. Additionally, you are welcome to open an issue at https://github.com/IQSS/dataverse-ansible which already includes a number of the tools listed above. + +Announcing Your External Tool ++++++++++++++++++++++++++++++ + +You are welcome to announce your external tool at https://groups.google.com/forum/#!forum/dataverse-community + +If you're too shy, we'll do it for you. We'll probably tweet about it too. Thank you for your contribution to Dataverse! diff --git a/doc/sphinx-guides/source/api/index.rst b/doc/sphinx-guides/source/api/index.rst index 0c0c4c186a3..45cdb1918fe 100755 --- a/doc/sphinx-guides/source/api/index.rst +++ b/doc/sphinx-guides/source/api/index.rst @@ -19,5 +19,6 @@ API Guide metrics sword client-libraries + external-tools apps faq diff --git a/doc/sphinx-guides/source/api/intro.rst b/doc/sphinx-guides/source/api/intro.rst index 5fec86fab03..b498a7c58b9 100755 --- a/doc/sphinx-guides/source/api/intro.rst +++ b/doc/sphinx-guides/source/api/intro.rst @@ -135,7 +135,7 @@ Developers of Integrations, External Tools, and Apps One of the primary purposes for Dataverse APIs in the first place is to enable integrations with third party software. Integrations are listed in the following places: - The :doc:`/admin/integrations` section of the Admin Guide. -- The :doc:`/installation/external-tools` section of the Installation Guide. +- The :doc:`/api/external-tools` section this guide. - The :doc:`apps` section of this guide. |Start| Good starting points are the three sections above to get a sense of third-party software that already integrates with Dataverse, followed by the :doc:`getting-started` section. @@ -194,6 +194,7 @@ Please note that some APIs are only documented in other guides that are more sui - Admin Guide + - :doc:`/admin/external-tools` - :doc:`/admin/metadatacustomization` - :doc:`/admin/metadataexport` - :doc:`/admin/make-data-count` @@ -203,7 +204,6 @@ Please note that some APIs are only documented in other guides that are more sui - Installation Guide - :doc:`/installation/config` - - :doc:`/installation/external-tools` Client Libraries ~~~~~~~~~~~~~~~~ diff --git a/doc/sphinx-guides/source/developers/intro.rst b/doc/sphinx-guides/source/developers/intro.rst index f5a970d772c..f95e6a97cf8 100755 --- a/doc/sphinx-guides/source/developers/intro.rst +++ b/doc/sphinx-guides/source/developers/intro.rst @@ -14,6 +14,8 @@ This guide is intended primarily for developers who want to work on the main Dat To get started, you'll want to set up your :doc:`dev-environment` and make sure you understand the branching strategy described in the :doc:`version-control` section and how to make a pull request. :doc:`testing` is expected. Opinions about :doc:`coding-style` are welcome! +.. _getting-help-developers: + Getting Help ------------ @@ -53,7 +55,7 @@ Related Projects As a developer, you also may be interested in these projects related to Dataverse: -- External Tools - add additional features to Dataverse: See the :doc:`/installation/external-tools` section of the Installation Guide. +- External Tools - add additional features to Dataverse without modifying the core: :doc:`/api/external-tools` - Dataverse API client libraries - use Dataverse APIs from various languages: :doc:`/api/client-libraries` - DVUploader - a stand-alone command-line Java application that uses the Dataverse API to support upload of files from local disk to a Dataset: https://github.com/IQSS/dataverse-uploader - dataverse-sample-data - populate your Dataverse installation with sample data: https://github.com/IQSS/dataverse-sample-data diff --git a/doc/sphinx-guides/source/installation/config.rst b/doc/sphinx-guides/source/installation/config.rst index 0e8e9e11dd0..3edb735637f 100644 --- a/doc/sphinx-guides/source/installation/config.rst +++ b/doc/sphinx-guides/source/installation/config.rst @@ -1441,12 +1441,12 @@ In the example below we reduce the timeout to 4 hours: :TwoRavensUrl +++++++++++++ -The ``:TwoRavensUrl`` option is no longer valid. See :doc:`r-rapache-tworavens` and :doc:`external-tools`. +The ``:TwoRavensUrl`` option is no longer valid. See :doc:`r-rapache-tworavens` and the :doc:`/admin/external-tools` section of the Admin Guide. :TwoRavensTabularView +++++++++++++++++++++ -The ``:TwoRavensTabularView`` option is no longer valid. See :doc:`r-rapache-tworavens` and :doc:`external-tools`. +The ``:TwoRavensTabularView`` option is no longer valid. See :doc:`r-rapache-tworavens` and the :doc:`/admin/external-tools` section of the Admin Guide. :GeoconnectCreateEditMaps +++++++++++++++++++++++++ diff --git a/doc/sphinx-guides/source/installation/external-tools.rst b/doc/sphinx-guides/source/installation/external-tools.rst index e91335f40df..028802fd736 100644 --- a/doc/sphinx-guides/source/installation/external-tools.rst +++ b/doc/sphinx-guides/source/installation/external-tools.rst @@ -1,7 +1,7 @@ External Tools ============== -External tools can provide additional features that are not part of Dataverse itself, such as data exploration. See the "Writing Your Own External Tool" section below for more information on developing your own tool for Dataverse. +External tools can provide additional features that are not part of Dataverse itself, such as data exploration. .. contents:: |toctitle| :local: @@ -9,68 +9,14 @@ External tools can provide additional features that are not part of Dataverse it Inventory of External Tools --------------------------- -Support for external tools is just getting off the ground but the following tools have been successfully integrated with Dataverse: +See :ref:`inventory-of-external-tools`. -- TwoRavens: a system of interlocking statistical tools for data exploration, analysis, and meta-analysis: http://2ra.vn. See the :doc:`/user/data-exploration/tworavens` section of the User Guide for more information on TwoRavens from the user perspective and the :doc:`r-rapache-tworavens` section of the Installation Guide. +Managing External Tools +----------------------- -- Data Explorer: a GUI which lists the variables in a tabular data file allowing searching, charting and cross tabulation analysis. See the README.md file at https://github.com/scholarsportal/Dataverse-Data-Explorer for the instructions on adding Data Explorer to your Dataverse; and the :doc:`prerequisites` section of the Installation Guide for the instructions on how to set up **basic R configuration required** (specifically, Dataverse uses R to generate .prep metadata files that are needed to run Data Explorer). +See the :doc:`/admin/external-tools` section of the Admin Guide. -- `Whole Tale `_: a platform for the creation of reproducible research packages that allows users to launch containerized interactive analysis environments based on popular tools such as Jupyter and RStudio. Using this integration, Dataverse users can launch Jupyter and RStudio environments to analyze published datasets. For more information, see the `Whole Tale User Guide `_. +Building External Tools +----------------------- -- `File Previewers `_: A set of tools that display the content of files - including audio, html, `Hypothes.is ` annotations, images, PDF, text, video - allowing them to be viewed without downloading. The previewers can be run directly from github.io, so the only required step is using the Dataverse API to register the ones you want to use. Documentation, including how to optionally brand the previewers, and an invitation to contribute through github are in the README.md file. - -- Data Curation Tool: a GUI for curating data by adding labels, groups, weights and other details to assist with informed reuse. See the README.md file at https://github.com/scholarsportal/Dataverse-Data-Curation-Tool for the installation instructions. - -- [Your tool here! Please get in touch! :) ] - - -Downloading and Adjusting an External Tool Manifest File --------------------------------------------------------- - -In order to make external tools available within Dataverse, you need to configure Dataverse to be aware of them. - -External tools must be expressed in an external tool manifest file, a specific JSON format Dataverse requires. The author of the external tool may be able to provide you with a JSON file and installation instructions. The JSON file might look like this: - -.. literalinclude:: ../_static/installation/files/root/external-tools/awesomeTool.json - -``type`` is required and must be ``explore`` or ``configure`` to make the tool appear under a button called "Explore" or "Configure", respectively. - -External tools can operate on any file, including tabular files that have been created by successful ingestion. (For more on ingest, see the :doc:`/user/tabulardataingest/ingestprocess` of the User Guide.) The optional ``contentType`` entry specifies the mimetype a tool works on. (Not providing this parameter makes the tool work on ingested tabular files and is equivalent to specifying the ``contentType`` as "text/tab-separated-values".) - -In the example above, a mix of required and optional reserved words appear that can be used to insert dynamic values into tools. The supported values are: - -- ``{fileId}`` (required) - The Dataverse database ID of a file the external tool has been launched on. -- ``{siteUrl}`` (optional) - The URL of the Dataverse installation that hosts the file with the fileId above. -- ``{apiToken}`` (optional) - The Dataverse API token of the user launching the external tool, if available. -- ``{datasetId}`` (optional) - The ID of the dataset containing the file. -- ``{datasetVersion}`` (optional) - The friendly version number ( or \:draft ) of the dataset version the tool is being launched from. - -Making an External Tool Available in Dataverse ----------------------------------------------- - -If the JSON file were called, for example, :download:`awesomeTool.json <../_static/installation/files/root/external-tools/awesomeTool.json>` you would make any necessary adjustments, as described above, and then make the tool available within Dataverse with the following curl command: - -``curl -X POST -H 'Content-type: application/json' --upload-file awesomeTool.json http://localhost:8080/api/admin/externalTools`` - -Listing all External Tools in Dataverse ---------------------------------------- - -To list all the external tools that are available in Dataverse: - -``curl http://localhost:8080/api/admin/externalTools`` - -Removing an External Tool Available in Dataverse ------------------------------------------------- - -Assuming the external tool database id is "1", remove it with the following command: - -``curl -X DELETE http://localhost:8080/api/admin/externalTools/1`` - -Writing Your Own External Tool ------------------------------- - -If you have an idea for an external tool, please let the Dataverse community know by posting about it on the dataverse-community mailing list: https://groups.google.com/forum/#!forum/dataverse-community - -If you need help with your tool, please feel free to post on the dataverse-dev mailing list: https://groups.google.com/forum/#!forum/dataverse-dev - -Once you've gotten your tool working, please make a pull request to update the list of tools above. +See the :doc:`/api/external-tools` section of the API Guide. diff --git a/doc/sphinx-guides/source/installation/prep.rst b/doc/sphinx-guides/source/installation/prep.rst index 2d8beeccdcb..711693125f0 100644 --- a/doc/sphinx-guides/source/installation/prep.rst +++ b/doc/sphinx-guides/source/installation/prep.rst @@ -70,7 +70,8 @@ Optional Components There are a number of optional components you may choose to install or configure, including: -- R, rApache, Zelig, and TwoRavens: :doc:`/user/data-exploration/tworavens` describes the feature and :doc:`r-rapache-tworavens` describes how to install these components. :doc:`external-tools` explains how third-party tools like TwoRavens can be added to Dataverse. +- External Tools: Third party tools for data exploration can be added to Dataverse by following the instructions in the :doc:`/admin/external-tools` section of the Admin Guide. +- R, rApache, Zelig, and TwoRavens: :doc:`/user/data-exploration/tworavens` describes the feature and :doc:`r-rapache-tworavens` describes how to install these components. :doc:`/admin/external-tools` explains how third-party tools like TwoRavens can be added to Dataverse. - Dropbox integration :ref:`dataverse.dropbox.key`: for uploading files from the Dropbox API. - Apache: a web server that can "reverse proxy" Glassfish applications and rewrite HTTP traffic. - Shibboleth: an authentication system described in :doc:`shibboleth`. Its use with Dataverse requires Apache. diff --git a/doc/sphinx-guides/source/installation/r-rapache-tworavens.rst b/doc/sphinx-guides/source/installation/r-rapache-tworavens.rst index 4857e48dbe0..10e901e1ce5 100644 --- a/doc/sphinx-guides/source/installation/r-rapache-tworavens.rst +++ b/doc/sphinx-guides/source/installation/r-rapache-tworavens.rst @@ -343,7 +343,7 @@ TwoRavens and Dataverse!)* e. Enable TwoRavens Button in Dataverse --------------------------------------- -Now that you have installed TwoRavens, you can make it available to your users by adding it an "external tool" for your Dataverse installation. (For more on external tools in general, see the :doc:`external-tools` section.) +Now that you have installed TwoRavens, you can make it available to your users by adding it an "external tool" for your Dataverse installation. (For more on external tools in general, see the :doc:`/admin/external-tools` section of the Admin Guide.) First, download :download:`twoRavens.json <../_static/installation/files/root/external-tools/twoRavens.json>` as a starting point and edit ``toolUrl`` in that external tool manifest file to be the URL where you want TwoRavens to run. This is the URL reported by the installer script (as in the example at the end of step ``c.``, above). diff --git a/doc/sphinx-guides/source/user/account.rst b/doc/sphinx-guides/source/user/account.rst index dfb357697b6..eaa86567265 100755 --- a/doc/sphinx-guides/source/user/account.rst +++ b/doc/sphinx-guides/source/user/account.rst @@ -167,7 +167,7 @@ API Token What APIs Are and Why They Are Useful ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -API stands for "Application Programming Interface" and Dataverse APIs allow you to take advantage of integrations with other software that may have been set up by admins of your installation of Dataverse. See the :doc:`/admin/integrations` section of the Admin Guide and the :doc:`/installation/external-tools` section of the Installation Guide for examples of software that is commonly integrated with Dataverse. +API stands for "Application Programming Interface" and Dataverse APIs allow you to take advantage of integrations with other software that may have been set up by admins of your installation of Dataverse. See the :doc:`/admin/external-tools` and :doc:`/admin/integrations` sections of the Admin Guide for examples of software that is commonly integrated with Dataverse. Additionally, if you are willing to write a little code (or find someone to write it for you), APIs provide a way to automate parts of your workflow. See the :doc:`/api/getting-started` section of the API Guide for details. diff --git a/doc/sphinx-guides/source/user/data-exploration/index.rst b/doc/sphinx-guides/source/user/data-exploration/index.rst index e6af35387b9..52833e3ae50 100755 --- a/doc/sphinx-guides/source/user/data-exploration/index.rst +++ b/doc/sphinx-guides/source/user/data-exploration/index.rst @@ -6,7 +6,7 @@ Data Exploration Guide ======================================================= -Note that the installation of Dataverse you are using may have additional or different tools configured. Developers interested in creating tools should refer to the :doc:`/installation/external-tools` section of the Installation Guide. +The installation of Dataverse you are using may have additional or different tools configured than what appears below. For the complete list of available tools, see :ref:`inventory-of-external-tools`. Contents: diff --git a/doc/sphinx-guides/source/user/dataset-management.rst b/doc/sphinx-guides/source/user/dataset-management.rst index 6df585f5821..3cdc9aec954 100755 --- a/doc/sphinx-guides/source/user/dataset-management.rst +++ b/doc/sphinx-guides/source/user/dataset-management.rst @@ -149,7 +149,7 @@ Certain file types in Dataverse are supported by additional functionality, which Tabular Data Files ------------------ -Files in certain formats - Stata, SPSS, R, Excel(xlsx) and CSV - may be ingested as tabular data (see :doc:`/user/tabulardataingest/index` section of the User Guide for details). Tabular data files can be further explored and manipulated with `TwoRavens <../user/data-exploration/tworavens.html>`_ - a statistical data exploration application integrated with Dataverse, as well as other :doc:`/installation/external-tools` if they have been enabled in the installation of Dataverse you are using. TwoRavens allows the user to run statistical models, view summary statistics, download subsets of variable vectors and more. To start, click on the "Explore" button, found next to each relevant tabular file (the application will be opened in a new window). Create and download your subset using `TwoRavens <../user/data-exploration/tworavens.html>`_. See the `TwoRavens documentation section <../user/data-exploration/tworavens.html>`_ for more information. +Files in certain formats - Stata, SPSS, R, Excel(xlsx) and CSV - may be ingested as tabular data (see :doc:`/user/tabulardataingest/index` section of the User Guide for details). Tabular data files can be further explored and manipulated with `TwoRavens <../user/data-exploration/tworavens.html>`_ - a statistical data exploration application integrated with Dataverse, as well as other :doc:`/admin/external-tools` if they have been enabled in the installation of Dataverse you are using. TwoRavens allows the user to run statistical models, view summary statistics, download subsets of variable vectors and more. To start, click on the "Explore" button, found next to each relevant tabular file (the application will be opened in a new window). Create and download your subset using `TwoRavens <../user/data-exploration/tworavens.html>`_. See the `TwoRavens documentation section <../user/data-exploration/tworavens.html>`_ for more information. Additional download options available for tabular data (found in the same drop-down menu under the "Download" button): diff --git a/doc/sphinx-guides/source/user/find-use-data.rst b/doc/sphinx-guides/source/user/find-use-data.rst index d54b7764775..2b513d7bb1c 100755 --- a/doc/sphinx-guides/source/user/find-use-data.rst +++ b/doc/sphinx-guides/source/user/find-use-data.rst @@ -96,7 +96,7 @@ Within the Files tab on a dataset page, you can download the files in that datas You may also download a file from its file page by clicking the Download button in the upper right corner of the page, or by :ref:`url_download` under the Metadata tab on the lower half of the page. -Tabular data files offer additional options: You can explore using any data exploration or visualization :doc:`/installation/external-tools` (if they have been enabled) by clicking the Explore button, or choose from a number of tabular-data-specific download options available as a dropdown under the Download button. +Tabular data files offer additional options: You can explore using any data exploration or visualization :doc:`/admin/external-tools` (if they have been enabled) by clicking the Explore button, or choose from a number of tabular-data-specific download options available as a dropdown under the Download button. Tabular Data ^^^^^^^^^^^^ @@ -146,7 +146,7 @@ After you've downloaded the Dataverse Package, you may want to double-check that Explore Data ------------ -Please see the :doc:`/user/data-exploration/index`. +Please see the :doc:`/user/data-exploration/index` to get started. .. |image-file-tree-view| image:: ./img/file-tree-view.png :class: img-responsive diff --git a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 41f957f063f..2dbe882e374 100644 --- a/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -6,8 +6,10 @@ import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean; import edu.harvard.iq.dataverse.authorization.Permission; import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean; +import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser; +import edu.harvard.iq.dataverse.authorization.users.User; import edu.harvard.iq.dataverse.branding.BrandingUtil; import edu.harvard.iq.dataverse.dataaccess.StorageIO; import edu.harvard.iq.dataverse.dataaccess.ImageThumbConverter; @@ -99,6 +101,7 @@ import edu.harvard.iq.dataverse.externaltools.ExternalTool; import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean; import edu.harvard.iq.dataverse.export.SchemaDotOrgExporter; +import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean; import edu.harvard.iq.dataverse.makedatacount.MakeDataCountLoggingServiceBean.MakeDataCountEntry; import java.util.Collections; @@ -135,6 +138,7 @@ import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; +import org.primefaces.PrimeFaces; import org.primefaces.model.DefaultTreeNode; import org.primefaces.model.TreeNode; @@ -312,10 +316,15 @@ public void setShowIngestSuccess(boolean showIngestSuccess) { this.showIngestSuccess = showIngestSuccess; } + // TODO: Consider renaming "configureTools" to "fileConfigureTools". List configureTools = new ArrayList<>(); + // TODO: Consider renaming "exploreTools" to "fileExploreTools". List exploreTools = new ArrayList<>(); + // TODO: Consider renaming "configureToolsByFileId" to "fileConfigureToolsByFileId". Map> configureToolsByFileId = new HashMap<>(); + // TODO: Consider renaming "exploreToolsByFileId" to "fileExploreToolsByFileId". Map> exploreToolsByFileId = new HashMap<>(); + private List datasetExploreTools; public Boolean isHasRsyncScript() { return hasRsyncScript; @@ -2014,8 +2023,9 @@ private String init(boolean initFull) { JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("dataset.unlocked.ingest.message")); } - configureTools = externalToolService.findByType(ExternalTool.Type.CONFIGURE); - exploreTools = externalToolService.findByType(ExternalTool.Type.EXPLORE); + configureTools = externalToolService.findFileToolsByType(ExternalTool.Type.CONFIGURE); + exploreTools = externalToolService.findFileToolsByType(ExternalTool.Type.EXPLORE); + datasetExploreTools = externalToolService.findDatasetToolsByType(ExternalTool.Type.EXPLORE); rowsPerPage = 10; @@ -5052,6 +5062,10 @@ public List getCachedToolsForDataFile(Long fileId, ExternalTool.Ty return cachedTools; } + public List getDatasetExploreTools() { + return datasetExploreTools; + } + Boolean thisLatestReleasedVersion = null; public boolean isThisLatestReleasedVersion() { @@ -5200,4 +5214,17 @@ public int compare(FileMetadata o1, FileMetadata o2) { return type1.compareTo(type2); } }; + + public void explore(ExternalTool externalTool) { + ApiToken apiToken = null; + User user = session.getUser(); + if (user instanceof AuthenticatedUser) { + apiToken = authService.findApiTokenByUser((AuthenticatedUser) user); + } + ExternalToolHandler externalToolHandler = new ExternalToolHandler(externalTool, dataset, apiToken); + String toolUrl = externalToolHandler.getToolUrlWithQueryParams(); + logger.fine("Exploring with " + toolUrl); + PrimeFaces.current().executeScript("window.open('"+toolUrl + "', target='_blank');"); + } + } diff --git a/src/main/java/edu/harvard/iq/dataverse/FilePage.java b/src/main/java/edu/harvard/iq/dataverse/FilePage.java index 270af97e163..15383eb3d57 100644 --- a/src/main/java/edu/harvard/iq/dataverse/FilePage.java +++ b/src/main/java/edu/harvard/iq/dataverse/FilePage.java @@ -206,8 +206,8 @@ public String init() { if (file.isTabularData()) { contentType=DataFileServiceBean.MIME_TYPE_TSV_ALT; } - configureTools = externalToolService.findByType(ExternalTool.Type.CONFIGURE, contentType); - exploreTools = externalToolService.findByType(ExternalTool.Type.EXPLORE, contentType); + configureTools = externalToolService.findFileToolsByTypeAndContentType(ExternalTool.Type.CONFIGURE, contentType); + exploreTools = externalToolService.findFileToolsByTypeAndContentType(ExternalTool.Type.EXPLORE, contentType); } else { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java index bdca1c6b38b..311e746a444 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Datasets.java @@ -78,11 +78,8 @@ import edu.harvard.iq.dataverse.S3PackageImporter; import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; import edu.harvard.iq.dataverse.batch.util.LoggingUtil; -import edu.harvard.iq.dataverse.dataaccess.StorageIO; import edu.harvard.iq.dataverse.engine.command.exception.CommandException; -import edu.harvard.iq.dataverse.engine.command.exception.PermissionException; import edu.harvard.iq.dataverse.engine.command.exception.UnforcedCommandException; -import edu.harvard.iq.dataverse.engine.command.impl.DeleteDataFileCommand; import edu.harvard.iq.dataverse.engine.command.impl.UpdateDvObjectPIDMetadataCommand; import edu.harvard.iq.dataverse.makedatacount.DatasetExternalCitations; import edu.harvard.iq.dataverse.makedatacount.DatasetExternalCitationsServiceBean; @@ -98,26 +95,20 @@ import edu.harvard.iq.dataverse.util.SystemConfig; import edu.harvard.iq.dataverse.util.json.JsonParseException; import edu.harvard.iq.dataverse.search.IndexServiceBean; -import edu.harvard.iq.dataverse.util.DateUtil; import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*; import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder; import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; -import java.math.BigDecimal; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.ResourceBundle; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/src/main/java/edu/harvard/iq/dataverse/api/ExternalTools.java b/src/main/java/edu/harvard/iq/dataverse/api/ExternalTools.java index 79f66070e79..aef30bfb0c2 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/ExternalTools.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/ExternalTools.java @@ -1,11 +1,10 @@ package edu.harvard.iq.dataverse.api; -import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord; import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; import edu.harvard.iq.dataverse.externaltools.ExternalTool; import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean; -import java.util.List; +import java.util.logging.Logger; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.ws.rs.DELETE; @@ -19,6 +18,8 @@ @Path("admin/externalTools") public class ExternalTools extends AbstractApiBean { + private static final Logger logger = Logger.getLogger(ExternalTools.class.getCanonicalName()); + @GET public Response getExternalTools() { JsonArrayBuilder jab = Json.createArrayBuilder(); @@ -28,6 +29,17 @@ public Response getExternalTools() { return ok(jab); } + @GET + @Path("{id}") + public Response getExternalTool(@PathParam("id") long externalToolIdFromUser) { + ExternalTool externalTool = externalToolService.findById(externalToolIdFromUser); + if (externalTool != null) { + return ok(externalTool.toJson()); + } else { + return error(BAD_REQUEST, "Could not find external tool with id of " + externalToolIdFromUser); + } + } + @POST public Response addExternalTool(String manifest) { try { @@ -53,23 +65,4 @@ public Response deleteExternalTool(@PathParam("id") long externalToolIdFromUser) } } - // TODO: Rather than only supporting looking up files by their database IDs, consider supporting persistent identifiers. - @GET - @Path("file/{id}") - public Response getExternalToolsByFile(@PathParam("id") Long fileIdFromUser) { - DataFile dataFile = fileSvc.find(fileIdFromUser); - if (dataFile == null) { - return error(BAD_REQUEST, "Could not find datafile with id " + fileIdFromUser); - } - JsonArrayBuilder tools = Json.createArrayBuilder(); - - List allExternalTools = externalToolService.findAll(); - List toolsByFile = ExternalToolServiceBean.findExternalToolsByFile(allExternalTools, dataFile); - for (ExternalTool tool : toolsByFile) { - tools.add(tool.toJson()); - } - - return ok(tools); - } - } diff --git a/src/main/java/edu/harvard/iq/dataverse/api/Files.java b/src/main/java/edu/harvard/iq/dataverse/api/Files.java index f304444a7f3..8948baec791 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/Files.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/Files.java @@ -27,8 +27,8 @@ import edu.harvard.iq.dataverse.engine.command.impl.GetDraftFileMetadataIfAvailableCommand; import edu.harvard.iq.dataverse.engine.command.impl.RedetectFileTypeCommand; import edu.harvard.iq.dataverse.engine.command.impl.RestrictFileCommand; -import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand; import edu.harvard.iq.dataverse.engine.command.impl.UningestFileCommand; +import edu.harvard.iq.dataverse.engine.command.impl.UpdateDatasetVersionCommand; import edu.harvard.iq.dataverse.export.ExportException; import edu.harvard.iq.dataverse.export.ExportService; import edu.harvard.iq.dataverse.ingest.IngestRequest; @@ -43,6 +43,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.ejb.EJB; @@ -55,6 +56,7 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -64,8 +66,6 @@ import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; -import java.util.List; -import javax.ws.rs.QueryParam; @Path("files") public class Files extends AbstractApiBean { diff --git a/src/main/java/edu/harvard/iq/dataverse/api/TestApi.java b/src/main/java/edu/harvard/iq/dataverse/api/TestApi.java new file mode 100644 index 00000000000..f2379276699 --- /dev/null +++ b/src/main/java/edu/harvard/iq/dataverse/api/TestApi.java @@ -0,0 +1,74 @@ +package edu.harvard.iq.dataverse.api; + +import edu.harvard.iq.dataverse.DataFile; +import edu.harvard.iq.dataverse.Dataset; +import static edu.harvard.iq.dataverse.api.AbstractApiBean.error; +import edu.harvard.iq.dataverse.authorization.users.ApiToken; +import edu.harvard.iq.dataverse.externaltools.ExternalTool; +import edu.harvard.iq.dataverse.externaltools.ExternalToolHandler; +import java.util.List; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import static javax.ws.rs.core.Response.Status.BAD_REQUEST; + +@Path("admin/test") +public class TestApi extends AbstractApiBean { + + @GET + @Path("datasets/{id}/externalTools") + public Response getExternalToolsforFile(@PathParam("id") String idSupplied, @QueryParam("type") String typeSupplied) { + ExternalTool.Type type; + try { + type = ExternalTool.Type.fromString(typeSupplied); + } catch (IllegalArgumentException ex) { + return error(BAD_REQUEST, ex.getLocalizedMessage()); + } + Dataset dataset; + try { + dataset = findDatasetOrDie(idSupplied); + JsonArrayBuilder tools = Json.createArrayBuilder(); + List datasetTools = externalToolService.findDatasetToolsByType(type); + for (ExternalTool tool : datasetTools) { + ApiToken apiToken = externalToolService.getApiToken(getRequestApiKey()); + ExternalToolHandler externalToolHandler = new ExternalToolHandler(tool, dataset, apiToken); + JsonObjectBuilder toolToJson = externalToolService.getToolAsJsonWithQueryParameters(externalToolHandler); + tools.add(toolToJson); + } + return ok(tools); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + } + + @Path("files/{id}/externalTools") + @GET + public Response getExternalToolsForFile(@PathParam("id") String idSupplied, @QueryParam("type") String typeSupplied) { + ExternalTool.Type type; + try { + type = ExternalTool.Type.fromString(typeSupplied); + } catch (IllegalArgumentException ex) { + return error(BAD_REQUEST, ex.getLocalizedMessage()); + } + try { + DataFile dataFile = findDataFileOrDie(idSupplied); + JsonArrayBuilder tools = Json.createArrayBuilder(); + List datasetTools = externalToolService.findFileToolsByTypeAndContentType(type, dataFile.getContentType()); + for (ExternalTool tool : datasetTools) { + ApiToken apiToken = externalToolService.getApiToken(getRequestApiKey()); + ExternalToolHandler externalToolHandler = new ExternalToolHandler(tool, dataFile, apiToken, dataFile.getFileMetadata()); + JsonObjectBuilder toolToJson = externalToolService.getToolAsJsonWithQueryParameters(externalToolHandler); + tools.add(toolToJson); + } + return ok(tools); + } catch (WrappedResponse wr) { + return wr.getResponse(); + } + } + +} diff --git a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalTool.java b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalTool.java index 005f61d6d38..162b6e04040 100644 --- a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalTool.java +++ b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalTool.java @@ -23,6 +23,7 @@ public class ExternalTool implements Serializable { public static final String DISPLAY_NAME = "displayName"; public static final String DESCRIPTION = "description"; public static final String TYPE = "type"; + public static final String SCOPE = "scope"; public static final String TOOL_URL = "toolUrl"; public static final String TOOL_PARAMETERS = "toolParameters"; public static final String CONTENT_TYPE = "contentType"; @@ -52,6 +53,13 @@ public class ExternalTool implements Serializable { @Enumerated(EnumType.STRING) private Type type; + /** + * Whether the tool operates at the dataset or file level. + */ + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private Scope scope; + @Column(nullable = false) private String toolUrl; @@ -63,12 +71,12 @@ public class ExternalTool implements Serializable { private String toolParameters; /** - * The file content type the tool works on. For tabular files, the type text/tab-separated-values should be sent + * The file content type the tool works on. For tabular files, the type + * text/tab-separated-values should be sent */ - @Column(nullable = false, columnDefinition = "TEXT") + @Column(nullable = true, columnDefinition = "TEXT") private String contentType; - /** * This default constructor is only here to prevent this error at * deployment: @@ -83,10 +91,11 @@ public class ExternalTool implements Serializable { public ExternalTool() { } - public ExternalTool(String displayName, String description, Type type, String toolUrl, String toolParameters, String contentType) { + public ExternalTool(String displayName, String description, Type type, Scope scope, String toolUrl, String toolParameters, String contentType) { this.displayName = displayName; this.description = description; this.type = type; + this.scope = scope; this.toolUrl = toolUrl; this.toolParameters = toolParameters; this.contentType = contentType; @@ -120,6 +129,34 @@ public String toString() { } } + public enum Scope { + + DATASET("dataset"), + FILE("file"); + + private final String text; + + private Scope(final String text) { + this.text = text; + } + + public static Scope fromString(String text) { + if (text != null) { + for (Scope scope : Scope.values()) { + if (text.equals(scope.text)) { + return scope; + } + } + } + throw new IllegalArgumentException("Scope must be one of these values: " + Arrays.asList(Scope.values()) + "."); + } + + @Override + public String toString() { + return text; + } + } + public Long getId() { return id; } @@ -148,6 +185,10 @@ public Type getType() { return type; } + public Scope getScope() { + return scope; + } + public String getToolUrl() { return toolUrl; } @@ -167,7 +208,7 @@ public void setToolParameters(String toolParameters) { public String getContentType() { return this.contentType; } - + public void setContentType(String contentType) { this.contentType = contentType; } @@ -178,9 +219,12 @@ public JsonObjectBuilder toJson() { jab.add(DISPLAY_NAME, getDisplayName()); jab.add(DESCRIPTION, getDescription()); jab.add(TYPE, getType().text); + jab.add(SCOPE, getScope().text); jab.add(TOOL_URL, getToolUrl()); jab.add(TOOL_PARAMETERS, getToolParameters()); - jab.add(CONTENT_TYPE, getContentType()); + if (getContentType() != null) { + jab.add(CONTENT_TYPE, getContentType()); + } return jab; } @@ -191,9 +235,13 @@ public enum ReservedWord { // various REST APIs. For example, "Variable substitutions will be made when a variable is named in {brackets}." // from https://swagger.io/specification/#fixed-fields-29 but that's for URLs. FILE_ID("fileId"), + FILE_PID("filePid"), SITE_URL("siteUrl"), API_TOKEN("apiToken"), + // datasetId is the database id DATASET_ID("datasetId"), + // datasetPid is the DOI or Handle + DATASET_PID("datasetPid"), DATASET_VERSION("datasetVersion"), FILE_METADATA_ID("fileMetadataId"); diff --git a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandler.java b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandler.java index c177442ee81..7eb65807dff 100644 --- a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandler.java +++ b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandler.java @@ -4,6 +4,7 @@ import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetVersion; import edu.harvard.iq.dataverse.FileMetadata; +import edu.harvard.iq.dataverse.GlobalId; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.externaltools.ExternalTool.ReservedWord; import edu.harvard.iq.dataverse.util.SystemConfig; @@ -33,6 +34,8 @@ public class ExternalToolHandler { private ApiToken apiToken; /** + * File level tool + * * @param externalTool The database entity. * @param dataFile Required. * @param apiToken The apiToken can be null because "explore" tools can be @@ -51,6 +54,27 @@ public ExternalToolHandler(ExternalTool externalTool, DataFile dataFile, ApiToke this.fileMetadata = fileMetadata; } + /** + * Dataset level tool + * + * @param externalTool The database entity. + * @param dataset Required. + * @param apiToken The apiToken can be null because "explore" tools can be + * used anonymously. + */ + public ExternalToolHandler(ExternalTool externalTool, Dataset dataset, ApiToken apiToken) { + this.externalTool = externalTool; + if (dataset == null) { + String error = "A Dataset is required."; + logger.warning("Error in ExternalToolHandler constructor: " + error); + throw new IllegalArgumentException(error); + } + this.dataset = dataset; + this.apiToken = apiToken; + this.dataFile = null; + this.fileMetadata = null; + } + public DataFile getDataFile() { return dataFile; } @@ -89,8 +113,14 @@ private String getQueryParam(String key, String value) { ReservedWord reservedWord = ReservedWord.fromString(value); switch (reservedWord) { case FILE_ID: - // getDataFile is never null because of the constructor + // getDataFile is never null for file tools because of the constructor return key + "=" + getDataFile().getId(); + case FILE_PID: + GlobalId filePid = getDataFile().getGlobalId(); + if (filePid != null) { + return key + "=" + getDataFile().getGlobalId(); + } + break; case SITE_URL: return key + "=" + SystemConfig.getDataverseSiteUrlStatic(); case API_TOKEN: @@ -103,6 +133,8 @@ private String getQueryParam(String key, String value) { break; case DATASET_ID: return key + "=" + dataset.getId(); + case DATASET_PID: + return key + "=" + dataset.getGlobalId().asString(); case DATASET_VERSION: String version = null; if (getApiToken() != null) { @@ -111,8 +143,7 @@ private String getQueryParam(String key, String value) { version = dataset.getLatestVersionForCopy().getFriendlyVersionNumber(); } if (("DRAFT").equals(version)) { - version = ":draft"; // send the token needed in api calls that can be substituted for a numeric - // version. + version = ":draft"; // send the token needed in api calls that can be substituted for a numeric version. } return key + "=" + version; case FILE_METADATA_ID: diff --git a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java index 05c79731d40..92adfe6d42b 100644 --- a/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBean.java @@ -2,14 +2,17 @@ import edu.harvard.iq.dataverse.DataFile; import edu.harvard.iq.dataverse.DataFileServiceBean; +import edu.harvard.iq.dataverse.authorization.users.ApiToken; import edu.harvard.iq.dataverse.externaltools.ExternalTool.ReservedWord; import edu.harvard.iq.dataverse.externaltools.ExternalTool.Type; +import edu.harvard.iq.dataverse.externaltools.ExternalTool.Scope; import static edu.harvard.iq.dataverse.externaltools.ExternalTool.DESCRIPTION; import static edu.harvard.iq.dataverse.externaltools.ExternalTool.DISPLAY_NAME; import static edu.harvard.iq.dataverse.externaltools.ExternalTool.TOOL_PARAMETERS; import static edu.harvard.iq.dataverse.externaltools.ExternalTool.TOOL_URL; import static edu.harvard.iq.dataverse.externaltools.ExternalTool.TYPE; +import static edu.harvard.iq.dataverse.externaltools.ExternalTool.SCOPE; import static edu.harvard.iq.dataverse.externaltools.ExternalTool.CONTENT_TYPE; import java.io.StringReader; import java.util.ArrayList; @@ -21,6 +24,7 @@ import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; import javax.json.JsonReader; import javax.persistence.EntityManager; import javax.persistence.NoResultException; @@ -42,29 +46,49 @@ public List findAll() { return typedQuery.getResultList(); } + /** + * @param type explore or configure + * @return A list of tools or an empty list. + */ + public List findDatasetToolsByType(Type type) { + String nullContentType = null; + return findByScopeTypeAndContentType(ExternalTool.Scope.DATASET, type, nullContentType); + } /** - * @param type + * @param type explore or configure * @return A list of tools or an empty list. */ - public List findByType(Type type) { - return findByType(type, null); + public List findFileToolsByType(Type type) { + String nullContentType = null; + return findByScopeTypeAndContentType(ExternalTool.Scope.FILE, type, nullContentType); } - + /** - * @param type - * @param contentType - mimetype + * @param type explore or configure + * @param contentType file content type (MIME type) * @return A list of tools or an empty list. */ - public List findByType(Type type, String contentType) { + public List findFileToolsByTypeAndContentType(Type type, String contentType) { + return findByScopeTypeAndContentType(ExternalTool.Scope.FILE, type, contentType); + } + /** + * @param scope dataset or file + * @param type explore or configure + * @param contentType file content type (MIME type) + * @return A list of tools or an empty list. + */ + private List findByScopeTypeAndContentType(Scope scope, Type type, String contentType) { List externalTools = new ArrayList<>(); - - //If contentType==null, get all tools of the given ExternalTool.Type - TypedQuery typedQuery = contentType != null ? em.createQuery("SELECT OBJECT(o) FROM ExternalTool AS o WHERE o.type = :type AND o.contentType = :contentType", ExternalTool.class): - em.createQuery("SELECT OBJECT(o) FROM ExternalTool AS o WHERE o.type = :type", ExternalTool.class); + String contentTypeClause = ""; + if (contentType != null) { + contentTypeClause = "AND o.contentType = :contentType"; + } + TypedQuery typedQuery = em.createQuery("SELECT OBJECT(o) FROM ExternalTool AS o WHERE o.scope = :scope AND o.type = :type " + contentTypeClause, ExternalTool.class); + typedQuery.setParameter("scope", scope); typedQuery.setParameter("type", type); - if(contentType!=null) { + if (contentType != null) { typedQuery.setParameter("contentType", contentType); } List toolsFromQuery = typedQuery.getResultList(); @@ -74,8 +98,6 @@ public List findByType(Type type, String contentType) { return externalTools; } - - public ExternalTool findById(long id) { TypedQuery typedQuery = em.createQuery("SELECT OBJECT(o) FROM ExternalTool AS o WHERE o.id = :id", ExternalTool.class); typedQuery.setParameter("id", id); @@ -104,8 +126,9 @@ public ExternalTool save(ExternalTool externalTool) { } /** - * This method takes a list of tools and a file and returns which tools that file supports - * The list of tools is passed in so it doesn't hit the database each time + * This method takes a list of tools and a file and returns which tools that + * file supports The list of tools is passed in so it doesn't hit the + * database each time */ public static List findExternalToolsByFile(List allExternalTools, DataFile file) { List externalTools = new ArrayList<>(); @@ -117,7 +140,7 @@ public static List findExternalToolsByFile(List allE externalTools.add(externalTool); } }); - + return externalTools; } @@ -131,34 +154,68 @@ public static ExternalTool parseAddExternalToolManifest(String manifest) { String displayName = getRequiredTopLevelField(jsonObject, DISPLAY_NAME); String description = getRequiredTopLevelField(jsonObject, DESCRIPTION); String typeUserInput = getRequiredTopLevelField(jsonObject, TYPE); + String scopeUserInput = getRequiredTopLevelField(jsonObject, SCOPE); String contentType = getOptionalTopLevelField(jsonObject, CONTENT_TYPE); - //Legacy support - assume tool manifests without any mimetype are for tabular data - if(contentType==null) { - contentType=DataFileServiceBean.MIME_TYPE_TSV_ALT; - } - + // Allow IllegalArgumentException to bubble up from ExternalTool.Type.fromString ExternalTool.Type type = ExternalTool.Type.fromString(typeUserInput); + ExternalTool.Scope scope = ExternalTool.Scope.fromString(scopeUserInput); String toolUrl = getRequiredTopLevelField(jsonObject, TOOL_URL); JsonObject toolParametersObj = jsonObject.getJsonObject(TOOL_PARAMETERS); JsonArray queryParams = toolParametersObj.getJsonArray("queryParameters"); boolean allRequiredReservedWordsFound = false; - for (JsonObject queryParam : queryParams.getValuesAs(JsonObject.class)) { - Set keyValuePair = queryParam.keySet(); - for (String key : keyValuePair) { - String value = queryParam.getString(key); - ReservedWord reservedWord = ReservedWord.fromString(value); - if (reservedWord.equals(ReservedWord.FILE_ID)) { - allRequiredReservedWordsFound = true; + if (scope.equals(Scope.FILE)) { + List requiredReservedWordCandidates = new ArrayList<>(); + requiredReservedWordCandidates.add(ReservedWord.FILE_ID); + requiredReservedWordCandidates.add(ReservedWord.FILE_PID); + for (JsonObject queryParam : queryParams.getValuesAs(JsonObject.class)) { + Set keyValuePair = queryParam.keySet(); + for (String key : keyValuePair) { + String value = queryParam.getString(key); + ReservedWord reservedWord = ReservedWord.fromString(value); + for (ReservedWord requiredReservedWordCandidate : requiredReservedWordCandidates) { + if (reservedWord.equals(requiredReservedWordCandidate)) { + allRequiredReservedWordsFound = true; + } + } } } - } - if (!allRequiredReservedWordsFound) { - // Some day there might be more reserved words than just {fileId}. - throw new IllegalArgumentException("Required reserved word not found: " + ReservedWord.FILE_ID.toString()); + if (!allRequiredReservedWordsFound) { + List requiredReservedWordCandidatesString = new ArrayList<>(); + for (ReservedWord requiredReservedWordCandidate : requiredReservedWordCandidates) { + requiredReservedWordCandidatesString.add(requiredReservedWordCandidate.toString()); + } + String friendly = String.join(", ", requiredReservedWordCandidatesString); + throw new IllegalArgumentException("One of the following reserved words is required: " + friendly + "."); + } + } else if (scope.equals(Scope.DATASET)) { + List requiredReservedWordCandidates = new ArrayList<>(); + requiredReservedWordCandidates.add(ReservedWord.DATASET_ID); + requiredReservedWordCandidates.add(ReservedWord.DATASET_PID); + for (JsonObject queryParam : queryParams.getValuesAs(JsonObject.class)) { + Set keyValuePair = queryParam.keySet(); + for (String key : keyValuePair) { + String value = queryParam.getString(key); + ReservedWord reservedWord = ReservedWord.fromString(value); + for (ReservedWord requiredReservedWordCandidate : requiredReservedWordCandidates) { + if (reservedWord.equals(requiredReservedWordCandidate)) { + allRequiredReservedWordsFound = true; + } + } + } + } + if (!allRequiredReservedWordsFound) { + List requiredReservedWordCandidatesString = new ArrayList<>(); + for (ReservedWord requiredReservedWordCandidate : requiredReservedWordCandidates) { + requiredReservedWordCandidatesString.add(requiredReservedWordCandidate.toString()); + } + String friendly = String.join(", ", requiredReservedWordCandidatesString); + throw new IllegalArgumentException("One of the following reserved words is required: " + friendly + "."); + } + } String toolParameters = toolParametersObj.toString(); - return new ExternalTool(displayName, description, type, toolUrl, toolParameters, contentType); + return new ExternalTool(displayName, description, type, scope, toolUrl, toolParameters, contentType); } private static String getRequiredTopLevelField(JsonObject jsonObject, String key) { @@ -168,7 +225,7 @@ private static String getRequiredTopLevelField(JsonObject jsonObject, String key throw new IllegalArgumentException(key + " is required."); } } - + private static String getOptionalTopLevelField(JsonObject jsonObject, String key) { try { return jsonObject.getString(key); @@ -177,7 +234,20 @@ private static String getOptionalTopLevelField(JsonObject jsonObject, String key } } + public ApiToken getApiToken(String apiTokenString) { + ApiToken apiToken = null; + if (apiTokenString != null) { + apiToken = new ApiToken(); + apiToken.setTokenString(apiTokenString); + } + return apiToken; + } - + public JsonObjectBuilder getToolAsJsonWithQueryParameters(ExternalToolHandler externalToolHandler) { + JsonObjectBuilder toolToJson = externalToolHandler.getExternalTool().toJson(); + String toolUrlWithQueryParams = externalToolHandler.getToolUrlWithQueryParams(); + toolToJson.add("toolUrlWithQueryParams", toolUrlWithQueryParams); + return toolToJson; + } } diff --git a/src/main/resources/db/migration/V4.16.0.1__5303-addColumn-to-settingTable.sql b/src/main/resources/db/migration/V4.16.0.1__5303-addColumn-to-settingTable.sql index d294afe8b56..8309dacf486 100644 --- a/src/main/resources/db/migration/V4.16.0.1__5303-addColumn-to-settingTable.sql +++ b/src/main/resources/db/migration/V4.16.0.1__5303-addColumn-to-settingTable.sql @@ -1,8 +1,8 @@ ALTER TABLE ONLY setting DROP CONSTRAINT setting_pkey ; -ALTER TABLE setting ADD COLUMN ID SERIAL PRIMARY KEY; +ALTER TABLE setting ADD COLUMN IF NOT EXISTS ID SERIAL PRIMARY KEY; -ALTER TABLE setting ADD COLUMN lang text; +ALTER TABLE setting ADD COLUMN IF NOT EXISTS lang text; ALTER TABLE setting ADD CONSTRAINT non_empty_lang @@ -10,4 +10,4 @@ ALTER TABLE setting CREATE UNIQUE INDEX unique_settings ON setting - (name, coalesce(lang, '')); \ No newline at end of file + (name, coalesce(lang, '')); diff --git a/src/main/resources/db/migration/V4.16.0.2__5028-dataset-explore.sql b/src/main/resources/db/migration/V4.16.0.2__5028-dataset-explore.sql new file mode 100644 index 00000000000..d880b1bddb4 --- /dev/null +++ b/src/main/resources/db/migration/V4.16.0.2__5028-dataset-explore.sql @@ -0,0 +1,3 @@ +ALTER TABLE externaltool ADD COLUMN IF NOT EXISTS scope VARCHAR(255); +UPDATE externaltool SET scope = 'FILE'; +ALTER TABLE externaltool ALTER COLUMN scope SET NOT NULL; diff --git a/src/main/webapp/dataset.xhtml b/src/main/webapp/dataset.xhtml index ded3dafb0ef..8dff1e37dd0 100644 --- a/src/main/webapp/dataset.xhtml +++ b/src/main/webapp/dataset.xhtml @@ -134,6 +134,36 @@ + + + +
+ + + + + + + + + +
+ + +
diff --git a/src/test/java/edu/harvard/iq/dataverse/api/ExternalToolsIT.java b/src/test/java/edu/harvard/iq/dataverse/api/ExternalToolsIT.java index 22a07387c14..21ae03531bd 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/ExternalToolsIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/ExternalToolsIT.java @@ -1,13 +1,20 @@ package edu.harvard.iq.dataverse.api; import com.jayway.restassured.RestAssured; +import com.jayway.restassured.path.json.JsonPath; import com.jayway.restassured.response.Response; import java.io.IOException; +import java.io.StringReader; import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; +import static javax.ws.rs.core.Response.Status.CREATED; import static javax.ws.rs.core.Response.Status.OK; import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; import org.junit.BeforeClass; import org.junit.Test; @@ -25,19 +32,66 @@ public void testGetExternalTools() { } @Test - public void getExternalToolsByFileId() { - // FIXME: Don't hard code the file id. Make a dataset. - long fileId = 12; - Response getExternalTools = UtilIT.getExternalToolsByFileId(fileId); - getExternalTools.prettyPrint(); - } + public void testFileLevelTool1() { + + // Delete all external tools before testing. + Response getTools = UtilIT.getExternalTools(); + getTools.prettyPrint(); + getTools.then().assertThat() + .statusCode(OK.getStatusCode()); + String body = getTools.getBody().asString(); + JsonReader bodyObject = Json.createReader(new StringReader(body)); + JsonArray tools = bodyObject.readObject().getJsonArray("data"); + for (int i = 0; i < tools.size(); i++) { + JsonObject tool = tools.getJsonObject(i); + int id = tool.getInt("id"); + Response deleteExternalTool = UtilIT.deleteExternalTool(id); + deleteExternalTool.prettyPrint(); + } + + Response createUser = UtilIT.createRandomUser(); + createUser.prettyPrint(); + createUser.then().assertThat() + .statusCode(OK.getStatusCode()); + String username = UtilIT.getUsernameFromResponse(createUser); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.prettyPrint(); + createDataverseResponse.then().assertThat() + .statusCode(CREATED.getStatusCode()); + + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDataset.prettyPrint(); + createDataset.then().assertThat() + .statusCode(CREATED.getStatusCode()); + + Integer datasetId = UtilIT.getDatasetIdFromResponse(createDataset); + + String pathToJupyterNotebok = "src/test/java/edu/harvard/iq/dataverse/util/irc-metrics.ipynb"; + Response uploadJupyterNotebook = UtilIT.uploadFileViaNative(datasetId.toString(), pathToJupyterNotebok, apiToken); + uploadJupyterNotebook.prettyPrint(); + uploadJupyterNotebook.then().assertThat() + .statusCode(OK.getStatusCode()); + + Integer jupyterNotebookFileId = JsonPath.from(uploadJupyterNotebook.getBody().asString()).getInt("data.files[0].dataFile.id"); + + String pathToTabularFile = "src/test/java/edu/harvard/iq/dataverse/util/irclog.tsv"; + Response uploadTabularFile = UtilIT.uploadFileViaNative(datasetId.toString(), pathToTabularFile, apiToken); + uploadTabularFile.prettyPrint(); + uploadTabularFile.then().assertThat() + .statusCode(OK.getStatusCode()); + + Integer tabularFileId = JsonPath.from(uploadTabularFile.getBody().asString()).getInt("data.files[0].dataFile.id"); - @Test - public void testAddExternalTool() throws IOException { JsonObjectBuilder job = Json.createObjectBuilder(); job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); - job.add("type", "explorer"); + job.add("type", "explore"); + job.add("scope", "file"); + job.add("contentType", "text/tab-separated-values"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder() .add("queryParameters", Json.createArrayBuilder() @@ -54,13 +108,134 @@ public void testAddExternalTool() throws IOException { addExternalTool.then().assertThat() .body("data.displayName", CoreMatchers.equalTo("AwesomeTool")) .statusCode(OK.getStatusCode()); + + long toolId = JsonPath.from(addExternalTool.getBody().asString()).getLong("data.id"); + + Response getTool = UtilIT.getExternalTool(toolId); + getTool.prettyPrint(); + getTool.then().assertThat() + .body("data.scope", CoreMatchers.equalTo("file")) + .statusCode(OK.getStatusCode()); + + Response getExternalToolsForFileInvalidType = UtilIT.getExternalToolsForFile(tabularFileId.toString(), "invalidType", apiToken); + getExternalToolsForFileInvalidType.prettyPrint(); + getExternalToolsForFileInvalidType.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("message", CoreMatchers.equalTo("Type must be one of these values: [explore, configure].")); + + Response getExternalToolsForTabularFiles = UtilIT.getExternalToolsForFile(tabularFileId.toString(), "explore", apiToken); + getExternalToolsForTabularFiles.prettyPrint(); + getExternalToolsForTabularFiles.then().assertThat() + .statusCode(OK.getStatusCode()) + .body("data[0].displayName", CoreMatchers.equalTo("AwesomeTool")) + .body("data[0].scope", CoreMatchers.equalTo("file")) + .body("data[0].contentType", CoreMatchers.equalTo("text/tab-separated-values")) + .body("data[0].toolUrlWithQueryParams", CoreMatchers.equalTo("http://awesometool.com?fileid=" + tabularFileId + "&key=" + apiToken)); + + Response getExternalToolsForJuptyerNotebooks = UtilIT.getExternalToolsForFile(jupyterNotebookFileId.toString(), "explore", apiToken); + getExternalToolsForJuptyerNotebooks.prettyPrint(); + getExternalToolsForJuptyerNotebooks.then().assertThat() + .statusCode(OK.getStatusCode()) + // No tools for this file type. + .body("data", Matchers.hasSize(0)); + } + + @Test + public void testDatasetLevelTool1() { + + // Delete all external tools before testing. + Response getTools = UtilIT.getExternalTools(); + getTools.prettyPrint(); + getTools.then().assertThat() + .statusCode(OK.getStatusCode()); + String body = getTools.getBody().asString(); + JsonReader bodyObject = Json.createReader(new StringReader(body)); + JsonArray tools = bodyObject.readObject().getJsonArray("data"); + for (int i = 0; i < tools.size(); i++) { + JsonObject tool = tools.getJsonObject(i); + int id = tool.getInt("id"); + Response deleteExternalTool = UtilIT.deleteExternalTool(id); + deleteExternalTool.prettyPrint(); + } + + Response createUser = UtilIT.createRandomUser(); + createUser.prettyPrint(); + createUser.then().assertThat() + .statusCode(OK.getStatusCode()); + String username = UtilIT.getUsernameFromResponse(createUser); + String apiToken = UtilIT.getApiTokenFromResponse(createUser); + + Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken); + createDataverseResponse.prettyPrint(); + createDataverseResponse.then().assertThat() + .statusCode(CREATED.getStatusCode()); + + String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse); + + Response createDataset = UtilIT.createRandomDatasetViaNativeApi(dataverseAlias, apiToken); + createDataset.prettyPrint(); + createDataset.then().assertThat() + .statusCode(CREATED.getStatusCode()); + +// Integer datasetId = UtilIT.getDatasetIdFromResponse(createDataset); + Integer datasetId = JsonPath.from(createDataset.getBody().asString()).getInt("data.id"); + String datasetPid = JsonPath.from(createDataset.getBody().asString()).getString("data.persistentId"); + + String pathToFile = "src/test/java/edu/harvard/iq/dataverse/util/irclog.tsv"; + UtilIT.uploadFileViaNative(datasetId.toString(), pathToFile, apiToken); + + Response getFileIdRequest = UtilIT.nativeGet(datasetId, apiToken); + getFileIdRequest.prettyPrint(); + getFileIdRequest.then().assertThat() + .statusCode(OK.getStatusCode());; + + int fileId = JsonPath.from(getFileIdRequest.getBody().asString()).getInt("data.latestVersion.files[0].dataFile.id"); + + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("displayName", "DatasetTool1"); + job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "dataset"); + job.add("toolUrl", "http://datasettool1.com"); + job.add("toolParameters", Json.createObjectBuilder() + .add("queryParameters", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("datasetPid", "{datasetPid}") + .build()) + .add(Json.createObjectBuilder() + .add("key", "{apiToken}") + .build()) + .build()) + .build()); + Response addExternalTool = UtilIT.addExternalTool(job.build()); + addExternalTool.prettyPrint(); + addExternalTool.then().assertThat() + .body("data.displayName", CoreMatchers.equalTo("DatasetTool1")) + .statusCode(OK.getStatusCode()); + + Response getExternalToolsByDatasetIdInvalidType = UtilIT.getExternalToolsForDataset(datasetId.toString(), "invalidType", apiToken); + getExternalToolsByDatasetIdInvalidType.prettyPrint(); + getExternalToolsByDatasetIdInvalidType.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("message", CoreMatchers.equalTo("Type must be one of these values: [explore, configure].")); + + Response getExternalToolsByDatasetId = UtilIT.getExternalToolsForDataset(datasetId.toString(), "explore", apiToken); + getExternalToolsByDatasetId.prettyPrint(); + getExternalToolsByDatasetId.then().assertThat() + .body("data[0].displayName", CoreMatchers.equalTo("DatasetTool1")) + .body("data[0].scope", CoreMatchers.equalTo("dataset")) + .body("data[0].toolUrlWithQueryParams", CoreMatchers.equalTo("http://datasettool1.com?datasetPid=" + datasetPid + "&key=" + apiToken)) + .statusCode(OK.getStatusCode()); + } @Test - public void testAddExternalToolNoFileId() throws IOException { + public void testAddFilelToolNoFileId() throws IOException { JsonObjectBuilder job = Json.createObjectBuilder(); job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder() .add("queryParameters", Json.createArrayBuilder() @@ -72,16 +247,30 @@ public void testAddExternalToolNoFileId() throws IOException { Response addExternalTool = UtilIT.addExternalTool(job.build()); addExternalTool.prettyPrint(); addExternalTool.then().assertThat() - .body("message", CoreMatchers.equalTo("Required reserved word not found: {fileId}")) + .body("message", CoreMatchers.equalTo("One of the following reserved words is required: {fileId}, {filePid}.")) .statusCode(BAD_REQUEST.getStatusCode()); } @Test - public void testDeleteExternalTool() { - // FIXME: Don't hard code a tool id here. - long externalToolId = 14l; - Response addExternalTool = UtilIT.deleteExternalTool(externalToolId); + public void testAddDatasetToolNoDatasetId() throws IOException { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("displayName", "AwesomeTool"); + job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "dataset"); + job.add("toolUrl", "http://awesometool.com"); + job.add("toolParameters", Json.createObjectBuilder() + .add("queryParameters", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("key", "{apiToken}") + .build()) + .build()) + .build()); + Response addExternalTool = UtilIT.addExternalTool(job.build()); addExternalTool.prettyPrint(); + addExternalTool.then().assertThat() + .statusCode(BAD_REQUEST.getStatusCode()) + .body("message", CoreMatchers.equalTo("One of the following reserved words is required: {datasetId}, {datasetPid}.")); } @Test @@ -89,6 +278,8 @@ public void testAddExternalToolNonReservedWord() throws IOException { JsonObjectBuilder job = Json.createObjectBuilder(); job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder() .add("queryParameters", Json.createArrayBuilder() diff --git a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java index e6b3d032309..840d77d4426 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java @@ -1748,9 +1748,9 @@ static Response getExternalTools() { .get("/api/admin/externalTools"); } - static Response getExternalToolsByFileId(long fileId) { + static Response getExternalTool(long id) { return given() - .get("/api/admin/externalTools/file/" + fileId); + .get("/api/admin/externalTools/" + id); } static Response addExternalTool(JsonObject jsonObject) { @@ -1766,6 +1766,36 @@ static Response deleteExternalTool(long externalToolid) { .delete("/api/admin/externalTools/" + externalToolid); } + static Response getExternalToolsForDataset(String idOrPersistentIdOfDataset, String type, String apiToken) { + String idInPath = idOrPersistentIdOfDataset; // Assume it's a number. + String optionalQueryParam = ""; // If idOrPersistentId is a number we'll just put it in the path. + if (!NumberUtils.isNumber(idOrPersistentIdOfDataset)) { + idInPath = ":persistentId"; + optionalQueryParam = "&persistentId=" + idOrPersistentIdOfDataset; + } + RequestSpecification requestSpecification = given(); + if (apiToken != null) { + requestSpecification = given() + .header(UtilIT.API_TOKEN_HTTP_HEADER, apiToken); + } + return requestSpecification.get("/api/admin/test/datasets/" + idInPath + "/externalTools?type=" + type + optionalQueryParam); + } + + static Response getExternalToolsForFile(String idOrPersistentIdOfFile, String type, String apiToken) { + String idInPath = idOrPersistentIdOfFile; // Assume it's a number. + String optionalQueryParam = ""; // If idOrPersistentId is a number we'll just put it in the path. + if (!NumberUtils.isNumber(idOrPersistentIdOfFile)) { + idInPath = ":persistentId"; + optionalQueryParam = "&persistentId=" + idOrPersistentIdOfFile; + } + RequestSpecification requestSpecification = given(); + if (apiToken != null) { + requestSpecification = given() + .header(UtilIT.API_TOKEN_HTTP_HEADER, apiToken); + } + return requestSpecification.get("/api/admin/test/files/" + idInPath + "/externalTools?type=" + type + optionalQueryParam); + } + static Response submitFeedback(JsonObjectBuilder job) { return given() .body(job.build().toString()) diff --git a/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandlerTest.java b/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandlerTest.java index bfa00aac677..b209eeb1364 100644 --- a/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandlerTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolHandlerTest.java @@ -21,8 +21,9 @@ public class ExternalToolHandlerTest { @Test public void testGetToolUrlWithOptionalQueryParameters() { ExternalTool.Type type = ExternalTool.Type.EXPLORE; + ExternalTool.Scope scope = ExternalTool.Scope.FILE; String toolUrl = "http://example.com"; - ExternalTool externalTool = new ExternalTool("displayName", "description", type, toolUrl, "{}", DataFileServiceBean.MIME_TYPE_TSV_ALT); + ExternalTool externalTool = new ExternalTool("displayName", "description", type, scope, toolUrl, "{}", DataFileServiceBean.MIME_TYPE_TSV_ALT); // One query parameter, not a reserved word, no {fileId} (required) used. externalTool.setToolParameters(Json.createObjectBuilder() diff --git a/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBeanTest.java b/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBeanTest.java index 54462b3ca0b..75cae3214c1 100644 --- a/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBeanTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolServiceBeanTest.java @@ -6,6 +6,7 @@ import edu.harvard.iq.dataverse.Dataset; import edu.harvard.iq.dataverse.DatasetVersion; import edu.harvard.iq.dataverse.FileMetadata; +import edu.harvard.iq.dataverse.GlobalId; import edu.harvard.iq.dataverse.authorization.users.ApiToken; import java.util.ArrayList; import java.util.List; @@ -13,13 +14,14 @@ import javax.json.JsonObjectBuilder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import org.junit.Test; public class ExternalToolServiceBeanTest { public ExternalToolServiceBeanTest() { } - + @Test public void testfindAll() { DataFile dataFile = new DataFile(); @@ -39,7 +41,8 @@ public void testfindAll() { ApiToken apiToken = new ApiToken(); apiToken.setTokenString("7196b5ce-f200-4286-8809-03ffdbc255d7"); ExternalTool.Type type = ExternalTool.Type.EXPLORE; - ExternalTool externalTool = new ExternalTool("displayName", "description", type, "http://foo.com", "{}", DataFileServiceBean.MIME_TYPE_TSV_ALT); + ExternalTool.Scope scope = ExternalTool.Scope.FILE; + ExternalTool externalTool = new ExternalTool("displayName", "description", type, scope, "http://foo.com", "{}", DataFileServiceBean.MIME_TYPE_TSV_ALT); ExternalToolHandler externalToolHandler4 = new ExternalToolHandler(externalTool, dataFile, apiToken, fmd); List externalTools = new ArrayList<>(); externalTools.add(externalTool); @@ -53,6 +56,7 @@ public void testParseAddExternalToolInput() { job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); job.add("type", "explore"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder() .add("queryParameters", Json.createArrayBuilder() @@ -85,18 +89,65 @@ public void testParseAddExternalToolInput() { dataFile.setFileMetadatas(fmdl); ApiToken apiToken = new ApiToken(); apiToken.setTokenString("7196b5ce-f200-4286-8809-03ffdbc255d7"); - ExternalToolHandler externalToolHandler = new ExternalToolHandler(externalTool, dataFile, apiToken,fmd); + ExternalToolHandler externalToolHandler = new ExternalToolHandler(externalTool, dataFile, apiToken, fmd); String toolUrl = externalToolHandler.getToolUrlWithQueryParams(); System.out.println("result: " + toolUrl); assertEquals("http://awesometool.com?fileid=42&key=7196b5ce-f200-4286-8809-03ffdbc255d7&fileMetadataId=2", toolUrl); } + @Test + public void testParseAddFileToolFilePid() { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("displayName", "AwesomeTool"); + job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "file"); + job.add("toolUrl", "http://awesometool.com"); + job.add("toolParameters", Json.createObjectBuilder() + .add("queryParameters", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("filePid", "{filePid}") + .build()) + .add(Json.createObjectBuilder() + .add("key", "{apiToken}") + .build()) + .add(Json.createObjectBuilder() + .add("fileMetadataId", "{fileMetadataId}") + .build()) + .build()) + .build()); + job.add(ExternalTool.CONTENT_TYPE, DataFileServiceBean.MIME_TYPE_TSV_ALT); + String tool = job.build().toString(); + System.out.println("tool: " + tool); + ExternalTool externalTool = ExternalToolServiceBean.parseAddExternalToolManifest(tool); + assertEquals("AwesomeTool", externalTool.getDisplayName()); + DataFile dataFile = new DataFile(); + dataFile.setId(42l); + dataFile.setGlobalId(new GlobalId("doi:10.5072/FK2/RMQT6J/G9F1A1")); + FileMetadata fmd = new FileMetadata(); + fmd.setId(2L); + DatasetVersion dv = new DatasetVersion(); + Dataset ds = new Dataset(); + dv.setDataset(ds); + fmd.setDatasetVersion(dv); + List fmdl = new ArrayList(); + fmdl.add(fmd); + dataFile.setFileMetadatas(fmdl); + ApiToken apiToken = new ApiToken(); + apiToken.setTokenString("7196b5ce-f200-4286-8809-03ffdbc255d7"); + ExternalToolHandler externalToolHandler = new ExternalToolHandler(externalTool, dataFile, apiToken, fmd); + String toolUrl = externalToolHandler.getToolUrlWithQueryParams(); + System.out.println("result: " + toolUrl); + assertEquals("http://awesometool.com?filePid=doi:10.5072/FK2/RMQT6J/G9F1A1&key=7196b5ce-f200-4286-8809-03ffdbc255d7&fileMetadataId=2", toolUrl); + } + @Test public void testParseAddExternalToolInputNoFileId() { JsonObjectBuilder job = Json.createObjectBuilder(); job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); job.add("type", "explore"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder() .add("queryParameters", Json.createArrayBuilder() @@ -115,7 +166,7 @@ public void testParseAddExternalToolInputNoFileId() { expectedException = ex; } assertNotNull(expectedException); - assertEquals("Required reserved word not found: {fileId}", expectedException.getMessage()); + assertEquals("One of the following reserved words is required: {fileId}, {filePid}.", expectedException.getMessage()); } @Test @@ -148,6 +199,7 @@ public void testParseAddExternalToolInputUnknownReservedWord() { job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); job.add("type", "explore"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder() .add("queryParameters", Json.createArrayBuilder() @@ -219,6 +271,7 @@ public void testParseAddExternalToolInputNoToolUrl() { job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); job.add("type", "explore"); + job.add("scope", "file"); job.add("toolParameters", Json.createObjectBuilder().build()); job.add(ExternalTool.CONTENT_TYPE, DataFileServiceBean.MIME_TYPE_TSV_ALT); String tool = job.build().toString(); @@ -239,6 +292,7 @@ public void testParseAddExternalToolInputWrongType() { job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); job.add("type", "noSuchType"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); job.add("toolParameters", Json.createObjectBuilder().build()); job.add(ExternalTool.CONTENT_TYPE, DataFileServiceBean.MIME_TYPE_TSV_ALT); @@ -261,8 +315,9 @@ public void testParseAddExternalToolInputNoContentType() { job.add("displayName", "AwesomeTool"); job.add("description", "This tool is awesome."); job.add("type", "explore"); + job.add("scope", "file"); job.add("toolUrl", "http://awesometool.com"); - + job.add("toolParameters", Json.createObjectBuilder().add("queryParameters", Json.createArrayBuilder() .add(Json.createObjectBuilder() .add("fileid", "{fileId}") @@ -271,7 +326,7 @@ public void testParseAddExternalToolInputNoContentType() { .add("key", "{apiToken}") .build()) .build()) - .build()); + .build()); String tool = job.build().toString(); System.out.println("tool: " + tool); ExternalTool externalTool = null; @@ -281,7 +336,96 @@ public void testParseAddExternalToolInputNoContentType() { System.out.println(ex.getMessage()); } assertNotNull(externalTool); - assertEquals(externalTool.getContentType(), DataFileServiceBean.MIME_TYPE_TSV_ALT); + assertNull(externalTool.getContentType()); + } + + @Test + public void testParseAddDatasetToolNoRequiredFields() { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("displayName", "AwesomeTool"); + job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "dataset"); + job.add("toolUrl", "http://awesometool.com"); + + job.add("toolParameters", Json.createObjectBuilder().add("queryParameters", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("key", "{apiToken}") + .build()) + .build()) + .build()); + String tool = job.build().toString(); + System.out.println("tool: " + tool); + Exception expectedException = null; + try { + ExternalTool externalTool = ExternalToolServiceBean.parseAddExternalToolManifest(tool); + } catch (Exception ex) { + expectedException = ex; + } + assertNotNull(expectedException); + assertEquals("One of the following reserved words is required: {datasetId}, {datasetPid}.", expectedException.getMessage()); + } + + @Test + public void testParseAddDatasetToolDatasetId() { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("displayName", "AwesomeTool"); + job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "dataset"); + job.add("toolUrl", "http://awesometool.com"); + + job.add("toolParameters", Json.createObjectBuilder().add("queryParameters", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("datasetId", "{datasetId}") + .build()) + .add(Json.createObjectBuilder() + .add("key", "{apiToken}") + .build()) + .build()) + .build()); + String tool = job.build().toString(); + System.out.println("tool: " + tool); + + ExternalTool externalTool = null; + try { + externalTool = ExternalToolServiceBean.parseAddExternalToolManifest(tool); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + assertNotNull(externalTool); + assertNull(externalTool.getContentType()); + } + + @Test + public void testParseAddDatasetToolDatasetPid() { + JsonObjectBuilder job = Json.createObjectBuilder(); + job.add("displayName", "AwesomeTool"); + job.add("description", "This tool is awesome."); + job.add("type", "explore"); + job.add("scope", "dataset"); + job.add("toolUrl", "http://awesometool.com"); + + job.add("toolParameters", Json.createObjectBuilder().add("queryParameters", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("datasetPid", "{datasetPid}") + .build()) + .add(Json.createObjectBuilder() + .add("key", "{apiToken}") + .build()) + .build()) + .build()); + String tool = job.build().toString(); + System.out.println("tool: " + tool); + + ExternalTool externalTool = null; + try { + externalTool = ExternalToolServiceBean.parseAddExternalToolManifest(tool); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + assertNotNull(externalTool); + assertNull(externalTool.getContentType()); } } diff --git a/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolTest.java b/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolTest.java index d0d51646adf..6dc4cfed44e 100644 --- a/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolTest.java +++ b/src/test/java/edu/harvard/iq/dataverse/externaltools/ExternalToolTest.java @@ -14,9 +14,10 @@ public void testToJson() { String displayName = "myDisplayName"; String description = "myDescription"; ExternalTool.Type type = ExternalTool.Type.EXPLORE; + ExternalTool.Scope scope = ExternalTool.Scope.FILE; String toolUrl = "http://example.com"; String toolParameters = "{}"; - ExternalTool externalTool = new ExternalTool(displayName, description, type, toolUrl, toolParameters, DataFileServiceBean.MIME_TYPE_TSV_ALT); + ExternalTool externalTool = new ExternalTool(displayName, description, type, scope, toolUrl, toolParameters, DataFileServiceBean.MIME_TYPE_TSV_ALT); externalTool.setId(42l); JsonObject jsonObject = externalTool.toJson().build(); System.out.println("result: " + jsonObject);