Merge branch 'feature/QQQ-28-bulk-ops-frontend' into feature/sprint-7-integration

This commit is contained in:
2022-07-25 08:20:18 -05:00
47 changed files with 2136 additions and 64 deletions

View File

@ -52,7 +52,7 @@ public class AsyncJobCallback
/******************************************************************************* /*******************************************************************************
** ** Update the message
*******************************************************************************/ *******************************************************************************/
public void updateStatus(String message) public void updateStatus(String message)
{ {
@ -63,7 +63,20 @@ public class AsyncJobCallback
/******************************************************************************* /*******************************************************************************
** ** Update all 3 status fields
*******************************************************************************/
public void updateStatus(String message, int current, int total)
{
this.asyncJobStatus.setMessage(message);
this.asyncJobStatus.setCurrent(current);
this.asyncJobStatus.setTotal(total);
storeUpdatedStatus();
}
/*******************************************************************************
** Update the current and total fields (e.g., 1 of 2, 2 of 2, 3 of 2)
*******************************************************************************/ *******************************************************************************/
public void updateStatus(int current, int total) public void updateStatus(int current, int total)
{ {
@ -75,13 +88,12 @@ public class AsyncJobCallback
/******************************************************************************* /*******************************************************************************
** ** Remove the values from the current & total fields
*******************************************************************************/ *******************************************************************************/
public void updateStatus(String message, int current, int total) public void clearCurrentAndTotal()
{ {
this.asyncJobStatus.setMessage(message); this.asyncJobStatus.setCurrent(null);
this.asyncJobStatus.setCurrent(current); this.asyncJobStatus.setTotal(null);
this.asyncJobStatus.setTotal(total);
storeUpdatedStatus(); storeUpdatedStatus();
} }

View File

@ -37,4 +37,17 @@ public interface DeleteInterface
** **
*******************************************************************************/ *******************************************************************************/
DeleteOutput execute(DeleteInput deleteInput) throws QException; DeleteOutput execute(DeleteInput deleteInput) throws QException;
/*******************************************************************************
** Specify whether this particular module's delete action natively supports
** receiving a queryFilter as input (e.g., SQL does). If the module doesn't
** support a query filter, then the qqq framework (DeleteAction) will, if it
** receives a queryFilter in its input, it will execute the query, and pass
** the list of primary keys down into the module's delete implementation.
*******************************************************************************/
default boolean supportsQueryFilterInput()
{
return (false);
}
} }

View File

@ -127,7 +127,7 @@ public class RunProcessAction
/////////////////////// ///////////////////////
// Run backend steps // // Run backend steps //
/////////////////////// ///////////////////////
runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, processState); runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState);
} }
else else
{ {
@ -225,11 +225,12 @@ public class RunProcessAction
/******************************************************************************* /*******************************************************************************
** Run a single backend step. ** Run a single backend step.
*******************************************************************************/ *******************************************************************************/
private void runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, ProcessState processState) throws Exception private void runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
{ {
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(runProcessInput.getInstance(), processState); RunBackendStepInput runBackendStepInput = new RunBackendStepInput(runProcessInput.getInstance(), processState);
runBackendStepInput.setProcessName(process.getName()); runBackendStepInput.setProcessName(process.getName());
runBackendStepInput.setStepName(backendStep.getName()); runBackendStepInput.setStepName(backendStep.getName());
runBackendStepInput.setTableName(process.getTableName());
runBackendStepInput.setSession(runProcessInput.getSession()); runBackendStepInput.setSession(runProcessInput.getSession());
runBackendStepInput.setCallback(runProcessInput.getCallback()); runBackendStepInput.setCallback(runProcessInput.getCallback());
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback()); runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());

View File

@ -22,12 +22,21 @@
package com.kingsrook.qqq.backend.core.actions.tables; package com.kingsrook.qqq.backend.core.actions.tables;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
@ -36,6 +45,10 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
*******************************************************************************/ *******************************************************************************/
public class DeleteAction public class DeleteAction
{ {
private static final Logger LOG = LogManager.getLogger(DeleteAction.class);
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -46,8 +59,62 @@ public class DeleteAction
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher(); QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend()); QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
// todo pre-customization - just get to modify the request? // todo pre-customization - just get to modify the request?
DeleteOutput deleteResult = qModule.getDeleteInterface().execute(deleteInput);
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
{
throw (new QException("A delete request may not contain both a list of primary keys and a query filter."));
}
DeleteInterface deleteInterface = qModule.getDeleteInterface();
if(deleteInput.getQueryFilter() != null && !deleteInterface.supportsQueryFilterInput())
{
LOG.info("Querying for primary keys, for backend module " + qModule.getBackendType() + " which does not support queryFilter input for deletes");
List<Serializable> primaryKeyList = getPrimaryKeysFromQueryFilter(deleteInput);
deleteInput.setPrimaryKeys(primaryKeyList);
if(primaryKeyList.isEmpty())
{
LOG.info("0 primaryKeys found. Returning with no-op");
DeleteOutput deleteOutput = new DeleteOutput();
deleteOutput.setRecordsWithErrors(new ArrayList<>());
return (deleteOutput);
}
}
DeleteOutput deleteResult = deleteInterface.execute(deleteInput);
// todo post-customization - can do whatever w/ the result if you want // todo post-customization - can do whatever w/ the result if you want
return deleteResult; return deleteResult;
} }
/*******************************************************************************
** For an implementation that doesn't support a queryFilter as its input,
** but a scenario where a query filter was passed in - run the query, to
** get a list of primary keys.
*******************************************************************************/
public static List<Serializable> getPrimaryKeysFromQueryFilter(DeleteInput deleteInput) throws QException
{
try
{
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
QueryInput queryInput = new QueryInput(deleteInput.getInstance(), deleteInput.getSession());
queryInput.setTableName(deleteInput.getTableName());
queryInput.setFilter(deleteInput.getQueryFilter());
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
return (queryOutput.getRecords().stream()
.map(r -> r.getValue(deleteInput.getTable().getPrimaryKeyField()))
.toList());
}
catch(Exception e)
{
LOG.warn("Error getting primary keys from query filter before bulk-delete", e);
throw (new QException("Error getting keys from filter prior to delete.", e));
}
}
} }

View File

@ -22,14 +22,31 @@
package com.kingsrook.qqq.backend.core.instances; package com.kingsrook.qqq.backend.core.instances;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Collectors;
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.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.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.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.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
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.QRecordListMetaData;
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.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteStoreStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditReceiveValuesStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditStoreRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -48,6 +65,7 @@ public class QInstanceEnricher
if(qInstance.getTables() != null) if(qInstance.getTables() != null)
{ {
qInstance.getTables().values().forEach(this::enrich); qInstance.getTables().values().forEach(this::enrich);
defineTableBulkProcesses(qInstance);
} }
if(qInstance.getProcesses() != null) if(qInstance.getProcesses() != null)
@ -122,7 +140,7 @@ public class QInstanceEnricher
step.getInputFields().forEach(this::enrich); step.getInputFields().forEach(this::enrich);
step.getOutputFields().forEach(this::enrich); step.getOutputFields().forEach(this::enrich);
if (step instanceof QFrontendStepMetaData frontendStepMetaData) if(step instanceof QFrontendStepMetaData frontendStepMetaData)
{ {
if(frontendStepMetaData.getFormFields() != null) if(frontendStepMetaData.getFormFields() != null)
{ {
@ -167,4 +185,229 @@ public class QInstanceEnricher
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1")); return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
} }
/*******************************************************************************
** Add bulk insert/edit/delete processes to all tables (unless the meta data
** already had these processes defined (e.g., the user defined custom ones)
*******************************************************************************/
private void defineTableBulkProcesses(QInstance qInstance)
{
for(QTableMetaData table : qInstance.getTables().values())
{
if(table.getFields() == null)
{
/////////////////////////////////////////////////////////////////
// these processes can't be defined if there aren't any fields //
/////////////////////////////////////////////////////////////////
continue;
}
// todo - add idea of 'supportsBulkX'
String bulkInsertProcessName = table.getName() + ".bulkInsert";
if(qInstance.getProcess(bulkInsertProcessName) == null)
{
defineTableBulkInsert(qInstance, table, bulkInsertProcessName);
}
String bulkEditProcessName = table.getName() + ".bulkEdit";
if(qInstance.getProcess(bulkEditProcessName) == null)
{
defineTableBulkEdit(qInstance, table, bulkEditProcessName);
}
String bulkDeleteProcessName = table.getName() + ".bulkDelete";
if(qInstance.getProcess(bulkDeleteProcessName) == null)
{
defineTableBulkDelete(qInstance, table, bulkDeleteProcessName);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private void defineTableBulkInsert(QInstance qInstance, QTableMetaData table, String processName)
{
List<QFieldMetaData> editableFields = table.getFields().values().stream()
.filter(QFieldMetaData::getIsEditable)
.toList();
String fieldsForHelpText = editableFields.stream()
.map(QFieldMetaData::getLabel)
.collect(Collectors.joining(", "));
QFrontendStepMetaData uploadScreen = new QFrontendStepMetaData()
.withName("upload")
.withLabel("Upload File")
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB).withIsRequired(true))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "Upload a CSV or XLSX file with the following columns: " + fieldsForHelpText));
QBackendStepMetaData receiveFileStep = new QBackendStepMetaData()
.withName("receiveFile")
.withCode(new QCodeReference(BulkInsertReceiveFileStep.class))
.withInputData(new QFunctionInputMetaData()
// todo - our upload file as a field? problem is, its type...
.withFieldList(List.of()))
.withOutputMetaData(new QFunctionOutputMetaData()
.withFieldList(List.of(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER))));
QFrontendStepMetaData reviewScreen = new QFrontendStepMetaData()
.withName("review")
.withRecordListFields(editableFields)
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below were parsed from your file, and will be inserted if you click Submit."))
.withViewField(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER).withLabel("# of file rows"));
QBackendStepMetaData storeStep = new QBackendStepMetaData()
.withName("storeRecords")
.withCode(new QCodeReference(BulkInsertStoreRecordsStep.class))
.withInputData(new QFunctionInputMetaData()
// todo - our upload file as a field? problem is, its type...
.withFieldList(List.of()))
.withOutputMetaData(new QFunctionOutputMetaData()
.withFieldList(List.of(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER))));
QFrontendStepMetaData resultsScreen = new QFrontendStepMetaData()
.withName("results")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below have been inserted."))
.withViewField(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER).withLabel("# of file rows"));
qInstance.addProcess(
new QProcessMetaData()
.withName(processName)
.withLabel(table.getLabel() + " Bulk Insert")
.withTableName(table.getName())
.withIsHidden(true)
.withStepList(List.of(
uploadScreen,
receiveFileStep,
reviewScreen,
storeStep,
resultsScreen
)));
}
/*******************************************************************************
**
*******************************************************************************/
private void defineTableBulkEdit(QInstance qInstance, QTableMetaData table, String processName)
{
List<QFieldMetaData> editableFields = table.getFields().values().stream()
.filter(QFieldMetaData::getIsEditable)
.toList();
QFrontendStepMetaData editScreen = new QFrontendStepMetaData()
.withName("edit")
.withLabel("Edit Values")
.withFormFields(editableFields)
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", """
Flip the switches next to the fields that you want to edit.
The values you supply here will be updated in all of the records you are bulk editing.
You can clear out the value in a field by flipping the switch on for that field and leaving the input field blank.
Fields whose switches are off will not be updated."""))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.BULK_EDIT_FORM)
);
QBackendStepMetaData receiveValuesStep = new QBackendStepMetaData()
.withName("receiveValues")
.withCode(new QCodeReference(BulkEditReceiveValuesStep.class))
.withInputData(new QFunctionInputMetaData()
.withRecordListMetaData(new QRecordListMetaData().withTableName(table.getName()))
.withField(new QFieldMetaData(BulkEditReceiveValuesStep.FIELD_ENABLED_FIELDS, QFieldType.STRING))
.withFields(editableFields));
QFrontendStepMetaData reviewScreen = new QFrontendStepMetaData()
.withName("review")
.withRecordListFields(editableFields)
.withViewField(new QFieldMetaData(BulkEditReceiveValuesStep.FIELD_VALUES_BEING_UPDATED, QFieldType.STRING))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below will be updated if you click Submit."));
QBackendStepMetaData storeStep = new QBackendStepMetaData()
.withName("storeRecords")
.withCode(new QCodeReference(BulkEditStoreRecordsStep.class))
.withInputData(new QFunctionInputMetaData()
// todo - our upload file as a field? problem is, its type...
.withFieldList(List.of()))
.withOutputMetaData(new QFunctionOutputMetaData()
.withFieldList(List.of(new QFieldMetaData("noOfFileRows", QFieldType.INTEGER))));
QFrontendStepMetaData resultsScreen = new QFrontendStepMetaData()
.withName("results")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below have been updated."));
qInstance.addProcess(
new QProcessMetaData()
.withName(processName)
.withLabel(table.getLabel() + " Bulk Edit")
.withTableName(table.getName())
.withIsHidden(true)
.withStepList(List.of(
LoadInitialRecordsStep.defineMetaData(table.getName()),
editScreen,
receiveValuesStep,
reviewScreen,
storeStep,
resultsScreen
)));
}
/*******************************************************************************
**
*******************************************************************************/
private void defineTableBulkDelete(QInstance qInstance, QTableMetaData table, String processName)
{
QFrontendStepMetaData reviewScreen = new QFrontendStepMetaData()
.withName("review")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below will be deleted if you click Submit."));
QBackendStepMetaData storeStep = new QBackendStepMetaData()
.withName("delete")
.withCode(new QCodeReference(BulkDeleteStoreStep.class));
QFrontendStepMetaData resultsScreen = new QFrontendStepMetaData()
.withName("results")
.withRecordListFields(new ArrayList<>(table.getFields().values()))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("text", "The records below have been deleted."));
qInstance.addProcess(
new QProcessMetaData()
.withName(processName)
.withLabel(table.getLabel() + " Bulk Delete")
.withTableName(table.getName())
.withIsHidden(true)
.withStepList(List.of(
LoadInitialRecordsStep.defineMetaData(table.getName()),
reviewScreen,
storeStep,
resultsScreen
)));
}
} }

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.actions; package com.kingsrook.qqq.backend.core.model.actions;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
@ -42,6 +45,8 @@ public abstract class AbstractActionInput
protected QInstance instance; protected QInstance instance;
protected QSession session; protected QSession session;
private AsyncJobCallback asyncJobCallback;
/******************************************************************************* /*******************************************************************************
@ -59,7 +64,16 @@ public abstract class AbstractActionInput
public AbstractActionInput(QInstance instance) public AbstractActionInput(QInstance instance)
{ {
this.instance = instance; this.instance = instance;
validateInstance(instance);
}
/*******************************************************************************
** performance instance validation (if not previously done).
*******************************************************************************/
private void validateInstance(QInstance instance)
{
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// if this instance hasn't been validated yet, do so now // // if this instance hasn't been validated yet, do so now //
// noting that this will also enrich any missing metaData // // noting that this will also enrich any missing metaData //
@ -107,6 +121,7 @@ public abstract class AbstractActionInput
*******************************************************************************/ *******************************************************************************/
public void setInstance(QInstance instance) public void setInstance(QInstance instance)
{ {
validateInstance(instance);
this.instance = instance; this.instance = instance;
} }
@ -131,4 +146,33 @@ public abstract class AbstractActionInput
{ {
this.session = session; this.session = session;
} }
/*******************************************************************************
** Getter for asyncJobCallback
**
*******************************************************************************/
public AsyncJobCallback getAsyncJobCallback()
{
if(asyncJobCallback == null)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// don't return null here (too easy to NPE). instead, if someone wants one of these, create one and give it to them. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
asyncJobCallback = new AsyncJobCallback(UUID.randomUUID(), new AsyncJobStatus());
}
return asyncJobCallback;
}
/*******************************************************************************
** Setter for asyncJobCallback
**
*******************************************************************************/
public void setAsyncJobCallback(AsyncJobCallback asyncJobCallback)
{
this.asyncJobCallback = asyncJobCallback;
}
} }

View File

@ -0,0 +1,79 @@
/*
* 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.processes;
import java.io.Serializable;
/*******************************************************************************
** Model a file that a user uploaded (or otherwise submitted to the qqq backend).
*******************************************************************************/
public class QUploadedFile implements Serializable
{
private String filename;
private byte[] bytes;
/*******************************************************************************
** Getter for filename
**
*******************************************************************************/
public String getFilename()
{
return filename;
}
/*******************************************************************************
** Setter for filename
**
*******************************************************************************/
public void setFilename(String filename)
{
this.filename = filename;
}
/*******************************************************************************
** Getter for bytes
**
*******************************************************************************/
public byte[] getBytes()
{
return bytes;
}
/*******************************************************************************
** Setter for bytes
**
*******************************************************************************/
public void setBytes(byte[] bytes)
{
this.bytes = bytes;
}
}

View File

@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -44,6 +45,7 @@ public class RunBackendStepInput extends AbstractActionInput
{ {
private ProcessState processState; private ProcessState processState;
private String processName; private String processName;
private String tableName;
private String stepName; private String stepName;
private QProcessCallback callback; private QProcessCallback callback;
private AsyncJobCallback asyncJobCallback; private AsyncJobCallback asyncJobCallback;
@ -126,6 +128,55 @@ public class RunBackendStepInput extends AbstractActionInput
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
**
*******************************************************************************/
public RunBackendStepInput withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QTableMetaData getTable()
{
if(tableName == null)
{
return (null);
}
return (instance.getTable(tableName));
}
/******************************************************************************* /*******************************************************************************
** Getter for functionName ** Getter for functionName
** **
@ -334,7 +385,7 @@ public class RunBackendStepInput extends AbstractActionInput
*******************************************************************************/ *******************************************************************************/
public AsyncJobCallback getAsyncJobCallback() public AsyncJobCallback getAsyncJobCallback()
{ {
if (asyncJobCallback == null) if(asyncJobCallback == null)
{ {
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
// avoid NPE in case we didn't have one of these! create a new one... // // avoid NPE in case we didn't have one of these! create a new one... //

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/******************************************************************************* /*******************************************************************************
@ -183,4 +184,38 @@ public class RunBackendStepOutput extends AbstractActionOutput
{ {
return exception; return exception;
} }
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Serializable getValue(String fieldName)
{
return (processState.getValues().get(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public String getValueString(String fieldName)
{
return ((String) getValue(fieldName));
}
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Integer getValueInteger(String fieldName)
{
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
}
} }

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
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;
@ -35,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
public class DeleteInput extends AbstractTableActionInput public class DeleteInput extends AbstractTableActionInput
{ {
private List<Serializable> primaryKeys; private List<Serializable> primaryKeys;
private QQueryFilter queryFilter;
@ -88,4 +90,37 @@ public class DeleteInput extends AbstractTableActionInput
this.primaryKeys = primaryKeys; this.primaryKeys = primaryKeys;
return (this); return (this);
} }
/*******************************************************************************
** Getter for queryFilter
**
*******************************************************************************/
public QQueryFilter getQueryFilter()
{
return queryFilter;
}
/*******************************************************************************
** Setter for queryFilter
**
*******************************************************************************/
public void setQueryFilter(QQueryFilter queryFilter)
{
this.queryFilter = queryFilter;
}
/*******************************************************************************
** Fluent setter for queryFilter
**
*******************************************************************************/
public DeleteInput withQueryFilter(QQueryFilter queryFilter)
{
this.queryFilter = queryFilter;
return this;
}
} }

View File

@ -22,6 +22,8 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.delete; package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -31,18 +33,31 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
* Output for a delete action * Output for a delete action
* *
*******************************************************************************/ *******************************************************************************/
public class DeleteOutput extends AbstractActionOutput public class DeleteOutput extends AbstractActionOutput implements Serializable
{ {
private List<QRecord> records; private int deletedRecordCount = 0;
private List<QRecord> recordsWithErrors;
/******************************************************************************* /*******************************************************************************
** Getter for deletedRecordCount
** **
*******************************************************************************/ *******************************************************************************/
public List<QRecord> getRecords() public int getDeletedRecordCount()
{ {
return records; return deletedRecordCount;
}
/*******************************************************************************
** Setter for deletedRecordCount
**
*******************************************************************************/
public void setDeletedRecordCount(int deletedRecordCount)
{
this.deletedRecordCount = deletedRecordCount;
} }
@ -50,8 +65,40 @@ public class DeleteOutput extends AbstractActionOutput
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public void setRecords(List<QRecord> records) public List<QRecord> getRecordsWithErrors()
{ {
this.records = records; return recordsWithErrors;
}
/*******************************************************************************
**
*******************************************************************************/
public void setRecordsWithErrors(List<QRecord> recordsWithErrors)
{
this.recordsWithErrors = recordsWithErrors;
}
/*******************************************************************************
**
*******************************************************************************/
public void addRecordWithError(QRecord recordWithError)
{
if(this.recordsWithErrors == null)
{
this.recordsWithErrors = new ArrayList<>();
}
this.recordsWithErrors.add(recordWithError);
}
/*******************************************************************************
**
*******************************************************************************/
public void addToDeletedRecordCount(int i)
{
deletedRecordCount += i;
} }
} }

View File

@ -30,7 +30,7 @@ import java.util.List;
* A single criteria Component of a Query * A single criteria Component of a Query
* *
*******************************************************************************/ *******************************************************************************/
public class QFilterCriteria public class QFilterCriteria implements Serializable
{ {
private String fieldName; private String fieldName;
private QCriteriaOperator operator; private QCriteriaOperator operator;

View File

@ -22,11 +22,14 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.query; package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
/******************************************************************************* /*******************************************************************************
** Bean representing an element of a query order-by clause. ** Bean representing an element of a query order-by clause.
** **
*******************************************************************************/ *******************************************************************************/
public class QFilterOrderBy public class QFilterOrderBy implements Serializable
{ {
private String fieldName; private String fieldName;
private boolean isAscending = true; private boolean isAscending = true;

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.query; package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -30,7 +31,7 @@ import java.util.List;
* Full "filter" for a query - a list of criteria and order-bys * Full "filter" for a query - a list of criteria and order-bys
* *
*******************************************************************************/ *******************************************************************************/
public class QQueryFilter public class QQueryFilter implements Serializable
{ {
private List<QFilterCriteria> criteria = new ArrayList<>(); private List<QFilterCriteria> criteria = new ArrayList<>();
private List<QFilterOrderBy> orderBys = new ArrayList<>(); private List<QFilterOrderBy> orderBys = new ArrayList<>();

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe; import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
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;
/******************************************************************************* /*******************************************************************************
@ -60,6 +61,17 @@ public class QueryInput extends AbstractTableActionInput
/*******************************************************************************
**
*******************************************************************************/
public QueryInput(QInstance instance, QSession session)
{
super(instance);
setSession(session);
}
/******************************************************************************* /*******************************************************************************
** Getter for filter ** Getter for filter
** **

View File

@ -36,6 +36,14 @@ public class UpdateInput extends AbstractTableActionInput
{ {
private List<QRecord> records; private List<QRecord> records;
////////////////////////////////////////////////////////////////////////////////////////////
// allow a caller to specify that they KNOW this optimization (e.g., in SQL) can be made. //
// If you set this to true, but it isn't, then you may not get an accurate update. //
// If you set this to false, but it isn't, then you may not get the best performance. //
// Just leave it null if you don't know what you're dong. //
////////////////////////////////////////////////////////////////////////////////////////////
private Boolean areAllValuesBeingUpdatedTheSame = null;
/******************************************************************************* /*******************************************************************************
@ -76,4 +84,27 @@ public class UpdateInput extends AbstractTableActionInput
{ {
this.records = records; this.records = records;
} }
/*******************************************************************************
** Getter for areAllValuesBeingUpdatedTheSame
**
*******************************************************************************/
public Boolean getAreAllValuesBeingUpdatedTheSame()
{
return areAllValuesBeingUpdatedTheSame;
}
/*******************************************************************************
** Setter for areAllValuesBeingUpdatedTheSame
**
*******************************************************************************/
public void setAreAllValuesBeingUpdatedTheSame(Boolean areAllValuesBeingUpdatedTheSame)
{
this.areAllValuesBeingUpdatedTheSame = areAllValuesBeingUpdatedTheSame;
}
} }

View File

@ -25,9 +25,12 @@ package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -54,7 +57,7 @@ public class QRecord implements Serializable
private Map<String, Serializable> values = new LinkedHashMap<>(); private Map<String, Serializable> values = new LinkedHashMap<>();
private Map<String, String> displayValues = new LinkedHashMap<>(); private Map<String, String> displayValues = new LinkedHashMap<>();
private Map<String, Serializable> backendDetails = new LinkedHashMap<>(); private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
// todo private List<String> errors = new ArrayList<>(); private List<String> errors = new ArrayList<>();
@ -66,6 +69,16 @@ public class QRecord implements Serializable
} }
/*******************************************************************************
**
*******************************************************************************/
public QRecord(QTableMetaData tableMetaData, Serializable primaryKeyValue)
{
setTableName(tableMetaData.getName());
setValue(tableMetaData.getPrimaryKeyField(), primaryKeyValue);
}
/******************************************************************************* /*******************************************************************************
** Copy constructor. ** Copy constructor.
@ -77,7 +90,7 @@ public class QRecord implements Serializable
this.values = record.values; this.values = record.values;
this.displayValues = record.displayValues; this.displayValues = record.displayValues;
this.backendDetails = record.backendDetails; this.backendDetails = record.backendDetails;
// todo! this.errors = record.errors; this.errors = record.errors;
} }
@ -344,6 +357,49 @@ public class QRecord implements Serializable
} }
/*******************************************************************************
** Getter for errors
**
*******************************************************************************/
public List<String> getErrors()
{
return (errors);
}
/*******************************************************************************
** Setter for errors
**
*******************************************************************************/
public void setErrors(List<String> errors)
{
this.errors = errors;
}
/*******************************************************************************
** Add one error to this record
**
*******************************************************************************/
public void addError(String error)
{
this.errors.add(error);
}
/*******************************************************************************
** Fluently Add one error to this record
**
*******************************************************************************/
public QRecord withError(String error)
{
addError(error);
return (this);
}
/******************************************************************************* /*******************************************************************************
** Convert this record to an QRecordEntity ** Convert this record to an QRecordEntity
@ -352,4 +408,5 @@ public class QRecord implements Serializable
{ {
return (QRecordEntity.fromQRecord(c, this)); return (QRecordEntity.fromQRecord(c, this));
} }
} }

View File

@ -22,8 +22,12 @@
package com.kingsrook.qqq.backend.core.model.metadata.code; package com.kingsrook.qqq.backend.core.model.metadata.code;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
/******************************************************************************* /*******************************************************************************
** ** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
** maybe process steps, maybe customization to a table, etc.
*******************************************************************************/ *******************************************************************************/
public class QCodeReference public class QCodeReference
{ {
@ -33,6 +37,59 @@ public class QCodeReference
/*******************************************************************************
** Default empty constructor
*******************************************************************************/
public QCodeReference()
{
}
/*******************************************************************************
** Constructor that takes all args
*******************************************************************************/
public QCodeReference(String name, QCodeType codeType, QCodeUsage codeUsage)
{
this.name = name;
this.codeType = codeType;
this.codeUsage = codeUsage;
}
/*******************************************************************************
** Constructor that just takes a java class, and infers the other fields.
*******************************************************************************/
public QCodeReference(Class<?> javaClass)
{
this.name = javaClass.getName();
this.codeType = QCodeType.JAVA;
if(BackendStep.class.isAssignableFrom(javaClass))
{
this.codeUsage = QCodeUsage.BACKEND_STEP;
}
else
{
throw (new IllegalStateException("Unable to infer code usage type for class: " + javaClass.getName()));
}
}
/*******************************************************************************
** Constructor that just takes a java class and code usage.
*******************************************************************************/
public QCodeReference(Class<?> javaClass, QCodeUsage codeUsage)
{
this.name = javaClass.getName();
this.codeType = QCodeType.JAVA;
this.codeUsage = codeUsage;
}
/******************************************************************************* /*******************************************************************************
** Getter for name ** Getter for name
** **

View File

@ -40,6 +40,12 @@ public class QFieldMetaData
private String backendName; private String backendName;
private QFieldType type; private QFieldType type;
private boolean isRequired = false; private boolean isRequired = false;
private boolean isEditable = true;
///////////////////////////////////////////////////////////////////////////////////
// if we need "only edit on insert" or "only edit on update" in the future, //
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
///////////////////////////////////////////////////////////////////////////////////
private Serializable defaultValue; private Serializable defaultValue;
private String possibleValueSourceName; private String possibleValueSourceName;
@ -315,4 +321,37 @@ public class QFieldMetaData
return (this); return (this);
} }
/*******************************************************************************
** Getter for isEditable
**
*******************************************************************************/
public boolean getIsEditable()
{
return isEditable;
}
/*******************************************************************************
** Setter for isEditable
**
*******************************************************************************/
public void setIsEditable(boolean isEditable)
{
this.isEditable = isEditable;
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldMetaData withIsEditable(boolean isEditable)
{
this.isEditable = isEditable;
return (this);
}
} }

View File

@ -40,7 +40,8 @@ public enum QFieldType
DATE_TIME, DATE_TIME,
TEXT, TEXT,
HTML, HTML,
PASSWORD; PASSWORD,
BLOB;

View File

@ -40,6 +40,7 @@ public class QFrontendFieldMetaData
private String label; private String label;
private QFieldType type; private QFieldType type;
private boolean isRequired; private boolean isRequired;
private boolean isEditable;
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! // // do not add setters. take values from the source-object in the constructor!! //
@ -48,7 +49,7 @@ public class QFrontendFieldMetaData
/******************************************************************************* /*******************************************************************************
** ** Constructor
*******************************************************************************/ *******************************************************************************/
public QFrontendFieldMetaData(QFieldMetaData fieldMetaData) public QFrontendFieldMetaData(QFieldMetaData fieldMetaData)
{ {
@ -56,6 +57,7 @@ public class QFrontendFieldMetaData
this.label = fieldMetaData.getLabel(); this.label = fieldMetaData.getLabel();
this.type = fieldMetaData.getType(); this.type = fieldMetaData.getType();
this.isRequired = fieldMetaData.getIsRequired(); this.isRequired = fieldMetaData.getIsRequired();
this.isEditable = fieldMetaData.getIsEditable();
} }
@ -101,4 +103,16 @@ public class QFrontendFieldMetaData
{ {
return isRequired; return isRequired;
} }
/*******************************************************************************
** Getter for isEditable
**
*******************************************************************************/
public boolean getIsEditable()
{
return isEditable;
}
} }

View File

@ -0,0 +1,35 @@
/*
* 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;
/*******************************************************************************
** Types of UI Components that can be specified in frontend process steps.
*******************************************************************************/
public enum QComponentType
{
HELP_TEXT,
BULK_EDIT_FORM;
///////////////////////////////////////////////////////////////////////////
// keep these values in sync with QComponentType.ts in qqq-frontend-core //
///////////////////////////////////////////////////////////////////////////
}

View File

@ -0,0 +1,123 @@
/*
* 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;
import java.util.HashMap;
import java.util.Map;
/*******************************************************************************
** Definition of a UI component in a frontend process steps.
*******************************************************************************/
public class QFrontendComponentMetaData
{
private QComponentType type;
private Map<String, Serializable> values;
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public QComponentType getType()
{
return type;
}
/*******************************************************************************
** Setter for type
**
*******************************************************************************/
public void setType(QComponentType type)
{
this.type = type;
}
/*******************************************************************************
** Fluent setter for type
**
*******************************************************************************/
public QFrontendComponentMetaData withType(QComponentType type)
{
this.type = type;
return (this);
}
/*******************************************************************************
** Getter for values
**
*******************************************************************************/
public Map<String, Serializable> getValues()
{
return values;
}
/*******************************************************************************
** Setter for values
**
*******************************************************************************/
public void setValues(Map<String, Serializable> values)
{
this.values = values;
}
/*******************************************************************************
** Fluent setter for values
**
*******************************************************************************/
public QFrontendComponentMetaData withValues(Map<String, Serializable> values)
{
this.values = values;
return (this);
}
/*******************************************************************************
** Fluent setter for values
**
*******************************************************************************/
public QFrontendComponentMetaData withValue(String key, Serializable value)
{
if(values == null)
{
values = new HashMap<>();
}
values.put(key, value);
return (this);
}
}

View File

@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
*******************************************************************************/ *******************************************************************************/
public class QFrontendStepMetaData extends QStepMetaData public class QFrontendStepMetaData extends QStepMetaData
{ {
private List<QFrontendComponentMetaData> components;
private List<QFieldMetaData> formFields; private List<QFieldMetaData> formFields;
private List<QFieldMetaData> viewFields; private List<QFieldMetaData> viewFields;
private List<QFieldMetaData> recordListFields; private List<QFieldMetaData> recordListFields;
@ -50,6 +51,56 @@ public class QFrontendStepMetaData extends QStepMetaData
/*******************************************************************************
** Getter for components
**
*******************************************************************************/
public List<QFrontendComponentMetaData> getComponents()
{
return components;
}
/*******************************************************************************
** Setter for components
**
*******************************************************************************/
public void setComponents(List<QFrontendComponentMetaData> components)
{
this.components = components;
}
/*******************************************************************************
** Fluent setter for adding 1 component
**
*******************************************************************************/
public QFrontendStepMetaData withComponent(QFrontendComponentMetaData component)
{
if(this.components == null)
{
this.components = new ArrayList<>();
}
this.components.add(component);
return (this);
}
/*******************************************************************************
** Fluent setter for components
**
*******************************************************************************/
public QFrontendStepMetaData withComponents(List<QFrontendComponentMetaData> components)
{
this.components = components;
return (this);
}
/******************************************************************************* /*******************************************************************************
** Getter for formFields ** Getter for formFields
** **

View File

@ -124,7 +124,23 @@ public class QFunctionInputMetaData
/******************************************************************************* /*******************************************************************************
** Setter for fieldList ** Fluently ADD a list of fields to this object's existing list
**
*******************************************************************************/
public QFunctionInputMetaData withFields(List<QFieldMetaData> fieldList)
{
if(this.fieldList == null)
{
this.fieldList = new ArrayList<>();
}
this.fieldList.addAll(fieldList);
return (this);
}
/*******************************************************************************
** Fluent Setter for fieldList - e.g., will overwrite any previously set fields!!
** **
*******************************************************************************/ *******************************************************************************/
public QFunctionInputMetaData withFieldList(List<QFieldMetaData> fieldList) public QFunctionInputMetaData withFieldList(List<QFieldMetaData> fieldList)
@ -136,10 +152,10 @@ public class QFunctionInputMetaData
/******************************************************************************* /*******************************************************************************
** Setter for fieldList ** Fluently add a field to the list
** **
*******************************************************************************/ *******************************************************************************/
public QFunctionInputMetaData addField(QFieldMetaData field) public QFunctionInputMetaData withField(QFieldMetaData field)
{ {
if(this.fieldList == null) if(this.fieldList == null)
{ {
@ -149,4 +165,17 @@ public class QFunctionInputMetaData
return (this); return (this);
} }
/*******************************************************************************
** Use withField instead, please.
**
** @deprecated
*******************************************************************************/
@Deprecated
public QFunctionInputMetaData addField(QFieldMetaData field)
{
return (withField(field));
}
} }

View File

@ -116,7 +116,7 @@ public class QRecordListMetaData
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public QRecordListMetaData addField(QFieldMetaData field) public QRecordListMetaData withField(QFieldMetaData field)
{ {
if(this.fields == null) if(this.fields == null)
{ {
@ -126,4 +126,15 @@ public class QRecordListMetaData
return (this); return (this);
} }
/*******************************************************************************
**
*******************************************************************************/
@Deprecated
public QRecordListMetaData addField(QFieldMetaData field)
{
return (withField(field));
}
} }

View File

@ -26,7 +26,6 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/******************************************************************************* /*******************************************************************************
@ -45,11 +44,7 @@ public class MockDeleteAction implements DeleteInterface
{ {
DeleteOutput rs = new DeleteOutput(); DeleteOutput rs = new DeleteOutput();
rs.setRecords(deleteInput.getPrimaryKeys().stream().map(primaryKey -> rs.setDeletedRecordCount(deleteInput.getPrimaryKeys().size());
new QRecord()
.withTableName(deleteInput.getTableName())
.withValue("id", primaryKey))
.toList());
return rs; return rs;
} }

View File

@ -26,6 +26,7 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/******************************************************************************* /*******************************************************************************
@ -46,6 +47,16 @@ public class MockInsertAction implements InsertInterface
rs.setRecords(insertInput.getRecords()); rs.setRecords(insertInput.getRecords());
String primaryKeyField = insertInput.getTable().getPrimaryKeyField();
int i = 1;
for(QRecord record : rs.getRecords())
{
if(record.getValue(primaryKeyField) == null)
{
record.setValue(primaryKeyField, i++);
}
}
return rs; return rs;
} }
catch(Exception e) catch(Exception e)

View File

@ -0,0 +1,91 @@
/*
* 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.bulk.delete;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Backend step to do a bulk delete.
*******************************************************************************/
public class BulkDeleteStoreStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
runBackendStepInput.getAsyncJobCallback().updateStatus("Deleting records in database...");
runBackendStepInput.getAsyncJobCallback().clearCurrentAndTotal();
DeleteInput deleteInput = new DeleteInput(runBackendStepInput.getInstance());
deleteInput.setSession(runBackendStepInput.getSession());
deleteInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
deleteInput.setTableName(runBackendStepInput.getTableName());
String queryFilterJSON = runBackendStepInput.getValueString("queryFilterJSON");
if(StringUtils.hasContent(queryFilterJSON))
{
try
{
deleteInput.setQueryFilter(JsonUtils.toObject(queryFilterJSON, QQueryFilter.class));
}
catch(IOException e)
{
throw (new QException("Error loading record query filter from process", e));
}
}
else if(CollectionUtils.nullSafeHasContents(runBackendStepInput.getRecords()))
{
String primaryKeyField = runBackendStepInput.getTable().getPrimaryKeyField();
List<Serializable> primaryKeyList = runBackendStepInput.getRecords().stream()
.map(r -> r.getValue(primaryKeyField))
.toList();
deleteInput.setPrimaryKeys(primaryKeyList);
}
DeleteAction deleteAction = new DeleteAction();
DeleteOutput deleteOutput = deleteAction.execute(deleteInput);
// todo - something with the output!!
deleteOutput.getRecordsWithErrors();
runBackendStepOutput.setRecords(runBackendStepInput.getRecords());
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.bulk.edit;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
** Backend step to receive values for a bulk edit.
*******************************************************************************/
public class BulkEditReceiveValuesStep implements BackendStep
{
public static final String FIELD_ENABLED_FIELDS = "bulkEditEnabledFields";
public static final String FIELD_VALUES_BEING_UPDATED = "valuesBeingUpdated";
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
String enabledFieldsString = runBackendStepInput.getValueString(FIELD_ENABLED_FIELDS);
String[] enabledFields = enabledFieldsString.split(",");
////////////////////////////////////////////////////////////////////////////////////////////
// put the value in all the records (note, this is just for display on the review screen, //
// and/or if we wanted to do some validation - this is NOT what will be store, as the //
// Update action only wants fields that are being changed. //
////////////////////////////////////////////////////////////////////////////////////////////
for(QRecord record : runBackendStepInput.getRecords())
{
for(String fieldName : enabledFields)
{
Serializable value = runBackendStepInput.getValue(fieldName);
record.setValue(fieldName, value);
}
}
/////////////////////////////////////////////////////////////////////
// build the string to show the user what fields are being changed //
/////////////////////////////////////////////////////////////////////
List<String> valuesBeingUpdated = new ArrayList<>();
QTableMetaData table = runBackendStepInput.getTable();
for(String fieldName : enabledFields)
{
String label = table.getField(fieldName).getLabel();
Serializable value = runBackendStepInput.getValue(fieldName);
if(StringUtils.hasContent(ValueUtils.getValueAsString(value)))
{
valuesBeingUpdated.add(label + " will be set to: " + value);
}
else
{
valuesBeingUpdated.add(label + " will be cleared out.");
}
}
runBackendStepOutput.addValue(FIELD_VALUES_BEING_UPDATED, String.join("\n", valuesBeingUpdated));
runBackendStepOutput.setRecords(runBackendStepInput.getRecords());
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.bulk.edit;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Backend step to store the records from a bulk insert file
*******************************************************************************/
public class BulkEditStoreRecordsStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
String enabledFieldsString = runBackendStepInput.getValueString(BulkEditReceiveValuesStep.FIELD_ENABLED_FIELDS);
String[] enabledFields = enabledFieldsString.split(",");
QTableMetaData table = runBackendStepInput.getTable();
List<QRecord> recordsToUpdate = new ArrayList<>();
runBackendStepInput.getAsyncJobCallback().updateStatus("Updating values in records...");
int i = 1;
for(QRecord record : runBackendStepInput.getRecords())
{
runBackendStepInput.getAsyncJobCallback().updateStatus(i++, runBackendStepInput.getRecords().size());
QRecord recordToUpdate = new QRecord();
recordsToUpdate.add(recordToUpdate);
recordToUpdate.setValue(table.getPrimaryKeyField(), record.getValue(table.getPrimaryKeyField()));
for(String fieldName : enabledFields)
{
Serializable value = runBackendStepInput.getValue(fieldName);
recordToUpdate.setValue(fieldName, value);
}
}
runBackendStepInput.getAsyncJobCallback().updateStatus("Updating database...");
runBackendStepInput.getAsyncJobCallback().clearCurrentAndTotal();
UpdateInput updateInput = new UpdateInput(runBackendStepInput.getInstance());
updateInput.setSession(runBackendStepInput.getSession());
updateInput.setAsyncJobCallback(runBackendStepInput.getAsyncJobCallback());
updateInput.setAreAllValuesBeingUpdatedTheSame(true);
updateInput.setTableName(runBackendStepInput.getTableName());
updateInput.setRecords(recordsToUpdate);
UpdateAction updateAction = new UpdateAction();
UpdateOutput updateOutput = updateAction.execute(updateInput);
runBackendStepOutput.setRecords(updateOutput.getRecords());
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.bulk.insert;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.data.QRecord;
/*******************************************************************************
** Backend step to receive a bulk-insert upload file
*******************************************************************************/
public class BulkInsertReceiveFileStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
List<QRecord> qRecords = BulkInsertUtils.getQRecordsFromFile(runBackendStepInput);
runBackendStepOutput.addValue("noOfFileRows", qRecords.size());
runBackendStepOutput.setRecords(qRecords);
}
}

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.processes.implementations.bulk.insert;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
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.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
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.data.QRecord;
/*******************************************************************************
** Backend step to store the records from a bulk insert file
*******************************************************************************/
public class BulkInsertStoreRecordsStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
List<QRecord> qRecords = BulkInsertUtils.getQRecordsFromFile(runBackendStepInput);
InsertInput insertInput = new InsertInput(runBackendStepInput.getInstance());
insertInput.setSession(runBackendStepInput.getSession());
insertInput.setTableName(runBackendStepInput.getTableName());
insertInput.setRecords(qRecords);
InsertAction insertAction = new InsertAction();
InsertOutput insertOutput = insertAction.execute(insertInput);
runBackendStepOutput.setRecords(insertOutput.getRecords());
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.bulk.insert;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping;
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.state.AbstractStateKey;
import com.kingsrook.qqq.backend.core.state.TempFileStateProvider;
/*******************************************************************************
** Utility methods used by bulk insert steps
*******************************************************************************/
public class BulkInsertUtils
{
/*******************************************************************************
**
*******************************************************************************/
static List<QRecord> getQRecordsFromFile(RunBackendStepInput runBackendStepInput) throws QException
{
AbstractStateKey stateKey = (AbstractStateKey) runBackendStepInput.getValue("uploadedFileKey");
Optional<QUploadedFile> optionalUploadedFile = TempFileStateProvider.getInstance().get(QUploadedFile.class, stateKey);
if(optionalUploadedFile.isEmpty())
{
throw (new QException("Could not find uploaded file"));
}
byte[] bytes = optionalUploadedFile.get().getBytes();
/////////////////////////////////////////////////////
// let the user specify field labels instead names //
/////////////////////////////////////////////////////
QTableMetaData table = runBackendStepInput.getTable();
QKeyBasedFieldMapping mapping = new QKeyBasedFieldMapping();
for(Map.Entry<String, QFieldMetaData> entry : table.getFields().entrySet())
{
mapping.addMapping(entry.getKey(), entry.getValue().getLabel());
}
// todo - sniff out file type...
String tableName = runBackendStepInput.getTableName();
List<QRecord> qRecords = new CsvToQRecordAdapter().buildRecordsFromCsv(new String(bytes), runBackendStepInput.getInstance().getTable(tableName), mapping);
////////////////////////////////////////////////
// remove values from any non-editable fields //
////////////////////////////////////////////////
List<QFieldMetaData> nonEditableFields = table.getFields().values().stream()
.filter(f -> !f.getIsEditable())
.toList();
if(!nonEditableFields.isEmpty())
{
for(QRecord qRecord : qRecords)
{
for(QFieldMetaData nonEditableField : nonEditableFields)
{
qRecord.setValue(nonEditableField.getName(), null);
}
}
}
return (qRecords);
}
}

View File

@ -26,12 +26,14 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.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.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
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.QFunctionInputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
/******************************************************************************* /*******************************************************************************
@ -49,8 +51,12 @@ public class LoadInitialRecordsStep implements BackendStep
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
///////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////
// actually, this is a no-op... we Just need a backendStep to be the first step in the process // // basically this is a no-op... we Just need a backendStep to be the first step in the process //
// but, while we're here, go ahead and put the query filter in the payload as a value, in case //
// someone else wants it (see BulkDelete) //
///////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////
QQueryFilter queryFilter = runBackendStepInput.getCallback().getQueryFilter();
runBackendStepOutput.addValue("queryFilterJSON", JsonUtils.toJson(queryFilter));
} }

View File

@ -49,20 +49,22 @@ public class MockBackendStep implements BackendStep
@Override @Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
runBackendStepOutput.getRecords().forEach(r ->
{
r.setValue(FIELD_MOCK_VALUE, "Ha ha!");
LOG.info("We are mocking {}: {}", r.getValueString("firstName"), r.getValue(FIELD_MOCK_VALUE));
});
runBackendStepOutput.setValues(runBackendStepInput.getValues());
runBackendStepOutput.addValue(FIELD_MOCK_VALUE, MOCK_VALUE);
///////////////////////////////// /////////////////////////////////
// mock the "greet" process... // // mock the "greet" process... //
///////////////////////////////// /////////////////////////////////
runBackendStepOutput.addValue("outputMessage", runBackendStepInput.getValueString(FIELD_GREETING_PREFIX) + " X " + runBackendStepInput.getValueString(FIELD_GREETING_SUFFIX)); runBackendStepOutput.addValue("outputMessage", runBackendStepInput.getValueString(FIELD_GREETING_PREFIX) + " X " + runBackendStepInput.getValueString(FIELD_GREETING_SUFFIX));
runBackendStepInput.getRecords().forEach(r ->
{
LOG.info("We are mocking {}: {}", r.getValueString("firstName"), r.getValue(FIELD_MOCK_VALUE));
r.setValue(FIELD_MOCK_VALUE, "Ha ha!");
r.setValue("greetingMessage", runBackendStepInput.getValueString(FIELD_GREETING_PREFIX) + " " + r.getValueString("firstName") + " " + runBackendStepInput.getValueString(FIELD_GREETING_SUFFIX));
});
runBackendStepOutput.setValues(runBackendStepInput.getValues());
runBackendStepOutput.addValue(FIELD_MOCK_VALUE, MOCK_VALUE);
runBackendStepOutput.addValue("noOfPeopleGreeted", runBackendStepInput.getRecords().size());
if("there".equalsIgnoreCase(runBackendStepInput.getValueString(FIELD_GREETING_SUFFIX))) if("there".equalsIgnoreCase(runBackendStepInput.getValueString(FIELD_GREETING_SUFFIX)))
{ {
throw (new QException("You said Hello There, didn't you...")); throw (new QException("You said Hello There, didn't you..."));

View File

@ -22,10 +22,13 @@
package com.kingsrook.qqq.backend.core.state; package com.kingsrook.qqq.backend.core.state;
import java.io.Serializable;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public abstract class AbstractStateKey public abstract class AbstractStateKey implements Serializable
{ {
/******************************************************************************* /*******************************************************************************
** Make the key give a unique string to identify itself. ** Make the key give a unique string to identify itself.

View File

@ -31,5 +31,7 @@ package com.kingsrook.qqq.backend.core.state;
public enum StateType public enum StateType
{ {
PROCESS_STATUS, PROCESS_STATUS,
ASYNC_JOB_STATUS ASYNC_JOB_STATUS,
ASYNC_JOB_RESULT,
UPLOADED_FILE
} }

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.state; package com.kingsrook.qqq.backend.core.state;
import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -29,7 +30,7 @@ import java.util.UUID;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class UUIDAndTypeStateKey extends AbstractStateKey public class UUIDAndTypeStateKey extends AbstractStateKey implements Serializable
{ {
private final UUID uuid; private final UUID uuid;
private final StateType stateType; private final StateType stateType;

View File

@ -109,6 +109,7 @@ public class RunBackendStepActionTest
XYZ"""; XYZ""";
case HTML -> "<b>Oh my</b>"; case HTML -> "<b>Oh my</b>";
case PASSWORD -> "myPa**word"; case PASSWORD -> "myPa**word";
case BLOB -> new byte[] { 1, 2, 3, 4 };
}); });
} }
return (rs); return (rs);

View File

@ -26,10 +26,14 @@ import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/******************************************************************************* /*******************************************************************************
@ -53,8 +57,28 @@ class DeleteActionTest
request.setPrimaryKeys(List.of(1, 2)); request.setPrimaryKeys(List.of(1, 2));
DeleteOutput result = new DeleteAction().execute(request); DeleteOutput result = new DeleteAction().execute(request);
assertNotNull(result); assertNotNull(result);
assertEquals(2, result.getRecords().size()); assertEquals(2, result.getDeletedRecordCount());
// todo - add errors to QRecord? assertTrue(result.getRecords().stream().allMatch(r -> r.getErrors() == null)); assertTrue(CollectionUtils.nullSafeIsEmpty(result.getRecordsWithErrors()));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testErrorIfBothPrimaryKeysAndFilter()
{
DeleteInput request = new DeleteInput(TestUtils.defineInstance());
request.setSession(TestUtils.getMockSession());
request.setTableName("person");
request.setPrimaryKeys(List.of(1, 2));
request.setQueryFilter(new QQueryFilter());
assertThrows(QException.class, () ->
{
new DeleteAction().execute(request);
});
} }
} }

View File

@ -0,0 +1,71 @@
/*
* 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.bulk.delete;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for BulkDeleteStoreStep
*******************************************************************************/
class BulkDeleteStoreStepTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testWithoutFilter() throws QException
{
RunBackendStepInput stepInput = new RunBackendStepInput(TestUtils.defineInstance());
stepInput.setSession(TestUtils.getMockSession());
stepInput.setTableName(TestUtils.defineTablePerson().getName());
stepInput.setRecords(TestUtils.queryTable(TestUtils.defineTablePerson().getName()));
RunBackendStepOutput stepOutput = new RunBackendStepOutput();
new BulkDeleteStoreStep().run(stepInput, stepOutput);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testWithFilter() throws QException
{
RunBackendStepInput stepInput = new RunBackendStepInput(TestUtils.defineInstance());
stepInput.setSession(TestUtils.getMockSession());
stepInput.setTableName(TestUtils.defineTablePerson().getName());
stepInput.addValue("queryFilterJSON", JsonUtils.toJson(new QQueryFilter()));
RunBackendStepOutput stepOutput = new RunBackendStepOutput();
new BulkDeleteStoreStep().run(stepInput, stepOutput);
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.bulk.edit;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
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.assertNull;
/*******************************************************************************
** Unit test for BulkEditReceiveValuesStep
*******************************************************************************/
class BulkEditReceiveValuesStepTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
RunBackendStepInput stepInput = new RunBackendStepInput(TestUtils.defineInstance());
stepInput.setSession(TestUtils.getMockSession());
stepInput.setTableName(TestUtils.defineTablePerson().getName());
stepInput.addValue(BulkEditReceiveValuesStep.FIELD_ENABLED_FIELDS, "firstName,email,birthDate");
stepInput.addValue("firstName", "Johnny");
stepInput.addValue("email", null);
stepInput.addValue("birthDate", "1909-01-09");
List<QRecord> records = TestUtils.queryTable(TestUtils.defineTablePerson().getName());
stepInput.setRecords(records);
RunBackendStepOutput stepOutput = new RunBackendStepOutput();
new BulkEditReceiveValuesStep().run(stepInput, stepOutput);
String valuesBeingUpdated = stepOutput.getValueString(BulkEditReceiveValuesStep.FIELD_VALUES_BEING_UPDATED);
assertThat(valuesBeingUpdated).matches("(?s).*First Name.*Johnny.*");
assertThat(valuesBeingUpdated).matches("(?s).*Email will be cleared.*");
assertThat(valuesBeingUpdated).matches("(?s).*Birth Date.*1909-01-09.*");
int count = 0;
for(QRecord record : stepOutput.getRecords())
{
assertEquals("Johnny", record.getValueString("firstName"));
assertNull(record.getValue("email"));
// todo value utils needed in getValueDate... assertEquals(LocalDate.of(1909, 1, 9), record.getValueDate("birthDate"));
count++;
}
assertEquals(records.size(), count);
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.bulk.edit;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for BulkEditStoreRecordsStep
*******************************************************************************/
class BulkEditStoreRecordsStepTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
RunBackendStepInput stepInput = new RunBackendStepInput(TestUtils.defineInstance());
stepInput.setSession(TestUtils.getMockSession());
stepInput.setTableName(TestUtils.defineTablePerson().getName());
stepInput.addValue(BulkEditReceiveValuesStep.FIELD_ENABLED_FIELDS, "firstName,email,birthDate");
stepInput.addValue("firstName", "Johnny");
stepInput.addValue("email", null);
stepInput.addValue("birthDate", "1909-01-09");
List<QRecord> records = TestUtils.queryTable(TestUtils.defineTablePerson().getName());
stepInput.setRecords(records);
RunBackendStepOutput stepOutput = new RunBackendStepOutput();
new BulkEditStoreRecordsStep().run(stepInput, stepOutput);
assertRecordValues(stepOutput.getRecords());
// re-fetch the records, make sure they are updated.
// but since Mock backend doesn't actually update them, we can't do this..
// todo - implement an in-memory backend, that would do this.
// List<QRecord> updatedRecords = TestUtils.queryTable(TestUtils.defineTablePerson().getName());
// assertRecordValues(updatedRecords);
}
/*******************************************************************************
**
*******************************************************************************/
private void assertRecordValues(List<QRecord> records)
{
for(QRecord record : records)
{
assertEquals("Johnny", record.getValueString("firstName"));
assertNull(record.getValue("email"));
// todo value utils needed in getValueDate... assertEquals(LocalDate.of(1909, 1, 9), record.getValueDate("birthDate"));
}
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.bulk.insert;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.TempFileStateProvider;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/*******************************************************************************
** Unit test for BulkInsertReceiveFileStep
*******************************************************************************/
class BulkInsertReceiveFileStepTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
////////////////////////////////////////////////////////////////
// create an uploaded file, similar to how an http server may //
////////////////////////////////////////////////////////////////
QUploadedFile qUploadedFile = new QUploadedFile();
qUploadedFile.setBytes((TestUtils.getPersonCsvHeaderUsingLabels() + TestUtils.getPersonCsvRow1() + TestUtils.getPersonCsvRow2()).getBytes());
qUploadedFile.setFilename("test.csv");
UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
TempFileStateProvider.getInstance().put(key, qUploadedFile);
////////////////////////////
// setup and run the step //
////////////////////////////
RunBackendStepInput stepInput = new RunBackendStepInput(TestUtils.defineInstance());
stepInput.setSession(TestUtils.getMockSession());
stepInput.setTableName(TestUtils.defineTablePerson().getName());
stepInput.addValue("uploadedFileKey", key);
RunBackendStepOutput stepOutput = new RunBackendStepOutput();
new BulkInsertReceiveFileStep().run(stepInput, stepOutput);
List<QRecord> records = stepOutput.getRecords();
assertEquals(2, records.size());
assertEquals("John", records.get(0).getValueString("firstName"));
assertEquals("Jane", records.get(1).getValueString("firstName"));
assertNull(records.get(0).getValue("id"));
assertNull(records.get(1).getValue("id"));
assertEquals(2, stepOutput.getValueInteger("noOfFileRows"));
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.bulk.insert;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.QUploadedFile;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.state.StateType;
import com.kingsrook.qqq.backend.core.state.TempFileStateProvider;
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for BulkInsertStoreRecordsStep
*******************************************************************************/
class BulkInsertStoreRecordsStepTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
////////////////////////////////////////////////////////////////
// create an uploaded file, similar to how an http server may //
////////////////////////////////////////////////////////////////
QUploadedFile qUploadedFile = new QUploadedFile();
qUploadedFile.setBytes((TestUtils.getPersonCsvHeaderUsingLabels() + TestUtils.getPersonCsvRow1() + TestUtils.getPersonCsvRow2()).getBytes());
qUploadedFile.setFilename("test.csv");
UUIDAndTypeStateKey key = new UUIDAndTypeStateKey(StateType.UPLOADED_FILE);
TempFileStateProvider.getInstance().put(key, qUploadedFile);
////////////////////////////
// setup and run the step //
////////////////////////////
RunBackendStepInput stepInput = new RunBackendStepInput(TestUtils.defineInstance());
stepInput.setSession(TestUtils.getMockSession());
stepInput.setTableName(TestUtils.defineTablePerson().getName());
stepInput.addValue("uploadedFileKey", key);
RunBackendStepOutput stepOutput = new RunBackendStepOutput();
new BulkInsertStoreRecordsStep().run(stepInput, stepOutput);
List<QRecord> records = stepOutput.getRecords();
assertEquals(2, records.size());
assertEquals("John", records.get(0).getValueString("firstName"));
assertEquals("Jane", records.get(1).getValueString("firstName"));
assertNotNull(records.get(0).getValue("id"));
assertNotNull(records.get(1).getValue("id"));
}
}

View File

@ -25,7 +25,12 @@ package com.kingsrook.qqq.backend.core.utils;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge; import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics; import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter; import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.model.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
@ -136,9 +141,9 @@ public class TestUtils
.withLabel("Person") .withLabel("Person")
.withBackendName(DEFAULT_BACKEND_NAME) .withBackendName(DEFAULT_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME)) .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME)) .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("firstName", QFieldType.STRING)) .withField(new QFieldMetaData("firstName", QFieldType.STRING))
.withField(new QFieldMetaData("lastName", QFieldType.STRING)) .withField(new QFieldMetaData("lastName", QFieldType.STRING))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE)) .withField(new QFieldMetaData("birthDate", QFieldType.DATE))
@ -306,4 +311,67 @@ public class TestUtils
MockAuthenticationModule mockAuthenticationModule = new MockAuthenticationModule(); MockAuthenticationModule mockAuthenticationModule = new MockAuthenticationModule();
return (mockAuthenticationModule.createSession(null)); return (mockAuthenticationModule.createSession(null));
} }
/*******************************************************************************
**
*******************************************************************************/
public static List<QRecord> queryTable(String tableName) throws QException
{
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
queryInput.setSession(TestUtils.getMockSession());
queryInput.setTableName(tableName);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
return (queryOutput.getRecords());
}
/*******************************************************************************
**
*******************************************************************************/
public static String getPersonCsvHeader()
{
return ("""
"id","createDate","modifyDate","firstName","lastName","birthDate","email"\r
""");
}
/*******************************************************************************
**
*******************************************************************************/
public static String getPersonCsvHeaderUsingLabels()
{
return ("""
"Id","Create Date","Modify Date","First Name","Last Name","Birth Date","Email"\r
""");
}
/*******************************************************************************
**
*******************************************************************************/
public static String getPersonCsvRow1()
{
return ("""
"0","2021-10-26 14:39:37","2021-10-26 14:39:37","John","Doe","1980-01-01","john@doe.com"\r
""");
}
/*******************************************************************************
**
*******************************************************************************/
public static String getPersonCsvRow2()
{
return ("""
"0","2021-10-26 14:39:37","2021-10-26 14:39:37","Jane","Doe","1981-01-01","john@doe.com"\r
""");
}
} }