Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5b37919
Merge develop without revert
Saixel Mar 6, 2025
9518acd
Merge remote-tracking branch 'origin/develop' into 10476-display-on-c…
Saixel Mar 6, 2025
404eb0b
Restore displayOnCreate field option changes
Saixel Mar 6, 2025
97b454d
Add displayOnCreate option for dataset field types
Saixel Mar 6, 2025
3c4e5cc
Update DataversesIT test to modify metadata block listing parameter
Saixel Mar 6, 2025
190f604
Update native API documentation for displayOnCreate field option
Saixel Mar 6, 2025
15fc52c
Implement null support for displayOnCreate field option
Saixel Mar 6, 2025
df39fb4
fix: make displayOnCreate nullable in DatasetFieldType
Saixel Mar 6, 2025
944911b
setDisplayOnCreate astroInstrument back to false #10476
pdurbin Mar 6, 2025
1db344b
Handle null displayOnCreate in MetadataBlock and JsonPrinter
Saixel Mar 6, 2025
848e391
Merge branch '10476-display-on-create-field-option-new' of https://gi…
Saixel Mar 6, 2025
973be9d
Refine displayOnCreate logic in JsonPrinter
Saixel Mar 6, 2025
fe5abdb
Update displayOnCreate method call in metadataFragment.xhtml
Saixel Mar 6, 2025
21ba2c2
Add getter method for displayOnCreate in DatasetFieldType
Saixel Mar 6, 2025
f0f91ef
Merge branch 'develop' into 10476-display-on-create-field-option-new
sekmiller Mar 10, 2025
ad3e92e
Improve metadata block field selection logic for display and required…
Saixel Mar 11, 2025
41bae39
#10476 add "override" test.
sekmiller Mar 11, 2025
080cf46
Fix input levels update preserving inherited metadata blocks
Saixel Mar 11, 2025
b9c6aa4
Merge branch '10476-display-on-create-field-option-new' of https://gi…
Saixel Mar 11, 2025
5c16c80
Add displayOnCreate field to dataset schema and update field display …
Saixel Mar 11, 2025
c1f402b
#10476 fix command test
sekmiller Mar 12, 2025
5e16582
#10476 update tests json printer
sekmiller Mar 12, 2025
67bbd37
#10476 code/test cleanup
sekmiller Mar 12, 2025
56cfa92
#10476 fix DataversesIT
sekmiller Mar 12, 2025
f804fa3
Merge branch 'develop' into 10476-display-on-create-field-option-new
Saixel Mar 13, 2025
e31db3d
Remove default displayOnCreate setting for required fields in Dataver…
Saixel Mar 13, 2025
4085c14
Merge branch 'develop' into 10476-display-on-create-field-option-new
Saixel Mar 13, 2025
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
6 changes: 6 additions & 0 deletions doc/release-notes/10476-display-on-create-field-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
New feature: Collection administrators can now configure which metadata fields appear during dataset creation through the `displayOnCreate` property, even when fields are not required. This provides greater control over metadata visibility and can help improve metadata completeness.

- The feature is currently available through the API endpoint `/api/dataverses/{alias}/inputLevels`
- UI implementation will be available in a future release [#11221](https://github.com/IQSS/dataverse/issues/11221)

For more information, see the [API Guide](https://guides.dataverse.org/en/latest/api/native-api.html#update-collection-input-levels) and issues [#10476](https://github.com/IQSS/dataverse/issues/10476) and [#11224](https://github.com/IQSS/dataverse/pull/11224).
3 changes: 3 additions & 0 deletions doc/sphinx-guides/source/_static/api/dataset-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
},
"typeName": {
"type": "string"
},
"displayOnCreate": {
"type": "boolean"
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1116,20 +1116,27 @@ This endpoint expects a JSON with the following format::
{
"datasetFieldTypeName": "datasetFieldTypeName1",
"required": true,
"include": true
"include": true,
"displayOnCreate": null
},
{
"datasetFieldTypeName": "datasetFieldTypeName2",
"required": true,
"include": true
"include": true,
"displayOnCreate": true
}
]

.. note::
Required fields will always be displayed regardless of their displayOnCreate setting, as this is necessary for dataset creation.
When displayOnCreate is null, the field's default display behavior is used.

Parameters:

- ``datasetFieldTypeName``: Name of the metadata field
- ``required``: Whether the field is required (boolean)
- ``include``: Whether the field is included (boolean)
- ``displayOnCreate`` (optional): Whether the field is displayed during dataset creation, even when not required (boolean)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think the code currently allows this to be optional - line 806 in Dataverses sets it to false by default. I've tried to handle this in #11330, which should be ~ up to date with this PR, but still checking to see if I've broken tests. (I also added a test updating an inputLevel with no displayOnCreate value in that PR).

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.

for the doc can we just say that it default to false if omitted?

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.

I'm the dummy who wrote the migration script and didn't default the column to false. Should I add another update? Are we ready for more flyway migration errors?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would be a backward incompatibility - if you don't include displayOnCreate you will override any true on the datasetfieldtype itself. If that's OK, then yes a doc change could address it.

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.

You're right. didn't think of that.
\

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.

Would it be better if it's omitted that the value would come from the table for that datasetFieldType?


.. code-block:: bash
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.

by the way - the "fully expanded" example down below here has bad data - "required": true, "included": false


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,12 @@ private Predicate buildFieldPresentInDataversePredicate(Dataverse dataverse, boo
criteriaBuilder.isTrue(datasetFieldTypeInputLevelJoin.get("required"))
);

// Predicate for displayOnCreate in input level
Predicate displayOnCreateInputLevelPredicate = criteriaBuilder.and(
criteriaBuilder.equal(datasetFieldTypeRoot, datasetFieldTypeInputLevelJoin.get("datasetFieldType")),
criteriaBuilder.isTrue(datasetFieldTypeInputLevelJoin.get("displayOnCreate"))
);

// Create a subquery to check for the absence of a specific DataverseFieldTypeInputLevel.
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<DataverseFieldTypeInputLevel> subqueryRoot = subquery.from(DataverseFieldTypeInputLevel.class);
Expand All @@ -963,10 +969,19 @@ private Predicate buildFieldPresentInDataversePredicate(Dataverse dataverse, boo
// Otherwise, use an always-true predicate (conjunction).
Predicate displayedOnCreatePredicate = onlyDisplayedOnCreate
? criteriaBuilder.or(
criteriaBuilder.or(
// 1. Field marked as displayOnCreate in input level
displayOnCreateInputLevelPredicate,

// 2. Field without input level that is marked as displayOnCreate or required
criteriaBuilder.and(
hasNoInputLevelPredicate,
Copy link
Copy Markdown
Member

@qqmyers qqmyers Mar 7, 2025

Choose a reason for hiding this comment

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

Related to making displayOnCrate nullable in the input level as noted below. If that is what's needed, then here:
With null displayOnCreate being possible, does this have to change to ~hasNoInputLevelPredicateWithDisplayOnCreateNotNull ?

criteriaBuilder.or(
criteriaBuilder.isTrue(datasetFieldTypeRoot.get("displayOnCreate")),
fieldRequiredInTheInstallation
)
),

// 3. Field required by input level
requiredAsInputLevelPredicate
)
: criteriaBuilder.conjunction();
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/edu/harvard/iq/dataverse/DatasetFieldType.java
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,18 @@ public void setValidationFormat(String validationFormat) {
* Determines whether this field type is displayed in the form when creating
* the Dataset (or only later when editing after the initial creation).
*/
private boolean displayOnCreate;
@Column(name = "displayoncreate", nullable = true)
private Boolean displayOnCreate;

public boolean isDisplayOnCreate() {
public Boolean isDisplayOnCreate() {
return displayOnCreate;
}

public void setDisplayOnCreate(boolean displayOnCreate) {
public Boolean getDisplayOnCreate() {
return displayOnCreate;
}

public void setDisplayOnCreate(Boolean displayOnCreate) {
this.displayOnCreate = displayOnCreate;
}

Expand Down
1 change: 1 addition & 0 deletions src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,7 @@ private void updateDatasetFieldInputLevels() {
if (dsf != null){
// Yes, call "setInclude"
dsf.setInclude(oneDSFieldTypeInputLevel.isInclude());
dsf.getDatasetFieldType().setDisplayOnCreate(oneDSFieldTypeInputLevel.isDisplayOnCreate());
// remove from hash
mapDatasetFields.remove(oneDSFieldTypeInputLevel.getDatasetFieldType().getId());
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/Dataverse.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,12 @@ public boolean isDatasetFieldTypeInInputLevels(Long datasetFieldTypeId) {
.anyMatch(inputLevel -> inputLevel.getDatasetFieldType().getId().equals(datasetFieldTypeId));
}

public boolean isDatasetFieldTypeDisplayOnCreateAsInputLevel(Long datasetFieldTypeId) {
return dataverseFieldTypeInputLevels.stream()
.anyMatch(inputLevel -> inputLevel.getDatasetFieldType().getId().equals(datasetFieldTypeId)
&& inputLevel.isDisplayOnCreate());
}

public Template getDefaultTemplate() {
return defaultTemplate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,16 @@ public class DataverseFieldTypeInputLevel implements Serializable {
private DatasetFieldType datasetFieldType;
private boolean include;
private boolean required;
private boolean displayOnCreate;

public DataverseFieldTypeInputLevel () {}

public DataverseFieldTypeInputLevel (DatasetFieldType fieldType, Dataverse dataverse, boolean required, boolean include) {
public DataverseFieldTypeInputLevel (DatasetFieldType fieldType, Dataverse dataverse, boolean required, boolean include, boolean displayOnCreate) {
this.datasetFieldType = fieldType;
this.dataverse = dataverse;
this.required = required;
this.include = include;
this.displayOnCreate = displayOnCreate;
}

public Long getId() {
Expand Down Expand Up @@ -115,6 +117,14 @@ public void setRequired(boolean required) {
this.required = required;
}

public boolean isDisplayOnCreate() {
return displayOnCreate;
}

public void setDisplayOnCreate(boolean displayOnCreate) {
this.displayOnCreate = displayOnCreate;
}

@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public void delete(DataverseFieldTypeInputLevel dataverseFieldTypeInputLevel) {
cache.invalidate();
}

public void deleteFacetsFor(Dataverse d) {
public void deleteDataverseFieldTypeInputLevelFor(Dataverse d) {
em.createNamedQuery("DataverseFieldTypeInputLevel.removeByOwnerId")
.setParameter("ownerId", d.getId())
.executeUpdate();
Expand All @@ -117,4 +117,13 @@ public void create(DataverseFieldTypeInputLevel dataverseFieldTypeInputLevel) {
em.persist(dataverseFieldTypeInputLevel);
}

public DataverseFieldTypeInputLevel save(DataverseFieldTypeInputLevel inputLevel) {
if (inputLevel.getId() == null) {
em.persist(inputLevel);
return inputLevel;
} else {
return em.merge(inputLevel);
}
}

}
132 changes: 79 additions & 53 deletions src/main/java/edu/harvard/iq/dataverse/DataversePage.java
Original file line number Diff line number Diff line change
Expand Up @@ -627,44 +627,17 @@ public String save() {
if (dataverse.isMetadataBlockRoot() && (mdb.isSelected() || mdb.isRequired())) {
selectedBlocks.add(mdb);
for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
// currently we don't allow input levels for setting an optional field as conditionally required
// so we skip looking at parents (which get set automatically with their children)
if (!dsft.isHasChildren() && dsft.isRequiredDV()) {
boolean addRequiredInputLevels = false;
boolean parentAlreadyAdded = false;
if (!dsft.isChild()) {
// Save input level for parent field
saveInputLevels(listDFTIL, dsft, dataverse);

if (!dsft.isHasParent() && dsft.isInclude()) {
addRequiredInputLevels = !dsft.isRequired();
}
if (dsft.isHasParent() && dsft.getParentDatasetFieldType().isInclude()) {
addRequiredInputLevels = !dsft.isRequired() || !dsft.getParentDatasetFieldType().isRequired();
}

if (addRequiredInputLevels) {
listDFTIL.add(new DataverseFieldTypeInputLevel(dsft, dataverse,true, true));

//also add the parent as required (if it hasn't been added already)
// todo: review needed .equals() methods, then change this to use a Set, in order to simplify code
if (dsft.isHasParent()) {
DataverseFieldTypeInputLevel parentToAdd = new DataverseFieldTypeInputLevel(dsft.getParentDatasetFieldType(), dataverse, true, true);
for (DataverseFieldTypeInputLevel dataverseFieldTypeInputLevel : listDFTIL) {
if (dataverseFieldTypeInputLevel.getDatasetFieldType().getId() == parentToAdd.getDatasetFieldType().getId()) {
parentAlreadyAdded = true;
break;
}
}
if (!parentAlreadyAdded) {
// Only add the parent once. There's a UNIQUE (dataverse_id, datasetfieldtype_id)
// constraint on the dataversefieldtypeinputlevel table we need to avoid.
listDFTIL.add(parentToAdd);
}
}
// Handle child fields
if (dsft.isHasChildren()) {
for (DatasetFieldType child : dsft.getChildDatasetFieldTypes()) {
saveInputLevels(listDFTIL, child, dataverse);
}
}
}
if ((!dsft.isHasParent() && !dsft.isInclude())
|| (dsft.isHasParent() && !dsft.getParentDatasetFieldType().isInclude())) {
listDFTIL.add(new DataverseFieldTypeInputLevel(dsft, dataverse,false, false));
}
}
}
}
Expand Down Expand Up @@ -1030,27 +1003,11 @@ private void refreshAllMetadataBlocks() {

for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
if (!dsft.isChild()) {
DataverseFieldTypeInputLevel dsfIl = dataverseFieldTypeInputLevelService.findByDataverseIdDatasetFieldTypeId(dataverseIdForInputLevel, dsft.getId());
if (dsfIl != null) {
dsft.setRequiredDV(dsfIl.isRequired());
dsft.setInclude(dsfIl.isInclude());
} else {
dsft.setRequiredDV(dsft.isRequired());
dsft.setInclude(true);
}
loadInputLevels(dsft, dataverseIdForInputLevel);
dsft.setOptionSelectItems(resetSelectItems(dsft));
if (dsft.isHasChildren()) {
for (DatasetFieldType child : dsft.getChildDatasetFieldTypes()) {
DataverseFieldTypeInputLevel dsfIlChild = dataverseFieldTypeInputLevelService.findByDataverseIdDatasetFieldTypeId(dataverseIdForInputLevel, child.getId());
if (dsfIlChild != null) {
child.setRequiredDV(dsfIlChild.isRequired());
child.setInclude(dsfIlChild.isInclude());
} else {
// in the case of conditionally required (child = true, parent = false)
// we set this to false; i.e this is the default "don't override" value
child.setRequiredDV(child.isRequired() && dsft.isRequired());
child.setInclude(true);
}
loadInputLevels(child, dataverseIdForInputLevel);
child.setOptionSelectItems(resetSelectItems(child));
}
}
Expand All @@ -1061,6 +1018,22 @@ private void refreshAllMetadataBlocks() {
setAllMetadataBlocks(retList);
}

private void loadInputLevels(DatasetFieldType dsft, Long dataverseIdForInputLevel) {
DataverseFieldTypeInputLevel dsfIl = dataverseFieldTypeInputLevelService
.findByDataverseIdDatasetFieldTypeId(dataverseIdForInputLevel, dsft.getId());

if (dsfIl != null) {
dsft.setRequiredDV(dsfIl.isRequired());
dsft.setInclude(dsfIl.isInclude());
dsft.setDisplayOnCreate(dsfIl.isDisplayOnCreate());
} else {
// If there is no input level, use the default values
dsft.setRequiredDV(dsft.isRequired());
dsft.setInclude(true);
dsft.setDisplayOnCreate(false);
}
}

public void validateAlias(FacesContext context, UIComponent toValidate, Object value) {
if (!StringUtils.isEmpty((String) value)) {
String alias = (String) value;
Expand Down Expand Up @@ -1337,4 +1310,57 @@ public Set<Entry<String, String>> getPidProviderOptions() {
}
return options;
}

public void updateDisplayOnCreate(Long mdbId, Long dsftId, boolean currentValue) {
for (MetadataBlock mdb : allMetadataBlocks) {
if (mdb.getId().equals(mdbId)) {
for (DatasetFieldType dsft : mdb.getDatasetFieldTypes()) {
if (dsft.getId().equals(dsftId)) {
// Update value in memory
dsft.setDisplayOnCreate(!currentValue);

// Update or create input level
DataverseFieldTypeInputLevel existingLevel = dataverseFieldTypeInputLevelService
.findByDataverseIdDatasetFieldTypeId(dataverse.getId(), dsftId);

if (existingLevel != null) {
existingLevel.setDisplayOnCreate(!currentValue);
dataverseFieldTypeInputLevelService.save(existingLevel);
} else {
DataverseFieldTypeInputLevel newLevel = new DataverseFieldTypeInputLevel(
dsft,
dataverse,
dsft.isRequiredDV(),
true, // default include
!currentValue // new value of displayOnCreate
);
dataverseFieldTypeInputLevelService.save(newLevel);
}
}
}
}
}
}

private void saveInputLevels(List<DataverseFieldTypeInputLevel> listDFTIL, DatasetFieldType dsft, Dataverse dataverse) {
// If the field already has an input level, update it
DataverseFieldTypeInputLevel existingLevel = dataverseFieldTypeInputLevelService
.findByDataverseIdDatasetFieldTypeId(dataverse.getId(), dsft.getId());

if (existingLevel != null) {
existingLevel.setDisplayOnCreate(dsft.isDisplayOnCreate());
existingLevel.setInclude(dsft.isInclude());
existingLevel.setRequired(dsft.isRequiredDV());
listDFTIL.add(existingLevel);
} else if (dsft.isInclude() || dsft.isDisplayOnCreate() || dsft.isRequiredDV()) {
// Only create new input level if there is any specific configuration
listDFTIL.add(new DataverseFieldTypeInputLevel(
dsft,
dataverse,
dsft.isRequiredDV(),
dsft.isInclude(),
dsft.isDisplayOnCreate()
));
}
}
}
Loading