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.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))));
}
}

View File

@ -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;
}
}

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.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<QTableMetaData> 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);
}
}

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.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<String, Serializable> 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);
}
}

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.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()

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);
}
}