diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java index 3238a92c..ae108115 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java @@ -24,7 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.scripts; import java.io.Serializable; import java.util.HashMap; +import java.util.Map; +import java.util.Objects; 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; import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger; import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.exceptions.QException; @@ -35,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptI import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; +import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.scripts.Script; @@ -46,6 +51,8 @@ import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision; *******************************************************************************/ public class RunAssociatedScriptAction { + private Map scriptRevisionCache = new HashMap<>(); + /******************************************************************************* ** @@ -54,21 +61,7 @@ public class RunAssociatedScriptAction { ActionHelper.validateSession(input); - Serializable scriptId = getScriptId(input); - if(scriptId == null) - { - throw (new QNotFoundException("The input record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() - + "] does not have a script specified for [" + input.getCodeReference().getFieldName() + "]")); - } - - Script script = getScript(input, scriptId); - if(script.getCurrentScriptRevisionId() == null) - { - throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() - + "] (scriptId=" + scriptId + ") does not have a current version.")); - } - - ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId()); + ScriptRevision scriptRevision = getScriptRevision(input); ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(); executeCodeInput.setInput(new HashMap<>(input.getInputValues())); @@ -78,7 +71,21 @@ public class RunAssociatedScriptAction executeCodeInput.getContext().put("output", input.getOutputObject()); } executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!! - executeCodeInput.setExecutionLogger(new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId())); + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // 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); @@ -87,6 +94,36 @@ public class RunAssociatedScriptAction + /******************************************************************************* + ** + *******************************************************************************/ + private ScriptRevision getScriptRevision(RunAssociatedScriptInput input) throws QException + { + if(!scriptRevisionCache.containsKey(input.getCodeReference())) + { + Serializable scriptId = getScriptId(input); + if(scriptId == null) + { + throw (new QNotFoundException("The input record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + + "] does not have a script specified for [" + input.getCodeReference().getFieldName() + "]")); + } + + Script script = getScript(input, scriptId); + if(script.getCurrentScriptRevisionId() == null) + { + throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + + "] (scriptId=" + scriptId + ") does not have a current version.")); + } + + ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId()); + scriptRevisionCache.put(input.getCodeReference(), scriptRevision); + } + + return scriptRevisionCache.get(input.getCodeReference()); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger.java new file mode 100644 index 00000000..2b2db36b --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger.java @@ -0,0 +1,160 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.scripts.logging; + + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLogAndScriptLogLineExecutionLogger implements ScriptExecutionLoggerInterface +{ + private static final QLogger LOG = QLogger.getLogger(AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger.class); + + private List scriptLogs = new ArrayList<>(); + private List> scriptLogLines = new ArrayList<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + public AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void acceptExecutionStart(ExecuteCodeInput executeCodeInput) + { + super.acceptExecutionStart(executeCodeInput); + super.setScriptLogLines(new ArrayList<>()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void acceptException(Exception exception) + { + accumulate(null, exception); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void acceptExecutionEnd(Serializable output) + { + accumulate(output, null); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void accumulate(Serializable output, Exception exception) + { + super.updateHeaderAtEnd(output, exception); + scriptLogs.add(super.getScriptLog()); + scriptLogLines.add(new ArrayList<>(super.getScriptLogLines())); + super.getScriptLogLines().clear(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void storeAndClear() + { + try + { + InsertInput insertInput = new InsertInput(); + insertInput.setTableName("scriptLog"); + insertInput.setRecords(scriptLogs); + InsertOutput insertOutput = new InsertAction().execute(insertInput); + + List flatScriptLogLines = new ArrayList<>(); + for(int i = 0; i < insertOutput.getRecords().size(); i++) + { + QRecord insertedScriptLog = insertOutput.getRecords().get(i); + List subScriptLogLines = scriptLogLines.get(i); + subScriptLogLines.forEach(r -> r.setValue("scriptLogId", insertedScriptLog.getValueInteger("id"))); + flatScriptLogLines.addAll(subScriptLogLines); + } + + insertInput = new InsertInput(); + insertInput.setTableName("scriptLogLine"); + insertInput.setRecords(flatScriptLogLines); + new InsertAction().execute(insertInput); + } + catch(Exception e) + { + LOG.warn("Error storing script logs", e); + } + + scriptLogs.clear(); + scriptLogLines.clear(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void setScriptId(Integer scriptId) + { + super.setScriptId(scriptId); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void setScriptRevisionId(Integer scriptRevisionId) + { + super.setScriptRevisionId(scriptRevisionId); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java index 82ce9a22..d577425b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java @@ -39,7 +39,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils; ** scriptLogLine records - but doesn't insert them. e.g., useful for testing ** (both in junit, and for users in-app). *******************************************************************************/ -public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecutionLoggerInterface +public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecutionLoggerInterface, ScriptExecutionLoggerInterface { private static final QLogger LOG = QLogger.getLogger(BuildScriptLogAndScriptLogLineExecutionLogger.class); @@ -217,4 +217,37 @@ public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecu { this.scriptLog = scriptLog; } + + + + /******************************************************************************* + ** Setter for scriptLogLines + ** + *******************************************************************************/ + protected void setScriptLogLines(List scriptLogLines) + { + this.scriptLogLines = scriptLogLines; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void setScriptId(Integer scriptId) + { + this.scriptId = scriptId; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void setScriptRevisionId(Integer scriptRevisionId) + { + this.scriptRevisionId = scriptRevisionId; + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/ScriptExecutionLoggerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/ScriptExecutionLoggerInterface.java new file mode 100644 index 00000000..ff10195c --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/ScriptExecutionLoggerInterface.java @@ -0,0 +1,39 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.scripts.logging; + + +/******************************************************************************* + ** + *******************************************************************************/ +public interface ScriptExecutionLoggerInterface +{ + /******************************************************************************* + ** + *******************************************************************************/ + void setScriptId(Integer scriptId); + + /******************************************************************************* + ** + *******************************************************************************/ + void setScriptRevisionId(Integer scriptRevisionId); +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAssociatedScriptInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAssociatedScriptInput.java index 40a5ce3d..86fdef1c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAssociatedScriptInput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAssociatedScriptInput.java @@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts; import java.io.Serializable; import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference; @@ -35,6 +36,7 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput { private AssociatedScriptCodeReference codeReference; private Map inputValues; + private QCodeExecutionLoggerInterface logger; private Serializable outputObject; @@ -149,4 +151,35 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput return (this); } + + /******************************************************************************* + ** Getter for logger + *******************************************************************************/ + public QCodeExecutionLoggerInterface getLogger() + { + return (this.logger); + } + + + + /******************************************************************************* + ** Setter for logger + *******************************************************************************/ + public void setLogger(QCodeExecutionLoggerInterface logger) + { + this.logger = logger; + } + + + + /******************************************************************************* + ** Fluent setter for logger + *******************************************************************************/ + public RunAssociatedScriptInput withLogger(QCodeExecutionLoggerInterface logger) + { + this.logger = logger; + return (this); + } + + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AssociatedScriptCodeReference.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AssociatedScriptCodeReference.java index 716b4063..16da825b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AssociatedScriptCodeReference.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AssociatedScriptCodeReference.java @@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.code; import java.io.Serializable; +import java.util.Objects; /******************************************************************************* @@ -136,4 +137,34 @@ public class AssociatedScriptCodeReference extends QCodeReference return (this); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + AssociatedScriptCodeReference that = (AssociatedScriptCodeReference) o; + return Objects.equals(recordTable, that.recordTable) && Objects.equals(recordPrimaryKey, that.recordPrimaryKey) && Objects.equals(fieldName, that.fieldName); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public int hashCode() + { + return Objects.hash(recordTable, recordPrimaryKey, fieldName); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java index ac671f61..68cea99c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java @@ -25,11 +25,16 @@ package com.kingsrook.qqq.backend.core.model.scripts; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType; import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; @@ -51,6 +56,35 @@ public class ScriptsMetaDataProvider { defineStandardScriptsTables(instance, backendName, backendDetailEnricher); defineStandardScriptsPossibleValueSources(instance); + defineStandardScriptsJoins(instance); + defineStandardScriptsWidgets(instance); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void defineStandardScriptsWidgets(QInstance instance) + { + instance.addWidget(ChildRecordListRenderer.widgetMetaDataBuilder(instance.getJoin(QJoinMetaData.makeInferredJoinName(ScriptLog.TABLE_NAME, ScriptLogLine.TABLE_NAME))) + .withLabel("Log Lines") + .getWidgetMetaData()); + } + + + /******************************************************************************* + ** + *******************************************************************************/ + public void defineStandardScriptsJoins(QInstance instance) + { + instance.addJoin(new QJoinMetaData() + .withType(JoinType.ONE_TO_MANY) + .withLeftTable(ScriptLog.TABLE_NAME) + .withRightTable(ScriptLogLine.TABLE_NAME) + .withJoinOn(new JoinOn("id", "scriptLogId")) + .withOrderBy(new QFilterOrderBy("id")) + .withInferredName()); } @@ -201,7 +235,8 @@ public class ScriptsMetaDataProvider .withSection(new QFieldSection("script", new QIcon().withName("data_object"), Tier.T2, List.of("scriptId", "scriptRevisionId"))) .withSection(new QFieldSection("timing", new QIcon().withName("schedule"), Tier.T2, List.of("startTimestamp", "endTimestamp", "runTimeMillis", "createDate", "modifyDate"))) .withSection(new QFieldSection("error", "Error", new QIcon().withName("error_outline"), Tier.T2, List.of("hadError", "error"))) - .withSection(new QFieldSection("inputOutput", "Input/Output", new QIcon().withName("chat"), Tier.T2, List.of("input", "output")))); + .withSection(new QFieldSection("inputOutput", "Input/Output", new QIcon().withName("chat"), Tier.T2, List.of("input", "output"))) + .withSection(new QFieldSection("lines", new QIcon().withName("horizontal_rule"), Tier.T2).withWidgetName(QJoinMetaData.makeInferredJoinName(ScriptLog.TABLE_NAME, ScriptLogLine.TABLE_NAME)))); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java index c98a687d..d607f456 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java @@ -56,6 +56,7 @@ public class MemoryRecordStore private static boolean collectStatistics = false; public static final String STAT_QUERIES_RAN = "queriesRan"; + public static final String STAT_INSERTS_RAN = "insertsRan"; private static final Map statistics = Collections.synchronizedMap(new HashMap<>()); @@ -173,6 +174,8 @@ public class MemoryRecordStore *******************************************************************************/ public List insert(InsertInput input, boolean returnInsertedRecords) { + incrementStatistic(input); + if(input.getRecords() == null) { return (new ArrayList<>()); @@ -324,6 +327,10 @@ public class MemoryRecordStore { incrementStatistic(STAT_QUERIES_RAN); } + else if(input instanceof InsertInput) + { + incrementStatistic(STAT_INSERTS_RAN); + } } } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptActionTest.java index 0a5fe847..be04f40a 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptActionTest.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.util.List; import java.util.Map; import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.scripts.logging.AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger; import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.context.QContext; @@ -45,10 +46,16 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.scripts.ScriptLog; import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider; +import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore; import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; /******************************************************************************* @@ -57,15 +64,28 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; class RunAssociatedScriptActionTest extends BaseTest { + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + @AfterEach + void beforeAndAfterEach() + { + MemoryRecordStore.getInstance().reset(); + MemoryRecordStore.resetStatistics(); + } + + + /******************************************************************************* ** *******************************************************************************/ @Test void test() throws QException { - QInstance instance = setupInstance(); + setupInstance(); - insertScript(instance, 1, """ + insertScript(1, """ return "Hello"; """); @@ -86,6 +106,11 @@ class RunAssociatedScriptActionTest extends BaseTest .isInstanceOf(QException.class) .hasRootCauseInstanceOf(ClassNotFoundException.class) .hasRootCauseMessage("com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor"); + + ///////////////////////////////////// + // assert that a log was generated // + ///////////////////////////////////// + assertEquals(1, TestUtils.queryTable(ScriptLog.TABLE_NAME).size()); } @@ -93,7 +118,61 @@ class RunAssociatedScriptActionTest extends BaseTest /******************************************************************************* ** *******************************************************************************/ - private QInstance setupInstance() throws QException + @Test + void testOverridingLoggerAndCachingScriptLookups() throws QException + { + setupInstance(); + + insertScript(1, """ + return "Hello"; + """); + + AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger scriptLogger = new AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger(); + + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setLogger(scriptLogger); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(1) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + MemoryRecordStore.setCollectStatistics(true); + RunAssociatedScriptAction runAssociatedScriptAction = new RunAssociatedScriptAction(); + + int N = 10; + for(int i = 0; i < N; i++) + { + assertThatThrownBy(() -> runAssociatedScriptAction.run(runAssociatedScriptInput, runAssociatedScriptOutput)); + } + + scriptLogger.storeAndClear(); + + ///////////////////////////////////// + // assert that logs were generated // + ///////////////////////////////////// + assertEquals(N, TestUtils.queryTable(ScriptLog.TABLE_NAME).size()); + + //////////////////////////////////////////////////////////////////////////////////////// + // and we should have just ran 2 inserts - for the log & logLines (even though empty) // + //////////////////////////////////////////////////////////////////////////////////////// + assertEquals(2, MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_INSERTS_RAN)); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // and we shouldn't have run N queries (which we would have (at least), if we would have built a new Action object inside the loop) // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + assertThat(MemoryRecordStore.getStatistics().get(MemoryRecordStore.STAT_QUERIES_RAN)).isLessThan(N); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void setupInstance() throws QException { QInstance instance = QContext.getQInstance(); QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) @@ -113,7 +192,6 @@ class RunAssociatedScriptActionTest extends BaseTest TestUtils.insertRecords(instance, instance.getTable("scriptType"), List.of( new QRecord().withValue("id", 1).withValue("name", "Test Script Type") )); - return instance; } @@ -124,7 +202,7 @@ class RunAssociatedScriptActionTest extends BaseTest @Test void testRecordNotFound() throws QException { - QInstance instance = setupInstance(); + setupInstance(); RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(); runAssociatedScriptInput.setInputValues(Map.of()); @@ -149,7 +227,7 @@ class RunAssociatedScriptActionTest extends BaseTest @Test void testNoScriptInRecord() throws QException { - QInstance instance = setupInstance(); + setupInstance(); RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(); runAssociatedScriptInput.setInputValues(Map.of()); @@ -174,7 +252,7 @@ class RunAssociatedScriptActionTest extends BaseTest @Test void testBadScriptIdInRecord() throws QException { - QInstance instance = setupInstance(); + setupInstance(); UpdateInput updateInput = new UpdateInput(); updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); @@ -204,9 +282,9 @@ class RunAssociatedScriptActionTest extends BaseTest @Test void testNoCurrentScriptRevisionOnScript() throws QException { - QInstance instance = setupInstance(); + setupInstance(); - insertScript(instance, 1, """ + insertScript(1, """ return "Hello"; """); @@ -244,9 +322,9 @@ class RunAssociatedScriptActionTest extends BaseTest @Test void testBadCurrentScriptRevisionOnScript() throws QException { - QInstance instance = setupInstance(); + setupInstance(); - insertScript(instance, 1, """ + insertScript(1, """ return "Hello"; """); @@ -281,7 +359,7 @@ class RunAssociatedScriptActionTest extends BaseTest /******************************************************************************* ** *******************************************************************************/ - private void insertScript(QInstance instance, Serializable recordId, String code) throws QException + private void insertScript(Serializable recordId, String code) throws QException { StoreAssociatedScriptInput storeAssociatedScriptInput = new StoreAssociatedScriptInput(); storeAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);