From d75a80bb5a99ad204e94551fadc0b277efdf7949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20M=C4=85dry?= Date: Tue, 28 Jan 2025 08:32:01 +0100 Subject: [PATCH 1/2] 2638: Add application property that allows to switch whether files are required to publish dataset --- .../edu/harvard/iq/dataverse/DatasetPage.java | 45 +++++- .../command/impl/PublishDatasetCommand.java | 7 +- .../impl/SubmitDatasetForReviewCommand.java | 4 +- .../settings/SettingsServiceBean.java | 5 + .../config/dataverse.default.properties | 1 + .../src/main/webapp/dataset.xhtml | 44 ++---- .../impl/PublishDatasetCommandTest.java | 148 ++++++++++++++++++ 7 files changed, 212 insertions(+), 42 deletions(-) create mode 100644 dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommandTest.java diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java index 06f28cc9cd..99ef825ee2 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/DatasetPage.java @@ -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 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() && diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommand.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommand.java index 859dba2e5a..b8357d9106 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommand.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommand.java @@ -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()); } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/SubmitDatasetForReviewCommand.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/SubmitDatasetForReviewCommand.java index 010430db55..a561270c07 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/SubmitDatasetForReviewCommand.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/engine/command/impl/SubmitDatasetForReviewCommand.java @@ -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()); } diff --git a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java index f187753e53..fb11a7033d 100644 --- a/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java +++ b/dataverse-webapp/src/main/java/edu/harvard/iq/dataverse/settings/SettingsServiceBean.java @@ -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 */ diff --git a/dataverse-webapp/src/main/resources/config/dataverse.default.properties b/dataverse-webapp/src/main/resources/config/dataverse.default.properties index c37989f901..6e837a0183 100644 --- a/dataverse-webapp/src/main/resources/config/dataverse.default.properties +++ b/dataverse-webapp/src/main/resources/config/dataverse.default.properties @@ -18,6 +18,7 @@ AllRightsReservedTermsOfUseActive=false RestrictedAccessTermsOfUseActive=false AllowApiTokenLookupViaApi=false DatasetPublishPopupCustomTextOnAllVersions=false +AllowDatasetPublishWithoutFiles=false DisableRootDataverseTheme=false ExcludeEmailFromExport=false GeoconnectCreateEditMaps=false diff --git a/dataverse-webapp/src/main/webapp/dataset.xhtml b/dataverse-webapp/src/main/webapp/dataset.xhtml index 5e3e0bec55..4b7b8a623d 100644 --- a/dataverse-webapp/src/main/webapp/dataset.xhtml +++ b/dataverse-webapp/src/main/webapp/dataset.xhtml @@ -236,32 +236,21 @@ + rendered="#{DatasetPage.showPublishButtonCase() == 'PUBLISH_RELEASED'}"> #{bundle['dataset.publish.btn']} + onclick="primeFacesShowModal('releaseDraft', this)" + rendered="#{DatasetPage.showPublishButtonCase() == 'PUBLISH_NOT_RELEASED'}"> #{bundle['dataset.publish.btn']} + onclick="primeFacesShowModal('noFilesInDataset', this)" + rendered="#{DatasetPage.showPublishButtonCase() == 'NO_FILES_IN_DATASET'}"> #{bundle['dataset.publish.btn']} @@ -269,33 +258,20 @@ + rendered="#{DatasetPage.showPublishButtonCase() == 'PARENT_NOT_RELEASED_NO_PERMISSION_TO_PUBLISH'}"> #{bundle['dataset.publish.btn']} + rendered="#{DatasetPage.showPublishButtonCase() == 'PARENT_NOT_RELEASED_CAN_PUBLISH'}"> #{bundle['dataset.publish.btn']} #{bundle['dataset.publish.btn']} @@ -1016,7 +992,7 @@ styleClass="largePopUp" > - +

#{bundle['dataset.submitForReview.submitMessage']}

@@ -1108,7 +1084,7 @@
- +

#{bundle['dataset.submit.failure.noFiles']}

diff --git a/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommandTest.java b/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommandTest.java new file mode 100644 index 0000000000..6a2be39775 --- /dev/null +++ b/dataverse-webapp/src/test/java/edu/harvard/iq/dataverse/engine/command/impl/PublishDatasetCommandTest.java @@ -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(); + } +} From 79c9aeb5f923cc62568b9607a5832d1a56da5db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20M=C4=85dry?= Date: Thu, 30 Jan 2025 08:36:43 +0100 Subject: [PATCH 2/2] cr response --- dataverse-webapp/src/main/webapp/dataset.xhtml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dataverse-webapp/src/main/webapp/dataset.xhtml b/dataverse-webapp/src/main/webapp/dataset.xhtml index 4b7b8a623d..fc95a83faa 100644 --- a/dataverse-webapp/src/main/webapp/dataset.xhtml +++ b/dataverse-webapp/src/main/webapp/dataset.xhtml @@ -85,6 +85,9 @@ + @@ -236,21 +239,21 @@ + rendered="#{DatasetPage.showPublishButtonCase() == publishButtonCaseEnum.PUBLISH_NOT_RELEASED}"> #{bundle['dataset.publish.btn']} + rendered="#{DatasetPage.showPublishButtonCase() == publishButtonCaseEnum.PUBLISH_RELEASED}"> #{bundle['dataset.publish.btn']} + rendered="#{DatasetPage.showPublishButtonCase() == publishButtonCaseEnum.NO_FILES_IN_DATASET}"> #{bundle['dataset.publish.btn']} @@ -258,20 +261,20 @@ + rendered="#{DatasetPage.showPublishButtonCase() == publishButtonCaseEnum.PARENT_NOT_RELEASED_NO_PERMISSION_TO_PUBLISH}"> #{bundle['dataset.publish.btn']} + rendered="#{DatasetPage.showPublishButtonCase() == publishButtonCaseEnum.PARENT_NOT_RELEASED_CAN_PUBLISH}"> #{bundle['dataset.publish.btn']} #{bundle['dataset.publish.btn']}