-
Notifications
You must be signed in to change notification settings - Fork 531
TKLabels v2 implementation #11294
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
Merged
ofahimIQSS
merged 30 commits into
IQSS:develop
from
GlobalDataverseCommunityConsortium:TKLabels
Jun 27, 2025
Merged
TKLabels v2 implementation #11294
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
593c31a
api calls to search for projects/get a project
qqmyers b573a45
initial tests
qqmyers 3724a1d
updated tests - for failure cases
qqmyers 67c9441
docs
qqmyers 7da4bd7
example image
qqmyers bf8e354
release note
qqmyers cdd32d2
remove links
qqmyers 8e7f2eb
Merge remote-tracking branch 'IQSS/develop' into DANS-CSL
qqmyers e6f1169
path fix
qqmyers 80c66af
further path fix
qqmyers b80856e
link fix
qqmyers c7c5af6
missing colon
qqmyers ecb9c79
Merge remote-tracking branch 'IQSS/develop' into DANS-CSL
qqmyers 9be84b2
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers f3fffe8
fix query per Ashley
qqmyers 5292848
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers da3f314
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers c86e49f
Merge remote-tracking branch 'IQSS/develop' into AWSv2
qqmyers 387a500
Merge remote-tracking branch 'IQSS/develop' into ApacheHTTPUpdate
qqmyers 8948d60
add reference to LC mdb
qqmyers 229fbbc
remove unused imports, info->fine per review
qqmyers 0d2ed6d
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers 7cbd078
Merge branch 'TKLabels' of https://github.com/GlobalDataverseCommunit…
qqmyers 2fdcafd
Update doc/release-notes/TKLabels.md
qqmyers 3cdc3f9
Update doc/release-notes/TKLabels.md
qqmyers f9d9efe
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers 0bd20e4
Merge branch 'TKLabels' of https://github.com/GlobalDataverseCommunit…
qqmyers 29cbca9
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers b075488
Merge remote-tracking branch 'IQSS/develop' into TKLabels
qqmyers 61181ba
Add a feature flag to optionally enable permission check in LC api
qqmyers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| New API calls to find projects at https://localcontextshub.org associated with a dataset have been added. This supports integration via | ||
| an external vocabulary script that allows users to associate such a project with their dataset and display the associated Notices and Tribal Knowledge Labels. | ||
|
|
||
|
|
||
| Connecting to LocalContexts requires a LocalContexts API Key. Using both the production and sandbox (test) LocalContexts servers are supported. | ||
|
|
||
| See also [the guides](https://dataverse-guide--11294.org.readthedocs.build/en/11294/installation/localcontexts.html) and #11294. | ||
|
|
||
| ## Settings Added | ||
|
|
||
| The following settings have been added: | ||
|
|
||
| dataverse.localcontexts.url | ||
| dataverse.localcontexts.api-key | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,4 +22,5 @@ Installation Guide | |
| oidc | ||
| orcid | ||
| external-tools | ||
| localcontexts | ||
| advanced | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| LocalContexts Integration | ||
| ========================= | ||
|
|
||
| .. contents:: |toctitle| | ||
| :local: | ||
|
|
||
| `Local Contexts <https://localcontexts.org/>`_ is a global initiative that supports Indigenous communities in the management and sharing of their cultural heritage and data. | ||
| The `Local Contexts Hub <https://localcontextshub.org/>`_ is a platform that enables the creation and application of Traditional Knowledge (TK) and Biocultural (BC) Labels and Notices. | ||
| These labels and notices help to communicate the cultural context and appropriate use of Indigenous data and cultural heritage materials. | ||
|
|
||
| Dataverse supports integration with the Local Contexts Hub so that Labels and Notices associated with a dataset can be displayed on the dataset page: | ||
|
|
||
| .. figure:: ./img/LCDemo.png | ||
| :alt: Dataset Page showing Local Contexts integration with Dataverse Software | ||
|
|
||
| Configuration | ||
| ------------- | ||
|
|
||
| There are several steps to LocalContexts integration. | ||
|
|
||
| First, you need to configure the LOCALCONTEXTS_URL and LOCALCONTEXTS_API_KEY as described in the :ref:`localcontexts` section of the Configuration Guide. | ||
| API Keys are available to Local Contexts Integration Partners - see https://localcontexts.org/hub-agreements/integration-partners/ for details. | ||
|
|
||
| Next, you should add the Local Contexts metadatablock and configure the associated external vocabulary script. | ||
| The metadatablock contains one field allowing Dataverse to store the URL of an associated Local Contexts Hub project. | ||
qqmyers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| The external vocabulary script interacts with the Local Contexts Hub (via the Dataverse server) to display the Labels and Notices associated with the proect and provide a link to it. | ||
| The script also supports adding/removing such a link from the dataset's metadata. Note that only a project that references the dataset in its `publication_doi` field can be linked to a dataset. | ||
| See https://github.com/gdcc/dataverse-external-vocab-support/blob/main/packages/local_contexts/README.md for details on these steps. | ||
|
|
||
| Lastly, if you wish the Local Contexts information to be shown in the summary section of the dataset page, as shown in the image above, you should add `LCProjectUrl` to list of custom summary fields via use of the :ref:`:CustomDatasetSummaryFields` setting. | ||
|
|
||
| Optionally, one could also set the dataverse.feature.add-local-contexts-permission-check FeatureFlag to true. This assures that only users editing datasets can use the LocalContexts search functionality. | ||
| However, as this currently would also require setting the dataverse.feature.api-session-auth, the security implications of which haven't been fully explored, it is not recommended unless problematic use is seen. | ||
| (When API access via OpenIdConnect is available, use of api-session-auth would not be required.) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
src/main/java/edu/harvard/iq/dataverse/api/LocalContexts.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| package edu.harvard.iq.dataverse.api; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.URI; | ||
| import java.net.URISyntaxException; | ||
| import java.net.http.HttpClient; | ||
| import java.net.http.HttpRequest; | ||
| import java.net.http.HttpResponse; | ||
| import java.util.logging.Logger; | ||
|
|
||
| import edu.harvard.iq.dataverse.Dataset; | ||
| import edu.harvard.iq.dataverse.DatasetServiceBean; | ||
| import edu.harvard.iq.dataverse.DataverseRequestServiceBean; | ||
| import edu.harvard.iq.dataverse.PermissionServiceBean; | ||
| import edu.harvard.iq.dataverse.api.auth.AuthRequired; | ||
| import edu.harvard.iq.dataverse.authorization.Permission; | ||
| import edu.harvard.iq.dataverse.engine.command.DataverseRequest; | ||
| import edu.harvard.iq.dataverse.settings.FeatureFlags; | ||
| import edu.harvard.iq.dataverse.settings.JvmSettings; | ||
| import edu.harvard.iq.dataverse.util.json.JsonUtil; | ||
| import jakarta.ejb.EJB; | ||
| import jakarta.inject.Inject; | ||
| import jakarta.json.JsonObject; | ||
| import jakarta.ws.rs.*; | ||
| import jakarta.ws.rs.core.Context; | ||
| import jakarta.ws.rs.core.MediaType; | ||
| import jakarta.ws.rs.core.Response; | ||
| import jakarta.ws.rs.container.ContainerRequestContext; | ||
qqmyers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Path("localcontexts") | ||
| public class LocalContexts extends AbstractApiBean { | ||
|
|
||
| protected static final Logger logger = Logger.getLogger(LocalContexts.class.getName()); | ||
|
|
||
| @EJB | ||
| DatasetServiceBean datasetService; | ||
|
|
||
| @Inject | ||
| DataverseRequestServiceBean dvRequestService; | ||
|
|
||
| @EJB | ||
| PermissionServiceBean permissionService; | ||
|
|
||
| @GET | ||
| @Path("/datasets/{id}") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| @AuthRequired | ||
| public Response getDatasetLocalContexts(@Context ContainerRequestContext crc, @PathParam("id") String id) { | ||
| try { | ||
| Dataset dataset = findDatasetOrDie(id); | ||
| DataverseRequest req = createDataverseRequest(getRequestUser(crc)); | ||
|
|
||
| // Check if the user has edit dataset permission | ||
| /* Feature flag to skip permission check | ||
| * If you add the api-session-auth FeatureFlag, you can verify if the user has edit permissions | ||
| * | ||
| */ | ||
| if (FeatureFlags.ADD_LOCAL_CONTEXTS_PERMISSION_CHECK.enabled() && !permissionService.userOn(req.getUser(), dataset).has(Permission.EditDataset)) { | ||
| return error(Response.Status.FORBIDDEN, | ||
| "You do not have permission to query LocalContexts about this dataset."); | ||
| } | ||
|
|
||
| String localContextsUrl = JvmSettings.LOCALCONTEXTS_URL.lookupOptional().orElse(null); | ||
| String localContextsApiKey = JvmSettings.LOCALCONTEXTS_API_KEY.lookupOptional().orElse(null); | ||
|
|
||
| if (localContextsUrl == null || localContextsApiKey == null) { | ||
| return error(Response.Status.NOT_FOUND, "LocalContexts API configuration is missing."); | ||
| } | ||
|
|
||
| String datasetDoi = dataset.getGlobalId().asString(); | ||
| String apiUrl = localContextsUrl + "api/v2/projects/?publication_doi=" + datasetDoi; | ||
| logger.fine("URL used: " + apiUrl); | ||
| try { | ||
| HttpClient client = HttpClient.newHttpClient(); | ||
| HttpRequest request = HttpRequest.newBuilder().uri(new URI(apiUrl)) | ||
| .header("X-Api-Key", localContextsApiKey).GET().build(); | ||
|
|
||
| HttpResponse<String> response; | ||
|
|
||
| response = client.send(request, HttpResponse.BodyHandlers.ofString()); | ||
|
|
||
| if (response.statusCode() == 200) { | ||
| // Assuming the response is already in JSON format | ||
| logger.fine("Response from search: " + response.body()); | ||
| JsonObject jsonObject = JsonUtil.getJsonObject(response.body()); | ||
| return ok(jsonObject); | ||
| } else { | ||
| return error(Response.Status.SERVICE_UNAVAILABLE, | ||
| "Error from LocalContexts API: " + response.statusCode()); | ||
| } | ||
| } catch (URISyntaxException e) { | ||
| logger.warning(e.getMessage()); | ||
| return error(Response.Status.SERVICE_UNAVAILABLE, "LocalContexts connection misconfigured."); | ||
| } catch (IOException | InterruptedException e) { | ||
| logger.warning(e.getMessage()); | ||
| e.printStackTrace(); | ||
| return error(Response.Status.SERVICE_UNAVAILABLE, "Error contacting LocalContexts"); | ||
|
|
||
| } | ||
| } catch (WrappedResponse ex) { | ||
| return ex.getResponse(); | ||
| } | ||
| } | ||
|
|
||
| @GET | ||
| @Path("/datasets/{id}/{projectId}") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| public Response searchLocalContexts(@PathParam("id") String datasetId, @PathParam("projectId") String projectId) { | ||
| try { | ||
| Dataset dataset = findDatasetOrDie(datasetId); | ||
| String localContextsUrl = JvmSettings.LOCALCONTEXTS_URL.lookupOptional().orElse(null); | ||
| String localContextsApiKey = JvmSettings.LOCALCONTEXTS_API_KEY.lookupOptional().orElse(null); | ||
|
|
||
| if (localContextsUrl == null || localContextsApiKey == null) { | ||
| return error(Response.Status.NOT_FOUND, "LocalContexts API configuration is missing."); | ||
| } | ||
|
|
||
| String apiUrl = localContextsUrl + "api/v2/projects/" + projectId + "/"; | ||
| logger.fine("URL used: " + apiUrl); | ||
| try { | ||
| HttpClient client = HttpClient.newHttpClient(); | ||
| HttpRequest request = HttpRequest.newBuilder().uri(new URI(apiUrl)) | ||
| .header("X-Api-Key", localContextsApiKey).GET().build(); | ||
|
|
||
| HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | ||
|
|
||
| if (response.statusCode() == 200) { | ||
| // Parse the JSON response | ||
| JsonObject jsonResponse = JsonUtil.getJsonObject(response.body()); | ||
| logger.fine("Response from get: " + JsonUtil.prettyPrint(jsonResponse)); | ||
|
|
||
| // Check if the response contains a "publication_doi" key | ||
| if (jsonResponse.containsKey("external_ids")) { | ||
| JsonObject externalIds = jsonResponse.getJsonObject("external_ids"); | ||
| if (externalIds.containsKey("publication_doi")) { | ||
| String responseDoi = externalIds.getString("publication_doi"); | ||
| String datasetDoi = dataset.getGlobalId().asString(); | ||
| // Compare the DOI from the response with the dataset's DOI | ||
| if (responseDoi.equals(datasetDoi)) { | ||
| // Return the JSON response as-is | ||
| return ok(jsonResponse); | ||
| } else { | ||
| // DOI mismatch, return 404 | ||
| return error(Response.Status.NOT_FOUND, | ||
| "LocalContexts information not found for this dataset."); | ||
| } | ||
| } else { | ||
| // "publication_doi" key not found in the response, return 404 | ||
| return error(Response.Status.NOT_FOUND, "Invalid response from Local Contexts API."); | ||
| } | ||
| } else { | ||
| // "external_ids" key not found in the response, return 404 | ||
| return error(Response.Status.NOT_FOUND, "Invalid response from Local Contexts API."); | ||
| } | ||
|
|
||
| } else { | ||
| return error(Response.Status.SERVICE_UNAVAILABLE, | ||
| "Error from Local Contexts API: " + response.statusCode()); | ||
| } | ||
| } catch (URISyntaxException e) { | ||
| logger.warning(e.getMessage()); | ||
| return error(Response.Status.SERVICE_UNAVAILABLE, "LocalContexts connection misconfigured."); | ||
| } catch (IOException | InterruptedException e) { | ||
| logger.warning(e.getMessage()); | ||
| e.printStackTrace(); | ||
| return error(Response.Status.SERVICE_UNAVAILABLE, "Error contacting LocalContexts"); | ||
| } | ||
| } catch (WrappedResponse ex) { | ||
| return ex.getResponse(); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.