Skip to content
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

Closes #2638: Add application property that allows to switch whether files are required to publish dataset #2639

Merged
merged 3 commits into from
Feb 4, 2025
Merged
Changes from 1 commit
Commits
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
Original file line number Diff line number Diff line change
@@ -86,12 +86,14 @@
import edu.harvard.iq.dataverse.persistence.dataset.DatasetLock;
import edu.harvard.iq.dataverse.persistence.dataset.DatasetRelPublication;
import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion;
import edu.harvard.iq.dataverse.persistence.dataset.DatasetVersion.VersionState;
import edu.harvard.iq.dataverse.persistence.dataverse.Dataverse;
import edu.harvard.iq.dataverse.persistence.user.AuthenticatedUser;
import edu.harvard.iq.dataverse.persistence.user.User;
import edu.harvard.iq.dataverse.privateurl.PrivateUrl;
import edu.harvard.iq.dataverse.privateurl.PrivateUrlUtil;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key;
import edu.harvard.iq.dataverse.util.ArchiverUtil;
import edu.harvard.iq.dataverse.util.JsfHelper;
import edu.harvard.iq.dataverse.util.SystemConfig;
@@ -109,6 +111,16 @@ public class DatasetPage implements Serializable {

private static final Logger logger = Logger.getLogger(DatasetPage.class.getCanonicalName());

public enum PublishButtonCase {
DO_NOT_SHOW,
NO_FILES_IN_DATASET,
PARENT_NOT_RELEASED_NO_PERMISSION_TO_PUBLISH,
PARENT_AND_GRANDPARENT_NOT_RELEASED,
PARENT_NOT_RELEASED_CAN_PUBLISH,
PUBLISH_NOT_RELEASED,
PUBLISH_RELEASED
}

private DataverseSession session;
private EjbDataverseEngine commandEngine;
private PermissionsWrapper permissionsWrapper;
@@ -320,10 +332,29 @@ public boolean canManagePermissions() {
return permissionsWrapper.canManagePermissions(dataset);
}

public boolean isLatestDatasetWithAnyFilesIncluded(){
isLatestDatasetWithAnyFilesIncluded = Optional.of(isLatestDatasetWithAnyFilesIncluded.orElseGet(() -> datasetPageFacade.isLatestDatasetWithAnyFilesIncluded(dataset.getLatestVersion().getId())));
public PublishButtonCase showPublishButtonCase() {
if (dataset.getLatestVersion().getVersionState() != VersionState.DRAFT || !canPublishDataset()) {
return PublishButtonCase.DO_NOT_SHOW;
}
if (!latestDatasetHasFileOrPublishingWithoutFilesAllowed()) {
return PublishButtonCase.NO_FILES_IN_DATASET;
}
if (!dataset.getOwner().isReleased()) {
if (!canPublishDataverse()) {
return PublishButtonCase.PARENT_NOT_RELEASED_NO_PERMISSION_TO_PUBLISH;
} else if (dataset.getOwner().getOwner() != null && !dataset.getOwner().getOwner().isReleased()) {
return PublishButtonCase.PARENT_AND_GRANDPARENT_NOT_RELEASED;
} else {
return PublishButtonCase.PARENT_NOT_RELEASED_CAN_PUBLISH;
}
}

return isLatestDatasetWithAnyFilesIncluded.get();
return dataset.isReleased() ? PublishButtonCase.PUBLISH_RELEASED : PublishButtonCase.PUBLISH_NOT_RELEASED;
}

public boolean latestDatasetHasFileOrPublishingWithoutFilesAllowed() {
return settingsService.isTrueForKey(Key.AllowDatasetPublishWithoutFiles) ||
isLatestDatasetWithAnyFilesIncluded();
}

public boolean canViewUnpublishedDataset() {
@@ -1302,6 +1333,8 @@ public boolean isMinorUpdate() {
public Optional<StreamedContent> getLicenseIconContent(FileTermsOfUse termsOfUse) {
return termsOfUse.getIcon().map(this::toStreamedContent);
}

// -------------------- PRIVATE ---------------------

private DefaultStreamedContent toStreamedContent(final LicenseIcon icon) {
return DefaultStreamedContent.builder()
@@ -1310,7 +1343,11 @@ private DefaultStreamedContent toStreamedContent(final LicenseIcon icon) {
.build();
}

// -------------------- PRIVATE ---------------------
private boolean isLatestDatasetWithAnyFilesIncluded(){
isLatestDatasetWithAnyFilesIncluded = Optional.of(isLatestDatasetWithAnyFilesIncluded.orElseGet(() -> datasetPageFacade.isLatestDatasetWithAnyFilesIncluded(dataset.getLatestVersion().getId())));

return isLatestDatasetWithAnyFilesIncluded.get();
}

private void validateVersusMaximumDate(FacesContext context, UIComponent toValidate, Object embargoDate) {
if(isMaximumEmbargoLengthSet() &&
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ public PublishDatasetCommand(Dataset datasetIn, DataverseRequest aRequest, boole
@Override
public PublishDatasetResult execute(CommandContext ctxt) {

verifyCommandArguments();
verifyCommandArguments(ctxt);

// Invariant 1: If we're here, publishing the dataset makes sense, from a "business logic" point of view.
// Invariant 2: The latest version of the dataset is the one being published, EVEN IF IT IS NOT DRAFT.
@@ -164,7 +164,7 @@ private boolean isReservingPidEnabled(GlobalIdServiceBean idServiceBean) {
*
* @throws IllegalCommandException if the publication request is invalid.
*/
private void verifyCommandArguments() throws IllegalCommandException {
private void verifyCommandArguments(CommandContext ctxt) throws IllegalCommandException {
if (!getDataset().getOwner().isReleased()) {
throw new IllegalCommandException("This dataset may not be published because its host dataverse (" + getDataset().getOwner().getAlias() + ") has not been published.",
this);
@@ -185,7 +185,8 @@ private void verifyCommandArguments() throws IllegalCommandException {
.collect(joining(","))+ ". Please try publishing later.", this);
}

if (getDataset().getLatestVersion().getFileMetadatas().isEmpty()) {
if (!ctxt.settings().isTrueForKey(SettingsServiceBean.Key.AllowDatasetPublishWithoutFiles)
&& getDataset().getLatestVersion().getFileMetadatas().isEmpty()) {
throw new NoDatasetFilesException("There was no files for dataset version with id: " + getDataset().getLatestVersion().getId());
}

Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
import edu.harvard.iq.dataverse.persistence.user.NotificationType;
import edu.harvard.iq.dataverse.persistence.user.Permission;
import edu.harvard.iq.dataverse.persistence.user.User;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;

import java.sql.Timestamp;
import java.util.Date;
@@ -42,7 +43,8 @@ public Dataset execute(CommandContext ctxt) {
throw new IllegalCommandException(BundleUtil.getStringFromBundle("dataset.submit.failure.inReview"), this);
}

if (getDataset().getLatestVersion().getFileMetadatas().isEmpty()) {
if (!ctxt.settings().isTrueForKey(SettingsServiceBean.Key.AllowDatasetPublishWithoutFiles)
&& getDataset().getLatestVersion().getFileMetadatas().isEmpty()) {
throw new NoDatasetFilesException("There was no files for dataset version with id: " + getDataset().getLatestVersion().getId());
}

Original file line number Diff line number Diff line change
@@ -293,6 +293,11 @@ public enum Key {
* Whether to display the publish text for every published version
*/
DatasetPublishPopupCustomTextOnAllVersions,
/**
* If true then it is possible to publish a Dataset that do not have
* any file
*/
AllowDatasetPublishWithoutFiles,
/**
* Whether Harvesting (OAI) service is enabled
*/
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ AllRightsReservedTermsOfUseActive=false
RestrictedAccessTermsOfUseActive=false
AllowApiTokenLookupViaApi=false
DatasetPublishPopupCustomTextOnAllVersions=false
AllowDatasetPublishWithoutFiles=false
DisableRootDataverseTheme=false
ExcludeEmailFromExport=false
GeoconnectCreateEditMaps=false
44 changes: 10 additions & 34 deletions dataverse-webapp/src/main/webapp/dataset.xhtml
Original file line number Diff line number Diff line change
@@ -236,66 +236,42 @@
<p:commandLink type="button"
styleClass="btn btn-default btn-access #{conditionallyDisable}"
onclick="primeFacesShowModal('publishConfirm', this)"
rendered="#{!DatasetPage.dataset.released
and DatasetPage.dataset.latestVersion.versionState=='DRAFT'
and DatasetPage.dataset.owner.released
and DatasetPage.canPublishDataset()
and DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
rendered="#{DatasetPage.showPublishButtonCase() == 'PUBLISH_RELEASED'}">
<span class="glyphicon glyphicon-globe"/> #{bundle['dataset.publish.btn']}
</p:commandLink>

<p:commandLink type="button"
styleClass="btn btn-default btn-access #{conditionallyDisable}"
onclick="primeFacesShowModal('noFilesInDataset', this)"
rendered="#{!DatasetPage.dataset.released
and DatasetPage.dataset.latestVersion.versionState=='DRAFT'
and DatasetPage.canPublishDataset()
and !DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
onclick="primeFacesShowModal('releaseDraft', this)"
rendered="#{DatasetPage.showPublishButtonCase() == 'PUBLISH_NOT_RELEASED'}">
<span class="glyphicon glyphicon-globe"/> #{bundle['dataset.publish.btn']}
</p:commandLink>

<p:commandLink type="button"
styleClass="btn btn-default btn-access #{conditionallyDisable}"
onclick="primeFacesShowModal('releaseDraft', this)"
rendered="#{DatasetPage.dataset.released
and DatasetPage.dataset.latestVersion.versionState=='DRAFT'
and DatasetPage.dataset.owner.released
and DatasetPage.canPublishDataset()
and DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
onclick="primeFacesShowModal('noFilesInDataset', this)"
rendered="#{DatasetPage.showPublishButtonCase() == 'NO_FILES_IN_DATASET'}">
<span class="glyphicon glyphicon-globe"/> #{bundle['dataset.publish.btn']}
</p:commandLink>

<!-- 4.2.1: replaced permissionServiceBean.on(DatasetPage.dataset.owner).has('PublishDataverse') with DatasetPage.canPublishDataverse() -->
<p:commandLink type="button"
styleClass="btn btn-default btn-access #{conditionallyDisable}"
onclick="primeFacesShowModal('mayNotRelease', this)"
rendered="#{DatasetPage.dataset.latestVersion.versionState=='DRAFT' and
!DatasetPage.dataset.owner.released
and DatasetPage.canPublishDataset()
and !DatasetPage.canPublishDataverse()
and DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
rendered="#{DatasetPage.showPublishButtonCase() == 'PARENT_NOT_RELEASED_NO_PERMISSION_TO_PUBLISH'}">
<span class="glyphicon glyphicon-globe"/> #{bundle['dataset.publish.btn']}
</p:commandLink>

<p:commandLink type="button"
styleClass="btn btn-default btn-access #{conditionallyDisable}"
onclick="primeFacesShowModal('publishParent', this)"
rendered="#{DatasetPage.dataset.latestVersion.versionState=='DRAFT' and !DatasetPage.dataset.owner.released
and DatasetPage.canPublishDataverse()
and (DatasetPage.dataset.owner.owner == null
or (DatasetPage.dataset.owner.owner != null and DatasetPage.dataset.owner.owner.released))
and DatasetPage.canPublishDataset()
and DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
rendered="#{DatasetPage.showPublishButtonCase() == 'PARENT_NOT_RELEASED_CAN_PUBLISH'}">
<span class="glyphicon glyphicon-globe"/> #{bundle['dataset.publish.btn']}
</p:commandLink>

<p:commandLink type="button"
styleClass="btn btn-default btn-access #{conditionallyDisable}"
rendered="#{DatasetPage.dataset.latestVersion.versionState=='DRAFT' and !DatasetPage.dataset.owner.released
and DatasetPage.canPublishDataverse()
and (DatasetPage.dataset.owner.owner != null and !DatasetPage.dataset.owner.owner.released)
and DatasetPage.canPublishDataset()
and DatasetPage.isLatestDatasetWithAnyFilesIncluded()}"
rendered="#{DatasetPage.showPublishButtonCase() == 'PARENT_AND_GRANDPARENT_NOT_RELEASED'}"
onclick="primeFacesShowModal('maynotPublishParent', this)">
<span class="glyphicon glyphicon-globe"/> #{bundle['dataset.publish.btn']}
</p:commandLink>
@@ -1016,7 +992,7 @@
styleClass="largePopUp"
>

<p:outputPanel rendered="#{DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
<p:outputPanel rendered="#{DatasetPage.latestDatasetHasFileOrPublishingWithoutFilesAllowed()}">
<p class="text-warning">
<span class="glyphicon glyphicon-warning-sign"/> #{bundle['dataset.submitForReview.submitMessage']}
</p>
@@ -1108,7 +1084,7 @@
</div>
</p:outputPanel>

<p:outputPanel rendered="#{!DatasetPage.isLatestDatasetWithAnyFilesIncluded()}">
<p:outputPanel rendered="#{!DatasetPage.latestDatasetHasFileOrPublishingWithoutFilesAllowed()}">
<p class="text-danger">
<span class="glyphicon glyphicon-exclamation-sign"/> #{bundle['dataset.submit.failure.noFiles']}
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package edu.harvard.iq.dataverse.engine.command.impl;

import static edu.harvard.iq.dataverse.mocks.MockRequestFactory.makeRequest;
import static edu.harvard.iq.dataverse.persistence.MocksFactory.makeAuthenticatedUser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;

import java.sql.Timestamp;
import java.util.Date;

import javax.persistence.EntityManager;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import edu.harvard.iq.dataverse.DatasetDao;
import edu.harvard.iq.dataverse.DataverseRoleServiceBean;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.engine.TestCommandContext;
import edu.harvard.iq.dataverse.engine.TestDataverseEngine;
import edu.harvard.iq.dataverse.engine.command.exception.NoDatasetFilesException;
import edu.harvard.iq.dataverse.globalid.GlobalIdServiceBean;
import edu.harvard.iq.dataverse.globalid.GlobalIdServiceBeanResolver;
import edu.harvard.iq.dataverse.persistence.MocksFactory;
import edu.harvard.iq.dataverse.persistence.dataset.Dataset;
import edu.harvard.iq.dataverse.search.index.IndexServiceBean;
import edu.harvard.iq.dataverse.search.index.SolrIndexServiceBean;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.workflow.WorkflowServiceBean;

@ExtendWith(MockitoExtension.class)
public class PublishDatasetCommandTest {

@Mock
private DatasetDao datasetDao;
@Mock
private GlobalIdServiceBeanResolver globalIdServiceBeanResolver;
@Mock
private GlobalIdServiceBean globalIdServiceBean;
@Mock
private WorkflowServiceBean workflowServiceBean;
@Mock
private SettingsServiceBean settingsServiceBean;
@Mock
private DataverseRoleServiceBean dataverseRoleServiceBean;
@Mock
private EntityManager em;
@Mock
private AuthenticationServiceBean authenticationServiceBean;
@Mock
private IndexServiceBean indexServiceBean;
@Mock
private SolrIndexServiceBean solrIndexServiceBean;

private TestDataverseEngine testEngine;

@BeforeEach
void beforeEach() {

lenient().when(globalIdServiceBeanResolver.resolve(any())).thenReturn(globalIdServiceBean);
lenient().when(globalIdServiceBean.publicizeIdentifier(any())).thenReturn(true);
lenient().when(settingsServiceBean.getValueForKey(SettingsServiceBean.Key.Protocol)).thenReturn("doi");
lenient().when(settingsServiceBean.getValueForKey(SettingsServiceBean.Key.Authority)).thenReturn("");
lenient().when(settingsServiceBean.getValueForKey(SettingsServiceBean.Key.DataFilePIDFormat)).thenReturn("");
lenient().when(em.merge(any())).thenAnswer(args -> args.getArgument(0));

testEngine = new TestDataverseEngine(new TestCommandContext() {
@Override
public DatasetDao datasets() {
return datasetDao;
}
@Override
public GlobalIdServiceBeanResolver globalIdServiceBeanResolver() {
return globalIdServiceBeanResolver;
}
@Override
public WorkflowServiceBean workflows() {
return workflowServiceBean;
}
@Override
public SettingsServiceBean settings() {
return settingsServiceBean;
}
@Override
public DataverseRoleServiceBean roles() {
return dataverseRoleServiceBean;
}
@Override
public EntityManager em() {
return em;
}
@Override
public AuthenticationServiceBean authentication() {
return authenticationServiceBean;
}
@Override
public IndexServiceBean index() {
return indexServiceBean;
}
@Override
public SolrIndexServiceBean solrIndex() {
return solrIndexServiceBean;
}
});
}


@Test
void execute__fail_when_no_files() {
// given
Dataset dataset = MocksFactory.makeDataset();
dataset.getFiles().clear();
dataset.getLatestVersion().getFileMetadatas().clear();

dataset.getOwner().setPublicationDate(new Timestamp(new Date().getTime()));
dataset.setGlobalIdCreateTime(new Date());
PublishDatasetCommand sut = new PublishDatasetCommand(dataset, makeRequest(makeAuthenticatedUser("Jane", "Doe")), false);

// when & then
assertThatThrownBy(() -> testEngine.submit(sut)).isInstanceOf(NoDatasetFilesException.class);
}

@Test
void execute__success_when_no_files_but_no_files_allowed() {
// given
Dataset dataset = MocksFactory.makeDataset();
dataset.getFiles().clear();
dataset.getLatestVersion().getFileMetadatas().clear();

lenient().when(settingsServiceBean.isTrueForKey(SettingsServiceBean.Key.AllowDatasetPublishWithoutFiles)).thenReturn(true);

dataset.getOwner().setPublicationDate(new Timestamp(new Date().getTime()));
dataset.setGlobalIdCreateTime(new Date());
PublishDatasetCommand sut = new PublishDatasetCommand(dataset, makeRequest(makeAuthenticatedUser("Jane", "Doe")), false);

// when
PublishDatasetResult result = testEngine.submit(sut);

// then
assertThat(result).isNotNull();
assertThat(result.isCompleted()).isTrue();
}
}