From bc7db31fca1d26553ea77833832330e33db98a76 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 28 Mar 2024 14:04:52 -0500 Subject: [PATCH] CE-881 - Update render saved report to take in ReportFormat PossibleValue (new related enum); add first test on same process --- .../processes/QProcessCallbackFactory.java | 39 ++++++ .../model/actions/reporting/ReportFormat.java | 25 +++- .../ReportFormatPossibleValueEnum.java | 59 +++++++++ .../SavedReportsMetaDataProvider.java | 14 +-- .../RenderSavedReportExecuteStep.java | 14 ++- .../RenderSavedReportMetaDataProducer.java | 9 +- .../RenderSavedReportProcessTest.java | 114 ++++++++++++++++++ 7 files changed, 253 insertions(+), 21 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormatPossibleValueEnum.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportProcessTest.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/QProcessCallbackFactory.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/QProcessCallbackFactory.java index 977104c0..74d2a93d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/QProcessCallbackFactory.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/QProcessCallbackFactory.java @@ -26,8 +26,15 @@ import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.Map; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; +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.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.utils.StringUtils; /******************************************************************************* @@ -65,4 +72,36 @@ public class QProcessCallbackFactory }; } + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QProcessCallback forRecordEntity(QRecordEntity entity) + { + return forRecord(entity.toQRecord()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QProcessCallback forRecord(QRecord record) + { + String primaryKeyField = "id"; + if(StringUtils.hasContent(record.getTableName())) + { + primaryKeyField = QContext.getQInstance().getTable(record.getTableName()).getPrimaryKeyField(); + } + + Serializable primaryKeyValue = record.getValue(primaryKeyField); + if(primaryKeyValue == null) + { + throw (new QRuntimeException("Record did not have value in its priary key field [" + primaryKeyField + "]")); + } + + return (forFilter(new QQueryFilter().withCriteria(new QFilterCriteria(primaryKeyField, QCriteriaOperator.EQUALS, primaryKeyValue)))); + } + } 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 1718b00d..9806b1a6 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 @@ -39,21 +39,22 @@ import org.dhatim.fastexcel.Worksheet; *******************************************************************************/ public enum ReportFormat { - XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelPoiBasedStreamingExportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelPoiBasedStreamingExportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"), ///////////////////////////////////////////////////////////////////////// // 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"), + // XLSX(Worksheet.MAX_ROWS, Worksheet.MAX_COLS, ExcelFastexcelExportStreamer::new, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"), - JSON(null, null, JsonExportStreamer::new, "application/json"), - CSV(null, null, CsvExportStreamer::new, "text/csv"), - LIST_OF_MAPS(null, null, ListOfMapsExportStreamer::new, null); + JSON(null, null, JsonExportStreamer::new, "application/json", "json"), + CSV(null, null, CsvExportStreamer::new, "text/csv", "csv"), + LIST_OF_MAPS(null, null, ListOfMapsExportStreamer::new, null, null); private final Integer maxRows; private final Integer maxCols; private final String mimeType; + private final String extension; private final Supplier streamerConstructor; @@ -62,12 +63,13 @@ public enum ReportFormat /******************************************************************************* ** *******************************************************************************/ - ReportFormat(Integer maxRows, Integer maxCols, Supplier streamerConstructor, String mimeType) + ReportFormat(Integer maxRows, Integer maxCols, Supplier streamerConstructor, String mimeType, String extension) { this.maxRows = maxRows; this.maxCols = maxCols; this.mimeType = mimeType; this.streamerConstructor = streamerConstructor; + this.extension = extension; } @@ -134,4 +136,15 @@ public enum ReportFormat { return (streamerConstructor.get()); } + + + + /******************************************************************************* + ** Getter for extension + ** + *******************************************************************************/ + public String getExtension() + { + return extension; + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormatPossibleValueEnum.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormatPossibleValueEnum.java new file mode 100644 index 00000000..6364cc73 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/reporting/ReportFormatPossibleValueEnum.java @@ -0,0 +1,59 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.actions.reporting; + + +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum; + + +/******************************************************************************* + ** sub-set of ReportFormats to expose as possible-values in-apps + *******************************************************************************/ +public enum ReportFormatPossibleValueEnum implements PossibleValueEnum +{ + XLSX, + JSON, + CSV; + + public static final String NAME = "reportFormat"; + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String getPossibleValueId() + { + return name(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String getPossibleValueLabel() + { + return name(); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/SavedReportsMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/SavedReportsMetaDataProvider.java index e8354a6d..dfdb8c57 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/SavedReportsMetaDataProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedreports/SavedReportsMetaDataProvider.java @@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.savedreports; import java.util.List; import java.util.function.Consumer; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; @@ -49,7 +50,8 @@ public class SavedReportsMetaDataProvider public void defineAll(QInstance instance, String backendName, Consumer backendDetailEnricher) throws QException { instance.addTable(defineSavedReportTable(backendName, backendDetailEnricher)); - instance.addPossibleValueSource(defineSavedReportPossibleValueSource()); + instance.addPossibleValueSource(QPossibleValueSource.newForTable(SavedReport.TABLE_NAME)); + instance.addPossibleValueSource(QPossibleValueSource.newForEnum(ReportFormatPossibleValueEnum.NAME, ReportFormatPossibleValueEnum.values())); instance.addProcess(new RenderSavedReportMetaDataProducer().produce(instance)); } @@ -88,14 +90,4 @@ public class SavedReportsMetaDataProvider return (table); } - - - /******************************************************************************* - ** - *******************************************************************************/ - private QPossibleValueSource defineSavedReportPossibleValueSource() - { - return QPossibleValueSource.newForTable(SavedReport.TABLE_NAME); - } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java index 4c768b86..764813ba 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportExecuteStep.java @@ -32,6 +32,7 @@ import java.util.Map; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; 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; @@ -47,6 +48,9 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; *******************************************************************************/ public class RenderSavedReportExecuteStep implements BackendStep { + private static final QLogger LOG = QLogger.getLogger(RenderSavedReportExecuteStep.class); + + /******************************************************************************* ** @@ -56,8 +60,10 @@ public class RenderSavedReportExecuteStep implements BackendStep { try { + ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString("reportFormat")); + SavedReport savedReport = new SavedReport(runBackendStepInput.getRecords().get(0)); - File tmpFile = File.createTempFile("SavedReport" + savedReport.getId(), ".xlsx", new File("/tmp/")); + File tmpFile = File.createTempFile("SavedReport" + savedReport.getId(), "." + reportFormat.getExtension(), new File("/tmp/")); runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report"); @@ -68,7 +74,7 @@ public class RenderSavedReportExecuteStep implements BackendStep ReportInput reportInput = new ReportInput(); reportInput.setReportMetaData(reportMetaData); reportInput.setReportDestination(new ReportDestination() - .withReportFormat(ReportFormat.XLSX) // todo - variable + .withReportFormat(reportFormat) .withReportOutputStream(reportOutputStream)); Map values = runBackendStepInput.getValues(); @@ -78,12 +84,14 @@ public class RenderSavedReportExecuteStep implements BackendStep } String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport); - runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + ".xlsx"); + runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + "." + reportFormat.getExtension()); runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath()); } catch(Exception e) { // todo - render error screen? + + LOG.warn("Error rendering saved report", e); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportMetaDataProducer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportMetaDataProducer.java index 06f6648c..dfe1cd31 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportMetaDataProducer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportMetaDataProducer.java @@ -24,8 +24,11 @@ package com.kingsrook.qqq.backend.core.processes.implementations.savedreports; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType; @@ -45,6 +48,7 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf public static final String NAME = "renderSavedReport"; + /******************************************************************************* ** *******************************************************************************/ @@ -62,7 +66,10 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf .withCode(new QCodeReference(RenderSavedReportPreStep.class))) .addStep(new QFrontendStepMetaData() .withName("input") - .withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM))) + .withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM)) + .withFormField(new QFieldMetaData("reportFormat", QFieldType.STRING) + .withPossibleValueSourceName(ReportFormatPossibleValueEnum.NAME) + .withIsRequired(true))) .addStep(new QBackendStepMetaData() .withName("execute") .withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData() diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportProcessTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportProcessTest.java new file mode 100644 index 00000000..c4fe7d71 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/RenderSavedReportProcessTest.java @@ -0,0 +1,114 @@ +/* + * 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.processes.implementations.savedreports; + + +import java.io.File; +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.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.tables.insert.InsertInput; +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.savedreports.SavedReport; +import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.LocalMacDevUtils; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.apache.commons.io.FileUtils; +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.assertTrue; + + +/******************************************************************************* + ** Unit test for RenderSavedReportExecuteStep + *******************************************************************************/ +class RenderSavedReportProcessTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() throws Exception + { + new SavedReportsMetaDataProvider().defineAll(QContext.getQInstance(), TestUtils.MEMORY_BACKEND_NAME, null); + + String label = "Test Report"; + + QRecord savedReport = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(new SavedReport() + .withLabel(label) + .withTableName(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withColumnsJson(""" + { + "columns": + [ + {"name": "id"}, + {"name": "firstName"}, + {"name": "lastName"} + ] + } + """) + .withQueryFilterJson(JsonUtils.toJson(new QQueryFilter())) + )).getRecords().get(0); + + GenerateReportActionTest.insertPersonRecords(QContext.getQInstance()); + + RunProcessInput input = new RunProcessInput(); + input.setProcessName(RenderSavedReportMetaDataProducer.NAME); + input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP); + input.setCallback(QProcessCallbackFactory.forRecord(savedReport)); + input.addValue("reportFormat", ReportFormatPossibleValueEnum.CSV.getPossibleValueId()); + RunProcessOutput runProcessOutput = new RunProcessAction().execute(input); + + String downloadFileName = runProcessOutput.getValueString("downloadFileName"); + String serverFilePath = runProcessOutput.getValueString("serverFilePath"); + + assertThat(downloadFileName) + .startsWith(label + " - ") + .matches(".*\\d\\d\\d\\d-\\d\\d-\\d\\d-\\d\\d\\d\\d.*") + .endsWith(".csv"); + + File serverFile = new File(serverFilePath); + assertTrue(serverFile.exists()); + + List lines = FileUtils.readLines(serverFile); + assertEquals(""" + "Id","First Name","Last Name" + """.trim(), lines.get(0)); + assertEquals(""" + "1","Darin","Jonson" + """.trim(), lines.get(1)); + + LocalMacDevUtils.openFile(serverFilePath); + } + +} \ No newline at end of file