QQQ-42 checkpoint of qqq reports

This commit is contained in:
2022-09-20 12:19:19 -05:00
parent 197dec3105
commit 1f546d8c7d
14 changed files with 375 additions and 55 deletions

View File

@ -22,7 +22,6 @@
package com.kingsrook.qqq.backend.core.actions.reporting; package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
@ -67,7 +66,8 @@ public class ExcelExportStreamer implements ExportStreamerInterface
private Workbook workbook; private Workbook workbook;
private Worksheet worksheet; private Worksheet worksheet;
private int row = 0; private int row = 0;
private int sheetCount = 0;
@ -112,6 +112,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface
table = exportInput.getTable(); table = exportInput.getTable();
outputStream = this.exportInput.getReportOutputStream(); outputStream = this.exportInput.getReportOutputStream();
this.row = 0; this.row = 0;
this.sheetCount++;
if(workbook == null) if(workbook == null)
{ {
@ -127,11 +128,11 @@ public class ExcelExportStreamer implements ExportStreamerInterface
worksheet.finish(); worksheet.finish();
} }
worksheet = workbook.newWorksheet(label); worksheet = workbook.newWorksheet(Objects.requireNonNullElse(label, "Sheet " + sheetCount));
writeReportHeaderRow(); writeReportHeaderRow();
} }
catch(IOException e) catch(Exception e)
{ {
throw (new QReportingException("Error starting worksheet", e)); throw (new QReportingException("Error starting worksheet", e));
} }

View File

@ -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.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter; import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -154,17 +155,17 @@ public class FormulaInterpreter
case "ADD": case "ADD":
{ {
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter); 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": case "MINUS":
{ {
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter); 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": case "MULTIPLY":
{ {
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter); 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": case "DIVIDE":
{ {
@ -173,7 +174,7 @@ public class FormulaInterpreter
{ {
return null; 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": case "DIVIDE_SCALE":
{ {
@ -182,23 +183,83 @@ public class FormulaInterpreter
{ {
return null; 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": case "ROUND":
{ {
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter); 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": case "SCALE":
{ {
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter); 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": case "NVL":
{ {
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter); List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
return Objects.requireNonNullElse(numbers.get(0), numbers.get(1)); 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: default:
{ {
//////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////
@ -235,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)) if(numbers.stream().anyMatch(Objects::isNull))
{ {
@ -275,4 +350,35 @@ public class FormulaInterpreter
return (rs); 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);
}
} }

View File

@ -469,6 +469,9 @@ public class GenerateReportAction
Map<String, AggregatesInterface<?>> fieldAggregates = entry.getValue(); Map<String, AggregatesInterface<?>> fieldAggregates = entry.getValue();
variableInterpreter.addValueMap("pivot", getPivotValuesForInterpreter(fieldAggregates)); variableInterpreter.addValueMap("pivot", getPivotValuesForInterpreter(fieldAggregates));
HashMap<String, Serializable> thisRowValues = new HashMap<>();
variableInterpreter.addValueMap("thisRow", thisRowValues);
if(!variancePivotAggregates.isEmpty()) if(!variancePivotAggregates.isEmpty())
{ {
Map<PivotKey, Map<String, AggregatesInterface<?>>> varianceMap = variancePivotAggregates.getOrDefault(view.getName(), Collections.emptyMap()); Map<PivotKey, Map<String, AggregatesInterface<?>>> varianceMap = variancePivotAggregates.getOrDefault(view.getName(), Collections.emptyMap());
@ -503,6 +506,7 @@ public class GenerateReportAction
{ {
Serializable serializable = getValueForColumn(variableInterpreter, column); Serializable serializable = getValueForColumn(variableInterpreter, column);
pivotRow.setValue(column.getName(), serializable); pivotRow.setValue(column.getName(), serializable);
thisRowValues.put(column.getName(), serializable);
} }
} }
@ -583,14 +587,17 @@ public class GenerateReportAction
*******************************************************************************/ *******************************************************************************/
private Serializable getValueForColumn(QMetaDataVariableInterpreter variableInterpreter, QReportField column) throws QFormulaException private Serializable getValueForColumn(QMetaDataVariableInterpreter variableInterpreter, QReportField column) throws QFormulaException
{ {
String formula = column.getFormula(); String formula = column.getFormula();
Serializable serializable = variableInterpreter.interpretForObject(formula); Serializable result;
if(formula.startsWith("=") && formula.length() > 1) if(formula.startsWith("=") && formula.length() > 1)
{ {
// serializable = interpretFormula(variableInterpreter, formula); result = FormulaInterpreter.interpretFormula(variableInterpreter, formula.substring(1));
serializable = FormulaInterpreter.interpretFormula(variableInterpreter, formula.substring(1));
} }
return serializable; else
{
result = variableInterpreter.interpretForObject(formula, null);
}
return (result);
} }

View File

@ -558,17 +558,17 @@ public class QInstanceEnricher
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// create an identity section for the id and any fields in the record label // // 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; boolean foundNonAppChild = false;
if(CollectionUtils.nullSafeHasContents(app.getChildren())) if(CollectionUtils.nullSafeHasContents(app.getChildren()))
{ {
for(QAppChildMetaData child : app.getChildren()) for(QAppChildMetaData child : app.getChildren())
{ {
//////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
// only tables and processes are allowed to be in sections at this time, apps // // 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 // // might be children but not in sections so keep track if we find any non-app //
//////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////
if(child.getClass().equals(QTableMetaData.class)) if(child.getClass().equals(QTableMetaData.class))
{ {
defaultSection.getTables().add(child.getName()); defaultSection.getTables().add(child.getName());
@ -579,6 +579,11 @@ public class QInstanceEnricher
defaultSection.getProcesses().add(child.getName()); defaultSection.getProcesses().add(child.getName());
foundNonAppChild = true; foundNonAppChild = true;
} }
else if(child.getClass().equals(QReportMetaData.class))
{
defaultSection.getReports().add(child.getName());
foundNonAppChild = true;
}
} }
} }

View File

@ -510,7 +510,8 @@ public class QInstanceValidator
assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for a section in app " + app.getLabel() + "."); assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for a section in app " + app.getLabel() + ".");
boolean hasTables = CollectionUtils.nullSafeHasContents(section.getTables()); boolean hasTables = CollectionUtils.nullSafeHasContents(section.getTables());
boolean hasProcesses = CollectionUtils.nullSafeHasContents(section.getProcesses()); 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) if(hasTables)
{ {
@ -532,6 +533,16 @@ public class QInstanceValidator
childNamesInSections.add(processName); 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);
}
}
} }
} }

View File

@ -36,6 +36,7 @@ public class QAppSection
private List<String> tables; private List<String> tables;
private List<String> processes; 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.name = name;
this.label = label; this.label = label;
this.icon = icon; this.icon = icon;
this.tables = tables; this.tables = tables;
this.processes = processes; 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 ** Getter for icon
** **

View File

@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInpu
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
/******************************************************************************* /*******************************************************************************
@ -50,8 +51,9 @@ public class ExecuteReportStep implements BackendStep
{ {
try try
{ {
String reportName = runBackendStepInput.getValueString("reportName"); String reportName = runBackendStepInput.getValueString("reportName");
File tmpFile = File.createTempFile(reportName, ".xlsx", new File("/tmp/")); QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
File tmpFile = File.createTempFile(reportName, ".xlsx", new File("/tmp/"));
runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report"); runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
@ -71,7 +73,7 @@ public class ExecuteReportStep implements BackendStep
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm").withZone(ZoneId.systemDefault()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm").withZone(ZoneId.systemDefault());
String datePart = formatter.format(Instant.now()); String datePart = formatter.format(Instant.now());
runBackendStepOutput.addValue("downloadFileName", reportName + "-" + datePart + ".xlsx"); runBackendStepOutput.addValue("downloadFileName", report.getLabel() + " " + datePart + ".xlsx");
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath()); runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
} }
} }

View File

@ -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.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.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)")); 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)"));
}
} }

View File

@ -59,7 +59,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
/******************************************************************************* /*******************************************************************************
** Unit test for GenerateReportAction ** Unit test for GenerateReportAction
*******************************************************************************/ *******************************************************************************/
class GenerateReportActionTest public class GenerateReportActionTest
{ {
private static final String REPORT_NAME = "personReport1"; private static final String REPORT_NAME = "personReport1";
@ -243,32 +243,32 @@ class GenerateReportActionTest
Map<String, String> row = iterator.next(); Map<String, String> row = iterator.next();
assertEquals(6, list.size()); 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("Last Name")).isEqualTo("Jonson");
assertThat(row.get("Quantity")).isNull(); assertThat(row.get("Quantity")).isNull();
row = iterator.next(); 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("Last Name")).isEqualTo("Jones");
assertThat(row.get("Quantity")).isEqualTo("3"); assertThat(row.get("Quantity")).isEqualTo("3");
row = iterator.next(); 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("Last Name")).isEqualTo("Kelly");
assertThat(row.get("Quantity")).isEqualTo("4"); assertThat(row.get("Quantity")).isEqualTo("4");
row = iterator.next(); 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("Last Name")).isEqualTo("Keller");
assertThat(row.get("Quantity")).isEqualTo("5"); assertThat(row.get("Quantity")).isEqualTo("5");
row = iterator.next(); 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("Last Name")).isEqualTo("Kelkhoff");
assertThat(row.get("Quantity")).isEqualTo("6"); assertThat(row.get("Quantity")).isEqualTo("6");
row = iterator.next(); 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("Last Name")).isEqualTo("Kelkhoff");
assertThat(row.get("Quantity")).isEqualTo("7"); assertThat(row.get("Quantity")).isEqualTo("7");
} }
@ -297,15 +297,14 @@ class GenerateReportActionTest
Iterator<Map<String, String>> iterator = list.iterator(); Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next(); Map<String, String> row = iterator.next();
assertEquals(2, list.size()); assertEquals(2, list.size());
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("Last Name")).isNull();
assertThat(row.get("Quantity")).isEqualTo("7"); assertThat(row.get("Quantity")).isEqualTo("7");
row = iterator.next(); 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("Last Name")).isNull();
assertThat(row.get("Quantity")).isEqualTo("18"); 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() return new QReportMetaData()
.withName(REPORT_NAME) .withName(REPORT_NAME)

View File

@ -584,7 +584,7 @@ class QInstanceValidatorTest
{ {
QAppMetaData app = new QAppMetaData().withName("test") QAppMetaData app = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "Missing a name");
} }
@ -598,7 +598,7 @@ class QInstanceValidatorTest
{ {
QAppMetaData app = new QAppMetaData().withName("test") QAppMetaData app = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "Missing a label");
} }
@ -612,12 +612,12 @@ class QInstanceValidatorTest
{ {
QAppMetaData app1 = new QAppMetaData().withName("test") QAppMetaData app1 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); 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") QAppMetaData app2 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); 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") QAppMetaData app1 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "not a child of this app");
QAppMetaData app2 = new QAppMetaData().withName("test") QAppMetaData app2 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "not a child of this app");
} }
@ -649,23 +649,23 @@ class QInstanceValidatorTest
{ {
QAppMetaData app1 = new QAppMetaData().withName("test") QAppMetaData app1 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "more than once");
QAppMetaData app2 = new QAppMetaData().withName("test") QAppMetaData app2 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test")) .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))
.withSection(new QAppSection("section2", "Section 2", new QIcon("person"), List.of("test"), null)); .withSection(new QAppSection("section2", "Section 2", new QIcon("person"), List.of("test"), null, null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "more than once"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "more than once");
QAppMetaData app3 = new QAppMetaData().withName("test") QAppMetaData app3 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app3), "more than once");
QAppMetaData app4 = new QAppMetaData().withName("test") QAppMetaData app4 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app4), "more than once");
} }
@ -687,7 +687,7 @@ class QInstanceValidatorTest
QAppMetaData app = new QAppMetaData().withName("test") QAppMetaData app = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("tset")) .withChild(new QTableMetaData().withName("tset"))
.withChild(new QTableMetaData().withName("test")) .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"); assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "not listed in any app sections");
} }

View File

@ -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}", "--"));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -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");
}
}

View File

@ -451,8 +451,8 @@ class QJavalinImplementationTest extends QJavalinTestBase
@Test @Test
void testExportFieldsQueryParam() void testExportFieldsQueryParam()
{ {
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/People.csv?fields=id,birthDate").asString(); HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/export/People.csv?fields=id,birthDate").asString();
String[] csvLines = response.getBody().split("\n"); String[] csvLines = response.getBody().split("\n");
assertEquals(""" assertEquals("""
"Id","Birth Date\"""", csvLines[0]); "Id","Birth Date\"""", csvLines[0]);
} }
@ -484,7 +484,7 @@ class QJavalinImplementationTest extends QJavalinTestBase
assertNotNull(jsonObject); assertNotNull(jsonObject);
assertEquals("barChart", jsonObject.getString("type")); assertEquals("barChart", jsonObject.getString("type"));
assertNotNull(jsonObject.getString("title")); assertNotNull(jsonObject.getString("title"));
assertNotNull(jsonObject.getJSONObject("barChartData")); assertNotNull(jsonObject.getJSONObject("chartData"));
} }
} }

View File

@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
/******************************************************************************* /*******************************************************************************
** Unit test for PersonsByCreateDateBarChart ** Unit test for PersonsByCreateDateBarChart
*******************************************************************************/ *******************************************************************************/
class PersonsByCreateDateChartTestData class PersonsByCreateDateBarChartTest
{ {
/******************************************************************************* /*******************************************************************************
@ -47,7 +47,7 @@ class PersonsByCreateDateChartTestData
Object widgetData = new PersonsByCreateDateBarChart().render(SampleMetaDataProvider.defineInstance(), new QSession(), null); Object widgetData = new PersonsByCreateDateBarChart().render(SampleMetaDataProvider.defineInstance(), new QSession(), null);
assertThat(widgetData).isInstanceOf(ChartData.class); assertThat(widgetData).isInstanceOf(ChartData.class);
ChartData chartData = (ChartData) widgetData; ChartData chartData = (ChartData) widgetData;
assertEquals("chartData", chartData.getType()); assertEquals("barChart", chartData.getType());
assertThat(chartData.getTitle()).isNotBlank(); assertThat(chartData.getTitle()).isNotBlank();
assertNotNull(chartData.getChartData()); assertNotNull(chartData.getChartData());
} }