CE-881 - Bridge code for RenderSavedReportProcess in qqq-middleware-api

This commit is contained in:
2024-03-29 08:46:41 -05:00
parent 98e9d1bf57
commit 52b64ffbc0
4 changed files with 343 additions and 1 deletions

View File

@ -0,0 +1,60 @@
/*
* 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.api.implementations.savedreports;
import com.kingsrook.qqq.api.model.metadata.processes.PreRunApiProcessCustomizer;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
/*******************************************************************************
** API-Customizer for the RenderSavedReport process
*******************************************************************************/
public class RenderSavedReportProcessApiCustomizer implements PreRunApiProcessCustomizer
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void preApiRun(RunProcessInput runProcessInput) throws QException
{
Integer reportId = runProcessInput.getValueInteger("reportId");
if(reportId != null)
{
QRecord record = new GetAction().executeForRecord(new GetInput(SavedReport.TABLE_NAME).withPrimaryKey(reportId));
if(record == null)
{
throw (new QNotFoundException("Report Id " + reportId + " was not found."));
}
runProcessInput.setCallback(QProcessCallbackFactory.forPrimaryKey("id", reportId));
}
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.api.implementations.savedreports;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData;
import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaDataContainer;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessCustomizers;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessInput;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessInputFieldsContainer;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessMetaData;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessMetaDataContainer;
import com.kingsrook.qqq.api.model.openapi.ExampleWithListValue;
import com.kingsrook.qqq.api.model.openapi.ExampleWithSingleValue;
import com.kingsrook.qqq.api.model.openapi.HttpMethod;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormatPossibleValueEnum;
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.processes.QProcessMetaData;
/*******************************************************************************
** Class that helps prepare the RenderSavedReport process for use in an API
*******************************************************************************/
public class RenderSavedReportProcessApiMetaDataEnricher
{
/*******************************************************************************
**
*******************************************************************************/
public static ApiProcessMetaData setupProcessForApi(QProcessMetaData process, String apiName, String initialApiVersion)
{
ApiProcessMetaDataContainer apiProcessMetaDataContainer = ApiProcessMetaDataContainer.ofOrWithNew(process);
ApiProcessInput input = new ApiProcessInput()
.withPathParams(new ApiProcessInputFieldsContainer()
.withField(new QFieldMetaData("reportId", QFieldType.INTEGER)
.withIsRequired(true)
.withSupplementalMetaData(newDefaultApiFieldMetaData("Saved Report Id", 1701))))
.withQueryStringParams(new ApiProcessInputFieldsContainer()
.withField(new QFieldMetaData("reportFormat", QFieldType.STRING)
.withIsRequired(true)
.withPossibleValueSourceName(ReportFormatPossibleValueEnum.NAME)
.withSupplementalMetaData(newDefaultApiFieldMetaData("Requested file format", "XLSX"))));
// todo (when implemented) - probably a JSON doc w/ input values.
RenderSavedReportProcessApiProcessOutput output = new RenderSavedReportProcessApiProcessOutput();
ApiProcessMetaData apiProcessMetaData = new ApiProcessMetaData()
.withInitialVersion(initialApiVersion)
.withCustomizer(ApiProcessCustomizers.PRE_RUN.getRole(), new QCodeReference(RenderSavedReportProcessApiCustomizer.class))
.withAsyncMode(ApiProcessMetaData.AsyncMode.OPTIONAL)
.withMethod(HttpMethod.GET)
.withInput(input)
.withOutput(output);
apiProcessMetaDataContainer.withApiProcessMetaData(apiName, apiProcessMetaData);
return (apiProcessMetaData);
}
/*******************************************************************************
** todo - move to higher-level utility
*******************************************************************************/
public static ApiFieldMetaDataContainer newDefaultApiFieldMetaData(String description, Serializable example)
{
ApiFieldMetaData defaultApiFieldMetaData = new ApiFieldMetaData().withDescription(description);
ApiFieldMetaDataContainer apiFieldMetaDataContainer = new ApiFieldMetaDataContainer().withDefaultApiFieldMetaData(defaultApiFieldMetaData);
if(example instanceof List)
{
defaultApiFieldMetaData.withExample(new ExampleWithListValue().withValue((List<String>) example));
}
else
{
defaultApiFieldMetaData.withExample(new ExampleWithSingleValue().withValue(example));
}
return (apiFieldMetaDataContainer);
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.api.implementations.savedreports;
import java.io.File;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Map;
import com.kingsrook.qqq.api.model.actions.HttpApiResponse;
import com.kingsrook.qqq.api.model.metadata.processes.ApiProcessOutputInterface;
import com.kingsrook.qqq.api.model.openapi.Content;
import com.kingsrook.qqq.api.model.openapi.Response;
import com.kingsrook.qqq.api.model.openapi.Schema;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.ReportFormat;
import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.http.HttpStatus;
/*******************************************************************************
** api process output specifier for the RenderSavedReport process
*******************************************************************************/
public class RenderSavedReportProcessApiProcessOutput implements ApiProcessOutputInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public Serializable getOutputForProcess(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput) throws QException
{
try
{
ReportFormat reportFormat = ReportFormat.fromString(runProcessOutput.getValueString("reportFormat"));
String filePath = runProcessOutput.getValueString("serverFilePath");
File file = new File(filePath);
if(reportFormat.getIsBinary())
{
return FileUtils.readFileToByteArray(file);
}
else
{
return FileUtils.readFileToString(file, Charset.defaultCharset());
}
}
catch(Exception e)
{
throw new QException("Error streaming report contents", e);
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void customizeHttpApiResponse(HttpApiResponse httpApiResponse, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput) throws QException
{
/////////////////////////////////////////////////////////////////////////////////////////////
// we don't need anyone else to format our response - assume that we've done so ourselves. //
/////////////////////////////////////////////////////////////////////////////////////////////
httpApiResponse.setNeedsFormattedAsJson(false);
ReportFormat reportFormat = ReportFormat.fromString(runProcessOutput.getValueString("reportFormat"));
httpApiResponse.setContentType(reportFormat.getMimeType());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public HttpStatus.Code getSuccessStatusCode(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput)
{
return (HttpStatus.Code.OK);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Map<Integer, Response> getSpecResponses(String apiName)
{
return Map.of(HttpStatus.Code.OK.getCode(), new Response()
.withDescription("Report contents in the requested format.")
.withContent(Map.of(
ReportFormat.JSON.getMimeType(), new Content()
.withSchema(new Schema()
.withDescription("JSON Report contents")
.withExample("""
[
{"id": 1, "name": "James"},
{"id": 2, "name": "Jean-Luc"}
]
""")
.withType("string")
.withFormat("text")),
ReportFormat.CSV.getMimeType(), new Content()
.withSchema(new Schema()
.withDescription("CSV Report contents")
.withExample("""
"id","name"
1,"James"
2,"Jean-Luc"
""")
.withType("string")
.withFormat("text")),
ReportFormat.XLSX.getMimeType(), new Content()
.withSchema(new Schema()
.withDescription("Excel Report contents")
.withType("string")
.withFormat("binary"))
))
);
}
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.api;
import java.util.List;
import java.util.function.Consumer;
import com.kingsrook.qqq.api.implementations.savedreports.RenderSavedReportProcessApiMetaDataEnricher;
import com.kingsrook.qqq.api.model.APIVersion;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
@ -45,6 +46,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
@ -71,7 +73,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMeta
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
import com.kingsrook.qqq.backend.core.model.statusmessages.SystemErrorStatusMessage;
@ -79,6 +84,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.Mem
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaUpdateStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.savedreports.RenderSavedReportMetaDataProducer;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -112,7 +118,7 @@ public class TestUtils
/*******************************************************************************
**
*******************************************************************************/
public static QInstance defineInstance()
public static QInstance defineInstance() throws QException
{
QInstance qInstance = new QInstance();
@ -133,6 +139,8 @@ public class TestUtils
qInstance.setAuthentication(new Auth0AuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS).withName("anonymous"));
addSavedReports(qInstance);
qInstance.withSupplementalMetaData(new ApiInstanceMetaDataContainer()
.withApiInstanceMetaData(new ApiInstanceMetaData()
.withName(API_NAME)
@ -161,6 +169,18 @@ public class TestUtils
/*******************************************************************************
**
*******************************************************************************/
private static void addSavedReports(QInstance qInstance) throws QException
{
qInstance.add(TablesPossibleValueSourceMetaDataProvider.defineTablesPossibleValueSource(qInstance));
new SavedReportsMetaDataProvider().defineAll(qInstance, MEMORY_BACKEND_NAME, null);
RenderSavedReportProcessApiMetaDataEnricher.setupProcessForApi(qInstance.getProcess(RenderSavedReportMetaDataProducer.NAME), API_NAME, V2022_Q4);
}
/*******************************************************************************
**
*******************************************************************************/
@ -531,6 +551,19 @@ public class TestUtils
}
/*******************************************************************************
**
*******************************************************************************/
public static Integer insertSavedReport(SavedReport savedReport) throws QException
{
InsertInput insertInput = new InsertInput();
insertInput.setTableName(SavedReport.TABLE_NAME);
insertInput.setRecords(List.of(savedReport.toQRecord()));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
return insertOutput.getRecords().get(0).getValueInteger("id");
}
/*******************************************************************************
**