CE-1955 Add search-by labels - e.g., exact-matches on a single-field used as the PVS's label... definitely not perfect, but a passable first-version for bulk-load to do PVS mapping

This commit is contained in:
2024-12-03 08:59:05 -06:00
parent 86f8e24d5f
commit 7cd3105ee6
4 changed files with 242 additions and 25 deletions

View File

@ -26,8 +26,12 @@ import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
@ -50,7 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.NotImplementedException; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/******************************************************************************* /*******************************************************************************
@ -61,6 +65,9 @@ public class SearchPossibleValueSourceAction
{ {
private static final QLogger LOG = QLogger.getLogger(SearchPossibleValueSourceAction.class); private static final QLogger LOG = QLogger.getLogger(SearchPossibleValueSourceAction.class);
private static final Set<String> warnedAboutUnexpectedValueField = Collections.synchronizedSet(new HashSet<>());
private static final Set<String> warnedAboutUnexpectedNoOfFieldsToSearchByLabel = Collections.synchronizedSet(new HashSet<>());
private QPossibleValueTranslator possibleValueTranslator; private QPossibleValueTranslator possibleValueTranslator;
@ -110,6 +117,7 @@ public class SearchPossibleValueSourceAction
List<Serializable> matchingIds = new ArrayList<>(); List<Serializable> matchingIds = new ArrayList<>();
List<?> inputIdsAsCorrectType = convertInputIdsToEnumIdType(possibleValueSource, input.getIdList()); List<?> inputIdsAsCorrectType = convertInputIdsToEnumIdType(possibleValueSource, input.getIdList());
Set<String> labels = null;
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues()) for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
{ {
@ -122,6 +130,18 @@ public class SearchPossibleValueSourceAction
match = true; match = true;
} }
} }
else if(input.getLabelList() != null)
{
if(labels == null)
{
labels = input.getLabelList().stream().filter(Objects::nonNull).map(l -> l.toLowerCase()).collect(Collectors.toSet());
}
if(labels.contains(possibleValue.getLabel().toLowerCase()))
{
match = true;
}
}
else else
{ {
if(StringUtils.hasContent(input.getSearchTerm())) if(StringUtils.hasContent(input.getSearchTerm()))
@ -168,22 +188,38 @@ public class SearchPossibleValueSourceAction
Object anIdFromTheEnum = possibleValueSource.getEnumValues().get(0).getId(); Object anIdFromTheEnum = possibleValueSource.getEnumValues().get(0).getId();
for(Serializable inputId : inputIdList)
{
Object properlyTypedId = null;
try
{
if(anIdFromTheEnum instanceof Integer) if(anIdFromTheEnum instanceof Integer)
{ {
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsInteger(id))); properlyTypedId = ValueUtils.getValueAsInteger(inputId);
} }
else if(anIdFromTheEnum instanceof String) else if(anIdFromTheEnum instanceof String)
{ {
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsString(id))); properlyTypedId = ValueUtils.getValueAsString(inputId);
} }
else if(anIdFromTheEnum instanceof Boolean) else if(anIdFromTheEnum instanceof Boolean)
{ {
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsBoolean(id))); properlyTypedId = ValueUtils.getValueAsBoolean(inputId);
} }
else else
{ {
LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName()); LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName());
} }
}
catch(Exception e)
{
LOG.debug("Error converting possible value id to expected id type", e, logPair("value", inputId));
}
if (properlyTypedId != null)
{
rs.add(properlyTypedId);
}
}
return (rs); return (rs);
} }
@ -209,6 +245,53 @@ public class SearchPossibleValueSourceAction
{ {
queryFilter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getIdList())); queryFilter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getIdList()));
} }
else if(input.getLabelList() != null)
{
List<String> fieldNames = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// the 'value fields' will either be 'id' or 'label' (which means, use the fields from the tableMetaData's label fields) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(String valueField : possibleValueSource.getValueFields())
{
if("id".equals(valueField))
{
fieldNames.add(table.getPrimaryKeyField());
}
else if("label".equals(valueField))
{
if(table.getRecordLabelFields() != null)
{
fieldNames.addAll(table.getRecordLabelFields());
}
}
else
{
String message = "Unexpected valueField defined in possibleValueSource when searching possibleValueSource by label (required: 'id' or 'label')";
if(!warnedAboutUnexpectedValueField.contains(possibleValueSource.getName()))
{
LOG.warn(message, logPair("valueField", valueField), logPair("possibleValueSource", possibleValueSource.getName()));
warnedAboutUnexpectedValueField.add(possibleValueSource.getName());
}
output.setWarning(message);
}
}
if(fieldNames.size() == 1)
{
queryFilter.addCriteria(new QFilterCriteria(fieldNames.get(0), QCriteriaOperator.IN, input.getLabelList()));
}
else
{
String message = "Unexpected number of fields found for searching possibleValueSource by label (required: 1, found: " + fieldNames.size() + ")";
if(!warnedAboutUnexpectedNoOfFieldsToSearchByLabel.contains(possibleValueSource.getName()))
{
LOG.warn(message);
warnedAboutUnexpectedNoOfFieldsToSearchByLabel.add(possibleValueSource.getName());
}
output.setWarning(message);
}
}
else else
{ {
String searchTerm = input.getSearchTerm(); String searchTerm = input.getSearchTerm();
@ -269,8 +352,8 @@ public class SearchPossibleValueSourceAction
queryFilter = input.getDefaultQueryFilter(); queryFilter = input.getDefaultQueryFilter();
} }
// todo - skip & limit as params queryFilter.setLimit(input.getLimit());
queryFilter.setLimit(250); queryFilter.setSkip(input.getSkip());
queryFilter.setOrderBys(possibleValueSource.getOrderByFields()); queryFilter.setOrderBys(possibleValueSource.getOrderByFields());
@ -301,7 +384,7 @@ public class SearchPossibleValueSourceAction
** **
*******************************************************************************/ *******************************************************************************/
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource) private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource) throws QException
{ {
try try
{ {
@ -314,11 +397,10 @@ public class SearchPossibleValueSourceAction
} }
catch(Exception e) catch(Exception e)
{ {
// LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e); String message = "Error sending searching custom possible value source [" + input.getPossibleValueSourceName() + "]";
LOG.warn(message, e);
throw (new QException(message));
} }
throw new NotImplementedException("Not impleemnted");
// return (null);
} }
} }

View File

@ -38,9 +38,10 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
private QQueryFilter defaultQueryFilter; private QQueryFilter defaultQueryFilter;
private String searchTerm; private String searchTerm;
private List<Serializable> idList; private List<Serializable> idList;
private List<String> labelList;
private Integer skip = 0; private Integer skip = 0;
private Integer limit = 100; private Integer limit = 250;
@ -281,4 +282,35 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
this.limit = limit; this.limit = limit;
return (this); return (this);
} }
/*******************************************************************************
** Getter for labelList
*******************************************************************************/
public List<String> getLabelList()
{
return (this.labelList);
}
/*******************************************************************************
** Setter for labelList
*******************************************************************************/
public void setLabelList(List<String> labelList)
{
this.labelList = labelList;
}
/*******************************************************************************
** Fluent setter for labelList
*******************************************************************************/
public SearchPossibleValueSourceInput withLabelList(List<String> labelList)
{
this.labelList = labelList;
return (this);
}
} }

View File

@ -35,6 +35,7 @@ public class SearchPossibleValueSourceOutput extends AbstractActionOutput
{ {
private List<QPossibleValue<?>> results = new ArrayList<>(); private List<QPossibleValue<?>> results = new ArrayList<>();
private String warning;
/******************************************************************************* /*******************************************************************************
@ -88,4 +89,35 @@ public class SearchPossibleValueSourceOutput extends AbstractActionOutput
return (this); return (this);
} }
/*******************************************************************************
** Getter for warning
*******************************************************************************/
public String getWarning()
{
return (this.warning);
}
/*******************************************************************************
** Setter for warning
*******************************************************************************/
public void setWarning(String warning)
{
this.warning = warning;
}
/*******************************************************************************
** Fluent setter for warning
*******************************************************************************/
public SearchPossibleValueSourceOutput withWarning(String warning)
{
this.warning = warning;
return (this);
}
} }

View File

@ -217,6 +217,63 @@ class SearchPossibleValueSourceActionTest extends BaseTest
} }
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_tableByLabels() throws QException
{
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of("Square", "Circle"), TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(2, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(2) && pv.getLabel().equals("Square"));
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(3) && pv.getLabel().equals("Circle"));
}
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of(), TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(0, output.getResults().size());
}
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of("notFound"), TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(0, output.getResults().size());
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_enumByLabel() throws QException
{
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of("IL", "MO", "XX"), TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(2, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(1) && pv.getLabel().equals("IL"));
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(2) && pv.getLabel().equals("MO"));
}
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of("Il", "mo", "XX"), TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(2, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(1) && pv.getLabel().equals("IL"));
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(2) && pv.getLabel().equals("MO"));
}
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of(), TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(0, output.getResults().size());
}
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByLabels(List.of("not-found"), TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(0, output.getResults().size());
}
}
/******************************************************************************* /*******************************************************************************
** **
@ -414,4 +471,18 @@ class SearchPossibleValueSourceActionTest extends BaseTest
return output; return output;
} }
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput getSearchPossibleValueSourceOutputByLabels(List<String> labels, String possibleValueSourceName) throws QException
{
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput();
input.setLabelList(labels);
input.setPossibleValueSourceName(possibleValueSourceName);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceAction().execute(input);
return output;
}
} }