mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
QQQ-42 adding data-sources to reports, customizer points
This commit is contained in:
@ -98,6 +98,23 @@
|
|||||||
<version>5.2.2</version>
|
<version>5.2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- the next 3 deps are being added for google drive support -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.api-client</groupId>
|
||||||
|
<artifactId>google-api-client</artifactId>
|
||||||
|
<version>1.35.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.auth</groupId>
|
||||||
|
<artifactId>google-auth-library-oauth2-http</artifactId>
|
||||||
|
<version>1.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.apis</groupId>
|
||||||
|
<artifactId>google-api-services-drive</artifactId>
|
||||||
|
<version>v3-rev20220815-2.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Common deps for all qqq modules -->
|
<!-- Common deps for all qqq modules -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
@ -49,6 +49,8 @@ public class FormulaInterpreter
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static Serializable interpretFormula(QMetaDataVariableInterpreter variableInterpreter, String formula) throws QFormulaException
|
public static Serializable interpretFormula(QMetaDataVariableInterpreter variableInterpreter, String formula) throws QFormulaException
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
List<Serializable> results = interpretFormula(variableInterpreter, formula, new AtomicInteger(0));
|
List<Serializable> results = interpretFormula(variableInterpreter, formula, new AtomicInteger(0));
|
||||||
if(results.size() == 1)
|
if(results.size() == 1)
|
||||||
@ -64,6 +66,11 @@ public class FormulaInterpreter
|
|||||||
throw (new QFormulaException("More than 1 result from formula"));
|
throw (new QFormulaException("More than 1 result from formula"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QFormulaException("Error interpreting formula [" + formula + "]", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,15 +29,19 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportViewCustomizer;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QFormulaException;
|
import com.kingsrook.qqq.backend.core.exceptions.QFormulaException;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||||
|
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.reporting.ExportInput;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||||
@ -48,11 +52,13 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
|||||||
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.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
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.reporting.ReportType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
@ -63,7 +69,16 @@ import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Action to generate a report!!
|
** Action to generate a report.
|
||||||
|
**
|
||||||
|
** A report can contain 1 or more Data Sources - e.g., tables + filters that define
|
||||||
|
** data that goes into the report.
|
||||||
|
**
|
||||||
|
** A report can also contain 1 or more Views - e.g., sheets in a spreadsheet workbook.
|
||||||
|
** (how do those work in non-XLSX formats??). Views can either be plain tables,
|
||||||
|
** summaries (like pivot tables, but called summary to avoid confusion with "native"
|
||||||
|
** pivot tables), or native pivot tables (not initially supported, due to lack of
|
||||||
|
** support in fastexcel...).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class GenerateReportAction
|
public class GenerateReportAction
|
||||||
{
|
{
|
||||||
@ -76,7 +91,6 @@ public class GenerateReportAction
|
|||||||
Map<String, AggregatesInterface<?>> totalAggregates = new HashMap<>();
|
Map<String, AggregatesInterface<?>> totalAggregates = new HashMap<>();
|
||||||
Map<String, AggregatesInterface<?>> varianceTotalAggregates = new HashMap<>();
|
Map<String, AggregatesInterface<?>> varianceTotalAggregates = new HashMap<>();
|
||||||
|
|
||||||
private boolean includeTableView = false;
|
|
||||||
private QReportMetaData report;
|
private QReportMetaData report;
|
||||||
private ReportFormat reportFormat;
|
private ReportFormat reportFormat;
|
||||||
private ExportStreamerInterface reportStreamer;
|
private ExportStreamerInterface reportStreamer;
|
||||||
@ -89,19 +103,52 @@ public class GenerateReportAction
|
|||||||
public void execute(ReportInput reportInput) throws QException
|
public void execute(ReportInput reportInput) throws QException
|
||||||
{
|
{
|
||||||
report = reportInput.getInstance().getReport(reportInput.getReportName());
|
report = reportInput.getInstance().getReport(reportInput.getReportName());
|
||||||
Optional<QReportView> tableView = report.getViews().stream().filter(v -> v.getType().equals(ReportType.TABLE)).findFirst();
|
|
||||||
|
|
||||||
reportFormat = reportInput.getReportFormat();
|
reportFormat = reportInput.getReportFormat();
|
||||||
reportStreamer = reportFormat.newReportStreamer();
|
reportStreamer = reportFormat.newReportStreamer();
|
||||||
|
|
||||||
if(tableView.isPresent())
|
for(QReportDataSource dataSource : report.getDataSources())
|
||||||
{
|
{
|
||||||
includeTableView = true;
|
List<QReportView> dataSourceTableViews = report.getViews().stream()
|
||||||
startTableView(reportInput, tableView.get());
|
.filter(v -> v.getType().equals(ReportType.TABLE))
|
||||||
|
.filter(v -> v.getDataSourceName().equals(dataSource.getName()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<QReportView> dataSourcePivotViews = report.getViews().stream()
|
||||||
|
.filter(v -> v.getType().equals(ReportType.SUMMARY))
|
||||||
|
.filter(v -> v.getDataSourceName().equals(dataSource.getName()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<QReportView> dataSourceVariantViews = report.getViews().stream()
|
||||||
|
.filter(v -> v.getType().equals(ReportType.SUMMARY))
|
||||||
|
.filter(v -> v.getVarianceDataSourceName() != null && v.getVarianceDataSourceName().equals(dataSource.getName()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if(dataSourceTableViews.isEmpty())
|
||||||
|
{
|
||||||
|
if(!dataSourcePivotViews.isEmpty() || !dataSourceVariantViews.isEmpty())
|
||||||
|
{
|
||||||
|
gatherData(reportInput, dataSource, null, dataSourcePivotViews, dataSourceVariantViews);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(QReportView dataSourceTableView : dataSourceTableViews)
|
||||||
|
{
|
||||||
|
if(dataSourceTableView.getViewCustomizer() != null)
|
||||||
|
{
|
||||||
|
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getFunction(dataSourceTableView.getViewCustomizer());
|
||||||
|
if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer)
|
||||||
|
{
|
||||||
|
reportViewCustomizer.setReportInput(reportInput);
|
||||||
|
}
|
||||||
|
dataSourceTableView = viewCustomizerFunction.apply(dataSourceTableView.clone()); // todo - will this throw concurrent mod exception??
|
||||||
}
|
}
|
||||||
|
|
||||||
gatherData(reportInput);
|
startTableView(reportInput, dataSource, dataSourceTableView);
|
||||||
gatherVarianceData(reportInput);
|
gatherData(reportInput, dataSource, dataSourceTableView, dataSourcePivotViews, dataSourceVariantViews);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
outputPivots(reportInput);
|
outputPivots(reportInput);
|
||||||
}
|
}
|
||||||
@ -111,9 +158,9 @@ public class GenerateReportAction
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void startTableView(ReportInput reportInput, QReportView reportView) throws QReportingException
|
private void startTableView(ReportInput reportInput, QReportDataSource dataSource, QReportView reportView) throws QReportingException
|
||||||
{
|
{
|
||||||
QTableMetaData table = reportInput.getInstance().getTable(report.getSourceTable());
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
|
|
||||||
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
||||||
exportInput.setSession(reportInput.getSession());
|
exportInput.setSession(reportInput.getSession());
|
||||||
@ -128,10 +175,17 @@ public class GenerateReportAction
|
|||||||
{
|
{
|
||||||
fields = new ArrayList<>();
|
fields = new ArrayList<>();
|
||||||
for(QReportField column : reportView.getColumns())
|
for(QReportField column : reportView.getColumns())
|
||||||
|
{
|
||||||
|
if(column.getIsVirtual())
|
||||||
|
{
|
||||||
|
fields.add(column.toField());
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
fields.add(table.getField(column.getName()));
|
fields.add(table.getField(column.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fields = new ArrayList<>(table.getFields().values());
|
fields = new ArrayList<>(table.getFields().values());
|
||||||
@ -145,50 +199,70 @@ public class GenerateReportAction
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void gatherData(ReportInput reportInput) throws QException
|
private void gatherData(ReportInput reportInput, QReportDataSource dataSource, QReportView tableView, List<QReportView> pivotViews, List<QReportView> variantViews) throws QException
|
||||||
{
|
{
|
||||||
QQueryFilter queryFilter = report.getQueryFilter();
|
QQueryFilter queryFilter = dataSource.getQueryFilter();
|
||||||
setInputValuesInQueryFilter(reportInput, queryFilter);
|
setInputValuesInQueryFilter(reportInput, queryFilter);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// check if this view has a transform step - if so, set it up now and run its pre-run //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
AbstractTransformStep transformStep = null;
|
||||||
|
RunBackendStepInput transformStepInput = null;
|
||||||
|
RunBackendStepOutput transformStepOutput = null;
|
||||||
|
if(tableView != null && tableView.getRecordTransformStep() != null)
|
||||||
|
{
|
||||||
|
transformStep = QCodeLoader.getBackendStep(AbstractTransformStep.class, tableView.getRecordTransformStep());
|
||||||
|
|
||||||
|
transformStepInput = new RunBackendStepInput(reportInput.getInstance());
|
||||||
|
transformStepInput.setSession(reportInput.getSession());
|
||||||
|
transformStepInput.setValues(reportInput.getInputValues());
|
||||||
|
|
||||||
|
transformStepOutput = new RunBackendStepOutput();
|
||||||
|
|
||||||
|
transformStep.preRun(transformStepInput, transformStepOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// create effectively-final versions of these vars for the lambda //
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
AbstractTransformStep finalTransformStep = transformStep;
|
||||||
|
RunBackendStepInput finalTransformStepInput = transformStepInput;
|
||||||
|
RunBackendStepOutput finalTransformStepOutput = transformStepOutput;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// run a record pipe loop, over the query for this data source //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
RecordPipe recordPipe = new RecordPipe();
|
RecordPipe recordPipe = new RecordPipe();
|
||||||
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
||||||
{
|
{
|
||||||
QueryInput queryInput = new QueryInput(reportInput.getInstance());
|
QueryInput queryInput = new QueryInput(reportInput.getInstance());
|
||||||
queryInput.setSession(reportInput.getSession());
|
queryInput.setSession(reportInput.getSession());
|
||||||
queryInput.setRecordPipe(recordPipe);
|
queryInput.setRecordPipe(recordPipe);
|
||||||
queryInput.setTableName(report.getSourceTable());
|
queryInput.setTableName(dataSource.getSourceTable());
|
||||||
queryInput.setFilter(queryFilter);
|
queryInput.setFilter(queryFilter);
|
||||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
||||||
return (new QueryAction().execute(queryInput));
|
return (new QueryAction().execute(queryInput));
|
||||||
}, () -> consumeRecords(reportInput, recordPipe, false));
|
}, () ->
|
||||||
|
{
|
||||||
|
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||||
|
if(finalTransformStep != null)
|
||||||
|
{
|
||||||
|
finalTransformStepInput.setRecords(records);
|
||||||
|
finalTransformStep.run(finalTransformStepInput, finalTransformStepOutput);
|
||||||
|
records = finalTransformStepOutput.getRecords();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (consumeRecords(reportInput, dataSource, records, tableView, pivotViews, variantViews));
|
||||||
|
});
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
/*******************************************************************************
|
// if there's a transformer, run its post-run //
|
||||||
**
|
////////////////////////////////////////////////
|
||||||
*******************************************************************************/
|
if(transformStep != null)
|
||||||
private void gatherVarianceData(ReportInput reportInput) throws QException
|
|
||||||
{
|
{
|
||||||
QQueryFilter varianceQueryFilter = report.getVarianceQueryFilter();
|
transformStep.postRun(transformStepInput, transformStepOutput);
|
||||||
if(varianceQueryFilter == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputValuesInQueryFilter(reportInput, varianceQueryFilter);
|
|
||||||
|
|
||||||
RecordPipe recordPipe = new RecordPipe();
|
|
||||||
new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) ->
|
|
||||||
{
|
|
||||||
QueryInput queryInput = new QueryInput(reportInput.getInstance());
|
|
||||||
queryInput.setSession(reportInput.getSession());
|
|
||||||
queryInput.setRecordPipe(recordPipe);
|
|
||||||
queryInput.setTableName(report.getSourceTable());
|
|
||||||
queryInput.setFilter(varianceQueryFilter);
|
|
||||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
|
||||||
return (new QueryAction().execute(queryInput));
|
|
||||||
}, () -> consumeRecords(reportInput, recordPipe, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -227,11 +301,14 @@ public class GenerateReportAction
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private Integer consumeRecords(ReportInput reportInput, RecordPipe recordPipe, boolean isForVariance) throws QReportingException
|
private Integer consumeRecords(ReportInput reportInput, QReportDataSource dataSource, List<QRecord> records, QReportView tableView, List<QReportView> pivotViews, List<QReportView> variantViews) throws QException
|
||||||
{
|
{
|
||||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
|
|
||||||
if(includeTableView && !isForVariance)
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if this record goes on a table view, add it to the report streamer now //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(tableView != null)
|
||||||
{
|
{
|
||||||
reportStreamer.addRecords(records);
|
reportStreamer.addRecords(records);
|
||||||
}
|
}
|
||||||
@ -239,20 +316,38 @@ public class GenerateReportAction
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// do aggregates for pivots //
|
// do aggregates for pivots //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
QTableMetaData table = reportInput.getInstance().getTable(report.getSourceTable());
|
if(pivotViews != null)
|
||||||
report.getViews().stream().filter(v -> v.getType().equals(ReportType.PIVOT)).forEach((view) ->
|
|
||||||
{
|
{
|
||||||
addRecordsToPivotAggregates(view, table, records, isForVariance ? variancePivotAggregates : pivotAggregates);
|
for(QReportView pivotView : pivotViews)
|
||||||
});
|
{
|
||||||
|
addRecordsToPivotAggregates(pivotView, table, records, pivotAggregates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(variantViews != null)
|
||||||
|
{
|
||||||
|
for(QReportView variantView : variantViews)
|
||||||
|
{
|
||||||
|
addRecordsToPivotAggregates(variantView, table, records, variancePivotAggregates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
// do totals too, if any views want them //
|
// do totals too, if any views want them //
|
||||||
///////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
if(report.getViews().stream().filter(v -> v.getType().equals(ReportType.PIVOT)).anyMatch(QReportView::getTotalRow))
|
if(pivotViews != null && pivotViews.stream().anyMatch(QReportView::getTotalRow))
|
||||||
{
|
{
|
||||||
for(QRecord record : records)
|
for(QRecord record : records)
|
||||||
{
|
{
|
||||||
addRecordToAggregatesMap(table, record, isForVariance ? varianceTotalAggregates : totalAggregates);
|
addRecordToAggregatesMap(table, record, totalAggregates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(variantViews != null && variantViews.stream().anyMatch(QReportView::getTotalRow))
|
||||||
|
{
|
||||||
|
for(QRecord record : records)
|
||||||
|
{
|
||||||
|
addRecordToAggregatesMap(table, record, varianceTotalAggregates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,11 +432,11 @@ public class GenerateReportAction
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void outputPivots(ReportInput reportInput) throws QReportingException, QFormulaException
|
private void outputPivots(ReportInput reportInput) throws QReportingException, QFormulaException
|
||||||
{
|
{
|
||||||
QTableMetaData table = reportInput.getInstance().getTable(report.getSourceTable());
|
List<QReportView> reportViews = report.getViews().stream().filter(v -> v.getType().equals(ReportType.SUMMARY)).toList();
|
||||||
|
|
||||||
List<QReportView> reportViews = report.getViews().stream().filter(v -> v.getType().equals(ReportType.PIVOT)).toList();
|
|
||||||
for(QReportView view : reportViews)
|
for(QReportView view : reportViews)
|
||||||
{
|
{
|
||||||
|
QReportDataSource dataSource = report.getDataSource(view.getDataSourceName());
|
||||||
|
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||||
PivotOutput pivotOutput = computePivotRowsForView(reportInput, view, table);
|
PivotOutput pivotOutput = computePivotRowsForView(reportInput, view, table);
|
||||||
|
|
||||||
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
||||||
@ -564,11 +659,14 @@ public class GenerateReportAction
|
|||||||
|
|
||||||
variableInterpreter.addValueMap("pivot", getPivotValuesForInterpreter(totalAggregates));
|
variableInterpreter.addValueMap("pivot", getPivotValuesForInterpreter(totalAggregates));
|
||||||
variableInterpreter.addValueMap("variancePivot", getPivotValuesForInterpreter(varianceTotalAggregates));
|
variableInterpreter.addValueMap("variancePivot", getPivotValuesForInterpreter(varianceTotalAggregates));
|
||||||
|
HashMap<String, Serializable> thisRowValues = new HashMap<>();
|
||||||
|
variableInterpreter.addValueMap("thisRow", thisRowValues);
|
||||||
|
|
||||||
for(QReportField column : view.getColumns())
|
for(QReportField column : view.getColumns())
|
||||||
{
|
{
|
||||||
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
||||||
totalRow.setValue(column.getName(), serializable);
|
totalRow.setValue(column.getName(), serializable);
|
||||||
|
thisRowValues.put(column.getName(), serializable);
|
||||||
|
|
||||||
String formatted = valueFormatter.formatValue(column.getDisplayFormat(), serializable);
|
String formatted = valueFormatter.formatValue(column.getDisplayFormat(), serializable);
|
||||||
System.out.printf("%25s", formatted);
|
System.out.printf("%25s", formatted);
|
||||||
|
@ -46,8 +46,9 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
|||||||
private ExportInput exportInput;
|
private ExportInput exportInput;
|
||||||
private List<QFieldMetaData> fields;
|
private List<QFieldMetaData> fields;
|
||||||
|
|
||||||
private static List<Map<String, String>> list = new ArrayList<>();
|
private static Map<String, List<Map<String, String>>> rows = new LinkedHashMap<>();
|
||||||
private static List<String> headers = new ArrayList<>();
|
private static Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||||
|
private static String currentSheetLabel;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -60,13 +61,25 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void reset()
|
||||||
|
{
|
||||||
|
rows.clear();
|
||||||
|
headers.clear();
|
||||||
|
currentSheetLabel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for list
|
** Getter for list
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static List<Map<String, String>> getList()
|
public static List<Map<String, String>> getList(String name)
|
||||||
{
|
{
|
||||||
return (list);
|
return (rows.get(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -80,10 +93,13 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
|||||||
this.exportInput = exportInput;
|
this.exportInput = exportInput;
|
||||||
this.fields = fields;
|
this.fields = fields;
|
||||||
|
|
||||||
headers = new ArrayList<>();
|
currentSheetLabel = label;
|
||||||
|
|
||||||
|
rows.put(label, new ArrayList<>());
|
||||||
|
headers.put(label, new ArrayList<>());
|
||||||
for(QFieldMetaData field : fields)
|
for(QFieldMetaData field : fields)
|
||||||
{
|
{
|
||||||
headers.add(field.getLabel());
|
headers.get(label).add(field.getLabel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,10 +128,10 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
|||||||
private void addRecord(QRecord qRecord)
|
private void addRecord(QRecord qRecord)
|
||||||
{
|
{
|
||||||
Map<String, String> row = new LinkedHashMap<>();
|
Map<String, String> row = new LinkedHashMap<>();
|
||||||
list.add(row);
|
rows.get(currentSheetLabel).add(row);
|
||||||
for(int i = 0; i < fields.size(); i++)
|
for(int i = 0; i < fields.size(); i++)
|
||||||
{
|
{
|
||||||
row.put(headers.get(i), qRecord.getValueString(fields.get(i).getName()));
|
row.put(headers.get(currentSheetLabel).get(i), qRecord.getValueString(fields.get(i).getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +141,7 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
public void addTotalsRow(QRecord record) throws QReportingException
|
public void addTotalsRow(QRecord record)
|
||||||
{
|
{
|
||||||
addRecord(record);
|
addRecord(record);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.actions.reporting.customizers;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public interface ReportViewCustomizer extends Function<QReportView, QReportView>
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
void setReportInput(ReportInput reportInput);
|
||||||
|
|
||||||
|
}
|
@ -266,7 +266,7 @@ public class QInstanceEnricher
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
static String nameToLabel(String name)
|
public static String nameToLabel(String name)
|
||||||
{
|
{
|
||||||
if(!StringUtils.hasContent(name))
|
if(!StringUtils.hasContent(name))
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,6 @@ public class QFrontendReportMetaData
|
|||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String label;
|
private String label;
|
||||||
private String tableName;
|
|
||||||
private String processName;
|
private String processName;
|
||||||
|
|
||||||
private String iconName;
|
private String iconName;
|
||||||
@ -55,7 +54,6 @@ public class QFrontendReportMetaData
|
|||||||
{
|
{
|
||||||
this.name = reportMetaData.getName();
|
this.name = reportMetaData.getName();
|
||||||
this.label = reportMetaData.getLabel();
|
this.label = reportMetaData.getLabel();
|
||||||
this.tableName = reportMetaData.getSourceTable();
|
|
||||||
this.processName = reportMetaData.getProcessName();
|
this.processName = reportMetaData.getProcessName();
|
||||||
|
|
||||||
if(reportMetaData.getIcon() != null)
|
if(reportMetaData.getIcon() != null)
|
||||||
@ -88,17 +86,6 @@ public class QFrontendReportMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for primaryKeyField
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public String getTableName()
|
|
||||||
{
|
|
||||||
return tableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for processName
|
** Getter for processName
|
||||||
**
|
**
|
||||||
|
@ -34,7 +34,8 @@ public enum QComponentType
|
|||||||
VIEW_FORM,
|
VIEW_FORM,
|
||||||
DOWNLOAD_FORM,
|
DOWNLOAD_FORM,
|
||||||
RECORD_LIST,
|
RECORD_LIST,
|
||||||
PROCESS_SUMMARY_RESULTS;
|
PROCESS_SUMMARY_RESULTS,
|
||||||
|
GOOGLE_DRIVE_SELECT_FOLDER;
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// keep these values in sync with QComponentType.ts in qqq-frontend-core //
|
// keep these values in sync with QComponentType.ts in qqq-frontend-core //
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public class QReportDataSource
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
private String sourceTable;
|
||||||
|
private QQueryFilter queryFilter;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for name
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for name
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for name
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for sourceTable
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getSourceTable()
|
||||||
|
{
|
||||||
|
return sourceTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for sourceTable
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setSourceTable(String sourceTable)
|
||||||
|
{
|
||||||
|
this.sourceTable = sourceTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for sourceTable
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withSourceTable(String sourceTable)
|
||||||
|
{
|
||||||
|
this.sourceTable = sourceTable;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryFilter
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QQueryFilter getQueryFilter()
|
||||||
|
{
|
||||||
|
return queryFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryFilter
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryFilter(QQueryFilter queryFilter)
|
||||||
|
{
|
||||||
|
this.queryFilter = queryFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for queryFilter
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource withQueryFilter(QQueryFilter queryFilter)
|
||||||
|
{
|
||||||
|
this.queryFilter = queryFilter;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,6 +22,10 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Field within a report
|
** Field within a report
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -29,9 +33,29 @@ public class QReportField
|
|||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String label;
|
private String label;
|
||||||
|
private QFieldType type;
|
||||||
private String formula;
|
private String formula;
|
||||||
private String displayFormat;
|
private String displayFormat;
|
||||||
// todo - type?
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Noew: new attributes added here probably belong in the toField method //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private boolean isVirtual = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFieldMetaData toField()
|
||||||
|
{
|
||||||
|
return new QFieldMetaData()
|
||||||
|
.withName(name)
|
||||||
|
.withLabel(label)
|
||||||
|
.withType(type)
|
||||||
|
.withDisplayFormat(displayFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -103,6 +127,40 @@ public class QReportField
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for type
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFieldType getType()
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for type
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setType(QFieldType type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for type
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportField withType(QFieldType type)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for formula
|
** Getter for formula
|
||||||
**
|
**
|
||||||
@ -169,4 +227,37 @@ public class QReportField
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for isVirtual
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public boolean getIsVirtual()
|
||||||
|
{
|
||||||
|
return isVirtual;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for isVirtual
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setIsVirtual(boolean isVirtual)
|
||||||
|
{
|
||||||
|
this.isVirtual = isVirtual;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for isVirtual
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportField withIsVirtual(boolean isVirtual)
|
||||||
|
{
|
||||||
|
this.isVirtual = isVirtual;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,10 @@ package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
|||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -36,11 +36,11 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String label;
|
private String label;
|
||||||
private List<QFieldMetaData> inputFields;
|
|
||||||
private String sourceTable;
|
|
||||||
private String processName;
|
private String processName;
|
||||||
private QQueryFilter queryFilter;
|
private List<QFieldMetaData> inputFields;
|
||||||
private QQueryFilter varianceQueryFilter;
|
|
||||||
|
private List<QReportDataSource> dataSources;
|
||||||
private List<QReportView> views;
|
private List<QReportView> views;
|
||||||
|
|
||||||
private String parentAppName;
|
private String parentAppName;
|
||||||
@ -150,40 +150,6 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for sourceTable
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public String getSourceTable()
|
|
||||||
{
|
|
||||||
return sourceTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for sourceTable
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setSourceTable(String sourceTable)
|
|
||||||
{
|
|
||||||
this.sourceTable = sourceTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for sourceTable
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public QReportMetaData withSourceTable(String sourceTable)
|
|
||||||
{
|
|
||||||
this.sourceTable = sourceTable;
|
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for processName
|
** Getter for processName
|
||||||
**
|
**
|
||||||
@ -219,68 +185,34 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for queryFilter
|
** Getter for dataSources
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QQueryFilter getQueryFilter()
|
public List<QReportDataSource> getDataSources()
|
||||||
{
|
{
|
||||||
return queryFilter;
|
return dataSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for queryFilter
|
** Setter for dataSources
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setQueryFilter(QQueryFilter queryFilter)
|
public void setDataSources(List<QReportDataSource> dataSources)
|
||||||
{
|
{
|
||||||
this.queryFilter = queryFilter;
|
this.dataSources = dataSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for queryFilter
|
** Fluent setter for dataSources
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QReportMetaData withQueryFilter(QQueryFilter queryFilter)
|
public QReportMetaData withDataSources(List<QReportDataSource> dataSources)
|
||||||
{
|
{
|
||||||
this.queryFilter = queryFilter;
|
this.dataSources = dataSources;
|
||||||
return (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Getter for varianceQueryFilter
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public QQueryFilter getVarianceQueryFilter()
|
|
||||||
{
|
|
||||||
return varianceQueryFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Setter for varianceQueryFilter
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public void setVarianceQueryFilter(QQueryFilter varianceQueryFilter)
|
|
||||||
{
|
|
||||||
this.varianceQueryFilter = varianceQueryFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
** Fluent setter for varianceQueryFilter
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
public QReportMetaData withVarianceQueryFilter(QQueryFilter varianceQueryFilter)
|
|
||||||
{
|
|
||||||
this.varianceQueryFilter = varianceQueryFilter;
|
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,4 +306,22 @@ public class QReportMetaData implements QAppChildMetaData
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportDataSource getDataSource(String dataSourceName)
|
||||||
|
{
|
||||||
|
for(QReportDataSource dataSource : CollectionUtils.nonNullList(dataSources))
|
||||||
|
{
|
||||||
|
if(dataSource.getName().equals(dataSourceName))
|
||||||
|
{
|
||||||
|
return (dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,21 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QReportView
|
public class QReportView implements Cloneable
|
||||||
{
|
{
|
||||||
private String name;
|
private String name;
|
||||||
private String label;
|
private String label;
|
||||||
|
private String dataSourceName;
|
||||||
|
private String varianceDataSourceName;
|
||||||
private ReportType type;
|
private ReportType type;
|
||||||
private String titleFormat;
|
private String titleFormat;
|
||||||
private List<String> titleFields;
|
private List<String> titleFields;
|
||||||
@ -42,6 +46,13 @@ public class QReportView
|
|||||||
private List<QReportField> columns;
|
private List<QReportField> columns;
|
||||||
private List<QFilterOrderBy> orderByFields;
|
private List<QFilterOrderBy> orderByFields;
|
||||||
|
|
||||||
|
private QCodeReference recordTransformStep;
|
||||||
|
private QCodeReference viewCustomizer;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Note: This class is Cloneable - think about if new fields added here need deep-copied in the clone method! //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -112,6 +123,74 @@ public class QReportView
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for dataSourceName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getDataSourceName()
|
||||||
|
{
|
||||||
|
return dataSourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for dataSourceName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setDataSourceName(String dataSourceName)
|
||||||
|
{
|
||||||
|
this.dataSourceName = dataSourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for dataSourceName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportView withDataSourceName(String dataSourceName)
|
||||||
|
{
|
||||||
|
this.dataSourceName = dataSourceName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for varianceDataSourceName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public String getVarianceDataSourceName()
|
||||||
|
{
|
||||||
|
return varianceDataSourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for varianceDataSourceName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setVarianceDataSourceName(String varianceDataSourceName)
|
||||||
|
{
|
||||||
|
this.varianceDataSourceName = varianceDataSourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for varianceDataSourceName
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportView withVarianceDataSourceName(String varianceDataSourceName)
|
||||||
|
{
|
||||||
|
this.varianceDataSourceName = varianceDataSourceName;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for type
|
** Getter for type
|
||||||
**
|
**
|
||||||
@ -382,4 +461,114 @@ public class QReportView
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for recordTransformerStep
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getRecordTransformStep()
|
||||||
|
{
|
||||||
|
return recordTransformStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for recordTransformerStep
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setRecordTransformStep(QCodeReference recordTransformStep)
|
||||||
|
{
|
||||||
|
this.recordTransformStep = recordTransformStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for recordTransformerStep
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportView withRecordTransformStep(QCodeReference recordTransformerStep)
|
||||||
|
{
|
||||||
|
this.recordTransformStep = recordTransformerStep;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for viewCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QCodeReference getViewCustomizer()
|
||||||
|
{
|
||||||
|
return viewCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for viewCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setViewCustomizer(QCodeReference viewCustomizer)
|
||||||
|
{
|
||||||
|
this.viewCustomizer = viewCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for viewCustomizer
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QReportView withViewCustomizer(QCodeReference viewCustomizer)
|
||||||
|
{
|
||||||
|
this.viewCustomizer = viewCustomizer;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public QReportView clone()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QReportView clone = (QReportView) super.clone();
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// copy any lists, etc //
|
||||||
|
/////////////////////////
|
||||||
|
if(titleFields != null)
|
||||||
|
{
|
||||||
|
clone.setTitleFields(new ArrayList<>(titleFields));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pivotFields != null)
|
||||||
|
{
|
||||||
|
clone.setPivotFields(new ArrayList<>(pivotFields));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(columns != null)
|
||||||
|
{
|
||||||
|
clone.setColumns(new ArrayList<>(columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(orderByFields != null)
|
||||||
|
{
|
||||||
|
clone.setOrderByFields(new ArrayList<>(orderByFields));
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch(CloneNotSupportedException e)
|
||||||
|
{
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public enum ReportType
|
public enum ReportType
|
||||||
{
|
{
|
||||||
PIVOT,
|
TABLE, // e.g., raw data in tabular form.
|
||||||
TABLE
|
SUMMARY, // e.g., summaries computed within QQQ
|
||||||
|
PIVOT // e.g., a true spreadsheet pivot. Not initially supported...
|
||||||
}
|
}
|
||||||
|
@ -398,7 +398,7 @@ public class CollectionUtils
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
endAt = startAt + limit;
|
endAt = startAt + limit;
|
||||||
if (endAt > list.size())
|
if(endAt > list.size())
|
||||||
{
|
{
|
||||||
endAt = list.size();
|
endAt = list.size();
|
||||||
}
|
}
|
||||||
@ -406,4 +406,20 @@ public class CollectionUtils
|
|||||||
|
|
||||||
return list.subList(startAt, endAt);
|
return list.subList(startAt, endAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Returns the input list, unless it was null - in which case a new array list is returned.
|
||||||
|
**
|
||||||
|
** Meant to help avoid null checks on foreach loops.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static <T> List<T> nonNullList(List<T> list)
|
||||||
|
{
|
||||||
|
if(list == null)
|
||||||
|
{
|
||||||
|
return (new ArrayList<>());
|
||||||
|
}
|
||||||
|
return (list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,12 +93,12 @@ class FormulaInterpreterTest
|
|||||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||||
vi.addValueMap("input", Map.of("i", 5, "c", 'c'));
|
vi.addValueMap("input", Map.of("i", 5, "c", 'c'));
|
||||||
|
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "")).hasMessageContaining("No results");
|
assertThatThrownBy(() -> interpretFormula(vi, "")).hasRootCauseMessage("No results from formula");
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "NOT-A-FUN(1,2)")).hasMessageContaining("unrecognized expression");
|
assertThatThrownBy(() -> interpretFormula(vi, "NOT-A-FUN(1,2)")).hasRootCauseMessage("Unable to evaluate unrecognized expression: NOT-A-FUN");
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1)")).hasMessageContaining("Wrong number of arguments");
|
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1)")).hasRootCauseMessage("Wrong number of arguments (required: 2, received: 1)");
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,2,3)")).hasMessageContaining("Wrong number of arguments");
|
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,2,3)")).hasRootCauseMessage("Wrong number of arguments (required: 2, received: 3)");
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,A)")).hasMessageContaining("[A] as a number");
|
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,A)")).hasRootCauseMessage("Could not process [A] as a number");
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,${input.c})")).hasMessageContaining("[c] as a number");
|
assertThatThrownBy(() -> interpretFormula(vi, "ADD(1,${input.c})")).hasRootCauseMessage("Could not process [c] as a number");
|
||||||
// todo - bad syntax (e.g., missing ')'
|
// todo - bad syntax (e.g., missing ')'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ class FormulaInterpreterTest
|
|||||||
assertTrue((Boolean) interpretFormula(vi, "GTE(${input.two},${input.one})"));
|
assertTrue((Boolean) interpretFormula(vi, "GTE(${input.two},${input.one})"));
|
||||||
|
|
||||||
// todo - google sheets compares strings differently...
|
// todo - google sheets compares strings differently...
|
||||||
assertThatThrownBy(() -> interpretFormula(vi, "LT(${input.foo},${input.one})")).hasMessageContaining("[bar] as a number");
|
assertThatThrownBy(() -> interpretFormula(vi, "LT(${input.foo},${input.one})")).hasRootCauseMessage("Could not process [bar] as a number");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
|
|||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
@ -41,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||||
@ -72,7 +74,7 @@ public class GenerateReportActionTest
|
|||||||
@AfterEach
|
@AfterEach
|
||||||
void beforeAndAfterEach()
|
void beforeAndAfterEach()
|
||||||
{
|
{
|
||||||
ListOfMapsExportStreamer.getList().clear();
|
ListOfMapsExportStreamer.reset();
|
||||||
MemoryRecordStore.getInstance().reset();
|
MemoryRecordStore.getInstance().reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,11 +87,11 @@ public class GenerateReportActionTest
|
|||||||
void testPivot1() throws QException
|
void testPivot1() throws QException
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
qInstance.addReport(defineReport(true));
|
qInstance.addReport(definePersonShoesPivotReport(true));
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
runReport(qInstance, LocalDate.of(1980, Month.JANUARY, 1), LocalDate.of(1980, Month.DECEMBER, 31));
|
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1), "endDate", LocalDate.of(1980, Month.DECEMBER, 31)));
|
||||||
|
|
||||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
|
||||||
Iterator<Map<String, String>> iterator = list.iterator();
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
assertEquals(3, list.size());
|
assertEquals(3, list.size());
|
||||||
@ -140,7 +142,7 @@ public class GenerateReportActionTest
|
|||||||
void testPivot2() throws QException
|
void testPivot2() throws QException
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QReportMetaData report = defineReport(false);
|
QReportMetaData report = definePersonShoesPivotReport(false);
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
// change from the default to sort reversed //
|
// change from the default to sort reversed //
|
||||||
@ -148,9 +150,9 @@ public class GenerateReportActionTest
|
|||||||
report.getViews().get(0).getOrderByFields().get(0).setIsAscending(false);
|
report.getViews().get(0).getOrderByFields().get(0).setIsAscending(false);
|
||||||
qInstance.addReport(report);
|
qInstance.addReport(report);
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
runReport(qInstance, LocalDate.of(1980, Month.JANUARY, 1), LocalDate.of(1980, Month.DECEMBER, 31));
|
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1), "endDate", LocalDate.of(1980, Month.DECEMBER, 31)));
|
||||||
|
|
||||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
|
||||||
Iterator<Map<String, String>> iterator = list.iterator();
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
assertEquals(2, list.size());
|
assertEquals(2, list.size());
|
||||||
@ -172,19 +174,19 @@ public class GenerateReportActionTest
|
|||||||
void testPivot3() throws QException
|
void testPivot3() throws QException
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QReportMetaData report = defineReport(false);
|
QReportMetaData report = definePersonShoesPivotReport(false);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// remove the filters, change to sort by personCount (to get some ties), then sumPrice desc //
|
// remove the filters, change to sort by personCount (to get some ties), then sumPrice desc //
|
||||||
// this also shows the behavior of a null value in an order by //
|
// this also shows the behavior of a null value in an order by //
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
report.setQueryFilter(null);
|
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
|
||||||
report.getViews().get(0).setOrderByFields(List.of(new QFilterOrderBy("personCount"), new QFilterOrderBy("sumPrice", false)));
|
report.getViews().get(0).setOrderByFields(List.of(new QFilterOrderBy("personCount"), new QFilterOrderBy("sumPrice", false)));
|
||||||
qInstance.addReport(report);
|
qInstance.addReport(report);
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
runReport(qInstance, LocalDate.now(), LocalDate.now());
|
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
|
||||||
|
|
||||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
|
||||||
Iterator<Map<String, String>> iterator = list.iterator();
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
|
|
||||||
@ -224,21 +226,21 @@ public class GenerateReportActionTest
|
|||||||
void testPivot4() throws QException
|
void testPivot4() throws QException
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QReportMetaData report = defineReport(false);
|
QReportMetaData report = definePersonShoesPivotReport(false);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// remove the filter, change to have 2 pivot columns - homeStateId and lastName - we should get no roll-up like this. //
|
// remove the filter, change to have 2 pivot columns - homeStateId and lastName - we should get no roll-up like this. //
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
report.setQueryFilter(null);
|
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
|
||||||
report.getViews().get(0).setPivotFields(List.of(
|
report.getViews().get(0).setPivotFields(List.of(
|
||||||
"homeStateId",
|
"homeStateId",
|
||||||
"lastName"
|
"lastName"
|
||||||
));
|
));
|
||||||
qInstance.addReport(report);
|
qInstance.addReport(report);
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
runReport(qInstance, LocalDate.now(), LocalDate.now());
|
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
|
||||||
|
|
||||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
|
||||||
Iterator<Map<String, String>> iterator = list.iterator();
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
assertEquals(6, list.size());
|
assertEquals(6, list.size());
|
||||||
@ -282,18 +284,18 @@ public class GenerateReportActionTest
|
|||||||
void testPivot5() throws QException
|
void testPivot5() throws QException
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QReportMetaData report = defineReport(false);
|
QReportMetaData report = definePersonShoesPivotReport(false);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
// remove the filter, and just pivot on homeStateId - should aggregate differently //
|
// remove the filter, and just pivot on homeStateId - should aggregate differently //
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
report.setQueryFilter(null);
|
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
|
||||||
report.getViews().get(0).setPivotFields(List.of("homeStateId"));
|
report.getViews().get(0).setPivotFields(List.of("homeStateId"));
|
||||||
qInstance.addReport(report);
|
qInstance.addReport(report);
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
runReport(qInstance, LocalDate.now(), LocalDate.now());
|
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
|
||||||
|
|
||||||
List<Map<String, String>> list = ListOfMapsExportStreamer.getList();
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
|
||||||
Iterator<Map<String, String>> iterator = list.iterator();
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
Map<String, String> row = iterator.next();
|
Map<String, String> row = iterator.next();
|
||||||
assertEquals(2, list.size());
|
assertEquals(2, list.size());
|
||||||
@ -319,7 +321,7 @@ public class GenerateReportActionTest
|
|||||||
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
qInstance.addReport(defineReport(true));
|
qInstance.addReport(definePersonShoesPivotReport(true));
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
|
|
||||||
ReportInput reportInput = new ReportInput(qInstance);
|
ReportInput reportInput = new ReportInput(qInstance);
|
||||||
@ -345,7 +347,7 @@ public class GenerateReportActionTest
|
|||||||
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
qInstance.addReport(defineReport(true));
|
qInstance.addReport(definePersonShoesPivotReport(true));
|
||||||
insertPersonRecords(qInstance);
|
insertPersonRecords(qInstance);
|
||||||
|
|
||||||
ReportInput reportInput = new ReportInput(qInstance);
|
ReportInput reportInput = new ReportInput(qInstance);
|
||||||
@ -364,14 +366,14 @@ public class GenerateReportActionTest
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void runReport(QInstance qInstance, LocalDate startDate, LocalDate endDate) throws QException
|
private void runReport(QInstance qInstance, Map<String, Serializable> inputValues) throws QException
|
||||||
{
|
{
|
||||||
ReportInput reportInput = new ReportInput(qInstance);
|
ReportInput reportInput = new ReportInput(qInstance);
|
||||||
reportInput.setSession(new QSession());
|
reportInput.setSession(new QSession());
|
||||||
reportInput.setReportName(REPORT_NAME);
|
reportInput.setReportName(REPORT_NAME);
|
||||||
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
|
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
|
||||||
reportInput.setReportOutputStream(new ByteArrayOutputStream());
|
reportInput.setReportOutputStream(new ByteArrayOutputStream());
|
||||||
reportInput.setInputValues(Map.of("startDate", startDate, "endDate", endDate));
|
reportInput.setInputValues(inputValues);
|
||||||
new GenerateReportAction().execute(reportInput);
|
new GenerateReportAction().execute(reportInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,23 +399,29 @@ public class GenerateReportActionTest
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static QReportMetaData defineReport(boolean includeTotalRow)
|
public static QReportMetaData definePersonShoesPivotReport(boolean includeTotalRow)
|
||||||
{
|
{
|
||||||
return new QReportMetaData()
|
return new QReportMetaData()
|
||||||
.withName(REPORT_NAME)
|
.withName(REPORT_NAME)
|
||||||
|
.withDataSources(List.of(
|
||||||
|
new QReportDataSource()
|
||||||
|
.withName("persons")
|
||||||
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
.withInputFields(List.of(
|
|
||||||
new QFieldMetaData("startDate", QFieldType.DATE_TIME),
|
|
||||||
new QFieldMetaData("endDate", QFieldType.DATE_TIME)
|
|
||||||
))
|
|
||||||
.withQueryFilter(new QQueryFilter()
|
.withQueryFilter(new QQueryFilter()
|
||||||
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.STARTS_WITH, List.of("K")))
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.STARTS_WITH, List.of("K")))
|
||||||
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.BETWEEN, List.of("${input.startDate}", "${input.endDate}")))
|
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.BETWEEN, List.of("${input.startDate}", "${input.endDate}")))
|
||||||
)
|
)
|
||||||
|
))
|
||||||
|
.withInputFields(List.of(
|
||||||
|
new QFieldMetaData("startDate", QFieldType.DATE_TIME),
|
||||||
|
new QFieldMetaData("endDate", QFieldType.DATE_TIME)
|
||||||
|
))
|
||||||
.withViews(List.of(
|
.withViews(List.of(
|
||||||
new QReportView()
|
new QReportView()
|
||||||
.withName("pivot")
|
.withName("pivot")
|
||||||
.withType(ReportType.PIVOT)
|
.withLabel("pivot")
|
||||||
|
.withDataSourceName("persons")
|
||||||
|
.withType(ReportType.SUMMARY)
|
||||||
.withPivotFields(List.of("lastName"))
|
.withPivotFields(List.of("lastName"))
|
||||||
.withTotalRow(includeTotalRow)
|
.withTotalRow(includeTotalRow)
|
||||||
.withTitleFormat("Number of shoes - people born between %s and %s - pivot on LastName, sort by Quantity, Revenue DESC")
|
.withTitleFormat("Number of shoes - people born between %s and %s - pivot on LastName, sort by Quantity, Revenue DESC")
|
||||||
@ -438,4 +446,112 @@ public class GenerateReportActionTest
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTableOnlyReport() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QReportMetaData report = new QReportMetaData()
|
||||||
|
.withName(REPORT_NAME)
|
||||||
|
.withDataSources(List.of(
|
||||||
|
new QReportDataSource()
|
||||||
|
.withName("persons")
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}")))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.withInputFields(List.of(
|
||||||
|
new QFieldMetaData("startDate", QFieldType.DATE_TIME)
|
||||||
|
))
|
||||||
|
.withViews(List.of(
|
||||||
|
new QReportView()
|
||||||
|
.withName("table1")
|
||||||
|
.withLabel("table1")
|
||||||
|
.withDataSourceName("persons")
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumns(List.of(
|
||||||
|
new QReportField().withName("id"),
|
||||||
|
new QReportField().withName("firstName"),
|
||||||
|
new QReportField().withName("lastName")
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
qInstance.addReport(report);
|
||||||
|
|
||||||
|
insertPersonRecords(qInstance);
|
||||||
|
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
|
||||||
|
|
||||||
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("table1");
|
||||||
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
|
Map<String, String> row = iterator.next();
|
||||||
|
assertEquals(5, list.size());
|
||||||
|
assertThat(row).containsOnlyKeys("Id", "First Name", "Last Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testTwoTableViewsOneDataSourceReport() throws QException
|
||||||
|
{
|
||||||
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
|
QReportMetaData report = new QReportMetaData()
|
||||||
|
.withName(REPORT_NAME)
|
||||||
|
.withDataSources(List.of(
|
||||||
|
new QReportDataSource()
|
||||||
|
.withName("persons")
|
||||||
|
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.withQueryFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}")))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.withInputFields(List.of(
|
||||||
|
new QFieldMetaData("startDate", QFieldType.DATE_TIME)
|
||||||
|
))
|
||||||
|
.withViews(List.of(
|
||||||
|
new QReportView()
|
||||||
|
.withName("table1")
|
||||||
|
.withLabel("table1")
|
||||||
|
.withDataSourceName("persons")
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumns(List.of(
|
||||||
|
new QReportField().withName("id"),
|
||||||
|
new QReportField().withName("firstName"),
|
||||||
|
new QReportField().withName("lastName")
|
||||||
|
)),
|
||||||
|
new QReportView()
|
||||||
|
.withName("table2")
|
||||||
|
.withLabel("table2")
|
||||||
|
.withDataSourceName("persons")
|
||||||
|
.withType(ReportType.TABLE)
|
||||||
|
.withColumns(List.of(
|
||||||
|
new QReportField().withName("birthDate")
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
qInstance.addReport(report);
|
||||||
|
|
||||||
|
insertPersonRecords(qInstance);
|
||||||
|
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
|
||||||
|
|
||||||
|
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("table1");
|
||||||
|
Iterator<Map<String, String>> iterator = list.iterator();
|
||||||
|
Map<String, String> row = iterator.next();
|
||||||
|
assertEquals(5, list.size());
|
||||||
|
assertThat(row).containsOnlyKeys("Id", "First Name", "Last Name");
|
||||||
|
|
||||||
|
list = ListOfMapsExportStreamer.getList("table2");
|
||||||
|
iterator = list.iterator();
|
||||||
|
row = iterator.next();
|
||||||
|
assertEquals(5, list.size());
|
||||||
|
assertThat(row).containsOnlyKeys("Birth Date");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -49,7 +49,7 @@ class BasicRunReportProcessTest
|
|||||||
void testRunReport() throws QException
|
void testRunReport() throws QException
|
||||||
{
|
{
|
||||||
QInstance instance = TestUtils.defineInstance();
|
QInstance instance = TestUtils.defineInstance();
|
||||||
QReportMetaData report = GenerateReportActionTest.defineReport(true);
|
QReportMetaData report = GenerateReportActionTest.definePersonShoesPivotReport(true);
|
||||||
QProcessMetaData runReportProcess = BasicRunReportProcess.defineProcessMetaData();
|
QProcessMetaData runReportProcess = BasicRunReportProcess.defineProcessMetaData();
|
||||||
|
|
||||||
instance.addReport(report);
|
instance.addReport(report);
|
||||||
|
Reference in New Issue
Block a user