Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make configure tools available at the dataset level #9925

Merged
merged 12 commits into from
Oct 2, 2023
Merged
1 change: 1 addition & 0 deletions doc/release-notes/9589-ds-configure-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Configure tools are now available at the dataset level. They appear under the "Edit Dataset" menu. See also #9589.
2 changes: 1 addition & 1 deletion doc/sphinx-guides/source/admin/external-tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Dataset level explore tools allow the user to explore all the files in a dataset
Dataset Level Configure Tools
+++++++++++++++++++++++++++++

Configure tools at the dataset level are not currently supported.
Dataset level configure tools can be launched by users who have edit access to the dataset. These tools are found under the "Edit Dataset" menu.

Writing Your Own External Tool
------------------------------
Expand Down
6 changes: 3 additions & 3 deletions doc/sphinx-guides/source/api/external-tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ How External Tools Are Presented to Users
An external tool can appear in your Dataverse installation in a variety of ways:

- as an explore, preview, query or configure option for a file
- as an explore option for a dataset
- as an explore or configure option for a dataset
- as an embedded preview on the file landing page

See also the :ref:`testing-external-tools` section of the Admin Guide for some perspective on how Dataverse installations will expect to test your tool before announcing it to their users.
Expand Down Expand Up @@ -88,11 +88,11 @@ Terminology

displayName The **name** of the tool in the Dataverse installation 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.
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 to 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).

types Whether the external tool is an **explore** tool, a **preview** tool, a **query** tool, a **configure** tool or any combination of these (multiple types are supported for a single 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. The older "type" keyword that allows you to pass a single type as a string is deprecated but still supported.
types Whether the external tool is an **explore** tool, a **preview** tool, a **query** tool, a **configure** tool or any combination of these (multiple types are supported for a single tool). Configure tools require an API token because they make changes to data files (files within datasets). The older "type" keyword that allows you to pass a single type as a string is deprecated but still supported.

toolUrl The **base URL** of the tool before query parameters are added.

Expand Down
26 changes: 26 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@ public String getTermsGuestbookPopupAction(){
Map<Long, List<ExternalTool>> fileQueryToolsByFileId = new HashMap<>();
List<ExternalTool> fileQueryTools = new ArrayList<>();
private List<ExternalTool> datasetExploreTools;
private List<ExternalTool> datasetConfigureTools;
// The selected dataset-level configure tool
private ExternalTool datasetConfigureTool;

public Boolean isHasRsyncScript() {
return hasRsyncScript;
Expand Down Expand Up @@ -2166,6 +2169,7 @@ private String init(boolean initFull) {
previewTools = externalToolService.findFileToolsByType(ExternalTool.Type.PREVIEW);
fileQueryTools = externalToolService.findFileToolsByType(ExternalTool.Type.QUERY);
datasetExploreTools = externalToolService.findDatasetToolsByType(ExternalTool.Type.EXPLORE);
datasetConfigureTools = externalToolService.findDatasetToolsByType(ExternalTool.Type.CONFIGURE);
rowsPerPage = 10;
if (dataset.getId() != null && canUpdateDataset()) {
hasRestrictedFiles = workingVersion.isHasRestrictedFile();
Expand Down Expand Up @@ -5668,6 +5672,18 @@ public List<ExternalTool> getDatasetExploreTools() {
return datasetExploreTools;
}

public List<ExternalTool> getDatasetConfigureTools() {
return datasetConfigureTools;
}

public ExternalTool getDatasetConfigureTool() {
return datasetConfigureTool;
}

public void setDatasetConfigureTool(ExternalTool datasetConfigureTool) {
this.datasetConfigureTool = datasetConfigureTool;
}

Boolean thisLatestReleasedVersion = null;

public boolean isThisLatestReleasedVersion() {
Expand Down Expand Up @@ -5885,6 +5901,16 @@ public void explore(ExternalTool externalTool) {
PrimeFaces.current().executeScript(externalToolHandler.getExploreScript());
}

public void configure(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, session.getLocaleCode());
PrimeFaces.current().executeScript(externalToolHandler.getConfigureScript());
}

private FileMetadata fileMetadataForAction;

public FileMetadata getFileMetadataForAction() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,11 @@ public String getExploreScript() {
logger.fine("Exploring with " + toolUrl);
return getScriptForUrl(toolUrl);
}

// TODO: Consider merging with getExploreScript
public String getConfigureScript() {
String toolUrl = this.getToolUrlWithQueryParams();
logger.fine("Configuring with " + toolUrl);
return getScriptForUrl(toolUrl);
}
}
1 change: 1 addition & 0 deletions src/main/java/propertyFiles/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,7 @@ dataset.pageTitle=Add New Dataset
dataset.accessBtn=Access Dataset
dataset.accessBtn.header.download=Download Options
dataset.accessBtn.header.explore=Explore Options
dataset.accessBtn.header.configure=Configure Options
dataset.accessBtn.header.compute=Compute Options
dataset.accessBtn.download.size=ZIP ({0})
dataset.accessBtn.too.big=The dataset is too large to download. Please select the files you need from the files table.
Expand Down
24 changes: 24 additions & 0 deletions src/main/webapp/dataset.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,17 @@
<h:outputText value="#{bundle['dataset.editBtn.itemLabel.thumbnailsAndWidgets']}"/>
</h:outputLink>
</li>
<ui:fragment rendered="#{DatasetPage.datasetConfigureTools.size() >= 1}">
<li class="dropdown-header">#{bundle['dataset.accessBtn.header.configure']} <span class="glyphicon glyphicon-cog"/></li>
<!-- Configure tool links -->
<ui:repeat var="tool" value="#{DatasetPage.datasetConfigureTools}">
<li>
<p:commandLink styleClass="btn-explore" actionListener="#{DatasetPage.setDatasetConfigureTool(tool)}" process="@this" update="datasetForm:configureToolDialog" oncomplete="PF('configureToolDialog').show();">
<h:outputText value="#{tool.getDisplayNameLang()}"/>
</p:commandLink>
</li>
</ui:repeat>
</ui:fragment>
<ui:fragment rendered="#{!DatasetPage.dataset.released and DatasetPage.dataset.latestVersion.versionState=='DRAFT' and permissionsWrapper.canIssueDeleteDatasetCommand(DatasetPage.dataset)}">
<li role="separator" class="divider"></li>
<li>
Expand Down Expand Up @@ -992,6 +1003,19 @@
</button>
</div>
</p:dialog>
<p:dialog id="configureToolDialog" styleClass="smallPopUp" header="#{DatasetPage.datasetConfigureTool.displayNameLang}" widgetVar="configureToolDialog" modal="true">
<p class="help-block">
<h:outputFormat value="#{DatasetPage.datasetConfigureTool.description}" escape="false"/>
</p>
<div class="button-block">
<p:commandLink styleClass="btn btn-default" action="#{DatasetPage.configure(DatasetPage.datasetConfigureTool)}" oncomplete="PF('configureToolDialog').hide();">
<h:outputFormat value="#{bundle['continue']}"/>
</p:commandLink>
<button class="btn btn-link" onclick="PF('configureToolDialog').hide();" type="button">
#{bundle.cancel}
</button>
</div>
</p:dialog>
<p:dialog id="citationsDialog" styleClass="smallPopUp" header="#{bundle['metrics.citations.dialog.header']}" widgetVar="citationsDialog" modal="true">
<p class="help-block">
<h:outputFormat value="#{bundle['metrics.citations.dialog.help']}" escape="false">
Expand Down
79 changes: 79 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/ExternalToolsIT.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.harvard.iq.dataverse.api;

import edu.harvard.iq.dataverse.util.json.JsonUtil;
import io.restassured.RestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
Expand Down Expand Up @@ -235,6 +236,84 @@ public void testDatasetLevelTool1() {

}

@Test
public void testDatasetLevelToolConfigure() {

// 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 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 = JsonPath.from(createDataset.getBody().asString()).getInt("data.id");
String datasetPid = JsonPath.from(createDataset.getBody().asString()).getString("data.persistentId");

String toolManifest = """
{
"displayName": "Dataset Configurator",
"description": "Slices! Dices! <a href='https://docs.datasetconfigurator.com' target='_blank'>More info</a>.",
"types": [
"configure"
],
"scope": "dataset",
"toolUrl": "https://datasetconfigurator.com",
"toolParameters": {
"queryParameters": [
{
"datasetPid": "{datasetPid}"
},
{
"localeCode": "{localeCode}"
}
]
}
}
""";

Response addExternalTool = UtilIT.addExternalTool(JsonUtil.getJsonObject(toolManifest));
addExternalTool.prettyPrint();
addExternalTool.then().assertThat()
.statusCode(OK.getStatusCode())
.body("data.displayName", CoreMatchers.equalTo("Dataset Configurator"));

Response getExternalToolsByDatasetId = UtilIT.getExternalToolsForDataset(datasetId.toString(), "configure", apiToken);
getExternalToolsByDatasetId.prettyPrint();
getExternalToolsByDatasetId.then().assertThat()
.body("data[0].displayName", CoreMatchers.equalTo("Dataset Configurator"))
.body("data[0].scope", CoreMatchers.equalTo("dataset"))
.body("data[0].types[0]", CoreMatchers.equalTo("configure"))
.body("data[0].toolUrlWithQueryParams", CoreMatchers.equalTo("https://datasetconfigurator.com?datasetPid=" + datasetPid))
.statusCode(OK.getStatusCode());

}

@Test
public void testAddFilelToolNoFileId() throws IOException {
JsonObjectBuilder job = Json.createObjectBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package edu.harvard.iq.dataverse.externaltools;

import edu.harvard.iq.dataverse.DOIServiceBean;
import edu.harvard.iq.dataverse.DataFile;
import edu.harvard.iq.dataverse.DataFileServiceBean;
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.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.settings.JvmSettings;
Expand All @@ -15,6 +17,7 @@
import jakarta.json.Json;
import jakarta.json.JsonObject;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down Expand Up @@ -234,4 +237,43 @@ public void testGetToolUrlWithAllowedApiCalls() {
assertTrue(signedUrl.contains("&token="));
System.out.println(JsonUtil.prettyPrint(jo));
}

@Test
@JvmSetting(key = JvmSettings.SITE_URL, value = "https://librascholar.org")
public void testDatasetConfigureTool() {
List<ExternalToolType> externalToolTypes = new ArrayList<>();
var externalToolType = new ExternalToolType();
externalToolType.setType(ExternalTool.Type.CONFIGURE);
externalToolTypes.add(externalToolType);
var scope = ExternalTool.Scope.DATASET;
String toolUrl = "http://example.com";
var externalTool = new ExternalTool("displayName", "toolName", "description", externalToolTypes, scope, toolUrl, "{}", DataFileServiceBean.MIME_TYPE_TSV_ALT);

externalTool.setToolParameters(Json.createObjectBuilder()
.add("queryParameters", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("siteUrl", "{siteUrl}")
)
.add(Json.createObjectBuilder()
.add("datasetPid", "{datasetPid}")
)
.add(Json.createObjectBuilder()
.add("localeCode", "{localeCode}")
)
)
.build().toString());

var dataset = new Dataset();
dataset.setGlobalId(new GlobalId(DOIServiceBean.DOI_PROTOCOL, "10.5072", "ABC123", null, DOIServiceBean.DOI_RESOLVER_URL, null));
ApiToken nullApiToken = null;
String nullLocaleCode = "en";
var externalToolHandler = new ExternalToolHandler(externalTool, dataset, nullApiToken, nullLocaleCode);
System.out.println("tool: " + externalToolHandler.getToolUrlWithQueryParams());
assertEquals("http://example.com?siteUrl=https://librascholar.org&datasetPid=doi:10.5072/ABC123&localeCode=en", externalToolHandler.getToolUrlWithQueryParams());
assertFalse(externalToolHandler.getExternalTool().isExploreTool());
assertEquals("configure", externalToolHandler.getExternalTool().getExternalToolTypes().get(0).getType().toString());
assertEquals("dataset", externalToolHandler.getExternalTool().getScope().toString());

}

}
Loading