CE-881 - Update render saved report to take in ReportFormat PossibleValue (new related enum); add first test on same process

This commit is contained in:
2024-03-28 14:04:52 -05:00
parent addcebefa5
commit bc7db31fca
7 changed files with 253 additions and 21 deletions

View File

@ -26,8 +26,15 @@ import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; 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.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.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))));
}
} }

View File

@ -39,21 +39,22 @@ import org.dhatim.fastexcel.Worksheet;
*******************************************************************************/ *******************************************************************************/
public enum ReportFormat 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. // // 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"), JSON(null, null, JsonExportStreamer::new, "application/json", "json"),
CSV(null, null, CsvExportStreamer::new, "text/csv"), CSV(null, null, CsvExportStreamer::new, "text/csv", "csv"),
LIST_OF_MAPS(null, null, ListOfMapsExportStreamer::new, null); LIST_OF_MAPS(null, null, ListOfMapsExportStreamer::new, null, null);
private final Integer maxRows; private final Integer maxRows;
private final Integer maxCols; private final Integer maxCols;
private final String mimeType; private final String mimeType;
private final String extension;
private final Supplier<? extends ExportStreamerInterface> streamerConstructor; 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.maxRows = maxRows;
this.maxCols = maxCols; this.maxCols = maxCols;
this.mimeType = mimeType; this.mimeType = mimeType;
this.streamerConstructor = streamerConstructor; this.streamerConstructor = streamerConstructor;
this.extension = extension;
} }
@ -134,4 +136,15 @@ public enum ReportFormat
{ {
return (streamerConstructor.get()); return (streamerConstructor.get());
} }
/*******************************************************************************
** Getter for extension
**
*******************************************************************************/
public String getExtension()
{
return extension;
}
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String>
{
XLSX,
JSON,
CSV;
public static final String NAME = "reportFormat";
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return name();
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
return name();
}
}

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.savedreports;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; 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<QTableMetaData> backendDetailEnricher) throws QException public void defineAll(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{ {
instance.addTable(defineSavedReportTable(backendName, backendDetailEnricher)); 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)); instance.addProcess(new RenderSavedReportMetaDataProducer().produce(instance));
} }
@ -88,14 +90,4 @@ public class SavedReportsMetaDataProvider
return (table); return (table);
} }
/*******************************************************************************
**
*******************************************************************************/
private QPossibleValueSource defineSavedReportPossibleValueSource()
{
return QPossibleValueSource.newForTable(SavedReport.TABLE_NAME);
}
} }

View File

@ -32,6 +32,7 @@ import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction; import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportAction;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; 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.ReportDestination;
@ -47,6 +48,9 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*******************************************************************************/ *******************************************************************************/
public class RenderSavedReportExecuteStep implements BackendStep public class RenderSavedReportExecuteStep implements BackendStep
{ {
private static final QLogger LOG = QLogger.getLogger(RenderSavedReportExecuteStep.class);
/******************************************************************************* /*******************************************************************************
** **
@ -56,8 +60,10 @@ public class RenderSavedReportExecuteStep implements BackendStep
{ {
try try
{ {
ReportFormat reportFormat = ReportFormat.fromString(runBackendStepInput.getValueString("reportFormat"));
SavedReport savedReport = new SavedReport(runBackendStepInput.getRecords().get(0)); 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"); runBackendStepInput.getAsyncJobCallback().updateStatus("Generating Report");
@ -68,7 +74,7 @@ public class RenderSavedReportExecuteStep implements BackendStep
ReportInput reportInput = new ReportInput(); ReportInput reportInput = new ReportInput();
reportInput.setReportMetaData(reportMetaData); reportInput.setReportMetaData(reportMetaData);
reportInput.setReportDestination(new ReportDestination() reportInput.setReportDestination(new ReportDestination()
.withReportFormat(ReportFormat.XLSX) // todo - variable .withReportFormat(reportFormat)
.withReportOutputStream(reportOutputStream)); .withReportOutputStream(reportOutputStream));
Map<String, Serializable> values = runBackendStepInput.getValues(); Map<String, Serializable> values = runBackendStepInput.getValues();
@ -78,12 +84,14 @@ public class RenderSavedReportExecuteStep implements BackendStep
} }
String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport); String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, savedReport);
runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + ".xlsx"); runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + "." + reportFormat.getExtension());
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath()); runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
} }
catch(Exception e) catch(Exception e)
{ {
// todo - render error screen? // todo - render error screen?
LOG.warn("Error rendering saved report", e);
} }
} }

View File

@ -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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; 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.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType; 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"; public static final String NAME = "renderSavedReport";
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -62,7 +66,10 @@ public class RenderSavedReportMetaDataProducer implements MetaDataProducerInterf
.withCode(new QCodeReference(RenderSavedReportPreStep.class))) .withCode(new QCodeReference(RenderSavedReportPreStep.class)))
.addStep(new QFrontendStepMetaData() .addStep(new QFrontendStepMetaData()
.withName("input") .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() .addStep(new QBackendStepMetaData()
.withName("execute") .withName("execute")
.withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData() .withInputData(new QFunctionInputMetaData().withRecordListMetaData(new QRecordListMetaData()

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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);
}
}