diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java index 19824ec0..d38e4d63 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface; @@ -73,79 +74,86 @@ public class RunAdHocRecordScriptAction *******************************************************************************/ public void run(RunAdHocRecordScriptInput input, RunAdHocRecordScriptOutput output) throws QException { - ActionHelper.validateSession(input); - - ///////////////////////// - // figure out the code // - ///////////////////////// - ScriptRevision scriptRevision = getScriptRevision(input); - if(scriptRevision == null) + try { - throw (new QException("Script revision was not found.")); - } + ActionHelper.validateSession(input); - //////////////////////////// - // figure out the records // - //////////////////////////// - QTableMetaData table = QContext.getQInstance().getTable(input.getTableName()); - if(CollectionUtils.nullSafeIsEmpty(input.getRecordList())) + ///////////////////////// + // figure out the code // + ///////////////////////// + ScriptRevision scriptRevision = getScriptRevision(input); + if(scriptRevision == null) + { + throw (new QException("Script revision was not found.")); + } + + //////////////////////////// + // figure out the records // + //////////////////////////// + QTableMetaData table = QContext.getQInstance().getTable(input.getTableName()); + if(CollectionUtils.nullSafeIsEmpty(input.getRecordList())) + { + QueryInput queryInput = new QueryInput(); + queryInput.setTableName(input.getTableName()); + queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getRecordPrimaryKeyList()))); + QueryOutput queryOutput = new QueryAction().execute(queryInput); + input.setRecordList(queryOutput.getRecords()); + } + + if(CollectionUtils.nullSafeIsEmpty(input.getRecordList())) + { + //////////////////////////////////////// + // just return if nothing found? idk // + //////////////////////////////////////// + LOG.info("No records supplied as input (or found via primary keys); exiting with noop"); + return; + } + + ///////////// + // run it! // + ///////////// + ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(); + executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new))); + executeCodeInput.getInput().put("records", new ArrayList<>(input.getRecordList())); + executeCodeInput.setContext(new HashMap<>()); + if(input.getOutputObject() != null) + { + executeCodeInput.getContext().put("output", input.getOutputObject()); + } + + if(input.getScriptUtils() != null) + { + executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils()); + } + + executeCodeInput.getContext().put("api", new ScriptApi()); + + executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!! + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger // + ///////////////////////////////////////////////////////////////////////////////////////////////// + QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId())); + executeCodeInput.setExecutionLogger(executionLogger); + if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface) + { + //////////////////////////////////////////////////////////////////////////////////////////////////// + // if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. // + //////////////////////////////////////////////////////////////////////////////////////////////////// + scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId()); + scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId()); + } + + ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput(); + new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); + + output.setOutput(executeCodeOutput.getOutput()); + output.setLogger(executionLogger); + } + catch(Exception e) { - QueryInput queryInput = new QueryInput(); - queryInput.setTableName(input.getTableName()); - queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getRecordPrimaryKeyList()))); - QueryOutput queryOutput = new QueryAction().execute(queryInput); - input.setRecordList(queryOutput.getRecords()); + output.setException(Optional.of(e)); } - - if(CollectionUtils.nullSafeIsEmpty(input.getRecordList())) - { - //////////////////////////////////////// - // just return if nothing found? idk // - //////////////////////////////////////// - LOG.info("No records supplied as input (or found via primary keys); exiting with noop"); - return; - } - - ///////////// - // run it! // - ///////////// - ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(); - executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new))); - executeCodeInput.getInput().put("records", new ArrayList<>(input.getRecordList())); - executeCodeInput.setContext(new HashMap<>()); - if(input.getOutputObject() != null) - { - executeCodeInput.getContext().put("output", input.getOutputObject()); - } - - if(input.getScriptUtils() != null) - { - executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils()); - } - - executeCodeInput.getContext().put("api", new ScriptApi()); - - executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!! - - ///////////////////////////////////////////////////////////////////////////////////////////////// - // let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger // - ///////////////////////////////////////////////////////////////////////////////////////////////// - QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId())); - executeCodeInput.setExecutionLogger(executionLogger); - if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface) - { - //////////////////////////////////////////////////////////////////////////////////////////////////// - // if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. // - //////////////////////////////////////////////////////////////////////////////////////////////////// - scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId()); - scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId()); - } - - ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput(); - new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); - - output.setOutput(executeCodeOutput.getOutput()); - output.setLogger(executionLogger); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java index 90f761f8..fc8098a1 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts; import java.io.Serializable; +import java.util.Optional; import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; @@ -32,9 +33,9 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; *******************************************************************************/ public class RunAdHocRecordScriptOutput extends AbstractActionOutput { - private Serializable output; - + private Serializable output; private QCodeExecutionLoggerInterface logger; + private Optional exception = Optional.empty(); @@ -101,4 +102,35 @@ public class RunAdHocRecordScriptOutput extends AbstractActionOutput return (this); } + + + /******************************************************************************* + ** Getter for exception + *******************************************************************************/ + public Optional getException() + { + return (this.exception); + } + + + + /******************************************************************************* + ** Setter for exception + *******************************************************************************/ + public void setException(Optional exception) + { + this.exception = exception; + } + + + + /******************************************************************************* + ** Fluent setter for exception + *******************************************************************************/ + public RunAdHocRecordScriptOutput withException(Optional exception) + { + this.exception = exception; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptLoadStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptLoadStep.java index 74ecdc40..b4af3470 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptLoadStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptLoadStep.java @@ -65,6 +65,8 @@ public class RunRecordScriptLoadStep extends AbstractLoadStep implements Process .withSingularPastMessage("had the script ran against it.") .withPluralPastMessage("had the script ran against them."); + private ProcessSummaryLine unloggedExceptionLine = new ProcessSummaryLine(Status.ERROR, null, "had an error that was not logged."); + private List okScriptLogIds = new ArrayList<>(); private List errorScriptLogIds = new ArrayList<>(); @@ -115,18 +117,24 @@ public class RunRecordScriptLoadStep extends AbstractLoadStep implements Process Integer scriptId = runBackendStepInput.getValueInteger("scriptId"); StoreScriptLogAndScriptLogLineExecutionLogger scriptLogger = new StoreScriptLogAndScriptLogLineExecutionLogger(null, null); // downstream these will get set! + RunAdHocRecordScriptInput input = new RunAdHocRecordScriptInput(); + input.setRecordList(runBackendStepInput.getRecords()); + input.setCodeReference(new AdHocScriptCodeReference().withScriptId(scriptId)); + input.setLogger(scriptLogger); + RunAdHocRecordScriptOutput output = new RunAdHocRecordScriptOutput(); + Exception caughtException = null; try { - RunAdHocRecordScriptInput input = new RunAdHocRecordScriptInput(); - input.setRecordList(runBackendStepInput.getRecords()); - input.setCodeReference(new AdHocScriptCodeReference().withScriptId(scriptId)); - input.setLogger(scriptLogger); - RunAdHocRecordScriptOutput output = new RunAdHocRecordScriptOutput(); new RunAdHocRecordScriptAction().run(input, output); + if(output.getException().isPresent()) + { + caughtException = output.getException().get(); + } } catch(Exception e) { LOG.info("Exception running record script", e, logPair("scriptId", scriptId)); + caughtException = e; } if(scriptLogger.getScriptLog() != null) @@ -138,6 +146,10 @@ public class RunRecordScriptLoadStep extends AbstractLoadStep implements Process (hadError ? errorScriptLogIds : okScriptLogIds).add(id); } } + else if(caughtException != null) + { + unloggedExceptionLine.incrementCount(runBackendStepInput.getRecords().size()); + } } @@ -163,6 +175,8 @@ public class RunRecordScriptLoadStep extends AbstractLoadStep implements Process .withLinkText("Created " + String.format("%,d", errorScriptLogIds.size()) + " Script Log" + StringUtils.plural(errorScriptLogIds) + " with Errors")); } + unloggedExceptionLine.addSelfToListIfAnyCount(summary); + return (summary); } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java index 19c316c3..30f9098f 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java @@ -46,7 +46,7 @@ import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider; import com.kingsrook.qqq.backend.core.utils.TestUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; /******************************************************************************* @@ -72,9 +72,10 @@ class RunAdHocRecordScriptActionTest extends BaseTest RunAdHocRecordScriptOutput runAdHocRecordScriptOutput = new RunAdHocRecordScriptOutput(); - assertThatThrownBy(() -> new RunAdHocRecordScriptAction().run(runAdHocRecordScriptInput, runAdHocRecordScriptOutput)) + new RunAdHocRecordScriptAction().run(runAdHocRecordScriptInput, runAdHocRecordScriptOutput); + assertThat(runAdHocRecordScriptOutput.getException()).isPresent().get() .isInstanceOf(QException.class) - .hasMessageContaining("Script revision was not found"); + .hasFieldOrPropertyWithValue("message", "Script revision was not found."); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptTest.java index 02d39585..76181ecc 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/RunRecordScriptTest.java @@ -22,17 +22,22 @@ package com.kingsrook.qqq.backend.core.processes.implementations.scripts; +import java.util.List; import com.kingsrook.qqq.backend.core.BaseTest; import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine; +import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput; +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.scripts.ScriptsMetaDataProvider; import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* @@ -50,6 +55,8 @@ class RunRecordScriptTest extends BaseTest QInstance qInstance = QContext.getQInstance(); new ScriptsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null); + TestUtils.insertRecords(qInstance, qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY), List.of(new QRecord().withValue("id", 1))); + RunProcessInput runProcessInput = new RunProcessInput(); runProcessInput.setProcessName(ScriptsMetaDataProvider.RUN_RECORD_SCRIPT_PROCESS_NAME); runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP); @@ -63,9 +70,9 @@ class RunRecordScriptTest extends BaseTest // so, turns out, we don't know how to do a join yet in memory backend, so this can't quite work // // still good to run the code and at least get this far w/ an expected exception. // /////////////////////////////////////////////////////////////////////////////////////////////////// - assertThatThrownBy(() -> new RunProcessAction().execute(runProcessInput)) - .isInstanceOf(QException.class) - .hasMessageContaining("Script revision was not found"); + RunProcessOutput runProcessOutput = new RunProcessAction().execute(runProcessInput); + System.out.println(runProcessOutput); + assertTrue(((List) runProcessOutput.getValues().get("processResults")).stream().anyMatch(psli -> psli instanceof ProcessSummaryLine psl && psl.getMessage().contains("error that was not logged"))); } } \ No newline at end of file