diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/CsvExportStreamer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/CsvExportStreamer.java index 3284279e..d35ec31b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/CsvExportStreamer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/CsvExportStreamer.java @@ -73,7 +73,7 @@ public class CsvExportStreamer implements ExportStreamerInterface table = exportInput.getTable(); outputStream = this.exportInput.getReportOutputStream(); - writeReportHeaderRow(); + writeTitleAndHeader(); } @@ -81,7 +81,7 @@ public class CsvExportStreamer implements ExportStreamerInterface /******************************************************************************* ** *******************************************************************************/ - private void writeReportHeaderRow() throws QReportingException + private void writeTitleAndHeader() throws QReportingException { try { @@ -90,16 +90,20 @@ public class CsvExportStreamer implements ExportStreamerInterface outputStream.write((exportInput.getTitleRow() + "\n").getBytes(StandardCharsets.UTF_8)); } - int col = 0; - for(QFieldMetaData column : fields) + if(exportInput.getIncludeHeaderRow()) { - if(col++ > 0) + int col = 0; + for(QFieldMetaData column : fields) { - outputStream.write(','); + if(col++ > 0) + { + outputStream.write(','); + } + outputStream.write(('"' + column.getLabel() + '"').getBytes(StandardCharsets.UTF_8)); } - outputStream.write(('"' + column.getLabel() + '"').getBytes(StandardCharsets.UTF_8)); + outputStream.write('\n'); } - outputStream.write('\n'); + outputStream.flush(); } catch(Exception e) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExcelExportStreamer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExcelExportStreamer.java index bb166c97..103156fc 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExcelExportStreamer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExcelExportStreamer.java @@ -130,7 +130,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface worksheet = workbook.newWorksheet(Objects.requireNonNullElse(label, "Sheet " + sheetCount)); - writeReportHeaderRow(); + writeTitleAndHeader(); } catch(Exception e) { @@ -143,7 +143,7 @@ public class ExcelExportStreamer implements ExportStreamerInterface /******************************************************************************* ** *******************************************************************************/ - private void writeReportHeaderRow() throws QReportingException + private void writeTitleAndHeader() throws QReportingException { try { @@ -160,25 +160,28 @@ public class ExcelExportStreamer implements ExportStreamerInterface titleStyle.set(); row++; + worksheet.flush(); } //////////////// // header row // //////////////// - int col = 0; - for(QFieldMetaData column : fields) + if(exportInput.getIncludeHeaderRow()) { - worksheet.value(row, col, column.getLabel()); - col++; + int col = 0; + for(QFieldMetaData column : fields) + { + worksheet.value(row, col, column.getLabel()); + col++; + } + + StyleSetter headerStyle = worksheet.range(row, 0, row, fields.size() - 1).style(); + excelStylerInterface.styleHeaderRow(headerStyle); + headerStyle.set(); + + row++; + worksheet.flush(); } - - StyleSetter headerStyle = worksheet.range(row, 0, row, fields.size() - 1).style(); - excelStylerInterface.styleHeaderRow(headerStyle); - headerStyle.set(); - - row++; - - worksheet.flush(); } catch(Exception e) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java index 370f164b..7fd96769 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; @@ -180,10 +181,15 @@ public class GenerateReportAction { QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable()); + QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter(); + variableInterpreter.addValueMap("input", reportInput.getInputValues()); + ExportInput exportInput = new ExportInput(reportInput.getInstance()); exportInput.setSession(reportInput.getSession()); exportInput.setReportFormat(reportFormat); exportInput.setFilename(reportInput.getFilename()); + exportInput.setTitleRow(getTitle(reportView, variableInterpreter)); + exportInput.setIncludeHeaderRow(reportView.getHeaderRow()); exportInput.setReportOutputStream(reportInput.getReportOutputStream()); List fields; @@ -222,9 +228,6 @@ public class GenerateReportAction *******************************************************************************/ private void gatherData(ReportInput reportInput, QReportDataSource dataSource, QReportView tableView, List pivotViews, List variantViews) throws QException { - QQueryFilter queryFilter = dataSource.getQueryFilter().clone(); - setInputValuesInQueryFilter(reportInput, queryFilter); - //////////////////////////////////////////////////////////////////////////////////////// // check if this view has a transform step - if so, set it up now and run its pre-run // //////////////////////////////////////////////////////////////////////////////////////// @@ -257,13 +260,40 @@ public class GenerateReportAction RecordPipe recordPipe = new RecordPipe(); new AsyncRecordPipeLoop().run("Report[" + reportInput.getReportName() + "]", null, recordPipe, (callback) -> { - QueryInput queryInput = new QueryInput(reportInput.getInstance()); - queryInput.setSession(reportInput.getSession()); - queryInput.setRecordPipe(recordPipe); - queryInput.setTableName(dataSource.getSourceTable()); - queryInput.setFilter(queryFilter); - queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this? - return (new QueryAction().execute(queryInput)); + if(dataSource.getSourceTable() != null) + { + QQueryFilter queryFilter = dataSource.getQueryFilter().clone(); + setInputValuesInQueryFilter(reportInput, queryFilter); + + QueryInput queryInput = new QueryInput(reportInput.getInstance()); + queryInput.setSession(reportInput.getSession()); + queryInput.setRecordPipe(recordPipe); + queryInput.setTableName(dataSource.getSourceTable()); + queryInput.setFilter(queryFilter); + queryInput.setShouldTranslatePossibleValues(true); // todo - any limits or conditions on this? + return (new QueryAction().execute(queryInput)); + } + else if(dataSource.getStaticDataSupplier() != null) + { + @SuppressWarnings("unchecked") + Supplier>> supplier = QCodeLoader.getAdHoc(Supplier.class, dataSource.getStaticDataSupplier()); + List> lists = supplier.get(); + for(List list : lists) + { + QRecord record = new QRecord(); + int index = 0; + for(Serializable value : list) + { + record.setValue("column" + (index++), value); + } + recordPipe.addRecord(record); + } + return (true); + } + else + { + throw (new IllegalStateException("Misconfigured data source [" + dataSource.getName() + "].")); + } }, () -> { List records = recordPipe.consumeAvailableRecords(); @@ -465,6 +495,7 @@ public class GenerateReportAction exportInput.setReportFormat(reportFormat); exportInput.setFilename(reportInput.getFilename()); exportInput.setTitleRow(pivotOutput.titleRow); + exportInput.setIncludeHeaderRow(view.getHeaderRow()); exportInput.setReportOutputStream(reportInput.getReportOutputStream()); reportStreamer.setDisplayFormats(getDisplayFormatMap(view)); @@ -540,26 +571,7 @@ public class GenerateReportAction /////////// // title // /////////// - String title = null; - if(view.getTitleFields() != null && StringUtils.hasContent(view.getTitleFormat())) - { - List titleValues = new ArrayList<>(); - for(String titleField : view.getTitleFields()) - { - titleValues.add(variableInterpreter.interpret(titleField)); - } - - title = valueFormatter.formatStringWithValues(view.getTitleFormat(), titleValues); - } - else if(StringUtils.hasContent(view.getTitleFormat())) - { - title = view.getTitleFormat(); - } - - if(StringUtils.hasContent(title)) - { - System.out.println(title); - } + String title = getTitle(view, variableInterpreter); ///////////// // headers // @@ -701,6 +713,36 @@ public class GenerateReportAction + /******************************************************************************* + ** + *******************************************************************************/ + private String getTitle(QReportView view, QMetaDataVariableInterpreter variableInterpreter) + { + String title = null; + if(view.getTitleFields() != null && StringUtils.hasContent(view.getTitleFormat())) + { + List titleValues = new ArrayList<>(); + for(String titleField : view.getTitleFields()) + { + titleValues.add(variableInterpreter.interpret(titleField)); + } + + title = new QValueFormatter().formatStringWithValues(view.getTitleFormat(), titleValues); + } + else if(StringUtils.hasContent(view.getTitleFormat())) + { + title = view.getTitleFormat(); + } + + if(StringUtils.hasContent(title)) + { + System.out.println(title); + } + return title; + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ListOfMapsExportStreamer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ListOfMapsExportStreamer.java index 93a2065e..10781064 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ListOfMapsExportStreamer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ListOfMapsExportStreamer.java @@ -96,10 +96,14 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface currentSheetLabel = label; rows.put(label, new ArrayList<>()); - headers.put(label, new ArrayList<>()); - for(QFieldMetaData field : fields) + + if(exportInput.getIncludeHeaderRow()) { - headers.get(label).add(field.getLabel()); + headers.put(label, new ArrayList<>()); + for(QFieldMetaData field : fields) + { + headers.get(label).add(field.getLabel()); + } } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ExportInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ExportInput.java index 0e8dbde8..a90826d8 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ExportInput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ExportInput.java @@ -43,6 +43,7 @@ public class ExportInput extends AbstractTableActionInput private ReportFormat reportFormat; private OutputStream reportOutputStream; private String titleRow; + private boolean includeHeaderRow = true; @@ -225,4 +226,27 @@ public class ExportInput extends AbstractTableActionInput { this.titleRow = titleRow; } + + + + /******************************************************************************* + ** Getter for includeHeaderRow + ** + *******************************************************************************/ + public boolean getIncludeHeaderRow() + { + return includeHeaderRow; + } + + + + /******************************************************************************* + ** Setter for includeHeaderRow + ** + *******************************************************************************/ + public void setIncludeHeaderRow(boolean includeHeaderRow) + { + this.includeHeaderRow = includeHeaderRow; + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeUsage.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeUsage.java index 7fd8fbd1..ce4509be 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeUsage.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeUsage.java @@ -31,5 +31,6 @@ public enum QCodeUsage BACKEND_STEP, // a backend-step in a process CUSTOMIZER, // a function to customize part of a QQQ table's behavior POSSIBLE_VALUE_PROVIDER, // code that drives a custom possibleValueSource - RECORD_AUTOMATION_HANDLER // code that executes record automations + RECORD_AUTOMATION_HANDLER, // code that executes record automations + REPORT_STATIC_DATA_SUPPLIER // code that supplies static data to a report } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportDataSource.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportDataSource.java index 54f2f80e..af1a0c40 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportDataSource.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportDataSource.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.reporting; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; /******************************************************************************* @@ -34,6 +35,8 @@ public class QReportDataSource private String sourceTable; private QQueryFilter queryFilter; + private QCodeReference staticDataSupplier; + /******************************************************************************* @@ -136,4 +139,38 @@ public class QReportDataSource return (this); } + + + /******************************************************************************* + ** Getter for staticDataSupplier + ** + *******************************************************************************/ + public QCodeReference getStaticDataSupplier() + { + return staticDataSupplier; + } + + + + /******************************************************************************* + ** Setter for staticDataSupplier + ** + *******************************************************************************/ + public void setStaticDataSupplier(QCodeReference staticDataSupplier) + { + this.staticDataSupplier = staticDataSupplier; + } + + + + /******************************************************************************* + ** Fluent setter for staticDataSupplier + ** + *******************************************************************************/ + public QReportDataSource withStaticDataSupplier(QCodeReference staticDataSupplier) + { + this.staticDataSupplier = staticDataSupplier; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportView.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportView.java index 624683b0..dad6d3cc 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportView.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportView.java @@ -41,6 +41,7 @@ public class QReportView implements Cloneable private String titleFormat; private List titleFields; private List pivotFields; + private boolean headerRow = true; private boolean totalRow = false; private boolean pivotSubTotals = false; private List columns; @@ -327,6 +328,40 @@ public class QReportView implements Cloneable + /******************************************************************************* + ** Getter for headerRow + ** + *******************************************************************************/ + public boolean getHeaderRow() + { + return headerRow; + } + + + + /******************************************************************************* + ** Setter for headerRow + ** + *******************************************************************************/ + public void setHeaderRow(boolean headerRow) + { + this.headerRow = headerRow; + } + + + + /******************************************************************************* + ** Fluent setter for headerRow + ** + *******************************************************************************/ + public QReportView withHeaderRow(boolean headerRow) + { + this.headerRow = headerRow; + return (this); + } + + + /******************************************************************************* ** Getter for totalRow **