Add RunReportForRecordProcess; 1st version of AbstractProcessMetaDataBuilder

This commit is contained in:
2022-11-03 10:24:59 -05:00
parent 3c04841f73
commit ab0b38dd82
16 changed files with 1154 additions and 26 deletions

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.actions.dashboard; package com.kingsrook.qqq.backend.core.actions.dashboard;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
@ -40,6 +41,8 @@ public class RenderWidgetAction
*******************************************************************************/ *******************************************************************************/
public RenderWidgetOutput execute(RenderWidgetInput input) throws QException public RenderWidgetOutput execute(RenderWidgetInput input) throws QException
{ {
ActionHelper.validateSession(input);
AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, input.getWidgetMetaData().getCodeReference()); AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, input.getWidgetMetaData().getCodeReference());
return (widgetRenderer.render(input)); return (widgetRenderer.render(input));
} }

View File

@ -31,13 +31,16 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler; import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers; import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
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.values.QCustomPossibleValueProvider; import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
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.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
@ -50,6 +53,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier; import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
@ -118,6 +123,7 @@ public class QInstanceValidator
validateAutomationProviders(qInstance); validateAutomationProviders(qInstance);
validateTables(qInstance); validateTables(qInstance);
validateProcesses(qInstance); validateProcesses(qInstance);
validateReports(qInstance);
validateApps(qInstance); validateApps(qInstance);
validatePossibleValueSources(qInstance); validatePossibleValueSources(qInstance);
validateQueuesAndProviders(qInstance); validateQueuesAndProviders(qInstance);
@ -186,6 +192,8 @@ public class QInstanceValidator
qInstance.getBackends().forEach((backendName, backend) -> qInstance.getBackends().forEach((backendName, backend) ->
{ {
assertCondition(Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + "."); assertCondition(Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
backend.performValidation(this);
}); });
} }
} }
@ -258,6 +266,8 @@ public class QInstanceValidator
////////////////////////////////////////// //////////////////////////////////////////
Set<String> fieldNamesInSections = new HashSet<>(); Set<String> fieldNamesInSections = new HashSet<>();
QFieldSection tier1Section = null; QFieldSection tier1Section = null;
Set<String> usedSectionNames = new HashSet<>();
Set<String> usedSectionLabels = new HashSet<>();
if(table.getSections() != null) if(table.getSections() != null)
{ {
for(QFieldSection section : table.getSections()) for(QFieldSection section : table.getSections())
@ -268,6 +278,12 @@ public class QInstanceValidator
assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1"); assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
tier1Section = section; tier1Section = section;
} }
assertCondition(!usedSectionNames.contains(section.getName()), "Table " + tableName + " has more than 1 section named " + section.getName());
usedSectionNames.add(section.getName());
assertCondition(!usedSectionLabels.contains(section.getLabel()), "Table " + tableName + " has more than 1 section labeled " + section.getLabel());
usedSectionLabels.add(section.getLabel());
} }
} }
@ -716,6 +732,133 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void validateReports(QInstance qInstance)
{
if(CollectionUtils.nullSafeHasContents(qInstance.getReports()))
{
qInstance.getReports().forEach((reportName, report) ->
{
assertCondition(Objects.equals(reportName, report.getName()), "Inconsistent naming for report: " + reportName + "/" + report.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, report);
////////////////////////////////////////
// validate dataSources in the report //
////////////////////////////////////////
Set<String> usedDataSourceNames = new HashSet<>();
if(assertCondition(CollectionUtils.nullSafeHasContents(report.getDataSources()), "At least 1 data source must be defined in report " + reportName + "."))
{
int index = 0;
for(QReportDataSource dataSource : report.getDataSources())
{
assertCondition(StringUtils.hasContent(dataSource.getName()), "Missing name for a dataSource at index " + index + " in report " + reportName);
index++;
assertCondition(!usedDataSourceNames.contains(dataSource.getName()), "More than one dataSource with name " + dataSource.getName() + " in report " + reportName);
usedDataSourceNames.add(dataSource.getName());
String dataSourceErrorPrefix = "Report " + reportName + " data source " + dataSource.getName() + " ";
if(StringUtils.hasContent(dataSource.getSourceTable()))
{
assertCondition(dataSource.getStaticDataSupplier() == null, dataSourceErrorPrefix + "has both a sourceTable and a staticDataSupplier (exactly 1 is required).");
if(assertCondition(qInstance.getTable(dataSource.getSourceTable()) != null, dataSourceErrorPrefix + "source table " + dataSource.getSourceTable() + " is not a table in this instance."))
{
if(dataSource.getQueryFilter() != null)
{
validateQueryFilter("In " + dataSourceErrorPrefix + "query filter - ", qInstance.getTable(dataSource.getSourceTable()), dataSource.getQueryFilter());
}
}
}
else if(dataSource.getStaticDataSupplier() != null)
{
validateSimpleCodeReference(dataSourceErrorPrefix, dataSource.getStaticDataSupplier(), Supplier.class);
}
else
{
errors.add(dataSourceErrorPrefix + "does not have a sourceTable or a staticDataSupplier (exactly 1 is required).");
}
}
}
////////////////////////////////////////
// validate dataSources in the report //
////////////////////////////////////////
if(assertCondition(CollectionUtils.nullSafeHasContents(report.getViews()), "At least 1 view must be defined in report " + reportName + "."))
{
int index = 0;
Set<String> usedViewNames = new HashSet<>();
for(QReportView view : report.getViews())
{
assertCondition(StringUtils.hasContent(view.getName()), "Missing name for a view at index " + index + " in report " + reportName);
index++;
assertCondition(!usedViewNames.contains(view.getName()), "More than one view with name " + view.getName() + " in report " + reportName);
usedViewNames.add(view.getName());
String viewErrorPrefix = "Report " + reportName + " view " + view.getName() + " ";
assertCondition(view.getType() != null, viewErrorPrefix + " is missing its type.");
if(assertCondition(StringUtils.hasContent(view.getDataSourceName()), viewErrorPrefix + " is missing a dataSourceName"))
{
assertCondition(usedDataSourceNames.contains(view.getDataSourceName()), viewErrorPrefix + " has an unrecognized dataSourceName: " + view.getDataSourceName());
}
if(StringUtils.hasContent(view.getVarianceDataSourceName()))
{
assertCondition(usedDataSourceNames.contains(view.getVarianceDataSourceName()), viewErrorPrefix + " has an unrecognized varianceDataSourceName: " + view.getVarianceDataSourceName());
}
// actually, this is okay if there's a customizer, so...
assertCondition(CollectionUtils.nullSafeHasContents(view.getColumns()), viewErrorPrefix + " does not have any columns.");
// todo - all these too...
// view.getPivotFields();
// view.getViewCustomizer(); // validate code ref
// view.getRecordTransformStep(); // validate code ref
// view.getOrderByFields(); // make sure valid field names?
// view.getIncludePivotSubTotals(); // only for pivot type
// view.getTitleFormat(); view.getTitleFields(); // validate these match?
}
}
});
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateQueryFilter(String context, QTableMetaData table, QQueryFilter queryFilter)
{
for(QFilterCriteria criterion : CollectionUtils.nonNullList(queryFilter.getCriteria()))
{
if(assertCondition(StringUtils.hasContent(criterion.getFieldName()), context + "Missing fieldName for a criteria"))
{
assertNoException(() -> table.getField(criterion.getFieldName()), context + "Criteria fieldName " + criterion.getFieldName() + " is not a field in this table.");
}
assertCondition(criterion.getOperator() != null, context + "Missing operator for a criteria on fieldName " + criterion.getFieldName());
assertCondition(criterion.getValues() != null, context + "Missing values for a criteria on fieldName " + criterion.getFieldName()); // todo - what about ops w/ no value (BLANK)
}
for(QFilterOrderBy orderBy : CollectionUtils.nonNullList(queryFilter.getOrderBys()))
{
if(assertCondition(StringUtils.hasContent(orderBy.getFieldName()), context + "Missing fieldName for an orderBy"))
{
assertNoException(() -> table.getField(orderBy.getFieldName()), context + "OrderBy fieldName " + orderBy.getFieldName() + " is not a field in this table.");
}
}
for(QQueryFilter subFilter : CollectionUtils.nonNullList(queryFilter.getSubFilters()))
{
validateQueryFilter(context, table, subFilter);
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -978,7 +1121,7 @@ public class QInstanceValidator
** But if it's false, add the provided message to the list of errors (and return false, ** But if it's false, add the provided message to the list of errors (and return false,
** e.g., in case you need to stop evaluating rules to avoid exceptions). ** e.g., in case you need to stop evaluating rules to avoid exceptions).
*******************************************************************************/ *******************************************************************************/
private boolean assertCondition(boolean condition, String message) public boolean assertCondition(boolean condition, String message)
{ {
if(!condition) if(!condition)
{ {
@ -1035,4 +1178,15 @@ public class QInstanceValidator
LOG.info("Validation warning: " + message); LOG.info("Validation warning: " + message);
} }
} }
/*******************************************************************************
** Getter for errors
**
*******************************************************************************/
public List<String> getErrors()
{
return errors;
}
} }

View File

@ -26,6 +26,7 @@ import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer; import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
@ -321,4 +322,15 @@ public class QBackendMetaData
return (this); return (this);
} }
/*******************************************************************************
**
*******************************************************************************/
public void performValidation(QInstanceValidator qInstanceValidator)
{
////////////////////////
// noop in base class //
////////////////////////
}
} }

View File

@ -0,0 +1,68 @@
/*
* 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.metadata.processes;
import java.io.Serializable;
/*******************************************************************************
**
*******************************************************************************/
public class AbstractProcessMetaDataBuilder
{
protected QProcessMetaData processMetaData;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public AbstractProcessMetaDataBuilder(QProcessMetaData processMetaData)
{
this.processMetaData = processMetaData;
}
/*******************************************************************************
** Getter for processMetaData
**
*******************************************************************************/
public QProcessMetaData getProcessMetaData()
{
return processMetaData;
}
/*******************************************************************************
**
*******************************************************************************/
protected void setInputFieldDefaultValue(String fieldName, Serializable value)
{
processMetaData.getInputFields().stream()
.filter(f -> f.getName().equals(fieldName)).findFirst()
.ifPresent(f -> f.setDefaultValue(value));
}
}

View File

@ -90,10 +90,17 @@ public class StreamedETLExecuteStep extends BaseStreamedETLStep implements Backe
updateRecordsWithDisplayValuesAndPossibleValues(runBackendStepInput, loadedRecordList); updateRecordsWithDisplayValuesAndPossibleValues(runBackendStepInput, loadedRecordList);
runBackendStepOutput.setRecords(loadedRecordList); runBackendStepOutput.setRecords(loadedRecordList);
//////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// get the process summary from the ... transform step? the load step? each knows some... todo? // // get the process summary from the load step, if it's a summary-provider -- else, use the transform step (which is always a provider) //
//////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(loadStep instanceof ProcessSummaryProviderInterface provider)
{
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, provider.doGetProcessSummary(runBackendStepOutput, true));
}
else
{
runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.doGetProcessSummary(runBackendStepOutput, true)); runBackendStepOutput.addValue(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY, transformStep.doGetProcessSummary(runBackendStepOutput, true));
}
transformStep.postRun(runBackendStepInput, runBackendStepOutput); transformStep.postRun(runBackendStepInput, runBackendStepOutput);
loadStep.postRun(runBackendStepInput, runBackendStepOutput); loadStep.postRun(runBackendStepInput, runBackendStepOutput);

View File

@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwit
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
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.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; 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.AbstractProcessMetaDataBuilder;
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;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
@ -131,8 +136,8 @@ public class StreamedETLWithFrontendProcess
.withField(new QFieldMetaData(FIELD_DESTINATION_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DESTINATION_TABLE))) .withField(new QFieldMetaData(FIELD_DESTINATION_TABLE, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DESTINATION_TABLE)))
.withField(new QFieldMetaData(FIELD_SUPPORTS_FULL_VALIDATION, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_SUPPORTS_FULL_VALIDATION, true))) .withField(new QFieldMetaData(FIELD_SUPPORTS_FULL_VALIDATION, QFieldType.BOOLEAN).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_SUPPORTS_FULL_VALIDATION, true)))
.withField(new QFieldMetaData(FIELD_DEFAULT_QUERY_FILTER, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DEFAULT_QUERY_FILTER))) .withField(new QFieldMetaData(FIELD_DEFAULT_QUERY_FILTER, QFieldType.STRING).withDefaultValue(defaultFieldValues.get(FIELD_DEFAULT_QUERY_FILTER)))
.withField(new QFieldMetaData(FIELD_EXTRACT_CODE, QFieldType.STRING).withDefaultValue(new QCodeReference(extractStepClass))) .withField(new QFieldMetaData(FIELD_EXTRACT_CODE, QFieldType.STRING).withDefaultValue(extractStepClass == null ? null : new QCodeReference(extractStepClass)))
.withField(new QFieldMetaData(FIELD_TRANSFORM_CODE, QFieldType.STRING).withDefaultValue(new QCodeReference(transformStepClass))) .withField(new QFieldMetaData(FIELD_TRANSFORM_CODE, QFieldType.STRING).withDefaultValue(transformStepClass == null ? null : new QCodeReference(transformStepClass)))
.withField(new QFieldMetaData(FIELD_PREVIEW_MESSAGE, QFieldType.STRING).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_PREVIEW_MESSAGE, DEFAULT_PREVIEW_MESSAGE_FOR_INSERT))) .withField(new QFieldMetaData(FIELD_PREVIEW_MESSAGE, QFieldType.STRING).withDefaultValue(defaultFieldValues.getOrDefault(FIELD_PREVIEW_MESSAGE, DEFAULT_PREVIEW_MESSAGE_FOR_INSERT)))
); );
@ -153,7 +158,7 @@ public class StreamedETLWithFrontendProcess
.withName(STEP_NAME_EXECUTE) .withName(STEP_NAME_EXECUTE)
.withCode(new QCodeReference(StreamedETLExecuteStep.class)) .withCode(new QCodeReference(StreamedETLExecuteStep.class))
.withInputData(new QFunctionInputMetaData() .withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(FIELD_LOAD_CODE, QFieldType.STRING).withDefaultValue(new QCodeReference(loadStepClass)))) .withField(new QFieldMetaData(FIELD_LOAD_CODE, QFieldType.STRING).withDefaultValue(loadStepClass == null ? null : new QCodeReference(loadStepClass))))
.withOutputMetaData(new QFunctionOutputMetaData() .withOutputMetaData(new QFunctionOutputMetaData()
.withField(new QFieldMetaData(FIELD_PROCESS_SUMMARY, QFieldType.STRING)) .withField(new QFieldMetaData(FIELD_PROCESS_SUMMARY, QFieldType.STRING))
); );
@ -169,4 +174,204 @@ public class StreamedETLWithFrontendProcess
.addStep(executeStep) .addStep(executeStep)
.addStep(resultStep); .addStep(resultStep);
} }
/*******************************************************************************
**
*******************************************************************************/
public static Builder processMetaDataBuilder()
{
return (new Builder(defineProcessMetaData(null, null, null, Collections.emptyMap())));
}
/*******************************************************************************
**
*******************************************************************************/
public static class Builder extends AbstractProcessMetaDataBuilder
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Builder(QProcessMetaData processMetaData)
{
super(processMetaData);
}
/*******************************************************************************
** Fluent setter for extractStepClass
**
*******************************************************************************/
public Builder withExtractStepClass(Class<? extends AbstractExtractStep> extractStepClass)
{
setInputFieldDefaultValue(FIELD_EXTRACT_CODE, new QCodeReference(extractStepClass));
return (this);
}
/*******************************************************************************
** Fluent setter for transformStepClass
**
*******************************************************************************/
public Builder withTransformStepClass(Class<? extends AbstractTransformStep> transformStepClass)
{
setInputFieldDefaultValue(FIELD_TRANSFORM_CODE, new QCodeReference(transformStepClass));
return (this);
}
/*******************************************************************************
** Fluent setter for loadStepClass
**
*******************************************************************************/
public Builder withLoadStepClass(Class<? extends AbstractLoadStep> loadStepClass)
{
setInputFieldDefaultValue(FIELD_LOAD_CODE, new QCodeReference(loadStepClass));
return (this);
}
/*******************************************************************************
** Fluent setter for sourceTable
**
*******************************************************************************/
public Builder withSourceTable(String sourceTable)
{
setInputFieldDefaultValue(FIELD_SOURCE_TABLE, sourceTable);
return (this);
}
/*******************************************************************************
** Fluent setter for destinationTable
**
*******************************************************************************/
public Builder withDestinationTable(String destinationTable)
{
setInputFieldDefaultValue(FIELD_DESTINATION_TABLE, destinationTable);
return (this);
}
/*******************************************************************************
** Fluent setter for supportsFullValidation
**
*******************************************************************************/
public Builder withSupportsFullValidation(Boolean supportsFullValidation)
{
setInputFieldDefaultValue(FIELD_SUPPORTS_FULL_VALIDATION, supportsFullValidation);
return (this);
}
/*******************************************************************************
** Fluent setter for doFullValidation
**
*******************************************************************************/
public Builder withDoFullValidation(Boolean doFullValidation)
{
setInputFieldDefaultValue(FIELD_DO_FULL_VALIDATION, doFullValidation);
return (this);
}
/*******************************************************************************
** Fluent setter for defaultQueryFilter
**
*******************************************************************************/
public Builder withDefaultQueryFilter(QQueryFilter defaultQueryFilter)
{
setInputFieldDefaultValue(FIELD_DEFAULT_QUERY_FILTER, defaultQueryFilter);
return (this);
}
/*******************************************************************************
** Fluent setter for previewMessage
**
*******************************************************************************/
public Builder withPreviewMessage(String previewMessage)
{
setInputFieldDefaultValue(FIELD_PREVIEW_MESSAGE, previewMessage);
return (this);
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public Builder withName(String name)
{
processMetaData.setName(name);
return (this);
}
/*******************************************************************************
** Fluent setter for label
**
*******************************************************************************/
public Builder withLabel(String name)
{
processMetaData.setLabel(name);
return (this);
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public Builder withTableName(String tableName)
{
processMetaData.setTableName(tableName);
return (this);
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public Builder withIcon(QIcon icon)
{
processMetaData.setIcon(icon);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withReviewStepRecordFields(List<QFieldMetaData> fieldList)
{
QFrontendStepMetaData reviewStep = processMetaData.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW);
for(QFieldMetaData fieldMetaData : fieldList)
{
reviewStep.withRecordListField(fieldMetaData);
}
return (this);
}
}
} }

View File

@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutp
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/******************************************************************************* /*******************************************************************************
@ -46,6 +47,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
*******************************************************************************/ *******************************************************************************/
public class ExecuteReportStep implements BackendStep public class ExecuteReportStep implements BackendStep
{ {
/*******************************************************************************
**
*******************************************************************************/
@Override @Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
@ -70,10 +75,9 @@ public class ExecuteReportStep implements BackendStep
new GenerateReportAction().execute(reportInput); new GenerateReportAction().execute(reportInput);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm").withZone(ZoneId.systemDefault()); String downloadFileBaseName = getDownloadFileBaseName(runBackendStepInput, report);
String datePart = formatter.format(Instant.now());
runBackendStepOutput.addValue("downloadFileName", report.getLabel() + " " + datePart + ".xlsx"); runBackendStepOutput.addValue("downloadFileName", downloadFileBaseName + ".xlsx");
runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath()); runBackendStepOutput.addValue("serverFilePath", tmpFile.getCanonicalPath());
} }
} }
@ -82,4 +86,26 @@ public class ExecuteReportStep implements BackendStep
throw (new QException("Error running report", e)); throw (new QException("Error running report", e));
} }
} }
/*******************************************************************************
**
*******************************************************************************/
private String getDownloadFileBaseName(RunBackendStepInput runBackendStepInput, QReportMetaData report)
{
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmm").withZone(ZoneId.systemDefault());
String datePart = formatter.format(Instant.now());
String downloadFileBaseName = runBackendStepInput.getValueString("downloadFileBaseName");
if(!StringUtils.hasContent(downloadFileBaseName))
{
downloadFileBaseName = report.getLabel();
}
downloadFileBaseName = downloadFileBaseName.replaceAll("/", "-");
return (downloadFileBaseName + " - " + datePart);
}
} }

View File

@ -0,0 +1,124 @@
/*
* 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.processes.implementations.reports;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
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.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Version of PrepareReportStep for a report that runs off a single record.
*******************************************************************************/
public class PrepareReportForRecordStep extends PrepareReportStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
super.run(runBackendStepInput, runBackendStepOutput);
//////////////////////////////////////////////////////////////////////////////////
// look for the recordId having been posted to the process - error if not found //
//////////////////////////////////////////////////////////////////////////////////
Serializable recordId = null;
if("recordIds".equals(runBackendStepInput.getValueString("recordsParam")))
{
String recordIdsString = runBackendStepInput.getValueString("recordIds");
String[] recordIdsArray = recordIdsString.split(",");
if(recordIdsArray.length != 1)
{
throw (new QUserFacingException("Exactly 1 record must be selected as input to this report."));
}
recordId = recordIdsArray[0];
}
else
{
throw (new QUserFacingException("No record was selected as input to this report."));
}
/////////////////////////////////////////////////////////////////////////////////////////////
// look for the recordI input field on the process - put the input recordId in that field. //
// then remove that input field from the process's inputFieldList //
/////////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("unchecked")
ArrayList<QFieldMetaData> inputFieldList = (ArrayList<QFieldMetaData>) runBackendStepOutput.getValue("inputFieldList");
if(CollectionUtils.nullSafeHasContents(inputFieldList))
{
Iterator<QFieldMetaData> inputFieldListIterator = inputFieldList.iterator();
while(inputFieldListIterator.hasNext())
{
QFieldMetaData fieldMetaData = inputFieldListIterator.next();
if(fieldMetaData.getName().equals(RunReportForRecordProcess.FIELD_RECORD_ID))
{
runBackendStepOutput.addValue(RunReportForRecordProcess.FIELD_RECORD_ID, recordId);
inputFieldListIterator.remove();
runBackendStepOutput.addValue("inputFieldList", inputFieldList);
break;
}
}
}
GetInput getInput = new GetInput(runBackendStepInput.getInstance());
getInput.setSession(runBackendStepInput.getSession());
getInput.setTableName(runBackendStepInput.getTableName());
getInput.setPrimaryKey(recordId);
getInput.setShouldGenerateDisplayValues(true);
GetOutput getOutput = new GetAction().execute(getInput);
QRecord record = getOutput.getRecord();
if(record == null)
{
throw (new QUserFacingException("The selected record for the report was not found."));
}
String reportName = runBackendStepInput.getValueString("reportName");
QReportMetaData report = runBackendStepInput.getInstance().getReport(reportName);
// runBackendStepOutput.addValue("downloadFileBaseName", runBackendStepInput.getTable().getLabel() + " " + record.getRecordLabel());
runBackendStepOutput.addValue("downloadFileBaseName", report.getLabel() + " - " + record.getRecordLabel());
/////////////////////////////////////////////////////////////////////////////////////
// if there are no more input fields, then remove the INPUT step from the process. //
/////////////////////////////////////////////////////////////////////////////////////
inputFieldList = (ArrayList<QFieldMetaData>) runBackendStepOutput.getValue("inputFieldList");
if(!CollectionUtils.nullSafeHasContents(inputFieldList))
{
removeInputStepFromProcess(runBackendStepOutput);
}
}
}

View File

@ -43,6 +43,10 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*******************************************************************************/ *******************************************************************************/
public class PrepareReportStep implements BackendStep public class PrepareReportStep implements BackendStep
{ {
/*******************************************************************************
**
*******************************************************************************/
@Override @Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
@ -67,6 +71,17 @@ public class PrepareReportStep implements BackendStep
runBackendStepOutput.addValue("inputFieldList", inputFieldList); runBackendStepOutput.addValue("inputFieldList", inputFieldList);
} }
else else
{
removeInputStepFromProcess(runBackendStepOutput);
}
}
/*******************************************************************************
**
*******************************************************************************/
protected void removeInputStepFromProcess(RunBackendStepOutput runBackendStepOutput)
{ {
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
// no input? re-route the process to skip the input screen // // no input? re-route the process to skip the input screen //
@ -75,5 +90,4 @@ public class PrepareReportStep implements BackendStep
stepList.removeIf(s -> s.equals(BasicRunReportProcess.STEP_NAME_INPUT)); stepList.removeIf(s -> s.equals(BasicRunReportProcess.STEP_NAME_INPUT));
runBackendStepOutput.getProcessState().setStepList(stepList); runBackendStepOutput.getProcessState().setStepList(stepList);
} }
}
} }

View File

@ -0,0 +1,167 @@
/*
* 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.processes.implementations.reports;
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.AbstractProcessMetaDataBuilder;
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.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
/*******************************************************************************
** Definition for Basic process to run a report.
*******************************************************************************/
public class RunReportForRecordProcess
{
public static final String PROCESS_NAME = "reports.forRecord";
public static final String STEP_NAME_PREPARE = "prepare";
public static final String STEP_NAME_INPUT = "input";
public static final String STEP_NAME_EXECUTE = "execute";
public static final String STEP_NAME_ACCESS = "accessReport";
public static final String FIELD_REPORT_NAME = "reportName";
public static final String FIELD_RECORD_ID = "recordId";
/*******************************************************************************
**
*******************************************************************************/
public static Builder processMetaDataBuilder()
{
return (new Builder(defineProcessMetaData()));
}
/*******************************************************************************
**
*******************************************************************************/
private static QProcessMetaData defineProcessMetaData()
{
QStepMetaData prepareStep = new QBackendStepMetaData()
.withName(STEP_NAME_PREPARE)
.withCode(new QCodeReference(PrepareReportForRecordStep.class))
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(FIELD_REPORT_NAME, QFieldType.STRING))
.withField(new QFieldMetaData(FIELD_RECORD_ID, QFieldType.STRING)));
QStepMetaData inputStep = new QFrontendStepMetaData()
.withName(STEP_NAME_INPUT)
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM));
QStepMetaData executeStep = new QBackendStepMetaData()
.withName(STEP_NAME_EXECUTE)
.withCode(new QCodeReference(ExecuteReportStep.class))
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData(FIELD_REPORT_NAME, QFieldType.STRING)));
QStepMetaData accessStep = new QFrontendStepMetaData()
.withName(STEP_NAME_ACCESS)
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.DOWNLOAD_FORM));
// .withViewField(new QFieldMetaData("outputFile", QFieldType.STRING))
// .withViewField(new QFieldMetaData("message", QFieldType.STRING));
return new QProcessMetaData()
.withName(PROCESS_NAME)
.addStep(prepareStep)
.addStep(inputStep)
.addStep(executeStep)
.addStep(accessStep);
}
/*******************************************************************************
**
*******************************************************************************/
public static class Builder extends AbstractProcessMetaDataBuilder
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Builder(QProcessMetaData processMetaData)
{
super(processMetaData);
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public Builder withProcessName(String name)
{
processMetaData.setName(name);
return (this);
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public Builder withTableName(String tableName)
{
processMetaData.setTableName(tableName);
return (this);
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public Builder withIcon(QIcon icon)
{
processMetaData.setIcon(icon);
return (this);
}
/*******************************************************************************
** Fluent setter for reportName
**
*******************************************************************************/
public Builder withReportName(String reportName)
{
setInputFieldDefaultValue(RunReportForRecordProcess.FIELD_REPORT_NAME, reportName);
return (this);
}
}
}

View File

@ -54,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier; import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
@ -143,9 +144,8 @@ class QInstanceValidatorTest
qInstance.setTables(null); qInstance.setTables(null);
qInstance.setProcesses(null); qInstance.setProcesses(null);
}, },
"At least 1 table must be defined", true,
"Unrecognized table shape for possibleValueSource shape", "At least 1 table must be defined");
"Unrecognized processName for queue");
} }
@ -162,9 +162,8 @@ class QInstanceValidatorTest
qInstance.setTables(new HashMap<>()); qInstance.setTables(new HashMap<>());
qInstance.setProcesses(new HashMap<>()); qInstance.setProcesses(new HashMap<>());
}, },
"At least 1 table must be defined", true,
"Unrecognized table shape for possibleValueSource shape", "At least 1 table must be defined");
"Unrecognized processName for queue");
} }
@ -570,6 +569,40 @@ class QInstanceValidatorTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionDuplicateName()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withSection(new QFieldSection("section1", "Section 2", new QIcon("person"), Tier.T2, List.of("name")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("name", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "more than 1 section named");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionDuplicateLabel()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withSection(new QFieldSection("section2", "Section 1", new QIcon("person"), Tier.T2, List.of("name")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("name", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "more than 1 section labeled");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -1258,6 +1291,138 @@ class QInstanceValidatorTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testReportName()
{
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).withName(null),
"Inconsistent naming for report");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).withName(""),
"Inconsistent naming for report");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).withName("wrongName"),
"Inconsistent naming for report");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testReportNoDataSources()
{
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).withDataSources(null),
"At least 1 data source",
"unrecognized dataSourceName");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).withDataSources(new ArrayList<>()),
"At least 1 data source",
"unrecognized dataSourceName");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testReportDataSourceNames()
{
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setName(null),
"Missing name for a dataSource",
"unrecognized dataSourceName");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setName(""),
"Missing name for a dataSource",
"unrecognized dataSourceName");
assertValidationFailureReasons((qInstance) ->
{
List<QReportDataSource> dataSources = new ArrayList<>(qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources());
dataSources.add(dataSources.get(0));
qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).setDataSources(dataSources);
},
"More than one dataSource with name");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testReportDataSourceTables()
{
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setSourceTable("notATable"),
"is not a table in this instance");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setSourceTable(null),
"does not have a sourceTable");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).setSourceTable(""),
"does not have a sourceTable");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testReportDataSourceTablesFilter()
{
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).getQueryFilter().getCriteria().get(0).setFieldName(null),
"Missing fieldName for a criteria");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).getQueryFilter().getCriteria().get(0).setFieldName("notAField"),
"is not a field in this table");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).getQueryFilter().getCriteria().get(0).setOperator(null),
"Missing operator for a criteria");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).getQueryFilter().withOrderBy(new QFilterOrderBy(null)),
"Missing fieldName for an orderBy");
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).getQueryFilter().withOrderBy(new QFilterOrderBy("notAField")),
"is not a field in this table");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testReportDataSourceStaticDataSupplier()
{
assertValidationFailureReasons((qInstance) -> qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0).withStaticDataSupplier(new QCodeReference()),
"has both a sourceTable and a staticDataSupplier");
assertValidationFailureReasons((qInstance) ->
{
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
dataSource.setSourceTable(null);
dataSource.setStaticDataSupplier(new QCodeReference(null, QCodeType.JAVA, null));
},
"missing a code reference name");
assertValidationFailureReasons((qInstance) ->
{
QReportDataSource dataSource = qInstance.getReport(TestUtils.REPORT_NAME_SHAPES_PERSON).getDataSources().get(0);
dataSource.setSourceTable(null);
dataSource.setStaticDataSupplier(new QCodeReference(ArrayList.class, null));
},
"is not of the expected type");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -0,0 +1,62 @@
/*
* 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.processes.implementations.reports;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
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.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for BasicRunReportProcess
*******************************************************************************/
class RunReportForRecordProcessTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRunReport() throws QException
{
QInstance instance = TestUtils.defineInstance();
TestUtils.insertDefaultShapes(instance);
RunProcessInput runProcessInput = new RunProcessInput(instance);
runProcessInput.setSession(TestUtils.getMockSession());
runProcessInput.setProcessName(TestUtils.PROCESS_NAME_RUN_SHAPES_PERSON_REPORT);
runProcessInput.addValue(BasicRunReportProcess.FIELD_REPORT_NAME, TestUtils.REPORT_NAME_SHAPES_PERSON);
runProcessInput.addValue("recordsParam", "recordIds");
runProcessInput.addValue("recordIds", "1");
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
// runProcessOutput = new RunProcessAction().execute(runProcessInput);
// assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo(BasicRunReportProcess.STEP_NAME_ACCESS);
// assertThat(runProcessOutput.getValues()).containsKeys("downloadFileName", "serverFilePath");
}
}

View File

@ -76,6 +76,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaDa
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking; import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTracking;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType; import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType;
@ -91,6 +96,7 @@ import com.kingsrook.qqq.backend.core.processes.implementations.basepull.Basepul
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess; import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess; import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep; import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.core.processes.implementations.reports.RunReportForRecordProcess;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -118,10 +124,12 @@ public class TestUtils
public static final String PROCESS_NAME_INCREASE_BIRTHDATE = "increaseBirthdate"; public static final String PROCESS_NAME_INCREASE_BIRTHDATE = "increaseBirthdate";
public static final String PROCESS_NAME_ADD_TO_PEOPLES_AGE = "addToPeoplesAge"; public static final String PROCESS_NAME_ADD_TO_PEOPLES_AGE = "addToPeoplesAge";
public static final String PROCESS_NAME_BASEPULL = "basepullTest"; public static final String PROCESS_NAME_BASEPULL = "basepullTest";
public static final String PROCESS_NAME_RUN_SHAPES_PERSON_REPORT = "runShapesPersonReport";
public static final String TABLE_NAME_PERSON_FILE = "personFile"; public static final String TABLE_NAME_PERSON_FILE = "personFile";
public static final String TABLE_NAME_PERSON_MEMORY = "personMemory"; public static final String TABLE_NAME_PERSON_MEMORY = "personMemory";
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly"; public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
public static final String TABLE_NAME_BASEPULL = "basepullTest"; public static final String TABLE_NAME_BASEPULL = "basepullTest";
public static final String REPORT_NAME_SHAPES_PERSON = "shapesPersonReport";
public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type public static final String POSSIBLE_VALUE_SOURCE_STATE = "state"; // enum-type
public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type public static final String POSSIBLE_VALUE_SOURCE_SHAPE = "shape"; // table-type
@ -167,6 +175,9 @@ public class TestUtils
qInstance.addProcess(defineProcessIncreasePersonBirthdate()); qInstance.addProcess(defineProcessIncreasePersonBirthdate());
qInstance.addProcess(defineProcessBasepull()); qInstance.addProcess(defineProcessBasepull());
qInstance.addReport(defineShapesPersonsReport());
qInstance.addProcess(defineShapesPersonReportProcess());
qInstance.addAutomationProvider(definePollingAutomationProvider()); qInstance.addAutomationProvider(definePollingAutomationProvider());
qInstance.addQueueProvider(defineSqsProvider()); qInstance.addQueueProvider(defineSqsProvider());
@ -951,4 +962,52 @@ public class TestUtils
.withProcessName(PROCESS_NAME_INCREASE_BIRTHDATE)); .withProcessName(PROCESS_NAME_INCREASE_BIRTHDATE));
} }
/*******************************************************************************
**
*******************************************************************************/
public static QReportMetaData defineShapesPersonsReport()
{
return new QReportMetaData()
.withName(REPORT_NAME_SHAPES_PERSON)
.withProcessName(PROCESS_NAME_RUN_SHAPES_PERSON_REPORT)
.withInputFields(List.of(
new QFieldMetaData(RunReportForRecordProcess.FIELD_RECORD_ID, QFieldType.INTEGER).withIsRequired(true)
))
.withDataSources(List.of(
new QReportDataSource()
.withName("persons")
.withSourceTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
.withQueryFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("favoriteShapeId", QCriteriaOperator.EQUALS, List.of("${input." + RunReportForRecordProcess.FIELD_RECORD_ID + "}")))
)
))
.withViews(List.of(
new QReportView()
.withName("person")
.withDataSourceName("persons")
.withType(ReportType.TABLE)
.withColumns(List.of(
new QReportField().withName("id"),
new QReportField().withName("firstName"),
new QReportField().withName("lastName")
))
));
}
/*******************************************************************************
**
*******************************************************************************/
public static QProcessMetaData defineShapesPersonReportProcess()
{
return RunReportForRecordProcess.processMetaDataBuilder()
.withProcessName(PROCESS_NAME_RUN_SHAPES_PERSON_REPORT)
.withReportName(REPORT_NAME_SHAPES_PERSON)
.withTableName(TestUtils.TABLE_NAME_SHAPE)
.getProcessMetaData();
}
} }

View File

@ -24,8 +24,10 @@ package com.kingsrook.qqq.backend.module.api.model.metadata;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
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.utils.StringUtils;
import com.kingsrook.qqq.backend.module.api.APIBackendModule; import com.kingsrook.qqq.backend.module.api.APIBackendModule;
import com.kingsrook.qqq.backend.module.api.model.AuthorizationType; import com.kingsrook.qqq.backend.module.api.model.AuthorizationType;
@ -379,4 +381,14 @@ public class APIBackendMetaData extends QBackendMetaData
return (this); return (this);
} }
/*******************************************************************************
**
*******************************************************************************/
@Override
public void performValidation(QInstanceValidator qInstanceValidator)
{
qInstanceValidator.assertCondition(StringUtils.hasContent(baseUrl), "Missing baseUrl for API backend: " + getName());
}
} }

View File

@ -32,9 +32,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
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.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData; import com.kingsrook.qqq.backend.module.api.model.metadata.APIBackendMetaData;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@ -43,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@DisabledOnOs(OS.LINUX) @Disabled // OnOs(OS.LINUX)
public class EasyPostApiTest public class EasyPostApiTest
{ {

View File

@ -0,0 +1,51 @@
/*
* 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.module.api.model.metadata;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for APIBackendMetaData
*******************************************************************************/
class APIBackendMetaDataTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
APIBackendMetaData apiBackendMetaData = new APIBackendMetaData()
.withName("test");
QInstanceValidator qInstanceValidator = new QInstanceValidator();
apiBackendMetaData.performValidation(qInstanceValidator);
assertEquals(1, qInstanceValidator.getErrors().size());
assertThat(qInstanceValidator.getErrors()).anyMatch(e -> e.contains("Missing baseUrl"));
}
}