Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7ebb100
placeholder
jp-tosca Nov 6, 2025
4c9044a
Endpoints work but need polish.
jp-tosca Nov 10, 2025
9460189
Merge remote-tracking branch 'origin/develop' into 11914-set-template…
jp-tosca Nov 12, 2025
097e972
pr notes
jp-tosca Nov 12, 2025
5d7379e
Merge remote-tracking branch 'origin/develop' into 11914-set-template…
jp-tosca Nov 18, 2025
d5aab83
Initial test
jp-tosca Nov 18, 2025
31a2078
Changes to show the ID of the created template
jp-tosca Nov 20, 2025
76ff3e0
Changes to the set default endpoint
jp-tosca Nov 20, 2025
8e72fea
Flush entity manager after setting default template in CreateTemplate…
jp-tosca Nov 20, 2025
17bb055
Update success messages for removing default dataset template
jp-tosca Nov 20, 2025
04fcb16
Implement RemoveDefaultDatasetCommand and update endpoint to use it f…
jp-tosca Nov 20, 2025
72237fb
Add tests for setting and removing default template in DataversesIT
jp-tosca Nov 21, 2025
a86a9ab
Fix permission description in API documentation and method signatures…
jp-tosca Nov 24, 2025
924da37
Merge branch 'develop' into 11914-set-template-default
jp-tosca Nov 24, 2025
fd645d8
Merge branch 'develop' into 11914-set-template-default
sekmiller Jan 8, 2026
1599767
#11914 fix merge conflicts
sekmiller Jan 8, 2026
e57568e
#11914 return default true for test
sekmiller Jan 8, 2026
1649109
#11914 fix find template and tests
sekmiller Jan 9, 2026
93fcb43
#11914 update command name for clarity
sekmiller Jan 9, 2026
bf10460
Merge branch 'develop' into 11914-set-template-default
sekmiller Jan 12, 2026
671901b
Merge branch 'develop' into 11914-set-template-default
sekmiller Jan 14, 2026
dc1c200
Update src/main/java/edu/harvard/iq/dataverse/engine/command/impl/Set…
sekmiller Jan 14, 2026
eef6ce0
Update src/main/java/edu/harvard/iq/dataverse/engine/command/impl/Rem…
sekmiller Jan 14, 2026
2fd494f
#11914 change path
sekmiller Jan 14, 2026
23253fb
Update native-api.rst
sekmiller Jan 14, 2026
f668cfa
Update native-api.rst
sekmiller Jan 14, 2026
53447e0
Merge branch 'develop' into 11914-set-template-default
sekmiller Jan 14, 2026
3e04cee
fix docs
stevenwinship Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/release-notes/11914-set-template-default-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## New Endpoint: POST `/dataverses/{id}/template/default/{templateId}`
Comment thread
stevenwinship marked this conversation as resolved.

A new endpoint has been implemented to set the default template to a given dataverse collection.

### Functionality
- Sets the default template of the given dataverse collection.
- You must have add dataset permission in the collection in order to use this endpoint.
Comment thread
jp-tosca marked this conversation as resolved.
Outdated

## New Endpoint: DELETE `/dataverses/{id}/template/default`
Comment thread
jp-tosca marked this conversation as resolved.
Comment thread
stevenwinship marked this conversation as resolved.
Comment thread
stevenwinship marked this conversation as resolved.

A new endpoint has been implemented to remove the default template to a given dataverse collection.

### Functionality
- Removes the default template of the given dataverse collection.
- You must have add dataset permission in the collection in order to use this endpoint.
Comment thread
jp-tosca marked this conversation as resolved.
Outdated
44 changes: 44 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,50 @@ public Response createTemplate(@Context ContainerRequestContext crc, String body
return e.getResponse();
}
}

@POST
@AuthRequired
@Path("{identifier}/templates/default/{templateId}")
public Response setDefaultTemplate(@Context ContainerRequestContext crc, String body,
Comment thread
jp-tosca marked this conversation as resolved.
Outdated
@PathParam("identifier") String dvId,
@PathParam("templateId") Long templateId) {

try {
System.out.println(
"Dataverse API: setting default template for dataverse " + dvId + " to template " + templateId);
Comment thread
jp-tosca marked this conversation as resolved.
Outdated
Dataverse dataverse = findDataverseOrDie(dvId);

Template templateToSet = dataverse.getTemplates().stream()
.filter(t -> Objects.equals(t.getId(), templateId))
.findFirst()
.orElse(null);
if (templateToSet == null) {
return error(Status.NOT_FOUND, "Template with id " + templateId + " not found for dataverse " + dvId);
}
dataverse.setDefaultTemplate(templateToSet);
Comment thread
jp-tosca marked this conversation as resolved.
Outdated
return ok(jsonTemplates(execCommand(
new ListDataverseTemplatesCommand(createDataverseRequest(getRequestUser(crc)), dataverse))));
} catch (WrappedResponse e) {
return e.getResponse();
}
}
Comment thread
jp-tosca marked this conversation as resolved.

@DELETE
@AuthRequired
@Path("{identifier}/templates/default")
public Response removeDefaultTemplate(@Context ContainerRequestContext crc, String body,
Comment thread
jp-tosca marked this conversation as resolved.
Outdated
@PathParam("identifier") String dvId) {
try {
Dataverse dataverse = findDataverseOrDie(dvId);
dataverse.setDefaultTemplate(null);
Comment thread
jp-tosca marked this conversation as resolved.
Outdated
return ok(jsonTemplates(execCommand(
new ListDataverseTemplatesCommand(createDataverseRequest(getRequestUser(crc)), dataverse))));
} catch (WrappedResponse e) {
return e.getResponse();
}
}
Comment thread
jp-tosca marked this conversation as resolved.



@GET
@AuthRequired
Expand Down
256 changes: 134 additions & 122 deletions src/test/java/edu/harvard/iq/dataverse/api/DataversesIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -2594,128 +2594,140 @@ public void testUpdateInputLevelDisplayOnCreateOverride() {
}

@Test
public void testCreateAndGetTemplates() throws JsonParseException {
Response createUserResponse = UtilIT.createRandomUser();
String apiToken = UtilIT.getApiTokenFromResponse(createUserResponse);
String username = UtilIT.getUsernameFromResponse(createUserResponse);

Response createSecondUserResponse = UtilIT.createRandomUser();
String secondApiToken = UtilIT.getApiTokenFromResponse(createSecondUserResponse);
String secondUsername = UtilIT.getUsernameFromResponse(createSecondUserResponse);


/*
We need to make this a non-inherited metadatablocks so the get template will only get templates from current dv
*/

Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode());
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);

String newName = "New Test Dataverse Name";
String newAffiliation = "New Test Dataverse Affiliation";
String newDataverseType = Dataverse.DataverseType.TEACHING_COURSES.toString();
String[] newContactEmails = new String[]{"new_email@dataverse.com"};
String[] newInputLevelNames = new String[]{"geographicCoverage"};
String[] newFacetIds = new String[]{"contributorName"};
String[] newMetadataBlockNames = new String[]{"citation", "geospatial", "biomedical"};

//Giving the new Dataverse updated metadatablocks so that it will not inherit templates
Response updateDataverseResponse = UtilIT.updateDataverse(
dataverseAlias, dataverseAlias, newName, newAffiliation, newDataverseType, newContactEmails, newInputLevelNames,
null, newMetadataBlockNames, apiToken,
Boolean.FALSE, Boolean.FALSE, null
);

updateDataverseResponse.then().assertThat()
.statusCode(OK.getStatusCode());

// Create a template

String jsonString = """
{
"name": "Dataverse template",
"isDefault": true,
"fields": [
{
"typeName": "author",
"value": [
{
"authorName": {
"typeName": "authorName",
"value": "Belicheck, Bill"
},
"authorAffiliation": {
"typeName": "authorIdentifierScheme",
"value": "ORCID"
}
}
]
}
],
"instructions": [
{
"instructionField": "author",
"instructionText": "The author data"
}
]
}
""";

Response createTemplateResponse = UtilIT.createTemplate(
dataverseAlias,
jsonString,
apiToken
);

createTemplateResponse.then().assertThat().statusCode(OK.getStatusCode())
.body("data.name", equalTo("Dataverse template"))
.body("data.isDefault", equalTo(true))
.body("data.usageCount", equalTo(0))
.body("data.termsOfUseAndAccess.license.name", equalTo("CC0 1.0"))
.body("data.datasetFields.citation.fields.size()", equalTo(1))
.body("data.instructions.size()", equalTo(1))
.body("data.instructions[0].instructionField", equalTo("author"))
.body("data.instructions[0].instructionText", equalTo("The author data"))
.body("data.dataverseAlias", equalTo(dataverseAlias));

// Template creation should fail if the user lacks dataverse edit permissions

createTemplateResponse = UtilIT.createTemplate(
dataverseAlias,
jsonString,
secondApiToken
);
createTemplateResponse.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());

// Get templates

Response getTemplateResponse = UtilIT.getTemplates(dataverseAlias, apiToken);
getTemplateResponse.then().assertThat().statusCode(OK.getStatusCode())
.body("data.size()", equalTo(1))
.body("data[0].name", equalTo("Dataverse template"))
.body("data[0].isDefault", equalTo(true))
.body("data[0].usageCount", equalTo(0))
.body("data[0].termsOfUseAndAccess.license.name", equalTo("CC0 1.0"))
.body("data[0].datasetFields.citation.fields.size()", equalTo(1))
.body("data[0].instructions.size()", equalTo(1))
.body("data[0].instructions[0].instructionField", equalTo("author"))
.body("data[0].instructions[0].instructionText", equalTo("The author data"))
.body("data[0].dataverseAlias", equalTo(dataverseAlias));

// Templates retrieval should fail if a secondary user lacks dataset creation permissions

getTemplateResponse = UtilIT.getTemplates(dataverseAlias, secondApiToken);
getTemplateResponse.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());

// Templates retrieval should succeed if the secondary user has dataset creation permissions

UtilIT.setSuperuserStatus(username, true);
Response grantRoleResponse = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR, "@" + secondUsername, apiToken);
grantRoleResponse.then().assertThat().statusCode(OK.getStatusCode());

getTemplateResponse = UtilIT.getTemplates(dataverseAlias, secondApiToken);
getTemplateResponse.then().assertThat().statusCode(OK.getStatusCode());
public void testCreateAndGetTemplates() throws JsonParseException {
Response createUserResponse = UtilIT.createRandomUser();
String apiToken = UtilIT.getApiTokenFromResponse(createUserResponse);
String username = UtilIT.getUsernameFromResponse(createUserResponse);

Response createSecondUserResponse = UtilIT.createRandomUser();
String secondApiToken = UtilIT.getApiTokenFromResponse(createSecondUserResponse);
String secondUsername = UtilIT.getUsernameFromResponse(createSecondUserResponse);

/*
* We need to make this a non-inherited metadatablocks so the get template will
* only get templates from current dv
*/

Response createDataverseResponse = UtilIT.createRandomDataverse(apiToken);
createDataverseResponse.then().assertThat().statusCode(CREATED.getStatusCode());
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);

String newName = "New Test Dataverse Name";
String newAffiliation = "New Test Dataverse Affiliation";
String newDataverseType = Dataverse.DataverseType.TEACHING_COURSES.toString();
String[] newContactEmails = new String[] { "new_email@dataverse.com" };
String[] newInputLevelNames = new String[] { "geographicCoverage" };
String[] newFacetIds = new String[] { "contributorName" };
String[] newMetadataBlockNames = new String[] { "citation", "geospatial", "biomedical" };

// Giving the new Dataverse updated metadatablocks so that it will not inherit
// templates
Response updateDataverseResponse = UtilIT.updateDataverse(
dataverseAlias, dataverseAlias, newName, newAffiliation, newDataverseType, newContactEmails,
newInputLevelNames,
null, newMetadataBlockNames, apiToken,
Boolean.FALSE, Boolean.FALSE, null);

updateDataverseResponse.then().assertThat()
.statusCode(OK.getStatusCode());

// Create a template

String jsonString = """
{
"name": "Dataverse template",
"isDefault": false,
"fields": [
{
"typeName": "author",
"value": [
{
"authorName": {
"typeName": "authorName",
"value": "Belicheck, Bill"
},
"authorAffiliation": {
"typeName": "authorIdentifierScheme",
"value": "ORCID"
}
}
]
}
],
"instructions": [
{
"instructionField": "author",
"instructionText": "The author data"
}
]
}
""";

Response createTemplateResponse = UtilIT.createTemplate(
dataverseAlias,
jsonString,
apiToken);

createTemplateResponse.then().assertThat().statusCode(OK.getStatusCode())
.body("data.name", equalTo("Dataverse template"))
.body("data.isDefault", equalTo(false))
.body("data.usageCount", equalTo(0))
.body("data.termsOfUseAndAccess.license.name", equalTo("CC0 1.0"))
.body("data.datasetFields.citation.fields.size()", equalTo(1))
.body("data.instructions.size()", equalTo(1))
.body("data.instructions[0].instructionField", equalTo("author"))
.body("data.instructions[0].instructionText", equalTo("The author data"))
.body("data.dataverseAlias", equalTo(dataverseAlias));

Long templateId = createTemplateResponse.then().extract().path("data.id");



Response setDefaultResp = UtilIT.setDefaultTemplate(dataverseAlias, templateId, secondApiToken);
setDefaultResp.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());

setDefaultResp = UtilIT.setDefaultTemplate(dataverseAlias, templateId, apiToken);
setDefaultResp.then().assertThat().statusCode(OK.getStatusCode());

// Template creation should fail if the user lacks dataverse edit permissions

createTemplateResponse = UtilIT.createTemplate(
dataverseAlias,
jsonString,
secondApiToken);
createTemplateResponse.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());

// Get templates

Response getTemplateResponse = UtilIT.getTemplates(dataverseAlias, apiToken);
getTemplateResponse.then().assertThat().statusCode(OK.getStatusCode())
.body("data.size()", equalTo(1))
.body("data[0].name", equalTo("Dataverse template"))
.body("data[0].isDefault", equalTo(true))
.body("data[0].usageCount", equalTo(0))
.body("data[0].termsOfUseAndAccess.license.name", equalTo("CC0 1.0"))
.body("data[0].datasetFields.citation.fields.size()", equalTo(1))
.body("data[0].instructions.size()", equalTo(1))
.body("data[0].instructions[0].instructionField", equalTo("author"))
.body("data[0].instructions[0].instructionText", equalTo("The author data"))
.body("data[0].dataverseAlias", equalTo(dataverseAlias));

// Templates retrieval should fail if a secondary user lacks dataset creation
// permissions

getTemplateResponse = UtilIT.getTemplates(dataverseAlias, secondApiToken);
getTemplateResponse.then().assertThat().statusCode(UNAUTHORIZED.getStatusCode());

// Templates retrieval should succeed if the secondary user has dataset creation
// permissions

UtilIT.setSuperuserStatus(username, true);
Response grantRoleResponse = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR,
"@" + secondUsername, apiToken);
grantRoleResponse.then().assertThat().statusCode(OK.getStatusCode());

getTemplateResponse = UtilIT.getTemplates(dataverseAlias, secondApiToken);
getTemplateResponse.then().assertThat().statusCode(OK.getStatusCode());
}

@Test
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -5054,6 +5054,22 @@ public static Response getTemplates(String dataverseAlias, String apiToken) {
.header(API_TOKEN_HTTP_HEADER, apiToken)
.get("/api/dataverses/" + dataverseAlias + "/templates");
}

public static Response setDefaultTemplate(String dataverseAlias, Long templateId, String apiToken) {
return given()
.contentType(ContentType.JSON)
.header(API_TOKEN_HTTP_HEADER, apiToken)
.post("/api/dataverses/" + dataverseAlias + "/templates/default/" + templateId);
}

public static Response removeDefaultTemplate(String dataverseAlias, String apiToken) {
return given()
.contentType(ContentType.JSON)
.header(API_TOKEN_HTTP_HEADER, apiToken)
.delete("/api/dataverses/" + dataverseAlias + "/templates/default");
}



/**
* Gets the tool URL for a dataset with optional parameters
Expand Down
Loading