Skip to content

feat: prevent draft creation for unchanged dataset metadata - #12452

Open
AnneGerlach wants to merge 3 commits into
IQSS:developfrom
AnneGerlach:feat/12376-skip-empty-drafts-for-unchanged-metadata
Open

feat: prevent draft creation for unchanged dataset metadata#12452
AnneGerlach wants to merge 3 commits into
IQSS:developfrom
AnneGerlach:feat/12376-skip-empty-drafts-for-unchanged-metadata

Conversation

@AnneGerlach

Copy link
Copy Markdown

What this PR does / why we need it:

🎫 Closes: #12376

This PR prevents Dataverse from creating or updating a draft version when metadata uploaded via PUT /api/datasets/{id}/versions/:draft is semantically identical to the existing dataset version.

Previously, repeated uploads of unchanged metadata created unnecessary draft versions. This was particularly problematic for automated synchronization workflows, which had to implement their own comparison logic before submitting updates.

The implementation reuses the existing DatasetVersionDifference logic to detect unchanged metadata and terms updates and skips draft creation or update when no effective changes are detected.

Changes

  • Added a no-op check before draft creation and draft update in PUT /api/datasets/{id}/versions/:draft

  • Reused the existing DatasetVersionDifference implementation to detect unchanged metadata and terms updates

  • Skipped draft creation/update and returned the existing dataset version when no effective changes are detected

  • Added integration tests covering both scenarios:

    • no draft exists
    • draft already exists

Suggestions on how to test this:

Run tests:

mvn test -Dtest=DatasetsTest,DatasetsIT

or tests manually:

Test no-op metadata update

  1. Login as dataverseAdmin
  2. Create and publish a dataset via the UI
  3. Generate an API token and copy the dataset PID (export those: API_TOKEN and PID)
# Download latest version metadata
curl -H "X-Dataverse-key: $API_TOKEN" \
"http://localhost:8080/api/datasets/:persistentId?persistentId=$PID" \
| jq '.data.latestVersion' > latestVersion.json
# Re-upload unchanged metadata
curl -X PUT \
-H "X-Dataverse-key: $API_TOKEN" \
-H "Content-Type: application/json" \
--data @latestVersion.json \
"http://localhost:8080/api/datasets/:persistentId/versions/:draft?persistentId=$PID"
# Verify no draft was created
curl -H "X-Dataverse-key: $API_TOKEN" \
"http://localhost:8080/api/datasets/:persistentId/versions?persistentId=$PID" \
| jq

Test changed metadata

# Modify metadata in the file e.g.
nano latestVersion.json
# Upload changed metadata
curl -X PUT \
-H "X-Dataverse-key: $API_TOKEN" \
-H "Content-Type: application/json" \
--data @latestVersion.json \
"http://localhost:8080/api/datasets/:persistentId/versions/:draft?persistentId=$PID"
# Verify draft now exists
curl -H "X-Dataverse-key: $API_TOKEN" \
"http://localhost:8080/api/datasets/:persistentId/versions?persistentId=$PID" \
| jq

Special notes for your reviewer:

Thanks for reviewing 🌻 (and special thanks to @vera for reviewing beforehand 🌷 )

@pdurbin pdurbin moved this to Ready for Triage in IQSS Dataverse Project Jun 11, 2026
@pdurbin pdurbin moved this from Ready for Triage to Ready for Review ⏩ in IQSS Dataverse Project Jun 16, 2026
@cmbz cmbz added FY26 Sprint 25 FY26 Sprint 25 (2026-06-03 - 2026-06-17) FY26 Sprint 26 FY26 Sprint 26 (2026-06-17 - 2026-07-01) labels Jun 17, 2026
@stevenwinship stevenwinship self-assigned this Jun 26, 2026
@stevenwinship stevenwinship moved this from Ready for Review ⏩ to In Review 🔎 in IQSS Dataverse Project Jun 26, 2026
}

boolean updateDraft = ds.getLatestVersion().isDraft();
DatasetVersion latestVersion = ds.getLatestVersion();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a release note explaining the change

@stevenwinship

stevenwinship commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@AnneGerlach When I tried this, changing the dataset contact email, it was treated as a no-op

Before:
"datasetContactEmail": {
"typeName": "datasetContactEmail",
"multiple": false,
"typeClass": "primitive",
"value": "spruce@mailinator.com"
}

Here is what I sent:
"datasetContactEmail": {
"typeName": "datasetContactEmail",
"multiple": false,
"typeClass": "primitive",
"value": "spruce1@mailinator.com"
}

Nothing changed when I get the draft:
"datasetContactEmail": {
"typeName": "datasetContactEmail",
"multiple": false,
"typeClass": "primitive",
"value": "spruce@mailinator.com"
}

Without this PR the email value gets updated
"value": "spruce1@mailinator.com"

@cmbz cmbz added the FY27 Sprint 1 FY27 Sprint 1 (2026-07-01 - 2026-07-15) label Jul 1, 2026
@cmbz cmbz added the FY27 Sprint 2 FY27 Sprint 2 (2026-07-15 - 2026-07-29) label Jul 15, 2026
@cmbz cmbz added the FY27 Sprint 3 FY27 Sprint 3 (2026-07-29 - 2026-08-12) label Jul 29, 2026
@pdurbin pdurbin assigned vera and unassigned AnneGerlach Aug 5, 2026
@cmbz cmbz added the FY27 Sprint 4 FY27 Sprint 4 (2026-08-12 - 2026-08-26) label Aug 12, 2026
@cmbz cmbz added the FY27 Sprint 5 FY27 Sprint 5 (2026-08-26 - 2026-09-09) label Aug 26, 2026
@vera

vera commented Sep 9, 2026

Copy link
Copy Markdown
Member

@AnneGerlach When I tried this, changing the dataset contact email, it was treated as a no-op

You're right. It seems this approach of simply re-using the DatasetVersionDifference implementation to detect no-op updates doesn't work.

Since DatasetVersionDifference uses display values and not the raw values for comparison, this fails to detect changes for a couple of specific field types:

  • Compound child fields whose display format hides their actual value, such as #EMAIL (e.g. datasetContactEmail)
  • Primitive fields with fieldType = "email", which are completely skipped during the comparison
  • Primitive fields when only the letter case is changed, because primitive comparisons are case-insensitive
  • Fields with display sanitization or escaping, where values become identical after sanitization/escaping

I've added examples for these to the testUpdateDatasetMetadataNoOp, so the IT tests are going to fail now.

To fix this, the change detection needs to be reworked to be based on raw values, not display values.

@cmbz cmbz added the FY27 Sprint 6 FY27 Sprint 6 (2026-09-09 - 2026-09-23) label Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FY26 Sprint 25 FY26 Sprint 25 (2026-06-03 - 2026-06-17) FY26 Sprint 26 FY26 Sprint 26 (2026-06-17 - 2026-07-01) FY27 Sprint 1 FY27 Sprint 1 (2026-07-01 - 2026-07-15) FY27 Sprint 2 FY27 Sprint 2 (2026-07-15 - 2026-07-29) FY27 Sprint 3 FY27 Sprint 3 (2026-07-29 - 2026-08-12) FY27 Sprint 4 FY27 Sprint 4 (2026-08-12 - 2026-08-26) FY27 Sprint 5 FY27 Sprint 5 (2026-08-26 - 2026-09-09) FY27 Sprint 6 FY27 Sprint 6 (2026-09-09 - 2026-09-23) Original size: 3

Projects

Status: In Review 🔎

Development

Successfully merging this pull request may close these issues.

5 participants