QQQ-21 adding break/skip/fail options for frontendSteps in runProcessAction; adding viewFields & recordsList fields to frontend steps

This commit is contained in:
2022-07-08 15:29:04 -05:00
parent cd0cfa6aca
commit 0afa88bd0c
7 changed files with 323 additions and 30 deletions

View File

@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResu
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider; import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
@ -86,30 +87,53 @@ public class RunProcessAction
List<QStepMetaData> stepList = getAvailableStepList(process, runProcessRequest); List<QStepMetaData> stepList = getAvailableStepList(process, runProcessRequest);
try try
{ {
STEP_LOOP:
for(QStepMetaData step : stepList) for(QStepMetaData step : stepList)
{ {
//////////////////////////////////////////////////////////////////////////////////////////////// if(step instanceof QFrontendStepMetaData)
// if the caller requested to only run backend steps, then break if this isn't a backend step //
////////////////////////////////////////////////////////////////////////////////////////////////
if(runProcessRequest.getBackendOnly())
{ {
if(!(step instanceof QBackendStepMetaData)) ////////////////////////////////////////////////////////////////
// Handle what to do with frontend steps, per request setting //
////////////////////////////////////////////////////////////////
switch(runProcessRequest.getFrontendStepBehavior())
{ {
LOG.info("Breaking process [" + process.getName() + "] at first non-backend step (as requested by caller): " + step.getName()); case BREAK ->
{
LOG.info("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processState.setNextStepName(step.getName()); processState.setNextStepName(step.getName());
break; break STEP_LOOP;
} }
} case SKIP ->
/////////////////////////////////////
// run the step, based on its type //
/////////////////////////////////////
if(step instanceof QBackendStepMetaData backendStepMetaData)
{ {
LOG.info("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
processState.setNextStepName(step.getName());
//////////////////////////////////////////////////////////////////////
// much less error prone in case this code changes in the future... //
//////////////////////////////////////////////////////////////////////
// noinspection UnnecessaryContinue
continue;
}
case FAIL ->
{
LOG.info("Throwing error for frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
throw (new QException("Failing process at step " + step.getName() + " (as requested, to fail on frontend steps)"));
}
default -> throw new IllegalStateException("Unexpected value: " + runProcessRequest.getFrontendStepBehavior());
}
}
else if(step instanceof QBackendStepMetaData backendStepMetaData)
{
///////////////////////
// Run backend steps //
///////////////////////
runBackendStep(runProcessRequest, process, runProcessResult, stateKey, backendStepMetaData, processState); runBackendStep(runProcessRequest, process, runProcessResult, stateKey, backendStepMetaData, processState);
} }
else else
{ {
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName())); throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
} }
} }

View File

@ -122,9 +122,20 @@ public class QInstanceEnricher
step.getInputFields().forEach(this::enrich); step.getInputFields().forEach(this::enrich);
step.getOutputFields().forEach(this::enrich); step.getOutputFields().forEach(this::enrich);
if (step instanceof QFrontendStepMetaData) if (step instanceof QFrontendStepMetaData frontendStepMetaData)
{ {
((QFrontendStepMetaData)step).getFormFields().forEach(this::enrich); if(frontendStepMetaData.getFormFields() != null)
{
frontendStepMetaData.getFormFields().forEach(this::enrich);
}
if(frontendStepMetaData.getViewFields() != null)
{
frontendStepMetaData.getViewFields().forEach(this::enrich);
}
if(frontendStepMetaData.getRecordListFields() != null)
{
frontendStepMetaData.getRecordListFields().forEach(this::enrich);
}
} }
} }

View File

@ -26,6 +26,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.interfaces.BackendStep; import com.kingsrook.qqq.backend.core.interfaces.BackendStep;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepRequest; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResult; import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResult;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
@ -35,16 +37,26 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResu
*******************************************************************************/ *******************************************************************************/
public class MockBackendStep implements BackendStep public class MockBackendStep implements BackendStep
{ {
private static final Logger LOG = LogManager.getLogger(MockBackendStep.class);
public static final String FIELD_GREETING_PREFIX = "greetingPrefix"; public static final String FIELD_GREETING_PREFIX = "greetingPrefix";
public static final String FIELD_GREETING_SUFFIX = "greetingSuffix"; public static final String FIELD_GREETING_SUFFIX = "greetingSuffix";
public static final String FIELD_MOCK_VALUE = "mockValue";
public static final String MOCK_VALUE = "You so silly";
@Override @Override
public void run(RunBackendStepRequest runBackendStepRequest, RunBackendStepResult runBackendStepResult) throws QException public void run(RunBackendStepRequest runBackendStepRequest, RunBackendStepResult runBackendStepResult) throws QException
{ {
runBackendStepResult.getRecords().forEach(r -> r.setValue("mockValue", "Ha ha!")); runBackendStepResult.getRecords().forEach(r ->
{
r.setValue(FIELD_MOCK_VALUE, "Ha ha!");
LOG.info("We are mocking {}: {}", r.getValueString("firstName"), r.getValue(FIELD_MOCK_VALUE));
});
runBackendStepResult.setValues(runBackendStepRequest.getValues()); runBackendStepResult.setValues(runBackendStepRequest.getValues());
runBackendStepResult.addValue("mockValue", "You so silly"); runBackendStepResult.addValue(FIELD_MOCK_VALUE, MOCK_VALUE);
///////////////////////////////// /////////////////////////////////
// mock the "greet" process... // // mock the "greet" process... //

View File

@ -42,13 +42,25 @@ public class RunProcessRequest extends AbstractQRequest
private String processName; private String processName;
private QProcessCallback callback; private QProcessCallback callback;
private ProcessState processState; private ProcessState processState;
private boolean backendOnly = false; private FrontendStepBehavior frontendStepBehavior = FrontendStepBehavior.BREAK;
private String startAfterStep; private String startAfterStep;
private String processUUID; private String processUUID;
private AsyncJobCallback asyncJobCallback; private AsyncJobCallback asyncJobCallback;
/*******************************************************************************
**
*******************************************************************************/
public enum FrontendStepBehavior
{
BREAK,
SKIP,
FAIL
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -285,21 +297,23 @@ public class RunProcessRequest extends AbstractQRequest
/******************************************************************************* /*******************************************************************************
** Getter for frontendStepBehavior
** **
*******************************************************************************/ *******************************************************************************/
public void setBackendOnly(boolean backendOnly) public FrontendStepBehavior getFrontendStepBehavior()
{ {
this.backendOnly = backendOnly; return frontendStepBehavior;
} }
/******************************************************************************* /*******************************************************************************
** Setter for frontendStepBehavior
** **
*******************************************************************************/ *******************************************************************************/
public boolean getBackendOnly() public void setFrontendStepBehavior(FrontendStepBehavior frontendStepBehavior)
{ {
return backendOnly; this.frontendStepBehavior = frontendStepBehavior;
} }

View File

@ -35,6 +35,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
public class QFrontendStepMetaData extends QStepMetaData public class QFrontendStepMetaData extends QStepMetaData
{ {
private List<QFieldMetaData> formFields; private List<QFieldMetaData> formFields;
private List<QFieldMetaData> viewFields;
private List<QFieldMetaData> recordListFields;
@ -88,6 +90,106 @@ public class QFrontendStepMetaData extends QStepMetaData
/*******************************************************************************
** Getter for viewFields
**
*******************************************************************************/
public List<QFieldMetaData> getViewFields()
{
return viewFields;
}
/*******************************************************************************
** Setter for viewFields
**
*******************************************************************************/
public void setViewFields(List<QFieldMetaData> viewFields)
{
this.viewFields = viewFields;
}
/*******************************************************************************
** fluent setter to add a single view field
**
*******************************************************************************/
public QFrontendStepMetaData withViewField(QFieldMetaData viewField)
{
if(this.viewFields == null)
{
this.viewFields = new ArrayList<>();
}
this.viewFields.add(viewField);
return (this);
}
/*******************************************************************************
** fluent setter for viewFields
**
*******************************************************************************/
public QFrontendStepMetaData withViewFields(List<QFieldMetaData> viewFields)
{
this.viewFields = viewFields;
return (this);
}
/*******************************************************************************
** Getter for recordListFields
**
*******************************************************************************/
public List<QFieldMetaData> getRecordListFields()
{
return recordListFields;
}
/*******************************************************************************
** Setter for recordListFields
**
*******************************************************************************/
public void setRecordListFields(List<QFieldMetaData> recordListFields)
{
this.recordListFields = recordListFields;
}
/*******************************************************************************
** fluent setter to add a single recordList field
**
*******************************************************************************/
public QFrontendStepMetaData withRecordListField(QFieldMetaData recordListField)
{
if(this.recordListFields == null)
{
this.recordListFields = new ArrayList<>();
}
this.recordListFields.add(recordListField);
return (this);
}
/*******************************************************************************
** fluent setter for recordListFields
**
*******************************************************************************/
public QFrontendStepMetaData withRecordListFields(List<QFieldMetaData> recordListFields)
{
this.recordListFields = recordListFields;
return (this);
}
/******************************************************************************* /*******************************************************************************
** fluent setter for name ** fluent setter for name
** **

View File

@ -0,0 +1,75 @@
/*
* 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;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.interfaces.BackendStep;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepResult;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
/*******************************************************************************
** Function body to take care of loading the initial records to be used by a
** process.
**
*******************************************************************************/
public class LoadInitialRecordsStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepRequest runBackendStepRequest, RunBackendStepResult runBackendStepResult) throws QException
{
/////////////////////////////////////////////////////////////////////////////////////////////////
// actually, this is a no-op... we Just need a backendStep to be the first step in the process //
/////////////////////////////////////////////////////////////////////////////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/
public static QBackendStepMetaData defineMetaData(String tableName)
{
return (new QBackendStepMetaData()
.withName("loadInitialRecords")
.withCode(new QCodeReference()
.withName(LoadInitialRecordsStep.class.getName())
.withCodeType(QCodeType.JAVA)
.withCodeUsage(QCodeUsage.BACKEND_STEP))
.withInputData(new QFunctionInputMetaData()
.withRecordListMetaData(new QRecordListMetaData()
.withTableName(tableName))));
}
}

View File

@ -29,6 +29,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback; import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.interfaces.mock.MockBackendStep;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
@ -37,10 +38,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/******************************************************************************* /*******************************************************************************
@ -75,7 +78,7 @@ public class RunProcessTest
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
public void testBackendOnly() throws QException public void testBreakOnFrontendSteps() throws QException
{ {
TestCallback callback = new TestCallback(); TestCallback callback = new TestCallback();
QInstance instance = TestUtils.defineInstance(); QInstance instance = TestUtils.defineInstance();
@ -84,7 +87,7 @@ public class RunProcessTest
request.setSession(TestUtils.getMockSession()); request.setSession(TestUtils.getMockSession());
request.setProcessName(processName); request.setProcessName(processName);
request.setBackendOnly(true); request.setFrontendStepBehavior(RunProcessRequest.FrontendStepBehavior.BREAK);
request.setCallback(callback); request.setCallback(callback);
RunProcessResult result0 = new RunProcessAction().execute(request); RunProcessResult result0 = new RunProcessAction().execute(request);
@ -114,6 +117,58 @@ public class RunProcessTest
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testSkipFrontendSteps() throws QException
{
TestCallback callback = new TestCallback();
QInstance instance = TestUtils.defineInstance();
RunProcessRequest request = new RunProcessRequest(instance);
String processName = TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE;
request.setSession(TestUtils.getMockSession());
request.setProcessName(processName);
request.setFrontendStepBehavior(RunProcessRequest.FrontendStepBehavior.SKIP);
request.setCallback(callback);
RunProcessResult runProcessResult = new RunProcessAction().execute(request);
assertTrue(runProcessResult.getException().isEmpty());
assertEquals(MockBackendStep.MOCK_VALUE, runProcessResult.getValues().get(MockBackendStep.FIELD_MOCK_VALUE));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
public void testFailOnFrontendSteps()
{
TestCallback callback = new TestCallback();
QInstance instance = TestUtils.defineInstance();
RunProcessRequest request = new RunProcessRequest(instance);
String processName = TestUtils.PROCESS_NAME_GREET_PEOPLE_INTERACTIVE;
request.setSession(TestUtils.getMockSession());
request.setProcessName(processName);
request.setFrontendStepBehavior(RunProcessRequest.FrontendStepBehavior.FAIL);
request.setCallback(callback);
try
{
new RunProcessAction().execute(request);
fail("This should have thrown...");
}
catch(Exception e)
{
assertTrue(e.getMessage().contains("fail on frontend steps"));
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/