mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
10 Commits
snapshot-i
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
6e3ef2254a | |||
b03de8ec0f | |||
a9999ee8ce | |||
f50e6d1a94 | |||
2a68478405 | |||
459510bba4 | |||
18e1852ce4 | |||
0dd7f5e1d2 | |||
601c66ddff | |||
e1ca85c746 |
@ -65,13 +65,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.Automatio
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.json.JSONObject;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -388,13 +389,15 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
if(filterId != null)
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(SavedFilter.TABLE_NAME);
|
||||
getInput.setTableName(SavedView.TABLE_NAME);
|
||||
getInput.setPrimaryKey(filterId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
SavedFilter savedFilter = new SavedFilter(getOutput.getRecord());
|
||||
filter = JsonUtils.toObject(savedFilter.getFilterJson(), QQueryFilter.class);
|
||||
SavedView savedView = new SavedView(getOutput.getRecord());
|
||||
JSONObject viewJson = new JSONObject(savedView.getViewJson());
|
||||
JSONObject queryFilter = viewJson.getJSONObject("queryFilter");
|
||||
filter = JsonUtils.toObject(queryFilter.toString(), QQueryFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.QQueryFilterDeduper;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -176,11 +177,13 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
return (totalString);
|
||||
}
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return ("<a href='" + tablePath + "?filter=" + JsonUtils.toJson(filter) + "'>" + totalString + "</a>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -192,6 +195,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
return;
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
urls.add(tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||
}
|
||||
|
||||
@ -208,6 +212,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
return (null);
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||
}
|
||||
|
||||
@ -224,6 +229,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
return (null);
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
@ -326,6 +332,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "/" + processName + "?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
@ -297,4 +297,21 @@ public enum DateTimeGroupBy
|
||||
ZonedDateTime zoned = instant.atZone(zoneId);
|
||||
return (zoned.plus(noOfChronoUnitsToAdd, chronoUnitToAdd).toInstant());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static DateTimeFormatter sqlDateFormatToSelectedDateTimeFormatter(String sqlDateFormat)
|
||||
{
|
||||
for(DateTimeGroupBy value : values())
|
||||
{
|
||||
if(value.sqlDateFormat.equals(sqlDateFormat))
|
||||
{
|
||||
return (value.selectedStringFormatter);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
@ -87,6 +88,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeLambda;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -493,6 +495,11 @@ public class QInstanceValidator
|
||||
validateTableRecordSecurityLocks(qInstance, table);
|
||||
validateTableAssociations(qInstance, table);
|
||||
validateExposedJoins(qInstance, joinGraph, table);
|
||||
|
||||
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
|
||||
{
|
||||
supplementalTableMetaData.validate(qInstance, table, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1784,20 +1791,6 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
interface UnsafeLambda
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||
@ -346,4 +347,37 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QFilterCriteria that = (QFilterCriteria) o;
|
||||
return Objects.equals(fieldName, that.fieldName) && operator == that.operator && Objects.equals(values, that.values) && Objects.equals(otherFieldName, that.otherFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(fieldName, operator, values, otherFieldName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -467,4 +468,36 @@ public class QQueryFilter implements Serializable, Cloneable
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QQueryFilter that = (QQueryFilter) o;
|
||||
return Objects.equals(criteria, that.criteria) && Objects.equals(orderBys, that.orderBys) && booleanOperator == that.booleanOperator && Objects.equals(subFilters, that.subFilters) && Objects.equals(skip, that.skip) && Objects.equals(limit, that.limit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(criteria, orderBys, booleanOperator, subFilters, skip, limit);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ public class TableTrigger extends QRecordEntity
|
||||
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME)
|
||||
private String tableName;
|
||||
|
||||
@QField(possibleValueSourceName = SavedFilter.TABLE_NAME)
|
||||
@QField(possibleValueSourceName = SavedView.TABLE_NAME)
|
||||
private Integer filterId;
|
||||
|
||||
@QField(possibleValueSourceName = Script.TABLE_NAME)
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
@ -69,4 +70,16 @@ public abstract class QSupplementalTableMetaData
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QTableMetaData tableMetaData, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.savedfilters;
|
||||
package com.kingsrook.qqq.backend.core.model.savedviews;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
@ -32,9 +32,9 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
/*******************************************************************************
|
||||
** Entity bean for the saved filter table
|
||||
*******************************************************************************/
|
||||
public class SavedFilter extends QRecordEntity
|
||||
public class SavedView extends QRecordEntity
|
||||
{
|
||||
public static final String TABLE_NAME = "savedFilter";
|
||||
public static final String TABLE_NAME = "savedView";
|
||||
|
||||
@QField(isEditable = false)
|
||||
private Integer id;
|
||||
@ -55,7 +55,7 @@ public class SavedFilter extends QRecordEntity
|
||||
private String userId;
|
||||
|
||||
@QField(isEditable = false)
|
||||
private String filterJson;
|
||||
private String viewJson;
|
||||
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ public class SavedFilter extends QRecordEntity
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedFilter()
|
||||
public SavedView()
|
||||
{
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ public class SavedFilter extends QRecordEntity
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedFilter(QRecord qRecord) throws QException
|
||||
public SavedView(QRecord qRecord) throws QException
|
||||
{
|
||||
populateFromQRecord(qRecord);
|
||||
}
|
||||
@ -172,7 +172,7 @@ public class SavedFilter extends QRecordEntity
|
||||
** Fluent setter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedFilter withLabel(String label)
|
||||
public SavedView withLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
return (this);
|
||||
@ -206,7 +206,7 @@ public class SavedFilter extends QRecordEntity
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedFilter withTableName(String tableName)
|
||||
public SavedView withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
@ -240,7 +240,7 @@ public class SavedFilter extends QRecordEntity
|
||||
** Fluent setter for userId
|
||||
**
|
||||
*******************************************************************************/
|
||||
public SavedFilter withUserId(String userId)
|
||||
public SavedView withUserId(String userId)
|
||||
{
|
||||
this.userId = userId;
|
||||
return (this);
|
||||
@ -249,34 +249,31 @@ public class SavedFilter extends QRecordEntity
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filterJson
|
||||
**
|
||||
** Getter for viewJson
|
||||
*******************************************************************************/
|
||||
public String getFilterJson()
|
||||
public String getViewJson()
|
||||
{
|
||||
return filterJson;
|
||||
return (this.viewJson);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filterJson
|
||||
**
|
||||
** Setter for viewJson
|
||||
*******************************************************************************/
|
||||
public void setFilterJson(String filterJson)
|
||||
public void setViewJson(String viewJson)
|
||||
{
|
||||
this.filterJson = filterJson;
|
||||
this.viewJson = viewJson;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filterJson
|
||||
**
|
||||
** Fluent setter for viewJson
|
||||
*******************************************************************************/
|
||||
public SavedFilter withFilterJson(String filterJson)
|
||||
public SavedView withViewJson(String viewJson)
|
||||
{
|
||||
this.filterJson = filterJson;
|
||||
this.viewJson = viewJson;
|
||||
return (this);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,25 +19,31 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.savedfilters;
|
||||
package com.kingsrook.qqq.backend.core.model.savedviews;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||
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.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedfilters.DeleteSavedFilterProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedfilters.QuerySavedFilterProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedfilters.StoreSavedFilterProcess;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.DeleteSavedViewProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.QuerySavedViewProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedviews.StoreSavedViewProcess;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SavedFiltersMetaDataProvider
|
||||
public class SavedViewsMetaDataProvider
|
||||
{
|
||||
|
||||
|
||||
@ -46,11 +52,11 @@ public class SavedFiltersMetaDataProvider
|
||||
*******************************************************************************/
|
||||
public void defineAll(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
instance.addTable(defineSavedFilterTable(backendName, backendDetailEnricher));
|
||||
instance.addPossibleValueSource(defineSavedFilterPossibleValueSource());
|
||||
instance.addProcess(QuerySavedFilterProcess.getProcessMetaData());
|
||||
instance.addProcess(StoreSavedFilterProcess.getProcessMetaData());
|
||||
instance.addProcess(DeleteSavedFilterProcess.getProcessMetaData());
|
||||
instance.addTable(defineSavedViewTable(backendName, backendDetailEnricher));
|
||||
instance.addPossibleValueSource(defineSavedViewPossibleValueSource());
|
||||
instance.addProcess(QuerySavedViewProcess.getProcessMetaData());
|
||||
instance.addProcess(StoreSavedViewProcess.getProcessMetaData());
|
||||
instance.addProcess(DeleteSavedViewProcess.getProcessMetaData());
|
||||
}
|
||||
|
||||
|
||||
@ -58,16 +64,21 @@ public class SavedFiltersMetaDataProvider
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QTableMetaData defineSavedFilterTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
public QTableMetaData defineSavedViewTable(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData()
|
||||
.withName(SavedFilter.TABLE_NAME)
|
||||
.withLabel("Saved Filter")
|
||||
.withName(SavedView.TABLE_NAME)
|
||||
.withLabel("Saved View")
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("label")
|
||||
.withBackendName(backendName)
|
||||
.withPrimaryKeyField("id")
|
||||
.withFieldsFromEntity(SavedFilter.class);
|
||||
.withFieldsFromEntity(SavedView.class)
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label")))
|
||||
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("userId", "tableName", "viewJson")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||
|
||||
table.getField("viewJson").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("json")));
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
@ -82,12 +93,12 @@ public class SavedFiltersMetaDataProvider
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QPossibleValueSource defineSavedFilterPossibleValueSource()
|
||||
private QPossibleValueSource defineSavedViewPossibleValueSource()
|
||||
{
|
||||
return new QPossibleValueSource()
|
||||
.withName(SavedFilter.TABLE_NAME)
|
||||
.withName(SavedView.TABLE_NAME)
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withTableName(SavedFilter.TABLE_NAME)
|
||||
.withTableName(SavedView.TABLE_NAME)
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||
.withOrderByField("label");
|
||||
}
|
@ -24,6 +24,10 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.memory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -35,6 +39,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.DateTimeGroupBy;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -66,6 +71,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
@ -577,7 +583,11 @@ public class MemoryRecordStore
|
||||
for(GroupBy groupBy : groupBys)
|
||||
{
|
||||
Serializable groupByValue = record.getValue(groupBy.getFieldName());
|
||||
if(groupBy.getType() != null)
|
||||
if(StringUtils.hasContent(groupBy.getFormatString()))
|
||||
{
|
||||
groupByValue = applyFormatString(groupByValue, groupBy);
|
||||
}
|
||||
else if(groupBy.getType() != null)
|
||||
{
|
||||
groupByValue = ValueUtils.getValueAsFieldType(groupBy.getType(), groupByValue);
|
||||
}
|
||||
@ -629,7 +639,9 @@ public class MemoryRecordStore
|
||||
/////////////////////
|
||||
if(aggregateInput.getFilter() != null && CollectionUtils.nullSafeHasContents(aggregateInput.getFilter().getOrderBys()))
|
||||
{
|
||||
Comparator<AggregateResult> comparator = null;
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// lambda to compare 2 serializables, as we'll assume (& cast) them to Comparables //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
Comparator<Serializable> serializableComparator = (Serializable a, Serializable b) ->
|
||||
{
|
||||
if(a == null && b == null)
|
||||
@ -647,9 +659,15 @@ public class MemoryRecordStore
|
||||
return ((Comparable) a).compareTo(b);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// reverse of the lambda above (we had some errors calling .reversed() on the comparator we were building, so this seemed simpler & worked) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Comparator<Serializable> reverseSerializableComparator = (Serializable a, Serializable b) -> -serializableComparator.compare(a, b);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// build a comparator out of all the orderBys //
|
||||
////////////////////////////////////////////////
|
||||
Comparator<AggregateResult> comparator = null;
|
||||
for(QFilterOrderBy orderBy : aggregateInput.getFilter().getOrderBys())
|
||||
{
|
||||
Function<AggregateResult, Serializable> keyExtractor = aggregateResult ->
|
||||
@ -670,16 +688,11 @@ public class MemoryRecordStore
|
||||
|
||||
if(comparator == null)
|
||||
{
|
||||
comparator = Comparator.comparing(keyExtractor, serializableComparator);
|
||||
comparator = Comparator.comparing(keyExtractor, orderBy.getIsAscending() ? serializableComparator : reverseSerializableComparator);
|
||||
}
|
||||
else
|
||||
{
|
||||
comparator = comparator.thenComparing(keyExtractor, serializableComparator);
|
||||
}
|
||||
|
||||
if(!orderBy.getIsAscending())
|
||||
{
|
||||
comparator = comparator.reversed();
|
||||
comparator = comparator.thenComparing(keyExtractor, orderBy.getIsAscending() ? serializableComparator : reverseSerializableComparator);
|
||||
}
|
||||
}
|
||||
|
||||
@ -696,6 +709,57 @@ public class MemoryRecordStore
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Serializable applyFormatString(Serializable value, GroupBy groupBy) throws QException
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
String formatString = groupBy.getFormatString();
|
||||
|
||||
try
|
||||
{
|
||||
if(formatString.startsWith("DATE_FORMAT"))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// one known-use case we have here looks like this: //
|
||||
// DATE_FORMAT(CONVERT_TZ(%s, 'UTC', 'UTC'), '%%Y-%%m-%%dT%%H') //
|
||||
// ... for now, let's just try to support the formatting bit at the end... //
|
||||
// todo - support the CONVERT_TZ bit too! //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
String sqlDateTimeFormat = formatString.replaceFirst(".*'%%", "%%").replaceFirst("'.*", "");
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeGroupBy.sqlDateFormatToSelectedDateTimeFormatter(sqlDateTimeFormat);
|
||||
if(dateTimeFormatter == null)
|
||||
{
|
||||
throw (new QException("Unsupported sql dateTime format string [" + sqlDateTimeFormat + "] for MemoryRecordStore"));
|
||||
}
|
||||
|
||||
String valueAsString = ValueUtils.getValueAsString(value);
|
||||
Instant valueAsInstant = ValueUtils.getValueAsInstant(valueAsString);
|
||||
ZonedDateTime zonedDateTime = valueAsInstant.atZone(ZoneId.systemDefault());
|
||||
return (dateTimeFormatter.format(zonedDateTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Unsupported group-by format string [" + formatString + "] for MemoryRecordStore"));
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error applying format string [" + formatString + "] to group by value [" + value + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.processes.implementations.columnstats;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -173,11 +175,10 @@ public class ColumnStatsStep implements BackendStep
|
||||
Aggregate aggregate = new Aggregate(table.getPrimaryKeyField(), AggregateOperator.COUNT).withFieldType(QFieldType.DECIMAL);
|
||||
GroupBy groupBy = new GroupBy(field.getType(), fieldName);
|
||||
|
||||
// todo - something here about "by-date, not time"
|
||||
// todo - something here about an input param to specify how you want dates & date-times grouped
|
||||
if(field.getType().equals(QFieldType.DATE_TIME))
|
||||
{
|
||||
// groupBy = new GroupBy(field.getType(), fieldName, "DATE(%s)");
|
||||
String sqlExpression = DateTimeGroupBy.HOUR.getSqlExpression();
|
||||
String sqlExpression = DateTimeGroupBy.HOUR.getSqlExpression(ZoneId.systemDefault());
|
||||
groupBy = new GroupBy(QFieldType.STRING, fieldName, sqlExpression);
|
||||
}
|
||||
|
||||
@ -230,6 +231,12 @@ public class ColumnStatsStep implements BackendStep
|
||||
for(AggregateResult result : aggregateOutput.getResults())
|
||||
{
|
||||
Serializable value = result.getGroupByValue(groupBy);
|
||||
|
||||
if(field.getType().equals(QFieldType.DATE_TIME) && value != null)
|
||||
{
|
||||
value = Instant.parse(value + ":00:00Z");
|
||||
}
|
||||
|
||||
Integer count = ValueUtils.getValueAsInteger(result.getAggregateValue(aggregate));
|
||||
valueCounts.add(new QRecord().withValue(fieldName, value).withValue("count", count));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.savedfilters;
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.savedviews;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
@ -34,15 +34,15 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process used by the delete filter dialog
|
||||
** Process used by the delete view dialog
|
||||
*******************************************************************************/
|
||||
public class DeleteSavedFilterProcess implements BackendStep
|
||||
public class DeleteSavedViewProcess implements BackendStep
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(DeleteSavedFilterProcess.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(DeleteSavedViewProcess.class);
|
||||
|
||||
|
||||
|
||||
@ -52,10 +52,10 @@ public class DeleteSavedFilterProcess implements BackendStep
|
||||
public static QProcessMetaData getProcessMetaData()
|
||||
{
|
||||
return (new QProcessMetaData()
|
||||
.withName("deleteSavedFilter")
|
||||
.withName("deleteSavedView")
|
||||
.withStepList(List.of(
|
||||
new QBackendStepMetaData()
|
||||
.withCode(new QCodeReference(DeleteSavedFilterProcess.class))
|
||||
.withCode(new QCodeReference(DeleteSavedViewProcess.class))
|
||||
.withName("delete")
|
||||
)));
|
||||
}
|
||||
@ -72,16 +72,16 @@ public class DeleteSavedFilterProcess implements BackendStep
|
||||
|
||||
try
|
||||
{
|
||||
Integer savedFilterId = runBackendStepInput.getValueInteger("id");
|
||||
Integer savedViewId = runBackendStepInput.getValueInteger("id");
|
||||
|
||||
DeleteInput input = new DeleteInput();
|
||||
input.setTableName(SavedFilter.TABLE_NAME);
|
||||
input.setPrimaryKeys(List.of(savedFilterId));
|
||||
input.setTableName(SavedView.TABLE_NAME);
|
||||
input.setPrimaryKeys(List.of(savedViewId));
|
||||
new DeleteAction().execute(input);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error deleting saved filter", e);
|
||||
LOG.warn("Error deleting saved view", e);
|
||||
throw (e);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,7 +19,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.savedfilters;
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.savedviews;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -43,15 +43,15 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process used by the saved filter dialogs
|
||||
** Process used by the saved view dialogs
|
||||
*******************************************************************************/
|
||||
public class QuerySavedFilterProcess implements BackendStep
|
||||
public class QuerySavedViewProcess implements BackendStep
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QuerySavedFilterProcess.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QuerySavedViewProcess.class);
|
||||
|
||||
|
||||
|
||||
@ -61,10 +61,10 @@ public class QuerySavedFilterProcess implements BackendStep
|
||||
public static QProcessMetaData getProcessMetaData()
|
||||
{
|
||||
return (new QProcessMetaData()
|
||||
.withName("querySavedFilter")
|
||||
.withName("querySavedView")
|
||||
.withStepList(List.of(
|
||||
new QBackendStepMetaData()
|
||||
.withCode(new QCodeReference(QuerySavedFilterProcess.class))
|
||||
.withCode(new QCodeReference(QuerySavedViewProcess.class))
|
||||
.withName("query")
|
||||
)));
|
||||
}
|
||||
@ -81,36 +81,36 @@ public class QuerySavedFilterProcess implements BackendStep
|
||||
|
||||
try
|
||||
{
|
||||
Integer savedFilterId = runBackendStepInput.getValueInteger("id");
|
||||
if(savedFilterId != null)
|
||||
Integer savedViewId = runBackendStepInput.getValueInteger("id");
|
||||
if(savedViewId != null)
|
||||
{
|
||||
GetInput input = new GetInput();
|
||||
input.setTableName(SavedFilter.TABLE_NAME);
|
||||
input.setPrimaryKey(savedFilterId);
|
||||
input.setTableName(SavedView.TABLE_NAME);
|
||||
input.setPrimaryKey(savedViewId);
|
||||
|
||||
GetOutput output = new GetAction().execute(input);
|
||||
runBackendStepOutput.addRecord(output.getRecord());
|
||||
runBackendStepOutput.addValue("savedFilter", output.getRecord());
|
||||
runBackendStepOutput.addValue("savedFilterList", (Serializable) List.of(output.getRecord()));
|
||||
runBackendStepOutput.addValue("savedView", output.getRecord());
|
||||
runBackendStepOutput.addValue("savedViewList", (Serializable) List.of(output.getRecord()));
|
||||
}
|
||||
else
|
||||
{
|
||||
String tableName = runBackendStepInput.getValueString("tableName");
|
||||
|
||||
QueryInput input = new QueryInput();
|
||||
input.setTableName(SavedFilter.TABLE_NAME);
|
||||
input.setTableName(SavedView.TABLE_NAME);
|
||||
input.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, tableName))
|
||||
.withOrderBy(new QFilterOrderBy("label")));
|
||||
|
||||
QueryOutput output = new QueryAction().execute(input);
|
||||
runBackendStepOutput.setRecords(output.getRecords());
|
||||
runBackendStepOutput.addValue("savedFilterList", (Serializable) output.getRecords());
|
||||
runBackendStepOutput.addValue("savedViewList", (Serializable) output.getRecords());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error deleting saved filter", e);
|
||||
LOG.warn("Error querying for saved views", e);
|
||||
throw (e);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
@ -19,37 +19,45 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.savedfilters;
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.savedviews;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
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.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
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.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process used by the saved filter dialog
|
||||
** Process used by the saved view dialog
|
||||
*******************************************************************************/
|
||||
public class StoreSavedFilterProcess implements BackendStep
|
||||
public class StoreSavedViewProcess implements BackendStep
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(StoreSavedFilterProcess.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(StoreSavedViewProcess.class);
|
||||
|
||||
|
||||
|
||||
@ -59,10 +67,10 @@ public class StoreSavedFilterProcess implements BackendStep
|
||||
public static QProcessMetaData getProcessMetaData()
|
||||
{
|
||||
return (new QProcessMetaData()
|
||||
.withName("storeSavedFilter")
|
||||
.withName("storeSavedView")
|
||||
.withStepList(List.of(
|
||||
new QBackendStepMetaData()
|
||||
.withCode(new QCodeReference(StoreSavedFilterProcess.class))
|
||||
.withCode(new QCodeReference(StoreSavedViewProcess.class))
|
||||
.withName("store")
|
||||
)));
|
||||
}
|
||||
@ -79,39 +87,73 @@ public class StoreSavedFilterProcess implements BackendStep
|
||||
|
||||
try
|
||||
{
|
||||
String userId = QContext.getQSession().getUser().getIdReference();
|
||||
String tableName = runBackendStepInput.getValueString("tableName");
|
||||
String label = runBackendStepInput.getValueString("label");
|
||||
|
||||
QRecord qRecord = new QRecord()
|
||||
.withValue("id", runBackendStepInput.getValueInteger("id"))
|
||||
.withValue("label", runBackendStepInput.getValueString("label"))
|
||||
.withValue("tableName", runBackendStepInput.getValueString("tableName"))
|
||||
.withValue("filterJson", runBackendStepInput.getValueString("filterJson"))
|
||||
.withValue("userId", runBackendStepInput.getSession().getUser().getIdReference());
|
||||
.withValue("viewJson", runBackendStepInput.getValueString("viewJson"))
|
||||
.withValue("label", label)
|
||||
.withValue("tableName", tableName)
|
||||
.withValue("userId", userId);
|
||||
|
||||
List<QRecord> savedFilterList = new ArrayList<>();
|
||||
List<QRecord> savedViewList;
|
||||
if(qRecord.getValueInteger("id") == null)
|
||||
{
|
||||
checkForDuplicates(userId, tableName, label, null);
|
||||
|
||||
InsertInput input = new InsertInput();
|
||||
input.setTableName(SavedFilter.TABLE_NAME);
|
||||
input.setTableName(SavedView.TABLE_NAME);
|
||||
input.setRecords(List.of(qRecord));
|
||||
|
||||
InsertOutput output = new InsertAction().execute(input);
|
||||
savedFilterList = output.getRecords();
|
||||
savedViewList = output.getRecords();
|
||||
}
|
||||
else
|
||||
{
|
||||
checkForDuplicates(userId, tableName, label, qRecord.getValueInteger("id"));
|
||||
|
||||
UpdateInput input = new UpdateInput();
|
||||
input.setTableName(SavedFilter.TABLE_NAME);
|
||||
input.setTableName(SavedView.TABLE_NAME);
|
||||
input.setRecords(List.of(qRecord));
|
||||
|
||||
UpdateOutput output = new UpdateAction().execute(input);
|
||||
savedFilterList = output.getRecords();
|
||||
savedViewList = output.getRecords();
|
||||
}
|
||||
|
||||
runBackendStepOutput.addValue("savedFilterList", (Serializable) savedFilterList);
|
||||
runBackendStepOutput.addValue("savedViewList", (Serializable) savedViewList);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error storing data saved filter", e);
|
||||
LOG.warn("Error storing saved view", e);
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void checkForDuplicates(String userId, String tableName, String label, Integer id) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(SavedView.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(
|
||||
new QFilterCriteria("userId", QCriteriaOperator.EQUALS, userId),
|
||||
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, tableName),
|
||||
new QFilterCriteria("label", QCriteriaOperator.EQUALS, label)));
|
||||
|
||||
if(id != null)
|
||||
{
|
||||
queryInput.getFilter().addCriteria(new QFilterCriteria("id", QCriteriaOperator.NOT_EQUALS, id));
|
||||
}
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
throw (new QUserFacingException("You already have a saved view on this table with this name."));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,363 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.utils;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.IN;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_EQUALS;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_IN;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Class to help deduplicate redundant criteria in filters.
|
||||
**
|
||||
** Original use-case is for making more clean url links out of filters.
|
||||
**
|
||||
** Does not (at this time) look into sub-filters at all, or support any "OR"
|
||||
** filters other than the most basic (a=1 OR a=1).
|
||||
**
|
||||
** Also, other than for completely redundant criteria (e.g., a>1 and a>1) only
|
||||
* works on a limited subset of criteria operators (EQUALS, NOT_EQUALS, IN, and NOT_IN)
|
||||
*******************************************************************************/
|
||||
public class QQueryFilterDeduper
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QQueryFilterDeduper.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QQueryFilter dedupeFilter(QQueryFilter filter)
|
||||
{
|
||||
if(filter == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// track (just for logging) if we failed or if we did any good //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
List<String> log = new ArrayList<>();
|
||||
boolean fail = false;
|
||||
boolean didAnyGood = false;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// always create a clone to be returned. this is especially useful because, //
|
||||
// the clone's lists will be ArrayLists, which are mutable - since some of the deduping //
|
||||
// involves manipulating value lists. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter rs = filter.clone();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// general strategy is: //
|
||||
// iterate over criteria, possibly removing the one the iterator is pointing at, //
|
||||
// if we are able to somehow merge it into other criteria we've already seen. //
|
||||
// the others-we've-seen will be tracked in the criteriaByFieldName listing hash. //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
ListingHash<String, QFilterCriteria> criteriaByFieldName = new ListingHash<>();
|
||||
Iterator<QFilterCriteria> iterator = rs.getCriteria().iterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
QFilterCriteria criteria = iterator.next();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// first thing to check is, have we seen any other criteria for this field - if so - try to do some de-duping. //
|
||||
// note that, any time we do a remove, we'll need to do a continue - to avoid adding the now-removed criteria //
|
||||
// to the listing hash //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(criteriaByFieldName.containsKey(criteria.getFieldName()))
|
||||
{
|
||||
List<QFilterCriteria> others = criteriaByFieldName.get(criteria.getFieldName());
|
||||
QFilterCriteria other = others.get(0);
|
||||
|
||||
if(others.size() == 1 && other.equals(criteria))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we've only see 1 other criteria for this field so far, and this one is an exact match, then remove this one. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
log.add(String.format("Remove duplicate criteria [%s]", criteria));
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// else - if there's still just 1 other, and it's an AND query - then apply some basic //
|
||||
// logic-merging operations, based on the pair of criteria operators //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(others.size() == 1 && QQueryFilter.BooleanOperator.AND.equals(filter.getBooleanOperator()))
|
||||
{
|
||||
if((NOT_EQUALS.equals(other.getOperator()) || NOT_IN.equals(other.getOperator())) && EQUALS.equals(criteria.getOperator()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if we previously saw a not-equals or not-in, and now we see an equals //
|
||||
// and the value from the EQUALS isn't in the not-in list //
|
||||
// then replace the not-equals with the equals //
|
||||
// then just discard this equals //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
if(other.getValues().contains(criteria.getValues().get(0)))
|
||||
{
|
||||
log.add("Contradicting NOT_EQUALS/NOT_IN and EQUALS");
|
||||
fail = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
other.setOperator(criteria.getOperator());
|
||||
other.setValues(criteria.getValues());
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Replace a not-equals or not-in superseded by an equals");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(EQUALS.equals(other.getOperator()) && (NOT_EQUALS.equals(criteria.getOperator()) || NOT_IN.equals(criteria.getOperator())))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// if we previously saw an equals, and now we see a not-equals or a not-in //
|
||||
// and the value from the EQUALS isn't in the not-in list //
|
||||
// then just discard this not-equals //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
if(criteria.getValues().contains(other.getValues().get(0)))
|
||||
{
|
||||
log.add("Contradicting NOT_EQUALS/NOT_IN and EQUALS");
|
||||
fail = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Remove a redundant not-equals");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(NOT_EQUALS.equals(other.getOperator()) && IN.equals(criteria.getOperator()))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we previously saw a not-equals, and now we see an IN //
|
||||
// then replace the not-equals with the IN (making sure the not-equals value isn't in the in-list) //
|
||||
// then just discard this equals //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Serializable notEqualsValue = other.getValues().get(0);
|
||||
List<Serializable> inValues = new ArrayList<>(criteria.getValues());
|
||||
inValues.remove(notEqualsValue);
|
||||
if(inValues.isEmpty())
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// if the only in-value was the not-equal value, then... i don't know, don't try //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
log.add("Contradicting IN and NOT_EQUAL");
|
||||
fail = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// else, we can proceed by replacing the not-equals with the in //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
other.setOperator(criteria.getOperator());
|
||||
other.setValues(criteria.getValues());
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Replace superseded not-equals (removing its value from in-list)");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(IN.equals(other.getOperator()) && NOT_EQUALS.equals(criteria.getOperator()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// if we previously saw an in, and now we see a not-equals //
|
||||
// discard the not-equals (removing its value from the in-list) //
|
||||
// then just discard this not-equals //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
Serializable notEqualsValue = criteria.getValues().get(0);
|
||||
List<Serializable> inValues = new ArrayList<>(other.getValues());
|
||||
inValues.remove(notEqualsValue);
|
||||
if(inValues.isEmpty())
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// if the only in-value was the not-equal value, then... i don't know, don't try //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
log.add("Contradicting IN and NOT_EQUAL");
|
||||
fail = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// else, we can proceed by replacing the not-equals with the in //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Remove redundant not-equals (removing its value from in-list)");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(NOT_EQUALS.equals(other.getOperator()) && NOT_IN.equals(criteria.getOperator()))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we previously saw a not-equals, and now we see a not-in //
|
||||
// we can change the not-equals to the not-in, and make sure it's value is in the list //
|
||||
// then just discard this not-in //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
Serializable originalNotEqualsValue = other.getValues().get(0);
|
||||
other.setOperator(criteria.getOperator());
|
||||
other.setValues(criteria.getValues());
|
||||
if(!other.getValues().contains(originalNotEqualsValue))
|
||||
{
|
||||
other.getValues().add(originalNotEqualsValue);
|
||||
}
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Replace superseded not-equals with not-in");
|
||||
continue;
|
||||
}
|
||||
else if(NOT_IN.equals(other.getOperator()) && NOT_EQUALS.equals(criteria.getOperator()))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we previously saw a not-in, and now we see a not-equals //
|
||||
// we can discard this not-equals, and just make sure its value is in the not-in list //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
Serializable originalNotEqualsValue = criteria.getValues().get(0);
|
||||
if(!other.getValues().contains(originalNotEqualsValue))
|
||||
{
|
||||
other.getValues().add(originalNotEqualsValue);
|
||||
}
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Remove not-equals, absorbing into not-in");
|
||||
continue;
|
||||
}
|
||||
else if(NOT_IN.equals(other.getOperator()) && NOT_IN.equals(criteria.getOperator()))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// for multiple not-ins, just merge their values (as a union) //
|
||||
////////////////////////////////////////////////////////////////
|
||||
for(Serializable value : criteria.getValues())
|
||||
{
|
||||
if(!other.getValues().contains(value))
|
||||
{
|
||||
other.getValues().add(value);
|
||||
}
|
||||
}
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Merging not-ins");
|
||||
continue;
|
||||
}
|
||||
else if(IN.equals(other.getOperator()) && IN.equals(criteria.getOperator()))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// for multiple not-ins, just merge their values (as an intersection) //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
Set<Serializable> otherValues = new HashSet<>(other.getValues());
|
||||
Set<Serializable> criteriaValues = new HashSet<>(criteria.getValues());
|
||||
otherValues.retainAll(criteriaValues);
|
||||
if(otherValues.isEmpty())
|
||||
{
|
||||
log.add("Contradicting IN lists (no values)");
|
||||
fail = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
other.setValues(new ArrayList<>(otherValues));
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Merging not-ins");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if(NOT_EQUALS.equals(other.getOperator()) && NOT_EQUALS.equals(criteria.getOperator()))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we have 2 not-equals, we can merge them in a not-in //
|
||||
// we can assume their values are different, else they'd have been equals up above //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
other.setOperator(NOT_IN);
|
||||
other.setValues(new ArrayList<>(List.of(other.getValues().get(0), criteria.getValues().get(0))));
|
||||
iterator.remove();
|
||||
didAnyGood = true;
|
||||
log.add("Merge two not-equals as not-in");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
log.add("Fail because unhandled operator pair");
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.add("Fail because > 1 other or operator: OR");
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we reach here (e.g., no continue), then assuming we didn't remove the criteria, add it to the listing hash. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
criteriaByFieldName.add(criteria.getFieldName(), criteria);
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// log based on booleans //
|
||||
///////////////////////////
|
||||
if(fail && didAnyGood)
|
||||
{
|
||||
LOG.info("Partially unsuccessful dedupe of filter", logPair("original", filter), logPair("deduped", rs), logPair("log", log));
|
||||
}
|
||||
else if(fail)
|
||||
{
|
||||
LOG.info("Unsuccessful dedupe of filter", logPair("filter", filter), logPair("log", log));
|
||||
}
|
||||
else if(didAnyGood)
|
||||
{
|
||||
LOG.debug("Successful dedupe of filter", logPair("original", filter), logPair("deduped", rs), logPair("log", log));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("No duplicates in filter, so nothing to dedupe", logPair("original", filter));
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error de-duping filter", e, logPair("filter", filter));
|
||||
return (filter.clone());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.utils.lambdas;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
public interface UnsafeLambda
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void run() throws Exception;
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.dashboard;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
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.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for AbstractHTMLWidgetRenderer
|
||||
*******************************************************************************/
|
||||
class AbstractHTMLWidgetRendererTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
String link = AbstractHTMLWidgetRenderer.getCountLink(null, TestUtils.TABLE_NAME_PERSON, new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("a", QCriteriaOperator.EQUALS, 1)), 2
|
||||
);
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// assert that filter de-duplication is occurring //
|
||||
////////////////////////////////////////////////////
|
||||
assertThat(link).doesNotMatch(".*EQUALS.*EQUALS.*");
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.processes.implementations.columnstats;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
@ -91,4 +92,50 @@ class ColumnStatsStepTest extends BaseTest
|
||||
.hasFieldOrPropertyWithValue("percent", new BigDecimal("16.67"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDateTimesRollupByHour() throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("timestamp", Instant.parse("2024-01-31T09:59:01Z")),
|
||||
new QRecord().withValue("timestamp", Instant.parse("2024-01-31T09:59:59Z")),
|
||||
new QRecord().withValue("timestamp", Instant.parse("2024-01-31T10:00:00Z")),
|
||||
new QRecord().withValue("timestamp", Instant.parse("2024-01-31T10:01:01Z")),
|
||||
new QRecord().withValue("timestamp", Instant.parse("2024-01-31T10:59:59Z")),
|
||||
new QRecord().withValue("timestamp", null)
|
||||
));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
input.addValue("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
input.addValue("fieldName", "timestamp");
|
||||
input.addValue("orderBy", "count.desc");
|
||||
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
new ColumnStatsStep().run(input, output);
|
||||
|
||||
Map<String, Serializable> values = output.getValues();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<QRecord> valueCounts = (List<QRecord>) values.get("valueCounts");
|
||||
|
||||
assertThat(valueCounts.get(0).getValues())
|
||||
.hasFieldOrPropertyWithValue("timestamp", Instant.parse("2024-01-31T10:00:00Z"))
|
||||
.hasFieldOrPropertyWithValue("count", 3);
|
||||
|
||||
assertThat(valueCounts.get(1).getValues())
|
||||
.hasFieldOrPropertyWithValue("timestamp", Instant.parse("2024-01-31T09:00:00Z"))
|
||||
.hasFieldOrPropertyWithValue("count", 2);
|
||||
|
||||
assertThat(valueCounts.get(2).getValues())
|
||||
.hasFieldOrPropertyWithValue("timestamp", null)
|
||||
.hasFieldOrPropertyWithValue("count", 1);
|
||||
}
|
||||
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.processes.implementations.savedfilters;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFiltersMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for all saved filter processes
|
||||
*******************************************************************************/
|
||||
class SavedFilterProcessTests extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
new SavedFiltersMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
|
||||
String tableName = TestUtils.TABLE_NAME_PERSON_MEMORY;
|
||||
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// query - should be no filters to start //
|
||||
///////////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(QuerySavedFilterProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertEquals(0, ((List<?>) runProcessOutput.getValues().get("savedFilterList")).size());
|
||||
}
|
||||
|
||||
Integer savedFilterId;
|
||||
{
|
||||
////////////////////////
|
||||
// store a new filter //
|
||||
////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(StoreSavedFilterProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("label", "My Filter");
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
runProcessInput.addValue("filterJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedFilterList = (List<QRecord>) runProcessOutput.getValues().get("savedFilterList");
|
||||
assertEquals(1, savedFilterList.size());
|
||||
savedFilterId = savedFilterList.get(0).getValueInteger("id");
|
||||
assertNotNull(savedFilterId);
|
||||
}
|
||||
|
||||
{
|
||||
////////////////////////////////////
|
||||
// query - should find our filter //
|
||||
////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(QuerySavedFilterProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedFilterList = (List<QRecord>) runProcessOutput.getValues().get("savedFilterList");
|
||||
assertEquals(1, savedFilterList.size());
|
||||
assertEquals(1, savedFilterList.get(0).getValueInteger("id"));
|
||||
assertEquals("My Filter", savedFilterList.get(0).getValueString("label"));
|
||||
}
|
||||
|
||||
{
|
||||
///////////////////////
|
||||
// update our filter //
|
||||
///////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(StoreSavedFilterProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("id", savedFilterId);
|
||||
runProcessInput.addValue("label", "My Updated Filter");
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
runProcessInput.addValue("filterJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedFilterList = (List<QRecord>) runProcessOutput.getValues().get("savedFilterList");
|
||||
assertEquals(1, savedFilterList.size());
|
||||
assertEquals(1, savedFilterList.get(0).getValueInteger("id"));
|
||||
assertEquals("My Updated Filter", savedFilterList.get(0).getValueString("label"));
|
||||
}
|
||||
|
||||
{
|
||||
///////////////////////
|
||||
// delete our filter //
|
||||
///////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(DeleteSavedFilterProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("id", savedFilterId);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
}
|
||||
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// query - should be no filters again //
|
||||
////////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(QuerySavedFilterProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertEquals(0, ((List<?>) runProcessOutput.getValues().get("savedFilterList")).size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.processes.implementations.savedviews;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedViewsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for all saved view processes
|
||||
*******************************************************************************/
|
||||
class SavedViewProcessTests extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
new SavedViewsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
|
||||
String tableName = TestUtils.TABLE_NAME_PERSON_MEMORY;
|
||||
|
||||
{
|
||||
/////////////////////////////////////////
|
||||
// query - should be no views to start //
|
||||
/////////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(QuerySavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertEquals(0, ((List<?>) runProcessOutput.getValues().get("savedViewList")).size());
|
||||
}
|
||||
|
||||
Integer savedViewId;
|
||||
{
|
||||
//////////////////////
|
||||
// store a new view //
|
||||
//////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(StoreSavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("label", "My View");
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
runProcessInput.addValue("viewJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedViewList = (List<QRecord>) runProcessOutput.getValues().get("savedViewList");
|
||||
assertEquals(1, savedViewList.size());
|
||||
savedViewId = savedViewList.get(0).getValueInteger("id");
|
||||
assertNotNull(savedViewId);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// try to store it again - should throw a "duplicate" exception //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
assertThatThrownBy(() -> new RunProcessAction().execute(runProcessInput))
|
||||
.isInstanceOf(QUserFacingException.class)
|
||||
.hasMessageContaining("already have a saved view");
|
||||
}
|
||||
|
||||
{
|
||||
///////////////////////////////////
|
||||
// query - should find our views //
|
||||
///////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(QuerySavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedViewList = (List<QRecord>) runProcessOutput.getValues().get("savedViewList");
|
||||
assertEquals(1, savedViewList.size());
|
||||
assertEquals(1, savedViewList.get(0).getValueInteger("id"));
|
||||
assertEquals("My View", savedViewList.get(0).getValueString("label"));
|
||||
}
|
||||
|
||||
{
|
||||
/////////////////////
|
||||
// update our view //
|
||||
/////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(StoreSavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("id", savedViewId);
|
||||
runProcessInput.addValue("label", "My Updated View");
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
runProcessInput.addValue("viewJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedViewList = (List<QRecord>) runProcessOutput.getValues().get("savedViewList");
|
||||
assertEquals(1, savedViewList.size());
|
||||
assertEquals(1, savedViewList.get(0).getValueInteger("id"));
|
||||
assertEquals("My Updated View", savedViewList.get(0).getValueString("label"));
|
||||
}
|
||||
|
||||
Integer anotherSavedViewId;
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// store a second one w/ different name (will be used below in update-dupe-check use-case) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(StoreSavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("label", "My Second View");
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
runProcessInput.addValue("viewJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
List<QRecord> savedViewList = (List<QRecord>) runProcessOutput.getValues().get("savedViewList");
|
||||
anotherSavedViewId = savedViewList.get(0).getValueInteger("id");
|
||||
}
|
||||
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
// try to rename the second to match the first //
|
||||
/////////////////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(StoreSavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("id", anotherSavedViewId);
|
||||
runProcessInput.addValue("label", "My Updated View");
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
runProcessInput.addValue("viewJson", JsonUtils.toJson(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 47))));
|
||||
|
||||
//////////////////////////////////////////
|
||||
// should throw a "duplicate" exception //
|
||||
//////////////////////////////////////////
|
||||
assertThatThrownBy(() -> new RunProcessAction().execute(runProcessInput))
|
||||
.isInstanceOf(QUserFacingException.class)
|
||||
.hasMessageContaining("already have a saved view");
|
||||
}
|
||||
|
||||
{
|
||||
//////////////////////
|
||||
// delete our views //
|
||||
//////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(DeleteSavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("id", savedViewId);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
|
||||
runProcessInput.addValue("id", anotherSavedViewId);
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
}
|
||||
|
||||
{
|
||||
//////////////////////////////////////
|
||||
// query - should be no views again //
|
||||
//////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(QuerySavedViewProcess.getProcessMetaData().getName());
|
||||
runProcessInput.addValue("tableName", tableName);
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertEquals(0, ((List<?>) runProcessOutput.getValues().get("savedViewList")).size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.utils;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.GREATER_THAN;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.IN;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_EQUALS;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.NOT_IN;
|
||||
import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter.BooleanOperator.OR;
|
||||
import static com.kingsrook.qqq.backend.core.utils.QQueryFilterDeduper.dedupeFilter;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for QQueryFilterDeduper
|
||||
*******************************************************************************/
|
||||
class QQueryFilterDeduperTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDegenerateCases()
|
||||
{
|
||||
assertNull(dedupeFilter(null));
|
||||
|
||||
QQueryFilter empty = new QQueryFilter();
|
||||
assertEquals(empty, dedupeFilter(empty));
|
||||
assertNotSame(empty, dedupeFilter(empty)); // method always clones, so, just assert that.
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSimpleFiltersWithNoChanges()
|
||||
{
|
||||
QQueryFilter oneCriteria = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1));
|
||||
assertEquals(oneCriteria, dedupeFilter(oneCriteria));
|
||||
assertNotSame(oneCriteria, dedupeFilter(oneCriteria));
|
||||
|
||||
QQueryFilter twoCriteriaDifferentFields = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("b", GREATER_THAN, 2));
|
||||
assertEquals(twoCriteriaDifferentFields, dedupeFilter(twoCriteriaDifferentFields));
|
||||
assertNotSame(twoCriteriaDifferentFields, dedupeFilter(twoCriteriaDifferentFields));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testOrs()
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// we've only written the simplest cases with ORs... //
|
||||
///////////////////////////////////////////////////////
|
||||
assertEquals(new QQueryFilter().withBooleanOperator(OR).withCriteria(new QFilterCriteria("a", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withBooleanOperator(OR)
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// just not built at this time - obviously, could become an IN list //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter notSupportedOrTwoEquals = new QQueryFilter()
|
||||
.withBooleanOperator(OR)
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 2));
|
||||
assertEquals(notSupportedOrTwoEquals, dedupeFilter(notSupportedOrTwoEquals));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// I think the logic would be, that the EQUALS 1 would be removed (is redundant) //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter notSupportedOrEqualsNotEquals = new QQueryFilter()
|
||||
.withBooleanOperator(OR)
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2));
|
||||
assertEquals(notSupportedOrEqualsNotEquals, dedupeFilter(notSupportedOrEqualsNotEquals));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMoreOperators()
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// only simplest case (of criteria being .equals()) is supported... //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("a", GREATER_THAN, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", GREATER_THAN, 1))
|
||||
.withCriteria(new QFilterCriteria("a", GREATER_THAN, 1))
|
||||
));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// in theory, we could do more, but we just haven't yet (e.g, this could be > 5) //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter tooComplex = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", GREATER_THAN, 1))
|
||||
.withCriteria(new QFilterCriteria("f", GREATER_THAN, 5));
|
||||
assertEquals(tooComplex, dedupeFilter(tooComplex));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAllEquals()
|
||||
{
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("a", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("a", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("c", EQUALS, 3)),
|
||||
dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("b", EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("a", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("c", EQUALS, 3))
|
||||
.withCriteria(new QFilterCriteria("c", EQUALS, 3))
|
||||
.withCriteria(new QFilterCriteria("c", EQUALS, 3))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testEqualsAndNotEqualsAndNotIn()
|
||||
{
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 4))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 4))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", EQUALS, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 4))
|
||||
));
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// this is a contradiction, so we choose not to dedupe it //
|
||||
////////////////////////////////////////////////////////////
|
||||
QQueryFilter contradiction1 = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1));
|
||||
assertEquals(contradiction1, dedupeFilter(contradiction1));
|
||||
|
||||
QQueryFilter contradiction2 = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 0, 1));
|
||||
assertEquals(contradiction2, dedupeFilter(contradiction2));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this case can collapse the two not-equals, but then fails to merge the equals with them, because they are a contradiction! //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertEquals(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 2)),
|
||||
dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||
.withCriteria(new QFilterCriteria("f", EQUALS, 2))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNotEqualsAndNotIn()
|
||||
{
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 1, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 2))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 3))
|
||||
));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ideally, maybe, this would have the values ordered 1,2,3, but, is equivalent enough //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3, 1)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", NOT_IN, 1, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 1, 2))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_IN, 2, 3))
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInAndNotEquals()
|
||||
{
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 2, 3))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 2, 3)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", IN, 2, 3))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||
));
|
||||
|
||||
QQueryFilter contradiction1 = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1));
|
||||
assertEquals(contradiction1, dedupeFilter(contradiction1));
|
||||
|
||||
QQueryFilter contradiction2 = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1))
|
||||
.withCriteria(new QFilterCriteria("f", NOT_EQUALS, 1));
|
||||
assertEquals(contradiction2, dedupeFilter(contradiction2));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMultipleInLists()
|
||||
{
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 2)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1, 2))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 2, 3))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 3, 4)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1, 2, 3, 4))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 3, 4, 5, 6))
|
||||
));
|
||||
|
||||
assertEquals(new QQueryFilter().withCriteria(new QFilterCriteria("f", IN, 3)), dedupeFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1, 2, 3, 4))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 3, 4, 5, 6))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1, 3, 5, 7))
|
||||
));
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// contradicting in-lists - we give up and refuse to simplify it //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
QQueryFilter contradiction = new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("f", IN, 1, 2))
|
||||
.withCriteria(new QFilterCriteria("f", IN, 3, 4));
|
||||
assertEquals(contradiction, dedupeFilter(contradiction));
|
||||
}
|
||||
|
||||
}
|
@ -66,7 +66,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFiltersMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedViewsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
@ -157,7 +157,7 @@ public class TestUtils
|
||||
qInstance.addBackend(defineMemoryBackend());
|
||||
try
|
||||
{
|
||||
new SavedFiltersMetaDataProvider().defineAll(qInstance, defineMemoryBackend().getName(), null);
|
||||
new SavedViewsMetaDataProvider().defineAll(qInstance, defineMemoryBackend().getName(), null);
|
||||
new ScriptsMetaDataProvider().defineAll(qInstance, defineMemoryBackend().getName(), null);
|
||||
}
|
||||
catch(Exception e)
|
||||
|
Reference in New Issue
Block a user