mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
CE-1115 pre-QA commit on saved report UI, including:
- adding pre-insert/update validation - move json field value formatting from post-query customizer to a FieldDisplayBehavior instead (works for audits this way :)
This commit is contained in:
@ -303,7 +303,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
|
String formattedValue = getFormattedValueForAuditDetail(table, record, fieldName, field, value);
|
||||||
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formattedValue);
|
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formattedValue);
|
||||||
detailRecord.withValue("newValue", formattedValue);
|
detailRecord.withValue("newValue", formattedValue);
|
||||||
}
|
}
|
||||||
@ -329,8 +329,8 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
|
String formattedValue = getFormattedValueForAuditDetail(table, record, fieldName, field, value);
|
||||||
String formattedOldValue = getFormattedValueForAuditDetail(oldRecord, fieldName, field, oldValue);
|
String formattedOldValue = getFormattedValueForAuditDetail(table, oldRecord, fieldName, field, oldValue);
|
||||||
|
|
||||||
if(oldValue == null)
|
if(oldValue == null)
|
||||||
{
|
{
|
||||||
@ -464,7 +464,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static String getFormattedValueForAuditDetail(QRecord record, String fieldName, QFieldMetaData field, Serializable value)
|
private static String getFormattedValueForAuditDetail(QTableMetaData table, QRecord record, String fieldName, QFieldMetaData field, Serializable value)
|
||||||
{
|
{
|
||||||
String formattedValue = null;
|
String formattedValue = null;
|
||||||
if(value != null)
|
if(value != null)
|
||||||
@ -479,7 +479,8 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
formattedValue = QValueFormatter.formatValue(field, value);
|
QValueFormatter.setDisplayValuesInRecord(table, table.getFields(), record);
|
||||||
|
formattedValue = record.getDisplayValue(fieldName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,7 +531,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private FieldAndJoinTable getFieldAndJoinTable(QTableMetaData mainTable, String fieldName) throws QException
|
public static FieldAndJoinTable getFieldAndJoinTable(QTableMetaData mainTable, String fieldName) throws QException
|
||||||
{
|
{
|
||||||
if(fieldName.indexOf('.') > -1)
|
if(fieldName.indexOf('.') > -1)
|
||||||
{
|
{
|
||||||
@ -1097,5 +1097,22 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable) {}
|
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getLabel(QTableMetaData mainTable)
|
||||||
|
{
|
||||||
|
if(mainTable.getName().equals(joinTable.getName()))
|
||||||
|
{
|
||||||
|
return (field.getLabel());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (joinTable.getLabel() + ": " + field.getLabel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.model.savedreports;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -35,6 +37,7 @@ public class ReportColumns implements Serializable
|
|||||||
private List<ReportColumn> columns;
|
private List<ReportColumn> columns;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for columns
|
** Getter for columns
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -45,6 +48,23 @@ public class ReportColumns implements Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public List<ReportColumn> extractVisibleColumns()
|
||||||
|
{
|
||||||
|
return CollectionUtils.nonNullList(getColumns()).stream()
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// if isVisible is missing, we assume it to be true //
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
.filter(rc -> rc.getIsVisible() == null || rc.getIsVisible())
|
||||||
|
.filter(rc -> StringUtils.hasContent(rc.getName()))
|
||||||
|
.filter(rc -> !rc.getName().startsWith("__check"))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for columns
|
** Setter for columns
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -65,6 +85,7 @@ public class ReportColumns implements Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter to add 1 column
|
** Fluent setter to add 1 column
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -79,6 +100,7 @@ public class ReportColumns implements Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter to add 1 column w/ just a name
|
** Fluent setter to add 1 column w/ just a name
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
|
||||||
|
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.metadata.fields.FieldDisplayBehavior;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class SavedReportJsonFieldDisplayValueFormatter implements FieldDisplayBehavior<SavedReportJsonFieldDisplayValueFormatter>
|
||||||
|
{
|
||||||
|
private static SavedReportJsonFieldDisplayValueFormatter savedReportJsonFieldDisplayValueFormatter = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Singleton constructor
|
||||||
|
*******************************************************************************/
|
||||||
|
private SavedReportJsonFieldDisplayValueFormatter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Singleton accessor
|
||||||
|
*******************************************************************************/
|
||||||
|
public static SavedReportJsonFieldDisplayValueFormatter getInstance()
|
||||||
|
{
|
||||||
|
if(savedReportJsonFieldDisplayValueFormatter == null)
|
||||||
|
{
|
||||||
|
savedReportJsonFieldDisplayValueFormatter = new SavedReportJsonFieldDisplayValueFormatter();
|
||||||
|
}
|
||||||
|
return (savedReportJsonFieldDisplayValueFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public SavedReportJsonFieldDisplayValueFormatter getDefault()
|
||||||
|
{
|
||||||
|
return getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||||
|
{
|
||||||
|
for(QRecord record : CollectionUtils.nonNullList(recordList))
|
||||||
|
{
|
||||||
|
if(field.getName().equals("queryFilterJson"))
|
||||||
|
{
|
||||||
|
String queryFilterJson = record.getValueString("queryFilterJson");
|
||||||
|
if(StringUtils.hasContent(queryFilterJson))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QQueryFilter qQueryFilter = SavedReportToReportMetaDataAdapter.getQQueryFilter(queryFilterJson);
|
||||||
|
int criteriaCount = CollectionUtils.nonNullList(qQueryFilter.getCriteria()).size();
|
||||||
|
record.setDisplayValue("queryFilterJson", criteriaCount + " Filter" + StringUtils.plural(criteriaCount));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
record.setDisplayValue("queryFilterJson", "Invalid Filter...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(field.getName().equals("columnsJson"))
|
||||||
|
{
|
||||||
|
String columnsJson = record.getValueString("columnsJson");
|
||||||
|
if(StringUtils.hasContent(columnsJson))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ReportColumns reportColumns = SavedReportToReportMetaDataAdapter.getReportColumns(columnsJson);
|
||||||
|
int columnCount = reportColumns.extractVisibleColumns().size();
|
||||||
|
|
||||||
|
record.setDisplayValue("columnsJson", columnCount + " Column" + StringUtils.plural(columnCount));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
record.setDisplayValue("columnsJson", "Invalid Columns...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(field.getName().equals("pivotTableJson"))
|
||||||
|
{
|
||||||
|
String pivotTableJson = record.getValueString("pivotTableJson");
|
||||||
|
if(StringUtils.hasContent(pivotTableJson))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PivotTableDefinition pivotTableDefinition = SavedReportToReportMetaDataAdapter.getPivotTableDefinition(pivotTableJson);
|
||||||
|
int rowCount = CollectionUtils.nonNullList(pivotTableDefinition.getRows()).size();
|
||||||
|
int columnCount = CollectionUtils.nonNullList(pivotTableDefinition.getColumns()).size();
|
||||||
|
int valueCount = CollectionUtils.nonNullList(pivotTableDefinition.getValues()).size();
|
||||||
|
record.setDisplayValue("pivotTableJson", rowCount + " Row" + StringUtils.plural(rowCount) + ", " + columnCount + " Column" + StringUtils.plural(columnCount) + ", and " + valueCount + " Value" + StringUtils.plural(valueCount));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
record.setDisplayValue("pivotTableJson", "Invalid Pivot Table...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,17 +22,26 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.savedreports;
|
package com.kingsrook.qqq.backend.core.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter;
|
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.SavedReportToReportMetaDataAdapter;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -45,63 +54,229 @@ public class SavedReportTableCustomizer implements TableCustomizerInterface
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
|
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||||
|
{
|
||||||
|
return (preInsertOrUpdate(records));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||||
|
{
|
||||||
|
return (preInsertOrUpdate(records));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private List<QRecord> preInsertOrUpdate(List<QRecord> records)
|
||||||
{
|
{
|
||||||
for(QRecord record : CollectionUtils.nonNullList(records))
|
for(QRecord record : CollectionUtils.nonNullList(records))
|
||||||
{
|
{
|
||||||
|
preValidateRecord(record);
|
||||||
|
}
|
||||||
|
return (records);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
void preValidateRecord(QRecord record)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String tableName = record.getValueString("tableName");
|
||||||
String queryFilterJson = record.getValueString("queryFilterJson");
|
String queryFilterJson = record.getValueString("queryFilterJson");
|
||||||
String columnsJson = record.getValueString("columnsJson");
|
String columnsJson = record.getValueString("columnsJson");
|
||||||
String pivotTableJson = record.getValueString("pivotTableJson");
|
String pivotTableJson = record.getValueString("pivotTableJson");
|
||||||
|
|
||||||
|
Set<String> usedColumns = new HashSet<>();
|
||||||
|
|
||||||
|
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||||
|
if(table == null)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Unrecognized table name: " + tableName));
|
||||||
|
}
|
||||||
|
|
||||||
if(StringUtils.hasContent(queryFilterJson))
|
if(StringUtils.hasContent(queryFilterJson))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QQueryFilter qQueryFilter = SavedReportToReportMetaDataAdapter.getQQueryFilter(queryFilterJson);
|
////////////////////////////////////////////////////////////////
|
||||||
int criteriaCount = CollectionUtils.nonNullList(qQueryFilter.getCriteria()).size();
|
// nothing to validate on filter, other than, we can parse it //
|
||||||
record.setDisplayValue("queryFilterJson", criteriaCount + " Filter" + StringUtils.plural(criteriaCount));
|
////////////////////////////////////////////////////////////////
|
||||||
|
SavedReportToReportMetaDataAdapter.getQQueryFilter(queryFilterJson);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(IOException e)
|
||||||
{
|
{
|
||||||
record.setDisplayValue("queryFilterJson", "Invalid Filter...");
|
record.addError(new BadInputStatusMessage("Unable to parse queryFilterJson: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hadColumnParseError = false;
|
||||||
if(StringUtils.hasContent(columnsJson))
|
if(StringUtils.hasContent(columnsJson))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure we can parse columns, and that we have at least 1 visible //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
ReportColumns reportColumns = SavedReportToReportMetaDataAdapter.getReportColumns(columnsJson);
|
ReportColumns reportColumns = SavedReportToReportMetaDataAdapter.getReportColumns(columnsJson);
|
||||||
long columnCount = CollectionUtils.nonNullList(reportColumns.getColumns())
|
for(ReportColumn column : reportColumns.extractVisibleColumns())
|
||||||
.stream().filter(rc -> BooleanUtils.isTrue(rc.getIsVisible()))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
record.setDisplayValue("columnsJson", columnCount + " Column" + StringUtils.plural((int) columnCount));
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
{
|
||||||
record.setDisplayValue("columnsJson", "Invalid Columns...");
|
usedColumns.add(column.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Unable to parse columnsJson: " + e.getMessage()));
|
||||||
|
hadColumnParseError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(usedColumns.isEmpty() && !hadColumnParseError)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("A Report must contain at least 1 column"));
|
||||||
|
}
|
||||||
|
|
||||||
if(StringUtils.hasContent(pivotTableJson))
|
if(StringUtils.hasContent(pivotTableJson))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure we can parse pivot table, and we have ... at least 1 ... row? maybe that's all that's needed //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
PivotTableDefinition pivotTableDefinition = SavedReportToReportMetaDataAdapter.getPivotTableDefinition(pivotTableJson);
|
PivotTableDefinition pivotTableDefinition = SavedReportToReportMetaDataAdapter.getPivotTableDefinition(pivotTableJson);
|
||||||
int rowCount = CollectionUtils.nonNullList(pivotTableDefinition.getRows()).size();
|
boolean anyRows = false;
|
||||||
int columnCount = CollectionUtils.nonNullList(pivotTableDefinition.getColumns()).size();
|
boolean missingAnyFieldNamesInRows = false;
|
||||||
int valueCount = CollectionUtils.nonNullList(pivotTableDefinition.getValues()).size();
|
boolean missingAnyFieldNamesInColumns = false;
|
||||||
record.setDisplayValue("pivotTableJson", rowCount + " Row" + StringUtils.plural(rowCount) + ", " + columnCount + " Column" + StringUtils.plural(columnCount) + ", and " + valueCount + " Value" + StringUtils.plural(valueCount));
|
boolean missingAnyFieldNamesInValues = false;
|
||||||
|
boolean missingAnyFunctionsInValues = false;
|
||||||
|
|
||||||
|
//////////////////
|
||||||
|
// look at rows //
|
||||||
|
//////////////////
|
||||||
|
for(PivotTableGroupBy row : CollectionUtils.nonNullList(pivotTableDefinition.getRows()))
|
||||||
|
{
|
||||||
|
anyRows = true;
|
||||||
|
if(StringUtils.hasContent(row.getFieldName()))
|
||||||
|
{
|
||||||
|
if(!usedColumns.contains(row.getFieldName()) && !hadColumnParseError)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("A pivot table row is using field (" + getFieldLabelElseName(table, row.getFieldName()) + ") which is not an active column on this report."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
missingAnyFieldNamesInRows = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!anyRows)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("A Pivot Table must contain at least 1 row"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// look at columns //
|
||||||
|
/////////////////////
|
||||||
|
for(PivotTableGroupBy column : CollectionUtils.nonNullList(pivotTableDefinition.getColumns()))
|
||||||
|
{
|
||||||
|
if(StringUtils.hasContent(column.getFieldName()))
|
||||||
|
{
|
||||||
|
if(!usedColumns.contains(column.getFieldName()) && !hadColumnParseError)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("A pivot table column is using field (" + getFieldLabelElseName(table, column.getFieldName()) + ") which is not an active column on this report."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
missingAnyFieldNamesInColumns = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// look at values //
|
||||||
|
////////////////////
|
||||||
|
for(PivotTableValue value : CollectionUtils.nonNullList(pivotTableDefinition.getValues()))
|
||||||
|
{
|
||||||
|
if(StringUtils.hasContent(value.getFieldName()))
|
||||||
|
{
|
||||||
|
if(!usedColumns.contains(value.getFieldName()) && !hadColumnParseError)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("A pivot table value is using field (" + getFieldLabelElseName(table, value.getFieldName()) + ") which is not an active column on this report."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
missingAnyFieldNamesInValues = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value.getFunction() == null)
|
||||||
|
{
|
||||||
|
missingAnyFunctionsInValues = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// errors based on missing things found above //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
if(missingAnyFieldNamesInRows)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Missing field name for at least one pivot table row."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(missingAnyFieldNamesInColumns)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Missing field name for at least one pivot table column."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(missingAnyFieldNamesInValues)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Missing field name for at least one pivot table value."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(missingAnyFunctionsInValues)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Missing function for at least one pivot table value."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
record.addError(new BadInputStatusMessage("Unable to parse pivotTableJson: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
record.setDisplayValue("pivotTableJson", "Invalid Pivot Table...");
|
LOG.warn("Error validating a savedReport");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (records);
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private String getFieldLabelElseName(QTableMetaData table, String fieldName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GenerateReportAction.FieldAndJoinTable fieldAndJoinTable = GenerateReportAction.getFieldAndJoinTable(table, fieldName);
|
||||||
|
return (fieldAndJoinTable.getLabel(table));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
return (fieldName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
|||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
|
||||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||||
@ -147,6 +149,7 @@ public class SavedReportsMetaDataProvider
|
|||||||
.withBackendName(backendName)
|
.withBackendName(backendName)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
.withFieldsFromEntity(SavedReport.class)
|
.withFieldsFromEntity(SavedReport.class)
|
||||||
|
.withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
|
||||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
|
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
|
||||||
.withSection(new QFieldSection("filtersAndColumns", new QIcon().withName("table_chart"), Tier.T2).withLabel("Filters and Columns").withWidgetName("reportSetupWidget"))
|
.withSection(new QFieldSection("filtersAndColumns", new QIcon().withName("table_chart"), Tier.T2).withLabel("Filters and Columns").withWidgetName("reportSetupWidget"))
|
||||||
.withSection(new QFieldSection("pivotTable", new QIcon().withName("pivot_table_chart"), Tier.T2).withLabel("Pivot Table").withWidgetName("pivotTableSetupWidget"))
|
.withSection(new QFieldSection("pivotTable", new QIcon().withName("pivot_table_chart"), Tier.T2).withLabel("Pivot Table").withWidgetName("pivotTableSetupWidget"))
|
||||||
@ -154,7 +157,12 @@ public class SavedReportsMetaDataProvider
|
|||||||
.withSection(new QFieldSection("hidden", new QIcon().withName("text_snippet"), Tier.T2, List.of("inputFieldsJson", "userId")).withIsHidden(true))
|
.withSection(new QFieldSection("hidden", new QIcon().withName("text_snippet"), Tier.T2, List.of("inputFieldsJson", "userId")).withIsHidden(true))
|
||||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
|
||||||
|
|
||||||
table.withCustomizer(TableCustomizers.POST_QUERY_RECORD, new QCodeReference(SavedReportTableCustomizer.class));
|
table.getField("queryFilterJson").withBehavior(SavedReportJsonFieldDisplayValueFormatter.getInstance());
|
||||||
|
table.getField("columnsJson").withBehavior(SavedReportJsonFieldDisplayValueFormatter.getInstance());
|
||||||
|
table.getField("pivotTableJson").withBehavior(SavedReportJsonFieldDisplayValueFormatter.getInstance());
|
||||||
|
|
||||||
|
table.withCustomizer(TableCustomizers.PRE_INSERT_RECORD, new QCodeReference(SavedReportTableCustomizer.class));
|
||||||
|
table.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(SavedReportTableCustomizer.class));
|
||||||
|
|
||||||
if(backendDetailEnricher != null)
|
if(backendDetailEnricher != null)
|
||||||
{
|
{
|
||||||
|
@ -57,7 +57,6 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
|||||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||||
import org.apache.commons.lang.BooleanUtils;
|
|
||||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
@ -122,16 +121,8 @@ public class SavedReportToReportMetaDataAdapter
|
|||||||
|
|
||||||
Set<String> neededJoinTables = new HashSet<>();
|
Set<String> neededJoinTables = new HashSet<>();
|
||||||
|
|
||||||
for(ReportColumn column : columnsObject.getColumns())
|
for(ReportColumn column : columnsObject.extractVisibleColumns())
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// if isVisible is missing, we assume it to be true - so only if it isFalse do we skip the column //
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
if(BooleanUtils.isFalse(column.getIsVisible()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
// figure out the field being named by the column //
|
// figure out the field being named by the column //
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
|
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
|
||||||
|
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.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for SavedReportJsonFieldDisplayValueFormatter
|
||||||
|
*******************************************************************************/
|
||||||
|
class SavedReportJsonFieldDisplayValueFormatterTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() throws QException
|
||||||
|
{
|
||||||
|
QContext.getQInstance().add(new SavedReportsMetaDataProvider().defineSavedReportTable(TestUtils.MEMORY_BACKEND_NAME, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testPostQuery() throws QException
|
||||||
|
{
|
||||||
|
UnsafeFunction<SavedReport, QRecord, QException> customize = savedReport ->
|
||||||
|
{
|
||||||
|
QInstance qInstance = QContext.getQInstance();
|
||||||
|
QTableMetaData table = qInstance.getTable(SavedReport.TABLE_NAME);
|
||||||
|
|
||||||
|
QRecord record = savedReport.toQRecord();
|
||||||
|
|
||||||
|
for(String fieldName : List.of("queryFilterJson", "columnsJson", "pivotTableJson"))
|
||||||
|
{
|
||||||
|
SavedReportJsonFieldDisplayValueFormatter.getInstance().apply(ValueBehaviorApplier.Action.FORMATTING, List.of(record), qInstance, table, table.getField(fieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (record);
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
QRecord record = customize.apply(new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
|
||||||
|
.withColumnsJson(JsonUtils.toJson(new ReportColumns()))
|
||||||
|
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition())));
|
||||||
|
|
||||||
|
assertEquals("0 Filters", record.getDisplayValue("queryFilterJson"));
|
||||||
|
assertEquals("0 Columns", record.getDisplayValue("columnsJson"));
|
||||||
|
assertEquals("0 Rows, 0 Columns, and 0 Values", record.getDisplayValue("pivotTableJson"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QRecord record = customize.apply(new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
|
||||||
|
.withColumnsJson(JsonUtils.toJson(new ReportColumns())));
|
||||||
|
|
||||||
|
assertEquals("0 Filters", record.getDisplayValue("queryFilterJson"));
|
||||||
|
assertEquals("0 Columns", record.getDisplayValue("columnsJson"));
|
||||||
|
assertNull(record.getDisplayValue("pivotTableJson"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QRecord record = customize.apply(new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IS_NOT_BLANK))))
|
||||||
|
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
|
||||||
|
.withColumn(new ReportColumn().withName("birthDate"))))
|
||||||
|
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy())
|
||||||
|
.withValue(new PivotTableValue())
|
||||||
|
)));
|
||||||
|
|
||||||
|
assertEquals("1 Filter", record.getDisplayValue("queryFilterJson"));
|
||||||
|
assertEquals("1 Column", record.getDisplayValue("columnsJson"));
|
||||||
|
assertEquals("1 Row, 0 Columns, and 1 Value", record.getDisplayValue("pivotTableJson"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QRecord record = customize.apply(new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, 1))
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IS_NOT_BLANK))))
|
||||||
|
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
|
||||||
|
.withColumn(new ReportColumn().withName("__check__").withIsVisible(true))
|
||||||
|
.withColumn(new ReportColumn().withName("id"))
|
||||||
|
.withColumn(new ReportColumn().withName("firstName").withIsVisible(true))
|
||||||
|
.withColumn(new ReportColumn().withName("lastName").withIsVisible(false))
|
||||||
|
.withColumn(new ReportColumn().withName("birthDate"))))
|
||||||
|
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy())
|
||||||
|
.withRow(new PivotTableGroupBy())
|
||||||
|
.withColumn(new PivotTableGroupBy())
|
||||||
|
.withValue(new PivotTableValue())
|
||||||
|
.withValue(new PivotTableValue())
|
||||||
|
.withValue(new PivotTableValue())
|
||||||
|
)));
|
||||||
|
|
||||||
|
assertEquals("2 Filters", record.getDisplayValue("queryFilterJson"));
|
||||||
|
assertEquals("3 Columns", record.getDisplayValue("columnsJson"));
|
||||||
|
assertEquals("2 Rows, 1 Column, and 3 Values", record.getDisplayValue("pivotTableJson"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QRecord record = customize.apply(new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson("blah")
|
||||||
|
.withColumnsJson("<xml?>")
|
||||||
|
.withPivotTableJson("{]"));
|
||||||
|
|
||||||
|
assertEquals("Invalid Filter...", record.getDisplayValue("queryFilterJson"));
|
||||||
|
assertEquals("Invalid Columns...", record.getDisplayValue("columnsJson"));
|
||||||
|
assertEquals("Invalid Pivot Table...", record.getDisplayValue("pivotTableJson"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.savedreports;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
|
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.model.actions.reporting.pivottable.PivotTableDefinition;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
|
||||||
|
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.QQueryFilter;
|
||||||
|
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.utils.JsonUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for SavedReportTableCustomizer
|
||||||
|
*******************************************************************************/
|
||||||
|
class SavedReportTableCustomizerTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() throws QException
|
||||||
|
{
|
||||||
|
QContext.getQInstance().add(new SavedReportsMetaDataProvider().defineSavedReportTable(TestUtils.MEMORY_BACKEND_NAME, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testPreInsertAndPreUpdateAreWired() throws QException
|
||||||
|
{
|
||||||
|
SavedReport badRecord = new SavedReport()
|
||||||
|
.withLabel("My Report")
|
||||||
|
.withTableName("notATable");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// assertions to apply both to a failed insert and a failed update //
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
Consumer<QRecord> asserter = record -> assertThat(record.getErrors())
|
||||||
|
.hasSizeGreaterThanOrEqualTo(2)
|
||||||
|
.anyMatch(e -> e.getMessage().contains("Unrecognized table name"))
|
||||||
|
.anyMatch(e -> e.getMessage().contains("must contain at least 1 column"));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// go through insert action, to ensure wired-up correctly //
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(badRecord));
|
||||||
|
asserter.accept(insertOutput.getRecords().get(0));
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// likewise for update action //
|
||||||
|
////////////////////////////////
|
||||||
|
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(SavedReport.TABLE_NAME).withRecordEntity(badRecord));
|
||||||
|
asserter.accept(updateOutput.getRecords().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testParseFails()
|
||||||
|
{
|
||||||
|
QRecord record = new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson("...")
|
||||||
|
.withColumnsJson("x")
|
||||||
|
.withPivotTableJson("[")
|
||||||
|
.toQRecord();
|
||||||
|
|
||||||
|
new SavedReportTableCustomizer().preValidateRecord(record);
|
||||||
|
|
||||||
|
assertThat(record.getErrors())
|
||||||
|
.hasSize(3)
|
||||||
|
.anyMatch(e -> e.getMessage().contains("Unable to parse queryFilterJson"))
|
||||||
|
.anyMatch(e -> e.getMessage().contains("Unable to parse columnsJson"))
|
||||||
|
.anyMatch(e -> e.getMessage().contains("Unable to parse pivotTableJson"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testNoColumns()
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// given a reportColumns object, serialize it to json, put it in a saved report record, and run the pre-validator //
|
||||||
|
// then assert we got error saying there were no columns. //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Consumer<ReportColumns> asserter = reportColumns ->
|
||||||
|
{
|
||||||
|
SavedReport savedReport = new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
|
||||||
|
.withColumnsJson(JsonUtils.toJson(reportColumns));
|
||||||
|
|
||||||
|
QRecord record = savedReport.toQRecord();
|
||||||
|
new SavedReportTableCustomizer().preValidateRecord(record);
|
||||||
|
|
||||||
|
assertThat(record.getErrors())
|
||||||
|
.hasSize(1)
|
||||||
|
.anyMatch(e -> e.getMessage().contains("must contain at least 1 column"));
|
||||||
|
};
|
||||||
|
|
||||||
|
asserter.accept(new ReportColumns());
|
||||||
|
asserter.accept(new ReportColumns().withColumns(null));
|
||||||
|
asserter.accept(new ReportColumns().withColumns(new ArrayList<>()));
|
||||||
|
asserter.accept(new ReportColumns().withColumn(new ReportColumn()
|
||||||
|
.withName("id").withIsVisible(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testPivotTables()
|
||||||
|
{
|
||||||
|
BiConsumer<PivotTableDefinition, List<String>> asserter = (PivotTableDefinition ptd, List<String> expectedAnyMessageToContain) ->
|
||||||
|
{
|
||||||
|
SavedReport savedReport = new SavedReport()
|
||||||
|
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
|
||||||
|
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
|
||||||
|
.withColumn("id")
|
||||||
|
.withColumn("firstName")
|
||||||
|
.withColumn("lastName")
|
||||||
|
.withColumn("birthDate")))
|
||||||
|
.withPivotTableJson(JsonUtils.toJson(ptd));
|
||||||
|
|
||||||
|
QRecord record = savedReport.toQRecord();
|
||||||
|
new SavedReportTableCustomizer().preValidateRecord(record);
|
||||||
|
|
||||||
|
assertThat(record.getErrors()).hasSize(expectedAnyMessageToContain.size());
|
||||||
|
|
||||||
|
for(String expected : expectedAnyMessageToContain)
|
||||||
|
{
|
||||||
|
assertThat(record.getErrors())
|
||||||
|
.anyMatch(e -> e.getMessage().contains(expected));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition(), List.of("must contain at least 1 row"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withRow(new PivotTableGroupBy()),
|
||||||
|
List.of("Missing field name for at least one pivot table row"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("createDate")),
|
||||||
|
List.of("row is using field (Create Date) which is not an active column"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withColumn(new PivotTableGroupBy()),
|
||||||
|
List.of("Missing field name for at least one pivot table column"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withColumn(new PivotTableGroupBy().withFieldName("createDate")),
|
||||||
|
List.of("column is using field (Create Date) which is not an active column"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withValue(new PivotTableValue().withFunction(PivotTableFunction.SUM)),
|
||||||
|
List.of("Missing field name for at least one pivot table value"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withValue(new PivotTableValue().withFieldName("createDate").withFunction(PivotTableFunction.SUM)),
|
||||||
|
List.of("value is using field (Create Date) which is not an active column"));
|
||||||
|
|
||||||
|
asserter.accept(new PivotTableDefinition()
|
||||||
|
.withRow(new PivotTableGroupBy().withFieldName("id"))
|
||||||
|
.withValue(new PivotTableValue().withFieldName("firstName")),
|
||||||
|
List.of("Missing function for at least one pivot table value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user