Skip to content

Dataverse collection linking/unlinking without superuser status#12250

Merged
sekmiller merged 13 commits into
IQSS:developfrom
vera:feat/non-superuser-dataverse-linking
Jul 8, 2026
Merged

Dataverse collection linking/unlinking without superuser status#12250
sekmiller merged 13 commits into
IQSS:developfrom
vera:feat/non-superuser-dataverse-linking

Conversation

@vera

@vera vera commented Mar 24, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it:

Dataverse collection linking and unlinking no longer requires superuser status. Users with the "Link Dataverse" permission on a collection can now perform these actions through both the UI and API.

This change follows up on the introduction of the "Link Dataverse" permission. Previously, even with this permission, only superusers could actually perform linking actions, as reported in #12076.

  • Removed the superuser requirement for linking and unlinking Dataverse collections. (Updated the LinkDataverseCommand and DeleteDataverseLinkingDataverseCommand to use the LinkDataverse permission instead of a superuser check.)
  • Enabled the "Link" button in the Dataverse collection UI for non-superusers.
  • Updated documentation and integration tests to reflect these changes.
  • Fixed a bug in the linking service and API (discovered during testing) where the "linking" and "linked" collections were being mixed up during lookups and deletions.

Which issue(s) this PR closes:

Special notes for your reviewer:

/

Suggestions on how to test this:

I've updated the following test:

mvn test -Dtest="LinkIT#testCreateDeleteDataverseLink"

Does this PR introduce a user interface change? If mockups are available, please link/include them here:

No

Is there a release notes update needed for this change?:

I added a release note

Additional documentation:

/

@pdurbin pdurbin moved this to Ready for Triage in IQSS Dataverse Project Mar 24, 2026
@scolapasta scolapasta moved this from Ready for Triage to Ready for Review ⏩ in IQSS Dataverse Project Mar 24, 2026
@cmbz cmbz added the FY26 Sprint 20 FY26 Sprint 20 (2026-03-26 - 2026-04-08) label Mar 27, 2026
@pdurbin pdurbin force-pushed the feat/non-superuser-dataverse-linking branch from dce93ce to 02ae1be Compare April 2, 2026 14:16
@stevenwinship stevenwinship self-assigned this Apr 2, 2026
@stevenwinship stevenwinship moved this from Ready for Review ⏩ to In Review 🔎 in IQSS Dataverse Project Apr 2, 2026
Comment thread doc/sphinx-guides/source/api/native-api.rst Outdated
Comment thread doc/sphinx-guides/source/api/native-api.rst Outdated
Comment thread doc/sphinx-guides/source/api/native-api.rst Outdated
@cmbz cmbz added the FY26 Sprint 21 FY26 Sprint 21 (2026-04-08 - 2026-04-22) label Apr 8, 2026
@github-project-automation github-project-automation Bot moved this from In Review 🔎 to Ready for QA ⏩ in IQSS Dataverse Project Apr 10, 2026
@cmbz cmbz added the FY26 Sprint 22 FY26 Sprint 22 (2026-04-22 - 2026-05-06) label Apr 22, 2026
@cmbz cmbz added the FY26 Sprint 23 FY26 Sprint 23 (2026-05-06 - 2026-05-20) label May 6, 2026
@sekmiller sekmiller self-assigned this May 11, 2026
@sekmiller sekmiller moved this from Ready for QA ⏩ to QA ✅ in IQSS Dataverse Project May 11, 2026
@sekmiller

Copy link
Copy Markdown
Contributor

There's a method in the Dataverse page that makes a list of Dataverses available to be linked to - updateLinkableDataverses. It is still considering all dataverses for this list since linking was a superuser only function. Now that non-superusers are allowed it should be changed to include only those Dataverses that the user has permission on. As is - if the non-super user picks a dataverse from this list that they do not have permission on the link will fail and an "internal error" is posted to the UI

@jp-tosca jp-tosca added the Status: Needs Input Applied to issues in need of input from someone currently unavailable label May 18, 2026
@vera

vera commented May 28, 2026

Copy link
Copy Markdown
Member Author

I'm sorry @vera I found a couple of other things. (possibly because I'm misunderstanding the issue).

No problem, I think this PR is taking a few more review iterations because these UI changes are follow-ups to API changes I introduced, and since we don’t normally work in or use this UI, I’m not very familiar with the code 😄

If I'm a non-superuser I see the link button on every dataset, but not every dataverse (as it stands you need to have admin status on a dataverse in order to see the link button.)

You're right, the logic for the "Link" button showing or not was incorrect.

I've updated the logic now to show the button whenever a logged-in user has the LinkDataverse permission on any eligible collection outside the current collection's tree. As before, the Link dialog handles the case when all of those eligible collections are already linking to the current collection.

Also, if I have linked a dataverse as a non-superuser I am seeing the link button on the linked dataverse, but not the unlink button.

I think that there is no "Unlink Dataverse" button functionality existing in the UI. It's been implemented for datasets, but not collections, as far as I could see.

@cmbz cmbz added the FY26 Sprint 25 FY26 Sprint 25 (2026-06-03 - 2026-06-17) label Jun 3, 2026
@sekmiller

Copy link
Copy Markdown
Contributor

@vera I just picked this up again after being away and focusing on other things. I'm seeing a test failure in MoveIT - testMoveLinkedDataset which seems to be related. Can you please take a look? thanks.

@vera

vera commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

I'm not sure I can reproduce the error:

$ mvn test -Dtest="MoveIT"
...
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 82.33 s -- in edu.harvard.iq.dataverse.api.MoveIT
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

Do you have a specific error message? Thanks!

@pdurbin

pdurbin commented Jun 5, 2026

Copy link
Copy Markdown
Member

@srmanda-cs this is a case where there's a failure in Jenkins (MoveIT) but not GitHub Actions. Strange.

@vera a related issue:

@pdurbin

pdurbin commented Jun 5, 2026

Copy link
Copy Markdown
Member

@vera here's the line number:

Screenshot 2026-06-05 at 6 04 23 AM

@srmanda-cs

Copy link
Copy Markdown
Contributor

@srmanda-cs this is a case where there's a failure in Jenkins (MoveIT) but not GitHub Actions. Strange.

@vera a related issue:

images (3)

@vera

vera commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

Thanks @pdurbin ! That is strange. I've run the test multiple times locally and never got a failure.

Looking at the test code, I suspect it may be a timing-related issue and not necessarily related to a change in this PR. The move with forceMove=true deletes the dataset link, but the DatasetLinkingDataverse record might still exist in the database when the test's next API call (getLinksAfter) is made, if the call is made too quickly.

@srmanda-cs

Copy link
Copy Markdown
Contributor

could you try rebasing the branch on develop and see if that fixes it?

@vera

vera commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

could you try rebasing the branch on develop and see if that fixes it?

Everything seems to be OK now 👍

@srmanda-cs

Copy link
Copy Markdown
Contributor

I triggered a rebuild on Jenkins, let's see.

@srmanda-cs

srmanda-cs commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

New failures
image

testImportDDI – edu.harvard.iq.dataverse.api.DataversesIT
9s
Error
expected: <201> but was: <500>
Stacktrace
org.opentest4j.AssertionFailedError: expected: <201> but was: <500>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:531)
	at edu.harvard.iq.dataverse.api.DataversesIT.testImportDDI(DataversesIT.java:700)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Standard Output
{
    "status": "OK",
    "data": {
        "id": 89,
        "persistentId": "doi:10.5072/FK2/N5OQ7A"
    }
}
{
    "status": "OK",
    "data": {
        "id": 90,
        "persistentId": "doi:10.5072/FK2/ABCD11"
    }
}
{
    "status": "OK",
    "data": {
        "id": 91,
        "persistentId": "doi:10.5072/FK2/ABCD22",
        "releaseCompleted": false
    }
}
{
    "status": "ERROR",
    "message": "Command [DatasetCreate dataset:null] failed: Exception thrown from bean: jakarta.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 5.0.0-B08.payara-p1.v202511041741-60b8f7dca8141786130b4d36785fdcc926eb8cff): org.eclipse.persistence.exceptions.DatabaseException\nInternal Exception: java.sql.SQLException: Connection closed\nError Code: 0\nCall: select lastval()\nQuery: ValueReadQuery(name=\"SEQ_GEN_IDENTITY\" sql=\"select lastval()\")"
}
Standard Error
Jun 05, 2026 1:49:56 PM edu.harvard.iq.dataverse.api.UtilIT createRandomUser
INFO: Creating random test user user6ca4c46a
Jun 05, 2026 1:49:57 PM edu.harvard.iq.dataverse.api.UtilIT getUsernameFromResponse
INFO: Username found in create user response: user6ca4c46a
Jun 05, 2026 1:49:57 PM edu.harvard.iq.dataverse.api.UtilIT getApiTokenFromResponse
INFO: API token found in create user response: 5467f01d-32e0-4e50-99fa-94862705191f
Jun 05, 2026 1:49:57 PM edu.harvard.iq.dataverse.api.UtilIT getAliasFromResponse
INFO: Alias found in create dataverse response: dv1b7db537
Jun 05, 2026 1:49:58 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: Here importDatasetDDIViaNativeApi
Jun 05, 2026 1:49:58 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: /api/dataverses/dv1b7db537/datasets/:importddi
Jun 05, 2026 1:49:58 PM edu.harvard.iq.dataverse.api.DataversesIT testImportDDI
INFO: {
    "status": "OK",
    "data": {
        "id": 89,
        "persistentId": "doi:10.5072/FK2/N5OQ7A"
    }
}
Jun 05, 2026 1:49:58 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: Here importDatasetDDIViaNativeApi
Jun 05, 2026 1:49:58 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: /api/dataverses/dv1b7db537/datasets/:importddi?pid=doi:10.5072/FK2/ABCD11
Jun 05, 2026 1:50:01 PM edu.harvard.iq.dataverse.api.DataversesIT testImportDDI
INFO: {
    "status": "OK",
    "data": {
        "id": 90,
        "persistentId": "doi:10.5072/FK2/ABCD11"
    }
}
Jun 05, 2026 1:50:01 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: Here importDatasetDDIViaNativeApi
Jun 05, 2026 1:50:01 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: /api/dataverses/dv1b7db537/datasets/:importddi?pid=doi:10.5072/FK2/ABCD22&release=yes
Jun 05, 2026 1:50:03 PM edu.harvard.iq.dataverse.api.DataversesIT testImportDDI
INFO: {
    "status": "OK",
    "data": {
        "id": 91,
        "persistentId": "doi:10.5072/FK2/ABCD22",
        "releaseCompleted": false
    }
}
Jun 05, 2026 1:50:03 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: Here importDatasetDDIViaNativeApi
Jun 05, 2026 1:50:03 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetDDIViaNativeApi
INFO: /api/dataverses/dv1b7db537/datasets/:importddi?release=yes
Jun 05, 2026 1:50:05 PM edu.harvard.iq.dataverse.api.DataversesIT testImportDDI
INFO: {
    "status": "ERROR",
    "message": "Command [DatasetCreate dataset:null] failed: Exception thrown from bean: jakarta.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 5.0.0-B08.payara-p1.v202511041741-60b8f7dca8141786130b4d36785fdcc926eb8cff): org.eclipse.persistence.exceptions.DatabaseException\nInternal Exception: java.sql.SQLException: Connection closed\nError Code: 0\nCall: select lastval()\nQuery: ValueReadQuery(name=\"SEQ_GEN_IDENTITY\" sql=\"select lastval()\")"
}

---

testImport – edu.harvard.iq.dataverse.api.DataversesIT
<1s
Error
expected: <201> but was: <403>
Stacktrace
org.opentest4j.AssertionFailedError: expected: <201> but was: <403>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:197)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:150)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:145)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:531)
	at edu.harvard.iq.dataverse.api.DataversesIT.testImport(DataversesIT.java:1122)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Standard Output
{
    "status": "ERROR",
    "message": "Please provide a persistent identifier, either by including it in the JSON, or by using the pid query parameter."
}
{
    "status": "ERROR",
    "message": "Please provide a persistent identifier, either by including it in the JSON, or by using the pid query parameter."
}
{
    "status": "ERROR",
    "message": "Cannot import a dataset that has a PID that doesn't match the server's settings"
}
{
    "status": "ERROR",
    "message": "Persistent identifier doi:10.5072/FK2/ABCD11 already exists in this Dataverse installation."
}
Standard Error
Jun 05, 2026 1:50:32 PM edu.harvard.iq.dataverse.api.UtilIT createRandomUser
INFO: Creating random test user user790c84f0
Jun 05, 2026 1:50:32 PM edu.harvard.iq.dataverse.api.UtilIT getUsernameFromResponse
INFO: Username found in create user response: user790c84f0
Jun 05, 2026 1:50:32 PM edu.harvard.iq.dataverse.api.UtilIT getApiTokenFromResponse
INFO: API token found in create user response: 598ad6fe-f2e9-46ca-817a-36243cd05c3e
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT getAliasFromResponse
INFO: Alias found in create dataverse response: dv676c49a6
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: Here importDatasetViaNativeApi
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: /api/dataverses/dv676c49a6/datasets/:import
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.DataversesIT testImport
INFO: {
    "status": "ERROR",
    "message": "Please provide a persistent identifier, either by including it in the JSON, or by using the pid query parameter."
}
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: Here importDatasetViaNativeApi
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: /api/dataverses/dv676c49a6/datasets/:import?release=yes
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.DataversesIT testImport
INFO: {
    "status": "ERROR",
    "message": "Please provide a persistent identifier, either by including it in the JSON, or by using the pid query parameter."
}
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: Here importDatasetViaNativeApi
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: /api/dataverses/dv676c49a6/datasets/:import?pid=doi:10.5073/FK2/ABCD11
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.DataversesIT testImport
INFO: {
    "status": "ERROR",
    "message": "Cannot import a dataset that has a PID that doesn't match the server's settings"
}
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: Here importDatasetViaNativeApi
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.UtilIT importDatasetViaNativeApi
INFO: /api/dataverses/dv676c49a6/datasets/:import?pid=doi:10.5072/FK2/ABCD11
Jun 05, 2026 1:50:33 PM edu.harvard.iq.dataverse.api.DataversesIT testImport
INFO: {
    "status": "ERROR",
    "message": "Persistent identifier doi:10.5072/FK2/ABCD11 already exists in this Dataverse installation."
}

@vera

vera commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

Strange, I ran DataversesIT locally and I'm not seeing those two test cases fail.

image

I also ran DataversesIT#testImportDDI and DataversesIT#testImport separately and they passed. I can try and take a closer look later but right now I'm unsure what's going on. Btw the CI pipeline seems to be passing as well, right? Is it another case of flakey tests?

@pdurbin

pdurbin commented Jun 11, 2026

Copy link
Copy Markdown
Member

Yeah, probably flaky tests. All API tests are passing in both CI systems as of this writing.

@sekmiller sekmiller merged commit 16ea4cc into IQSS:develop Jul 8, 2026
16 checks passed
@github-project-automation github-project-automation Bot moved this from QA ✅ to Merged 🚀 in IQSS Dataverse Project Jul 8, 2026
@github-project-automation github-project-automation Bot moved this from 🔍 Interest to Done in Recherche Data Gouv Jul 8, 2026
@pdurbin pdurbin moved this from Merged 🚀 to Done 🧹 in IQSS Dataverse Project Jul 8, 2026
@pdurbin pdurbin added this to the 6.12 milestone Jul 8, 2026
@pdurbin pdurbin moved this from Done 🧹 to Merged 🚀 in IQSS Dataverse Project Jul 8, 2026
@pdurbin pdurbin assigned pdurbin and unassigned sekmiller and vera Jul 8, 2026
@pdurbin

pdurbin commented Jul 9, 2026

Copy link
Copy Markdown
Member

Merging this PR may have caused complications with this one:

@stevenwinship found two bugs in this PR (#12250), which he has fixed in the PR above. See this commit: fe2c8ad

@pdurbin pdurbin moved this from Merged 🚀 to Done 🧹 in IQSS Dataverse Project Jul 9, 2026
@pdurbin pdurbin removed their assignment Jul 9, 2026
pdurbin added a commit to beepsoft/dataverse that referenced this pull request Jul 10, 2026
Conflicts with IQSS#12250:
src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FY26 Sprint 20 FY26 Sprint 20 (2026-03-26 - 2026-04-08) FY26 Sprint 21 FY26 Sprint 21 (2026-04-08 - 2026-04-22) FY26 Sprint 22 FY26 Sprint 22 (2026-04-22 - 2026-05-06) FY26 Sprint 23 FY26 Sprint 23 (2026-05-06 - 2026-05-20) FY26 Sprint 24 FY26 Sprint 24 (2026-05-20 - 2026-06-03) FY26 Sprint 25 FY26 Sprint 25 (2026-06-03 - 2026-06-17)

Projects

Status: Done 🧹
Status: Done

Development

Successfully merging this pull request may close these issues.

Dataverse linking still requires superuser despite introduction of LinkDataverse permission

9 participants