mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 05:30:43 +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 UpdateInput updateInput;
|
||||||
protected List<QRecord> oldRecordList;
|
protected List<QRecord> oldRecordList;
|
||||||
|
protected boolean isPreview = false;
|
||||||
|
|
||||||
private Map<Serializable, QRecord> oldRecordMap = null;
|
private Map<Serializable, QRecord> oldRecordMap = null;
|
||||||
|
|
||||||
@ -123,4 +124,35 @@ public abstract class AbstractPreUpdateCustomizer
|
|||||||
return (oldRecordMap);
|
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);
|
Optional<List<QRecord>> oldRecordList = fetchOldRecords(updateInput, updateInterface);
|
||||||
|
|
||||||
/////////////////////////////
|
performValidations(updateInput, oldRecordList, false);
|
||||||
// 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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////
|
////////////////////////////////////
|
||||||
// have the backend do the update //
|
// 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.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()));
|
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.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
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.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.edit.BulkEditTransformStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertExtractStep;
|
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.bulk.insert.BulkInsertTransformStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
|
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.LoadViaDeleteStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaInsertStep;
|
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.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
@ -761,7 +761,7 @@ public class QInstanceEnricher
|
|||||||
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(
|
QProcessMetaData process = StreamedETLWithFrontendProcess.defineProcessMetaData(
|
||||||
ExtractViaQueryStep.class,
|
ExtractViaQueryStep.class,
|
||||||
BulkEditTransformStep.class,
|
BulkEditTransformStep.class,
|
||||||
LoadViaUpdateStep.class,
|
BulkEditLoadStep.class,
|
||||||
values
|
values
|
||||||
)
|
)
|
||||||
.withName(processName)
|
.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.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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.QPossibleValueTranslator;
|
||||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
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.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.processes.Status;
|
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.data.QRecord;
|
||||||
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.tables.QTableMetaData;
|
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.AbstractTransformStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
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.StringUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
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 ProcessSummaryLine okSummary = new ProcessSummaryLine(Status.OK);
|
||||||
private List<ProcessSummaryLine> infoSummaries = new ArrayList<>();
|
private List<ProcessSummaryLine> infoSummaries = new ArrayList<>();
|
||||||
|
|
||||||
|
private ProcessSummaryWarningsAndErrorsRollup processSummaryWarningsAndErrorsRollup = getProcessSummaryWarningsAndErrorsRollup();
|
||||||
|
|
||||||
private QTableMetaData table;
|
private QTableMetaData table;
|
||||||
private String tableLabel;
|
private String tableLabel;
|
||||||
private String[] enabledFields;
|
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();
|
tableLabel = table.getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
String enabledFieldsString = runBackendStepInput.getValueString(FIELD_ENABLED_FIELDS);
|
|
||||||
enabledFields = enabledFieldsString.split(",");
|
|
||||||
|
|
||||||
isValidateStep = runBackendStepInput.getStepName().equals(StreamedETLWithFrontendProcess.STEP_NAME_VALIDATE);
|
isValidateStep = runBackendStepInput.getStepName().equals(StreamedETLWithFrontendProcess.STEP_NAME_VALIDATE);
|
||||||
isExecuteStep = runBackendStepInput.getStepName().equals(StreamedETLWithFrontendProcess.STEP_NAME_EXECUTE);
|
isExecuteStep = runBackendStepInput.getStepName().equals(StreamedETLWithFrontendProcess.STEP_NAME_EXECUTE);
|
||||||
haveRecordCount = runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_RECORD_COUNT) != null;
|
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);
|
outputRecords.add(recordToUpdate);
|
||||||
setUpdatedFieldsInRecord(runBackendStepInput, enabledFields, recordToUpdate);
|
setUpdatedFieldsInRecord(runBackendStepInput, enabledFields, recordToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
okSummary.incrementCount(runBackendStepInput.getRecords().size());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// put the value in all the records (note, this is just for display on the review screen, //
|
// Build records-to-update for passing into the validation method of the Update action //
|
||||||
// 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. //
|
List<QRecord> recordsForValidation = new ArrayList<>();
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////
|
Map<Serializable, QRecord> pkeyToFullRecordMap = new HashMap<>();
|
||||||
for(QRecord record : runBackendStepInput.getRecords())
|
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);
|
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);
|
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)
|
for(String fieldName : enabledFields)
|
||||||
{
|
{
|
||||||
QFieldMetaData field = table.getField(fieldName);
|
QFieldMetaData field = table.getField(fieldName);
|
||||||
@ -168,7 +250,7 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
|||||||
String verb = isExecuteStep ? "was" : "will be";
|
String verb = isExecuteStep ? "was" : "will be";
|
||||||
if(StringUtils.hasContent(ValueUtils.getValueAsString(value)))
|
if(StringUtils.hasContent(ValueUtils.getValueAsString(value)))
|
||||||
{
|
{
|
||||||
String formattedValue = qValueFormatter.formatValue(field, value);
|
String formattedValue = QValueFormatter.formatValue(field, value);
|
||||||
|
|
||||||
if(field.getPossibleValueSourceName() != null)
|
if(field.getPossibleValueSourceName() != null)
|
||||||
{
|
{
|
||||||
@ -211,15 +293,19 @@ public class BulkEditTransformStep extends AbstractTransformStep
|
|||||||
@Override
|
@Override
|
||||||
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput runBackendStepOutput, boolean isForResultScreen)
|
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<>();
|
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);
|
rs.addAll(infoSummaries);
|
||||||
return (rs);
|
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;
|
package com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
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.actions.processes.RunProcessAction;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
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.actions.tables.query.QQueryFilter;
|
||||||
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.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.modules.backend.implementations.memory.MemoryRecordStore;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
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"));
|
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
|
public static void insertRecords(QInstance qInstance, QTableMetaData table, List<QRecord> records) throws QException
|
||||||
{
|
{
|
||||||
insertRecords(table, records);
|
insertRecords(table, records);
|
||||||
|
Reference in New Issue
Block a user