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 49ecd772..fac33e13 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 @@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.StringUtils; @@ -65,12 +66,12 @@ public class CsvExportStreamer implements ExportStreamerInterface ** *******************************************************************************/ @Override - public void start(ExportInput exportInput, List fields, String label) throws QReportingException + public void start(ExportInput exportInput, List fields, String label, QReportView view) throws QReportingException { this.exportInput = exportInput; this.fields = fields; table = exportInput.getTable(); - outputStream = this.exportInput.getReportOutputStream(); + outputStream = this.exportInput.getReportDestination().getReportOutputStream(); writeTitleAndHeader(); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportAction.java index 6bd4b83d..4d4d6bff 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportAction.java @@ -52,6 +52,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher; @@ -138,7 +139,7 @@ public class ExportAction /////////////////////////////////////////////////////////////////////////////////////////////////////////// // check if this report format has a max-rows limit -- if so, do a count to verify we're under the limit // /////////////////////////////////////////////////////////////////////////////////////////////////////////// - ReportFormat reportFormat = exportInput.getReportFormat(); + ReportFormat reportFormat = exportInput.getReportDestination().getReportFormat(); verifyCountUnderMax(exportInput, backendModule, reportFormat); preExecuteRan = true; @@ -232,6 +233,7 @@ public class ExportAction } queryInput.getFilter().setLimit(exportInput.getLimit()); queryInput.setShouldTranslatePossibleValues(true); + queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS); ///////////////////////////////////////////////////////////////// // tell this query that it needs to put its output into a pipe // @@ -242,10 +244,19 @@ public class ExportAction //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // set up a report streamer, which will read rows from the pipe, and write formatted report rows to the output stream // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ReportFormat reportFormat = exportInput.getReportFormat(); + ReportFormat reportFormat = exportInput.getReportDestination().getReportFormat(); ExportStreamerInterface reportStreamer = reportFormat.newReportStreamer(); List fields = getFields(exportInput); - reportStreamer.start(exportInput, fields, "Sheet 1"); + + ////////////////////////////////////////////////////////// + // it seems we can pass a view with just a name in here // + ////////////////////////////////////////////////////////// + List views = new ArrayList<>(); + views.add(new QReportView() + .withName("export")); + + reportStreamer.preRun(exportInput.getReportDestination(), views); + reportStreamer.start(exportInput, fields, "Sheet 1", views.get(0)); ////////////////////////////////////////// // run the query action as an async job // @@ -334,7 +345,7 @@ public class ExportAction try { - exportInput.getReportOutputStream().close(); + exportInput.getReportDestination().getReportOutputStream().close(); } catch(Exception e) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java index 473b3b34..4e2f7e02 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ExportStreamerInterface.java @@ -26,8 +26,10 @@ import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.exceptions.QReportingException; import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportDestination; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; /******************************************************************************* @@ -35,20 +37,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; *******************************************************************************/ public interface ExportStreamerInterface { - /******************************************************************************* - ** Called once, before any rows are available. Meant to write a header, for example. - *******************************************************************************/ - void start(ExportInput exportInput, List fields, String label) throws QReportingException; /******************************************************************************* - ** Called as records flow into the pipe. - ******************************************************************************/ - void addRecords(List recordList) throws QReportingException; - - /******************************************************************************* - ** Called once, after all rows are available. Meant to write a footer, or close resources, for example. + ** Called once, before any sheets are actually being produced. *******************************************************************************/ - void finish() throws QReportingException; + default void preRun(ReportDestination reportDestination, List views) throws QReportingException + { + // noop in base class + } /******************************************************************************* ** @@ -58,6 +54,20 @@ public interface ExportStreamerInterface // noop in base class } + /******************************************************************************* + ** Called once per sheet, before any rows are available. Meant to write a + ** header, for example. + ** + ** If multiple sheets are being created, there is no separate end-sheet call. + ** Rather, a new one will just get started... + *******************************************************************************/ + void start(ExportInput exportInput, List fields, String label, QReportView view) throws QReportingException; + + /******************************************************************************* + ** Called as records flow into the pipe. + ******************************************************************************/ + void addRecords(List recordList) throws QReportingException; + /******************************************************************************* ** *******************************************************************************/ @@ -65,4 +75,11 @@ public interface ExportStreamerInterface { addRecords(List.of(record)); } + + /******************************************************************************* + ** Called after all sheets are complete. Meant to do a final write, or close + ** resources, for example. + *******************************************************************************/ + void finish() throws QReportingException; + } 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 4f1fc32b..91f07d98 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 @@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.actions.reporting.customizers.DataSourceQu import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportViewCustomizer; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter; +import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QFormulaException; import com.kingsrook.qqq.backend.core.exceptions.QReportingException; @@ -108,9 +109,9 @@ public class GenerateReportAction Map> totalAggregates = new HashMap<>(); Map> varianceTotalAggregates = new HashMap<>(); - private QReportMetaData report; - private ReportFormat reportFormat; private ExportStreamerInterface reportStreamer; + private List dataSources; + private List views; @@ -119,33 +120,39 @@ public class GenerateReportAction *******************************************************************************/ public void execute(ReportInput reportInput) throws QException { - report = reportInput.getInstance().getReport(reportInput.getReportName()); - reportFormat = reportInput.getReportFormat(); + QReportMetaData report = getReportMetaData(reportInput); + + this.views = report.getViews(); + this.dataSources = report.getDataSources(); + + ReportFormat reportFormat = reportInput.getReportDestination().getReportFormat(); if(reportFormat == null) { throw new QException("Report format was not specified."); } reportStreamer = reportFormat.newReportStreamer(); + reportStreamer.preRun(reportInput.getReportDestination(), views); + //////////////////////////////////////////////////////////////////////////////////////////////// // foreach data source, do a query (possibly more than 1, if it goes to multiple table views) // //////////////////////////////////////////////////////////////////////////////////////////////// - for(QReportDataSource dataSource : report.getDataSources()) + for(QReportDataSource dataSource : dataSources) { ////////////////////////////////////////////////////////////////////////////// // make a list of the views that use this data source for various purposes. // ////////////////////////////////////////////////////////////////////////////// - List dataSourceTableViews = report.getViews().stream() + List dataSourceTableViews = views.stream() .filter(v -> v.getType().equals(ReportType.TABLE)) .filter(v -> v.getDataSourceName().equals(dataSource.getName())) .toList(); - List dataSourceSummaryViews = report.getViews().stream() + List dataSourceSummaryViews = views.stream() .filter(v -> v.getType().equals(ReportType.SUMMARY)) .filter(v -> v.getDataSourceName().equals(dataSource.getName())) .toList(); - List dataSourceVariantViews = report.getViews().stream() + List dataSourceVariantViews = views.stream() .filter(v -> v.getType().equals(ReportType.SUMMARY)) .filter(v -> v.getVarianceDataSourceName() != null && v.getVarianceDataSourceName().equals(dataSource.getName())) .toList(); @@ -190,13 +197,29 @@ public class GenerateReportAction } } + //////////////////////////////////////// + // add pivot sheets // + // todo - but, only for Excel, right? // + //////////////////////////////////////// + for(QReportView view : views) + { + if(view.getType().equals(ReportType.PIVOT)) + { + startTableView(reportInput, null, view); + + ////////////////////////////////////////////////////////////////////////// + // there's no data to add to a pivot table, so nothing else to do here. // + ////////////////////////////////////////////////////////////////////////// + } + } + outputSummaries(reportInput); reportStreamer.finish(); try { - reportInput.getReportOutputStream().close(); + reportInput.getReportDestination().getReportOutputStream().close(); } catch(Exception e) { @@ -206,31 +229,50 @@ public class GenerateReportAction + /******************************************************************************* + ** + *******************************************************************************/ + private QReportMetaData getReportMetaData(ReportInput reportInput) throws QException + { + if(reportInput.getReportMetaData() != null) + { + return reportInput.getReportMetaData(); + } + + if(StringUtils.hasContent(reportInput.getReportName())) + { + return QContext.getQInstance().getReport(reportInput.getReportName()); + } + + throw (new QReportingException("ReportInput did not contain required parameters to identify the report being generated")); + } + + + /******************************************************************************* ** *******************************************************************************/ private void startTableView(ReportInput reportInput, QReportDataSource dataSource, QReportView reportView) throws QException { - QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable()); - QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter(); variableInterpreter.addValueMap("input", reportInput.getInputValues()); ExportInput exportInput = new ExportInput(); - exportInput.setReportFormat(reportFormat); - exportInput.setFilename(reportInput.getFilename()); + exportInput.setReportDestination(reportInput.getReportDestination()); exportInput.setTitleRow(getTitle(reportView, variableInterpreter)); exportInput.setIncludeHeaderRow(reportView.getIncludeHeaderRow()); - exportInput.setReportOutputStream(reportInput.getReportOutputStream()); JoinsContext joinsContext = null; - if(StringUtils.hasContent(dataSource.getSourceTable())) + if(dataSource != null) { - joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), dataSource.getQueryFilter()); + if(StringUtils.hasContent(dataSource.getSourceTable())) + { + joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), dataSource.getQueryFilter()); + } } List fields = new ArrayList<>(); - for(QReportField column : reportView.getColumns()) + for(QReportField column : CollectionUtils.nonNullList(reportView.getColumns())) { if(column.getIsVirtual()) { @@ -256,7 +298,7 @@ public class GenerateReportAction } reportStreamer.setDisplayFormats(getDisplayFormatMap(fields)); - reportStreamer.start(exportInput, fields, reportView.getLabel()); + reportStreamer.start(exportInput, fields, reportView.getLabel(), reportView); } @@ -307,6 +349,7 @@ public class GenerateReportAction queryInput.setTableName(dataSource.getSourceTable()); queryInput.setFilter(queryFilter); queryInput.setQueryJoins(dataSource.getQueryJoins()); + queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS); queryInput.setShouldTranslatePossibleValues(true); queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter()))); @@ -371,7 +414,7 @@ public class GenerateReportAction { Set fieldsToTranslatePossibleValues = new HashSet<>(); - for(QReportView view : report.getViews()) + for(QReportView view : views) { for(QReportField column : CollectionUtils.nonNullList(view.getColumns())) { @@ -577,22 +620,20 @@ public class GenerateReportAction *******************************************************************************/ private void outputSummaries(ReportInput reportInput) throws QReportingException, QFormulaException { - List reportViews = report.getViews().stream().filter(v -> v.getType().equals(ReportType.SUMMARY)).toList(); + List reportViews = views.stream().filter(v -> v.getType().equals(ReportType.SUMMARY)).toList(); for(QReportView view : reportViews) { - QReportDataSource dataSource = report.getDataSource(view.getDataSourceName()); + QReportDataSource dataSource = getDataSource(view.getDataSourceName()); QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable()); SummaryOutput summaryOutput = computeSummaryRowsForView(reportInput, view, table); ExportInput exportInput = new ExportInput(); - exportInput.setReportFormat(reportFormat); - exportInput.setFilename(reportInput.getFilename()); + exportInput.setReportDestination(reportInput.getReportDestination()); exportInput.setTitleRow(summaryOutput.titleRow); exportInput.setIncludeHeaderRow(view.getIncludeHeaderRow()); - exportInput.setReportOutputStream(reportInput.getReportOutputStream()); reportStreamer.setDisplayFormats(getDisplayFormatMap(view)); - reportStreamer.start(exportInput, getFields(table, view), view.getLabel()); + reportStreamer.start(exportInput, getFields(table, view), view.getLabel(), view); reportStreamer.addRecords(summaryOutput.summaryRows); // todo - what if this set is huge? @@ -605,6 +646,24 @@ public class GenerateReportAction + /******************************************************************************* + ** + *******************************************************************************/ + private QReportDataSource getDataSource(String dataSourceName) + { + for(QReportDataSource dataSource : CollectionUtils.nonNullList(dataSources)) + { + if(dataSource.getName().equals(dataSourceName)) + { + return (dataSource); + } + } + + return (null); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/JsonExportStreamer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/JsonExportStreamer.java index b90cde16..f44f85f3 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/JsonExportStreamer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/JsonExportStreamer.java @@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; 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.StringUtils; @@ -69,12 +70,12 @@ public class JsonExportStreamer implements ExportStreamerInterface ** *******************************************************************************/ @Override - public void start(ExportInput exportInput, List fields, String label) throws QReportingException + public void start(ExportInput exportInput, List fields, String label, QReportView view) throws QReportingException { this.exportInput = exportInput; this.fields = fields; table = exportInput.getTable(); - outputStream = this.exportInput.getReportOutputStream(); + outputStream = this.exportInput.getReportDestination().getReportOutputStream(); try { 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 365c3a40..7e422a21 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 @@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; /******************************************************************************* @@ -87,7 +88,7 @@ public class ListOfMapsExportStreamer implements ExportStreamerInterface ** *******************************************************************************/ @Override - public void start(ExportInput exportInput, List fields, String label) throws QReportingException + public void start(ExportInput exportInput, List fields, String label, QReportView view) throws QReportingException { this.exportInput = exportInput; this.fields = fields; diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ReportUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ReportUtils.java new file mode 100644 index 00000000..9c682e22 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/ReportUtils.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.reporting; + + +import java.util.List; +import java.util.Optional; +import com.kingsrook.qqq.backend.core.exceptions.QReportingException; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class ReportUtils +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public static QReportView getSourceViewForPivotTableView(List views, QReportView pivotTableView) throws QReportingException + { + Optional sourceView = views.stream().filter(v -> v.getName().equals(pivotTableView.getPivotTableSourceViewName())).findFirst(); + if(sourceView.isEmpty()) + { + throw (new QReportingException("Could not find data view [" + pivotTableView.getPivotTableSourceViewName() + "] for pivot table view [" + pivotTableView.getName() + "]")); + } + + return sourceView.get(); + } + +} 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 b774ed73..2ed0ba4e 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 @@ -22,7 +22,6 @@ package com.kingsrook.qqq.backend.core.model.actions.reporting; -import java.io.OutputStream; import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; @@ -37,9 +36,8 @@ public class ExportInput extends AbstractTableActionInput private Integer limit; private List fieldNames; - private String filename; - private ReportFormat reportFormat; - private OutputStream reportOutputStream; + private ReportDestination reportDestination; + private String titleRow; private boolean includeHeaderRow = true; @@ -120,71 +118,6 @@ public class ExportInput extends AbstractTableActionInput - /******************************************************************************* - ** Getter for filename - ** - *******************************************************************************/ - public String getFilename() - { - return filename; - } - - - - /******************************************************************************* - ** Setter for filename - ** - *******************************************************************************/ - public void setFilename(String filename) - { - this.filename = filename; - } - - - - /******************************************************************************* - ** Getter for reportFormat - ** - *******************************************************************************/ - public ReportFormat getReportFormat() - { - return reportFormat; - } - - - - /******************************************************************************* - ** Setter for reportFormat - ** - *******************************************************************************/ - public void setReportFormat(ReportFormat reportFormat) - { - this.reportFormat = reportFormat; - } - - - - /******************************************************************************* - ** Getter for reportOutputStream - ** - *******************************************************************************/ - public OutputStream getReportOutputStream() - { - return reportOutputStream; - } - - - - /******************************************************************************* - ** Setter for reportOutputStream - ** - *******************************************************************************/ - public void setReportOutputStream(OutputStream reportOutputStream) - { - this.reportOutputStream = reportOutputStream; - } - - /******************************************************************************* ** @@ -226,4 +159,36 @@ public class ExportInput extends AbstractTableActionInput this.includeHeaderRow = includeHeaderRow; } + + + /******************************************************************************* + ** Getter for reportDestination + *******************************************************************************/ + public ReportDestination getReportDestination() + { + return (this.reportDestination); + } + + + + /******************************************************************************* + ** Setter for reportDestination + *******************************************************************************/ + public void setReportDestination(ReportDestination reportDestination) + { + this.reportDestination = reportDestination; + } + + + + /******************************************************************************* + ** Fluent setter for reportDestination + *******************************************************************************/ + public ExportInput withReportDestination(ReportDestination reportDestination) + { + this.reportDestination = reportDestination; + return (this); + } + + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportDestination.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportDestination.java new file mode 100644 index 00000000..c233c8aa --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportDestination.java @@ -0,0 +1,130 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.model.actions.reporting; + + +import java.io.OutputStream; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class ReportDestination +{ + private String filename; + private ReportFormat reportFormat; + private OutputStream reportOutputStream; + + + + /******************************************************************************* + ** Getter for filename + *******************************************************************************/ + public String getFilename() + { + return (this.filename); + } + + + + /******************************************************************************* + ** Setter for filename + *******************************************************************************/ + public void setFilename(String filename) + { + this.filename = filename; + } + + + + /******************************************************************************* + ** Fluent setter for filename + *******************************************************************************/ + public ReportDestination withFilename(String filename) + { + this.filename = filename; + return (this); + } + + + + /******************************************************************************* + ** Getter for reportFormat + *******************************************************************************/ + public ReportFormat getReportFormat() + { + return (this.reportFormat); + } + + + + /******************************************************************************* + ** Setter for reportFormat + *******************************************************************************/ + public void setReportFormat(ReportFormat reportFormat) + { + this.reportFormat = reportFormat; + } + + + + /******************************************************************************* + ** Fluent setter for reportFormat + *******************************************************************************/ + public ReportDestination withReportFormat(ReportFormat reportFormat) + { + this.reportFormat = reportFormat; + return (this); + } + + + + /******************************************************************************* + ** Getter for reportOutputStream + *******************************************************************************/ + public OutputStream getReportOutputStream() + { + return (this.reportOutputStream); + } + + + + /******************************************************************************* + ** Setter for reportOutputStream + *******************************************************************************/ + public void setReportOutputStream(OutputStream reportOutputStream) + { + this.reportOutputStream = reportOutputStream; + } + + + + /******************************************************************************* + ** Fluent setter for reportOutputStream + *******************************************************************************/ + public ReportDestination withReportOutputStream(OutputStream reportOutputStream) + { + this.reportOutputStream = reportOutputStream; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormat.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormat.java index c95b1be8..1718b00d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormat.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormat.java @@ -25,10 +25,10 @@ package com.kingsrook.qqq.backend.core.model.actions.reporting; import java.util.Locale; import java.util.function.Supplier; import com.kingsrook.qqq.backend.core.actions.reporting.CsvExportStreamer; -import com.kingsrook.qqq.backend.core.actions.reporting.ExcelExportStreamer; import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface; import com.kingsrook.qqq.backend.core.actions.reporting.JsonExportStreamer; import com.kingsrook.qqq.backend.core.actions.reporting.ListOfMapsExportStreamer; +import com.kingsrook.qqq.backend.core.actions.reporting.excel.poi.ExcelPoiBasedStreamingExportStreamer; import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.utils.StringUtils; import org.dhatim.fastexcel.Worksheet; @@ -39,7 +39,13 @@ import org.dhatim.fastexcel.Worksheet; *******************************************************************************/ public enum ReportFormat { - XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelExportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelPoiBasedStreamingExportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + + ///////////////////////////////////////////////////////////////////////// + // if we need to fall back to Fastexcel, this was its version of this. // + ///////////////////////////////////////////////////////////////////////// + // XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelFastexcelExportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + JSON(null, null, JsonExportStreamer::new, "application/json"), CSV(null, null, CsvExportStreamer::new, "text/csv"), LIST_OF_MAPS(null, null, ListOfMapsExportStreamer::new, null); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java index e712a79e..2b081129 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportInput.java @@ -22,11 +22,11 @@ package com.kingsrook.qqq.backend.core.model.actions.reporting; -import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; +import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; /******************************************************************************* @@ -34,12 +34,12 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; *******************************************************************************/ public class ReportInput extends AbstractTableActionInput { - private String reportName; + private String reportName; + private QReportMetaData reportMetaData; + private Map inputValues; - private String filename; - private ReportFormat reportFormat; - private OutputStream reportOutputStream; + private ReportDestination reportDestination; @@ -111,66 +111,63 @@ public class ReportInput extends AbstractTableActionInput /******************************************************************************* - ** Getter for filename - ** + ** Getter for reportDestination *******************************************************************************/ - public String getFilename() + public ReportDestination getReportDestination() { - return filename; + return (this.reportDestination); } /******************************************************************************* - ** Setter for filename - ** + ** Setter for reportDestination *******************************************************************************/ - public void setFilename(String filename) + public void setReportDestination(ReportDestination reportDestination) { - this.filename = filename; + this.reportDestination = reportDestination; } /******************************************************************************* - ** Getter for reportFormat - ** + ** Fluent setter for reportDestination *******************************************************************************/ - public ReportFormat getReportFormat() + public ReportInput withReportDestination(ReportDestination reportDestination) { - return reportFormat; + this.reportDestination = reportDestination; + return (this); + } + + + /******************************************************************************* + ** Getter for reportMetaData + *******************************************************************************/ + public QReportMetaData getReportMetaData() + { + return (this.reportMetaData); } /******************************************************************************* - ** Setter for reportFormat - ** + ** Setter for reportMetaData *******************************************************************************/ - public void setReportFormat(ReportFormat reportFormat) + public void setReportMetaData(QReportMetaData reportMetaData) { - this.reportFormat = reportFormat; + this.reportMetaData = reportMetaData; } /******************************************************************************* - ** Getter for reportOutputStream - ** + ** Fluent setter for reportMetaData *******************************************************************************/ - public OutputStream getReportOutputStream() + public ReportInput withReportMetaData(QReportMetaData reportMetaData) { - return reportOutputStream; + this.reportMetaData = reportMetaData; + return (this); } - - /******************************************************************************* - ** Setter for reportOutputStream - ** - *******************************************************************************/ - public void setReportOutputStream(OutputStream reportOutputStream) - { - this.reportOutputStream = reportOutputStream; - } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/reports/ExecuteReportStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/reports/ExecuteReportStep.java index 8d71629e..fdfa381c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/reports/ExecuteReportStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/reports/ExecuteReportStep.java @@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.actions.reporting.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.metadata.reporting.QReportMetaData; @@ -66,8 +67,9 @@ public class ExecuteReportStep implements BackendStep { ReportInput reportInput = new ReportInput(); reportInput.setReportName(reportName); - reportInput.setReportFormat(ReportFormat.XLSX); // todo - variable - reportInput.setReportOutputStream(reportOutputStream); + reportInput.setReportDestination(new ReportDestination() + .withReportFormat(ReportFormat.XLSX) // todo - variable + .withReportOutputStream(reportOutputStream)); Map values = runBackendStepInput.getValues(); reportInput.setInputValues(values); diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/reporting/GenerateReportActionRDBMSTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/reporting/GenerateReportActionRDBMSTest.java index e49bc014..18890603 100644 --- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/reporting/GenerateReportActionRDBMSTest.java +++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/reporting/GenerateReportActionRDBMSTest.java @@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream; import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction; 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.tables.query.QCriteriaOperator; @@ -204,9 +205,12 @@ public class GenerateReportActionRDBMSTest extends RDBMSActionTest ReportInput reportInput = new ReportInput(); QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true)); reportInput.setReportName(TEST_REPORT); - reportInput.setReportFormat(ReportFormat.CSV); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - reportInput.setReportOutputStream(outputStream); + + reportInput.setReportDestination(new ReportDestination() + .withReportFormat(ReportFormat.CSV) + .withReportOutputStream(outputStream)); + new GenerateReportAction().execute(reportInput); return (outputStream.toString()); } diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 6b674b09..7c2a9487 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -77,6 +77,7 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput; 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.QInputSource; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; @@ -1492,10 +1493,11 @@ public class QJavalinImplementation setupSession(context, exportInput); exportInput.setTableName(tableName); - exportInput.setReportFormat(reportFormat); String filename = optionalFilename.orElse(tableName + "." + reportFormat.toString().toLowerCase(Locale.ROOT)); - exportInput.setFilename(filename); + exportInput.withReportDestination(new ReportDestination() + .withReportFormat(reportFormat) + .withFilename(filename)); Integer limit = QJavalinUtils.integerQueryParam(context, "limit"); exportInput.setLimit(limit); @@ -1526,7 +1528,7 @@ public class QJavalinImplementation UnsafeFunction preAction = (PipedOutputStream pos) -> { - exportInput.setReportOutputStream(pos); + exportInput.getReportDestination().setReportOutputStream(pos); ExportAction exportAction = new ExportAction(); exportAction.preExecute(exportInput); diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java index 941c9f24..cce70af5 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java @@ -60,6 +60,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState; import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile; 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.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.tables.insert.InsertInput; @@ -203,10 +204,12 @@ public class QJavalinProcessHandler QJavalinImplementation.setupSession(context, reportInput); PermissionsHelper.checkReportPermissionThrowing(reportInput, reportName); - reportInput.setReportFormat(reportFormat); reportInput.setReportName(reportName); reportInput.setInputValues(null); // todo! - reportInput.setFilename(filename); + + reportInput.setReportDestination(new ReportDestination() + .withReportFormat(reportFormat) + .withFilename(filename)); ////////////////////////////////////////////////////////////// // process the report's input fields, from the query string // @@ -239,7 +242,7 @@ public class QJavalinProcessHandler UnsafeFunction preAction = (PipedOutputStream pos) -> { - reportInput.setReportOutputStream(pos); + reportInput.getReportDestination().setReportOutputStream(pos); GenerateReportAction reportAction = new GenerateReportAction(); // any pre-action?? export uses this for "too many rows" checks... diff --git a/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 620433ab..b0583cdb 100644 --- a/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/qqq-middleware-picocli/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -60,6 +60,7 @@ 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.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.shared.mapping.AbstractQFieldMapping; import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping; @@ -618,9 +619,10 @@ public class QPicoCliImplementation ///////////////////////////////////////////// ExportInput exportInput = new ExportInput(); exportInput.setTableName(tableName); - exportInput.setReportFormat(reportFormat); - exportInput.setFilename(filename); - exportInput.setReportOutputStream(outputStream); + exportInput.setReportDestination(new ReportDestination() + .withReportFormat(reportFormat) + .withFilename(filename) + .withReportOutputStream(outputStream)); exportInput.setLimit(subParseResult.matchedOptionValue("limit", null)); exportInput.setQueryFilter(generateQueryFilter(subParseResult)); diff --git a/qqq-middleware-slack/src/main/java/com/kingsrook/qqq/slack/QSlackImplementation.java b/qqq-middleware-slack/src/main/java/com/kingsrook/qqq/slack/QSlackImplementation.java index 56ee591c..25185398 100644 --- a/qqq-middleware-slack/src/main/java/com/kingsrook/qqq/slack/QSlackImplementation.java +++ b/qqq-middleware-slack/src/main/java/com/kingsrook/qqq/slack/QSlackImplementation.java @@ -49,6 +49,7 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; 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.get.GetInput; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; @@ -429,8 +430,11 @@ public class QSlackImplementation ExportInput exportInput = new ExportInput(); exportInput.setLimit(1000); exportInput.setTableName(tableName); - exportInput.setReportFormat(ReportFormat.valueOf(format)); - exportInput.setReportOutputStream(baos); + + exportInput.setReportDestination(new ReportDestination() + .withReportFormat(ReportFormat.valueOf(format)) + .withReportOutputStream(baos)); + setupSession(context, exportInput); ExportOutput output = new ExportAction().execute(exportInput);