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 extends ExportStreamerInterface> streamerConstructor;
@@ -62,12 +63,13 @@ public enum ReportFormat
/*******************************************************************************
**
*******************************************************************************/
- ReportFormat(Integer maxRows, Integer maxCols, Supplier extends ExportStreamerInterface> streamerConstructor, String mimeType)
+ ReportFormat(Integer maxRows, Integer maxCols, Supplier extends ExportStreamerInterface> 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