mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Merge branch 'feature/QQQ-42-reports' into feature/sprint-11
This commit is contained in:
@ -33,10 +33,12 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
@ -81,6 +83,17 @@ public class MetaDataAction
|
||||
}
|
||||
metaDataOutput.setProcesses(processes);
|
||||
|
||||
//////////////////////////////////////
|
||||
// map reports to frontend metadata //
|
||||
//////////////////////////////////////
|
||||
Map<String, QFrontendReportMetaData> reports = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, QReportMetaData> entry : metaDataInput.getInstance().getReports().entrySet())
|
||||
{
|
||||
reports.put(entry.getKey(), new QFrontendReportMetaData(entry.getValue(), false));
|
||||
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
|
||||
}
|
||||
metaDataOutput.setReports(reports);
|
||||
|
||||
///////////////////////////////////
|
||||
// map apps to frontend metadata //
|
||||
///////////////////////////////////
|
||||
|
@ -66,7 +66,7 @@ public class CsvExportStreamer implements ExportStreamerInterface
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields) throws QReportingException
|
||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields, String label) throws QReportingException
|
||||
{
|
||||
this.exportInput = exportInput;
|
||||
this.fields = fields;
|
||||
@ -87,7 +87,7 @@ public class CsvExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
if(StringUtils.hasContent(exportInput.getTitleRow()))
|
||||
{
|
||||
outputStream.write(exportInput.getTitleRow().getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.write((exportInput.getTitleRow() + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
int col = 0;
|
||||
@ -114,9 +114,8 @@ public class CsvExportStreamer implements ExportStreamerInterface
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException
|
||||
public int addRecords(List<QRecord> qRecords) throws QReportingException
|
||||
{
|
||||
List<QRecord> qRecords = recordPipe.consumeAvailableRecords();
|
||||
LOG.info("Consuming [" + qRecords.size() + "] records from the pipe");
|
||||
|
||||
for(QRecord qRecord : qRecords)
|
||||
|
@ -31,6 +31,9 @@ import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excelformatting.ExcelStylerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excelformatting.PlainExcelStyler;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -41,8 +44,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dhatim.fastexcel.BorderSide;
|
||||
import org.dhatim.fastexcel.BorderStyle;
|
||||
import org.dhatim.fastexcel.StyleSetter;
|
||||
import org.dhatim.fastexcel.Workbook;
|
||||
import org.dhatim.fastexcel.Worksheet;
|
||||
|
||||
@ -59,11 +61,13 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
private List<QFieldMetaData> fields;
|
||||
private OutputStream outputStream;
|
||||
|
||||
private Map<String, String> excelCellFormats;
|
||||
private ExcelStylerInterface excelStylerInterface = new PlainExcelStyler();
|
||||
private Map<String, String> excelCellFormats;
|
||||
|
||||
private Workbook workbook;
|
||||
private Worksheet worksheet;
|
||||
private int row = 0;
|
||||
private int row = 0;
|
||||
private int sheetCount = 0;
|
||||
|
||||
|
||||
|
||||
@ -99,17 +103,39 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields) throws QReportingException
|
||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields, String label) throws QReportingException
|
||||
{
|
||||
this.exportInput = exportInput;
|
||||
this.fields = fields;
|
||||
table = exportInput.getTable();
|
||||
outputStream = this.exportInput.getReportOutputStream();
|
||||
try
|
||||
{
|
||||
this.exportInput = exportInput;
|
||||
this.fields = fields;
|
||||
table = exportInput.getTable();
|
||||
outputStream = this.exportInput.getReportOutputStream();
|
||||
this.row = 0;
|
||||
this.sheetCount++;
|
||||
|
||||
workbook = new Workbook(outputStream, "QQQ", null);
|
||||
worksheet = workbook.newWorksheet("Sheet 1");
|
||||
if(workbook == null)
|
||||
{
|
||||
workbook = new Workbook(outputStream, "QQQ", null);
|
||||
}
|
||||
|
||||
writeReportHeaderRow();
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if start is called a second time (e.g., and there's already an open worksheet), //
|
||||
// finish that sheet, before a new one is created. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
if(worksheet != null)
|
||||
{
|
||||
worksheet.finish();
|
||||
}
|
||||
|
||||
worksheet = workbook.newWorksheet(Objects.requireNonNullElse(label, "Sheet " + sheetCount));
|
||||
|
||||
writeReportHeaderRow();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QReportingException("Error starting worksheet", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -121,18 +147,24 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
try
|
||||
{
|
||||
///////////////
|
||||
// title row //
|
||||
///////////////
|
||||
if(StringUtils.hasContent(exportInput.getTitleRow()))
|
||||
{
|
||||
worksheet.value(row, 0, exportInput.getTitleRow());
|
||||
worksheet.range(row, 0, row, fields.size() - 1).merge();
|
||||
worksheet.range(row, 0, row, fields.size() - 1).style()
|
||||
.bold()
|
||||
.fontSize(14)
|
||||
.horizontalAlignment("center")
|
||||
.set();
|
||||
|
||||
StyleSetter titleStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
||||
excelStylerInterface.styleTitleRow(titleStyle);
|
||||
titleStyle.set();
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
////////////////
|
||||
// header row //
|
||||
////////////////
|
||||
int col = 0;
|
||||
for(QFieldMetaData column : fields)
|
||||
{
|
||||
@ -140,10 +172,9 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
col++;
|
||||
}
|
||||
|
||||
worksheet.range(row, 0, row, fields.size() - 1).style()
|
||||
.bold()
|
||||
.borderStyle(BorderSide.BOTTOM, BorderStyle.THIN)
|
||||
.set();
|
||||
StyleSetter headerStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
||||
excelStylerInterface.styleHeaderRow(headerStyle);
|
||||
headerStyle.set();
|
||||
|
||||
row++;
|
||||
|
||||
@ -161,9 +192,8 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException
|
||||
public int addRecords(List<QRecord> qRecords) throws QReportingException
|
||||
{
|
||||
List<QRecord> qRecords = recordPipe.consumeAvailableRecords();
|
||||
LOG.info("Consuming [" + qRecords.size() + "] records from the pipe");
|
||||
|
||||
try
|
||||
@ -203,6 +233,11 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
for(QFieldMetaData field : fields)
|
||||
{
|
||||
Serializable value = qRecord.getValue(field.getName());
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
value = Objects.requireNonNullElse(qRecord.getDisplayValue(field.getName()), value);
|
||||
}
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
if(value instanceof String s)
|
||||
@ -264,11 +299,9 @@ public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
writeRecord(record);
|
||||
|
||||
worksheet.range(row, 0, row, fields.size() - 1).style()
|
||||
.bold()
|
||||
.borderStyle(BorderSide.TOP, BorderStyle.THIN)
|
||||
.borderStyle(BorderSide.BOTTOM, BorderStyle.DOUBLE)
|
||||
.set();
|
||||
StyleSetter totalsRowStyle = worksheet.range(row, 0, row, fields.size() - 1).style();
|
||||
excelStylerInterface.styleTotalsRow(totalsRowStyle);
|
||||
totalsRowStyle.set();
|
||||
}
|
||||
|
||||
|
||||
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
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.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
@ -164,7 +165,7 @@ public class ExportAction
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ReportFormat reportFormat = exportInput.getReportFormat();
|
||||
ExportStreamerInterface reportStreamer = reportFormat.newReportStreamer();
|
||||
reportStreamer.start(exportInput, getFields(exportInput));
|
||||
reportStreamer.start(exportInput, getFields(exportInput), "Sheet 1");
|
||||
|
||||
//////////////////////////////////////////
|
||||
// run the query action as an async job //
|
||||
@ -207,7 +208,8 @@ public class ExportAction
|
||||
lastReceivedRecordsAt = System.currentTimeMillis();
|
||||
nextSleepMillis = INIT_SLEEP_MS;
|
||||
|
||||
int recordsConsumed = reportStreamer.takeRecordsFromPipe(recordPipe);
|
||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||
int recordsConsumed = reportStreamer.addRecords(records);
|
||||
recordCount += recordsConsumed;
|
||||
|
||||
LOG.info(countFromPreExecute != null
|
||||
@ -235,7 +237,8 @@ public class ExportAction
|
||||
///////////////////////////////////////////////////
|
||||
// send the final records to the report streamer //
|
||||
///////////////////////////////////////////////////
|
||||
int recordsConsumed = reportStreamer.takeRecordsFromPipe(recordPipe);
|
||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||
int recordsConsumed = reportStreamer.addRecords(records);
|
||||
recordCount += recordsConsumed;
|
||||
|
||||
long reportEndTime = System.currentTimeMillis();
|
||||
|
@ -38,12 +38,12 @@ public interface ExportStreamerInterface
|
||||
/*******************************************************************************
|
||||
** Called once, before any rows are available. Meant to write a header, for example.
|
||||
*******************************************************************************/
|
||||
void start(ExportInput exportInput, List<QFieldMetaData> fields) throws QReportingException;
|
||||
void start(ExportInput exportInput, List<QFieldMetaData> fields, String label) throws QReportingException;
|
||||
|
||||
/*******************************************************************************
|
||||
** Called as records flow into the pipe.
|
||||
******************************************************************************/
|
||||
int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException;
|
||||
int addRecords(List<QRecord> recordList) throws QReportingException;
|
||||
|
||||
/*******************************************************************************
|
||||
** Called once, after all rows are available. Meant to write a footer, or close resources, for example.
|
||||
@ -63,8 +63,6 @@ public interface ExportStreamerInterface
|
||||
*******************************************************************************/
|
||||
default void addTotalsRow(QRecord record) throws QReportingException
|
||||
{
|
||||
RecordPipe recordPipe = new RecordPipe();
|
||||
recordPipe.addRecord(record);
|
||||
takeRecordsFromPipe(recordPipe);
|
||||
addRecords(List.of(record));
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QFormulaException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
@ -154,17 +155,17 @@ public class FormulaInterpreter
|
||||
case "ADD":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).add(numbers.get(1)));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).add(numbers.get(1)));
|
||||
}
|
||||
case "MINUS":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).subtract(numbers.get(1)));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).subtract(numbers.get(1)));
|
||||
}
|
||||
case "MULTIPLY":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).multiply(numbers.get(1)));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).multiply(numbers.get(1)));
|
||||
}
|
||||
case "DIVIDE":
|
||||
{
|
||||
@ -173,7 +174,7 @@ public class FormulaInterpreter
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).divide(numbers.get(1), 4, RoundingMode.HALF_UP));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).divide(numbers.get(1), 4, RoundingMode.HALF_UP));
|
||||
}
|
||||
case "DIVIDE_SCALE":
|
||||
{
|
||||
@ -182,17 +183,82 @@ public class FormulaInterpreter
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).divide(numbers.get(1), numbers.get(2).intValue(), RoundingMode.HALF_UP));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).divide(numbers.get(1), numbers.get(2).intValue(), RoundingMode.HALF_UP));
|
||||
}
|
||||
case "ROUND":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).round(new MathContext(numbers.get(1).intValue())));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).round(new MathContext(numbers.get(1).intValue())));
|
||||
}
|
||||
case "SCALE":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElse(numbers, () -> numbers.get(0).setScale(numbers.get(1).intValue(), RoundingMode.HALF_UP));
|
||||
return nullIfAnyNullArgsElseBigDecimal(numbers, () -> numbers.get(0).setScale(numbers.get(1).intValue(), RoundingMode.HALF_UP));
|
||||
}
|
||||
case "NVL":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return Objects.requireNonNullElse(numbers.get(0), numbers.get(1));
|
||||
}
|
||||
case "IF":
|
||||
{
|
||||
// IF(CONDITION,TRUE,ELSE)
|
||||
List<Serializable> actualArgs = getArgumentList(args, 3, variableInterpreter);
|
||||
Serializable condition = actualArgs.get(0);
|
||||
boolean conditionBoolean;
|
||||
if(condition == null)
|
||||
{
|
||||
conditionBoolean = false;
|
||||
}
|
||||
else if(condition instanceof Boolean b)
|
||||
{
|
||||
conditionBoolean = b;
|
||||
}
|
||||
else if(condition instanceof BigDecimal bd)
|
||||
{
|
||||
conditionBoolean = (bd.compareTo(BigDecimal.ZERO) != 0);
|
||||
}
|
||||
else if(condition instanceof String s)
|
||||
{
|
||||
if("true".equalsIgnoreCase(s))
|
||||
{
|
||||
conditionBoolean = true;
|
||||
}
|
||||
else if("false".equalsIgnoreCase(s))
|
||||
{
|
||||
conditionBoolean = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
conditionBoolean = StringUtils.hasContent(s);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
conditionBoolean = false;
|
||||
}
|
||||
|
||||
return conditionBoolean ? actualArgs.get(1) : actualArgs.get(2);
|
||||
}
|
||||
case "LT":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElseBoolean(numbers, () -> numbers.get(0).compareTo(numbers.get(1)) < 0);
|
||||
}
|
||||
case "LTE":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElseBoolean(numbers, () -> numbers.get(0).compareTo(numbers.get(1)) <= 0);
|
||||
}
|
||||
case "GT":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElseBoolean(numbers, () -> numbers.get(0).compareTo(numbers.get(1)) > 0);
|
||||
}
|
||||
case "GTE":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
return nullIfAnyNullArgsElseBoolean(numbers, () -> numbers.get(0).compareTo(numbers.get(1)) >= 0);
|
||||
}
|
||||
default:
|
||||
{
|
||||
@ -230,7 +296,21 @@ public class FormulaInterpreter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Serializable nullIfAnyNullArgsElse(List<BigDecimal> numbers, Supplier<BigDecimal> supplier)
|
||||
private static Serializable nullIfAnyNullArgsElseBigDecimal(List<BigDecimal> numbers, Supplier<BigDecimal> supplier)
|
||||
{
|
||||
if(numbers.stream().anyMatch(Objects::isNull))
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Serializable nullIfAnyNullArgsElseBoolean(List<BigDecimal> numbers, Supplier<Boolean> supplier)
|
||||
{
|
||||
if(numbers.stream().anyMatch(Objects::isNull))
|
||||
{
|
||||
@ -259,7 +339,7 @@ public class FormulaInterpreter
|
||||
{
|
||||
try
|
||||
{
|
||||
Serializable interpretedArg = variableInterpreter.interpretForObject(ValueUtils.getValueAsString(originalArg));
|
||||
Serializable interpretedArg = variableInterpreter.interpretForObject(ValueUtils.getValueAsString(originalArg), null);
|
||||
rs.add(ValueUtils.getValueAsBigDecimal(interpretedArg));
|
||||
}
|
||||
catch(QValueException e)
|
||||
@ -270,4 +350,35 @@ public class FormulaInterpreter
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<Serializable> getArgumentList(List<Serializable> originalArgs, Integer howMany, QMetaDataVariableInterpreter variableInterpreter) throws QFormulaException
|
||||
{
|
||||
if(howMany != null)
|
||||
{
|
||||
if(!howMany.equals(originalArgs.size()))
|
||||
{
|
||||
throw (new QFormulaException("Wrong number of arguments (required: " + howMany + ", received: " + originalArgs.size() + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
List<Serializable> rs = new ArrayList<>();
|
||||
for(Serializable originalArg : originalArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
Serializable interpretedArg = variableInterpreter.interpretForObject(ValueUtils.getValueAsString(originalArg), null);
|
||||
rs.add(interpretedArg);
|
||||
}
|
||||
catch(QValueException e)
|
||||
{
|
||||
throw (new QFormulaException("Could not process [" + originalArg + "] as a number"));
|
||||
}
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@ -69,9 +70,16 @@ public class GenerateReportAction
|
||||
//////////////////////////////////////////////////
|
||||
// viewName > PivotKey > fieldName > Aggregates //
|
||||
//////////////////////////////////////////////////
|
||||
Map<String, Map<PivotKey, Map<String, AggregatesInterface<?>>>> pivotAggregates = new HashMap<>();
|
||||
Map<String, Map<PivotKey, Map<String, AggregatesInterface<?>>>> pivotAggregates = new HashMap<>();
|
||||
Map<String, Map<PivotKey, Map<String, AggregatesInterface<?>>>> variancePivotAggregates = new HashMap<>();
|
||||
|
||||
Map<String, AggregatesInterface<?>> totalAggregates = new HashMap<>();
|
||||
Map<String, AggregatesInterface<?>> totalAggregates = new HashMap<>();
|
||||
Map<String, AggregatesInterface<?>> varianceTotalAggregates = new HashMap<>();
|
||||
|
||||
private boolean includeTableView = false;
|
||||
private QReportMetaData report;
|
||||
private ReportFormat reportFormat;
|
||||
private ExportStreamerInterface reportStreamer;
|
||||
|
||||
|
||||
|
||||
@ -80,8 +88,56 @@ public class GenerateReportAction
|
||||
*******************************************************************************/
|
||||
public void execute(ReportInput reportInput) throws QException
|
||||
{
|
||||
report = reportInput.getInstance().getReport(reportInput.getReportName());
|
||||
Optional<QReportView> tableView = report.getViews().stream().filter(v -> v.getType().equals(ReportType.TABLE)).findFirst();
|
||||
|
||||
reportFormat = reportInput.getReportFormat();
|
||||
reportStreamer = reportFormat.newReportStreamer();
|
||||
|
||||
if(tableView.isPresent())
|
||||
{
|
||||
includeTableView = true;
|
||||
startTableView(reportInput, tableView.get());
|
||||
}
|
||||
|
||||
gatherData(reportInput);
|
||||
output(reportInput);
|
||||
gatherVarianceData(reportInput);
|
||||
|
||||
outputPivots(reportInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void startTableView(ReportInput reportInput, QReportView reportView) throws QReportingException
|
||||
{
|
||||
QTableMetaData table = reportInput.getInstance().getTable(report.getSourceTable());
|
||||
|
||||
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
||||
exportInput.setSession(reportInput.getSession());
|
||||
exportInput.setReportFormat(reportFormat);
|
||||
exportInput.setFilename(reportInput.getFilename());
|
||||
exportInput.setReportOutputStream(reportInput.getReportOutputStream());
|
||||
|
||||
// todo! reportStreamer.setDisplayFormats(getDisplayFormatMap(view));
|
||||
|
||||
List<QFieldMetaData> fields;
|
||||
if(CollectionUtils.nullSafeHasContents(reportView.getColumns()))
|
||||
{
|
||||
fields = new ArrayList<>();
|
||||
for(QReportField column : reportView.getColumns())
|
||||
{
|
||||
fields.add(table.getField(column.getName()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fields = new ArrayList<>(table.getFields().values());
|
||||
}
|
||||
reportStreamer.setDisplayFormats(getDisplayFormatMap(fields));
|
||||
reportStreamer.start(exportInput, fields, reportView.getLabel());
|
||||
}
|
||||
|
||||
|
||||
@ -91,9 +147,7 @@ public class GenerateReportAction
|
||||
*******************************************************************************/
|
||||
private void gatherData(ReportInput reportInput) throws QException
|
||||
{
|
||||
QReportMetaData report = reportInput.getInstance().getReport(reportInput.getReportName());
|
||||
QQueryFilter queryFilter = report.getQueryFilter();
|
||||
|
||||
QQueryFilter queryFilter = report.getQueryFilter();
|
||||
setInputValuesInQueryFilter(reportInput, queryFilter);
|
||||
|
||||
RecordPipe recordPipe = new RecordPipe();
|
||||
@ -104,8 +158,37 @@ public class GenerateReportAction
|
||||
queryInput.setRecordPipe(recordPipe);
|
||||
queryInput.setTableName(report.getSourceTable());
|
||||
queryInput.setFilter(queryFilter);
|
||||
queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this?
|
||||
return (new QueryAction().execute(queryInput));
|
||||
}, () -> consumeRecords(report, reportInput, recordPipe));
|
||||
}, () -> consumeRecords(reportInput, recordPipe, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void gatherVarianceData(ReportInput reportInput) throws QException
|
||||
{
|
||||
QQueryFilter varianceQueryFilter = report.getVarianceQueryFilter();
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@ -144,20 +227,35 @@ public class GenerateReportAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Integer consumeRecords(QReportMetaData report, ReportInput reportInput, RecordPipe recordPipe)
|
||||
private Integer consumeRecords(ReportInput reportInput, RecordPipe recordPipe, boolean isForVariance) throws QReportingException
|
||||
{
|
||||
// todo - stream to output if report has a simple type output
|
||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||
|
||||
if(includeTableView && !isForVariance)
|
||||
{
|
||||
reportStreamer.addRecords(records);
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// do aggregates for pivots //
|
||||
//////////////////////////////
|
||||
QTableMetaData table = reportInput.getInstance().getTable(report.getSourceTable());
|
||||
report.getViews().stream().filter(v -> v.getType().equals(ReportType.PIVOT)).forEach((view) ->
|
||||
{
|
||||
doPivotAggregates(view, table, records);
|
||||
addRecordsToPivotAggregates(view, table, records, isForVariance ? variancePivotAggregates : pivotAggregates);
|
||||
});
|
||||
|
||||
///////////////////////////////////////////
|
||||
// do totals too, if any views want them //
|
||||
///////////////////////////////////////////
|
||||
if(report.getViews().stream().filter(v -> v.getType().equals(ReportType.PIVOT)).anyMatch(QReportView::getTotalRow))
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
addRecordToAggregatesMap(table, record, isForVariance ? varianceTotalAggregates : totalAggregates);
|
||||
}
|
||||
}
|
||||
|
||||
return (records.size());
|
||||
}
|
||||
|
||||
@ -166,44 +264,33 @@ public class GenerateReportAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void doPivotAggregates(QReportView view, QTableMetaData table, List<QRecord> records)
|
||||
private void addRecordsToPivotAggregates(QReportView view, QTableMetaData table, List<QRecord> records, Map<String, Map<PivotKey, Map<String, AggregatesInterface<?>>>> aggregatesMap)
|
||||
{
|
||||
Map<PivotKey, Map<String, AggregatesInterface<?>>> viewAggregates = pivotAggregates.computeIfAbsent(view.getName(), (name) -> new HashMap<>());
|
||||
Map<PivotKey, Map<String, AggregatesInterface<?>>> viewAggregates = aggregatesMap.computeIfAbsent(view.getName(), (name) -> new HashMap<>());
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
PivotKey key = new PivotKey();
|
||||
for(String pivotField : view.getPivotFields())
|
||||
{
|
||||
key.add(pivotField, record.getValue(pivotField));
|
||||
Serializable pivotValue = record.getValue(pivotField);
|
||||
if(table.getField(pivotField).getPossibleValueSourceName() != null)
|
||||
{
|
||||
pivotValue = record.getDisplayValue(pivotField);
|
||||
}
|
||||
key.add(pivotField, pivotValue);
|
||||
|
||||
if(view.getPivotSubTotals() && key.getKeys().size() < view.getPivotFields().size())
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// be careful here, with these key objects, and their identity, being used as map keys //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
PivotKey subKey = key.clone();
|
||||
addRecordToPivotKeyAggregates(table, record, viewAggregates, subKey);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, AggregatesInterface<?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
|
||||
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
if(field.getType().equals(QFieldType.INTEGER))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<Integer> fieldAggregates = (AggregatesInterface<Integer>) keyAggregates.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
|
||||
fieldAggregates.add(record.getValueInteger(field.getName()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<Integer> fieldTotalAggregates = (AggregatesInterface<Integer>) totalAggregates.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
|
||||
fieldTotalAggregates.add(record.getValueInteger(field.getName()));
|
||||
}
|
||||
else if(field.getType().equals(QFieldType.DECIMAL))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<BigDecimal> fieldAggregates = (AggregatesInterface<BigDecimal>) keyAggregates.computeIfAbsent(field.getName(), (name) -> new BigDecimalAggregates());
|
||||
fieldAggregates.add(record.getValueBigDecimal(field.getName()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<BigDecimal> fieldTotalAggregates = (AggregatesInterface<BigDecimal>) totalAggregates.computeIfAbsent(field.getName(), (name) -> new BigDecimalAggregates());
|
||||
fieldTotalAggregates.add(record.getValueBigDecimal(field.getName()));
|
||||
}
|
||||
// todo - more types (dates, at least?)
|
||||
}
|
||||
addRecordToPivotKeyAggregates(table, record, viewAggregates, key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,16 +299,50 @@ public class GenerateReportAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void output(ReportInput reportInput) throws QReportingException, QFormulaException
|
||||
private void addRecordToPivotKeyAggregates(QTableMetaData table, QRecord record, Map<PivotKey, Map<String, AggregatesInterface<?>>> viewAggregates, PivotKey key)
|
||||
{
|
||||
QReportMetaData report = reportInput.getInstance().getReport(reportInput.getReportName());
|
||||
QTableMetaData table = reportInput.getInstance().getTable(report.getSourceTable());
|
||||
Map<String, AggregatesInterface<?>> keyAggregates = viewAggregates.computeIfAbsent(key, (name) -> new HashMap<>());
|
||||
addRecordToAggregatesMap(table, record, keyAggregates);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void addRecordToAggregatesMap(QTableMetaData table, QRecord record, Map<String, AggregatesInterface<?>> aggregatesMap)
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
if(field.getType().equals(QFieldType.INTEGER))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<Integer> fieldAggregates = (AggregatesInterface<Integer>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
|
||||
fieldAggregates.add(record.getValueInteger(field.getName()));
|
||||
}
|
||||
else if(field.getType().equals(QFieldType.DECIMAL))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
AggregatesInterface<BigDecimal> fieldAggregates = (AggregatesInterface<BigDecimal>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new BigDecimalAggregates());
|
||||
fieldAggregates.add(record.getValueBigDecimal(field.getName()));
|
||||
}
|
||||
// todo - more types (dates, at least?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
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.PIVOT)).toList();
|
||||
for(QReportView view : reportViews)
|
||||
{
|
||||
PivotOutput pivotOutput = outputPivot(reportInput, view, table);
|
||||
ReportFormat reportFormat = reportInput.getReportFormat();
|
||||
PivotOutput pivotOutput = computePivotRowsForView(reportInput, view, table);
|
||||
|
||||
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
||||
exportInput.setSession(reportInput.getSession());
|
||||
@ -230,21 +351,18 @@ public class GenerateReportAction
|
||||
exportInput.setTitleRow(pivotOutput.titleRow);
|
||||
exportInput.setReportOutputStream(reportInput.getReportOutputStream());
|
||||
|
||||
ExportStreamerInterface reportStreamer = reportFormat.newReportStreamer();
|
||||
reportStreamer.setDisplayFormats(getDisplayFormatMap(view));
|
||||
reportStreamer.start(exportInput, getFields(table, view));
|
||||
reportStreamer.start(exportInput, getFields(table, view), view.getLabel());
|
||||
|
||||
RecordPipe recordPipe = new RecordPipe(); // todo - make it an unlimited pipe or something...
|
||||
recordPipe.addRecords(pivotOutput.pivotRows);
|
||||
reportStreamer.takeRecordsFromPipe(recordPipe);
|
||||
reportStreamer.addRecords(pivotOutput.pivotRows); // todo - what if this set is huge?
|
||||
|
||||
if(pivotOutput.totalRow != null)
|
||||
{
|
||||
reportStreamer.addTotalsRow(pivotOutput.totalRow);
|
||||
}
|
||||
|
||||
reportStreamer.finish();
|
||||
}
|
||||
|
||||
reportStreamer.finish();
|
||||
}
|
||||
|
||||
|
||||
@ -261,6 +379,18 @@ public class GenerateReportAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<String, String> getDisplayFormatMap(List<QFieldMetaData> fields)
|
||||
{
|
||||
return (fields.stream()
|
||||
.filter(f -> f.getDisplayFormat() != null)
|
||||
.collect(Collectors.toMap(QFieldMetaData::getName, QFieldMetaData::getDisplayFormat)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -284,7 +414,7 @@ public class GenerateReportAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private PivotOutput outputPivot(ReportInput reportInput, QReportView view, QTableMetaData table) throws QReportingException, QFormulaException
|
||||
private PivotOutput computePivotRowsForView(ReportInput reportInput, QReportView view, QTableMetaData table) throws QReportingException, QFormulaException
|
||||
{
|
||||
QValueFormatter valueFormatter = new QValueFormatter();
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
@ -339,17 +469,44 @@ public class GenerateReportAction
|
||||
Map<String, AggregatesInterface<?>> fieldAggregates = entry.getValue();
|
||||
variableInterpreter.addValueMap("pivot", getPivotValuesForInterpreter(fieldAggregates));
|
||||
|
||||
HashMap<String, Serializable> thisRowValues = new HashMap<>();
|
||||
variableInterpreter.addValueMap("thisRow", thisRowValues);
|
||||
|
||||
if(!variancePivotAggregates.isEmpty())
|
||||
{
|
||||
Map<PivotKey, Map<String, AggregatesInterface<?>>> varianceMap = variancePivotAggregates.getOrDefault(view.getName(), Collections.emptyMap());
|
||||
Map<String, AggregatesInterface<?>> varianceSubMap = varianceMap.getOrDefault(pivotKey, Collections.emptyMap());
|
||||
variableInterpreter.addValueMap("variancePivot", getPivotValuesForInterpreter(varianceSubMap));
|
||||
}
|
||||
|
||||
QRecord pivotRow = new QRecord();
|
||||
pivotRows.add(pivotRow);
|
||||
|
||||
//////////////////////////
|
||||
// add the pivot values //
|
||||
//////////////////////////
|
||||
for(Pair<String, Serializable> key : pivotKey.getKeys())
|
||||
{
|
||||
pivotRow.setValue(key.getA(), key.getB());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// for pivot subtotals, add the text "Total" to the last field in this key //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
if(pivotKey.getKeys().size() < view.getPivotFields().size())
|
||||
{
|
||||
String fieldName = pivotKey.getKeys().get(pivotKey.getKeys().size() - 1).getA();
|
||||
pivotRow.setValue(fieldName, pivotRow.getValueString(fieldName) + " Total");
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// add the column values //
|
||||
///////////////////////////
|
||||
for(QReportField column : view.getColumns())
|
||||
{
|
||||
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
||||
pivotRow.setValue(column.getName(), serializable);
|
||||
thisRowValues.put(column.getName(), serializable);
|
||||
}
|
||||
}
|
||||
|
||||
@ -406,6 +563,8 @@ public class GenerateReportAction
|
||||
}
|
||||
|
||||
variableInterpreter.addValueMap("pivot", getPivotValuesForInterpreter(totalAggregates));
|
||||
variableInterpreter.addValueMap("variancePivot", getPivotValuesForInterpreter(varianceTotalAggregates));
|
||||
|
||||
for(QReportField column : view.getColumns())
|
||||
{
|
||||
Serializable serializable = getValueForColumn(variableInterpreter, column);
|
||||
@ -428,14 +587,17 @@ public class GenerateReportAction
|
||||
*******************************************************************************/
|
||||
private Serializable getValueForColumn(QMetaDataVariableInterpreter variableInterpreter, QReportField column) throws QFormulaException
|
||||
{
|
||||
String formula = column.getFormula();
|
||||
Serializable serializable = variableInterpreter.interpretForObject(formula);
|
||||
String formula = column.getFormula();
|
||||
Serializable result;
|
||||
if(formula.startsWith("=") && formula.length() > 1)
|
||||
{
|
||||
// serializable = interpretFormula(variableInterpreter, formula);
|
||||
serializable = FormulaInterpreter.interpretFormula(variableInterpreter, formula.substring(1));
|
||||
result = FormulaInterpreter.interpretFormula(variableInterpreter, formula.substring(1));
|
||||
}
|
||||
return serializable;
|
||||
else
|
||||
{
|
||||
result = variableInterpreter.interpretForObject(formula, null);
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
|
||||
|
@ -75,7 +75,7 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields) throws QReportingException
|
||||
public void start(ExportInput exportInput, List<QFieldMetaData> fields, String label) throws QReportingException
|
||||
{
|
||||
this.exportInput = exportInput;
|
||||
this.fields = fields;
|
||||
@ -93,9 +93,8 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int takeRecordsFromPipe(RecordPipe recordPipe) throws QReportingException
|
||||
public int addRecords(List<QRecord> qRecords) throws QReportingException
|
||||
{
|
||||
List<QRecord> qRecords = recordPipe.consumeAvailableRecords();
|
||||
LOG.info("Consuming [" + qRecords.size() + "] records from the pipe");
|
||||
|
||||
for(QRecord qRecord : qRecords)
|
||||
|
@ -32,7 +32,7 @@ import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class PivotKey
|
||||
public class PivotKey implements Cloneable
|
||||
{
|
||||
private List<Pair<String, Serializable>> keys = new ArrayList<>();
|
||||
|
||||
@ -108,4 +108,21 @@ public class PivotKey
|
||||
return Objects.hash(keys);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public PivotKey clone()
|
||||
{
|
||||
PivotKey clone = new PivotKey();
|
||||
|
||||
for(Pair<String, Serializable> key : keys)
|
||||
{
|
||||
clone.add(key.getA(), key.getB());
|
||||
}
|
||||
|
||||
return (clone);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.excelformatting;
|
||||
|
||||
|
||||
import org.dhatim.fastexcel.BorderSide;
|
||||
import org.dhatim.fastexcel.BorderStyle;
|
||||
import org.dhatim.fastexcel.StyleSetter;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BoldHeaderAndFooterExcelStyler implements ExcelStylerInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void styleTitleRow(StyleSetter titleRowStyle)
|
||||
{
|
||||
titleRowStyle
|
||||
.bold()
|
||||
.fontSize(14)
|
||||
.horizontalAlignment("center");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void styleHeaderRow(StyleSetter headerRowStyle)
|
||||
{
|
||||
headerRowStyle
|
||||
.bold()
|
||||
.borderStyle(BorderSide.BOTTOM, BorderStyle.THIN);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void styleTotalsRow(StyleSetter totalsRowStyle)
|
||||
{
|
||||
totalsRowStyle
|
||||
.bold()
|
||||
.borderStyle(BorderSide.TOP, BorderStyle.THIN)
|
||||
.borderStyle(BorderSide.BOTTOM, BorderStyle.DOUBLE);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.excelformatting;
|
||||
|
||||
|
||||
import org.dhatim.fastexcel.StyleSetter;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface ExcelStylerInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void styleTitleRow(StyleSetter titleRowStyle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void styleHeaderRow(StyleSetter headerRowStyle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void styleTotalsRow(StyleSetter totalsRowStyle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.excelformatting;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class PlainExcelStyler implements ExcelStylerInterface
|
||||
{
|
||||
|
||||
}
|
@ -294,11 +294,26 @@ public class QPossibleValueTranslator
|
||||
{
|
||||
for(QFieldMetaData field : fieldsByPvsTable.get(tableName))
|
||||
{
|
||||
values.add(record.getValue(field.getName()));
|
||||
Serializable fieldValue = record.getValue(field.getName());
|
||||
|
||||
//////////////////////////////////////
|
||||
// check if value is already cached //
|
||||
//////////////////////////////////////
|
||||
QPossibleValueSource possibleValueSource = pvsesByTable.get(tableName).get(0);
|
||||
possibleValueCache.putIfAbsent(possibleValueSource.getName(), new HashMap<>());
|
||||
Map<Serializable, String> cacheForPvs = possibleValueCache.get(possibleValueSource.getName());
|
||||
|
||||
if(!cacheForPvs.containsKey(fieldValue))
|
||||
{
|
||||
values.add(fieldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
primePvsCache(tableName, pvsesByTable.get(tableName), values);
|
||||
if(!values.isEmpty())
|
||||
{
|
||||
primePvsCache(tableName, pvsesByTable.get(tableName), values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponen
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
@ -99,6 +100,11 @@ public class QInstanceEnricher
|
||||
{
|
||||
qInstance.getApps().values().forEach(this::enrich);
|
||||
}
|
||||
|
||||
if(qInstance.getReports() != null)
|
||||
{
|
||||
qInstance.getReports().values().forEach(this::enrich);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -239,6 +245,24 @@ public class QInstanceEnricher
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void enrich(QReportMetaData report)
|
||||
{
|
||||
if(!StringUtils.hasContent(report.getLabel()))
|
||||
{
|
||||
report.setLabel(nameToLabel(report.getName()));
|
||||
}
|
||||
|
||||
if(report.getInputFields() != null)
|
||||
{
|
||||
report.getInputFields().forEach(this::enrich);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -551,17 +575,17 @@ public class QInstanceEnricher
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// create an identity section for the id and any fields in the record label //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
QAppSection defaultSection = new QAppSection(app.getName(), app.getLabel(), new QIcon("badge"), new ArrayList<>(), new ArrayList<>());
|
||||
QAppSection defaultSection = new QAppSection(app.getName(), app.getLabel(), new QIcon("badge"), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
|
||||
|
||||
boolean foundNonAppChild = false;
|
||||
if(CollectionUtils.nullSafeHasContents(app.getChildren()))
|
||||
{
|
||||
for(QAppChildMetaData child : app.getChildren())
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// only tables and processes are allowed to be in sections at this time, apps //
|
||||
// might be children but not in sections so keep track if we find any non-app //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// only tables, processes, and reports are allowed to be in sections at this time, apps //
|
||||
// might be children but not in sections so keep track if we find any non-app //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(child.getClass().equals(QTableMetaData.class))
|
||||
{
|
||||
defaultSection.getTables().add(child.getName());
|
||||
@ -572,6 +596,11 @@ public class QInstanceEnricher
|
||||
defaultSection.getProcesses().add(child.getName());
|
||||
foundNonAppChild = true;
|
||||
}
|
||||
else if(child.getClass().equals(QReportMetaData.class))
|
||||
{
|
||||
defaultSection.getReports().add(child.getName());
|
||||
foundNonAppChild = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,7 +510,8 @@ public class QInstanceValidator
|
||||
assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for a section in app " + app.getLabel() + ".");
|
||||
boolean hasTables = CollectionUtils.nullSafeHasContents(section.getTables());
|
||||
boolean hasProcesses = CollectionUtils.nullSafeHasContents(section.getProcesses());
|
||||
if(assertCondition(hasTables || hasProcesses, "App " + app.getName() + " section " + section.getName() + " does not have any children."))
|
||||
boolean hasReports = CollectionUtils.nullSafeHasContents(section.getReports());
|
||||
if(assertCondition(hasTables || hasProcesses || hasReports, "App " + app.getName() + " section " + section.getName() + " does not have any children."))
|
||||
{
|
||||
if(hasTables)
|
||||
{
|
||||
@ -532,6 +533,16 @@ public class QInstanceValidator
|
||||
childNamesInSections.add(processName);
|
||||
}
|
||||
}
|
||||
if(hasReports)
|
||||
{
|
||||
for(String reportName : section.getReports())
|
||||
{
|
||||
assertCondition(app.getChildren().stream().anyMatch(c -> c.getName().equals(reportName)), "App " + app.getName() + " section " + section.getName() + " specifies report " + reportName + ", which is not a child of this app.");
|
||||
assertCondition(!childNamesInSections.contains(reportName), "App " + app.getName() + " has report " + reportName + " listed more than once in its sections.");
|
||||
|
||||
childNamesInSections.add(reportName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,6 +148,23 @@ public class QMetaDataVariableInterpreter
|
||||
** Else the output is the input.
|
||||
*******************************************************************************/
|
||||
public Serializable interpretForObject(String value)
|
||||
{
|
||||
return (interpretForObject(value, value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interpret a value string, which may be a variable, into its run-time value.
|
||||
**
|
||||
** If input is null, output is null.
|
||||
** If input looks like ${env.X}, then the return value is the value of the env variable 'X'
|
||||
** If input looks like ${prop.X}, then the return value is the value of the system property 'X'
|
||||
** If input looks like ${literal.X}, then the return value is the literal 'X'
|
||||
** - used if you really want to get back the literal value, ${env.X}, for example.
|
||||
** Else the output is the input.
|
||||
*******************************************************************************/
|
||||
public Serializable interpretForObject(String value, Serializable defaultIfLooksLikeVariableButNotFound)
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
@ -176,6 +193,7 @@ public class QMetaDataVariableInterpreter
|
||||
|
||||
if(valueMaps != null)
|
||||
{
|
||||
boolean looksLikeVariable = false;
|
||||
for(Map.Entry<String, Map<String, Serializable>> entry : valueMaps.entrySet())
|
||||
{
|
||||
String name = entry.getKey();
|
||||
@ -184,6 +202,7 @@ public class QMetaDataVariableInterpreter
|
||||
String prefix = "${" + name + ".";
|
||||
if(value.startsWith(prefix) && value.endsWith("}"))
|
||||
{
|
||||
looksLikeVariable = true;
|
||||
String lookupName = value.substring(prefix.length()).replaceFirst("}$", "");
|
||||
if(valueMap != null && valueMap.containsKey(lookupName))
|
||||
{
|
||||
@ -191,6 +210,11 @@ public class QMetaDataVariableInterpreter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(looksLikeVariable)
|
||||
{
|
||||
return (defaultIfLooksLikeVariableButNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
return (value);
|
||||
|
@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
|
||||
|
||||
|
||||
@ -40,6 +41,7 @@ public class MetaDataOutput extends AbstractActionOutput
|
||||
{
|
||||
private Map<String, QFrontendTableMetaData> tables;
|
||||
private Map<String, QFrontendProcessMetaData> processes;
|
||||
private Map<String, QFrontendReportMetaData> reports;
|
||||
private Map<String, QFrontendAppMetaData> apps;
|
||||
|
||||
private List<AppTreeNode> appTree;
|
||||
@ -92,6 +94,28 @@ public class MetaDataOutput extends AbstractActionOutput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for reports
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QFrontendReportMetaData> getReports()
|
||||
{
|
||||
return reports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for reports
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setReports(Map<String, QFrontendReportMetaData> reports)
|
||||
{
|
||||
this.reports = reports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for appTree
|
||||
**
|
||||
|
@ -38,6 +38,11 @@ public class ProcessSummaryLine implements Serializable
|
||||
private Integer count = 0;
|
||||
private String message;
|
||||
|
||||
private String singularFutureMessage;
|
||||
private String pluralFutureMessage;
|
||||
private String singularPastMessage;
|
||||
private String pluralPastMessage;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// using ArrayList, because we need to be Serializable, and List is not //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -240,4 +245,160 @@ public class ProcessSummaryLine implements Serializable
|
||||
rs.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for singularFutureMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getSingularFutureMessage()
|
||||
{
|
||||
return singularFutureMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for singularFutureMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSingularFutureMessage(String singularFutureMessage)
|
||||
{
|
||||
this.singularFutureMessage = singularFutureMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for singularFutureMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withSingularFutureMessage(String singularFutureMessage)
|
||||
{
|
||||
this.singularFutureMessage = singularFutureMessage;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for pluralFutureMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getPluralFutureMessage()
|
||||
{
|
||||
return pluralFutureMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for pluralFutureMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPluralFutureMessage(String pluralFutureMessage)
|
||||
{
|
||||
this.pluralFutureMessage = pluralFutureMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for pluralFutureMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withPluralFutureMessage(String pluralFutureMessage)
|
||||
{
|
||||
this.pluralFutureMessage = pluralFutureMessage;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for singularPastMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getSingularPastMessage()
|
||||
{
|
||||
return singularPastMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for singularPastMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSingularPastMessage(String singularPastMessage)
|
||||
{
|
||||
this.singularPastMessage = singularPastMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for singularPastMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withSingularPastMessage(String singularPastMessage)
|
||||
{
|
||||
this.singularPastMessage = singularPastMessage;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for pluralPastMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getPluralPastMessage()
|
||||
{
|
||||
return pluralPastMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for pluralPastMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPluralPastMessage(String pluralPastMessage)
|
||||
{
|
||||
this.pluralPastMessage = pluralPastMessage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for pluralPastMessage
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withPluralPastMessage(String pluralPastMessage)
|
||||
{
|
||||
this.pluralPastMessage = pluralPastMessage;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void pickMessage(boolean isPast)
|
||||
{
|
||||
if(count != null)
|
||||
{
|
||||
if(count.equals(1))
|
||||
{
|
||||
setMessage(isPast ? getSingularPastMessage() : getSingularFutureMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
setMessage(isPast ? getPluralPastMessage() : getPluralFutureMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
@ -64,6 +65,10 @@ public class AppTreeNode
|
||||
{
|
||||
this.type = AppTreeNodeType.PROCESS;
|
||||
}
|
||||
else if(appChildMetaData.getClass().equals(QReportMetaData.class))
|
||||
{
|
||||
this.type = AppTreeNodeType.REPORT;
|
||||
}
|
||||
else if(appChildMetaData.getClass().equals(QAppMetaData.class))
|
||||
{
|
||||
this.type = AppTreeNodeType.APP;
|
||||
|
@ -23,11 +23,12 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Type for an Node in the an app tree.
|
||||
** Type for an Node in the app tree.
|
||||
*******************************************************************************/
|
||||
public enum AppTreeNodeType
|
||||
{
|
||||
TABLE,
|
||||
PROCESS,
|
||||
REPORT,
|
||||
APP
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.frontend;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Version of QReportMetaData that's meant for transmitting to a frontend.
|
||||
* e.g., it excludes backend-only details.
|
||||
*
|
||||
*******************************************************************************/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
public class QFrontendReportMetaData
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private String tableName;
|
||||
private String processName;
|
||||
|
||||
private String iconName;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFrontendReportMetaData(QReportMetaData reportMetaData, boolean includeSteps)
|
||||
{
|
||||
this.name = reportMetaData.getName();
|
||||
this.label = reportMetaData.getLabel();
|
||||
this.tableName = reportMetaData.getSourceTable();
|
||||
this.processName = reportMetaData.getProcessName();
|
||||
|
||||
if(reportMetaData.getIcon() != null)
|
||||
{
|
||||
this.iconName = reportMetaData.getIcon().getName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for primaryKeyField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getProcessName()
|
||||
{
|
||||
return processName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for iconName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getIconName()
|
||||
{
|
||||
return iconName;
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,7 @@ public class QAppSection
|
||||
|
||||
private List<String> tables;
|
||||
private List<String> processes;
|
||||
private List<String> reports;
|
||||
|
||||
|
||||
|
||||
@ -51,13 +52,14 @@ public class QAppSection
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAppSection(String name, String label, QIcon icon, List<String> tables, List<String> processes)
|
||||
public QAppSection(String name, String label, QIcon icon, List<String> tables, List<String> processes, List<String> reports)
|
||||
{
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
this.icon = icon;
|
||||
this.tables = tables;
|
||||
this.processes = processes;
|
||||
this.reports = reports;
|
||||
}
|
||||
|
||||
|
||||
@ -198,6 +200,40 @@ public class QAppSection
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for reports
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getReports()
|
||||
{
|
||||
return reports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for reports
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setReports(List<String> reports)
|
||||
{
|
||||
this.reports = reports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for reports
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QAppSection withReports(List<String> reports)
|
||||
{
|
||||
this.reports = reports;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for icon
|
||||
**
|
||||
|
@ -32,6 +32,7 @@ public enum QComponentType
|
||||
VALIDATION_REVIEW_SCREEN,
|
||||
EDIT_FORM,
|
||||
VIEW_FORM,
|
||||
DOWNLOAD_FORM,
|
||||
RECORD_LIST,
|
||||
PROCESS_SUMMARY_RESULTS;
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -29,7 +29,6 @@ public class QReportField
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private String fieldName;
|
||||
private String formula;
|
||||
private String displayFormat;
|
||||
// todo - type?
|
||||
@ -104,40 +103,6 @@ public class QReportField
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getFieldName()
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fieldName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QReportField withFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for formula
|
||||
**
|
||||
|
@ -25,20 +25,27 @@ package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||
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.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data definition of a report generated by QQQ
|
||||
*******************************************************************************/
|
||||
public class QReportMetaData
|
||||
public class QReportMetaData implements QAppChildMetaData
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
private List<QFieldMetaData> inputFields;
|
||||
private String sourceTable;
|
||||
private String processName;
|
||||
private QQueryFilter queryFilter;
|
||||
private QQueryFilter varianceQueryFilter;
|
||||
private List<QReportView> views;
|
||||
|
||||
private String parentAppName;
|
||||
private QIcon icon;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -177,6 +184,40 @@ public class QReportMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getProcessName()
|
||||
{
|
||||
return processName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setProcessName(String processName)
|
||||
{
|
||||
this.processName = processName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for processName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QReportMetaData withProcessName(String processName)
|
||||
{
|
||||
this.processName = processName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryFilter
|
||||
**
|
||||
@ -211,6 +252,40 @@ public class QReportMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for views
|
||||
**
|
||||
@ -243,4 +318,60 @@ public class QReportMetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setParentAppName(String parentAppName)
|
||||
{
|
||||
this.parentAppName = parentAppName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getParentAppName()
|
||||
{
|
||||
return (this.parentAppName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for icon
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QIcon getIcon()
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for icon
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setIcon(QIcon icon)
|
||||
{
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for icon
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QReportMetaData withIcon(QIcon icon)
|
||||
{
|
||||
this.icon = icon;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ public class QReportView
|
||||
private String titleFormat;
|
||||
private List<String> titleFields;
|
||||
private List<String> pivotFields;
|
||||
private boolean totalRow = false;
|
||||
private boolean totalRow = false;
|
||||
private boolean pivotSubTotals = false;
|
||||
private List<QReportField> columns;
|
||||
private List<QFilterOrderBy> orderByFields;
|
||||
|
||||
@ -281,6 +282,40 @@ public class QReportView
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for pivotSubTotals
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getPivotSubTotals()
|
||||
{
|
||||
return pivotSubTotals;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for pivotSubTotals
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPivotSubTotals(boolean pivotSubTotals)
|
||||
{
|
||||
this.pivotSubTotals = pivotSubTotals;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for pivotSubTotals
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QReportView withPivotSubTotals(boolean pivotSubTotals)
|
||||
{
|
||||
this.pivotSubTotals = pivotSubTotals;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for columns
|
||||
**
|
||||
|
@ -28,5 +28,5 @@ package com.kingsrook.qqq.backend.core.model.metadata.reporting;
|
||||
public enum ReportType
|
||||
{
|
||||
PIVOT,
|
||||
SIMPLE
|
||||
TABLE
|
||||
}
|
||||
|
@ -24,10 +24,11 @@ package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwit
|
||||
|
||||
import java.util.ArrayList;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for a class that can proivate a ProcessSummary - a list of Process Summary Lines
|
||||
** Interface for a class that can provide a ProcessSummary - a list of Process Summary Lines
|
||||
*******************************************************************************/
|
||||
public interface ProcessSummaryProviderInterface
|
||||
{
|
||||
@ -37,4 +38,29 @@ public interface ProcessSummaryProviderInterface
|
||||
*******************************************************************************/
|
||||
ArrayList<ProcessSummaryLine> getProcessSummary(boolean isForResultScreen);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** not meant to be overridden - meant to be called by framework - to make sure that
|
||||
** all lines have their proper message picked (e.g., if they have singular/plural
|
||||
** and past/future variants).
|
||||
*******************************************************************************/
|
||||
default ArrayList<ProcessSummaryLine> doGetProcessSummary(boolean isForResultScreen)
|
||||
{
|
||||
ArrayList<ProcessSummaryLine> processSummary = getProcessSummary(isForResultScreen);
|
||||
if(processSummary == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
for(ProcessSummaryLine processSummaryLine : processSummary)
|
||||
{
|
||||
if(!StringUtils.hasContent(processSummaryLine.getMessage()))
|
||||
{
|
||||
processSummaryLine.pickMessage(isForResultScreen);
|
||||
}
|
||||
}
|
||||
|
||||
return (processSummary);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// get the process summary from the ... transform step? the load step? each knows some... todo? //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.getProcessSummary(true));
|
||||
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.doGetProcessSummary(true));
|
||||
|
||||
transformStep.postRun(runBackendStepInput, runBackendStepOutput);
|
||||
loadStep.postRun(runBackendStepInput, runBackendStepOutput);
|
||||
@ -147,7 +147,7 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// make streamed input & output objects from the run input & outputs //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
StreamedBackendStepInput streamedBackendStepInput = new StreamedBackendStepInput(runBackendStepInput, qRecords);
|
||||
StreamedBackendStepInput streamedBackendStepInput = new StreamedBackendStepInput(runBackendStepInput, qRecords);
|
||||
StreamedBackendStepOutput streamedBackendStepOutput = new StreamedBackendStepOutput(runBackendStepOutput);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
@ -105,7 +105,7 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
|
||||
//////////////////////////////////////////////////////
|
||||
// get the process summary from the validation step //
|
||||
//////////////////////////////////////////////////////
|
||||
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_VALIDATION_SUMMARY, transformStep.getProcessSummary(false));
|
||||
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_VALIDATION_SUMMARY, transformStep.doGetProcessSummary(false));
|
||||
|
||||
transformStep.postRun(runBackendStepInput, runBackendStepOutput);
|
||||
}
|
||||
@ -131,7 +131,7 @@ public class StreamedETLValidateStep extends BaseStreamedETLStep implements Back
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// make streamed input & output objects from the run input & outputs //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
StreamedBackendStepInput streamedBackendStepInput = new StreamedBackendStepInput(runBackendStepInput, qRecords);
|
||||
StreamedBackendStepInput streamedBackendStepInput = new StreamedBackendStepInput(runBackendStepInput, qRecords);
|
||||
StreamedBackendStepOutput streamedBackendStepOutput = new StreamedBackendStepOutput(runBackendStepOutput);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.processes.implementations.reports;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
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.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Definition for Basic process to run a report.
|
||||
*******************************************************************************/
|
||||
public class BasicRunReportProcess
|
||||
{
|
||||
public static final String PROCESS_NAME = "reports.basic";
|
||||
|
||||
public static final String STEP_NAME_PREPARE = "prepare";
|
||||
public static final String STEP_NAME_INPUT = "input";
|
||||
public static final String STEP_NAME_EXECUTE = "execute";
|
||||
public static final String STEP_NAME_ACCESS = "accessReport";
|
||||
|
||||
public static final String FIELD_REPORT_NAME = "reportName";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QProcessMetaData defineProcessMetaData()
|
||||
{
|
||||
QStepMetaData prepareStep = new QBackendStepMetaData()
|
||||
.withName(STEP_NAME_PREPARE)
|
||||
.withCode(new QCodeReference(PrepareReportStep.class))
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withField(new QFieldMetaData(FIELD_REPORT_NAME, QFieldType.STRING)));
|
||||
|
||||
QStepMetaData inputStep = new QFrontendStepMetaData()
|
||||
.withName(STEP_NAME_INPUT)
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM));
|
||||
|
||||
QStepMetaData executeStep = new QBackendStepMetaData()
|
||||
.withName(STEP_NAME_EXECUTE)
|
||||
.withCode(new QCodeReference(ExecuteReportStep.class))
|
||||
.withInputData(new QFunctionInputMetaData()
|
||||
.withField(new QFieldMetaData(FIELD_REPORT_NAME, QFieldType.STRING)));
|
||||
|
||||
QStepMetaData accessStep = new QFrontendStepMetaData()
|
||||
.withName(STEP_NAME_ACCESS)
|
||||
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM));
|
||||
// .withViewField(new QFieldMetaData("outputFile", QFieldType.STRING))
|
||||
// .withViewField(new QFieldMetaData("message", QFieldType.STRING));
|
||||
|
||||
return new QProcessMetaData()
|
||||
.withName(PROCESS_NAME)
|
||||
.withIsHidden(true)
|
||||
.addStep(prepareStep)
|
||||
.addStep(inputStep)
|
||||
.addStep(executeStep)
|
||||
.addStep(accessStep);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.processes.implementations.reports;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process step to execute a report.
|
||||
**
|
||||
** Writes it to a temp file... Returns that file name in process output.
|
||||
*******************************************************************************/
|
||||
public class ExecuteReportStep implements BackendStep
|
||||
{
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
String reportName = runBackendStepInput.getValueString("reportName");
|
||||
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
|
||||
File tmpFile = File.createTempFile(reportName, ".xlsx", new File("/tmp/"));
|
||||
|
||||
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
|
||||
|
||||
try(FileOutputStream reportOutputStream = new FileOutputStream(tmpFile))
|
||||
{
|
||||
ReportInput reportInput = new ReportInput(runBackendStepInput.getInstance());
|
||||
reportInput.setSession(runBackendStepInput.getSession());
|
||||
reportInput.setReportName(reportName);
|
||||
reportInput.setReportFormat(ReportFormat.XLSX); // todo - variable
|
||||
reportInput.setReportOutputStream(reportOutputStream);
|
||||
|
||||
Map<String, Serializable> values = runBackendStepInput.getValues();
|
||||
reportInput.setInputValues(values);
|
||||
|
||||
new GenerateReportAction().execute(reportInput);
|
||||
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm").withZone(ZoneId.systemDefault());
|
||||
String datePart = formatter.format(Instant.now());
|
||||
|
||||
runBackendStepOutput.addValue("downloadFileName", report.getLabel() + " " + datePart + ".xlsx");
|
||||
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error running report", e));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.processes.implementations.reports;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Process step to prepare for running a report.
|
||||
**
|
||||
** Checks for input fields - if there are any, it puts them in process value output
|
||||
** as inputFieldList (QFieldMetaData objects).
|
||||
** If there aren't any input fields, re-routes the process to skip the input screen.
|
||||
*******************************************************************************/
|
||||
public class PrepareReportStep implements BackendStep
|
||||
{
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
String reportName = runBackendStepInput.getValueString("reportName");
|
||||
if(!StringUtils.hasContent(reportName))
|
||||
{
|
||||
throw (new QException("Process value [reportName] was not given."));
|
||||
}
|
||||
|
||||
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
|
||||
if(report == null)
|
||||
{
|
||||
throw (new QException("Process named [" + reportName + "] was not found in this instance."));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// if there are input fields, communicate them to the frontend //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(report.getInputFields()))
|
||||
{
|
||||
ArrayList<QFieldMetaData> inputFieldList = new ArrayList<>(report.getInputFields());
|
||||
runBackendStepOutput.addValue("inputFieldList", inputFieldList);
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////
|
||||
// no input? re-route the process to skip the input screen //
|
||||
//////////////////////////////////////////////////////////////
|
||||
List<String> stepList = new ArrayList<>(runBackendStepOutput.getProcessState().getStepList());
|
||||
stepList.removeIf(s -> s.equals(BasicRunReportProcess.STEP_NAME_INPUT));
|
||||
runBackendStepOutput.getProcessState().setStepList(stepList);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ import java.util.Objects;
|
||||
/*******************************************************************************
|
||||
** Simple container for two objects
|
||||
*******************************************************************************/
|
||||
public class Pair<A, B>
|
||||
public class Pair<A, B> implements Cloneable
|
||||
{
|
||||
private A a;
|
||||
private B b;
|
||||
@ -46,6 +46,9 @@ public class Pair<A, B>
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
@ -104,4 +107,23 @@ public class Pair<A, B>
|
||||
{
|
||||
return Objects.hash(a, b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Pair<A, B> clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Pair<A, B>) super.clone();
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class WidgetDataLoaderTest
|
||||
Object widgetData = new WidgetDataLoader().execute(TestUtils.defineInstance(), TestUtils.getMockSession(), PersonsByCreateDateBarChart.class.getSimpleName());
|
||||
assertThat(widgetData).isInstanceOf(ChartData.class);
|
||||
ChartData chartData = (ChartData) widgetData;
|
||||
assertEquals("chartData", chartData.getType());
|
||||
assertEquals("barChart", chartData.getType());
|
||||
assertThat(chartData.getTitle()).isNotBlank();
|
||||
assertNotNull(chartData.getChartData());
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ import static com.kingsrook.qqq.backend.core.actions.reporting.FormulaInterprete
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -140,4 +142,55 @@ class FormulaInterpreterTest
|
||||
assertEquals(new BigDecimal("27.78"), interpretFormula(vi, "SCALE(MULTIPLY(100,DIVIDE_SCALE(${pivot.sum.noOfShoes},${total.sum.noOfShoes},6)),2)"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testComparisons() throws QFormulaException
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
vi.addValueMap("input", Map.of("one", 1, "two", 2, "foo", "bar"));
|
||||
|
||||
assertTrue((Boolean) interpretFormula(vi, "LT(${input.one},${input.two})"));
|
||||
assertFalse((Boolean) interpretFormula(vi, "LT(${input.two},${input.one})"));
|
||||
|
||||
assertFalse((Boolean) interpretFormula(vi, "GT(${input.one},${input.two})"));
|
||||
assertTrue((Boolean) interpretFormula(vi, "GT(${input.two},${input.one})"));
|
||||
|
||||
assertTrue((Boolean) interpretFormula(vi, "LTE(${input.one},${input.two})"));
|
||||
assertTrue((Boolean) interpretFormula(vi, "LTE(${input.one},${input.one})"));
|
||||
assertFalse((Boolean) interpretFormula(vi, "LTE(${input.two},${input.one})"));
|
||||
|
||||
assertFalse((Boolean) interpretFormula(vi, "GTE(${input.one},${input.two})"));
|
||||
assertTrue((Boolean) interpretFormula(vi, "GTE(${input.one},${input.one})"));
|
||||
assertTrue((Boolean) interpretFormula(vi, "GTE(${input.two},${input.one})"));
|
||||
|
||||
// todo - google sheets compares strings differently...
|
||||
assertThatThrownBy(() -> interpretFormula(vi, "LT(${input.foo},${input.one})")).hasMessageContaining("[bar] as a number");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testConditionals() throws QFormulaException
|
||||
{
|
||||
QMetaDataVariableInterpreter vi = new QMetaDataVariableInterpreter();
|
||||
vi.addValueMap("input", Map.of("one", 1, "two", 2, "three", 3, "foo", "bar"));
|
||||
|
||||
assertEquals("A", interpretFormula(vi, "IF(LT(${input.one},${input.two}),A,B)"));
|
||||
assertEquals("B", interpretFormula(vi, "IF(GT(${input.one},${input.two}),A,B)"));
|
||||
|
||||
assertEquals("C", interpretFormula(vi, "IF(GT(${input.one},${input.two}),A,IF(GT(${input.two},${input.three}),B,C))"));
|
||||
assertEquals("B", interpretFormula(vi, "IF(GT(${input.one},${input.two}),A,IF(LT(${input.two},${input.three}),B,C))"));
|
||||
assertEquals("A", interpretFormula(vi, "IF(GT(${input.two},${input.one}),A,IF(LT(${input.two},${input.three}),B,C))"));
|
||||
|
||||
assertEquals("Yes", interpretFormula(vi, "IF(GT(${input.one},0),Yes,No)"));
|
||||
assertEquals("No", interpretFormula(vi, "IF(LT(${input.one},0),Yes,No)"));
|
||||
}
|
||||
|
||||
}
|
@ -59,7 +59,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
/*******************************************************************************
|
||||
** Unit test for GenerateReportAction
|
||||
*******************************************************************************/
|
||||
class GenerateReportActionTest
|
||||
public class GenerateReportActionTest
|
||||
{
|
||||
private static final String REPORT_NAME = "personReport1";
|
||||
|
||||
@ -243,32 +243,32 @@ class GenerateReportActionTest
|
||||
Map<String, String> row = iterator.next();
|
||||
assertEquals(6, list.size());
|
||||
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Jonson");
|
||||
assertThat(row.get("Quantity")).isNull();
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Jones");
|
||||
assertThat(row.get("Quantity")).isEqualTo("3");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelly");
|
||||
assertThat(row.get("Quantity")).isEqualTo("4");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Keller");
|
||||
assertThat(row.get("Quantity")).isEqualTo("5");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Quantity")).isEqualTo("6");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("2");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("MO");
|
||||
assertThat(row.get("Last Name")).isEqualTo("Kelkhoff");
|
||||
assertThat(row.get("Quantity")).isEqualTo("7");
|
||||
}
|
||||
@ -297,15 +297,14 @@ class GenerateReportActionTest
|
||||
Iterator<Map<String, String>> iterator = list.iterator();
|
||||
Map<String, String> row = iterator.next();
|
||||
assertEquals(2, list.size());
|
||||
assertThat(row.get("Home State Id")).isEqualTo("2");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("MO");
|
||||
assertThat(row.get("Last Name")).isNull();
|
||||
assertThat(row.get("Quantity")).isEqualTo("7");
|
||||
|
||||
row = iterator.next();
|
||||
assertThat(row.get("Home State Id")).isEqualTo("1");
|
||||
assertThat(row.get("Home State Id")).isEqualTo("IL");
|
||||
assertThat(row.get("Last Name")).isNull();
|
||||
assertThat(row.get("Quantity")).isEqualTo("18");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -398,7 +397,7 @@ class GenerateReportActionTest
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QReportMetaData defineReport(boolean includeTotalRow)
|
||||
public static QReportMetaData defineReport(boolean includeTotalRow)
|
||||
{
|
||||
return new QReportMetaData()
|
||||
.withName(REPORT_NAME)
|
||||
|
@ -584,7 +584,7 @@ class QInstanceValidatorTest
|
||||
{
|
||||
QAppMetaData app = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection(null, "Section 1", new QIcon("person"), List.of("test"), null));
|
||||
.withSection(new QAppSection(null, "Section 1", new QIcon("person"), List.of("test"), null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "Missing a name");
|
||||
}
|
||||
|
||||
@ -598,7 +598,7 @@ class QInstanceValidatorTest
|
||||
{
|
||||
QAppMetaData app = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("Section 1", null, new QIcon("person"), List.of("test"), null));
|
||||
.withSection(new QAppSection("Section 1", null, new QIcon("person"), List.of("test"), null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "Missing a label");
|
||||
}
|
||||
|
||||
@ -612,12 +612,12 @@ class QInstanceValidatorTest
|
||||
{
|
||||
QAppMetaData app1 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of(), List.of()));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of(), List.of(), null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "section1 does not have any children", "child test is not listed in any app sections");
|
||||
|
||||
QAppMetaData app2 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), null, null));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), null, null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "section1 does not have any children", "child test is not listed in any app sections");
|
||||
}
|
||||
|
||||
@ -631,11 +631,11 @@ class QInstanceValidatorTest
|
||||
{
|
||||
QAppMetaData app1 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test", "tset"), null));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test", "tset"), null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "not a child of this app");
|
||||
QAppMetaData app2 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), List.of("tset")));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), List.of("tset"), null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "not a child of this app");
|
||||
}
|
||||
|
||||
@ -649,23 +649,23 @@ class QInstanceValidatorTest
|
||||
{
|
||||
QAppMetaData app1 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test", "test"), null));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test", "test"), null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "more than once");
|
||||
|
||||
QAppMetaData app2 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), null))
|
||||
.withSection(new QAppSection("section2", "Section 2", new QIcon("person"), List.of("test"), null));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), null, null))
|
||||
.withSection(new QAppSection("section2", "Section 2", new QIcon("person"), List.of("test"), null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "more than once");
|
||||
|
||||
QAppMetaData app3 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), List.of("test")));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), List.of("test"), null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app3), "more than once");
|
||||
|
||||
QAppMetaData app4 = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), null, List.of("test", "test")));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), null, List.of("test", "test"), null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app4), "more than once");
|
||||
}
|
||||
|
||||
@ -687,7 +687,7 @@ class QInstanceValidatorTest
|
||||
QAppMetaData app = new QAppMetaData().withName("test")
|
||||
.withChild(new QTableMetaData().withName("tset"))
|
||||
.withChild(new QTableMetaData().withName("test"))
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), null));
|
||||
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), null, null));
|
||||
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "not listed in any app sections");
|
||||
}
|
||||
|
||||
|
@ -200,6 +200,29 @@ class QMetaDataVariableInterpreterTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testLooksLikeVariableButNotFound()
|
||||
{
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
variableInterpreter.addValueMap("input", Map.of("x", 1, "y", 2));
|
||||
variableInterpreter.addValueMap("others", Map.of("foo", "bar"));
|
||||
|
||||
assertNull(variableInterpreter.interpretForObject("${input.notFound}", null));
|
||||
assertEquals(0, variableInterpreter.interpretForObject("${input.notFound}", 0));
|
||||
assertEquals("--", variableInterpreter.interpretForObject("${input.notFound}", "--"));
|
||||
assertEquals("--", variableInterpreter.interpretForObject("${others.notFound}", "--"));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this one doesn't count as "looking like a variable" - because the "prefix" (notValid) isn't a value map... //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertEquals("${notValid.notFound}", variableInterpreter.interpretForObject("${notValid.notFound}", "--"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.processes.implementations.reports;
|
||||
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportActionTest;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for BasicRunReportProcess
|
||||
*******************************************************************************/
|
||||
class BasicRunReportProcessTest
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testRunReport() throws QException
|
||||
{
|
||||
QInstance instance = TestUtils.defineInstance();
|
||||
QReportMetaData report = GenerateReportActionTest.defineReport(true);
|
||||
QProcessMetaData runReportProcess = BasicRunReportProcess.defineProcessMetaData();
|
||||
|
||||
instance.addReport(report);
|
||||
report.setProcessName(runReportProcess.getName());
|
||||
instance.addProcess(runReportProcess);
|
||||
|
||||
RunProcessInput runProcessInput = new RunProcessInput(instance);
|
||||
runProcessInput.setSession(TestUtils.getMockSession());
|
||||
runProcessInput.setProcessName(report.getProcessName());
|
||||
runProcessInput.addValue(BasicRunReportProcess.FIELD_REPORT_NAME, report.getName());
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
String processUUID = runProcessOutput.getProcessUUID();
|
||||
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo(BasicRunReportProcess.STEP_NAME_INPUT);
|
||||
|
||||
runProcessInput.addValue("startDate", LocalDate.of(1980, Month.JANUARY, 1));
|
||||
runProcessInput.addValue("endDate", LocalDate.of(2099, Month.DECEMBER, 31));
|
||||
runProcessInput.setStartAfterStep(BasicRunReportProcess.STEP_NAME_INPUT);
|
||||
runProcessInput.setProcessUUID(processUUID);
|
||||
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo(BasicRunReportProcess.STEP_NAME_ACCESS);
|
||||
assertThat(runProcessOutput.getValues()).containsKeys("downloadFileName", "serverFilePath");
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user