mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 21:20:45 +00:00
Updates to allow validations on bulk-edit, with warnings and errors coming back on review & result screens.
This commit is contained in:
@ -52,6 +52,7 @@ public abstract class AbstractPreUpdateCustomizer
|
||||
{
|
||||
protected UpdateInput updateInput;
|
||||
protected List<QRecord> oldRecordList;
|
||||
protected boolean isPreview = false;
|
||||
|
||||
private Map<Serializable, QRecord> oldRecordMap = null;
|
||||
|
||||
@ -123,4 +124,35 @@ public abstract class AbstractPreUpdateCustomizer
|
||||
return (oldRecordMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isPreview
|
||||
*******************************************************************************/
|
||||
public boolean getIsPreview()
|
||||
{
|
||||
return (this.isPreview);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isPreview
|
||||
*******************************************************************************/
|
||||
public void setIsPreview(boolean isPreview)
|
||||
{
|
||||
this.isPreview = isPreview;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isPreview
|
||||
*******************************************************************************/
|
||||
public AbstractPreUpdateCustomizer withIsPreview(boolean isPreview)
|
||||
{
|
||||
this.isPreview = isPreview;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,30 +105,7 @@ public class UpdateAction
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<List<QRecord>> oldRecordList = fetchOldRecords(updateInput, updateInterface);
|
||||
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), table, updateInput.getRecords());
|
||||
validatePrimaryKeysAreGiven(updateInput);
|
||||
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList.get());
|
||||
}
|
||||
|
||||
validateRequiredFields(updateInput);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-update customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPreUpdateCustomizer> preUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPreUpdateCustomizer.class, table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
preUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
oldRecordList.ifPresent(l -> preUpdateCustomizer.get().setOldRecordList(l));
|
||||
updateInput.setRecords(preUpdateCustomizer.get().apply(updateInput.getRecords()));
|
||||
}
|
||||
performValidations(updateInput, oldRecordList, false);
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the update //
|
||||
@ -191,6 +168,42 @@ public class UpdateAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void performValidations(UpdateInput updateInput, Optional<List<QRecord>> oldRecordList, boolean isPreview) throws QException
|
||||
{
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), table, updateInput.getRecords());
|
||||
validatePrimaryKeysAreGiven(updateInput);
|
||||
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList.get());
|
||||
}
|
||||
|
||||
validateRequiredFields(updateInput);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-update customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<AbstractPreUpdateCustomizer> preUpdateCustomizer = QCodeLoader.getTableCustomizer(AbstractPreUpdateCustomizer.class, table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
preUpdateCustomizer.get().setUpdateInput(updateInput);
|
||||
preUpdateCustomizer.get().setIsPreview(isPreview);
|
||||
oldRecordList.ifPresent(l -> preUpdateCustomizer.get().setOldRecordList(l));
|
||||
updateInput.setRecords(preUpdateCustomizer.get().apply(updateInput.getRecords()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -318,7 +331,7 @@ public class UpdateAction
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(record.getValues().containsKey(requiredField.getName()))
|
||||
{
|
||||
if(record.getValue(requiredField.getName()) == null || (requiredField.getType().isStringLike() && record.getValueString(requiredField.getName()).trim().equals("")))
|
||||
if(record.getValue(requiredField.getName()) == null || record.getValueString(requiredField.getName()).trim().equals(""))
|
||||
{
|
||||
record.addError(new BadInputStatusMessage("Missing value in required field: " + requiredField.getLabel()));
|
||||
}
|
||||
|
@ -66,13 +66,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMeta
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditLoadStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertExtractStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaDeleteStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaInsertStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaUpdateStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -761,7 +761,7 @@ public class QInstanceEnricher
|
||||
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(
|
||||
ExtractViaQueryStep.class,
|
||||
BulkEditTransformStep.class,
|
||||
LoadViaUpdateStep.class,
|
||||
BulkEditLoadStep.class,
|
||||
values
|
||||
)
|
||||
.withName(processName)
|
||||
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
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.Status;
|
||||
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.processes.implementations.etl.streamedwithfrontend.LoadViaUpdateStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ProcessSummaryProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.general.ProcessSummaryWarningsAndErrorsRollup;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import static com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep.buildInfoSummaryLines;
|
||||
import static com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep.getProcessSummaryWarningsAndErrorsRollup;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Load step for generic table bulk-edit ETL process
|
||||
*******************************************************************************/
|
||||
public class BulkEditLoadStep extends LoadViaUpdateStep implements ProcessSummaryProviderInterface
|
||||
{
|
||||
public static final String FIELD_ENABLED_FIELDS = "bulkEditEnabledFields";
|
||||
|
||||
private ProcessSummaryLine okSummary = new ProcessSummaryLine(Status.OK);
|
||||
private List<ProcessSummaryLine> infoSummaries = new ArrayList<>();
|
||||
|
||||
private ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = getProcessSummaryWarningsAndErrorsRollup();
|
||||
|
||||
private String tableLabel;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput runBackendStepOutput, boolean isForResultScreen)
|
||||
{
|
||||
ArrayList<ProcessSummaryLineInterface> rs = new ArrayList<>();
|
||||
|
||||
String noWarningsSuffix = processSummaryWarningsAndErrorsRollup.countWarnings() == 0 ? "" : " with no warnings";
|
||||
|
||||
okSummary.setSingularPastMessage(tableLabel + " record was edited" + noWarningsSuffix + ".");
|
||||
okSummary.setPluralPastMessage(tableLabel + " records were edited" + noWarningsSuffix + ".");
|
||||
okSummary.pickMessage(isForResultScreen);
|
||||
okSummary.addSelfToListIfAnyCount(rs);
|
||||
|
||||
processSummaryWarningsAndErrorsRollup.addToList(rs);
|
||||
rs.addAll(infoSummaries);
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void preRun(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
super.preRun(runBackendStepInput, runBackendStepOutput);
|
||||
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
if(table != null)
|
||||
{
|
||||
tableLabel = table.getLabel();
|
||||
}
|
||||
|
||||
buildInfoSummaryLines(runBackendStepInput, table, infoSummaries, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
|
||||
{
|
||||
QTableMetaData table = runBackendStepInput.getInstance().getTable(runBackendStepInput.getTableName());
|
||||
|
||||
super.run(runBackendStepInput, runBackendStepOutput);
|
||||
for(QRecord record : runBackendStepOutput.getRecords())
|
||||
{
|
||||
Serializable recordPrimaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
String message = record.getErrors().get(0).getMessage();
|
||||
processSummaryWarningsAndErrorsRollup.addError(message, recordPrimaryKey);
|
||||
}
|
||||
else if(CollectionUtils.nullSafeHasContents(record.getWarnings()))
|
||||
{
|
||||
String message = record.getWarnings().get(0).getMessage();
|
||||
processSummaryWarningsAndErrorsRollup.addWarning(message, recordPrimaryKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
okSummary.incrementCountAndAddPrimaryKey(recordPrimaryKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -24,7 +24,11 @@ package com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -33,11 +37,14 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine
|
||||
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.Status;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
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.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.general.ProcessSummaryWarningsAndErrorsRollup;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
@ -52,6 +59,8 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
private ProcessSummaryLine okSummary = new ProcessSummaryLine(Status.OK);
|
||||
private List<ProcessSummaryLine> infoSummaries = new ArrayList<>();
|
||||
|
||||
private ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = getProcessSummaryWarningsAndErrorsRollup();
|
||||
|
||||
private QTableMetaData table;
|
||||
private String tableLabel;
|
||||
private String[] enabledFields;
|
||||
@ -62,6 +71,36 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** used by Load step too
|
||||
*******************************************************************************/
|
||||
static ProcessSummaryWarningsAndErrorsRollup getProcessSummaryWarningsAndErrorsRollup()
|
||||
{
|
||||
return new ProcessSummaryWarningsAndErrorsRollup()
|
||||
.withErrorTemplate(new ProcessSummaryLine(Status.ERROR)
|
||||
.withSingularFutureMessage("record has an error: ")
|
||||
.withPluralFutureMessage("records have an error: ")
|
||||
.withSingularPastMessage("record had an error: ")
|
||||
.withPluralPastMessage("records had an error: "))
|
||||
.withWarningTemplate(new ProcessSummaryLine(Status.WARNING)
|
||||
.withSingularFutureMessage("record will be edited, but has a warning: ")
|
||||
.withPluralFutureMessage("records will be edited, but have a warning: ")
|
||||
.withSingularPastMessage("record was edited, but had a warning: ")
|
||||
.withPluralPastMessage("records were edited, but had a warning: "))
|
||||
.withOtherErrorsSummary(new ProcessSummaryLine(Status.ERROR)
|
||||
.withSingularFutureMessage("record has an other error.")
|
||||
.withPluralFutureMessage("records have other errors.")
|
||||
.withSingularPastMessage("record had an other error.")
|
||||
.withPluralPastMessage("records had other errors."))
|
||||
.withOtherWarningsSummary(new ProcessSummaryLine(Status.WARNING)
|
||||
.withSingularFutureMessage("record will be edited, but has an other warning.")
|
||||
.withPluralFutureMessage("records will be edited, but have other warnings.")
|
||||
.withSingularPastMessage("record was edited, but had other warnings.")
|
||||
.withPluralPastMessage("records were edited, but had other warnings."));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -77,14 +116,14 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
tableLabel = table.getLabel();
|
||||
}
|
||||
|
||||
String enabledFieldsString = runBackendStepInput.getValueString(FIELD_ENABLED_FIELDS);
|
||||
enabledFields = enabledFieldsString.split(",");
|
||||
|
||||
isValidateStep = runBackendStepInput.getStepName().equals(StreamedETLWithFrontendProcess.STEP_NAME_VALIDATE);
|
||||
isExecuteStep = runBackendStepInput.getStepName().equals(StreamedETLWithFrontendProcess.STEP_NAME_EXECUTE);
|
||||
haveRecordCount = runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_RECORD_COUNT) != null;
|
||||
|
||||
buildInfoSummaryLines(runBackendStepInput, enabledFields);
|
||||
String enabledFieldsString = runBackendStepInput.getValueString(FIELD_ENABLED_FIELDS);
|
||||
enabledFields = enabledFieldsString.split(",");
|
||||
|
||||
buildInfoSummaryLines(runBackendStepInput, table, infoSummaries, isExecuteStep);
|
||||
}
|
||||
|
||||
|
||||
@ -129,22 +168,63 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
outputRecords.add(recordToUpdate);
|
||||
setUpdatedFieldsInRecord(runBackendStepInput, enabledFields, recordToUpdate);
|
||||
}
|
||||
|
||||
okSummary.incrementCount(runBackendStepInput.getRecords().size());
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Build records-to-update for passing into the validation method of the Update action //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> recordsForValidation = new ArrayList<>();
|
||||
Map<Serializable, QRecord> pkeyToFullRecordMap = new HashMap<>();
|
||||
for(QRecord record : runBackendStepInput.getRecords())
|
||||
{
|
||||
outputRecords.add(record);
|
||||
QRecord recordToUpdate = new QRecord();
|
||||
recordToUpdate.setValue(table.getPrimaryKeyField(), record.getValue(table.getPrimaryKeyField()));
|
||||
setUpdatedFieldsInRecord(runBackendStepInput, enabledFields, recordToUpdate);
|
||||
recordsForValidation.add(recordToUpdate);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// put the full record (with updated values) in the output //
|
||||
/////////////////////////////////////////////////////////////
|
||||
setUpdatedFieldsInRecord(runBackendStepInput, enabledFields, record);
|
||||
pkeyToFullRecordMap.put(record.getValue(table.getPrimaryKeyField()), record);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// run the validation - critically - in preview mode (boolean param) //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(table.getName());
|
||||
updateInput.setRecords(recordsForValidation);
|
||||
new UpdateAction().performValidations(updateInput, Optional.of(runBackendStepInput.getRecords()), true);
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// look at the update input to build process summary lines //
|
||||
/////////////////////////////////////////////////////////////
|
||||
for(QRecord record : updateInput.getRecords())
|
||||
{
|
||||
Serializable recordPrimaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
String message = record.getErrors().get(0).getMessage();
|
||||
processSummaryWarningsAndErrorsRollup.addError(message, recordPrimaryKey);
|
||||
}
|
||||
else if(CollectionUtils.nullSafeHasContents(record.getWarnings()))
|
||||
{
|
||||
String message = record.getWarnings().get(0).getMessage();
|
||||
processSummaryWarningsAndErrorsRollup.addWarning(message, recordPrimaryKey);
|
||||
outputRecords.add(pkeyToFullRecordMap.get(recordPrimaryKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
okSummary.incrementCountAndAddPrimaryKey(recordPrimaryKey);
|
||||
outputRecords.add(pkeyToFullRecordMap.get(recordPrimaryKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
runBackendStepOutput.setRecords(outputRecords);
|
||||
okSummary.incrementCount(runBackendStepInput.getRecords().size());
|
||||
}
|
||||
|
||||
|
||||
@ -152,9 +232,11 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void buildInfoSummaryLines(RunBackendStepInput runBackendStepInput, String[] enabledFields)
|
||||
static void buildInfoSummaryLines(RunBackendStepInput runBackendStepInput, QTableMetaData table, List<ProcessSummaryLine> infoSummaries, boolean isExecuteStep)
|
||||
{
|
||||
QValueFormatter qValueFormatter = new QValueFormatter();
|
||||
String enabledFieldsString = runBackendStepInput.getValueString(FIELD_ENABLED_FIELDS);
|
||||
String[] enabledFields = enabledFieldsString.split(",");
|
||||
|
||||
for(String fieldName : enabledFields)
|
||||
{
|
||||
QFieldMetaData field = table.getField(fieldName);
|
||||
@ -168,7 +250,7 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
String verb = isExecuteStep ? "was" : "will be";
|
||||
if(StringUtils.hasContent(ValueUtils.getValueAsString(value)))
|
||||
{
|
||||
String formattedValue = qValueFormatter.formatValue(field, value);
|
||||
String formattedValue = QValueFormatter.formatValue(field, value);
|
||||
|
||||
if(field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
@ -211,15 +293,19 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
||||
@Override
|
||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput runBackendStepOutput, boolean isForResultScreen)
|
||||
{
|
||||
okSummary.setSingularFutureMessage(tableLabel + " record will be edited.");
|
||||
okSummary.setPluralFutureMessage(tableLabel + " records will be edited.");
|
||||
okSummary.setSingularPastMessage(tableLabel + " record was edited.");
|
||||
okSummary.setPluralPastMessage(tableLabel + " records were edited.");
|
||||
okSummary.pickMessage(isForResultScreen);
|
||||
|
||||
ArrayList<ProcessSummaryLineInterface> rs = new ArrayList<>();
|
||||
rs.add(okSummary);
|
||||
|
||||
String noWarningsSuffix = processSummaryWarningsAndErrorsRollup.countWarnings() == 0 ? "" : " with no warnings";
|
||||
|
||||
okSummary.setSingularFutureMessage(tableLabel + " record will be edited" + noWarningsSuffix + ".");
|
||||
okSummary.setPluralFutureMessage(tableLabel + " records will be edited" + noWarningsSuffix + ".");
|
||||
okSummary.pickMessage(isForResultScreen);
|
||||
okSummary.addSelfToListIfAnyCount(rs);
|
||||
|
||||
processSummaryWarningsAndErrorsRollup.addToList(rs);
|
||||
|
||||
rs.addAll(infoSummaries);
|
||||
return (rs);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.general;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ProcessSummaryWarningsAndErrorsRollup
|
||||
{
|
||||
private Map<String, ProcessSummaryLine> errorSummaries = new HashMap<>();
|
||||
private Map<String, ProcessSummaryLine> warningSummaries = new HashMap<>();
|
||||
|
||||
private ProcessSummaryLine otherErrorsSummary;
|
||||
private ProcessSummaryLine otherWarningsSummary;
|
||||
private ProcessSummaryLine errorTemplate;
|
||||
private ProcessSummaryLine warningTemplate;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addToList(ArrayList<ProcessSummaryLineInterface> list)
|
||||
{
|
||||
addProcessSummaryLinesFromMap(list, errorSummaries);
|
||||
if(otherErrorsSummary != null)
|
||||
{
|
||||
otherErrorsSummary.addSelfToListIfAnyCount(list);
|
||||
}
|
||||
|
||||
addProcessSummaryLinesFromMap(list, warningSummaries);
|
||||
if(otherWarningsSummary != null)
|
||||
{
|
||||
otherWarningsSummary.addSelfToListIfAnyCount(list);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addError(String message, Serializable primaryKey)
|
||||
{
|
||||
add(Status.ERROR, errorSummaries, errorTemplate, message, primaryKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addWarning(String message, Serializable primaryKey)
|
||||
{
|
||||
add(Status.WARNING, warningSummaries, warningTemplate, message, primaryKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int countWarnings()
|
||||
{
|
||||
int sum = 0;
|
||||
for(ProcessSummaryLine processSummaryLine : warningSummaries.values())
|
||||
{
|
||||
sum += Objects.requireNonNullElse(processSummaryLine.getCount(), 0);
|
||||
}
|
||||
if(otherWarningsSummary != null)
|
||||
{
|
||||
sum += Objects.requireNonNullElse(otherWarningsSummary.getCount(), 0);
|
||||
}
|
||||
return (sum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int countErrors()
|
||||
{
|
||||
int sum = 0;
|
||||
for(ProcessSummaryLine processSummaryLine : errorSummaries.values())
|
||||
{
|
||||
sum += Objects.requireNonNullElse(processSummaryLine.getCount(), 0);
|
||||
}
|
||||
if(otherErrorsSummary != null)
|
||||
{
|
||||
sum += Objects.requireNonNullElse(otherErrorsSummary.getCount(), 0);
|
||||
}
|
||||
return (sum);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void add(Status status, Map<String, ProcessSummaryLine> summaryLineMap, ProcessSummaryLine templateLine, String message, Serializable primaryKey)
|
||||
{
|
||||
ProcessSummaryLine processSummaryLine = summaryLineMap.get(message);
|
||||
if(processSummaryLine == null)
|
||||
{
|
||||
if(summaryLineMap.size() < 50)
|
||||
{
|
||||
processSummaryLine = new ProcessSummaryLine(status)
|
||||
.withMessageSuffix(message)
|
||||
.withSingularFutureMessage(templateLine.getSingularFutureMessage())
|
||||
.withPluralFutureMessage(templateLine.getPluralFutureMessage())
|
||||
.withSingularPastMessage(templateLine.getSingularPastMessage())
|
||||
.withPluralPastMessage(templateLine.getPluralPastMessage());
|
||||
summaryLineMap.put(message, processSummaryLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(status.equals(Status.ERROR))
|
||||
{
|
||||
if(otherErrorsSummary == null)
|
||||
{
|
||||
otherErrorsSummary = new ProcessSummaryLine(Status.ERROR).withMessageSuffix("records had an other error.");
|
||||
}
|
||||
processSummaryLine = otherErrorsSummary;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(otherWarningsSummary == null)
|
||||
{
|
||||
otherWarningsSummary = new ProcessSummaryLine(Status.WARNING).withMessageSuffix("records had an other warning.");
|
||||
}
|
||||
processSummaryLine = otherWarningsSummary;
|
||||
}
|
||||
}
|
||||
}
|
||||
processSummaryLine.incrementCountAndAddPrimaryKey(primaryKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** sort the process summary lines by count desc
|
||||
*******************************************************************************/
|
||||
private static void addProcessSummaryLinesFromMap(ArrayList<ProcessSummaryLineInterface> rs, Map<String, ProcessSummaryLine> summaryMap)
|
||||
{
|
||||
summaryMap.values().stream()
|
||||
.sorted(Comparator.comparing((ProcessSummaryLine psl) -> Objects.requireNonNullElse(psl.getCount(), 0)).reversed()
|
||||
.thenComparing((ProcessSummaryLine psl) -> Objects.requireNonNullElse(psl.getMessage(), ""))
|
||||
.thenComparing((ProcessSummaryLine psl) -> Objects.requireNonNullElse(psl.getMessageSuffix(), ""))
|
||||
)
|
||||
.forEach(psl -> psl.addSelfToListIfAnyCount(rs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for otherErrorsSummary
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine getOtherErrorsSummary()
|
||||
{
|
||||
return (this.otherErrorsSummary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for otherErrorsSummary
|
||||
*******************************************************************************/
|
||||
public void setOtherErrorsSummary(ProcessSummaryLine otherErrorsSummary)
|
||||
{
|
||||
this.otherErrorsSummary = otherErrorsSummary;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for otherErrorsSummary
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryWarningsAndErrorsRollup withOtherErrorsSummary(ProcessSummaryLine otherErrorsSummary)
|
||||
{
|
||||
this.otherErrorsSummary = otherErrorsSummary;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for otherWarningsSummary
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine getOtherWarningsSummary()
|
||||
{
|
||||
return (this.otherWarningsSummary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for otherWarningsSummary
|
||||
*******************************************************************************/
|
||||
public void setOtherWarningsSummary(ProcessSummaryLine otherWarningsSummary)
|
||||
{
|
||||
this.otherWarningsSummary = otherWarningsSummary;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for otherWarningsSummary
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryWarningsAndErrorsRollup withOtherWarningsSummary(ProcessSummaryLine otherWarningsSummary)
|
||||
{
|
||||
this.otherWarningsSummary = otherWarningsSummary;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for errorTemplate
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine getErrorTemplate()
|
||||
{
|
||||
return (this.errorTemplate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for errorTemplate
|
||||
*******************************************************************************/
|
||||
public void setErrorTemplate(ProcessSummaryLine errorTemplate)
|
||||
{
|
||||
this.errorTemplate = errorTemplate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for errorTemplate
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryWarningsAndErrorsRollup withErrorTemplate(ProcessSummaryLine errorTemplate)
|
||||
{
|
||||
this.errorTemplate = errorTemplate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for warningTemplate
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine getWarningTemplate()
|
||||
{
|
||||
return (this.warningTemplate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for warningTemplate
|
||||
*******************************************************************************/
|
||||
public void setWarningTemplate(ProcessSummaryLine warningTemplate)
|
||||
{
|
||||
this.warningTemplate = warningTemplate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for warningTemplate
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryWarningsAndErrorsRollup withWarningTemplate(ProcessSummaryLine warningTemplate)
|
||||
{
|
||||
this.warningTemplate = warningTemplate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -22,9 +22,12 @@
|
||||
package com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPreUpdateCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -37,6 +40,10 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
@ -143,4 +150,231 @@ class BulkEditTest extends BaseTest
|
||||
assertEquals("james.maes@kingsrook.com", records.get(2).getValue("email"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testWarningsAndErrors() throws QException
|
||||
{
|
||||
//////////////////////////////
|
||||
// insert some test records //
|
||||
//////////////////////////////
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
List<QRecord> personsToLoad = new ArrayList<>();
|
||||
for(int i = 0; i < 100; i++)
|
||||
{
|
||||
personsToLoad.add(new QRecord().withValue("id", i).withValue("firstName", "Darin" + i));
|
||||
}
|
||||
TestUtils.insertRecords(table, personsToLoad);
|
||||
|
||||
table.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PersonPreUpdateReusedMessages.class));
|
||||
|
||||
//////////////////////////////////
|
||||
// set up the run-process input //
|
||||
//////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(TestUtils.TABLE_NAME_PERSON_MEMORY + ".bulkEdit");
|
||||
runProcessInput.addValue(StreamedETLWithFrontendProcess.FIELD_DEFAULT_QUERY_FILTER,
|
||||
new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, 100)));
|
||||
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
String processUUID = runProcessOutput.getProcessUUID();
|
||||
|
||||
runProcessInput.addValue(BulkEditTransformStep.FIELD_ENABLED_FIELDS, "firstName");
|
||||
runProcessInput.addValue("firstName", "Johnny");
|
||||
|
||||
runProcessInput.setProcessUUID(processUUID);
|
||||
runProcessInput.setStartAfterStep("edit");
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertThat(runProcessOutput.getRecords()).hasSize(0);
|
||||
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo("review");
|
||||
|
||||
runProcessInput.addValue(StreamedETLWithFrontendProcess.FIELD_DO_FULL_VALIDATION, true);
|
||||
runProcessInput.setStartAfterStep("review");
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertThat(runProcessOutput.getRecords()).hasSize(20);
|
||||
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo("review");
|
||||
assertThat(runProcessOutput.getValues().get(StreamedETLWithFrontendProcess.FIELD_VALIDATION_SUMMARY)).isNotNull().isInstanceOf(List.class);
|
||||
|
||||
runProcessInput.setStartAfterStep("review");
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
assertThat(runProcessOutput.getRecords()).hasSize(20);
|
||||
assertThat(runProcessOutput.getProcessState().getNextStepName()).isPresent().get().isEqualTo("result");
|
||||
assertThat(runProcessOutput.getValues().get(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY)).isNotNull().isInstanceOf(List.class);
|
||||
assertThat(runProcessOutput.getException()).isEmpty();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ProcessSummaryLine> processSummaryLines = (List<ProcessSummaryLine>) runProcessOutput.getValues().get(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY);
|
||||
assertThat(processSummaryLines).hasSize(4);
|
||||
|
||||
assertThat(processSummaryLines.get(0))
|
||||
.hasFieldOrPropertyWithValue("status", Status.OK)
|
||||
.hasFieldOrPropertyWithValue("count", 10)
|
||||
.matches(psl -> psl.getMessage().contains("edited with no warnings"), "expected message");
|
||||
|
||||
assertThat(processSummaryLines.get(1))
|
||||
.hasFieldOrPropertyWithValue("status", Status.ERROR)
|
||||
.hasFieldOrPropertyWithValue("count", 60)
|
||||
.matches(psl -> psl.getMessage().contains("Id less than 60 is error"), "expected message");
|
||||
|
||||
assertThat(processSummaryLines.get(2))
|
||||
.hasFieldOrPropertyWithValue("status", Status.WARNING)
|
||||
.hasFieldOrPropertyWithValue("count", 30)
|
||||
.matches(psl -> psl.getMessage().contains("Id less than 90 is warning"), "expected message");
|
||||
|
||||
List<ProcessSummaryLine> infoLines = processSummaryLines.stream().filter(psl -> psl.getStatus().equals(Status.INFO)).collect(Collectors.toList());
|
||||
assertThat(infoLines).hasSize(1);
|
||||
assertThat(infoLines.stream().map(ProcessSummaryLine::getMessage)).anyMatch(m -> m.matches("(?s).*First Name.*Johnny.*"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PersonPreUpdateReusedMessages extends AbstractPreUpdateCustomizer
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
Integer id = record.getValueInteger("id");
|
||||
if(id < 60)
|
||||
{
|
||||
record.addError(new BadInputStatusMessage("Id less than 60 is error."));
|
||||
}
|
||||
else if(id < 90)
|
||||
{
|
||||
record.addWarning(new QWarningMessage("Id less than 90 is warning."));
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUniqueWarningsAndErrors() throws QException
|
||||
{
|
||||
//////////////////////////////
|
||||
// insert some test records //
|
||||
//////////////////////////////
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
List<QRecord> personsToLoad = new ArrayList<>();
|
||||
for(int i = 0; i < 100; i++)
|
||||
{
|
||||
personsToLoad.add(new QRecord().withValue("id", i).withValue("firstName", "Darin" + i));
|
||||
}
|
||||
TestUtils.insertRecords(table, personsToLoad);
|
||||
|
||||
table.withCustomizer(TableCustomizers.PRE_UPDATE_RECORD, new QCodeReference(PersonPreUpdateUniqueMessages.class));
|
||||
|
||||
//////////////////////////////////
|
||||
// set up the run-process input //
|
||||
//////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(TestUtils.TABLE_NAME_PERSON_MEMORY + ".bulkEdit");
|
||||
runProcessInput.addValue(StreamedETLWithFrontendProcess.FIELD_DEFAULT_QUERY_FILTER,
|
||||
new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.LESS_THAN_OR_EQUALS, 100)));
|
||||
|
||||
RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
String processUUID = runProcessOutput.getProcessUUID();
|
||||
|
||||
runProcessInput.addValue(BulkEditTransformStep.FIELD_ENABLED_FIELDS, "firstName");
|
||||
runProcessInput.addValue("firstName", "Johnny");
|
||||
|
||||
runProcessInput.setProcessUUID(processUUID);
|
||||
runProcessInput.setStartAfterStep("edit");
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
|
||||
runProcessInput.addValue(StreamedETLWithFrontendProcess.FIELD_DO_FULL_VALIDATION, true);
|
||||
runProcessInput.setStartAfterStep("review");
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
|
||||
runProcessInput.setStartAfterStep("review");
|
||||
runProcessOutput = new RunProcessAction().execute(runProcessInput);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ProcessSummaryLine> processSummaryLines = (List<ProcessSummaryLine>) runProcessOutput.getValues().get(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY);
|
||||
assertThat(processSummaryLines).hasSize(1 + 50 + 1 + 30 + 1);
|
||||
|
||||
int index = 0;
|
||||
assertThat(processSummaryLines.get(index++))
|
||||
.hasFieldOrPropertyWithValue("status", Status.OK)
|
||||
.hasFieldOrPropertyWithValue("count", 10)
|
||||
.matches(psl -> psl.getMessage().contains("edited with no warnings"), "expected message");
|
||||
|
||||
for(int i = 0; i < 50; i++)
|
||||
{
|
||||
assertThat(processSummaryLines.get(index++))
|
||||
.hasFieldOrPropertyWithValue("status", Status.ERROR)
|
||||
.hasFieldOrPropertyWithValue("count", 1)
|
||||
.matches(psl -> psl.getMessage().contains("less than 60 is error"), "expected message");
|
||||
}
|
||||
|
||||
assertThat(processSummaryLines.get(index++))
|
||||
.hasFieldOrPropertyWithValue("status", Status.ERROR)
|
||||
.hasFieldOrPropertyWithValue("count", 10)
|
||||
.matches(psl -> psl.getMessage().contains("had other errors"), "expected message");
|
||||
|
||||
for(int i = 0; i < 30; i++)
|
||||
{
|
||||
assertThat(processSummaryLines.get(index++))
|
||||
.hasFieldOrPropertyWithValue("status", Status.WARNING)
|
||||
.hasFieldOrPropertyWithValue("count", 1)
|
||||
.matches(psl -> psl.getMessage().contains("less than 90 is warning"), "expected message");
|
||||
}
|
||||
|
||||
assertThat(processSummaryLines.get(index++))
|
||||
.hasFieldOrPropertyWithValue("status", Status.INFO)
|
||||
.matches(psl -> psl.getMessage().matches("(?s).*First Name.*Johnny.*"), "expected message");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class PersonPreUpdateUniqueMessages extends AbstractPreUpdateCustomizer
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> apply(List<QRecord> records) throws QException
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
Integer id = record.getValueInteger("id");
|
||||
if(id < 60)
|
||||
{
|
||||
record.addError(new BadInputStatusMessage("Id [" + id + "] less than 60 is error."));
|
||||
}
|
||||
else if(id < 90)
|
||||
{
|
||||
record.addWarning(new QWarningMessage("Id [" + id + "] less than 90 is warning."));
|
||||
}
|
||||
}
|
||||
|
||||
return (records);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -289,7 +289,7 @@ public class TestUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated
|
||||
@Deprecated(since = "better to call the one without qInstance param")
|
||||
public static void insertRecords(QInstance qInstance, QTableMetaData table, List<QRecord> records) throws QException
|
||||
{
|
||||
insertRecords(table, records);
|
||||
|
Reference in New Issue
Block a user