Skip to content

Commit

Permalink
Closes #2554: Generalize Suggestion box to support controlled vocabul…
Browse files Browse the repository at this point in the history
…ary values
  • Loading branch information
dkorbel committed Sep 23, 2024
1 parent 3c65c14 commit 357ae52
Show file tree
Hide file tree
Showing 13 changed files with 410 additions and 13 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,29 @@ Dataverse is a trademark of President and Fellows of Harvard College and is regi
[chat.dataverse.org]: http://chat.dataverse.org
[Dataverse Community Meeting]: https://dataverse.org/events
[open source]: LICENSE.md


# Running integration tests
Starting integration tests dependencies:

```bash
./mvnw docker:start -Dtest.solr.port=8984 -pl dataverse-webapp
```

All tests:

```bash
./mvnw verify
```

Single test:

```bash
./mvnw verify -Dit.test=UserNotificationRepositoryIT -pl dataverse-persistence
```

Stopping integration tests dependencies:

```bash
./mvnw docker:stop -pl dataverse-webapp
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.util.List;

/**
* @author skraffmiller
* @author skraffmiller, Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
@Stateless
public class ControlledVocabularyValueServiceBean implements java.io.Serializable {
Expand All @@ -30,4 +30,16 @@ public List<ControlledVocabularyValue> findByDatasetFieldTypeId(Long dsftId) {
return query.getResultList();

}
public List<ControlledVocabularyValue> findByDatasetFieldTypeNameAndValueLike(String datasetFieldTypeName, String suggestionSourceFieldValue, int queryLimit) {

String queryString = "select DISTINCT v from ControlledVocabularyValue as v " +
"where UPPER(v.strValue) LIKE CONCAT('%', UPPER(:suggestionSourceFieldValue), '%') " +
"and v.datasetFieldType.id = (select d.id from DatasetFieldType as d where d.name = :datasetFieldTypeName)";
TypedQuery<ControlledVocabularyValue> query = em.createQuery(queryString, ControlledVocabularyValue.class);
query.setParameter("suggestionSourceFieldValue", suggestionSourceFieldValue);
query.setParameter("datasetFieldTypeName", datasetFieldTypeName);
query.setMaxResults(queryLimit);
query.setHint("eclipselink.QUERY_RESULTS_CACHE", "TRUE");
return query.getResultList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import java.util.Optional;
import java.util.stream.Collectors;

/**
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
public class SuggestionInputFieldRenderer implements InputFieldRenderer {
private static final String VALUE_HEADER_KEY_FORMAT = "datasetfieldtype.%s.suggestionDisplay.valueHeader";
private static final String DETAILS_HEADER_KEY_FORMAT = "datasetfieldtype.%s.suggestionDisplay.detailsHeader";
Expand All @@ -29,13 +32,13 @@ public class SuggestionInputFieldRenderer implements InputFieldRenderer {

// -------------------- CONSTRUCTORS --------------------

public SuggestionInputFieldRenderer() {
protected SuggestionInputFieldRenderer() {
}

/**
* Constructs renderer with support for suggestions.
*/
public SuggestionInputFieldRenderer(
protected SuggestionInputFieldRenderer(
SuggestionHandler suggestionHandler,
Map<String, String> datasetFieldTypeToSuggestionFilterMapping,
SuggestionDisplayType suggestionDisplayType,
Expand Down Expand Up @@ -118,10 +121,12 @@ public List<Suggestion> processSuggestionQuery(DatasetField datasetField, String
public List<Suggestion> createSuggestions(DatasetField datasetField, String query) {

Map<String, String> suggestionFilteredFields = new HashMap<>();

if (!datasetFieldTypeToSuggestionFilterMapping.isEmpty()) {

suggestionFilteredFields = findFilterValues(datasetField, datasetFieldTypeToSuggestionFilterMapping);
if(suggestionHandler.isDependentOnSiblings()) {
suggestionFilteredFields = findFilterValuesInSiblings(datasetField, datasetFieldTypeToSuggestionFilterMapping);
} else {
suggestionFilteredFields = getFilterValue(datasetField);
}

if (suggestionFilteredFields.isEmpty()){
return new ArrayList<>();
Expand All @@ -131,9 +136,25 @@ public List<Suggestion> createSuggestions(DatasetField datasetField, String quer
return suggestionHandler.generateSuggestions(suggestionFilteredFields, query);
}

private Map<String, String> getFilterValue(DatasetField datasetField) {
return datasetFieldTypeToSuggestionFilterMapping
.entrySet()
.stream()
.filter(entry -> entry.getKey().equalsIgnoreCase(datasetField.getTypeName()))
.findFirst()
.map(entry -> {
Map<String, String> filterValues = new HashMap<>();
// Assuming getFieldValue() returns an Optional or similar
filterValues.put(entry.getValue(), entry.getKey());
return filterValues;
})
// If no matching entry is found, return an empty map
.orElseGet(HashMap::new);
}

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

private Map<String, String> findFilterValues(DatasetField datasetField, Map<String, String> datasetFieldTypeToSuggestionFilterMapping) {
private Map<String, String> findFilterValuesInSiblings(DatasetField datasetField, Map<String, String> datasetFieldTypeToSuggestionFilterMapping) {
Map<String, String> filteredValues = datasetField.getDatasetFieldParent()
.getOrElseThrow(() -> new NullPointerException("datasetfield with type: " + datasetField.getTypeName() + " didn't have any parent required to generate suggestions"))
.getDatasetFieldsChildren().stream()
Expand All @@ -145,5 +166,4 @@ private Map<String, String> findFilterValues(DatasetField datasetField, Map<Stri

return filteredValues.containsValue(StringUtils.EMPTY) ? new HashMap<>() : filteredValues;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package edu.harvard.iq.dataverse.dataset.metadata.inputRenderer.suggestion;

import com.google.common.collect.Lists;
import edu.harvard.iq.dataverse.ControlledVocabularyValueServiceBean;
import edu.harvard.iq.dataverse.dataset.metadata.inputRenderer.Suggestion;

import javax.ejb.Stateless;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;

import static java.util.stream.Collectors.toList;

/**
* This suggestion handler provides suggestions based on controlled vocabulary values.
*
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
@Stateless
public class ControlledVocabularySuggestionHandler implements SuggestionHandler {

public static final String CONTROLLED_VOCABULARY_NAME_COLUMN = "controlledVocabularyName";
private ControlledVocabularyValueServiceBean controlledVocabularyValueServiceBean;

// -------------------- CONSTRUCTORS --------------------

@Deprecated
public ControlledVocabularySuggestionHandler() {
}

@Inject
public ControlledVocabularySuggestionHandler(ControlledVocabularyValueServiceBean controlledVocabularyValueServiceBean) {
this.controlledVocabularyValueServiceBean = controlledVocabularyValueServiceBean;
}

// -------------------- LOGIC --------------------

/**
* {@inheritDoc}
* <p>
* This implementation always returns class name.
*/
@Override
public String getName() {
return getClass().getSimpleName();
}


/**
* This is not dependent on siblings input values. All values will be taken
* to build matching suggestions list.
*/
@Override
public boolean isDependentOnSiblings() {
return false;
}

/**
* {@inheritDoc}
* <p>
* This implementation filers out base on controlled vocabulary (input) name,
* a.k.a. dictionary name
*/
@Override
public List<String> getAllowedFilters() {
return Lists.newArrayList(CONTROLLED_VOCABULARY_NAME_COLUMN);
}

@Override
public List<Suggestion> generateSuggestions(Map<String, String> filters, String suggestionSourceFieldValue) {
return controlledVocabularyValueServiceBean
.findByDatasetFieldTypeNameAndValueLike(filters.get(CONTROLLED_VOCABULARY_NAME_COLUMN), suggestionSourceFieldValue, 10)
.stream().map(vocabulary -> new Suggestion(vocabulary.getStrValue(), vocabulary.getIdentifier()))
.collect(toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

import static java.util.stream.Collectors.toList;

/**
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
@Stateless
public class GrantAgencyAcronymSuggestionHandler implements SuggestionHandler {

Expand Down Expand Up @@ -40,6 +43,17 @@ public String getName() {
return getClass().getSimpleName();
}

/**
* This suggestion is dependent on sibling input value.
* Only values that match pointed out sibling input value will be taken
* to create suggestion.
* @see this#getAllowedFilters()
*/
@Override
public boolean isDependentOnSiblings() {
return !getAllowedFilters().isEmpty();
}

/**
* {@inheritDoc}
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

import static java.util.stream.Collectors.toList;

/**
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
@Stateless
public class GrantAgencySuggestionHandler implements SuggestionHandler {

Expand Down Expand Up @@ -40,6 +43,15 @@ public String getName() {
return getClass().getSimpleName();
}

/**
* This suggestion is not dependent on siblings.
* All values will be taken to mach the suggestion string.
*/
@Override
public boolean isDependentOnSiblings() {
return !getAllowedFilters().isEmpty();
}

/**
* {@inheritDoc}
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
import com.google.common.collect.Lists;
import edu.harvard.iq.dataverse.dataset.metadata.inputRenderer.Suggestion;
import edu.harvard.iq.dataverse.persistence.dataset.suggestion.GrantSuggestion;
import edu.harvard.iq.dataverse.persistence.dataset.suggestion.GrantSuggestions;

import javax.ejb.Stateless;
import javax.inject.Inject;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.stream.Collectors.toList;

/**
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
@Stateless
public class GrantProgramSuggestionHandler implements SuggestionHandler {

Expand Down Expand Up @@ -42,6 +43,17 @@ public String getName() {
return getClass().getSimpleName();
}

/**
* This suggestion is dependent on sibling input value.
* Only values that match pointed out sibling input value will be taken
* to create suggestion.
* @see this#getAllowedFilters()
*/
@Override
public boolean isDependentOnSiblings() {
return !getAllowedFilters().isEmpty();
}

/**
* {@inheritDoc}
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

/**
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
@Stateless
public class RorSuggestionHandler implements SuggestionHandler {

Expand All @@ -35,6 +38,17 @@ public String getName() {
return getClass().getSimpleName();
}

/**
* This suggestion is dependent on sibling input value.
* Only values that match pointed out sibling input value will be taken
* to create suggestion.
* @see this#getAllowedFilters()
*/
@Override
public boolean isDependentOnSiblings() {
return !getAllowedFilters().isEmpty();
}

/**
* {@inheritDoc}
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@
import java.util.List;
import java.util.Map;

/**
* @author Filipe Dias Lewandowski, Krzysztof Mądry, Daniel Korbel, Sylwester Niewczas
*/
public interface SuggestionHandler {

/**
* Returns unique name of action handler.
*/
String getName();

/**
* @return true if this handler is dependent on siblings input values.
* If it is dependant it will use other input values
* to filter out while generating suggestions
*/
boolean isDependentOnSiblings();

/**
* Returns name of filters that this handler understands
* and can handle.
Expand Down
Loading

0 comments on commit 357ae52

Please sign in to comment.