diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java index 3defb074..1260ec09 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard; +import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; @@ -51,6 +52,8 @@ public class QuickSightChartRenderer extends AbstractWidgetRenderer @Override public RenderWidgetOutput render(RenderWidgetInput input) throws QException { + ActionHelper.validateSession(input); + try { QuickSightChartMetaData quickSightMetaData = (QuickSightChartMetaData) input.getWidgetMetaData(); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java index 7527556e..9cf25b4c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java @@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode; import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData; @@ -69,8 +70,10 @@ public class MetaDataAction Map tables = new LinkedHashMap<>(); for(Map.Entry entry : metaDataInput.getInstance().getTables().entrySet()) { - tables.put(entry.getKey(), new QFrontendTableMetaData(entry.getValue(), false)); - treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue())); + String tableName = entry.getKey(); + QBackendMetaData backendForTable = metaDataInput.getInstance().getBackendForTable(tableName); + tables.put(tableName, new QFrontendTableMetaData(backendForTable, entry.getValue(), false)); + treeNodes.put(tableName, new AppTreeNode(entry.getValue())); } metaDataOutput.setTables(tables); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/TableMetaDataAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/TableMetaDataAction.java index 62623bdd..d67ec855 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/TableMetaDataAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/TableMetaDataAction.java @@ -27,6 +27,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; @@ -52,7 +53,8 @@ public class TableMetaDataAction { throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found.")); } - tableMetaDataOutput.setTable(new QFrontendTableMetaData(table, true)); + QBackendMetaData backendForTable = tableMetaDataInput.getInstance().getBackendForTable(table.getName()); + tableMetaDataOutput.setTable(new QFrontendTableMetaData(backendForTable, table, true)); // todo post-customization - can do whatever w/ the result if you want diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java index 2715060e..44db3df9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java @@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.actions.scripts; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger; import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; import com.kingsrook.qqq.backend.core.exceptions.QCodeException; @@ -33,7 +35,17 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; /******************************************************************************* + ** Action to execute user/runtime defined code. ** + ** This action is designed to support code in multiple languages, by using + ** executors, e.g., provided by additional runtime qqq dependencies. Initially + ** we are building qqq-language-support-javascript. + ** + ** We also have a Java executor, to provide at least a little bit of testability + ** within qqq-backend-core. This executor is a candidate to be replaced in the + ** future with something that would do actual dynamic java (whether that's compiled + ** at runtime, or loaded from a plugin jar at runtime). In other words, the java + ** executor in place today is just meant to be a placeholder. *******************************************************************************/ public class ExecuteCodeAction { @@ -65,7 +77,20 @@ public class ExecuteCodeAction Class executorClass = (Class) Class.forName(languageExecutor); QCodeExecutor qCodeExecutor = executorClass.getConstructor().newInstance(); - Serializable codeOutput = qCodeExecutor.execute(codeReference, input.getContext(), executionLogger); + //////////////////////////////////////////////////////////////////////////////////////////////////// + // merge all of the input context, plus the input... input - into a context for the code executor // + //////////////////////////////////////////////////////////////////////////////////////////////////// + Map context = new HashMap<>(); + if(input.getContext() != null) + { + context.putAll(input.getContext()); + } + if(input.getInput() != null) + { + context.putAll(input.getInput()); + } + + Serializable codeOutput = qCodeExecutor.execute(codeReference, context, executionLogger); output.setOutput(codeOutput); executionLogger.acceptExecutionEnd(codeOutput); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QCodeExecutor.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QCodeExecutor.java index 9ea44224..e6cd808a 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QCodeExecutor.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QCodeExecutor.java @@ -30,7 +30,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; /******************************************************************************* - ** + ** Interface to be implemented by language-specific code executors, e.g., in + ** qqq-language-support-${languageName} maven modules. *******************************************************************************/ public interface QCodeExecutor { @@ -38,6 +39,6 @@ public interface QCodeExecutor /******************************************************************************* ** *******************************************************************************/ - Serializable execute(QCodeReference codeReference, Map context, QCodeExecutionLoggerInterface executionLogger) throws QCodeException; + Serializable execute(QCodeReference codeReference, Map inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException; } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java index 82d303c6..61451b38 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java @@ -33,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; /******************************************************************************* - ** + ** Java *******************************************************************************/ public class QJavaExecutor implements QCodeExecutor { @@ -42,9 +42,9 @@ public class QJavaExecutor implements QCodeExecutor ** *******************************************************************************/ @Override - public Serializable execute(QCodeReference codeReference, Map input, QCodeExecutionLoggerInterface executionLogger) throws QCodeException + public Serializable execute(QCodeReference codeReference, Map inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException { - Map context = new HashMap<>(input); + Map context = new HashMap<>(inputContext); if(!context.containsKey("logger")) { context.put("logger", executionLogger); 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 7a14cfb3..9a88b15a 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,9 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.scripts; import java.io.Serializable; import java.util.HashMap; -import com.kingsrook.qqq.backend.core.actions.scripts.logging.HeaderAndDetailTableCodeExecutionLogger; +import com.kingsrook.qqq.backend.core.actions.ActionHelper; +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; +import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput; import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptInput; @@ -50,15 +52,34 @@ public class RunAssociatedScriptAction *******************************************************************************/ public void run(RunAssociatedScriptInput input, RunAssociatedScriptOutput output) throws QException { - Serializable scriptId = getScriptId(input); - Script script = getScript(input, scriptId); + 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()); ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance()); executeCodeInput.setSession(input.getSession()); - executeCodeInput.setContext(new HashMap<>(input.getInputValues())); - executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); - executeCodeInput.setExecutionLogger(new HeaderAndDetailTableCodeExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId())); + executeCodeInput.setInput(new HashMap<>(input.getInputValues())); + executeCodeInput.setContext(new HashMap<>()); + if(input.getOutputObject() != null) + { + 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())); ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput(); new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); @@ -77,6 +98,12 @@ public class RunAssociatedScriptAction getInput.setTableName("scriptRevision"); getInput.setPrimaryKey(scriptRevisionId); GetOutput getOutput = new GetAction().execute(getInput); + if(getOutput.getRecord() == null) + { + throw (new QNotFoundException("The current revision of the script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "][" + + input.getCodeReference().getFieldName() + "] (scriptRevisionId=" + scriptRevisionId + ") was not found.")); + } + return (new ScriptRevision(getOutput.getRecord())); } @@ -92,6 +119,13 @@ public class RunAssociatedScriptAction getInput.setTableName("script"); getInput.setPrimaryKey(scriptId); GetOutput getOutput = new GetAction().execute(getInput); + + if(getOutput.getRecord() == null) + { + throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "][" + + input.getCodeReference().getFieldName() + "] (script id=" + scriptId + ") was not found.")); + } + return (new Script(getOutput.getRecord())); } @@ -107,6 +141,11 @@ public class RunAssociatedScriptAction getInput.setTableName(input.getCodeReference().getRecordTable()); getInput.setPrimaryKey(input.getCodeReference().getRecordPrimaryKey()); GetOutput getOutput = new GetAction().execute(getInput); + if(getOutput.getRecord() == null) + { + throw (new QNotFoundException("The requested record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "] was not found.")); + } + return (getOutput.getRecord().getValue(input.getCodeReference().getFieldName())); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptAction.java index aad454cd..c4bfe711 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptAction.java @@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.scripts; import java.io.Serializable; import java.util.List; import java.util.Optional; +import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; @@ -50,6 +51,13 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; /******************************************************************************* + ** Action to store a new version of a script, associated with a record. + ** + ** If there's never been a script assigned to the record (for the specified field), + ** then a new Script record is first inserted. + ** + ** The script referenced by the record is always updated to point at the new + ** scriptRevision record that is inserted. ** *******************************************************************************/ public class StoreAssociatedScriptAction @@ -60,6 +68,8 @@ public class StoreAssociatedScriptAction *******************************************************************************/ public void run(StoreAssociatedScriptInput input, StoreAssociatedScriptOutput output) throws QException { + ActionHelper.validateSession(input); + QTableMetaData table = input.getTable(); Optional optAssociatedScript = table.getAssociatedScripts().stream().filter(as -> as.getFieldName().equals(input.getFieldName())).findFirst(); if(optAssociatedScript.isEmpty()) @@ -77,6 +87,7 @@ public class StoreAssociatedScriptAction getInput.setSession(input.getSession()); getInput.setTableName(input.getTableName()); getInput.setPrimaryKey(input.getRecordPrimaryKey()); + getInput.setShouldGenerateDisplayValues(true); GetOutput getOutput = new GetAction().execute(getInput); associatedRecord = getOutput.getRecord(); } @@ -100,8 +111,13 @@ public class StoreAssociatedScriptAction getInput.setSession(input.getSession()); getInput.setTableName("scriptType"); getInput.setPrimaryKey(associatedScript.getScriptTypeId()); + getInput.setShouldGenerateDisplayValues(true); GetOutput getOutput = new GetAction().execute(getInput); QRecord scriptType = getOutput.getRecord(); + if(scriptType == null) + { + throw (new QException("Script type [" + associatedScript.getScriptTypeId() + "] was not found.")); + } ///////////////////////// // insert a new script // diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptAction.java index 3b651907..7e9e1e5d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptAction.java @@ -22,14 +22,20 @@ package com.kingsrook.qqq.backend.core.actions.scripts; +import java.util.HashMap; +import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; +import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput; import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput; import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput; +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.metadata.tables.QTableMetaData; /******************************************************************************* - ** + ** Class for running a test of a script - e.g., maybe before it is saved. *******************************************************************************/ public class TestScriptAction { @@ -40,5 +46,20 @@ public class TestScriptAction public void run(TestScriptInput input, TestScriptOutput output) throws QException { QTableMetaData table = input.getTable(); + + ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance()); + executeCodeInput.setSession(input.getSession()); + executeCodeInput.setInput(new HashMap<>(input.getInputValues())); + executeCodeInput.setContext(new HashMap<>()); + // todo! if(input.getOutputObject() != null) + // todo! { + // todo! executeCodeInput.getContext().put("output", input.getOutputObject()); + // todo! } + executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(input.getCode()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!! + executeCodeInput.setExecutionLogger(new BuildScriptLogAndScriptLogLineExecutionLogger()); + ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput(); + new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); + + // todo! output.setOutput(executeCodeOutput.getOutput()); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/HeaderAndDetailTableCodeExecutionLogger.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java similarity index 59% rename from qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/HeaderAndDetailTableCodeExecutionLogger.java rename to qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java index 315a6680..7dc4b45e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/HeaderAndDetailTableCodeExecutionLogger.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLogger.java @@ -27,30 +27,26 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; -import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; 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.actions.tables.update.UpdateInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; -import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /******************************************************************************* - ** Implementation of a code execution logger that logs into a header and child - ** table + ** Implementation of a code execution logger that builds a scriptLog and 0 or more + ** scriptLogLine records - but doesn't insert them. e.g., useful for testing + ** (both in junit, and for users in-app). *******************************************************************************/ -public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLoggerInterface +public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecutionLoggerInterface { - private static final Logger LOG = LogManager.getLogger(HeaderAndDetailTableCodeExecutionLogger.class); + private static final Logger LOG = LogManager.getLogger(BuildScriptLogAndScriptLogLineExecutionLogger.class); - private QRecord header; - private List children = new ArrayList<>(); - private ExecuteCodeInput executeCodeInput; + private QRecord scriptLog; + private List scriptLogLines = new ArrayList<>(); + protected ExecuteCodeInput executeCodeInput; private Serializable scriptId; private Serializable scriptRevisionId; @@ -61,7 +57,17 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo ** Constructor ** *******************************************************************************/ - public HeaderAndDetailTableCodeExecutionLogger(Serializable scriptId, Serializable scriptRevisionId) + public BuildScriptLogAndScriptLogLineExecutionLogger() + { + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public BuildScriptLogAndScriptLogLineExecutionLogger(Serializable scriptId, Serializable scriptRevisionId) { this.scriptId = scriptId; this.scriptRevisionId = scriptRevisionId; @@ -78,7 +84,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo .withValue("scriptId", scriptId) .withValue("scriptRevisionId", scriptRevisionId) .withValue("startTimestamp", Instant.now()) - .withValue("input", truncate(executeCodeInput.getContext().toString()))); + .withValue("input", truncate(executeCodeInput.getInput()))); } @@ -89,7 +95,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo protected QRecord buildDetailLogRecord(String logLine) { return (new QRecord() - .withValue("scriptLogId", header.getValue("id")) + .withValue("scriptLogId", scriptLog.getValue("id")) .withValue("timestamp", Instant.now()) .withValue("text", truncate(logLine))); } @@ -99,9 +105,9 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo /******************************************************************************* ** *******************************************************************************/ - private String truncate(String logLine) + private String truncate(Object o) { - return StringUtils.safeTruncate(logLine, 1000, "..."); + return StringUtils.safeTruncate(ValueUtils.getValueAsString(o), 1000, "..."); } @@ -109,22 +115,22 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo /******************************************************************************* ** *******************************************************************************/ - private void updateHeaderAtEnd(Serializable output, Exception exception) + protected void updateHeaderAtEnd(Serializable output, Exception exception) { - Instant startTimestamp = (Instant) header.getValue("startTimestamp"); + Instant startTimestamp = (Instant) scriptLog.getValue("startTimestamp"); Instant endTimestamp = Instant.now(); - header.setValue("endTimestamp", endTimestamp); - header.setValue("runTimeMillis", startTimestamp.until(endTimestamp, ChronoUnit.MILLIS)); + scriptLog.setValue("endTimestamp", endTimestamp); + scriptLog.setValue("runTimeMillis", startTimestamp.until(endTimestamp, ChronoUnit.MILLIS)); if(exception != null) { - header.setValue("hadError", true); - header.setValue("error", exception.getMessage()); + scriptLog.setValue("hadError", true); + scriptLog.setValue("error", exception.getMessage()); } else { - header.setValue("hadError", false); - header.setValue("output", truncate(String.valueOf(output))); + scriptLog.setValue("hadError", false); + scriptLog.setValue("output", truncate(output)); } } @@ -139,14 +145,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo try { this.executeCodeInput = executeCodeInput; - QRecord scriptLog = buildHeaderRecord(executeCodeInput); - - InsertInput insertInput = new InsertInput(executeCodeInput.getInstance()); - insertInput.setSession(executeCodeInput.getSession()); - insertInput.setTableName("scriptLog"); - insertInput.setRecords(List.of(scriptLog)); - InsertOutput insertOutput = new InsertAction().execute(insertInput); - this.header = insertOutput.getRecords().get(0); + this.scriptLog = buildHeaderRecord(executeCodeInput); } catch(Exception e) { @@ -162,7 +161,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo @Override public void acceptLogLine(String logLine) { - children.add(buildDetailLogRecord(logLine)); + scriptLogLines.add(buildDetailLogRecord(logLine)); } @@ -173,7 +172,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo @Override public void acceptException(Exception exception) { - store(null, exception); + updateHeaderAtEnd(null, exception); } @@ -184,38 +183,39 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo @Override public void acceptExecutionEnd(Serializable output) { - store(output, null); + updateHeaderAtEnd(output, null); } /******************************************************************************* + ** Getter for scriptLog ** *******************************************************************************/ - private void store(Serializable output, Exception exception) + public QRecord getScriptLog() { - try - { - updateHeaderAtEnd(output, exception); - UpdateInput updateInput = new UpdateInput(executeCodeInput.getInstance()); - updateInput.setSession(executeCodeInput.getSession()); - updateInput.setTableName("scriptLog"); - updateInput.setRecords(List.of(header)); - new UpdateAction().execute(updateInput); - - if(CollectionUtils.nullSafeHasContents(children)) - { - InsertInput insertInput = new InsertInput(executeCodeInput.getInstance()); - insertInput.setSession(executeCodeInput.getSession()); - insertInput.setTableName("scriptLogLine"); - insertInput.setRecords(children); - new InsertAction().execute(insertInput); - } - } - catch(Exception e) - { - LOG.warn("Error storing script log", e); - } + return scriptLog; } + + + /******************************************************************************* + ** Getter for scriptLogLines + ** + *******************************************************************************/ + public List getScriptLogLines() + { + return scriptLogLines; + } + + + + /******************************************************************************* + ** Setter for scriptLog + ** + *******************************************************************************/ + protected void setScriptLog(QRecord scriptLog) + { + this.scriptLog = scriptLog; + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/Log4jCodeExecutionLogger.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/Log4jCodeExecutionLogger.java index af914b38..498652ef 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/Log4jCodeExecutionLogger.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/Log4jCodeExecutionLogger.java @@ -33,7 +33,7 @@ import org.apache.logging.log4j.Logger; /******************************************************************************* - ** Implementation of a code execution logger that just logs to LOG 4j + ** Implementation of a code execution logger that logs to LOG 4j *******************************************************************************/ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface { @@ -52,8 +52,8 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface { this.qCodeReference = executeCodeInput.getCodeReference(); - String contextString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getContext()), 250, "..."); - LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with context: " + contextString); + String inputString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getInput()), 250, "..."); + LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with input: " + inputString); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/QCodeExecutionLoggerInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/QCodeExecutionLoggerInterface.java index caafd906..5277084f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/QCodeExecutionLoggerInterface.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/QCodeExecutionLoggerInterface.java @@ -27,23 +27,24 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; /******************************************************************************* - ** + ** Interface to provide logging functionality to QCodeExecution (e.g., scripts) *******************************************************************************/ public interface QCodeExecutionLoggerInterface { /******************************************************************************* - ** + ** Called when the execution starts - takes the execution's input object. *******************************************************************************/ void acceptExecutionStart(ExecuteCodeInput executeCodeInput); /******************************************************************************* - ** + ** Called to log a line, a message. *******************************************************************************/ void acceptLogLine(String logLine); /******************************************************************************* - ** + ** In case the loggerInterface object is provided to the script as context, + ** this method gives a clean interface for the script to log a line. *******************************************************************************/ default void log(String message) { @@ -51,12 +52,12 @@ public interface QCodeExecutionLoggerInterface } /******************************************************************************* - ** + ** Called if the script fails with an exception. *******************************************************************************/ void acceptException(Exception exception); /******************************************************************************* - ** + ** Called if the script completes without exception. *******************************************************************************/ void acceptExecutionEnd(Serializable output); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/StoreScriptLogAndScriptLogLineExecutionLogger.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/StoreScriptLogAndScriptLogLineExecutionLogger.java new file mode 100644 index 00000000..6e0b84fc --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/StoreScriptLogAndScriptLogLineExecutionLogger.java @@ -0,0 +1,136 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.scripts.logging; + + +import java.io.Serializable; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; +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.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +/******************************************************************************* + ** Implementation of a code execution logger that logs into scriptLog and scriptLogLine + ** tables - e.g., as defined in ScriptMetaDataProvider. + *******************************************************************************/ +public class StoreScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLogAndScriptLogLineExecutionLogger +{ + private static final Logger LOG = LogManager.getLogger(StoreScriptLogAndScriptLogLineExecutionLogger.class); + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public StoreScriptLogAndScriptLogLineExecutionLogger(Serializable scriptId, Serializable scriptRevisionId) + { + super(scriptId, scriptRevisionId); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void acceptExecutionStart(ExecuteCodeInput executeCodeInput) + { + try + { + super.acceptExecutionStart(executeCodeInput); + + InsertInput insertInput = new InsertInput(executeCodeInput.getInstance()); + insertInput.setSession(executeCodeInput.getSession()); + insertInput.setTableName("scriptLog"); + insertInput.setRecords(List.of(getScriptLog())); + InsertOutput insertOutput = new InsertAction().execute(insertInput); + + setScriptLog(insertOutput.getRecords().get(0)); + } + catch(Exception e) + { + LOG.warn("Error starting storage of script log", e); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void acceptException(Exception exception) + { + store(null, exception); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void acceptExecutionEnd(Serializable output) + { + store(output, null); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void store(Serializable output, Exception exception) + { + try + { + updateHeaderAtEnd(output, exception); + UpdateInput updateInput = new UpdateInput(executeCodeInput.getInstance()); + updateInput.setSession(executeCodeInput.getSession()); + updateInput.setTableName("scriptLog"); + updateInput.setRecords(List.of(getScriptLog())); + new UpdateAction().execute(updateInput); + + if(CollectionUtils.nullSafeHasContents(getScriptLogLines())) + { + InsertInput insertInput = new InsertInput(executeCodeInput.getInstance()); + insertInput.setSession(executeCodeInput.getSession()); + insertInput.setTableName("scriptLogLine"); + insertInput.setRecords(getScriptLogLines()); + new InsertAction().execute(insertInput); + } + } + catch(Exception e) + { + LOG.warn("Error storing script log", e); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java index 31ccea38..31c3f315 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java @@ -406,8 +406,11 @@ public class QPossibleValueTranslator ///////////////////////////////////////////////////////////////////////////////////////// // this is needed to get record labels, which are what we use here... unclear if best! // ///////////////////////////////////////////////////////////////////////////////////////// - queryInput.setShouldTranslatePossibleValues(true); - queryInput.setShouldGenerateDisplayValues(true); + if(notTooDeep()) + { + queryInput.setShouldTranslatePossibleValues(true); + queryInput.setShouldGenerateDisplayValues(true); + } QueryOutput queryOutput = new QueryAction().execute(queryInput); @@ -428,4 +431,24 @@ public class QPossibleValueTranslator } } + + + /******************************************************************************* + ** Avoid infinite recursion, for where one field's PVS depends on another's... + ** not too smart, just breaks at 5... + *******************************************************************************/ + private boolean notTooDeep() + { + int count = 0; + for(StackTraceElement stackTraceElement : new Throwable().getStackTrace()) + { + if(stackTraceElement.getMethodName().equals("translatePossibleValuesInRecords")) + { + count++; + } + } + + return (count < 5); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java index 791986a2..e6772489 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java @@ -184,6 +184,7 @@ public class QValueFormatter } catch(Exception e) { + LOG.debug("Error formatting record label", e); return (formatRecordLabelExceptionalCases(table, record)); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QCodeException.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QCodeException.java index 0bbbc0b3..12e16fb5 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QCodeException.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/exceptions/QCodeException.java @@ -23,8 +23,10 @@ package com.kingsrook.qqq.backend.core.exceptions; /******************************************************************************* - * Exception thrown while generating reports - * + ** Exception thrown while executing custom code in QQQ. + ** + ** Context field is meant to give the user "context" for where the error occurred + ** - e.g., a line number or word that was bad. *******************************************************************************/ public class QCodeException extends QException { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/ExecuteCodeInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/ExecuteCodeInput.java index 6514e797..6bb4df7d 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/ExecuteCodeInput.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/ExecuteCodeInput.java @@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; public class ExecuteCodeInput extends AbstractActionInput { private QCodeReference codeReference; + private Map input; private Map context; private QCodeExecutionLoggerInterface executionLogger; @@ -87,6 +88,56 @@ public class ExecuteCodeInput extends AbstractActionInput + /******************************************************************************* + ** Getter for input + ** + *******************************************************************************/ + public Map getInput() + { + return input; + } + + + + /******************************************************************************* + ** Setter for input + ** + *******************************************************************************/ + public void setInput(Map input) + { + this.input = input; + } + + + + /******************************************************************************* + ** Fluent setter for input + ** + *******************************************************************************/ + public ExecuteCodeInput withInput(Map input) + { + this.input = input; + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for input + ** + *******************************************************************************/ + public ExecuteCodeInput withInput(String key, Serializable value) + { + if(this.input == null) + { + input = new HashMap<>(); + } + this.input.put(key, value); + return (this); + } + + + /******************************************************************************* ** Getter for context ** 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 96dbbb82..77acfe43 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 @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts; +import java.io.Serializable; import java.util.Map; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; @@ -34,7 +35,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeRe public class RunAssociatedScriptInput extends AbstractTableActionInput { private AssociatedScriptCodeReference codeReference; - private Map inputValues; + private Map inputValues; + + private Serializable outputObject; @@ -86,7 +89,7 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput ** Getter for inputValues ** *******************************************************************************/ - public Map getInputValues() + public Map getInputValues() { return inputValues; } @@ -97,7 +100,7 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput ** Setter for inputValues ** *******************************************************************************/ - public void setInputValues(Map inputValues) + public void setInputValues(Map inputValues) { this.inputValues = inputValues; } @@ -108,10 +111,44 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput ** Fluent setter for inputValues ** *******************************************************************************/ - public RunAssociatedScriptInput withInputValues(Map inputValues) + public RunAssociatedScriptInput withInputValues(Map inputValues) { this.inputValues = inputValues; return (this); } + + + /******************************************************************************* + ** Getter for outputObject + ** + *******************************************************************************/ + public Serializable getOutputObject() + { + return outputObject; + } + + + + /******************************************************************************* + ** Setter for outputObject + ** + *******************************************************************************/ + public void setOutputObject(Serializable outputObject) + { + this.outputObject = outputObject; + } + + + + /******************************************************************************* + ** Fluent setter for outputObject + ** + *******************************************************************************/ + public RunAssociatedScriptInput withOutputObject(Serializable outputObject) + { + this.outputObject = outputObject; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java index ea4cd840..ba9d2c10 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QBackendMetaData.java @@ -22,8 +22,12 @@ package com.kingsrook.qqq.backend.core.model.metadata; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; @@ -38,6 +42,9 @@ public class QBackendMetaData private String name; private String backendType; + private Set enabledCapabilities = new HashSet<>(); + private Set disabledCapabilities = new HashSet<>(); + // todo - at some point, we may want to apply this to secret properties on subclasses? // @JsonFilter("secretsFilter") @@ -157,4 +164,161 @@ public class QBackendMetaData // noop in base class // //////////////////////// } + + + + /******************************************************************************* + ** Getter for enabledCapabilities + ** + *******************************************************************************/ + public Set getEnabledCapabilities() + { + return enabledCapabilities; + } + + + + /******************************************************************************* + ** Setter for enabledCapabilities + ** + *******************************************************************************/ + public void setEnabledCapabilities(Set enabledCapabilities) + { + this.enabledCapabilities = enabledCapabilities; + } + + + + /******************************************************************************* + ** Fluent setter for enabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withEnabledCapabilities(Set enabledCapabilities) + { + this.enabledCapabilities = enabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for enabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withCapabilities(Set enabledCapabilities) + { + this.enabledCapabilities = enabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for a single enabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withCapability(Capability capability) + { + if(this.enabledCapabilities == null) + { + this.enabledCapabilities = new HashSet<>(); + } + this.enabledCapabilities.add(capability); + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for enabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withCapabilities(Capability... enabledCapabilities) + { + if(this.enabledCapabilities == null) + { + this.enabledCapabilities = new HashSet<>(); + } + this.enabledCapabilities.addAll(Arrays.stream(enabledCapabilities).toList()); + return (this); + } + + + + /******************************************************************************* + ** Getter for disabledCapabilities + ** + *******************************************************************************/ + public Set getDisabledCapabilities() + { + return disabledCapabilities; + } + + + + /******************************************************************************* + ** Setter for disabledCapabilities + ** + *******************************************************************************/ + public void setDisabledCapabilities(Set disabledCapabilities) + { + this.disabledCapabilities = disabledCapabilities; + } + + + + /******************************************************************************* + ** Fluent setter for disabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withDisabledCapabilities(Set disabledCapabilities) + { + this.disabledCapabilities = disabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for disabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withoutCapabilities(Capability... disabledCapabilities) + { + if(this.disabledCapabilities == null) + { + this.disabledCapabilities = new HashSet<>(); + } + this.disabledCapabilities.addAll(Arrays.stream(disabledCapabilities).toList()); + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for disabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withoutCapabilities(Set disabledCapabilities) + { + this.disabledCapabilities = disabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for a single disabledCapabilities + ** + *******************************************************************************/ + public QBackendMetaData withoutCapability(Capability capability) + { + if(this.disabledCapabilities == null) + { + this.disabledCapabilities = new HashSet<>(); + } + this.disabledCapabilities.add(capability); + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java index b3c481a1..45da4911 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java @@ -22,6 +22,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.dashboard; +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; @@ -38,6 +41,8 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface protected Integer gridColumns; protected QCodeReference codeReference; + protected Map defaultValues = new LinkedHashMap<>(); + /******************************************************************************* @@ -242,4 +247,56 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface return (this); } + + + /******************************************************************************* + ** Getter for defaultValues + ** + *******************************************************************************/ + public Map getDefaultValues() + { + return defaultValues; + } + + + + /******************************************************************************* + ** Setter for defaultValues + ** + *******************************************************************************/ + public void setDefaultValues(Map defaultValues) + { + this.defaultValues = defaultValues; + } + + + + /******************************************************************************* + ** Fluent setter for defaultValues + ** + *******************************************************************************/ + public QWidgetMetaData withDefaultValues(Map defaultValues) + { + this.defaultValues = defaultValues; + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for a single defaultValue + ** + *******************************************************************************/ + public QWidgetMetaData withDefaultValue(String key, Serializable value) + { + if(this.defaultValues == null) + { + this.defaultValues = new LinkedHashMap<>(); + } + + this.defaultValues.put(key, value); + + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java index 62abd7db..ad5f3c53 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/fields/AdornmentType.java @@ -34,7 +34,11 @@ public enum AdornmentType { LINK, CHIP, - SIZE; + SIZE, + CODE_EDITOR; + ////////////////////////////////////////////////////////////////////////// + // keep these values in sync with AdornmentType.ts in qqq-frontend-core // + ////////////////////////////////////////////////////////////////////////// diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java index c5c18d7b..b52ed802 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java @@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -53,6 +58,8 @@ public class QFrontendTableMetaData private List widgets; + private Set capabilities; + ////////////////////////////////////////////////////////////////////////////////// // do not add setters. take values from the source-object in the constructor!! // ////////////////////////////////////////////////////////////////////////////////// @@ -62,7 +69,7 @@ public class QFrontendTableMetaData /******************************************************************************* ** *******************************************************************************/ - public QFrontendTableMetaData(QTableMetaData tableMetaData, boolean includeFields) + public QFrontendTableMetaData(QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFields) { this.name = tableMetaData.getName(); this.label = tableMetaData.getLabel(); @@ -89,6 +96,62 @@ public class QFrontendTableMetaData { this.widgets = tableMetaData.getWidgets(); } + + setCapabilities(backendForTable, tableMetaData); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void setCapabilities(QBackendMetaData backend, QTableMetaData table) + { + Set enabledCapabilities = new HashSet<>(); + for(Capability capability : Capability.values()) + { + /////////////////////////////////////////////// + // by default, every table can do everything // + /////////////////////////////////////////////// + boolean hasCapability = true; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // if the table's backend says the capability is disabled, then by default, then the capability is disabled... // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(backend.getDisabledCapabilities().contains(capability)) + { + hasCapability = false; + + ///////////////////////////////////////////////////////////////// + // unless the table overrides that and says that it IS enabled // + ///////////////////////////////////////////////////////////////// + if(table.getEnabledCapabilities().contains(capability)) + { + hasCapability = true; + } + } + else + { + ///////////////////////////////////////////////////////////////////////////////////////// + // if the backend doesn't specify the capability, then disable it if the table says so // + ///////////////////////////////////////////////////////////////////////////////////////// + if(table.getDisabledCapabilities().contains(capability)) + { + hasCapability = false; + } + } + + if(hasCapability) + { + /////////////////////////////////////// + // todo - check if user is allowed!! // + /////////////////////////////////////// + + enabledCapabilities.add(capability); + } + } + + this.capabilities = enabledCapabilities.stream().map(Enum::name).collect(Collectors.toSet()); } @@ -178,4 +241,16 @@ public class QFrontendTableMetaData { return widgets; } + + + + /******************************************************************************* + ** Getter for capabilities + ** + *******************************************************************************/ + public Set getCapabilities() + { + return capabilities; + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java index 36f2a0a2..542d2a79 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java @@ -24,6 +24,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout; import java.util.ArrayList; import java.util.List; +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; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -340,4 +343,38 @@ public class QAppMetaData implements QAppChildMetaData this.addSection(section); return (this); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QAppMetaData withSectionOfChildren(QAppSection section, QAppChildMetaData... children) + { + this.addSection(section); + + for(QAppChildMetaData child : children) + { + withChild(child); + if(child instanceof QTableMetaData) + { + section.withTable(child.getName()); + } + else if(child instanceof QProcessMetaData) + { + section.withProcess(child.getName()); + } + else if(child instanceof QReportMetaData) + { + section.withReport(child.getName()); + } + else + { + throw new IllegalArgumentException("Unrecognized child type: " + child.getName()); + } + } + + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java index 461871b0..b1d0c5b5 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout; +import java.util.ArrayList; import java.util.List; @@ -166,6 +167,22 @@ public class QAppSection + /******************************************************************************* + ** Fluent setter for tables + ** + *******************************************************************************/ + public QAppSection withTable(String tableName) + { + if(this.tables == null) + { + this.tables = new ArrayList<>(); + } + this.tables.add(tableName); + return (this); + } + + + /******************************************************************************* ** Getter for processes ** @@ -200,6 +217,22 @@ public class QAppSection + /******************************************************************************* + ** Fluent setter for processes + ** + *******************************************************************************/ + public QAppSection withProcess(String processName) + { + if(this.processes == null) + { + this.processes = new ArrayList<>(); + } + this.processes.add(processName); + return (this); + } + + + /******************************************************************************* ** Getter for reports ** @@ -234,6 +267,22 @@ public class QAppSection + /******************************************************************************* + ** Fluent setter for reports + ** + *******************************************************************************/ + public QAppSection withReport(String reportName) + { + if(this.reports == null) + { + this.reports = new ArrayList<>(); + } + this.reports.add(reportName); + return (this); + } + + + /******************************************************************************* ** Getter for icon ** diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java index accce041..280c3c4e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/possiblevalues/QPossibleValueSource.java @@ -289,6 +289,7 @@ public class QPossibleValueSource *******************************************************************************/ public void setTableName(String tableName) { + this.type = QPossibleValueSourceType.TABLE; this.tableName = tableName; } @@ -300,7 +301,7 @@ public class QPossibleValueSource *******************************************************************************/ public QPossibleValueSource withTableName(String tableName) { - this.tableName = tableName; + setTableName(tableName); return (this); } @@ -446,6 +447,7 @@ public class QPossibleValueSource public void setEnumValues(List> enumValues) { this.enumValues = enumValues; + setType(QPossibleValueSourceType.ENUM); } @@ -456,7 +458,7 @@ public class QPossibleValueSource *******************************************************************************/ public QPossibleValueSource withEnumValues(List> enumValues) { - this.enumValues = enumValues; + setEnumValues(enumValues); return this; } @@ -472,6 +474,7 @@ public class QPossibleValueSource this.enumValues = new ArrayList<>(); } this.enumValues.add(possibleValue); + setType(QPossibleValueSourceType.ENUM); } @@ -512,6 +515,7 @@ public class QPossibleValueSource public void setCustomCodeReference(QCodeReference customCodeReference) { this.customCodeReference = customCodeReference; + setType(QPossibleValueSourceType.CUSTOM); } @@ -522,7 +526,7 @@ public class QPossibleValueSource *******************************************************************************/ public QPossibleValueSource withCustomCodeReference(QCodeReference customCodeReference) { - this.customCodeReference = customCodeReference; + setCustomCodeReference(customCodeReference); return (this); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/Capability.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/Capability.java new file mode 100644 index 00000000..e8321397 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/Capability.java @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.tables; + + +/******************************************************************************* + ** Things that can be done to tables, fields. + ** + *******************************************************************************/ +public enum Capability +{ + TABLE_QUERY, + TABLE_GET, + TABLE_COUNT, + TABLE_INSERT, + TABLE_UPDATE, + TABLE_DELETE + ////////////////////////////////////////////////////////////////////////// + // keep these values in sync with AdornmentType.ts in qqq-frontend-core // + ////////////////////////////////////////////////////////////////////////// + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java index c1369e65..f62140f4 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java @@ -38,6 +38,8 @@ public class QFieldSection private List fieldNames; private QIcon icon; + private boolean isHidden = false; + /******************************************************************************* @@ -244,4 +246,38 @@ public class QFieldSection return (this); } + + + /******************************************************************************* + ** Getter for isHidden + ** + *******************************************************************************/ + public boolean getIsHidden() + { + return (isHidden); + } + + + + /******************************************************************************* + ** Setter for isHidden + ** + *******************************************************************************/ + public void setIsHidden(boolean isHidden) + { + this.isHidden = isHidden; + } + + + + /******************************************************************************* + ** Fluent Setter for isHidden + ** + *******************************************************************************/ + public QFieldSection withIsHidden(boolean isHidden) + { + this.isHidden = isHidden; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java index 7cfc0efe..c7f20b48 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java @@ -26,10 +26,12 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; @@ -81,6 +83,9 @@ public class QTableMetaData implements QAppChildMetaData, Serializable private List widgets; private List associatedScripts; + private Set enabledCapabilities = new HashSet<>(); + private Set disabledCapabilities = new HashSet<>(); + /******************************************************************************* @@ -874,4 +879,160 @@ public class QTableMetaData implements QAppChildMetaData, Serializable return (this); } + + + /******************************************************************************* + ** Getter for enabledCapabilities + ** + *******************************************************************************/ + public Set getEnabledCapabilities() + { + return enabledCapabilities; + } + + + + /******************************************************************************* + ** Setter for enabledCapabilities + ** + *******************************************************************************/ + public void setEnabledCapabilities(Set enabledCapabilities) + { + this.enabledCapabilities = enabledCapabilities; + } + + + + /******************************************************************************* + ** Fluent setter for enabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withEnabledCapabilities(Set enabledCapabilities) + { + this.enabledCapabilities = enabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for enabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withCapabilities(Set enabledCapabilities) + { + this.enabledCapabilities = enabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for a single enabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withCapability(Capability capability) + { + if(this.enabledCapabilities == null) + { + this.enabledCapabilities = new HashSet<>(); + } + this.enabledCapabilities.add(capability); + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for enabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withCapabilities(Capability... enabledCapabilities) + { + if(this.enabledCapabilities == null) + { + this.enabledCapabilities = new HashSet<>(); + } + this.enabledCapabilities.addAll(Arrays.stream(enabledCapabilities).toList()); + return (this); + } + + + + /******************************************************************************* + ** Getter for disabledCapabilities + ** + *******************************************************************************/ + public Set getDisabledCapabilities() + { + return disabledCapabilities; + } + + + + /******************************************************************************* + ** Setter for disabledCapabilities + ** + *******************************************************************************/ + public void setDisabledCapabilities(Set disabledCapabilities) + { + this.disabledCapabilities = disabledCapabilities; + } + + + + /******************************************************************************* + ** Fluent setter for disabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withDisabledCapabilities(Set disabledCapabilities) + { + this.disabledCapabilities = disabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for disabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withoutCapabilities(Capability... disabledCapabilities) + { + if(this.disabledCapabilities == null) + { + this.disabledCapabilities = new HashSet<>(); + } + this.disabledCapabilities.addAll(Arrays.stream(disabledCapabilities).toList()); + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for disabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withoutCapabilities(Set disabledCapabilities) + { + this.disabledCapabilities = disabledCapabilities; + return (this); + } + + + + /******************************************************************************* + ** Alternative fluent setter for a single disabledCapabilities + ** + *******************************************************************************/ + public QTableMetaData withoutCapability(Capability capability) + { + if(this.disabledCapabilities == null) + { + this.disabledCapabilities = new HashSet<>(); + } + this.disabledCapabilities.add(capability); + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/Script.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/Script.java index 4c412dcb..42fb6fe9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/Script.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/Script.java @@ -48,10 +48,10 @@ public class Script extends QRecordEntity @QField() private String name; - @QField() + @QField(possibleValueSourceName = "scriptType") private Integer scriptTypeId; - @QField() + @QField(possibleValueSourceName = "scriptRevision") private Integer currentScriptRevisionId; diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLog.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLog.java index 1f297c75..5949f326 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLog.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLog.java @@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.scripts; import java.time.Instant; import com.kingsrook.qqq.backend.core.model.data.QField; import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; +import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat; /******************************************************************************* @@ -43,10 +44,10 @@ public class ScriptLog extends QRecordEntity @QField() private Instant modifyDate; - @QField() + @QField(possibleValueSourceName = "script") private Integer scriptId; - @QField() + @QField(possibleValueSourceName = "scriptRevision") private Integer scriptRevisionId; @QField() @@ -55,7 +56,7 @@ public class ScriptLog extends QRecordEntity @QField() private Instant endTimestamp; - @QField() + @QField(displayFormat = DisplayFormat.COMMAS) private Integer runTimeMillis; @QField() diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLogLine.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLogLine.java index 48accdc8..f878e91b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLogLine.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptLogLine.java @@ -43,7 +43,7 @@ public class ScriptLogLine extends QRecordEntity @QField() private Instant modifyDate; - @QField() + @QField(possibleValueSourceName = "scriptLog") private Integer scriptLogId; @QField() diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java index 76824409..ff8f0d26 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java @@ -45,7 +45,7 @@ public class ScriptRevision extends QRecordEntity @QField() private Instant modifyDate; - @QField() + @QField(possibleValueSourceName = "script") private Integer scriptId; @QField() 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 b81e60b1..39c2249d 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 @@ -28,7 +28,14 @@ import java.util.function.Consumer; import com.kingsrook.qqq.backend.core.exceptions.QException; 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.layout.QIcon; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier; /******************************************************************************* @@ -37,6 +44,17 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; public class ScriptsMetaDataProvider { + /******************************************************************************* + ** + *******************************************************************************/ + public void defineAll(QInstance instance, String backendName, Consumer backendDetailEnricher) throws QException + { + defineStandardScriptsTables(instance, backendName, backendDetailEnricher); + defineStandardScriptsPossibleValueSources(instance); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -53,7 +71,35 @@ public class ScriptsMetaDataProvider /******************************************************************************* ** *******************************************************************************/ - public List defineStandardScriptsTables(String backendName, Consumer backendDetailEnricher) throws QException + public void defineStandardScriptsPossibleValueSources(QInstance instance) throws QException + { + instance.addPossibleValueSource(new QPossibleValueSource() + .withName(Script.TABLE_NAME) + .withTableName(Script.TABLE_NAME) + ); + + instance.addPossibleValueSource(new QPossibleValueSource() + .withName(ScriptRevision.TABLE_NAME) + .withTableName(ScriptRevision.TABLE_NAME) + ); + + instance.addPossibleValueSource(new QPossibleValueSource() + .withName(ScriptType.TABLE_NAME) + .withTableName(ScriptType.TABLE_NAME) + ); + + instance.addPossibleValueSource(new QPossibleValueSource() + .withName(ScriptLog.TABLE_NAME) + .withTableName(ScriptLog.TABLE_NAME) + ); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private List defineStandardScriptsTables(String backendName, Consumer backendDetailEnricher) throws QException { List rs = new ArrayList<>(); rs.add(enrich(backendDetailEnricher, defineScriptTypeTable(backendName))); @@ -101,7 +147,9 @@ public class ScriptsMetaDataProvider *******************************************************************************/ private QTableMetaData defineScriptTable(String backendName) throws QException { - return (defineStandardTable(backendName, Script.TABLE_NAME, Script.class)); + return (defineStandardTable(backendName, Script.TABLE_NAME, Script.class) + .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name", "scriptTypeId", "currentScriptRevisionId"))) + .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))); } @@ -111,7 +159,12 @@ public class ScriptsMetaDataProvider *******************************************************************************/ private QTableMetaData defineScriptTypeTable(String backendName) throws QException { - return (defineStandardTable(backendName, ScriptType.TABLE_NAME, ScriptType.class)); + QTableMetaData tableMetaData = defineStandardTable(backendName, ScriptType.TABLE_NAME, ScriptType.class) + .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name"))) + .withSection(new QFieldSection("details", new QIcon().withName("dataset"), Tier.T2, List.of("helpText", "sampleCode"))) + .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate"))); + tableMetaData.getField("sampleCode").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR)); + return (tableMetaData); } @@ -121,8 +174,17 @@ public class ScriptsMetaDataProvider *******************************************************************************/ private QTableMetaData defineScriptRevisionTable(String backendName) throws QException { - return (defineStandardTable(backendName, ScriptRevision.TABLE_NAME, ScriptRevision.class) - .withRecordLabelFields(List.of("id"))); + QTableMetaData tableMetaData = defineStandardTable(backendName, ScriptRevision.TABLE_NAME, ScriptRevision.class) + .withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE) + .withRecordLabelFormat("%s v%s") + .withRecordLabelFields(List.of("scriptId", "sequenceNo")) + .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "scriptId", "sequenceNo"))) + .withSection(new QFieldSection("code", new QIcon().withName("data_object"), Tier.T2, List.of("contents"))) + .withSection(new QFieldSection("changeManagement", new QIcon().withName("history"), Tier.T2, List.of("commitMessage", "author"))) + .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate"))); + + tableMetaData.getField("contents").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR)); + return (tableMetaData); } @@ -133,7 +195,12 @@ public class ScriptsMetaDataProvider private QTableMetaData defineScriptLogTable(String backendName) throws QException { return (defineStandardTable(backendName, ScriptLog.TABLE_NAME, ScriptLog.class) - .withRecordLabelFields(List.of("id"))); + .withRecordLabelFields(List.of("id")) + .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id"))) + .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")))); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/enumeration/EnumerationBackendModule.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/enumeration/EnumerationBackendModule.java index 324c5aaa..67650182 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/enumeration/EnumerationBackendModule.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/enumeration/EnumerationBackendModule.java @@ -28,7 +28,11 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface; /******************************************************************************* + ** Backend module for a table based on a java enum. So we can expose an enum + ** as a table (similar to exposing an enum as a possible value source), with multiple + ** fields in the enum (exposed via getter methods in the enum) as fields in the table. ** + ** Only supports read-operations, as you can't modify an enum. *******************************************************************************/ public class EnumerationBackendModule implements QBackendModuleInterface { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java index 75d78bb9..aa2bdfb1 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java @@ -39,12 +39,16 @@ import org.apache.commons.lang.NotImplementedException; /******************************************************************************* + ** Utility class for backend modules that need to do filter operations. ** + ** e.g., like an in-memory module, or one that's working with files - basically + ** one that doesn't have filtering provided by the backend (like a database or API). *******************************************************************************/ public class BackendQueryFilterUtils { + /******************************************************************************* - ** + ** Test if record matches filter. *******************************************************************************/ @SuppressWarnings("checkstyle:indentation") public static boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord) @@ -407,7 +411,7 @@ public class BackendQueryFilterUtils /******************************************************************************* - ** + ** Sort list of records based on filter. *******************************************************************************/ public static void sortRecordList(QQueryFilter filter, List recordList) { @@ -443,7 +447,7 @@ public class BackendQueryFilterUtils /******************************************************************************* - ** + ** Apply skip & limit attributes from queryInput to a list of records. *******************************************************************************/ public static List applySkipAndLimit(QueryInput queryInput, List recordList) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java index 8782acf4..7962ec68 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java @@ -41,13 +41,19 @@ import com.kingsrook.qqq.backend.core.utils.ListingHash; /******************************************************************************* - ** + ** Utility methods for working with QQQ records and table actions inside user - + ** defined QQQ processes steps. *******************************************************************************/ public class GeneralProcessUtils { /******************************************************************************* + ** For a list of sourceRecords, + ** lookup records in the foreignTableName, + ** that have their foreignTablePrimaryKeyName in the sourceTableForeignKeyFieldName on the sourceRecords. ** + ** e.g., for a list of orders (with a clientId field), build a map of client.id => client record + ** via getForeignRecordMap(input, orderList, "clientId", "client", "id") *******************************************************************************/ public static Map getForeignRecordMap(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException { @@ -69,9 +75,14 @@ public class GeneralProcessUtils /******************************************************************************* + ** For a list of sourceRecords, + ** lookup records in the foreignTableName, + ** that have their foreignTableForeignKeyName in the sourceTableForeignKeyFieldName on the sourceRecords. ** + ** e.g., for a list of orders, build a ListingHash of order.id => List(OrderLine records) + ** via getForeignRecordListingHashMap(input, orderList, "id", "orderLine", "orderId") *******************************************************************************/ - public static ListingHash getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException + public static ListingHash getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException { ListingHash foreignRecordMap = new ListingHash<>(); QueryInput queryInput = new QueryInput(parentActionInput.getInstance()); @@ -79,11 +90,11 @@ public class GeneralProcessUtils queryInput.setTableName(foreignTableName); List foreignIds = new ArrayList<>(sourceRecords.stream().map(r -> r.getValue(sourceTableForeignKeyFieldName)).toList()); - queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(foreignTablePrimaryKeyName, QCriteriaOperator.IN, foreignIds))); + queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(foreignTableForeignKeyName, QCriteriaOperator.IN, foreignIds))); QueryOutput queryOutput = new QueryAction().execute(queryInput); for(QRecord foreignRecord : queryOutput.getRecords()) { - foreignRecordMap.add(foreignRecord.getValue(foreignTablePrimaryKeyName), foreignRecord); + foreignRecordMap.add(foreignRecord.getValue(foreignTableForeignKeyName), foreignRecord); } return foreignRecordMap; } @@ -91,7 +102,13 @@ public class GeneralProcessUtils /******************************************************************************* + ** For a list of sourceRecords, + ** lookup records in the foreignTableName, + ** that have their foreignTablePrimaryKeyName in the sourceTableForeignKeyFieldName on the sourceRecords. + ** and set those foreign records as a value in the sourceRecords. ** + ** e.g., for a list of orders (with a clientId field), setValue("client", QRecord(client)); + ** via addForeignRecordsToRecordList(input, orderList, "clientId", "client", "id") *******************************************************************************/ public static void addForeignRecordsToRecordList(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException { @@ -106,11 +123,16 @@ public class GeneralProcessUtils /******************************************************************************* + ** For a list of sourceRecords, + ** lookup records in the foreignTableName, + ** that have their foreignTableForeignKeyName in the sourceTableForeignKeyFieldName on the sourceRecords. ** + ** e.g., for a list of orders, setValue("orderLine", List(QRecord(orderLine))) + ** via addForeignRecordsListToRecordList(input, orderList, "id", "orderLine", "orderId") *******************************************************************************/ - public static void addForeignRecordsListToRecordList(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException + public static void addForeignRecordsListToRecordList(AbstractActionInput parentActionInput, List sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException { - ListingHash foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName); + ListingHash foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName); for(QRecord sourceRecord : sourceRecords) { List foreignRecordList = foreignRecordMap.get(sourceRecord.getValue(sourceTableForeignKeyFieldName)); @@ -131,7 +153,8 @@ public class GeneralProcessUtils /******************************************************************************* - ** + ** Run a query on tableName, for where fieldName equals fieldValue, and return + ** the list of QRecords. *******************************************************************************/ public static List getRecordListByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException { @@ -146,7 +169,9 @@ public class GeneralProcessUtils /******************************************************************************* - ** + ** Query to get one record by a unique key value. That field can be the primary + ** key, or any other field on the table. Note, if multiple rows do match the value, + ** only 1 (determined in an unspecified way) is returned. *******************************************************************************/ public static Optional getRecordById(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException { @@ -162,7 +187,10 @@ public class GeneralProcessUtils /******************************************************************************* + ** Load all rows from a table. ** + ** Note, this is inherently unsafe, if you were to call it on a table with + ** too many rows... Caveat emptor. *******************************************************************************/ public static List loadTable(AbstractActionInput parentActionInput, String tableName) throws QException { @@ -176,7 +204,15 @@ public class GeneralProcessUtils /******************************************************************************* + ** Load all rows from a table, into a map, keyed by the keyFieldName. + ** ** Note - null values from the key field are NOT put in the map. + ** + ** If multiple values are found for the key, they'll squash each other, and only + ** one random value will appear. + ** + ** Also, note, this is inherently unsafe, if you were to call it on a table with + ** too many rows... Caveat emptor. *******************************************************************************/ public static Map loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException { @@ -201,7 +237,14 @@ public class GeneralProcessUtils /******************************************************************************* + ** Load all rows from a table, into a ListingHash, keyed by the keyFieldName. + ** ** Note - null values from the key field are NOT put in the map. + ** + ** The ordering of the records is not specified. + ** + ** Also, note, this is inherently unsafe, if you were to call it on a table with + ** too many rows... Caveat emptor. *******************************************************************************/ public static ListingHash loadTableToListingHash(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException { diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeActionTest.java index 963d29b4..400eaf23 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeActionTest.java @@ -26,10 +26,10 @@ import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.function.Function; -import com.kingsrook.qqq.backend.core.actions.scripts.logging.HeaderAndDetailTableCodeExecutionLogger; import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger; import com.kingsrook.qqq.backend.core.actions.scripts.logging.NoopCodeExecutionLogger; import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; +import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger; import com.kingsrook.qqq.backend.core.exceptions.QCodeException; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; @@ -120,9 +120,9 @@ class ExecuteCodeActionTest void testTableLogger() throws QException { QInstance qInstance = TestUtils.defineInstance(); - new ScriptsMetaDataProvider().defineStandardScriptsTables(qInstance, TestUtils.MEMORY_BACKEND_NAME, null); + new ScriptsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null); - ExecuteCodeInput executeCodeInput = setupInput(qInstance, Map.of("x", 4), new HeaderAndDetailTableCodeExecutionLogger(1701, 1702)); + ExecuteCodeInput executeCodeInput = setupInput(qInstance, Map.of("x", 4), new StoreScriptLogAndScriptLogLineExecutionLogger(1701, 1702)); ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput(); new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); assertEquals(16, executeCodeOutput.getOutput()); 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 4ecd1ed2..5d708ed4 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 @@ -25,11 +25,17 @@ package com.kingsrook.qqq.backend.core.actions.scripts; import java.io.Serializable; import java.util.List; import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; +import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptInput; import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptOutput; import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptInput; import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptOutput; +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.actions.tables.update.UpdateInput; 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.code.AssociatedScriptCodeReference; @@ -56,24 +62,7 @@ class RunAssociatedScriptActionTest @Test void test() throws QException { - QInstance instance = TestUtils.defineInstance(); - QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) - .withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER)) - .withAssociatedScript(new AssociatedScript() - .withScriptTypeId(1) - .withFieldName("testScriptId") - ); - - new ScriptsMetaDataProvider().defineStandardScriptsTables(instance, TestUtils.MEMORY_BACKEND_NAME, null); - - TestUtils.insertRecords(instance, table, List.of( - new QRecord().withValue("id", 1), - new QRecord().withValue("id", 2) - )); - - TestUtils.insertRecords(instance, instance.getTable("scriptType"), List.of( - new QRecord().withValue("id", 1).withValue("name", "Test Script Type") - )); + QInstance instance = setupInstance(); insertScript(instance, 1, """ return "Hello"; @@ -90,6 +79,9 @@ class RunAssociatedScriptActionTest ); RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ok - since the core module doesn't have the javascript language support module as a dep, this action will fail - but at least we can confirm it fails with this specific exception! // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) .isInstanceOf(QException.class) .hasRootCauseInstanceOf(ClassNotFoundException.class) @@ -98,6 +90,204 @@ class RunAssociatedScriptActionTest + /******************************************************************************* + ** + *******************************************************************************/ + private QInstance setupInstance() throws QException + { + QInstance instance = TestUtils.defineInstance(); + QTableMetaData table = instance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER)) + .withAssociatedScript(new AssociatedScript() + .withScriptTypeId(1) + .withFieldName("testScriptId") + ); + + new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null); + + TestUtils.insertRecords(instance, table, List.of( + new QRecord().withValue("id", 1), + new QRecord().withValue("id", 2) + )); + + TestUtils.insertRecords(instance, instance.getTable("scriptType"), List.of( + new QRecord().withValue("id", 1).withValue("name", "Test Script Type") + )); + return instance; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testRecordNotFound() throws QException + { + QInstance instance = setupInstance(); + + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance); + runAssociatedScriptInput.setSession(new QSession()); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(-9999) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) + .isInstanceOf(QNotFoundException.class) + .hasMessageMatching("The requested record.*was not found.*"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNoScriptInRecord() throws QException + { + QInstance instance = setupInstance(); + + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance); + runAssociatedScriptInput.setSession(new QSession()); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(1) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) + .isInstanceOf(QNotFoundException.class) + .hasMessageMatching("The input record.*does not have a script specified for.*"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testBadScriptIdInRecord() throws QException + { + QInstance instance = setupInstance(); + + UpdateInput updateInput = new UpdateInput(instance); + updateInput.setSession(new QSession()); + updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("testScriptId", -9998))); + new UpdateAction().execute(updateInput); + + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance); + runAssociatedScriptInput.setSession(new QSession()); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(1) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) + .isInstanceOf(QNotFoundException.class) + .hasMessageMatching("The script for record .* was not found.*"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNoCurrentScriptRevisionOnScript() throws QException + { + QInstance instance = setupInstance(); + + insertScript(instance, 1, """ + return "Hello"; + """); + + GetInput getInput = new GetInput(instance); + getInput.setSession(new QSession()); + getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + getInput.setPrimaryKey(1); + GetOutput getOutput = new GetAction().execute(getInput); + Integer scriptId = getOutput.getRecord().getValueInteger("testScriptId"); + + UpdateInput updateInput = new UpdateInput(instance); + updateInput.setSession(new QSession()); + updateInput.setTableName("script"); + updateInput.setRecords(List.of(new QRecord().withValue("id", scriptId).withValue("currentScriptRevisionId", null))); + new UpdateAction().execute(updateInput); + + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance); + runAssociatedScriptInput.setSession(new QSession()); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(1) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) + .isInstanceOf(QNotFoundException.class) + .hasMessageMatching("The script for record .* does not have a current version.*"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testBadCurrentScriptRevisionOnScript() throws QException + { + QInstance instance = setupInstance(); + + insertScript(instance, 1, """ + return "Hello"; + """); + + GetInput getInput = new GetInput(instance); + getInput.setSession(new QSession()); + getInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + getInput.setPrimaryKey(1); + GetOutput getOutput = new GetAction().execute(getInput); + Integer scriptId = getOutput.getRecord().getValueInteger("testScriptId"); + + UpdateInput updateInput = new UpdateInput(instance); + updateInput.setSession(new QSession()); + updateInput.setTableName("script"); + updateInput.setRecords(List.of(new QRecord().withValue("id", scriptId).withValue("currentScriptRevisionId", 9997))); + new UpdateAction().execute(updateInput); + + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(instance); + runAssociatedScriptInput.setSession(new QSession()); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(1) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) + .isInstanceOf(QNotFoundException.class) + .hasMessageMatching("The current revision of the script for record .* was not found.*"); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptActionTest.java index 1ae95373..8c0cced7 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/StoreAssociatedScriptActionTest.java @@ -24,15 +24,12 @@ package com.kingsrook.qqq.backend.core.actions.scripts; import java.io.Serializable; import java.util.List; -import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptInput; import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptOutput; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; -import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; +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.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; @@ -47,6 +44,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /******************************************************************************* @@ -85,7 +83,7 @@ class StoreAssociatedScriptActionTest .withFieldName("otherScriptId") ); - new ScriptsMetaDataProvider().defineStandardScriptsTables(instance, TestUtils.MEMORY_BACKEND_NAME, null); + new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null); TestUtils.insertRecords(instance, table, List.of( new QRecord().withValue("id", 1), @@ -149,7 +147,6 @@ class StoreAssociatedScriptActionTest assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "testScriptId", 1); assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "otherScriptId", 3); assertValueInField(instance, "script", 3, "currentScriptRevisionId", 4); - } @@ -157,16 +154,19 @@ class StoreAssociatedScriptActionTest /******************************************************************************* ** *******************************************************************************/ - private Serializable assertValueInField(QInstance instance, String tableName, Serializable recordId, String fieldName, Serializable value) throws QException + private void assertValueInField(QInstance instance, String tableName, Serializable recordId, String fieldName, Serializable value) throws QException { - QueryInput queryInput = new QueryInput(instance); - queryInput.setSession(new QSession()); - queryInput.setTableName(tableName); - queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, List.of(recordId)))); - QueryOutput queryOutput = new QueryAction().execute(queryInput); - Serializable actual = queryOutput.getRecords().get(0).getValue(fieldName); - assertEquals(value, actual); - return (actual); + GetInput getInput = new GetInput(instance); + getInput.setSession(new QSession()); + getInput.setTableName(tableName); + getInput.setPrimaryKey(recordId); + GetOutput getOutput = new GetAction().execute(getInput); + if(getOutput.getRecord() == null) + { + fail("Expected value [" + value + "] in field [" + fieldName + "], record [" + tableName + "][" + recordId + "], but the record wasn't found..."); + } + Serializable actual = getOutput.getRecord().getValue(fieldName); + assertEquals(value, actual, "Expected value in field [" + fieldName + "], record [" + tableName + "][" + recordId + "]"); } } \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptActionTest.java index 7d492c9c..7afad2de 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/TestScriptActionTest.java @@ -27,6 +27,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput; import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -39,6 +40,7 @@ class TestScriptActionTest ** *******************************************************************************/ @Test + @Disabled("Not yet done.") void test() throws QException { QInstance instance = TestUtils.defineInstance(); diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLoggerTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLoggerTest.java new file mode 100644 index 00000000..dcee174b --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/BuildScriptLogAndScriptLogLineExecutionLoggerTest.java @@ -0,0 +1,85 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.scripts.logging; + + +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; +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.model.session.QSession; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + + +/******************************************************************************* + ** Unit test for BuildScriptLogAndScriptLogLineExecutionLogger + *******************************************************************************/ +class BuildScriptLogAndScriptLogLineExecutionLoggerTest +{ + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() throws QException + { + QInstance instance = TestUtils.defineInstance(); + new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null); + ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(instance); + executeCodeInput.setSession(new QSession()); + executeCodeInput.setInput(Map.of("a", 1)); + + BuildScriptLogAndScriptLogLineExecutionLogger logger = new BuildScriptLogAndScriptLogLineExecutionLogger(9999, 8888); + logger.acceptExecutionStart(executeCodeInput); + logger.acceptLogLine("This is a log"); + logger.acceptLogLine("This is also a log"); + logger.acceptExecutionEnd(true); + + QRecord scriptLog = logger.getScriptLog(); + assertNull(scriptLog.getValueInteger("id")); + assertNotNull(scriptLog.getValue("startTimestamp")); + assertNotNull(scriptLog.getValue("endTimestamp")); + assertNotNull(scriptLog.getValue("runTimeMillis")); + assertEquals(9999, scriptLog.getValueInteger("scriptId")); + assertEquals(8888, scriptLog.getValueInteger("scriptRevisionId")); + assertEquals("{a=1}", scriptLog.getValueString("input")); + assertEquals("true", scriptLog.getValueString("output")); + assertNull(scriptLog.getValueString("exception")); + assertFalse(scriptLog.getValueBoolean("hadError")); + + List scriptLogLineRecords = logger.getScriptLogLines(); + assertEquals(2, scriptLogLineRecords.size()); + QRecord scriptLogLine = scriptLogLineRecords.get(0); + assertNull(scriptLogLine.getValueInteger("scriptLogId")); + assertNotNull(scriptLogLine.getValue("timestamp")); + assertEquals("This is a log", scriptLogLine.getValueString("text")); + scriptLogLine = scriptLogLineRecords.get(1); + assertEquals("This is also a log", scriptLogLine.getValueString("text")); + } +} \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/StoreScriptLogAndScriptLogLineExecutionLoggerTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/StoreScriptLogAndScriptLogLineExecutionLoggerTest.java new file mode 100644 index 00000000..42fb6cca --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/logging/StoreScriptLogAndScriptLogLineExecutionLoggerTest.java @@ -0,0 +1,103 @@ +/* + * 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 . + */ + +package com.kingsrook.qqq.backend.core.actions.scripts.logging; + + +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput; +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.model.session.QSession; +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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + + +/******************************************************************************* + ** Unit test for StoreScriptLogAndScriptLogLineExecutionLogger + *******************************************************************************/ +class StoreScriptLogAndScriptLogLineExecutionLoggerTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + @AfterEach + void beforeAndAfterEach() + { + MemoryRecordStore.getInstance().reset(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() throws QException + { + QInstance instance = TestUtils.defineInstance(); + new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null); + ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(instance); + executeCodeInput.setSession(new QSession()); + executeCodeInput.setInput(Map.of("a", 1)); + + StoreScriptLogAndScriptLogLineExecutionLogger logger = new StoreScriptLogAndScriptLogLineExecutionLogger(9999, 8888); + logger.acceptExecutionStart(executeCodeInput); + logger.acceptLogLine("This is a log"); + logger.acceptLogLine("This is also a log"); + logger.acceptExecutionEnd(true); + + List scriptLogRecords = TestUtils.queryTable(instance, "scriptLog"); + assertEquals(1, scriptLogRecords.size()); + QRecord scriptLog = scriptLogRecords.get(0); + assertNotNull(scriptLog.getValueInteger("id")); + assertNotNull(scriptLog.getValue("startTimestamp")); + assertNotNull(scriptLog.getValue("endTimestamp")); + assertNotNull(scriptLog.getValue("runTimeMillis")); + assertEquals(9999, scriptLog.getValueInteger("scriptId")); + assertEquals(8888, scriptLog.getValueInteger("scriptRevisionId")); + assertEquals("{a=1}", scriptLog.getValueString("input")); + assertEquals("true", scriptLog.getValueString("output")); + assertNull(scriptLog.getValueString("exception")); + assertFalse(scriptLog.getValueBoolean("hadError")); + + List scriptLogLineRecords = TestUtils.queryTable(instance, "scriptLogLine"); + assertEquals(2, scriptLogLineRecords.size()); + QRecord scriptLogLine = scriptLogLineRecords.get(0); + assertEquals(scriptLog.getValueInteger("id"), scriptLogLine.getValueInteger("scriptLogId")); + assertNotNull(scriptLogLine.getValue("timestamp")); + assertEquals("This is a log", scriptLogLine.getValueString("text")); + scriptLogLine = scriptLogLineRecords.get(1); + assertEquals("This is also a log", scriptLogLine.getValueString("text")); + } +} \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index ec8246ca..30d6f610 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -51,6 +51,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; @@ -805,6 +806,7 @@ class QInstanceValidatorTest possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id"))); possibleValueSource.setCustomCodeReference(new QCodeReference()); possibleValueSource.setEnumValues(null); + possibleValueSource.setType(QPossibleValueSourceType.ENUM); }, "should not have a tableName", "should not have searchFields", @@ -831,6 +833,7 @@ class QInstanceValidatorTest possibleValueSource.setOrderByFields(new ArrayList<>()); possibleValueSource.setCustomCodeReference(new QCodeReference()); possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test"))); + possibleValueSource.setType(QPossibleValueSourceType.TABLE); }, "should not have enum values", "should not have a customCodeReference", @@ -860,6 +863,7 @@ class QInstanceValidatorTest possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id"))); possibleValueSource.setCustomCodeReference(null); possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test"))); + possibleValueSource.setType(QPossibleValueSourceType.CUSTOM); }, "should not have enum values", "should not have a tableName", diff --git a/qqq-dev-tools/MODULE_LIST b/qqq-dev-tools/MODULE_LIST index 8c54c8af..1d7132d4 100644 --- a/qqq-dev-tools/MODULE_LIST +++ b/qqq-dev-tools/MODULE_LIST @@ -2,6 +2,7 @@ qqq-backend-core qqq-backend-module-api qqq-backend-module-rdbms qqq-backend-module-filesystem +qqq-language-support-javascript qqq-middleware-javalin qqq-middleware-picocli qqq-middleware-lambda diff --git a/qqq-language-support-javascript/src/main/java/com/kingsrook/qqq/languages/javascript/QJavaScriptExecutor.java b/qqq-language-support-javascript/src/main/java/com/kingsrook/qqq/languages/javascript/QJavaScriptExecutor.java index 90933f32..3c28a026 100644 --- a/qqq-language-support-javascript/src/main/java/com/kingsrook/qqq/languages/javascript/QJavaScriptExecutor.java +++ b/qqq-language-support-javascript/src/main/java/com/kingsrook/qqq/languages/javascript/QJavaScriptExecutor.java @@ -50,10 +50,10 @@ public class QJavaScriptExecutor implements QCodeExecutor ** *******************************************************************************/ @Override - public Serializable execute(QCodeReference codeReference, Map context, QCodeExecutionLoggerInterface executionLogger) throws QCodeException + public Serializable execute(QCodeReference codeReference, Map inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException { String code = getCode(codeReference); - Serializable output = runInline(code, context, executionLogger); + Serializable output = runInline(code, inputContext, executionLogger); return (output); } @@ -62,13 +62,17 @@ public class QJavaScriptExecutor implements QCodeExecutor /******************************************************************************* ** *******************************************************************************/ - private Serializable runInline(String code, Map context, QCodeExecutionLoggerInterface executionLogger) throws QCodeException + private Serializable runInline(String code, Map inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException { new NashornScriptEngineFactory(); ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); + ////////////////////////////////////////////// + // setup the javascript environment/context // + ////////////////////////////////////////////// Bindings bindings = engine.createBindings(); - bindings.putAll(context); + bindings.putAll(inputContext); + if(!bindings.containsKey("logger")) { bindings.put("logger", executionLogger); diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java index 1bf2a614..89da7217 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java @@ -135,7 +135,7 @@ public class TestUtils qInstance.addBackend(defineMemoryBackend()); try { - new ScriptsMetaDataProvider().defineStandardScriptsTables(qInstance, defineMemoryBackend().getName(), null); + new ScriptsMetaDataProvider().defineAll(qInstance, defineMemoryBackend().getName(), null); } catch(Exception e) {