From 889697f86f2859ee1a67f5503d45ba9f151fd35d Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 10 May 2024 15:16:59 -0500 Subject: [PATCH] CE-1180 Better built-in support for processes with dynamic flows - that is: - in a backendStepOutput, you can set overrideLastStepName and call updateStepList - those values then flow through RunProcessAction to the runProcessOutput, then out through the javalin to the frontend. --- docs/metaData/Processes.adoc | 117 +++++++++- .../actions/processes/RunProcessAction.java | 34 ++- .../processes/RunBackendStepOutput.java | 128 +++++++++++ .../actions/processes/RunProcessOutput.java | 35 +++ .../RunProcessUpdateStepListTest.java | 211 ++++++++++++++++++ .../javalin/QJavalinProcessHandler.java | 7 + 6 files changed, 522 insertions(+), 10 deletions(-) create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessUpdateStepListTest.java diff --git a/docs/metaData/Processes.adoc b/docs/metaData/Processes.adoc index 253f6318..2232b3f7 100644 --- a/docs/metaData/Processes.adoc +++ b/docs/metaData/Processes.adoc @@ -16,6 +16,7 @@ Processes are defined in a QQQ Instance in a `*QProcessMetaData*` object. In addition to directly building a `QProcessMetaData` object setting its properties directly, there are a few common process patterns that provide *Builder* objects for ease-of-use. See StreamedETLWithFrontendProcess below for a common example +[#_QProcessMetaData_Properties] *QProcessMetaData Properties:* * `name` - *String, Required* - Unique name for the process within the QQQ Instance. @@ -30,12 +31,13 @@ See below for details. * `permissionRules` - *QPermissionRules object* - define the permission/access rules for the process. See {link-permissionRules} for details. * `steps` and `stepList` - *Map of String → <>* and *List of QStepMetaData* - Defines the <> and <> that makes up the process. -** `stepList` is the list of steps in the order that they will by default be executed. -** `steps` is a map, including all steps from `stepList`, but which may also include steps which can used by the process if its backend steps make the decision to do so, at run-time. +** `stepList` is the list of steps in the order that they will be executed +(that is to say - this is the _default_ order of execution - but it can be customized - see <<_custom_process_flow>> for details). +** `steps` is a map, including all steps from `stepList`, but which may also include steps which can used by the process if its backend steps make the decision to do so, at run-time (e.g., using <<_custom_process_flow>>). ** A process's steps are normally defined in one of two was: *** 1) by a single call to `.withStepList(List)`, which internally adds each step into the `steps` map. *** 2) by multiple calls to `.addStep(QStepMetaData)`, which adds a step to both the `stepList` and `steps` map. -** If a process also needs optional steps, they should be added by a call to `.addOptionalStep(QStepMetaData)`, which only places them in the `steps` map. +** If a process also needs optional steps (for a <<_custom_process_flow>>), they should be added by a call to `.addOptionalStep(QStepMetaData)`, which only places them in the `steps` map. * `schedule` - *<>* - set up the process to run automatically on the specified schedule. See below for details. * `minInputRecords` - *Integer* - #not used...# @@ -214,3 +216,112 @@ But for some cases, doing page-level transactions can reduce long-transactions a * `withFields(List fieldList)` - Adds additional input fields to the preview step of the process. * `withBasepullConfiguration(BasepullConfiguration basepullConfiguration)` - Add a <> to the process. * `withSchedule(QScheduleMetaData schedule)` - Add a <> to the process. + +[#_custom_process_flow] +==== Custom Process Flow +As referenced in the definition of the <<_QProcessMetaData_Properties,QProcessMetaData Properties>>, by default, a process +will execute each of its steps in-order, as defined in the `stepList` property. +However, a Backend Step can customize this flow #todo - write more clearly here... + +There are generally 2 method to call (in a `BackendStep`) to do a dynamic flow: + +* `RunBackendStepOutput.setOverrideLastStepName(String stepName)` +** QQQ's `RunProcessAction` keeps track of which step it "last" ran, e.g., to tell it which one to run next. +However, if a step sets the `OverrideLastStepName` property in its output object, +then the step named in that property becomes the effective "last" step, +thus determining which step comes next. + +* `RunBackendStepOutput.updateStepList(List stepNameList)` +** Calling this method changes the process's runtime definition of steps to be executed. +Thus allowing a completely custom flow. +It should be noted, that the "last" step name (as tracked by QQQ within `RunProcessAction`) +does need to be found in the new `stepNameList` - otherwise, the framework will not know where you were, +for figuring out where to go next. + +[source,java] +.Example of a defining process that can use a flexible flow: +---- +// for a case like this, it would be recommended to define all step names in constants: +public final static String STEP_START = "start"; +public final static String STEP_A = "a"; +public final static String STEP_B = "b"; +public final static String STEP_C = "c"; +public final static String STEP_1 = "1"; +public final static String STEP_2 = "2"; +public final static String STEP_3 = "3"; +public final static String STEP_END = "end"; + +// also, to define the possible flows (lists of steps) in constants as well: +public final static List LETTERS_STEP_LIST = List.of( + STEP_START, STEP_A, STEP_B, STEP_C, STEP_END); + +public final static List NUMBERS_STEP_LIST = List.of( + STEP_START, STEP_1, STEP_2, STEP_3, STEP_END); + +// when we define the process's meta-data, we only give a "skeleton" stepList - +// we must at least have our starting step, and we may want at least one frontend step +// for the UI to show some placeholder(s): +QProcessMetaData process = new QProcessMetaData() + .withName(PROCESS_NAME) + .withStepList(List.of( + new QBackendStepMetaData().withName(STEP_START) + .withCode(new QCodeReference(/*...*/)), + new QFrontendStepMetaData() + .withName(STEP_END) + )); + +// the additional steps get added via `addOptionalStep`, which only puts them in +// the process's stepMap, not its stepList! +process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_A)); +process.addOptionalStep(new QBackendStepMetaData().withName(STEP_B) + .withCode(new QCodeReference(/*...*/))); +process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_C)); + +process.addOptionalStep(new QBackendStepMetaData().withName(STEP_1) + .withCode(new QCodeReference(/*...*/))); +process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_2)); +process.addOptionalStep(new QBackendStepMetaData().withName(STEP_3) + .withCode(new QCodeReference(/*...*/))); + +---- + +[source,java] +.Example of a process backend step adjusting the process's runtime flow: +---- +/*************************************************************************** +** look at the value named "which". if it's "letters", then make the process +** go through the stepList consisting of letters; else, update the step list +** to be the "numbers" steps. +** +** Also - if the "skipSomeSteps" value is give as true, then set the +** overrideLastStepName to skip again (in the letters case, skip past A, B +** and C; in the numbers case, skip past 1 and 2). +***************************************************************************/ +public static class StartStep implements BackendStep +{ + @Override + public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException + { + Boolean skipSomeSteps = runBackendStepInput.getValueBoolean("skipSomeSteps"); + + if(runBackendStepInput.getValueString("which").equals("letters")) + { + runBackendStepOutput.updateStepList(LETTERS_STEP_LIST); + if(BooleanUtils.isTrue(skipSomeSteps)) + { + runBackendStepOutput.setOverrideLastStepName(STEP_C); + } + } + else + { + runBackendStepOutput.updateStepList(NUMBERS_STEP_LIST); + if(BooleanUtils.isTrue(skipSomeSteps)) + { + runBackendStepOutput.setOverrideLastStepName(STEP_2); + } + } + } +} +---- + + diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java index 658bd65f..d2b82ee2 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessAction.java @@ -190,7 +190,25 @@ public class RunProcessAction // Run backend steps // /////////////////////// LOG.debug("Running backend step [" + step.getName() + "] in process [" + process.getName() + "]"); - runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState); + RunBackendStepOutput runBackendStepOutput = runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState); + + ///////////////////////////////////////////////////////////////////////////////////////// + // if the step returned an override lastStepName, use that to determine how we proceed // + ///////////////////////////////////////////////////////////////////////////////////////// + if(runBackendStepOutput.getOverrideLastStepName() != null) + { + LOG.debug("Process step [" + lastStepName + "] returned an overrideLastStepName [" + runBackendStepOutput.getOverrideLastStepName() + "]!"); + lastStepName = runBackendStepOutput.getOverrideLastStepName(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // similarly, if the step produced an updatedFrontendStepList, propagate that data outward // + ///////////////////////////////////////////////////////////////////////////////////////////// + if(runBackendStepOutput.getUpdatedFrontendStepList() != null) + { + LOG.debug("Process step [" + lastStepName + "] generated an updatedFrontendStepList [" + runBackendStepOutput.getUpdatedFrontendStepList().stream().map(s -> s.getName()).toList() + "]!"); + runProcessOutput.setUpdatedFrontendStepList(runBackendStepOutput.getUpdatedFrontendStepList()); + } } else { @@ -339,7 +357,7 @@ public class RunProcessAction /******************************************************************************* ** Run a single backend step. *******************************************************************************/ - private void runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception + private RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception { RunBackendStepInput runBackendStepInput = new RunBackendStepInput(processState); runBackendStepInput.setProcessName(process.getName()); @@ -368,14 +386,16 @@ public class RunProcessAction runBackendStepInput.setBasepullLastRunTime((Instant) runProcessInput.getValues().get(BASEPULL_LAST_RUNTIME_KEY)); } - RunBackendStepOutput lastFunctionResult = new RunBackendStepAction().execute(runBackendStepInput); - storeState(stateKey, lastFunctionResult.getProcessState()); + RunBackendStepOutput runBackendStepOutput = new RunBackendStepAction().execute(runBackendStepInput); + storeState(stateKey, runBackendStepOutput.getProcessState()); - if(lastFunctionResult.getException() != null) + if(runBackendStepOutput.getException() != null) { - runProcessOutput.setException(lastFunctionResult.getException()); - throw (lastFunctionResult.getException()); + runProcessOutput.setException(runBackendStepOutput.getException()); + throw (runBackendStepOutput.getException()); } + + return (runBackendStepOutput); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java index 9a915141..4cb6aa3b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunBackendStepOutput.java @@ -27,10 +27,14 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput; import com.kingsrook.qqq.backend.core.model.actions.audits.AuditSingleInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; +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.utils.ValueUtils; @@ -40,9 +44,14 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils; *******************************************************************************/ public class RunBackendStepOutput extends AbstractActionOutput implements Serializable { + private String processName; + private ProcessState processState; private Exception exception; // todo - make optional + private String overrideLastStepName; + private List updatedFrontendStepList = null; + private List auditInputList = new ArrayList<>(); @@ -78,6 +87,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial public void seedFromRequest(RunBackendStepInput runBackendStepInput) { this.processState = runBackendStepInput.getProcessState(); + this.processName = runBackendStepInput.getProcessName(); } @@ -312,4 +322,122 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial auditInput.addAuditSingleInput(auditSingleInput); } + + + /******************************************************************************* + ** Getter for overrideLastStepName + *******************************************************************************/ + public String getOverrideLastStepName() + { + return (this.overrideLastStepName); + } + + + + /******************************************************************************* + ** Setter for overrideLastStepName + *******************************************************************************/ + public void setOverrideLastStepName(String overrideLastStepName) + { + this.overrideLastStepName = overrideLastStepName; + } + + + + /******************************************************************************* + ** Fluent setter for overrideLastStepName + *******************************************************************************/ + public RunBackendStepOutput withOverrideLastStepName(String overrideLastStepName) + { + this.overrideLastStepName = overrideLastStepName; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void updateStepList(List stepList) + { + getProcessState().setStepList(stepList); + + if(processName == null) + { + throw (new QRuntimeException("ProcessName was not set in this object, therefore updateStepList cannot complete successfully. Try to manually call setProcessName as a work around.")); + } + + QProcessMetaData processMetaData = QContext.getQInstance().getProcess(processName); + + ArrayList updatedFrontendStepList = new ArrayList<>(stepList.stream() + .map(name -> processMetaData.getStep(name)) + .filter(step -> step instanceof QFrontendStepMetaData) + .map(step -> (QFrontendStepMetaData) step) + .toList()); + + setUpdatedFrontendStepList(updatedFrontendStepList); + } + + + + /******************************************************************************* + ** Getter for processName + *******************************************************************************/ + public String getProcessName() + { + return (this.processName); + } + + + + /******************************************************************************* + ** Setter for processName + *******************************************************************************/ + public void setProcessName(String processName) + { + this.processName = processName; + } + + + + /******************************************************************************* + ** Fluent setter for processName + *******************************************************************************/ + public RunBackendStepOutput withProcessName(String processName) + { + this.processName = processName; + return (this); + } + + + + /******************************************************************************* + ** Getter for updatedFrontendStepList + *******************************************************************************/ + public List getUpdatedFrontendStepList() + { + return (this.updatedFrontendStepList); + } + + + + /******************************************************************************* + ** Setter for updatedFrontendStepList + *******************************************************************************/ + public void setUpdatedFrontendStepList(List updatedFrontendStepList) + { + this.updatedFrontendStepList = updatedFrontendStepList; + } + + + + /******************************************************************************* + ** Fluent setter for updatedFrontendStepList + *******************************************************************************/ + public RunBackendStepOutput withUpdatedFrontendStepList(List updatedFrontendStepList) + { + this.updatedFrontendStepList = updatedFrontendStepList; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessOutput.java index 466e02c4..7e05a660 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessOutput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/processes/RunProcessOutput.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.Optional; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.utils.ValueUtils; @@ -45,6 +46,8 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab private String processUUID; private Optional exception = Optional.empty(); + private List updatedFrontendStepList = null; + /******************************************************************************* @@ -327,4 +330,36 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab { return exception; } + + + + /******************************************************************************* + ** Getter for updatedFrontendStepList + *******************************************************************************/ + public List getUpdatedFrontendStepList() + { + return (this.updatedFrontendStepList); + } + + + + /******************************************************************************* + ** Setter for updatedFrontendStepList + *******************************************************************************/ + public void setUpdatedFrontendStepList(List updatedFrontendStepList) + { + this.updatedFrontendStepList = updatedFrontendStepList; + } + + + + /******************************************************************************* + ** Fluent setter for updatedFrontendStepList + *******************************************************************************/ + public RunProcessOutput withUpdatedFrontendStepList(List updatedFrontendStepList) + { + this.updatedFrontendStepList = updatedFrontendStepList; + return (this); + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessUpdateStepListTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessUpdateStepListTest.java new file mode 100644 index 00000000..7ae1ab99 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/processes/RunProcessUpdateStepListTest.java @@ -0,0 +1,211 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.processes; + + +import java.util.List; +import java.util.Optional; +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.processes.RunProcessTest.NoopBackendStep; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +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.utils.collections.MapBuilder; +import org.apache.commons.lang.BooleanUtils; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RunProcessUpdateStepListTest extends BaseTest +{ + private static final String PROCESS_NAME = RunProcessUpdateStepListTest.class.getSimpleName(); + + private final static String STEP_START = "start"; + private final static String STEP_A = "a"; + private final static String STEP_B = "b"; + private final static String STEP_C = "c"; + private final static String STEP_1 = "1"; + private final static String STEP_2 = "2"; + private final static String STEP_3 = "3"; + private final static String STEP_END = "end"; + + private final static List LETTERS_STEP_LIST = List.of( + STEP_START, + STEP_A, + STEP_B, + STEP_C, + STEP_END + ); + + private final static List NUMBERS_STEP_LIST = List.of( + STEP_START, + STEP_1, + STEP_2, + STEP_3, + STEP_END + ); + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGoingLettersPath() throws QException + { + QContext.getQInstance().addProcess(defineProcess()); + + //////////////////////////////////////////////////////////// + // start the process, telling it to go the "letters" path // + //////////////////////////////////////////////////////////// + RunProcessInput runProcessInput = new RunProcessInput(); + runProcessInput.setProcessName(PROCESS_NAME); + runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK); + runProcessInput.setValues(MapBuilder.of("which", "letters")); + RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // assert that we got back the next-step name of A, and the updated list of frontend steps (A, C) // + //////////////////////////////////////////////////////////////////////////////////////////////////// + Optional nextStepName = runProcessOutput.getProcessState().getNextStepName(); + assertTrue(nextStepName.isPresent()); + assertEquals(STEP_A, nextStepName.get()); + assertEquals(List.of(STEP_A, STEP_C, STEP_END), runProcessOutput.getUpdatedFrontendStepList().stream().map(s -> s.getName()).toList()); + + ///////////////////////////////////////////////// + // resume the process after that frontend step // + ///////////////////////////////////////////////// + runProcessInput.setProcessUUID(runProcessOutput.getProcessUUID()); + runProcessInput.setStartAfterStep(nextStepName.get()); + runProcessOutput = new RunProcessAction().execute(runProcessInput); + + /////////////////////////////////////////////////////////////////////////////////////// + // assert we got back C as the next-step now, and no updated frontend list this time // + /////////////////////////////////////////////////////////////////////////////////////// + nextStepName = runProcessOutput.getProcessState().getNextStepName(); + assertTrue(nextStepName.isPresent()); + assertEquals(STEP_C, nextStepName.get()); + assertNull(runProcessOutput.getUpdatedFrontendStepList()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGoingNumbersPathAndSkippingAhead() throws QException + { + QContext.getQInstance().addProcess(defineProcess()); + + //////////////////////////////////////////////////////////////////////////////////// + // start the process, telling it to go the "numbers" path, and to skip ahead some // + //////////////////////////////////////////////////////////////////////////////////// + RunProcessInput runProcessInput = new RunProcessInput(); + runProcessInput.setProcessName(PROCESS_NAME); + runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK); + runProcessInput.setValues(MapBuilder.of("which", "numbers", "skipSomeSteps", true)); + RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput); + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // assert that we got back the next-step name of 2, and the updated list of frontend steps (1, 3) // + //////////////////////////////////////////////////////////////////////////////////////////////////// + Optional nextStepName = runProcessOutput.getProcessState().getNextStepName(); + assertTrue(nextStepName.isPresent()); + assertEquals(STEP_END, nextStepName.get()); + assertEquals(List.of(STEP_2, STEP_END), runProcessOutput.getUpdatedFrontendStepList().stream().map(s -> s.getName()).toList()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QProcessMetaData defineProcess() + { + QProcessMetaData process = new QProcessMetaData() + .withName(PROCESS_NAME) + .withStepList(List.of( + new QBackendStepMetaData() + .withName(STEP_START) + .withCode(new QCodeReference(StartStep.class)), + new QFrontendStepMetaData() + .withName(STEP_END) + )); + + process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_A)); + process.addOptionalStep(new QBackendStepMetaData().withName(STEP_B).withCode(new QCodeReference(NoopBackendStep.class))); + process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_C)); + + process.addOptionalStep(new QBackendStepMetaData().withName(STEP_1).withCode(new QCodeReference(NoopBackendStep.class))); + process.addOptionalStep(new QFrontendStepMetaData().withName(STEP_2)); + process.addOptionalStep(new QBackendStepMetaData().withName(STEP_3).withCode(new QCodeReference(NoopBackendStep.class))); + + return (process); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class StartStep implements BackendStep + { + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException + { + boolean skipSomeSteps = BooleanUtils.isTrue(runBackendStepInput.getValueBoolean("skipSomeSteps")); + + if(runBackendStepInput.getValueString("which").equals("letters")) + { + runBackendStepOutput.updateStepList(LETTERS_STEP_LIST); + if(skipSomeSteps) + { + runBackendStepOutput.setOverrideLastStepName(STEP_C); + } + } + else + { + runBackendStepOutput.updateStepList(NUMBERS_STEP_LIST); + if(skipSomeSteps) + { + runBackendStepOutput.setOverrideLastStepName(STEP_2); + } + } + } + } +} diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java index d4238f64..957b5a21 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinProcessHandler.java @@ -72,6 +72,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput; 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.fields.QFieldMetaData; +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.reporting.QReportMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; @@ -448,6 +449,12 @@ public class QJavalinProcessHandler } resultForCaller.put("values", runProcessOutput.getValues()); runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep)); + + List updatedFrontendStepList = runProcessOutput.getUpdatedFrontendStepList(); + if(updatedFrontendStepList != null) + { + resultForCaller.put("updatedFrontendStepList", updatedFrontendStepList); + } }