Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions doc/release-notes/8461-filecategories-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### The default file categories are now configurable

The default list of the pre-defined file categories - "Documentation", "Data" and "Code" - can now be redefined via a database setting `:FileCategories`. Consult the [Database Settings](https://guides.dataverse.org/en/latest/installation/config.html) section of the Guides for more information.
15 changes: 15 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2576,3 +2576,18 @@ For example:
++++++++++++++++++++++++++++++++

When set to ``true``, this setting allows a superuser to publish and/or update Dataverse collections and datasets bypassing the external validation checks (specified by the settings above). In an event where an external script is reporting validation failures that appear to be in error, this option gives an admin with superuser privileges a quick way to publish the dataset or update a collection for the user.

:FileCategories
+++++++++++++++

Overrides the default list of file categories that is used in the UI when adding tags to files. The default list is Documentation, Data, and Code.

This setting is a comma-separated list of the new tags.

To override the default list with Docs, Data, Code, and Workflow:

``curl -X PUT -d 'Docs,Data,Code,Workflow' http://localhost:8080/api/admin/settings/:FileCategories``

To remove the override and go back to the default list:

``curl -X PUT -d '' http://localhost:8080/api/admin/settings/:FileCategories``
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package edu.harvard.iq.dataverse;

import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Service to manage the default values for Dataset file categories and allow
* to be overridden with a :FileCategories Settings configuration.
*
* @author adaybujeda
*/
@Stateless
public class DataFileCategoryServiceBean {

public static final String FILE_CATEGORIES_KEY = ":FileCategories";

@EJB
private SettingsServiceBean settingsService;

public List<String> mergeDatasetFileCategories(List<DataFileCategory> datasetFileCategories) {
List<DataFileCategory> fileCategories = Optional.ofNullable(datasetFileCategories).orElse(Collections.emptyList());
List<String> defaultFileCategories = getFileCategories();

//avoid resizing
List<String> mergedFileCategories = new ArrayList<>(defaultFileCategories.size() + fileCategories.size());

for(DataFileCategory category: fileCategories) {
mergedFileCategories.add(category.getName());
}

for(String defaultCategory: defaultFileCategories) {
if (!mergedFileCategories.contains(defaultCategory)) {
mergedFileCategories.add(defaultCategory);
}
}

return mergedFileCategories;
}

public List<String> getFileCategories() {
List<String> fileCategoriesOverride = getFileCategoriesOverride();
return fileCategoriesOverride.isEmpty() ? getFileCategoriesDefault() : fileCategoriesOverride;
}

private List<String> getFileCategoriesDefault() {
// "Documentation", "Data" and "Code" are the 3 default categories that we
// present by default
return Arrays.asList(
BundleUtil.getStringFromBundle("dataset.category.documentation"),
BundleUtil.getStringFromBundle("dataset.category.data"),
BundleUtil.getStringFromBundle("dataset.category.code")
);
}

private List<String> getFileCategoriesOverride() {
String applicationLanguage = BundleUtil.getCurrentLocale().getLanguage();
Optional<String> fileCategoriesOverride = Optional.ofNullable(settingsService.get(FILE_CATEGORIES_KEY));

if (fileCategoriesOverride.isPresent()) {
// There is an override, check if there is language specific value
String overrideValue = settingsService.get(FILE_CATEGORIES_KEY, applicationLanguage, fileCategoriesOverride.get());

return parseCategoriesString(overrideValue);
}

return Collections.emptyList();
}

private List<String> parseCategoriesString(String categoriesString) {
if (categoriesString == null) {
return Collections.emptyList();
}

String[] categories = categoriesString.split(",");
return Arrays.stream(categories).map(item -> item.trim()).filter(item -> !item.isBlank()).collect(Collectors.toUnmodifiableList());
}

}
18 changes: 0 additions & 18 deletions src/main/java/edu/harvard/iq/dataverse/Dataset.java
Original file line number Diff line number Diff line change
Expand Up @@ -448,24 +448,6 @@ public void addFileCategory(DataFileCategory category) {
dataFileCategories.add(category);
}

public Collection<String> getCategoriesByName() {
Collection<String> ret = getCategoryNames();

// "Documentation", "Data" and "Code" are the 3 default categories that we
// present by default:
if (!ret.contains(BundleUtil.getStringFromBundle("dataset.category.documentation"))) {
ret.add(BundleUtil.getStringFromBundle("dataset.category.documentation"));
}
if (!ret.contains(BundleUtil.getStringFromBundle("dataset.category.data"))) {
ret.add(BundleUtil.getStringFromBundle("dataset.category.data"));
}
if (!ret.contains(BundleUtil.getStringFromBundle("dataset.category.code"))) {
ret.add(BundleUtil.getStringFromBundle("dataset.category.code"));
}

return ret;
}

public void setCategoriesByName(List<String> newCategoryNames) {
if (newCategoryNames != null) {
Collection<String> oldCategoryNames = getCategoryNames();
Expand Down
11 changes: 4 additions & 7 deletions src/main/java/edu/harvard/iq/dataverse/DatasetPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@
import java.io.InputStream;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
Expand All @@ -78,7 +75,6 @@
import java.util.Set;
import java.util.Collection;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ejb.EJB;
import javax.ejb.EJBException;
import javax.faces.application.FacesMessage;
Expand All @@ -88,7 +84,6 @@
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.json.JsonObject;

import org.apache.commons.lang3.StringUtils;
import org.primefaces.event.FileUploadEvent;
Expand Down Expand Up @@ -128,7 +123,6 @@
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.validator.routines.EmailValidator;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.io.IOUtils;
import org.primefaces.component.selectonemenu.SelectOneMenu;
Expand Down Expand Up @@ -251,6 +245,8 @@ public enum DisplayMode {
EmbargoServiceBean embargoService;
@Inject
LicenseServiceBean licenseServiceBean;
@Inject
DataFileCategoryServiceBean dataFileCategoryService;

private Dataset dataset = new Dataset();

Expand Down Expand Up @@ -4529,7 +4525,8 @@ public void setTabFileTagsByName(List<String> tabFileTagsByName) {

private void refreshCategoriesByName(){
categoriesByName= new ArrayList<>();
for (String category: dataset.getCategoriesByName() ){
List<String> datasetFileCategories = dataFileCategoryService.mergeDatasetFileCategories(dataset.getCategories());
for (String category: datasetFileCategories ){
categoriesByName.add(category);
}
refreshSelectedTags();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ public enum Referrer {
SettingsWrapper settingsWrapper;
@Inject
LicenseServiceBean licenseServiceBean;
@Inject
DataFileCategoryServiceBean dataFileCategoryService;

private Dataset dataset = new Dataset();

Expand Down Expand Up @@ -2737,7 +2739,8 @@ private void refreshSelectedTabFileTags() {

private void refreshCategoriesByName(){
categoriesByName= new ArrayList<>();
for (String category: dataset.getCategoriesByName() ){
List<String> datasetFileCategories = dataFileCategoryService.mergeDatasetFileCategories(dataset.getCategories());
for (String category: datasetFileCategories ){
categoriesByName.add(category);
}
refreshSelectedTags();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package edu.harvard.iq.dataverse;

import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
*
* @author adaybujeda
*/
@RunWith(MockitoJUnitRunner.class)
public class DataFileCategoryServiceBeanTest {

@Mock
private SettingsServiceBean settingsServiceBean;
@InjectMocks
private DataFileCategoryServiceBean target;

@Test
public void getFileCategories_should_return_default_file_categories_in_expected_order_when_no_override_configured() {
Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY)).thenReturn(null);

List<String> result = target.getFileCategories();

MatcherAssert.assertThat(result.size(), Matchers.is(3));
MatcherAssert.assertThat(result.get(0), Matchers.is("Documentation"));
MatcherAssert.assertThat(result.get(1), Matchers.is("Data"));
MatcherAssert.assertThat(result.get(2), Matchers.is("Code"));
}

@Test
public void getFileCategories_should_return_default_file_categories_in_expected_order_when_empty_override_is_configured() {
Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY)).thenReturn(" ");

List<String> result = target.getFileCategories();

MatcherAssert.assertThat(result.size(), Matchers.is(3));
MatcherAssert.assertThat(result.get(0), Matchers.is("Documentation"));
MatcherAssert.assertThat(result.get(1), Matchers.is("Data"));
MatcherAssert.assertThat(result.get(2), Matchers.is("Code"));
}

@Test
public void getFileCategories_should_return_override_file_categories_from_settings_service() {
setup_override("Override01, Override02");

List<String> result = target.getFileCategories();

MatcherAssert.assertThat(result.size(), Matchers.is(2));
MatcherAssert.assertThat(result.get(0), Matchers.is("Override01"));
MatcherAssert.assertThat(result.get(1), Matchers.is("Override02"));
}

@Test
public void getFileCategories_should_trim_override_values() {
setup_override(" Test Value 01 , Test Value 02 ");

List<String> result = target.getFileCategories();

MatcherAssert.assertThat(result.size(), Matchers.is(2));
MatcherAssert.assertThat(result.get(0), Matchers.is("Test Value 01"));
MatcherAssert.assertThat(result.get(1), Matchers.is("Test Value 02"));
}

@Test
public void getFileCategories_should_ignore_empty_override_values() {
setup_override(",value01,,value02,,value03,,");

List<String> result = target.getFileCategories();

MatcherAssert.assertThat(result.size(), Matchers.is(3));
MatcherAssert.assertThat(result.get(0), Matchers.is("value01"));
MatcherAssert.assertThat(result.get(1), Matchers.is("value02"));
MatcherAssert.assertThat(result.get(2), Matchers.is("value03"));
}

@Test
public void mergeDatasetFileCategories_should_handle_null_datafile_categories() {
Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY)).thenReturn(null);

List<String> result = target.mergeDatasetFileCategories(null);

MatcherAssert.assertThat(result.size(), Matchers.is(3));
MatcherAssert.assertThat(result.get(0), Matchers.is("Documentation"));
MatcherAssert.assertThat(result.get(1), Matchers.is("Data"));
MatcherAssert.assertThat(result.get(2), Matchers.is("Code"));
}

@Test
public void mergeDatasetFileCategories_should_add_dataset_values_first_then_default_categories() {
Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY)).thenReturn(null);

List<String> result = target.mergeDatasetFileCategories(setup_data_file_categories("dataset01", "dataset02"));

MatcherAssert.assertThat(result.size(), Matchers.is(5));
MatcherAssert.assertThat(result.get(0), Matchers.is("dataset01"));
MatcherAssert.assertThat(result.get(1), Matchers.is("dataset02"));
MatcherAssert.assertThat(result.get(2), Matchers.is("Documentation"));
MatcherAssert.assertThat(result.get(3), Matchers.is("Data"));
MatcherAssert.assertThat(result.get(4), Matchers.is("Code"));
}

@Test
public void mergeDatasetFileCategories_should_add_dataset_values_first_then_override_categories() {
setup_override("override01, override02");

List<String> result = target.mergeDatasetFileCategories(setup_data_file_categories("dataset01", "dataset02"));

MatcherAssert.assertThat(result.size(), Matchers.is(4));
MatcherAssert.assertThat(result.get(0), Matchers.is("dataset01"));
MatcherAssert.assertThat(result.get(1), Matchers.is("dataset02"));
MatcherAssert.assertThat(result.get(2), Matchers.is("override01"));
MatcherAssert.assertThat(result.get(3), Matchers.is("override02"));
}

@Test
public void mergeDatasetFileCategories_should_ignore_duplicates() {
Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY)).thenReturn(null);

List<String> result = target.mergeDatasetFileCategories(setup_data_file_categories("Code", "Data", "Custom"));

MatcherAssert.assertThat(result.size(), Matchers.is(4));
MatcherAssert.assertThat(result.get(0), Matchers.is("Code"));
MatcherAssert.assertThat(result.get(1), Matchers.is("Data"));
MatcherAssert.assertThat(result.get(2), Matchers.is("Custom"));
MatcherAssert.assertThat(result.get(3), Matchers.is("Documentation"));
}

private void setup_override(String overrideValue) {
String currentLang = BundleUtil.getCurrentLocale().getLanguage();

Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY)).thenReturn(overrideValue);
Mockito.when(settingsServiceBean.get(DataFileCategoryServiceBean.FILE_CATEGORIES_KEY, currentLang, overrideValue)).thenReturn(overrideValue);
}

private List<DataFileCategory> setup_data_file_categories(String... names) {
return Arrays.stream(names).map(name -> {
DataFileCategory dataFileCategory = new DataFileCategory();
dataFileCategory.setName(name);
return dataFileCategory;
}).collect(Collectors.toList());
}

}