Merge pull request #79 from Kingsrook/feature/CE-881-create-basic-saved-reports

Feature/ce 881 create basic saved reports
This commit is contained in:
2024-04-17 18:55:53 -05:00
committed by GitHub
127 changed files with 10689 additions and 539 deletions

View File

@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -43,6 +44,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
@ -118,6 +120,26 @@ class ExportActionTest extends BaseTest
runReport(recordCount, filename, ReportFormat.XLSX, true);
File file = new File(filename);
LocalMacDevUtils.openFile(file.getAbsolutePath());
assertTrue(file.delete());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testExcelPOI() throws Exception
{
int recordCount = 1000;
String filename = "/tmp/ReportActionTest-POI.xlsx";
runReport(recordCount, filename, ReportFormat.XLSX, true);
File file = new File(filename);
LocalMacDevUtils.openFile(file.getAbsolutePath());
assertTrue(file.delete());
}
@ -147,9 +169,10 @@ class ExportActionTest extends BaseTest
ExportInput exportInput = new ExportInput();
exportInput.setTableName(TestUtils.TABLE_NAME_ORDER);
exportInput.setReportFormat(ReportFormat.CSV);
ByteArrayOutputStream reportOutputStream = new ByteArrayOutputStream();
exportInput.setReportOutputStream(reportOutputStream);
exportInput.setReportDestination(new ReportDestination()
.withReportFormat(ReportFormat.CSV)
.withReportOutputStream(reportOutputStream));
exportInput.setQueryFilter(new QQueryFilter());
exportInput.setFieldNames(List.of("id", "orderNo", "storeId", "orderLine.id", "orderLine.sku", "orderLine.quantity"));
// exportInput.setFieldNames(List.of("id", "orderNo", "storeId"));
@ -197,8 +220,7 @@ class ExportActionTest extends BaseTest
exportInput.setTableName("person");
QTableMetaData table = exportInput.getTable();
exportInput.setReportFormat(reportFormat);
exportInput.setReportOutputStream(outputStream);
exportInput.setReportDestination(new ReportDestination().withReportFormat(reportFormat).withReportOutputStream(outputStream));
exportInput.setQueryFilter(new QQueryFilter());
exportInput.setLimit(recordCount);
@ -243,7 +265,7 @@ class ExportActionTest extends BaseTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// use xlsx, which has a max-rows limit, to verify that code runs, but doesn't throw when there aren't too many rows //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
exportInput.setReportFormat(ReportFormat.XLSX);
exportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.XLSX));
new ExportAction().preExecute(exportInput);
@ -278,7 +300,7 @@ class ExportActionTest extends BaseTest
////////////////////////////////////////////////////////////////
// use xlsx, which has a max-cols limit, to verify that code. //
////////////////////////////////////////////////////////////////
exportInput.setReportFormat(ReportFormat.XLSX);
exportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.XLSX));
assertThrows(QUserFacingException.class, () ->
{

View File

@ -23,19 +23,33 @@ package com.kingsrook.qqq.backend.core.actions.reporting;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.fastexcel.ExcelFastexcelExportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.BoldHeaderAndFooterPoiExcelStyler;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer;
import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.PoiExcelStylerInterface;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination;
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.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
@ -51,7 +65,17 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.core.testutils.PersonQRecord;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFPivotTable;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -85,14 +109,14 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot1() throws QException
void testSummary1() throws QException
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePersonShoesPivotReport(true));
qInstance.addReport(definePersonShoesSummaryReport(true));
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1), "endDate", LocalDate.of(1980, Month.DECEMBER, 31)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(3, list.size());
@ -140,10 +164,10 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot2() throws QException
void testSummary2() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
//////////////////////////////////////////////
// change from the default to sort reversed //
@ -153,7 +177,7 @@ public class GenerateReportActionTest extends BaseTest
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1), "endDate", LocalDate.of(1980, Month.DECEMBER, 31)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(2, list.size());
@ -172,10 +196,10 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot3() throws QException
void testSummary3() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
//////////////////////////////////////////////////////////////////////////////////////////////
// remove the filters, change to sort by personCount (to get some ties), then sumPrice desc //
@ -187,7 +211,7 @@ public class GenerateReportActionTest extends BaseTest
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
@ -224,16 +248,16 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot4() throws QException
void testSummary4() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// remove the filter, change to have 2 pivot columns - homeStateId and lastName - we should get no roll-up like this. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
report.getViews().get(0).setPivotFields(List.of(
report.getViews().get(0).setSummaryFields(List.of(
"homeStateId",
"lastName"
));
@ -241,7 +265,7 @@ public class GenerateReportActionTest extends BaseTest
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(6, list.size());
@ -282,21 +306,21 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void testPivot5() throws QException
void testSummary5() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = definePersonShoesPivotReport(false);
QReportMetaData report = definePersonShoesSummaryReport(false);
/////////////////////////////////////////////////////////////////////////////////////
// remove the filter, and just pivot on homeStateId - should aggregate differently //
/////////////////////////////////////////////////////////////////////////////////////
report.getDataSources().get(0).getQueryFilter().setCriteria(null);
report.getViews().get(0).setPivotFields(List.of("homeStateId"));
report.getViews().get(0).setSummaryFields(List.of("homeStateId"));
qInstance.addReport(report);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.now(), "endDate", LocalDate.now()));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("pivot");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("summary");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(2, list.size());
@ -315,23 +339,18 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void runToCsv() throws Exception
private String runToString(ReportFormat reportFormat, String reportName) throws Exception
{
String name = "/tmp/report.csv";
String name = "/tmp/report." + reportFormat.getExtension();
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePersonShoesPivotReport(true));
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportFormat(ReportFormat.CSV);
reportInput.setReportOutputStream(fileOutputStream);
reportInput.setReportName(reportName);
reportInput.setReportDestination(new ReportDestination().withReportFormat(reportFormat).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
return (FileUtils.readFileToString(new File(name), StandardCharsets.UTF_8));
}
}
@ -341,27 +360,189 @@ public class GenerateReportActionTest extends BaseTest
**
*******************************************************************************/
@Test
void runToXlsx() throws Exception
void runSummaryToXlsx() throws Exception
{
String name = "/tmp/report.xlsx";
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-" + format + ".xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePersonShoesPivotReport(true));
qInstance.addReport(definePersonShoesSummaryReport(true));
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportFormat(ReportFormat.XLSX);
reportInput.setReportOutputStream(fileOutputStream);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
** Keep some test coverage on the fastexcel library (as long as we keep it around)
*******************************************************************************/
@Test
void runTableToXlsxFastexcel() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-fastexcel.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(defineTableOnlyReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
reportInput.setOverrideExportStreamerSupplier(ExcelFastexcelExportStreamer::new);
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
** Imagine if we used the boldHeaderAndFooter styler
*******************************************************************************/
@Test
void runTableToXlsxWithOverrideStyles() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-fastexcel.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData reportMetaData = defineTableOnlyReport();
reportMetaData.getViews().get(0).withTitleFormat("My Title");
qInstance.addReport(reportMetaData);
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
reportInput.setOverrideExportStreamerSupplier(() -> new ExcelPoiBasedStreamingExportStreamer()
{
@Override
protected PoiExcelStylerInterface getStylerInterface()
{
return new BoldHeaderAndFooterPoiExcelStyler();
}
});
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void runTableToXlsx() throws Exception
{
ReportFormat format = ReportFormat.XLSX;
String name = "/tmp/report-" + format + ".xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(defineTableOnlyReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(format).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void runPivotToXlsx() throws Exception
{
String name = "/tmp/pivot-test.xlsx";
try(FileOutputStream fileOutputStream = new FileOutputStream(name))
{
QInstance qInstance = QContext.getQInstance();
qInstance.addReport(definePivotReport());
insertPersonRecords(qInstance);
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.XLSX).withReportOutputStream(fileOutputStream));
reportInput.setInputValues(Map.of("startDate", LocalDate.of(1970, Month.MAY, 15), "endDate", LocalDate.now()));
new GenerateReportAction().execute(reportInput);
System.out.println("Wrote File: " + name);
LocalMacDevUtils.openFile(name, "/Applications/Numbers.app");
}
///////////////////////////////////////////////////////////
// read the file we wrote, and assert about its contents //
///////////////////////////////////////////////////////////
FileInputStream file = new FileInputStream(name);
XSSFWorkbook workbook = new XSSFWorkbook(file);
XSSFSheet sheet = workbook.getSheetAt(1);
List<XSSFPivotTable> pivotTables = sheet.getPivotTables();
XSSFPivotTable xssfPivotTable = pivotTables.get(0);
List<Integer> rowLabelColumns = xssfPivotTable.getRowLabelColumns();
List<Integer> colLabelColumns = xssfPivotTable.getColLabelColumns();
Sheet dataSheet = xssfPivotTable.getDataSheet();
Sheet parentSheet = xssfPivotTable.getParentSheet();
System.out.println();
Map<Integer, List<Object>> data = new HashMap<>();
int i = 0;
for(Row row : sheet)
{
data.put(i, new ArrayList<>());
for(Cell cell : row)
{
data.get(i).add(switch(cell.getCellType())
{
case _NONE -> "<_NONE>";
case NUMERIC -> cell.getNumericCellValue();
case STRING -> cell.getStringCellValue();
case FORMULA -> cell.getCellFormula();
case BLANK -> "<BLANK>";
case BOOLEAN -> cell.getBooleanCellValue();
case ERROR -> cell.getErrorCellValue();
});
}
i++;
}
System.out.println(data);
}
/*******************************************************************************
**
*******************************************************************************/
@ -369,8 +550,7 @@ public class GenerateReportActionTest extends BaseTest
{
ReportInput reportInput = new ReportInput();
reportInput.setReportName(REPORT_NAME);
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
reportInput.setReportOutputStream(new ByteArrayOutputStream());
reportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.LIST_OF_MAPS).withReportOutputStream(new ByteArrayOutputStream()));
reportInput.setInputValues(inputValues);
new GenerateReportAction().execute(reportInput);
}
@ -380,15 +560,15 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private void insertPersonRecords(QInstance qInstance) throws QException
public static void insertPersonRecords(QInstance qInstance) throws QException
{
TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY), List.of(
new PersonQRecord().withLastName("Jonson").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(null).withHomeStateId(1).withPrice(null).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withLastName("Jones").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(3).withHomeStateId(1).withPrice(new BigDecimal("1.00")).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withLastName("Kelly").withBirthDate(LocalDate.of(1979, Month.DECEMBER, 30)).withNoOfShoes(4).withHomeStateId(1).withPrice(new BigDecimal("1.20")).withCost(new BigDecimal("0.50")), // bad birthdate
new PersonQRecord().withLastName("Keller").withBirthDate(LocalDate.of(1980, Month.JANUARY, 7)).withNoOfShoes(5).withHomeStateId(1).withPrice(new BigDecimal("2.40")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.FEBRUARY, 15)).withNoOfShoes(6).withHomeStateId(1).withPrice(new BigDecimal("3.60")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.MARCH, 20)).withNoOfShoes(7).withHomeStateId(2).withPrice(new BigDecimal("4.80")).withCost(new BigDecimal("3.50"))
new PersonQRecord().withFirstName("Darin").withLastName("Jonson").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(null).withHomeStateId(1).withPrice(null).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withFirstName("Darin").withLastName("Jones").withBirthDate(LocalDate.of(1980, Month.JANUARY, 31)).withNoOfShoes(3).withHomeStateId(1).withPrice(new BigDecimal("1.00")).withCost(new BigDecimal("0.50")), // wrong last initial
new PersonQRecord().withFirstName("Darin").withLastName("Kelly").withBirthDate(LocalDate.of(1979, Month.DECEMBER, 30)).withNoOfShoes(4).withHomeStateId(1).withPrice(new BigDecimal("1.20")).withCost(new BigDecimal("0.50")), // bad birthdate
new PersonQRecord().withFirstName("Trevor").withLastName("Keller").withBirthDate(LocalDate.of(1980, Month.JANUARY, 7)).withNoOfShoes(5).withHomeStateId(1).withPrice(new BigDecimal("2.40")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withFirstName("Trevor").withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.FEBRUARY, 15)).withNoOfShoes(6).withHomeStateId(1).withPrice(new BigDecimal("3.60")).withCost(new BigDecimal("3.50")),
new PersonQRecord().withFirstName("Kelly").withLastName("Kelkhoff").withBirthDate(LocalDate.of(1980, Month.MARCH, 20)).withNoOfShoes(7).withHomeStateId(2).withPrice(new BigDecimal("4.80")).withCost(new BigDecimal("3.50"))
));
}
@ -397,7 +577,7 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
public static QReportMetaData definePersonShoesPivotReport(boolean includeTotalRow)
public static QReportMetaData definePersonShoesSummaryReport(boolean includeTotalRow)
{
return new QReportMetaData()
.withName(REPORT_NAME)
@ -416,11 +596,11 @@ public class GenerateReportActionTest extends BaseTest
))
.withViews(List.of(
new QReportView()
.withName("pivot")
.withLabel("pivot")
.withName("summary")
.withLabel("summary")
.withDataSourceName("persons")
.withType(ReportType.SUMMARY)
.withPivotFields(List.of("lastName"))
.withSummaryFields(List.of("lastName"))
.withIncludeTotalRow(includeTotalRow)
.withTitleFormat("Number of shoes - people born between %s and %s - pivot on LastName, sort by Quantity, Revenue DESC")
.withTitleFields(List.of("${input.startDate}", "${input.endDate}"))
@ -452,39 +632,14 @@ public class GenerateReportActionTest extends BaseTest
@Test
void testTableOnlyReport() throws QException
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}")))
)
))
.withInputFields(List.of(
new QFieldMetaData("startDate", QFieldType.DATE_TIME)
))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("table1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName")
))
));
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineTableOnlyReport();
qInstance.addReport(report);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("table1");
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Table 1");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(5, list.size());
@ -493,6 +648,88 @@ public class GenerateReportActionTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private static QReportMetaData defineTableOnlyReport()
{
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}"))))))
.withInputFields(List.of(
new QFieldMetaData("startDate", QFieldType.DATE_TIME)))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName")))));
return report;
}
/*******************************************************************************
**
*******************************************************************************/
private static QReportMetaData definePivotReport()
{
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("birthDate", QCriteriaOperator.GREATER_THAN, List.of("${input.startDate}"))))))
.withInputFields(List.of(
new QFieldMetaData("startDate", QFieldType.DATE_TIME)))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName"),
new QReportField().withName("homeStateId")
)),
new QReportView()
.withName("pivotTable1")
.withLabel("My Pivot Table")
.withType(ReportType.PIVOT)
.withPivotTableSourceViewName("table1")
.withPivotTableDefinition(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("homeStateId"))
// .withRow(new PivotTableGroupBy().withFieldName("lastName"))
// .withColumn(new PivotTableGroupBy().withFieldName("firstName"))
.withValue(new PivotTableValue().withFunction(PivotTableFunction.COUNT).withFieldName("id")))
));
return report;
}
/*******************************************************************************
**
*******************************************************************************/
@ -500,6 +737,180 @@ public class GenerateReportActionTest extends BaseTest
void testTwoTableViewsOneDataSourceReport() throws QException
{
QInstance qInstance = QContext.getQInstance();
defineTwoViewsOneDataSourceReport(qInstance);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Table 1");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Id", "First Name", "Last Name");
list = ListOfMapsExportStreamer.getList("Table 2");
iterator = list.iterator();
row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Birth Date");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOneTableViewsOneDataSourceJsonReport() throws Exception
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineTableOnlyReport();
qInstance.addReport(report);
insertPersonRecords(qInstance);
String json = runToString(ReportFormat.JSON, report.getName());
// System.out.println(json);
/////////////////////////////////////////////////////////////////////////////////
// for a one-view report, we should just have an array of the report's records //
/////////////////////////////////////////////////////////////////////////////////
JSONArray jsonArray = new JSONArray(json);
assertEquals(6, jsonArray.length());
assertThat(jsonArray.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("lastName", "Jonson");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTwoTableViewsOneDataSourceJsonReport() throws Exception
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineTwoViewsOneDataSourceReport(qInstance);
insertPersonRecords(qInstance);
String json = runToString(ReportFormat.JSON, report.getName());
// System.out.println(json);
/////////////////////////////////////////////////////////////////////////////////
// for a multi-view report, we should have an array with the views as elements //
/////////////////////////////////////////////////////////////////////////////////
JSONArray jsonArray = new JSONArray(json);
assertEquals(2, jsonArray.length());
JSONObject firstView = jsonArray.getJSONObject(0);
assertEquals("Table 1", firstView.getString("name"));
JSONArray firstViewData = firstView.getJSONArray("data");
assertEquals(6, firstViewData.length());
assertThat(firstViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("lastName", "Jonson");
JSONObject secondView = jsonArray.getJSONObject(1);
assertEquals("Table 2", secondView.getString("name"));
JSONArray secondViewData = secondView.getJSONArray("data");
assertEquals(6, secondViewData.length());
assertThat(secondViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("birthDate", "1980-01-31");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableViewsAndSummaryViewJsonReport() throws Exception
{
QInstance qInstance = QContext.getQInstance();
QReportMetaData report = defineSimplePersonTableAndSummaryByFirstNameReport();
qInstance.addReport(report);
insertPersonRecords(qInstance);
String json = runToString(ReportFormat.JSON, report.getName());
System.out.println(json);
/////////////////////////////////////////////////////////////////////////////////
// for a multi-view report, we should have an array with the views as elements //
/////////////////////////////////////////////////////////////////////////////////
JSONArray jsonArray = new JSONArray(json);
assertEquals(2, jsonArray.length());
JSONObject firstView = jsonArray.getJSONObject(0);
assertEquals("Table 1", firstView.getString("name"));
JSONArray firstViewData = firstView.getJSONArray("data");
assertEquals(6, firstViewData.length());
assertThat(firstViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("lastName", "Jonson");
JSONObject secondView = jsonArray.getJSONObject(1);
assertEquals("Summary", secondView.getString("name"));
JSONArray secondViewData = secondView.getJSONArray("data");
assertEquals(4, secondViewData.length());
assertThat(secondViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("personCount", 3);
assertThat(secondViewData.getJSONObject(3).toMap())
.hasFieldOrPropertyWithValue("firstName", "Totals")
.hasFieldOrPropertyWithValue("personCount", 6);
}
/*******************************************************************************
**
*******************************************************************************/
public static QReportMetaData defineSimplePersonTableAndSummaryByFirstNameReport()
{
return new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter())
))
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName"),
new QReportField().withName("homeStateId")
)),
new QReportView()
.withName("summary")
.withLabel("Summary")
.withDataSourceName("persons")
.withType(ReportType.SUMMARY)
.withSummaryFields(List.of("firstName"))
.withIncludeTotalRow(true)
.withOrderByFields(List.of(new QFilterOrderBy("personCount", false)))
.withColumns(List.of(
new QReportField().withName("personCount").withLabel("Person Count").withFormula("${pivot.count.id}").withDisplayFormat(DisplayFormat.COMMAS)
))
));
}
/*******************************************************************************
**
*******************************************************************************/
private static QReportMetaData defineTwoViewsOneDataSourceReport(QInstance qInstance)
{
QReportMetaData report = new QReportMetaData()
.withName(REPORT_NAME)
.withDataSources(List.of(
@ -516,7 +927,7 @@ public class GenerateReportActionTest extends BaseTest
.withViews(List.of(
new QReportView()
.withName("table1")
.withLabel("table1")
.withLabel("Table 1")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
@ -526,7 +937,7 @@ public class GenerateReportActionTest extends BaseTest
)),
new QReportView()
.withName("table2")
.withLabel("table2")
.withLabel("Table 2")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
@ -535,21 +946,7 @@ public class GenerateReportActionTest extends BaseTest
));
qInstance.addReport(report);
insertPersonRecords(qInstance);
runReport(qInstance, Map.of("startDate", LocalDate.of(1980, Month.JANUARY, 1)));
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("table1");
Iterator<Map<String, String>> iterator = list.iterator();
Map<String, String> row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Id", "First Name", "Last Name");
list = ListOfMapsExportStreamer.getList("table2");
iterator = list.iterator();
row = iterator.next();
assertEquals(5, list.size());
assertThat(row).containsOnlyKeys("Birth Date");
return (report);
}
@ -566,8 +963,7 @@ public class GenerateReportActionTest extends BaseTest
ReportInput reportInput = new ReportInput();
reportInput.setReportName(TestUtils.REPORT_NAME_PERSON_SIMPLE);
reportInput.setReportFormat(ReportFormat.LIST_OF_MAPS);
reportInput.setReportOutputStream(new ByteArrayOutputStream());
reportInput.setReportDestination(new ReportDestination().withReportFormat(ReportFormat.LIST_OF_MAPS).withReportOutputStream(new ByteArrayOutputStream()));
new GenerateReportAction().execute(reportInput);
List<Map<String, String>> list = ListOfMapsExportStreamer.getList("Simple Report");

View File

@ -0,0 +1,54 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.reporting;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for JsonExportStreamer
*******************************************************************************/
class JsonExportStreamerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
Function<String, String> runOne = label -> new JsonExportStreamer().getLabelForJson(new QFieldMetaData("test", QFieldType.STRING).withLabel(label));
assertEquals("sku", runOne.apply("SKU"));
assertEquals("clientName", runOne.apply("Client Name"));
assertEquals("slaStatus", runOne.apply("SLA Status"));
assertEquals("lineItem:sku", runOne.apply("Line Item: SKU"));
assertEquals("parcel:slaStatus", runOne.apply("Parcel: SLA Status"));
assertEquals("order:client", runOne.apply("Order: Client"));
}
}

View File

@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateInpu
import com.kingsrook.qqq.backend.core.model.actions.templates.RenderTemplateOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.templates.TemplateType;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import org.junit.jupiter.api.Test;
@ -107,8 +108,8 @@ class ConvertHtmlToPdfActionTest extends BaseTest
/////////////////////////////////////////////////////////////////////////
// for local dev on a mac, turn this on to auto-open the generated PDF //
/////////////////////////////////////////////////////////////////////////
// todo not commit
// Runtime.getRuntime().exec(new String[] { "/usr/bin/open", "/tmp/file.pdf" });
// LocalMacDevUtils.mayOpenFiles = true;
LocalMacDevUtils.openFile("/tmp/file.pdf");
}
}

View File

@ -216,7 +216,7 @@ class QInstanceHelpContentManagerTest extends BaseTest
// now - post-insert customizer should have automatically added help content to the instance //
///////////////////////////////////////////////////////////////////////////////////////////////
assertTrue(widget.getHelpContent().containsKey("label"));
assertEquals("i need somebody", widget.getHelpContent().get("label").getContent());
assertEquals("i need somebody", widget.getHelpContent().get("label").get(0).getContent());
}

View File

@ -272,6 +272,25 @@ public class QInstanceValidatorTest extends BaseTest
/*******************************************************************************
** Test rules for process step names (must be set; must not be duplicated)
**
*******************************************************************************/
@Test
public void test_validateProcessStepNames()
{
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE).getStepList().get(0).setName(null),
"Missing name for a step at index");
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE).getStepList().get(0).setName(""),
"Missing name for a step at index");
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE).getStepList().forEach(s -> s.setName("myStep")),
"Duplicate step name [myStep]", "Duplicate step name [myStep]");
}
/*******************************************************************************
** Test that a process with a step that is a private class fails
**

View File

@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@ -156,4 +157,47 @@ class DynamicDefaultValueBehaviorTest extends BaseTest
assertNull(record.getValue("firstName"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testUserId()
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
table.getField("firstName").withBehavior(DynamicDefaultValueBehavior.USER_ID);
{
////////////////////////////////
// set it (if null) on insert //
////////////////////////////////
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record), null);
assertEquals(QContext.getQSession().getUser().getIdReference(), record.getValue("firstName"));
}
{
////////////////////////////////
// set it (if null) on update //
////////////////////////////////
QRecord record = new QRecord().withValue("id", 1);
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, qInstance, table, List.of(record), null);
assertEquals(QContext.getQSession().getUser().getIdReference(), record.getValue("firstName"));
}
{
////////////////////////////////////////////////////////////////////
// only set it if it wasn't previously set (both insert & update) //
////////////////////////////////////////////////////////////////////
QRecord record = new QRecord().withValue("id", 1).withValue("firstName", "Bob");
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, qInstance, table, List.of(record), null);
assertEquals("Bob", record.getValue("firstName"));
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, qInstance, table, List.of(record), null);
assertEquals("Bob", record.getValue("firstName"));
}
}
}

View File

@ -0,0 +1,166 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.savedreports;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for SavedReportJsonFieldDisplayValueFormatter
*******************************************************************************/
class SavedReportJsonFieldDisplayValueFormatterTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws QException
{
QContext.getQInstance().add(new SavedReportsMetaDataProvider().defineSavedReportTable(TestUtils.MEMORY_BACKEND_NAME, null));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPostQuery() throws QException
{
UnsafeFunction<SavedReport, QRecord, QException> customize = savedReport ->
{
QInstance qInstance = QContext.getQInstance();
QTableMetaData table = qInstance.getTable(SavedReport.TABLE_NAME);
QRecord record = savedReport.toQRecord();
for(String fieldName : List.of("queryFilterJson", "columnsJson", "pivotTableJson"))
{
SavedReportJsonFieldDisplayValueFormatter.getInstance().apply(ValueBehaviorApplier.Action.FORMATTING, List.of(record), qInstance, table, table.getField(fieldName));
}
return (record);
};
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition())));
assertEquals("0 Filters", record.getDisplayValue("queryFilterJson"));
assertEquals("0 Columns", record.getDisplayValue("columnsJson"));
assertEquals("0 Rows, 0 Columns, and 0 Values", record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(new ReportColumns())));
assertEquals("0 Filters", record.getDisplayValue("queryFilterJson"));
assertEquals("0 Columns", record.getDisplayValue("columnsJson"));
assertNull(record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IS_NOT_BLANK))))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn(new ReportColumn().withName("birthDate"))))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy())
.withValue(new PivotTableValue())
)));
assertEquals("1 Filter", record.getDisplayValue("queryFilterJson"));
assertEquals("1 Column", record.getDisplayValue("columnsJson"));
assertEquals("1 Row, 0 Columns, and 1 Value", record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()
.withCriteria(new QFilterCriteria("id", QCriteriaOperator.GREATER_THAN, 1))
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IS_NOT_BLANK))))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn(new ReportColumn().withName("__check__").withIsVisible(true))
.withColumn(new ReportColumn().withName("id"))
.withColumn(new ReportColumn().withName("firstName").withIsVisible(true))
.withColumn(new ReportColumn().withName("lastName").withIsVisible(false))
.withColumn(new ReportColumn().withName("birthDate"))))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy())
.withRow(new PivotTableGroupBy())
.withColumn(new PivotTableGroupBy())
.withValue(new PivotTableValue())
.withValue(new PivotTableValue())
.withValue(new PivotTableValue())
)));
assertEquals("2 Filters", record.getDisplayValue("queryFilterJson"));
assertEquals("3 Columns", record.getDisplayValue("columnsJson"));
assertEquals("2 Rows, 1 Column, and 3 Values", record.getDisplayValue("pivotTableJson"));
}
{
QRecord record = customize.apply(new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson("blah")
.withColumnsJson("<xml?>")
.withPivotTableJson("{]"));
assertEquals("Invalid Filter...", record.getDisplayValue("queryFilterJson"));
assertEquals("Invalid Columns...", record.getDisplayValue("columnsJson"));
assertEquals("Invalid Pivot Table...", record.getDisplayValue("pivotTableJson"));
}
}
}

View File

@ -0,0 +1,228 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.savedreports;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Unit test for SavedReportTableCustomizer
*******************************************************************************/
class SavedReportTableCustomizerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws QException
{
QContext.getQInstance().add(new SavedReportsMetaDataProvider().defineSavedReportTable(TestUtils.MEMORY_BACKEND_NAME, null));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPreInsertAndPreUpdateAreWired() throws QException
{
SavedReport badRecord = new SavedReport()
.withLabel("My Report")
.withTableName("notATable");
/////////////////////////////////////////////////////////////////////
// assertions to apply both to a failed insert and a failed update //
/////////////////////////////////////////////////////////////////////
Consumer<QRecord> asserter = record -> assertThat(record.getErrors())
.hasSizeGreaterThanOrEqualTo(2)
.anyMatch(e -> e.getMessage().contains("Unrecognized table name"))
.anyMatch(e -> e.getMessage().contains("must contain at least 1 column"));
////////////////////////////////////////////////////////////
// go through insert action, to ensure wired-up correctly //
////////////////////////////////////////////////////////////
InsertOutput insertOutput = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(badRecord));
asserter.accept(insertOutput.getRecords().get(0));
////////////////////////////////
// likewise for update action //
////////////////////////////////
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(SavedReport.TABLE_NAME).withRecordEntity(badRecord));
asserter.accept(updateOutput.getRecords().get(0));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testParseFails()
{
QRecord record = new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson("...")
.withColumnsJson("x")
.withPivotTableJson("[")
.toQRecord();
new SavedReportTableCustomizer().preValidateRecord(record);
assertThat(record.getErrors())
.hasSize(3)
.anyMatch(e -> e.getMessage().contains("Unable to parse queryFilterJson"))
.anyMatch(e -> e.getMessage().contains("Unable to parse columnsJson"))
.anyMatch(e -> e.getMessage().contains("Unable to parse pivotTableJson"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testNoColumns()
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// given a reportColumns object, serialize it to json, put it in a saved report record, and run the pre-validator //
// then assert we got error saying there were no columns. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Consumer<ReportColumns> asserter = reportColumns ->
{
SavedReport savedReport = new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(reportColumns));
QRecord record = savedReport.toQRecord();
new SavedReportTableCustomizer().preValidateRecord(record);
assertThat(record.getErrors())
.hasSize(1)
.anyMatch(e -> e.getMessage().contains("must contain at least 1 column"));
};
asserter.accept(new ReportColumns());
asserter.accept(new ReportColumns().withColumns(null));
asserter.accept(new ReportColumns().withColumns(new ArrayList<>()));
asserter.accept(new ReportColumns().withColumn(new ReportColumn()
.withName("id").withIsVisible(false)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotTables()
{
BiConsumer<PivotTableDefinition, List<String>> asserter = (PivotTableDefinition ptd, List<String> expectedAnyMessageToContain) ->
{
SavedReport savedReport = new SavedReport()
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn("id")
.withColumn("firstName")
.withColumn("lastName")
.withColumn("birthDate")))
.withPivotTableJson(JsonUtils.toJson(ptd));
QRecord record = savedReport.toQRecord();
new SavedReportTableCustomizer().preValidateRecord(record);
assertThat(record.getErrors()).hasSize(expectedAnyMessageToContain.size());
for(String expected : expectedAnyMessageToContain)
{
assertThat(record.getErrors())
.anyMatch(e -> e.getMessage().contains(expected));
}
};
asserter.accept(new PivotTableDefinition(), List.of("must contain at least 1 row"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withRow(new PivotTableGroupBy()),
List.of("Missing field name for at least one pivot table row"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withRow(new PivotTableGroupBy().withFieldName("createDate")),
List.of("row is using field (Create Date) which is not an active column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withColumn(new PivotTableGroupBy()),
List.of("Missing field name for at least one pivot table column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withColumn(new PivotTableGroupBy().withFieldName("createDate")),
List.of("column is using field (Create Date) which is not an active column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withValue(new PivotTableValue().withFunction(PivotTableFunction.SUM)),
List.of("Missing field name for at least one pivot table value"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withValue(new PivotTableValue().withFieldName("createDate").withFunction(PivotTableFunction.SUM)),
List.of("value is using field (Create Date) which is not an active column"));
asserter.accept(new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("id"))
.withValue(new PivotTableValue().withFieldName("firstName")),
List.of("Missing function for at least one pivot table value"));
}
}

View File

@ -50,7 +50,7 @@ class BasicRunReportProcessTest extends BaseTest
void testRunReport() throws QException
{
QInstance instance = TestUtils.defineInstance();
QReportMetaData report = GenerateReportActionTest.definePersonShoesPivotReport(true);
QReportMetaData report = GenerateReportActionTest.definePersonShoesSummaryReport(true);
QProcessMetaData runReportProcess = BasicRunReportProcess.defineProcessMetaData();
instance.addReport(report);

View File

@ -0,0 +1,406 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.List;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportActionTest;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableDefinition;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableFunction;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableGroupBy;
import com.kingsrook.qqq.backend.core.model.actions.reporting.pivottable.PivotTableValue;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.savedreports.ReportColumns;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryStorageAction;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for RenderSavedReportExecuteStep
*******************************************************************************/
class RenderSavedReportProcessTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws Exception
{
new SavedReportsMetaDataProvider().defineAll(QContext.getQInstance(), TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null);
GenerateReportActionTest.insertPersonRecords(QContext.getQInstance());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
@Disabled
void testForDevPrintAPivotDefinitionAsJson()
{
System.out.println(JsonUtils.toPrettyJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy()
.withFieldName("homeStateId"))
.withRow(new PivotTableGroupBy()
.withFieldName("firstName"))
.withValue(new PivotTableValue()
.withFieldName("id")
.withFunction(PivotTableFunction.COUNT))
.withValue(new PivotTableValue()
.withFieldName("cost")
.withFunction(PivotTableFunction.SUM))
));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTableOnlyReport() throws Exception
{
String label = "Test Report";
//////////////////////////////////////////////////////////////////////////////////////////
// do columns json as a string, rather than a toJson'ed ReportColumns object, //
// to help verify that we don't choke on un-recognized properties (e.g., as QFMD sends) //
//////////////////////////////////////////////////////////////////////////////////////////
String columnsJson = """
{"columns":[
{"name": "k"},
{"name": "id"},
{"name": "firstName", "isVisible": true},
{"name": "lastName", "pinned": "left"},
{"name": "createDate", "isVisible": false}
]}""";
QRecord savedReport = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport()
.withLabel(label)
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withColumnsJson(columnsJson)
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
)).getRecords().get(0);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.CSV);
String downloadFileName = runProcessOutput.getValueString("downloadFileName");
assertThat(downloadFileName)
.startsWith(label + " - ")
.matches(".*\\d\\d\\d\\d-\\d\\d-\\d\\d-\\d\\d\\d\\d.*")
.endsWith(".csv");
InputStream inputStream = getInputStream(runProcessOutput);
List<String> lines = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
assertEquals("""
"Id","First Name","Last Name"
""".trim(), lines.get(0));
assertEquals("""
"1","Darin","Jonson"
""".trim(), lines.get(1));
writeTmpFileAndOpen(inputStream, ".csv");
}
/*******************************************************************************
**
*******************************************************************************/
private static InputStream getInputStream(RunProcessOutput runProcessOutput) throws QException
{
String storageTableName = runProcessOutput.getValueString("storageTableName");
String storageReference = runProcessOutput.getValueString("storageReference");
InputStream inputStream = new MemoryStorageAction().getInputStream(new StorageInput(storageTableName).withReference(storageReference));
return inputStream;
}
/*******************************************************************************
**
*******************************************************************************/
private void writeTmpFileAndOpen(InputStream inputStream, String suffix) throws IOException
{
// LocalMacDevUtils.mayOpenFiles = true;
if(LocalMacDevUtils.mayOpenFiles)
{
inputStream.reset();
File tmpFile = File.createTempFile(getClass().getName(), suffix, new File("/tmp/"));
FileOutputStream fileOutputStream = new FileOutputStream(tmpFile);
inputStream.transferTo(fileOutputStream);
fileOutputStream.close();
LocalMacDevUtils.openFile(tmpFile.getAbsolutePath());
}
}
/*******************************************************************************
**
*******************************************************************************/
private QRecord insertBasicSavedPivotReport(String label) throws QException
{
return new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport()
.withLabel(label)
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn("id")
.withColumn("firstName")
.withColumn("lastName")
.withColumn("cost")
.withColumn("birthDate")
.withColumn("homeStateId")))
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withPivotTableJson(JsonUtils.toJson(new PivotTableDefinition()
.withRow(new PivotTableGroupBy()
.withFieldName("homeStateId"))
.withRow(new PivotTableGroupBy()
.withFieldName("firstName"))
.withValue(new PivotTableValue()
.withFieldName("id")
.withFunction(PivotTableFunction.COUNT))
.withValue(new PivotTableValue()
.withFieldName("cost")
.withFunction(PivotTableFunction.SUM))
.withValue(new PivotTableValue()
.withFieldName("birthDate")
.withFunction(PivotTableFunction.MIN))
.withValue(new PivotTableValue()
.withFieldName("birthDate")
.withFunction(PivotTableFunction.MAX))
))
)).getRecords().get(0);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotXlsx() throws Exception
{
String label = "Test Pivot Report";
QRecord savedReport = insertBasicSavedPivotReport(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.XLSX);
InputStream inputStream = getInputStream(runProcessOutput);
writeTmpFileAndOpen(inputStream, ".xlsx");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotJson() throws Exception
{
String label = "Test Pivot Report JSON";
QRecord savedReport = insertBasicSavedPivotReport(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.JSON);
InputStream inputStream = getInputStream(runProcessOutput);
String json = StringUtils.join("\n", IOUtils.readLines(inputStream, StandardCharsets.UTF_8));
printReport(json);
JSONArray jsonArray = new JSONArray(json);
assertEquals(2, jsonArray.length());
JSONObject firstView = jsonArray.getJSONObject(0);
assertEquals(label, firstView.getString("name"));
JSONArray firstViewData = firstView.getJSONArray("data");
assertEquals(6, firstViewData.length());
assertThat(firstViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("id", 1)
.hasFieldOrPropertyWithValue("firstName", "Darin");
JSONObject pivotView = jsonArray.getJSONObject(1);
assertEquals("Pivot Table", pivotView.getString("name"));
JSONArray pivotViewData = pivotView.getJSONArray("data");
assertEquals(4, pivotViewData.length());
assertThat(pivotViewData.getJSONObject(0).toMap())
.hasFieldOrPropertyWithValue("homeState", "IL")
.hasFieldOrPropertyWithValue("firstName", "Darin")
.hasFieldOrPropertyWithValue("countOfId", 3)
.hasFieldOrPropertyWithValue("sumOfCost", new BigDecimal("1.50"));
assertThat(pivotViewData.getJSONObject(3).toMap())
.hasFieldOrPropertyWithValue("homeState", "Totals")
.hasFieldOrPropertyWithValue("countOfId", 6)
.hasFieldOrPropertyWithValue("sumOfCost", new BigDecimal("12.00"));
}
/*******************************************************************************
**
*******************************************************************************/
private void printReport(String report)
{
// System.out.println(report);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotCSV() throws Exception
{
String label = "Test Pivot Report CSV";
QRecord savedReport = insertBasicSavedPivotReport(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.CSV);
InputStream inputStream = getInputStream(runProcessOutput);
List<String> csv = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
System.out.println(csv);
assertEquals("""
"Home State","First Name","Count Of Id","Sum Of Cost","Min Of Birth Date","Max Of Birth Date\"""", csv.get(0));
assertEquals("""
"Totals","","6","12.00","1979-12-30","1980-03-20\"""", csv.get(4));
}
/*******************************************************************************
**
*******************************************************************************/
private QRecord insertSavedPivotReportWithAllFunctions(String label) throws QException
{
PivotTableDefinition pivotTableDefinition = new PivotTableDefinition()
.withRow(new PivotTableGroupBy().withFieldName("firstName"));
for(PivotTableFunction function : PivotTableFunction.values())
{
pivotTableDefinition.withValue(new PivotTableValue().withFieldName("cost").withFunction(function));
}
return new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport()
.withLabel(label)
.withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withColumnsJson(JsonUtils.toJson(new ReportColumns()
.withColumn("id")
.withColumn("firstName")
.withColumn("cost")))
.withQueryFilterJson(JsonUtils.toJson(new QQueryFilter()))
.withPivotTableJson(JsonUtils.toJson(pivotTableDefinition))
)).getRecords().get(0);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotXlsxAllFunctions() throws Exception
{
String label = "Test Pivot Report";
QRecord savedReport = insertSavedPivotReportWithAllFunctions(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.XLSX);
String serverFilePath = runProcessOutput.getValueString("serverFilePath");
System.out.println(serverFilePath);
InputStream inputStream = getInputStream(runProcessOutput);
writeTmpFileAndOpen(inputStream, ".xlsx");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testPivotCSVAllFunctions() throws Exception
{
String label = "Test Pivot Report CSV";
QRecord savedReport = insertSavedPivotReportWithAllFunctions(label);
RunProcessOutput runProcessOutput = runRenderReportProcess(savedReport, ReportFormatPossibleValueEnum.CSV);
InputStream inputStream = getInputStream(runProcessOutput);
List<String> csv = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
System.out.println(csv);
assertEquals("""
"First Name","Average Of Cost","Count Of Cost","Count_nums Of Cost","Max Of Cost","Min Of Cost","Product Of Cost","Std_dev Of Cost","Std_devp Of Cost","Sum Of Cost","Var Of Cost","Varp Of Cost\"""", csv.get(0));
assertEquals("""
"Totals","2.0","6","6","3.50","0.50","5.359375000000","1.6432","1.5000","12.00","2.7000","2.2500\"""", csv.get(4));
}
/*******************************************************************************
**
*******************************************************************************/
private static RunProcessOutput runRenderReportProcess(QRecord savedReport, ReportFormatPossibleValueEnum reportFormat) throws QException
{
RunProcessInput input = new RunProcessInput();
input.setProcessName(RenderSavedReportMetaDataProducer.NAME);
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
input.setCallback(QProcessCallbackFactory.forRecord(savedReport));
input.addValue("reportFormat", reportFormat.getPossibleValueId());
RunProcessOutput runProcessOutput = new RunProcessAction().execute(input);
return runProcessOutput;
}
}

View File

@ -22,7 +22,6 @@
package com.kingsrook.qqq.backend.core.scheduler;
import java.util.ArrayList;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
@ -32,8 +31,6 @@ import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobParameter;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
@ -83,29 +80,6 @@ class QScheduleManagerTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
private ScheduledJob newScheduledJob(ScheduledJobType type, Map<String, String> params)
{
ScheduledJob scheduledJob = new ScheduledJob()
.withId(1)
.withIsActive(true)
.withSchedulerName(TestUtils.SIMPLE_SCHEDULER_NAME)
.withType(type.name())
.withRepeatSeconds(1)
.withJobParameters(new ArrayList<>());
for(Map.Entry<String, String> entry : params.entrySet())
{
scheduledJob.getJobParameters().add(new ScheduledJobParameter().withKey(entry.getKey()).withValue(entry.getValue()));
}
return (scheduledJob);
}
/*******************************************************************************
**
*******************************************************************************/
@ -114,54 +88,54 @@ class QScheduleManagerTest extends BaseTest
{
QScheduleManager qScheduleManager = QScheduleManager.initInstance(QContext.getQInstance(), () -> QContext.getQSession());
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS, Map.of()).withRepeatSeconds(null)))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS, Map.of()).withRepeatSeconds(null)))
.hasMessageContaining("Missing a schedule");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS, Map.of()).withType(null)))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS, Map.of()).withType(null)))
.hasMessageContaining("Missing a type");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS, Map.of()).withType("notAType")))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS, Map.of()).withType("notAType")))
.hasMessageContaining("Unrecognized type");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS, Map.of())))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS, Map.of())))
.hasMessageContaining("Missing scheduledJobParameter with key [processName]");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS, Map.of("processName", "notAProcess"))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS, Map.of("processName", "notAProcess"))))
.hasMessageContaining("Unrecognized processName");
QContext.getQInstance().getProcess(TestUtils.PROCESS_NAME_BASEPULL).withSchedule(new QScheduleMetaData());
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS, Map.of("processName", TestUtils.PROCESS_NAME_BASEPULL))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS, Map.of("processName", TestUtils.PROCESS_NAME_BASEPULL))))
.hasMessageContaining("has a schedule in its metaData - so it should not be dynamically scheduled");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR, Map.of())))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR, Map.of())))
.hasMessageContaining("Missing scheduledJobParameter with key [queueName]");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR, Map.of("queueName", "notAQueue"))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR, Map.of("queueName", "notAQueue"))))
.hasMessageContaining("Unrecognized queueName");
QContext.getQInstance().getQueue(TestUtils.TEST_SQS_QUEUE).withSchedule(new QScheduleMetaData());
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR, Map.of("queueName", TestUtils.TEST_SQS_QUEUE))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR, Map.of("queueName", TestUtils.TEST_SQS_QUEUE))))
.hasMessageContaining("has a schedule in its metaData - so it should not be dynamically scheduled");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of())))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of())))
.hasMessageContaining("Missing scheduledJobParameter with key [tableName]");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", "notATable"))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", "notATable"))))
.hasMessageContaining("Missing scheduledJobParameter with key [automationStatus]");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", "notATable", "automationStatus", AutomationStatus.PENDING_INSERT_AUTOMATIONS.name()))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", "notATable", "automationStatus", AutomationStatus.PENDING_INSERT_AUTOMATIONS.name()))))
.hasMessageContaining("Unrecognized tableName");
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC, "automationStatus", AutomationStatus.PENDING_INSERT_AUTOMATIONS.name()))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC, "automationStatus", AutomationStatus.PENDING_INSERT_AUTOMATIONS.name()))))
.hasMessageContaining("does not have automationDetails");
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().withSchedule(null);
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY, "automationStatus", "foobar"))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY, "automationStatus", "foobar"))))
.hasMessageContaining("Did not find table automation actions matching automationStatus")
.hasMessageContaining("Found: PENDING_INSERT_AUTOMATIONS,PENDING_UPDATE_AUTOMATIONS");
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().withSchedule(new QScheduleMetaData());
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY, "automationStatus", AutomationStatus.PENDING_INSERT_AUTOMATIONS.name()))))
assertThatThrownBy(() -> qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS, Map.of("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY, "automationStatus", AutomationStatus.PENDING_INSERT_AUTOMATIONS.name()))))
.hasMessageContaining("has a schedule in its metaData - so it should not be dynamically scheduled");
}
@ -181,19 +155,19 @@ class QScheduleManagerTest extends BaseTest
QScheduleManager qScheduleManager = QScheduleManager.initInstance(qInstance, () -> QContext.getQSession());
qScheduleManager.start();
qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.PROCESS,
qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS,
Map.of("processName", TestUtils.PROCESS_NAME_GREET_PEOPLE))
.withId(2)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME));
qInstance.getQueue(TestUtils.TEST_SQS_QUEUE).setSchedule(null);
qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR,
qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.QUEUE_PROCESSOR,
Map.of("queueName", TestUtils.TEST_SQS_QUEUE))
.withId(3)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME));
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getAutomationDetails().setSchedule(null);
qScheduleManager.setupScheduledJob(newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS,
qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.TABLE_AUTOMATIONS,
Map.of("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY, "automationStatus", AutomationStatus.PENDING_UPDATE_AUTOMATIONS.name()))
.withId(4)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME));

View File

@ -22,7 +22,9 @@
package com.kingsrook.qqq.backend.core.scheduler;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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;
@ -31,6 +33,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJob;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobParameter;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
/*******************************************************************************
@ -57,6 +63,28 @@ public class SchedulerTestUtils
/*******************************************************************************
**
*******************************************************************************/
public static ScheduledJob newScheduledJob(ScheduledJobType type, Map<String, String> params)
{
ScheduledJob scheduledJob = new ScheduledJob()
.withId(1)
.withIsActive(true)
.withSchedulerName(TestUtils.SIMPLE_SCHEDULER_NAME)
.withType(type.name())
.withRepeatSeconds(1)
.withJobParameters(new ArrayList<>());
for(Map.Entry<String, String> entry : params.entrySet())
{
scheduledJob.getJobParameters().add(new ScheduledJobParameter().withKey(entry.getKey()).withValue(entry.getValue()));
}
return (scheduledJob);
}
/*******************************************************************************
**

View File

@ -0,0 +1,128 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.scheduler.processes;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
import com.kingsrook.qqq.backend.core.scheduler.SchedulerTestUtils;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzJobAndTriggerWrapper;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.quartz.SchedulerException;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for RescheduleAllJobsProcess
*******************************************************************************/
class RescheduleAllJobsProcessTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(QuartzScheduler.class);
try
{
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException, SchedulerException
{
QInstance qInstance = QContext.getQInstance();
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, RescheduleAllJobsProcess.class.getPackageName());
QuartzTestUtils.setupInstanceForQuartzTests();
QScheduleManager qScheduleManager = QScheduleManager.initInstance(qInstance, () -> QContext.getQSession());
qScheduleManager.start();
qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS,
Map.of("processName", TestUtils.PROCESS_NAME_GREET_PEOPLE))
.withId(2)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME));
QuartzScheduler quartzScheduler = QuartzScheduler.getInstance();
List<QuartzJobAndTriggerWrapper> wrappers = quartzScheduler.queryQuartz();
///////////////////////////////////////////////////////////////
// make sure our scheduledJob here got scheduled with quartz //
///////////////////////////////////////////////////////////////
assertTrue(wrappers.stream().anyMatch(w -> w.jobDetail().getKey().getName().equals("scheduledJob:2")));
/////////////////////////
// run the re-schedule //
/////////////////////////
RunProcessInput input = new RunProcessInput();
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
input.setProcessName(RescheduleAllJobsProcess.class.getSimpleName());
new RunProcessAction().execute(input);
////////////////////////////////////////////////////////////////////////////////////////
// now, because our scheduled job record isn't actually stored in ScheduledJob table, //
// when we reschdule all, it should become unscheduled. //
////////////////////////////////////////////////////////////////////////////////////////
wrappers = quartzScheduler.queryQuartz();
assertTrue(wrappers.stream().noneMatch(w -> w.jobDetail().getKey().getName().equals("scheduledJob:2")));
}
}

View File

@ -0,0 +1,117 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.scheduler.processes;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.scheduledjobs.ScheduledJobType;
import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
import com.kingsrook.qqq.backend.core.scheduler.SchedulerTestUtils;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzJobAndTriggerWrapper;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzScheduler;
import com.kingsrook.qqq.backend.core.scheduler.quartz.QuartzTestUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.quartz.SchedulerException;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for UnscheduleAllJobsProcess
*******************************************************************************/
class UnscheduleAllJobsProcessTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(QuartzScheduler.class);
try
{
QScheduleManager.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
try
{
QuartzScheduler.getInstance().unInit();
}
catch(IllegalStateException ise)
{
/////////////////////////////////////////////////////////////////
// ok, might just mean that this test didn't init the instance //
/////////////////////////////////////////////////////////////////
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException, SchedulerException
{
QInstance qInstance = QContext.getQInstance();
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, UnscheduleAllJobsProcess.class.getPackageName());
QuartzTestUtils.setupInstanceForQuartzTests();
QScheduleManager qScheduleManager = QScheduleManager.initInstance(qInstance, () -> QContext.getQSession());
qScheduleManager.start();
qScheduleManager.setupScheduledJob(SchedulerTestUtils.newScheduledJob(ScheduledJobType.PROCESS,
Map.of("processName", TestUtils.PROCESS_NAME_GREET_PEOPLE))
.withId(2)
.withSchedulerName(QuartzTestUtils.QUARTZ_SCHEDULER_NAME));
QuartzScheduler quartzScheduler = QuartzScheduler.getInstance();
List<QuartzJobAndTriggerWrapper> wrappers = quartzScheduler.queryQuartz();
assertTrue(wrappers.stream().anyMatch(w -> w.jobDetail().getKey().getName().equals("scheduledJob:2")));
RunProcessInput input = new RunProcessInput();
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
input.setProcessName(UnscheduleAllJobsProcess.class.getSimpleName());
new RunProcessAction().execute(input);
wrappers = quartzScheduler.queryQuartz();
assertTrue(wrappers.stream().noneMatch(w -> w.jobDetail().getKey().getName().equals("scheduledJob:2")));
}
}

View File

@ -40,6 +40,14 @@ public class PersonQRecord extends QRecord
public PersonQRecord withFirstName(String firstName)
{
setValue("firstName", firstName);
return (this);
}
public PersonQRecord withBirthDate(LocalDate birthDate)
{
setValue("birthDate", birthDate);

View File

@ -25,7 +25,9 @@ package com.kingsrook.qqq.backend.core.utils;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
@ -79,4 +81,20 @@ class ObjectUtilsTest
assertEquals("else", ObjectUtils.tryAndRequireNonNullElse(() -> null, "else"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIfCan()
{
Object nullObject = null;
assertTrue(ObjectUtils.ifCan(() -> true));
assertTrue(ObjectUtils.ifCan(() -> "a".equals("a")));
assertFalse(ObjectUtils.ifCan(() -> 1 == 2));
assertFalse(ObjectUtils.ifCan(() -> nullObject.equals("a")));
assertFalse(ObjectUtils.ifCan(() -> null));
}
}

View File

@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.utils.aggregates;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Month;
import com.kingsrook.qqq.backend.core.BaseTest;
import org.assertj.core.data.Offset;
import org.junit.jupiter.api.Test;
@ -78,6 +81,12 @@ class AggregatesTest extends BaseTest
assertEquals(15, aggregates.getMax());
assertEquals(30, aggregates.getSum());
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10"), Offset.offset(BigDecimal.ZERO));
assertEquals(new BigDecimal("750"), aggregates.getProduct());
assertEquals(new BigDecimal("25.0000"), aggregates.getVariance());
assertEquals(new BigDecimal("5.0000"), aggregates.getStandardDeviation());
assertThat(aggregates.getVarP()).isCloseTo(new BigDecimal("16.6667"), Offset.offset(new BigDecimal(".0001")));
assertThat(aggregates.getStdDevP()).isCloseTo(new BigDecimal("4.0824"), Offset.offset(new BigDecimal(".0001")));
}
@ -89,6 +98,7 @@ class AggregatesTest extends BaseTest
void testBigDecimal()
{
BigDecimalAggregates aggregates = new BigDecimalAggregates();
aggregates.add(null);
assertEquals(0, aggregates.getCount());
assertNull(aggregates.getMin());
@ -114,13 +124,117 @@ class AggregatesTest extends BaseTest
BigDecimal bd148 = new BigDecimal("14.8");
aggregates.add(bd148);
aggregates.add(null);
assertEquals(3, aggregates.getCount());
assertEquals(bd51, aggregates.getMin());
assertEquals(bd148, aggregates.getMax());
assertEquals(new BigDecimal("30.0"), aggregates.getSum());
assertThat(aggregates.getAverage()).isCloseTo(new BigDecimal("10.0"), Offset.offset(BigDecimal.ZERO));
assertEquals(new BigDecimal("762.348"), aggregates.getProduct());
assertEquals(new BigDecimal("23.5300"), aggregates.getVariance());
assertEquals(new BigDecimal("4.8508"), aggregates.getStandardDeviation());
assertThat(aggregates.getVarP()).isCloseTo(new BigDecimal("15.6867"), Offset.offset(new BigDecimal(".0001")));
assertThat(aggregates.getStdDevP()).isCloseTo(new BigDecimal("3.9606"), Offset.offset(new BigDecimal(".0001")));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testInstant()
{
InstantAggregates aggregates = new InstantAggregates();
assertEquals(0, aggregates.getCount());
assertNull(aggregates.getMin());
assertNull(aggregates.getMax());
assertNull(aggregates.getSum());
assertNull(aggregates.getAverage());
Instant i1970 = Instant.parse("1970-01-01T00:00:00Z");
aggregates.add(i1970);
assertEquals(1, aggregates.getCount());
assertEquals(i1970, aggregates.getMin());
assertEquals(i1970, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(i1970, aggregates.getAverage());
Instant i1980 = Instant.parse("1980-01-01T00:00:00Z");
aggregates.add(i1980);
assertEquals(2, aggregates.getCount());
assertEquals(i1970, aggregates.getMin());
assertEquals(i1980, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(Instant.parse("1975-01-01T00:00:00Z"), aggregates.getAverage());
Instant i1990 = Instant.parse("1990-01-01T00:00:00Z");
aggregates.add(i1990);
assertEquals(3, aggregates.getCount());
assertEquals(i1970, aggregates.getMin());
assertEquals(i1990, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(Instant.parse("1980-01-01T08:00:00Z"), aggregates.getAverage()); // a leap day throws this off by 8 hours :)
/////////////////////////////////////////////////////////////////////
// assert we gracefully return null for these ops we don't support //
/////////////////////////////////////////////////////////////////////
assertNull(aggregates.getProduct());
assertNull(aggregates.getVariance());
assertNull(aggregates.getStandardDeviation());
assertNull(aggregates.getVarP());
assertNull(aggregates.getStdDevP());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testLocalDate()
{
LocalDateAggregates aggregates = new LocalDateAggregates();
assertEquals(0, aggregates.getCount());
assertNull(aggregates.getMin());
assertNull(aggregates.getMax());
assertNull(aggregates.getSum());
assertNull(aggregates.getAverage());
LocalDate ld1970 = LocalDate.of(1970, Month.JANUARY, 1);
aggregates.add(ld1970);
assertEquals(1, aggregates.getCount());
assertEquals(ld1970, aggregates.getMin());
assertEquals(ld1970, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(ld1970, aggregates.getAverage());
LocalDate ld1980 = LocalDate.of(1980, Month.JANUARY, 1);
aggregates.add(ld1980);
assertEquals(2, aggregates.getCount());
assertEquals(ld1970, aggregates.getMin());
assertEquals(ld1980, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(LocalDate.of(1975, Month.JANUARY, 1), aggregates.getAverage());
LocalDate ld1990 = LocalDate.of(1990, Month.JANUARY, 1);
aggregates.add(ld1990);
assertEquals(3, aggregates.getCount());
assertEquals(ld1970, aggregates.getMin());
assertEquals(ld1990, aggregates.getMax());
assertNull(aggregates.getSum());
assertEquals(ld1980, aggregates.getAverage());
/////////////////////////////////////////////////////////////////////
// assert we gracefully return null for these ops we don't support //
/////////////////////////////////////////////////////////////////////
assertNull(aggregates.getProduct());
assertNull(aggregates.getVariance());
assertNull(aggregates.getStandardDeviation());
assertNull(aggregates.getVarP());
assertNull(aggregates.getStdDevP());
}
}