Updating to support possible value searching

This commit is contained in:
2022-10-10 16:42:49 -05:00
parent 262038bc87
commit c43a8998ec
16 changed files with 1312 additions and 25 deletions

View File

@ -111,7 +111,35 @@ public class QPossibleValueTranslator
/******************************************************************************* /*******************************************************************************
** ** Translate a list of ids to a list of possible values (e.g., w/ rendered values)
*******************************************************************************/
public List<QPossibleValue<?>> buildTranslatedPossibleValueList(QPossibleValueSource possibleValueSource, List<Serializable> ids)
{
if(ids == null)
{
return (null);
}
if(ids.isEmpty())
{
return (new ArrayList<>());
}
List<QPossibleValue<?>> rs = new ArrayList<>();
primePvsCache(possibleValueSource.getTableName(), List.of(possibleValueSource), ids);
for(Serializable id : ids)
{
String translated = translatePossibleValue(possibleValueSource, id);
rs.add(new QPossibleValue<>(id, translated));
}
return (rs);
}
/*******************************************************************************
** For a given field and (raw/id) value, get the translated (string) value.
*******************************************************************************/ *******************************************************************************/
String translatePossibleValue(QFieldMetaData field, Serializable value) String translatePossibleValue(QFieldMetaData field, Serializable value)
{ {
@ -122,6 +150,16 @@ public class QPossibleValueTranslator
return (null); return (null);
} }
return translatePossibleValue(possibleValueSource, value);
}
/*******************************************************************************
** For a given PossibleValueSource and (raw/id) value, get the translated (string) value.
*******************************************************************************/
String translatePossibleValue(QPossibleValueSource possibleValueSource, Serializable value)
{
String resultValue = null; String resultValue = null;
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM)) if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
{ {
@ -129,15 +167,15 @@ public class QPossibleValueTranslator
} }
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE)) else if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
{ {
resultValue = translatePossibleValueTable(field, value, possibleValueSource); resultValue = translatePossibleValueTable(value, possibleValueSource);
} }
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.CUSTOM)) else if(possibleValueSource.getType().equals(QPossibleValueSourceType.CUSTOM))
{ {
resultValue = translatePossibleValueCustom(field, value, possibleValueSource); resultValue = translatePossibleValueCustom(value, possibleValueSource);
} }
else else
{ {
LOG.error("Unrecognized possibleValueSourceType [" + possibleValueSource.getType() + "] in PVS named [" + possibleValueSource.getName() + "] on field [" + field.getName() + "]"); LOG.error("Unrecognized possibleValueSourceType [" + possibleValueSource.getType() + "] in PVS named [" + possibleValueSource.getName() + "]");
} }
if(resultValue == null) if(resultValue == null)
@ -151,7 +189,7 @@ public class QPossibleValueTranslator
/******************************************************************************* /*******************************************************************************
** ** do translation for an enum-type PVS
*******************************************************************************/ *******************************************************************************/
private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource) private String translatePossibleValueEnum(Serializable value, QPossibleValueSource possibleValueSource)
{ {
@ -169,9 +207,9 @@ public class QPossibleValueTranslator
/******************************************************************************* /*******************************************************************************
** ** do translation for a table-type PVS
*******************************************************************************/ *******************************************************************************/
private String translatePossibleValueTable(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource) String translatePossibleValueTable(Serializable value, QPossibleValueSource possibleValueSource)
{ {
///////////////////////////////// /////////////////////////////////
// null input gets null output // // null input gets null output //
@ -197,9 +235,9 @@ public class QPossibleValueTranslator
/******************************************************************************* /*******************************************************************************
** ** do translation for a custom-type PVS
*******************************************************************************/ *******************************************************************************/
private String translatePossibleValueCustom(QFieldMetaData field, Serializable value, QPossibleValueSource possibleValueSource) private String translatePossibleValueCustom(Serializable value, QPossibleValueSource possibleValueSource)
{ {
try try
{ {
@ -208,7 +246,7 @@ public class QPossibleValueTranslator
} }
catch(Exception e) catch(Exception e)
{ {
LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e); LOG.warn("Error sending [" + value + "] for through custom code for PVS [" + possibleValueSource.getName() + "]", e);
} }
return (null); return (null);

View File

@ -0,0 +1,244 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Class responsible for looking up possible-values for fields/records and
** make them into display values.
*******************************************************************************/
public class SearchPossibleValueSourceAction
{
private static final Logger LOG = LogManager.getLogger(SearchPossibleValueSourceAction.class);
private QPossibleValueTranslator possibleValueTranslator;
/*******************************************************************************
**
*******************************************************************************/
public SearchPossibleValueSourceOutput execute(SearchPossibleValueSourceInput input) throws QException
{
QInstance qInstance = input.getInstance();
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(input.getPossibleValueSourceName());
if(possibleValueSource == null)
{
throw new QException("Missing possible value source named [" + input.getPossibleValueSourceName() + "]");
}
possibleValueTranslator = new QPossibleValueTranslator(input.getInstance(), input.getSession());
SearchPossibleValueSourceOutput output = null;
if(possibleValueSource.getType().equals(QPossibleValueSourceType.ENUM))
{
output = searchPossibleValueEnum(input, possibleValueSource);
}
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
{
output = searchPossibleValueTable(input, possibleValueSource);
}
else if(possibleValueSource.getType().equals(QPossibleValueSourceType.CUSTOM))
{
output = searchPossibleValueCustom(input, possibleValueSource);
}
else
{
LOG.error("Unrecognized possibleValueSourceType [" + possibleValueSource.getType() + "] in PVS named [" + possibleValueSource.getName() + "]");
}
return (output);
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput searchPossibleValueEnum(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
{
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
List<Serializable> matchingIds = new ArrayList<>();
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
{
boolean match = false;
if(input.getIdList() != null)
{
if(input.getIdList().contains(possibleValue.getId()))
{
match = true;
}
}
else
{
if(StringUtils.hasContent(input.getSearchTerm()))
{
match = (Objects.equals(ValueUtils.getValueAsString(possibleValue.getId()).toLowerCase(), input.getSearchTerm().toLowerCase())
|| possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase()));
}
else
{
match = true;
}
}
if(match)
{
matchingIds.add((Serializable) possibleValue.getId());
}
// todo - skip & limit?
// todo - default filter
}
List<QPossibleValue<?>> qPossibleValues = possibleValueTranslator.buildTranslatedPossibleValueList(possibleValueSource, matchingIds);
output.setResults(qPossibleValues);
return (output);
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput searchPossibleValueTable(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource) throws QException
{
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
QueryInput queryInput = new QueryInput(input.getInstance());
queryInput.setSession(input.getSession());
queryInput.setTableName(possibleValueSource.getTableName());
QTableMetaData table = input.getInstance().getTable(possibleValueSource.getTableName());
QQueryFilter queryFilter = new QQueryFilter();
queryFilter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
queryInput.setFilter(queryFilter);
if(input.getIdList() != null)
{
queryFilter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getIdList()));
}
else
{
if(StringUtils.hasContent(input.getSearchTerm()))
{
for(String valueField : possibleValueSource.getSearchFields())
{
QFieldMetaData field = table.getField(valueField);
if(field.getType().equals(QFieldType.STRING))
{
queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.STARTS_WITH, List.of(input.getSearchTerm())));
}
else if(field.getType().equals(QFieldType.DATE) || field.getType().equals(QFieldType.DATE_TIME))
{
LOG.debug("Not querying PVS [" + possibleValueSource.getName() + "] on date field [" + field.getName() + "]");
// todo - what? queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.STARTS_WITH, List.of(input.getSearchTerm())));
}
else
{
try
{
Integer valueAsInteger = ValueUtils.getValueAsInteger(input.getSearchTerm());
if(valueAsInteger != null)
{
queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.EQUALS, List.of(valueAsInteger)));
}
}
catch(Exception e)
{
////////////////////////////////////////////////////////
// write a FALSE criteria if the value isn't a number //
////////////////////////////////////////////////////////
queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.IN, List.of()));
}
}
}
}
}
queryFilter.setOrderBys(possibleValueSource.getOrderByFields());
// todo - default filter
// todo - skip & limit as params
queryInput.setLimit(250);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
List<Serializable> ids = queryOutput.getRecords().stream().map(r -> r.getValue(table.getPrimaryKeyField())).toList();
List<QPossibleValue<?>> qPossibleValues = possibleValueTranslator.buildTranslatedPossibleValueList(possibleValueSource, ids);
output.setResults(qPossibleValues);
return (output);
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
{
try
{
// QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
// return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
}
catch(Exception e)
{
// LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
}
throw new NotImplementedException("Not impleemnted");
// return (null);
}
}

View File

@ -121,6 +121,11 @@ public class QInstanceEnricher
{ {
qInstance.getReports().values().forEach(this::enrichReport); qInstance.getReports().values().forEach(this::enrichReport);
} }
if(qInstance.getPossibleValueSources() != null)
{
qInstance.getPossibleValueSources().values().forEach(this::enrichPossibleValueSource);
}
} }
@ -748,4 +753,50 @@ public class QInstanceEnricher
} }
} }
/*******************************************************************************
**
*******************************************************************************/
private void enrichPossibleValueSource(QPossibleValueSource possibleValueSource)
{
if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()))
{
if(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getSearchFields()))
{
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
if(table != null)
{
if(table.getPrimaryKeyField() != null)
{
possibleValueSource.withSearchField(table.getPrimaryKeyField());
}
for(String recordLabelField : CollectionUtils.nonNullList(table.getRecordLabelFields()))
{
possibleValueSource.withSearchField(recordLabelField);
}
}
}
if(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getOrderByFields()))
{
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
if(table != null)
{
for(String recordLabelField : CollectionUtils.nonNullList(table.getRecordLabelFields()))
{
possibleValueSource.withOrderByField(recordLabelField);
}
if(table.getPrimaryKeyField() != null)
{
possibleValueSource.withOrderByField(table.getPrimaryKeyField());
}
}
}
}
}
} }

View File

@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider; import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
@ -715,6 +716,8 @@ public class QInstanceValidator
case ENUM -> case ENUM ->
{ {
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName."); assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + pvsName + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + pvsName + " should not have orderByFields.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference."); assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values"); assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
@ -724,15 +727,44 @@ public class QInstanceValidator
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values."); assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference."); assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
QTableMetaData tableMetaData = null;
if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName.")) if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
{ {
assertCondition(qInstance.getTable(possibleValueSource.getTableName()) != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + "."); tableMetaData = qInstance.getTable(possibleValueSource.getTableName());
assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + pvsName + " is missing searchFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(String searchField : possibleValueSource.getSearchFields())
{
assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + pvsName + " has an unrecognized searchField: " + searchField);
}
}
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + pvsName + " is missing orderByFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields())
{
assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + pvsName + " has an unrecognized orderByField: " + orderByField.getFieldName());
}
}
} }
} }
case CUSTOM -> case CUSTOM ->
{ {
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values."); assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName."); assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + pvsName + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + pvsName + " should not have orderByFields.");
if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference.")) if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
{ {

View File

@ -0,0 +1,268 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.values;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Input for the Search possible value source action
*******************************************************************************/
public class SearchPossibleValueSourceInput extends AbstractActionInput
{
private String possibleValueSourceName;
private QQueryFilter defaultQueryFilter;
private String searchTerm;
private List<Serializable> idList;
private Integer skip = 0;
private Integer limit = 100;
/*******************************************************************************
**
*******************************************************************************/
public SearchPossibleValueSourceInput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public SearchPossibleValueSourceInput(QInstance instance)
{
super(instance);
}
/*******************************************************************************
** Getter for possibleValueSourceName
**
*******************************************************************************/
public String getPossibleValueSourceName()
{
return possibleValueSourceName;
}
/*******************************************************************************
** Setter for possibleValueSourceName
**
*******************************************************************************/
public void setPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
}
/*******************************************************************************
** Fluent setter for possibleValueSourceName
**
*******************************************************************************/
public SearchPossibleValueSourceInput withPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
return (this);
}
/*******************************************************************************
** Getter for defaultQueryFilter
**
*******************************************************************************/
public QQueryFilter getDefaultQueryFilter()
{
return defaultQueryFilter;
}
/*******************************************************************************
** Setter for defaultQueryFilter
**
*******************************************************************************/
public void setDefaultQueryFilter(QQueryFilter defaultQueryFilter)
{
this.defaultQueryFilter = defaultQueryFilter;
}
/*******************************************************************************
** Fluent setter for defaultQueryFilter
**
*******************************************************************************/
public SearchPossibleValueSourceInput withDefaultQueryFilter(QQueryFilter defaultQueryFilter)
{
this.defaultQueryFilter = defaultQueryFilter;
return (this);
}
/*******************************************************************************
** Getter for searchTerm
**
*******************************************************************************/
public String getSearchTerm()
{
return searchTerm;
}
/*******************************************************************************
** Setter for searchTerm
**
*******************************************************************************/
public void setSearchTerm(String searchTerm)
{
this.searchTerm = searchTerm;
}
/*******************************************************************************
** Fluent setter for searchTerm
**
*******************************************************************************/
public SearchPossibleValueSourceInput withSearchTerm(String searchTerm)
{
this.searchTerm = searchTerm;
return (this);
}
/*******************************************************************************
** Getter for idList
**
*******************************************************************************/
public List<Serializable> getIdList()
{
return idList;
}
/*******************************************************************************
** Setter for idList
**
*******************************************************************************/
public void setIdList(List<Serializable> idList)
{
this.idList = idList;
}
/*******************************************************************************
** Fluent setter for idList
**
*******************************************************************************/
public SearchPossibleValueSourceInput withIdList(List<Serializable> idList)
{
this.idList = idList;
return (this);
}
/*******************************************************************************
** Getter for skip
**
*******************************************************************************/
public Integer getSkip()
{
return skip;
}
/*******************************************************************************
** Setter for skip
**
*******************************************************************************/
public void setSkip(Integer skip)
{
this.skip = skip;
}
/*******************************************************************************
** Fluent setter for skip
**
*******************************************************************************/
public SearchPossibleValueSourceInput withSkip(Integer skip)
{
this.skip = skip;
return (this);
}
/*******************************************************************************
** Getter for limit
**
*******************************************************************************/
public Integer getLimit()
{
return limit;
}
/*******************************************************************************
** Setter for limit
**
*******************************************************************************/
public void setLimit(Integer limit)
{
this.limit = limit;
}
/*******************************************************************************
** Fluent setter for limit
**
*******************************************************************************/
public SearchPossibleValueSourceInput withLimit(Integer limit)
{
this.limit = limit;
return (this);
}
}

View File

@ -0,0 +1,91 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.values;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
/*******************************************************************************
** Output for the Search possible value source action
*******************************************************************************/
public class SearchPossibleValueSourceOutput extends AbstractActionOutput
{
private List<QPossibleValue<?>> results = new ArrayList<>();
/*******************************************************************************
**
*******************************************************************************/
public SearchPossibleValueSourceOutput()
{
}
/*******************************************************************************
**
*******************************************************************************/
public void addResult(QPossibleValue<?> possibleValue)
{
results.add(possibleValue);
}
/*******************************************************************************
** Getter for results
**
*******************************************************************************/
public List<QPossibleValue<?>> getResults()
{
return results;
}
/*******************************************************************************
** Setter for results
**
*******************************************************************************/
public void setResults(List<QPossibleValue<?>> results)
{
this.results = results;
}
/*******************************************************************************
** Fluent setter for results
**
*******************************************************************************/
public SearchPossibleValueSourceOutput withResults(List<QPossibleValue<?>> results)
{
this.results = results;
return (this);
}
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.possiblevalues;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
@ -49,7 +50,8 @@ public class QPossibleValueSource
// for type = TABLE // // for type = TABLE //
////////////////////// //////////////////////
private String tableName; private String tableName;
// todo - override labelFormat & labelFields? private List<String> searchFields;
private List<QFilterOrderBy> orderByFields;
///////////////////// /////////////////////
// for type = ENUM // // for type = ENUM //
@ -304,6 +306,128 @@ public class QPossibleValueSource
/*******************************************************************************
** Getter for searchFields
**
*******************************************************************************/
public List<String> getSearchFields()
{
return searchFields;
}
/*******************************************************************************
** Setter for searchFields
**
*******************************************************************************/
public void setSearchFields(List<String> searchFields)
{
this.searchFields = searchFields;
}
/*******************************************************************************
** Fluent setter for searchFields
**
*******************************************************************************/
public QPossibleValueSource withSearchFields(List<String> searchFields)
{
this.searchFields = searchFields;
return (this);
}
/*******************************************************************************
** Fluent setter for searchFields
**
*******************************************************************************/
public QPossibleValueSource withSearchField(String searchField)
{
if(this.searchFields == null)
{
this.searchFields = new ArrayList<>();
}
this.searchFields.add(searchField);
return (this);
}
/*******************************************************************************
** Getter for orderByFields
**
*******************************************************************************/
public List<QFilterOrderBy> getOrderByFields()
{
return orderByFields;
}
/*******************************************************************************
** Setter for orderByFields
**
*******************************************************************************/
public void setOrderByFields(List<QFilterOrderBy> orderByFields)
{
this.orderByFields = orderByFields;
}
/*******************************************************************************
** Fluent setter for orderByFields
**
*******************************************************************************/
public QPossibleValueSource withOrderByFields(List<QFilterOrderBy> orderByFields)
{
this.orderByFields = orderByFields;
return (this);
}
/*******************************************************************************
** Fluent setter for orderByFields
**
*******************************************************************************/
public QPossibleValueSource withOrderByField(QFilterOrderBy orderByField)
{
if(this.orderByFields == null)
{
this.orderByFields = new ArrayList<>();
}
this.orderByFields.add(orderByField);
return (this);
}
/*******************************************************************************
** Fluent setter for orderByFields - default to ASCENDING
**
*******************************************************************************/
public QPossibleValueSource withOrderByField(String fieldName)
{
return (withOrderByField(new QFilterOrderBy(fieldName)));
}
/*******************************************************************************
** Fluent setter for orderByFields
**
*******************************************************************************/
public QPossibleValueSource withOrderByField(String fieldName, boolean isAscending)
{
return (withOrderByField(new QFilterOrderBy(fieldName, isAscending)));
}
/******************************************************************************* /*******************************************************************************
** Getter for enumValues ** Getter for enumValues
** **

View File

@ -364,7 +364,17 @@ public class MemoryRecordStore
return (false); return (false);
} }
if(!value.equals(criterion.getValues().get(0))) Serializable criteriaValue = criterion.getValues().get(0);
if(value instanceof String && criteriaValue instanceof Number)
{
criteriaValue = String.valueOf(criteriaValue);
}
else if(criteriaValue instanceof String && value instanceof Number)
{
value = String.valueOf(value);
}
if(!value.equals(criteriaValue))
{ {
return (false); return (false);
} }

View File

@ -454,8 +454,15 @@ public class ValueUtils
return (null); return (null);
} }
try
{
return Instant.parse(s); return Instant.parse(s);
} }
catch(DateTimeParseException e)
{
return tryAlternativeInstantParsing(s, e);
}
}
else else
{ {
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Instant.")); throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Instant."));
@ -473,6 +480,26 @@ public class ValueUtils
/*******************************************************************************
**
*******************************************************************************/
private static Instant tryAlternativeInstantParsing(String s, DateTimeParseException e)
{
if(s.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$"))
{
//////////////////////////
// todo ... time zone?? //
//////////////////////////
return Instant.parse(s + ":00Z");
}
else
{
throw (e);
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -0,0 +1,243 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceOutput;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for SearchPossibleValueSourceAction
*******************************************************************************/
class SearchPossibleValueSourceActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach() throws QException
{
MemoryRecordStore.getInstance().reset();
TestUtils.insertDefaultShapes(TestUtils.defineInstance());
}
/*******************************************************************************
**
*******************************************************************************/
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " " })
void testSearchPvsAction_enumNullAndEmptySearchTerms(String searchTerm) throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutput(searchTerm, 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"));
}
/*******************************************************************************
**
*******************************************************************************/
@ParameterizedTest
@ValueSource(strings = { "I", "IL", "1", "i", "iL", "il" })
void testSearchPvsAction_enumMatchesForIL(String searchTerm) throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutput(searchTerm, TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(1, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(1) && pv.getLabel().equals("IL"));
}
/*******************************************************************************
**
*******************************************************************************/
@ParameterizedTest
@ValueSource(strings = { "3", "ILL" })
void testSearchPvsAction_enumMatchesNothing(String searchTerm) throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutput(searchTerm, TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(0, output.getResults().size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_enumById() throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputById(2, TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(1, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(2) && pv.getLabel().equals("MO"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_enumByIdNotFound() throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputById(-1, TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
assertEquals(0, output.getResults().size());
}
/*******************************************************************************
**
*******************************************************************************/
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " " })
void testSearchPvsAction_tableNullAndEmptySearchTerms(String searchTerm) throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutput(searchTerm, TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(3, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(1) && pv.getLabel().equals("Triangle"));
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"));
}
/*******************************************************************************
**
*******************************************************************************/
@ParameterizedTest
@ValueSource(strings = { "1", "Triangle" })
void testSearchPvsAction_tableMatchesOne(String searchTerm) throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutput(searchTerm, TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(1, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(1) && pv.getLabel().equals("Triangle"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_tableById() throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputById(2, TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(1, output.getResults().size());
assertThat(output.getResults()).anyMatch(pv -> pv.getId().equals(2) && pv.getLabel().equals("Square"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_tableByIds() throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputByIds(List.of(2, 3), 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"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSearchPvsAction_tableByIdNotFound() throws QException
{
SearchPossibleValueSourceOutput output = getSearchPossibleValueSourceOutputById(-1, TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
assertEquals(0, output.getResults().size());
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput getSearchPossibleValueSourceOutput(String searchTerm, String possibleValueSourceName) throws QException
{
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput(TestUtils.defineInstance());
input.setSession(new QSession());
input.setSearchTerm(searchTerm);
input.setPossibleValueSourceName(possibleValueSourceName);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceAction().execute(input);
return output;
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput getSearchPossibleValueSourceOutputById(Serializable id, String possibleValueSourceName) throws QException
{
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput(TestUtils.defineInstance());
input.setSession(new QSession());
input.setIdList(List.of(id));
input.setPossibleValueSourceName(possibleValueSourceName);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceAction().execute(input);
return output;
}
/*******************************************************************************
**
*******************************************************************************/
private SearchPossibleValueSourceOutput getSearchPossibleValueSourceOutputByIds(List<Serializable> ids, String possibleValueSourceName) throws QException
{
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput(TestUtils.defineInstance());
input.setSession(new QSession());
input.setIdList(ids);
input.setPossibleValueSourceName(possibleValueSourceName);
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceAction().execute(input);
return output;
}
}

View File

@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInpu
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -793,10 +794,14 @@ class QInstanceValidatorTest
assertValidationFailureReasons((qInstance) -> { assertValidationFailureReasons((qInstance) -> {
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE); QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_STATE);
possibleValueSource.setTableName("person"); possibleValueSource.setTableName("person");
possibleValueSource.setSearchFields(List.of("id"));
possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id")));
possibleValueSource.setCustomCodeReference(new QCodeReference()); possibleValueSource.setCustomCodeReference(new QCodeReference());
possibleValueSource.setEnumValues(null); possibleValueSource.setEnumValues(null);
}, },
"should not have a tableName", "should not have a tableName",
"should not have searchFields",
"should not have orderByFields",
"should not have a customCodeReference", "should not have a customCodeReference",
"is missing enum values"); "is missing enum values");
@ -815,15 +820,22 @@ class QInstanceValidatorTest
assertValidationFailureReasons((qInstance) -> { assertValidationFailureReasons((qInstance) -> {
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE); QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE);
possibleValueSource.setTableName(null); possibleValueSource.setTableName(null);
possibleValueSource.setSearchFields(null);
possibleValueSource.setOrderByFields(new ArrayList<>());
possibleValueSource.setCustomCodeReference(new QCodeReference()); possibleValueSource.setCustomCodeReference(new QCodeReference());
possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test"))); possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test")));
}, },
"should not have enum values", "should not have enum values",
"should not have a customCodeReference", "should not have a customCodeReference",
"is missing a tableName"); "is missing a tableName",
"is missing searchFields",
"is missing orderByFields");
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE).setTableName("Not a table"), assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE).setTableName("Not a table"),
"Unrecognized table"); "Unrecognized table");
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_SHAPE).setSearchFields(List.of("id", "notAField", "name")),
"unrecognized searchField: notAField");
} }
@ -837,11 +849,15 @@ class QInstanceValidatorTest
assertValidationFailureReasons((qInstance) -> { assertValidationFailureReasons((qInstance) -> {
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM); QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM);
possibleValueSource.setTableName("person"); possibleValueSource.setTableName("person");
possibleValueSource.setSearchFields(List.of("id"));
possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id")));
possibleValueSource.setCustomCodeReference(null); possibleValueSource.setCustomCodeReference(null);
possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test"))); possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test")));
}, },
"should not have enum values", "should not have enum values",
"should not have a tableName", "should not have a tableName",
"should not have searchFields",
"should not have orderByFields",
"is missing a customCodeReference"); "is missing a customCodeReference");
assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM).setCustomCodeReference(new QCodeReference()), assertValidationFailureReasons((qInstance) -> qInstance.getPossibleValueSource(TestUtils.POSSIBLE_VALUE_SOURCE_CUSTOM).setCustomCodeReference(new QCodeReference()),

View File

@ -331,7 +331,9 @@ public class TestUtils
return new QPossibleValueSource() return new QPossibleValueSource()
.withName(POSSIBLE_VALUE_SOURCE_SHAPE) .withName(POSSIBLE_VALUE_SOURCE_SHAPE)
.withType(QPossibleValueSourceType.TABLE) .withType(QPossibleValueSourceType.TABLE)
.withTableName(TABLE_NAME_SHAPE); .withTableName(TABLE_NAME_SHAPE)
.withSearchFields(List.of("id", "name"))
.withOrderByField("name");
} }

View File

@ -28,6 +28,7 @@ import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -46,6 +47,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAction;
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter; import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException; import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
@ -76,10 +78,13 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceOutput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule; import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule;
@ -277,6 +282,7 @@ public class QJavalinImplementation
get("/count", QJavalinImplementation::dataCount); get("/count", QJavalinImplementation::dataCount);
get("/export", QJavalinImplementation::dataExportWithoutFilename); get("/export", QJavalinImplementation::dataExportWithoutFilename);
get("/export/{filename}", QJavalinImplementation::dataExportWithFilename); get("/export/{filename}", QJavalinImplementation::dataExportWithFilename);
get("/possibleValues/{fieldName}", QJavalinImplementation::possibleValues);
// todo - add put and/or patch at this level (without a primaryKey) to do a bulk update based on primaryKeys in the records. // todo - add put and/or patch at this level (without a primaryKey) to do a bulk update based on primaryKeys in the records.
path("/{primaryKey}", () -> path("/{primaryKey}", () ->
@ -864,6 +870,64 @@ public class QJavalinImplementation
/*******************************************************************************
**
*******************************************************************************/
private static void possibleValues(Context context)
{
try
{
String tableName = context.pathParam("table");
String fieldName = context.pathParam("fieldName");
String searchTerm = context.queryParam("searchTerm");
String ids = context.queryParam("ids");
QTableMetaData table = qInstance.getTable(tableName);
if(table == null)
{
throw (new QNotFoundException("Could not find table named " + tableName + " in this instance."));
}
QFieldMetaData field;
try
{
field = table.getField(fieldName);
}
catch(Exception e)
{
throw (new QNotFoundException("Could not find field named " + fieldName + " in table " + tableName + "."));
}
if(!StringUtils.hasContent(field.getPossibleValueSourceName()))
{
throw (new QNotFoundException("Field " + fieldName + " in table " + tableName + " is not associated with a possible value source."));
}
SearchPossibleValueSourceInput input = new SearchPossibleValueSourceInput(qInstance);
setupSession(context, input);
input.setPossibleValueSourceName(field.getPossibleValueSourceName());
input.setSearchTerm(searchTerm);
if(StringUtils.hasContent(ids))
{
List<Serializable> idList = new ArrayList<>(Arrays.asList(ids.split(",")));
input.setIdList(idList);
}
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceAction().execute(input);
Map<String, Object> result = new HashMap<>();
result.put("options", output.getResults());
context.result(JsonUtils.toJson(result));
}
catch(Exception e)
{
handleException(context, e);
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -497,4 +497,57 @@ class QJavalinImplementationTest extends QJavalinTestBase
assertNotNull(jsonObject.getJSONObject("chartData")); assertNotNull(jsonObject.getJSONObject("chartData"));
} }
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPossibleValueUnfiltered()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/possibleValues/partnerPersonId").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertNotNull(jsonObject.getJSONArray("options"));
assertEquals(5, jsonObject.getJSONArray("options").length());
assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id"));
assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPossibleValueWithSearchTerm()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/possibleValues/partnerPersonId?searchTerm=Chamber").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertNotNull(jsonObject.getJSONArray("options"));
assertEquals(1, jsonObject.getJSONArray("options").length());
assertEquals("Tim Chamberlain (3)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPossibleValueWithIds()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/possibleValues/partnerPersonId?ids=4,5").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertNotNull(jsonObject.getJSONArray("options"));
assertEquals(2, jsonObject.getJSONArray("options").length());
assertEquals(4, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id"));
assertEquals(5, jsonObject.getJSONArray("options").getJSONObject(1).getInt("id"));
}
} }

View File

@ -25,28 +25,31 @@ package com.kingsrook.qqq.backend.javalin;
import java.io.InputStream; import java.io.InputStream;
import java.sql.Connection; import java.sql.Connection;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
@ -123,6 +126,7 @@ public class TestUtils
qInstance.addProcess(defineProcessSimpleSleep()); qInstance.addProcess(defineProcessSimpleSleep());
qInstance.addProcess(defineProcessScreenThenSleep()); qInstance.addProcess(defineProcessScreenThenSleep());
qInstance.addProcess(defineProcessSimpleThrow()); qInstance.addProcess(defineProcessSimpleThrow());
qInstance.addPossibleValueSource(definePossibleValueSourcePerson());
defineWidgets(qInstance); defineWidgets(qInstance);
return (qInstance); return (qInstance);
} }
@ -181,6 +185,8 @@ public class TestUtils
return new QTableMetaData() return new QTableMetaData()
.withName("person") .withName("person")
.withLabel("Person") .withLabel("Person")
.withRecordLabelFormat("%s %s")
.withRecordLabelFields("firstName", "lastName")
.withBackendName(defineBackend().getName()) .withBackendName(defineBackend().getName())
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER))
@ -189,6 +195,7 @@ public class TestUtils
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name")) .withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name"))
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name")) .withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name"))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date")) .withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
.withField(new QFieldMetaData("partnerPersonId", QFieldType.INTEGER).withBackendName("partner_person_id").withPossibleValueSourceName("person"))
.withField(new QFieldMetaData("email", QFieldType.STRING)); .withField(new QFieldMetaData("email", QFieldType.STRING));
} }
@ -268,6 +275,22 @@ public class TestUtils
/*******************************************************************************
**
*******************************************************************************/
private static QPossibleValueSource definePossibleValueSourcePerson()
{
return (new QPossibleValueSource()
.withName("person")
.withType(QPossibleValueSourceType.TABLE)
.withTableName("person")
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_PARENS_ID)
.withOrderByField("id")
);
}
/******************************************************************************* /*******************************************************************************
** Define a process with just one step that sleeps ** Define a process with just one step that sleeps
*******************************************************************************/ *******************************************************************************/

View File

@ -29,7 +29,8 @@ CREATE TABLE person
first_name VARCHAR(80) NOT NULL, first_name VARCHAR(80) NOT NULL,
last_name VARCHAR(80) NOT NULL, last_name VARCHAR(80) NOT NULL,
birth_date DATE, birth_date DATE,
email VARCHAR(250) NOT NULL email VARCHAR(250) NOT NULL,
partner_person_id INT
); );
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com'); INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com');