Feature/UI fixes 2025 09 #2
No files matched your search
+42
@@ -25,6 +25,7 @@ package com.kingsrook.qbits.workflows.implementations.recordworkflows;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -35,6 +36,8 @@ import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAc
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.values.SearchPossibleValueSourceInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||
@@ -143,6 +146,8 @@ public class UpdateInputRecordFieldMetaDataAdjuster implements FormAdjusterInter
|
||||
}
|
||||
}
|
||||
|
||||
adjustPossibleValueSourceFilterCriteriaInputs(updatedField.getPossibleValueSourceFilter());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// build the output object, starting with the new meta-data of the 'value' field //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -186,6 +191,43 @@ public class UpdateInputRecordFieldMetaDataAdjuster implements FormAdjusterInter
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* if the field has a possible-value-source filter on it, and that filter
|
||||
* uses an input field e.g., ${input.someField} - consider that this value
|
||||
* *might* be an attribute on the workflow so add an optional
|
||||
* ??${input.workflow.someField} after it.
|
||||
***************************************************************************/
|
||||
static void adjustPossibleValueSourceFilterCriteriaInputs(QQueryFilter filter)
|
||||
{
|
||||
if(filter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(filter.getCriteria()))
|
||||
{
|
||||
List<Serializable> values = CollectionUtils.nonNullList(criteria.getValues());
|
||||
ListIterator<Serializable> valueIterator = values.listIterator();
|
||||
while(valueIterator.hasNext())
|
||||
{
|
||||
Serializable value = valueIterator.next();
|
||||
if(value instanceof String stringValue && stringValue.startsWith("${input."))
|
||||
{
|
||||
String inputFieldName = stringValue.replace("${input.", "").replace("}", "");
|
||||
String updatedValue = stringValue + "??${input.workflow." + inputFieldName + "}";
|
||||
valueIterator.set(updatedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
|
||||
{
|
||||
adjustPossibleValueSourceFilterCriteriaInputs(subFilter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
|
||||
+88
-29
@@ -60,7 +60,10 @@ import org.json.JSONObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** workflow step that updates a record with one value in one field
|
||||
* Workflow step that updates a record with one value in one field
|
||||
*
|
||||
* This class includes some public static methods that can be useful for other
|
||||
* workflow step implementations that have similar functionality.
|
||||
*******************************************************************************/
|
||||
public class UpdateInputRecordFieldStep extends WorkflowStepType implements WorkflowStepExecutorInterface, WorkflowStepValidatorInterface
|
||||
{
|
||||
@@ -101,7 +104,15 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
* For a given fieldName, tableName, and api and version (which come from a
|
||||
* workflowRevision), get QFieldMetaData for that field (if it's in that
|
||||
* API Version).
|
||||
*
|
||||
* @param fieldNameMaybeWithTableNamePrefix - field name - which can be
|
||||
* prefixed by tableName + "."
|
||||
* @param tableName table name
|
||||
* @param workflowRevision from which api name and version will come
|
||||
* @return optional containing QFieldMetaData if found, or empty if not.
|
||||
***************************************************************************/
|
||||
public static Optional<QFieldMetaData> getApiField(String fieldNameMaybeWithTableNamePrefix, String tableName, WorkflowRevision workflowRevision) throws QException
|
||||
{
|
||||
@@ -121,6 +132,31 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getDynamicStepSummary(Integer workflowId, Map<String, Serializable> inputValues) throws QException
|
||||
{
|
||||
QRecord workflowRecord = GetAction.execute(Workflow.TABLE_NAME, workflowId);
|
||||
String tableName = workflowRecord.getValueString("tableName");
|
||||
return getStepSummary(inputValues, tableName, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Helper for {@link #getDynamicStepSummary(Integer, Map)} or
|
||||
* {@link #execute(WorkflowStep, Map, WorkflowExecutionContext)} to generate
|
||||
* a string like "${fieldLabel} will be / was set to ${value}" or "... cleared out"
|
||||
* where the value will be a translated possible value if applicable.
|
||||
*
|
||||
* @param inputValues map of values user assigned to the step. expected to
|
||||
* include String fieldName and String value.
|
||||
* @param tableName table that the field should come from. Many times this
|
||||
* may be the tableName on the workflow - but it could be
|
||||
* a hard-coded tableName in other implementations of this
|
||||
* step type.
|
||||
* @param isPastTense to format the message as "will be" or "was". e.g.,
|
||||
* for getDynamicStepSummary would be false ("will be")
|
||||
* and for execute would be true ("was").
|
||||
***************************************************************************/
|
||||
public static String getStepSummary(Map<String, Serializable> inputValues, String tableName, boolean isPastTense)
|
||||
{
|
||||
String fieldName = ValueUtils.getValueAsString(inputValues.get("fieldName"));
|
||||
String value = ValueUtils.getValueAsString(inputValues.get("value"));
|
||||
@@ -137,45 +173,38 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
|
||||
}
|
||||
}
|
||||
|
||||
return getStepSummary(workflowId, fieldName, apiName, apiVersion, value, false);
|
||||
return getStepSummary(fieldName, tableName, apiName, apiVersion, value, isPastTense);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
*
|
||||
***************************************************************************/
|
||||
private String getStepSummary(Integer workflowId, String fieldName, String apiName, String apiVersion, String value, boolean isPastTense)
|
||||
private static String getStepSummary(String fieldName, String tableName, String apiName, String apiVersion, String value, boolean isPastTense)
|
||||
{
|
||||
String fieldLabel = null;
|
||||
QTableMetaData table = null;
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
|
||||
if(StringUtils.hasContent(fieldName))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(workflowId != null)
|
||||
WorkflowRevision workflowRevision = new WorkflowRevision().withApiName(apiName).withApiVersion(apiVersion);
|
||||
if(WorkflowStepUtils.useApi(workflowRevision))
|
||||
{
|
||||
QRecord workflowRecord = GetAction.execute(Workflow.TABLE_NAME, workflowId);
|
||||
table = QContext.getQInstance().getTable(workflowRecord.getValueString("tableName"));
|
||||
|
||||
WorkflowRevision workflowRevision = new WorkflowRevision().withApiName(apiName).withApiVersion(apiVersion);
|
||||
if(WorkflowStepUtils.useApi(workflowRevision))
|
||||
Optional<QFieldMetaData> optionalApiField = getApiField(fieldName, tableName, workflowRevision);
|
||||
if(optionalApiField.isPresent())
|
||||
{
|
||||
Optional<QFieldMetaData> optionalApiField = getApiField(fieldName, table.getName(), workflowRevision);
|
||||
if(optionalApiField.isPresent())
|
||||
{
|
||||
// fieldName = optionalApiField.field().getName(); // hmm?
|
||||
fieldLabel = optionalApiField.get().getLabel();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
|
||||
fieldName = fieldAndJoinTable.field().getName();
|
||||
fieldLabel = fieldAndJoinTable.field().getLabel();
|
||||
fieldLabel = optionalApiField.get().getLabel();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
|
||||
fieldName = fieldAndJoinTable.field().getName();
|
||||
fieldLabel = fieldAndJoinTable.field().getLabel();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -183,15 +212,22 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
|
||||
}
|
||||
}
|
||||
|
||||
String verb = isPastTense ? "was" : "will be";
|
||||
|
||||
if(StringUtils.hasContent(fieldLabel))
|
||||
{
|
||||
String verb = isPastTense ? "was" : "will be";
|
||||
if(StringUtils.hasContent(value))
|
||||
{
|
||||
String displayValue = value;
|
||||
try
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// field names from this PVS are always table.field - but QValueFormatter doesn't expect that - so strip away table. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(fieldName != null && fieldName.startsWith(tableName + "."))
|
||||
{
|
||||
fieldName = fieldName.replace(tableName + ".", "");
|
||||
}
|
||||
|
||||
QRecord record = new QRecord().withValue(fieldName, value);
|
||||
QValueFormatter.setDisplayValuesInRecordsIncludingPossibleValueTranslations(table, List.of(record));
|
||||
displayValue = record.getDisplayValue(fieldName);
|
||||
@@ -241,7 +277,8 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
|
||||
|
||||
context.doesRecordNeedUpdated.set(true);
|
||||
|
||||
String stepSummary = getStepSummary(context.getWorkflow().getId(), fieldName, context.getWorkflowRevision().getApiName(), context.getWorkflowRevision().getApiVersion(), value, true);
|
||||
String tableName = context.getWorkflow().getTableName();
|
||||
String stepSummary = getStepSummary(fieldName, tableName, context.getWorkflowRevision().getApiName(), context.getWorkflowRevision().getApiVersion(), value, true);
|
||||
return new WorkflowStepOutput(value, stepSummary);
|
||||
}
|
||||
|
||||
@@ -284,12 +321,34 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
|
||||
@Override
|
||||
public void validate(WorkflowStep step, Map<String, Serializable> inputValues, QRecord workflowRevision, QRecord workflow, List<String> errors) throws QException
|
||||
{
|
||||
if(WorkflowStepUtils.useApi(new WorkflowRevision(workflowRevision)))
|
||||
validate(workflow.getValueString("tableName"), inputValues, workflowRevision, errors);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* implementation logic for {@link #validate(WorkflowStep, Map, QRecord, QRecord, List)}.
|
||||
* makes sure that, if the workflow uses API, that the field is a valid field
|
||||
* for the api version being used in the workflow revision.
|
||||
*
|
||||
* @param tableName table that the field should come from. Many times this
|
||||
* may be the tableName on the workflow - but it could be
|
||||
* a hard-coded tableName in other implementations of this
|
||||
* step type.
|
||||
* @param inputValues map of values user assigned to the step. expected to
|
||||
* include String fieldName.
|
||||
* @param workflowRevision from which api name and version will come
|
||||
* @param errors Out param - list of error message strings, which will be
|
||||
* added to if the input fieldName isn't in the table for the
|
||||
* api version on the workflowRevision
|
||||
***************************************************************************/
|
||||
public static void validate(String tableName, Map<String, Serializable> inputValues, QRecord workflowRevision, List<String> errors) throws QException
|
||||
{
|
||||
if(workflowRevision != null && WorkflowStepUtils.useApi(new WorkflowRevision(workflowRevision)))
|
||||
{
|
||||
String fieldName = ValueUtils.getValueAsString(inputValues.get("fieldName"));
|
||||
String apiName = ValueUtils.getValueAsString(workflowRevision.getValueString("apiName"));
|
||||
String apiVersion = ValueUtils.getValueAsString(workflowRevision.getValueString("apiVersion"));
|
||||
String tableName = ValueUtils.getValueAsString(workflow.getValueString("tableName"));
|
||||
|
||||
Optional<QFieldMetaData> optionalApiField = getApiField(fieldName, tableName, new WorkflowRevision(workflowRevision));
|
||||
if(optionalApiField.isEmpty())
|
||||
|
||||
+55
-10
@@ -140,6 +140,38 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
|
||||
throw (new QUserFacingException("Workflow not found by id: " + workflowId));
|
||||
}
|
||||
|
||||
String stepsJSON = runBackendStepInput.getValueString("steps");
|
||||
if(!StringUtils.hasContent(stepsJSON))
|
||||
{
|
||||
throw (new QUserFacingException("Workflow steps input was not provided."));
|
||||
}
|
||||
|
||||
List<WorkflowStep> workflowSteps;
|
||||
try
|
||||
{
|
||||
workflowSteps = JsonUtils.toObject(stepsJSON, new TypeReference<>() {});
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QUserFacingException("Error parsing workflow steps json: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
String linksJSON = runBackendStepInput.getValueString("links");
|
||||
if(!StringUtils.hasContent(linksJSON))
|
||||
{
|
||||
throw (new QUserFacingException("Workflow links input was not provided."));
|
||||
}
|
||||
|
||||
List<WorkflowLink> workflowLinks;
|
||||
try
|
||||
{
|
||||
workflowLinks = JsonUtils.toObject(linksJSON, new TypeReference<>() {});
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QUserFacingException("Error parsing workflow links json: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// look up next versionNo //
|
||||
////////////////////////////
|
||||
@@ -166,15 +198,15 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
|
||||
// insert the workflow revision //
|
||||
//////////////////////////////////
|
||||
String commitMessage = runBackendStepInput.getValueString("commitMessage");
|
||||
String userName = ObjectUtils.tryElse(() -> QContext.getQSession().getUser().getFullName(), "Unknown");
|
||||
WorkflowRevision workflowRevision = new WorkflowRevision();
|
||||
workflowRevision.setWorkflowId(workflowId);
|
||||
workflowRevision.setVersionNo(versionNo);
|
||||
workflowRevision.setCommitMessage(StringUtils.hasContent(commitMessage) ? commitMessage : "New workflow revision created by " + QContext.getQSession().getUser().getFullName());
|
||||
workflowRevision.setCommitMessage(StringUtils.hasContent(commitMessage) ? commitMessage : "New workflow revision created by " + userName);
|
||||
workflowRevision.setApiName(runBackendStepInput.getValueString("apiName"));
|
||||
workflowRevision.setApiVersion(runBackendStepInput.getValueString("apiVersion"));
|
||||
workflowRevision.setAuthor(ObjectUtils.tryElse(() -> QContext.getQSession().getUser().getFullName(), "Unknown"));
|
||||
workflowRevision.setAuthor(userName);
|
||||
|
||||
List<WorkflowStep> workflowSteps = JsonUtils.toObject(runBackendStepInput.getValueString("steps"), new TypeReference<>() {});
|
||||
if(!workflowSteps.isEmpty())
|
||||
{
|
||||
workflowRevision.setStartStepNo(workflowSteps.get(0).getStepNo());
|
||||
@@ -208,19 +240,24 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// store steps (first setting revision id, validating, and putting a fresh summary on each) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<String> errorsForThisStep = new ArrayList<>();
|
||||
workflowSteps.forEach(step ->
|
||||
{
|
||||
try
|
||||
{
|
||||
step.setWorkflowRevisionId(insertedRevisionId);
|
||||
|
||||
WorkflowStepType workflowStepType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowStepType(step.getWorkflowStepTypeName());
|
||||
Map<String, Serializable> inputValues = ValueUtils.getValueAsMap(step.getInputValuesJson());
|
||||
WorkflowStepType workflowStepType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowStepType(step.getWorkflowStepTypeName());
|
||||
if(workflowStepType == null)
|
||||
{
|
||||
throw (new QUserFacingException("Unknown workflow step type " + step.getWorkflowStepTypeName()));
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// summary - if it fails, :shrug: //
|
||||
////////////////////////////////////
|
||||
Map<String, Serializable> inputValues = ValueUtils.getValueAsMap(step.getInputValuesJson());
|
||||
try
|
||||
{
|
||||
step.setSummary(workflowStepType.getDynamicStepSummary(workflowId, inputValues));
|
||||
@@ -237,18 +274,27 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
|
||||
if(validatorCodeReference != null)
|
||||
{
|
||||
WorkflowStepValidatorInterface validator = QCodeLoader.getAdHoc(WorkflowStepValidatorInterface.class, validatorCodeReference);
|
||||
validator.validate(step, inputValues, workflowRevisionRecord, workflowRecord, errors);
|
||||
validator.validate(step, inputValues, workflowRevisionRecord, workflowRecord, errorsForThisStep);
|
||||
if(!errorsForThisStep.isEmpty())
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// put Step # in front of all errors //
|
||||
///////////////////////////////////////
|
||||
errors.addAll(errorsForThisStep.stream().map(e -> "Step " + step.getStepNo() + ": " + e).toList());
|
||||
errorsForThisStep.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
errors.add("Error processing step [" + step.getSummary() + "]: " + e.getMessage());
|
||||
errors.add("Error processing Step " + step.getStepNo() + ": " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
if(!errors.isEmpty())
|
||||
{
|
||||
throw (new QUserFacingException("Validation errors in steps: " + StringUtils.joinWithCommasAndAnd(errors)));
|
||||
String message = errors.size() + " validation error" + StringUtils.plural(errors) + " occurred within the workflow's steps:\n" + StringUtils.join("\n", errors);
|
||||
throw (new QUserFacingException(message));
|
||||
}
|
||||
|
||||
new InsertAction().execute(new InsertInput(WorkflowStep.TABLE_NAME).withRecordEntities(workflowSteps).withTransaction(transaction));
|
||||
@@ -256,7 +302,6 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
|
||||
/////////////////
|
||||
// store links //
|
||||
/////////////////
|
||||
List<WorkflowLink> workflowLinks = JsonUtils.toObject(runBackendStepInput.getValueString("links"), new TypeReference<>() {});
|
||||
workflowLinks.forEach(link -> link.setWorkflowRevisionId(insertedRevisionId));
|
||||
new InsertAction().execute(new InsertInput(WorkflowLink.TABLE_NAME).withRecordEntities(workflowLinks).withTransaction(transaction));
|
||||
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.qbits.workflows.implementations.recordworkflows;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qbits.workflows.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster.FormAdjusterInput;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster.FormAdjusterOutput;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster.RunFormAdjusterProcess;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for UpdateInputRecordFieldMetaDataAdjuster
|
||||
*******************************************************************************/
|
||||
class UpdateInputRecordFieldMetaDataAdjusterTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testExecute() throws QException
|
||||
{
|
||||
Map<String, Serializable> allValues = Map.of(
|
||||
"workflowValuesJSON", JsonUtils.toJson(Map.of("tableName", TABLE_NAME_PERSON)),
|
||||
"workflowRevisionValuesJSON", JsonUtils.toJson(Map.of()),
|
||||
"value", 1
|
||||
);
|
||||
|
||||
///////////////////////
|
||||
// run for on-change //
|
||||
///////////////////////
|
||||
FormAdjusterOutput onChangeOutput = new UpdateInputRecordFieldMetaDataAdjuster().execute(new FormAdjusterInput()
|
||||
.withFieldName("fieldName")
|
||||
.withNewValue(TABLE_NAME_PERSON + ".firstName")
|
||||
.withAllValues(allValues)
|
||||
.withEvent(RunFormAdjusterProcess.EVENT_ON_CHANGE));
|
||||
assertEquals(Set.of("value"), onChangeOutput.getFieldsToClear());
|
||||
|
||||
assertEquals(1, onChangeOutput.getUpdatedFieldMetaData().size());
|
||||
assertThat(onChangeOutput.getUpdatedFieldDisplayValues()).isNullOrEmpty();
|
||||
QFrontendFieldMetaData updatedValueField = onChangeOutput.getUpdatedFieldMetaData().get("value");
|
||||
assertEquals("Value (First Name)", updatedValueField.getLabel());
|
||||
|
||||
/////////////////////
|
||||
// run for on-load //
|
||||
/////////////////////
|
||||
new InsertAction().execute(new InsertInput(TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("id", 1).withValue("name", "Square")));
|
||||
|
||||
FormAdjusterOutput onLoadOutput = new UpdateInputRecordFieldMetaDataAdjuster().execute(new FormAdjusterInput()
|
||||
.withFieldName("fieldName")
|
||||
.withNewValue(TABLE_NAME_PERSON + ".favoriteShapeId")
|
||||
.withAllValues(allValues)
|
||||
.withEvent(RunFormAdjusterProcess.EVENT_ON_LOAD));
|
||||
assertThat(onLoadOutput.getFieldsToClear()).isNullOrEmpty();
|
||||
assertEquals(Map.of("value", "Square"), onLoadOutput.getUpdatedFieldDisplayValues());
|
||||
updatedValueField = onLoadOutput.getUpdatedFieldMetaData().get("value");
|
||||
assertEquals("Value (Favorite Shape)", updatedValueField.getLabel());
|
||||
assertEquals(TABLE_NAME_SHAPE, updatedValueField.getPossibleValueSourceName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAdjustPossibleValueSourceFilterCriteriaInputs()
|
||||
{
|
||||
QFieldMetaData field = new QFieldMetaData();
|
||||
UpdateInputRecordFieldMetaDataAdjuster.adjustPossibleValueSourceFilterCriteriaInputs(field.getPossibleValueSourceFilter());
|
||||
assertNull(field.getPossibleValueSourceFilter());
|
||||
|
||||
field.setPossibleValueSourceFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 1)));
|
||||
UpdateInputRecordFieldMetaDataAdjuster.adjustPossibleValueSourceFilterCriteriaInputs(field.getPossibleValueSourceFilter());
|
||||
assertEquals(1, field.getPossibleValueSourceFilter().getCriteria().get(0).getValues().get(0));
|
||||
|
||||
field.setPossibleValueSourceFilter(new QQueryFilter(new QFilterCriteria("someId", QCriteriaOperator.EQUALS, "${input.someId}")));
|
||||
UpdateInputRecordFieldMetaDataAdjuster.adjustPossibleValueSourceFilterCriteriaInputs(field.getPossibleValueSourceFilter());
|
||||
assertEquals("${input.someId}??${input.workflow.someId}", field.getPossibleValueSourceFilter().getCriteria().get(0).getValues().get(0));
|
||||
|
||||
field.setPossibleValueSourceFilter(new QQueryFilter().withSubFilter(new QQueryFilter(new QFilterCriteria("someId", QCriteriaOperator.EQUALS, "${input.someId}"))));
|
||||
UpdateInputRecordFieldMetaDataAdjuster.adjustPossibleValueSourceFilterCriteriaInputs(field.getPossibleValueSourceFilter());
|
||||
assertEquals("${input.someId}??${input.workflow.someId}", field.getPossibleValueSourceFilter().getSubFilters().get(0).getCriteria().get(0).getValues().get(0));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.qbits.workflows.implementations.recordworkflows;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.workflows.BaseTest;
|
||||
import com.kingsrook.qbits.workflows.model.WorkflowRevision;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for UpdateInputRecordFieldStep
|
||||
*******************************************************************************/
|
||||
class UpdateInputRecordFieldStepTest extends BaseTest
|
||||
{
|
||||
private static WorkflowRevision workflowRevisionV1 = new WorkflowRevision().withApiName(API_NAME).withApiVersion(V1);
|
||||
private static WorkflowRevision workflowRevisionV2 = new WorkflowRevision().withApiName(API_NAME).withApiVersion(V2);
|
||||
private static WorkflowRevision workflowRevisionV3 = new WorkflowRevision().withApiName(API_NAME).withApiVersion(V3);
|
||||
private static WorkflowRevision workflowRevisionWithoutApi = new WorkflowRevision();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testGetApiField() throws QException
|
||||
{
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".firstName", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isPresent().get().hasFieldOrPropertyWithValue("name", "firstName");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField("firstName", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isPresent().get().hasFieldOrPropertyWithValue("name", "firstName");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField("noSuchField", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isEmpty();
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".noSuchField", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isEmpty();
|
||||
|
||||
/////////////////////////////////////
|
||||
// this field is excluded from api //
|
||||
/////////////////////////////////////
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".debt", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isEmpty();
|
||||
|
||||
///////////////////////////////
|
||||
// this field is added in V3 //
|
||||
///////////////////////////////
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".salary", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isEmpty();
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".salary", TABLE_NAME_PERSON, workflowRevisionV3))
|
||||
.isPresent().get().hasFieldOrPropertyWithValue("name", "salary");
|
||||
|
||||
//////////////////////////////////////////
|
||||
// confirm shoeCount/noOfShoes renaming //
|
||||
//////////////////////////////////////////
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".shoeCount", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isPresent().get().hasFieldOrPropertyWithValue("name", "shoeCount");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".shoeCount", TABLE_NAME_PERSON, workflowRevisionV2))
|
||||
.isEmpty();
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".noOfShoes", TABLE_NAME_PERSON, workflowRevisionV1))
|
||||
.isEmpty();
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getApiField(TABLE_NAME_PERSON + ".noOfShoes", TABLE_NAME_PERSON, workflowRevisionV2))
|
||||
.isPresent().get().hasFieldOrPropertyWithValue("name", "noOfShoes");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testGetStepSummary() throws QException
|
||||
{
|
||||
assertThat(UpdateInputRecordFieldStep.getStepSummary(Map.of("fieldName", TABLE_NAME_PERSON + ".firstName", "value", "John"), TABLE_NAME_PERSON, false))
|
||||
.isEqualTo("First Name will be set to 'John'");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getStepSummary(Map.of("fieldName", TABLE_NAME_PERSON + ".firstName", "value", "John"), TABLE_NAME_PERSON, true))
|
||||
.isEqualTo("First Name was set to 'John'");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getStepSummary(Map.of("fieldName", TABLE_NAME_PERSON + ".firstName", "value", ""), TABLE_NAME_PERSON, false))
|
||||
.isEqualTo("First Name will be cleared out");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getStepSummary(Map.of("fieldName", TABLE_NAME_PERSON + ".firstName"), TABLE_NAME_PERSON, true))
|
||||
.isEqualTo("First Name was cleared out");
|
||||
|
||||
new InsertAction().execute(new InsertInput(TABLE_NAME_SHAPE).withRecord(new QRecord().withValue("id", 1).withValue("name", "Square")));
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getStepSummary(Map.of("fieldName", TABLE_NAME_PERSON + ".favoriteShapeId", "value", "1"), TABLE_NAME_PERSON, false))
|
||||
.isEqualTo("Favorite Shape will be set to 'Square'");
|
||||
|
||||
assertThat(UpdateInputRecordFieldStep.getStepSummary(Map.of("fieldName", TABLE_NAME_PERSON + ".favoriteShapeId", "value", "1"), TABLE_NAME_PERSON, true))
|
||||
.isEqualTo("Favorite Shape was set to 'Square'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testValidate() throws QException
|
||||
{
|
||||
assertThat(runValidate(Map.of("fieldName", TABLE_NAME_PERSON + ".firstName"), workflowRevisionV1)).isEmpty();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// debt field - found if not using api, else not found as it is excluded from api //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
assertThat(runValidate(Map.of("fieldName", TABLE_NAME_PERSON + ".debt"), workflowRevisionWithoutApi)).isEmpty();
|
||||
assertThat(runValidate(Map.of("fieldName", TABLE_NAME_PERSON + ".debt"), workflowRevisionV1)).hasSize(1)
|
||||
.element(0).asString()
|
||||
.contains("Could not find field 'person.debt' in table '" + TABLE_NAME_PERSON + "' for API '" + API_NAME + "' version 'v1'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private List<String> runValidate(Map<String, Serializable> inputValues, WorkflowRevision workflowRevision) throws QException
|
||||
{
|
||||
List<String> errors = new ArrayList<>();
|
||||
UpdateInputRecordFieldStep.validate(TABLE_NAME_PERSON, inputValues, workflowRevision == null ? null : workflowRevision.toQRecord(), errors);
|
||||
return (errors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. 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.qbits.workflows.processes;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qbits.workflows.BaseTest;
|
||||
import com.kingsrook.qbits.workflows.TestWorkflowDefinitions;
|
||||
import com.kingsrook.qbits.workflows.WorkflowsTestDataSource;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for StoreNewWorkflowRevisionProcess
|
||||
*******************************************************************************/
|
||||
class StoreNewWorkflowRevisionProcessTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
TestWorkflowDefinitions.registerTestWorkflowTypes();
|
||||
Integer workflowId = WorkflowsTestDataSource.insertTestWorkflow();
|
||||
|
||||
RunBackendStepInput input = new RunBackendStepInput();
|
||||
|
||||
RunBackendStepOutput output = new RunBackendStepOutput();
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("Workflow id is required");
|
||||
|
||||
input.addValue("workflowId", -999);
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("Workflow not found by id: -999");
|
||||
|
||||
input.addValue("workflowId", workflowId);
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("steps input was not provided");
|
||||
|
||||
input.addValue("steps", "1, 2, 3");
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("Error parsing workflow steps");
|
||||
|
||||
input.addValue("steps", JsonUtils.toJson(List.of(
|
||||
WorkflowsTestDataSource.newStep(1, "myStepType", Map.of("foo", 1)),
|
||||
WorkflowsTestDataSource.newStep(2, "yourStepType", Map.of("bar", true, "baz", false))
|
||||
)));
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("links input was not provided");
|
||||
|
||||
input.addValue("links", "not json");
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("Error parsing workflow links");
|
||||
|
||||
input.addValue("links", JsonUtils.toJson(List.of(WorkflowsTestDataSource.newLink(1, 2))));
|
||||
assertThatThrownBy(() -> new StoreNewWorkflowRevisionProcess().run(input, output))
|
||||
.hasMessageContaining("2 validation errors occurred within the workflow's steps:")
|
||||
.hasMessageContaining("Error processing Step 1: Unknown workflow step type myStepType")
|
||||
.hasMessageContaining("Error processing Step 2: Unknown workflow step type yourStepType");
|
||||
|
||||
input.addValue("steps", JsonUtils.toJson(List.of(
|
||||
WorkflowsTestDataSource.newStep(1, TestWorkflowDefinitions.ADD_X_TO_SUM_ACTION, Map.of("foo", 1)),
|
||||
WorkflowsTestDataSource.newStep(2, TestWorkflowDefinitions.BOOLEAN_CONDITIONAL, Map.of("bar", true, "baz", false))
|
||||
)));
|
||||
new StoreNewWorkflowRevisionProcess().run(input, output);
|
||||
assertNotNull(output.getValue("workflowRevisionId"));
|
||||
assertEquals(2, output.getValue("versionNo"));
|
||||
|
||||
new StoreNewWorkflowRevisionProcess().run(input, output);
|
||||
assertEquals(3, output.getValue("versionNo"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user