Cleanup from code review

This commit is contained in:
2022-11-01 16:12:32 -05:00
parent 165583cd98
commit 683b3c658d
46 changed files with 1662 additions and 165 deletions

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.actions.dashboard; 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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
@ -51,6 +52,8 @@ public class QuickSightChartRenderer extends AbstractWidgetRenderer
@Override @Override
public RenderWidgetOutput render(RenderWidgetInput input) throws QException public RenderWidgetOutput render(RenderWidgetInput input) throws QException
{ {
ActionHelper.validateSession(input);
try try
{ {
QuickSightChartMetaData quickSightMetaData = (QuickSightChartMetaData) input.getWidgetMetaData(); QuickSightChartMetaData quickSightMetaData = (QuickSightChartMetaData) input.getWidgetMetaData();

View File

@ -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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; 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.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.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode; import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
@ -69,8 +70,10 @@ public class MetaDataAction
Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>(); Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>();
for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet()) for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet())
{ {
tables.put(entry.getKey(), new QFrontendTableMetaData(entry.getValue(), false)); String tableName = entry.getKey();
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue())); 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); metaDataOutput.setTables(tables);

View File

@ -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.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput; 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.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.frontend.QFrontendTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; 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.")); 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 // todo post-customization - can do whatever w/ the result if you want

View File

@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable; 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.Log4jCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException; 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 public class ExecuteCodeAction
{ {
@ -65,7 +77,20 @@ public class ExecuteCodeAction
Class<? extends QCodeExecutor> executorClass = (Class<? extends QCodeExecutor>) Class.forName(languageExecutor); Class<? extends QCodeExecutor> executorClass = (Class<? extends QCodeExecutor>) Class.forName(languageExecutor);
QCodeExecutor qCodeExecutor = executorClass.getConstructor().newInstance(); 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<String, Serializable> 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); output.setOutput(codeOutput);
executionLogger.acceptExecutionEnd(codeOutput); executionLogger.acceptExecutionEnd(codeOutput);
} }

View File

@ -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 public interface QCodeExecutor
{ {
@ -38,6 +39,6 @@ public interface QCodeExecutor
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
Serializable execute(QCodeReference codeReference, Map<String, Serializable> context, QCodeExecutionLoggerInterface executionLogger) throws QCodeException; Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException;
} }

View File

@ -33,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/******************************************************************************* /*******************************************************************************
** ** Java
*******************************************************************************/ *******************************************************************************/
public class QJavaExecutor implements QCodeExecutor public class QJavaExecutor implements QCodeExecutor
{ {
@ -42,9 +42,9 @@ public class QJavaExecutor implements QCodeExecutor
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public Serializable execute(QCodeReference codeReference, Map<String, Serializable> input, QCodeExecutionLoggerInterface executionLogger) throws QCodeException public Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
{ {
Map<String, Object> context = new HashMap<>(input); Map<String, Object> context = new HashMap<>(inputContext);
if(!context.containsKey("logger")) if(!context.containsKey("logger"))
{ {
context.put("logger", executionLogger); context.put("logger", executionLogger);

View File

@ -24,9 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; 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.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput; import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptInput; 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 public void run(RunAssociatedScriptInput input, RunAssociatedScriptOutput output) throws QException
{ {
ActionHelper.validateSession(input);
Serializable scriptId = getScriptId(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); Script script = getScript(input, scriptId);
if(script.getCurrentScriptRevisionId() == null)
{
throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
+ "] (scriptId=" + scriptId + ") does not have a current version."));
}
ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId()); ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId());
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance()); ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
executeCodeInput.setSession(input.getSession()); executeCodeInput.setSession(input.getSession());
executeCodeInput.setContext(new HashMap<>(input.getInputValues())); executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); executeCodeInput.setContext(new HashMap<>());
executeCodeInput.setExecutionLogger(new HeaderAndDetailTableCodeExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId())); 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(); ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
@ -77,6 +98,12 @@ public class RunAssociatedScriptAction
getInput.setTableName("scriptRevision"); getInput.setTableName("scriptRevision");
getInput.setPrimaryKey(scriptRevisionId); getInput.setPrimaryKey(scriptRevisionId);
GetOutput getOutput = new GetAction().execute(getInput); 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())); return (new ScriptRevision(getOutput.getRecord()));
} }
@ -92,6 +119,13 @@ public class RunAssociatedScriptAction
getInput.setTableName("script"); getInput.setTableName("script");
getInput.setPrimaryKey(scriptId); getInput.setPrimaryKey(scriptId);
GetOutput getOutput = new GetAction().execute(getInput); 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())); return (new Script(getOutput.getRecord()));
} }
@ -107,6 +141,11 @@ public class RunAssociatedScriptAction
getInput.setTableName(input.getCodeReference().getRecordTable()); getInput.setTableName(input.getCodeReference().getRecordTable());
getInput.setPrimaryKey(input.getCodeReference().getRecordPrimaryKey()); getInput.setPrimaryKey(input.getCodeReference().getRecordPrimaryKey());
GetOutput getOutput = new GetAction().execute(getInput); 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())); return (getOutput.getRecord().getValue(input.getCodeReference().getFieldName()));
} }

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Optional; 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.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; 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 public class StoreAssociatedScriptAction
@ -60,6 +68,8 @@ public class StoreAssociatedScriptAction
*******************************************************************************/ *******************************************************************************/
public void run(StoreAssociatedScriptInput input, StoreAssociatedScriptOutput output) throws QException public void run(StoreAssociatedScriptInput input, StoreAssociatedScriptOutput output) throws QException
{ {
ActionHelper.validateSession(input);
QTableMetaData table = input.getTable(); QTableMetaData table = input.getTable();
Optional<AssociatedScript> optAssociatedScript = table.getAssociatedScripts().stream().filter(as -> as.getFieldName().equals(input.getFieldName())).findFirst(); Optional<AssociatedScript> optAssociatedScript = table.getAssociatedScripts().stream().filter(as -> as.getFieldName().equals(input.getFieldName())).findFirst();
if(optAssociatedScript.isEmpty()) if(optAssociatedScript.isEmpty())
@ -77,6 +87,7 @@ public class StoreAssociatedScriptAction
getInput.setSession(input.getSession()); getInput.setSession(input.getSession());
getInput.setTableName(input.getTableName()); getInput.setTableName(input.getTableName());
getInput.setPrimaryKey(input.getRecordPrimaryKey()); getInput.setPrimaryKey(input.getRecordPrimaryKey());
getInput.setShouldGenerateDisplayValues(true);
GetOutput getOutput = new GetAction().execute(getInput); GetOutput getOutput = new GetAction().execute(getInput);
associatedRecord = getOutput.getRecord(); associatedRecord = getOutput.getRecord();
} }
@ -100,8 +111,13 @@ public class StoreAssociatedScriptAction
getInput.setSession(input.getSession()); getInput.setSession(input.getSession());
getInput.setTableName("scriptType"); getInput.setTableName("scriptType");
getInput.setPrimaryKey(associatedScript.getScriptTypeId()); getInput.setPrimaryKey(associatedScript.getScriptTypeId());
getInput.setShouldGenerateDisplayValues(true);
GetOutput getOutput = new GetAction().execute(getInput); GetOutput getOutput = new GetAction().execute(getInput);
QRecord scriptType = getOutput.getRecord(); QRecord scriptType = getOutput.getRecord();
if(scriptType == null)
{
throw (new QException("Script type [" + associatedScript.getScriptTypeId() + "] was not found."));
}
///////////////////////// /////////////////////////
// insert a new script // // insert a new script //

View File

@ -22,14 +22,20 @@
package com.kingsrook.qqq.backend.core.actions.scripts; 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.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.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput; 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; 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 public class TestScriptAction
{ {
@ -40,5 +46,20 @@ public class TestScriptAction
public void run(TestScriptInput input, TestScriptOutput output) throws QException public void run(TestScriptInput input, TestScriptOutput output) throws QException
{ {
QTableMetaData table = input.getTable(); 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());
} }
} }

View File

@ -27,30 +27,26 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.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.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.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
** Implementation of a code execution logger that logs into a header and child ** Implementation of a code execution logger that builds a scriptLog and 0 or more
** table ** 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 QRecord scriptLog;
private List<QRecord> children = new ArrayList<>(); private List<QRecord> scriptLogLines = new ArrayList<>();
private ExecuteCodeInput executeCodeInput; protected ExecuteCodeInput executeCodeInput;
private Serializable scriptId; private Serializable scriptId;
private Serializable scriptRevisionId; private Serializable scriptRevisionId;
@ -61,7 +57,17 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo
** Constructor ** Constructor
** **
*******************************************************************************/ *******************************************************************************/
public HeaderAndDetailTableCodeExecutionLogger(Serializable scriptId, Serializable scriptRevisionId) public BuildScriptLogAndScriptLogLineExecutionLogger()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public BuildScriptLogAndScriptLogLineExecutionLogger(Serializable scriptId, Serializable scriptRevisionId)
{ {
this.scriptId = scriptId; this.scriptId = scriptId;
this.scriptRevisionId = scriptRevisionId; this.scriptRevisionId = scriptRevisionId;
@ -78,7 +84,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo
.withValue("scriptId", scriptId) .withValue("scriptId", scriptId)
.withValue("scriptRevisionId", scriptRevisionId) .withValue("scriptRevisionId", scriptRevisionId)
.withValue("startTimestamp", Instant.now()) .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) protected QRecord buildDetailLogRecord(String logLine)
{ {
return (new QRecord() return (new QRecord()
.withValue("scriptLogId", header.getValue("id")) .withValue("scriptLogId", scriptLog.getValue("id"))
.withValue("timestamp", Instant.now()) .withValue("timestamp", Instant.now())
.withValue("text", truncate(logLine))); .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(); Instant endTimestamp = Instant.now();
header.setValue("endTimestamp", endTimestamp); scriptLog.setValue("endTimestamp", endTimestamp);
header.setValue("runTimeMillis", startTimestamp.until(endTimestamp, ChronoUnit.MILLIS)); scriptLog.setValue("runTimeMillis", startTimestamp.until(endTimestamp, ChronoUnit.MILLIS));
if(exception != null) if(exception != null)
{ {
header.setValue("hadError", true); scriptLog.setValue("hadError", true);
header.setValue("error", exception.getMessage()); scriptLog.setValue("error", exception.getMessage());
} }
else else
{ {
header.setValue("hadError", false); scriptLog.setValue("hadError", false);
header.setValue("output", truncate(String.valueOf(output))); scriptLog.setValue("output", truncate(output));
} }
} }
@ -139,14 +145,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo
try try
{ {
this.executeCodeInput = executeCodeInput; this.executeCodeInput = executeCodeInput;
QRecord scriptLog = buildHeaderRecord(executeCodeInput); this.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);
} }
catch(Exception e) catch(Exception e)
{ {
@ -162,7 +161,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo
@Override @Override
public void acceptLogLine(String logLine) public void acceptLogLine(String logLine)
{ {
children.add(buildDetailLogRecord(logLine)); scriptLogLines.add(buildDetailLogRecord(logLine));
} }
@ -173,7 +172,7 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo
@Override @Override
public void acceptException(Exception exception) public void acceptException(Exception exception)
{ {
store(null, exception); updateHeaderAtEnd(null, exception);
} }
@ -184,38 +183,39 @@ public class HeaderAndDetailTableCodeExecutionLogger implements QCodeExecutionLo
@Override @Override
public void acceptExecutionEnd(Serializable output) 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 return scriptLog;
{
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);
}
} }
/*******************************************************************************
** Getter for scriptLogLines
**
*******************************************************************************/
public List<QRecord> getScriptLogLines()
{
return scriptLogLines;
}
/*******************************************************************************
** Setter for scriptLog
**
*******************************************************************************/
protected void setScriptLog(QRecord scriptLog)
{
this.scriptLog = scriptLog;
}
} }

View File

@ -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 public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
{ {
@ -52,8 +52,8 @@ public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
{ {
this.qCodeReference = executeCodeInput.getCodeReference(); this.qCodeReference = executeCodeInput.getCodeReference();
String contextString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getContext()), 250, "..."); String inputString = StringUtils.safeTruncate(ValueUtils.getValueAsString(executeCodeInput.getInput()), 250, "...");
LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with context: " + contextString); LOG.info("Starting script execution: " + qCodeReference.getName() + ", uuid: " + uuid + ", with input: " + inputString);
} }

View File

@ -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 public interface QCodeExecutionLoggerInterface
{ {
/******************************************************************************* /*******************************************************************************
** ** Called when the execution starts - takes the execution's input object.
*******************************************************************************/ *******************************************************************************/
void acceptExecutionStart(ExecuteCodeInput executeCodeInput); void acceptExecutionStart(ExecuteCodeInput executeCodeInput);
/******************************************************************************* /*******************************************************************************
** ** Called to log a line, a message.
*******************************************************************************/ *******************************************************************************/
void acceptLogLine(String logLine); 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) default void log(String message)
{ {
@ -51,12 +52,12 @@ public interface QCodeExecutionLoggerInterface
} }
/******************************************************************************* /*******************************************************************************
** ** Called if the script fails with an exception.
*******************************************************************************/ *******************************************************************************/
void acceptException(Exception exception); void acceptException(Exception exception);
/******************************************************************************* /*******************************************************************************
** ** Called if the script completes without exception.
*******************************************************************************/ *******************************************************************************/
void acceptExecutionEnd(Serializable output); void acceptExecutionEnd(Serializable output);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -406,8 +406,11 @@ public class QPossibleValueTranslator
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// this is needed to get record labels, which are what we use here... unclear if best! // // this is needed to get record labels, which are what we use here... unclear if best! //
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
if(notTooDeep())
{
queryInput.setShouldTranslatePossibleValues(true); queryInput.setShouldTranslatePossibleValues(true);
queryInput.setShouldGenerateDisplayValues(true); queryInput.setShouldGenerateDisplayValues(true);
}
QueryOutput queryOutput = new QueryAction().execute(queryInput); 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);
}
} }

View File

@ -184,6 +184,7 @@ public class QValueFormatter
} }
catch(Exception e) catch(Exception e)
{ {
LOG.debug("Error formatting record label", e);
return (formatRecordLabelExceptionalCases(table, record)); return (formatRecordLabelExceptionalCases(table, record));
} }
} }

View File

@ -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 public class QCodeException extends QException
{ {

View File

@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
public class ExecuteCodeInput extends AbstractActionInput public class ExecuteCodeInput extends AbstractActionInput
{ {
private QCodeReference codeReference; private QCodeReference codeReference;
private Map<String, Serializable> input;
private Map<String, Serializable> context; private Map<String, Serializable> context;
private QCodeExecutionLoggerInterface executionLogger; private QCodeExecutionLoggerInterface executionLogger;
@ -87,6 +88,56 @@ public class ExecuteCodeInput extends AbstractActionInput
/*******************************************************************************
** Getter for input
**
*******************************************************************************/
public Map<String, Serializable> getInput()
{
return input;
}
/*******************************************************************************
** Setter for input
**
*******************************************************************************/
public void setInput(Map<String, Serializable> input)
{
this.input = input;
}
/*******************************************************************************
** Fluent setter for input
**
*******************************************************************************/
public ExecuteCodeInput withInput(Map<String, Serializable> 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 ** Getter for context
** **

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.model.actions.scripts; package com.kingsrook.qqq.backend.core.model.actions.scripts;
import java.io.Serializable;
import java.util.Map; import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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 public class RunAssociatedScriptInput extends AbstractTableActionInput
{ {
private AssociatedScriptCodeReference codeReference; private AssociatedScriptCodeReference codeReference;
private Map<String, String> inputValues; private Map<String, Serializable> inputValues;
private Serializable outputObject;
@ -86,7 +89,7 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput
** Getter for inputValues ** Getter for inputValues
** **
*******************************************************************************/ *******************************************************************************/
public Map<String, String> getInputValues() public Map<String, Serializable> getInputValues()
{ {
return inputValues; return inputValues;
} }
@ -97,7 +100,7 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput
** Setter for inputValues ** Setter for inputValues
** **
*******************************************************************************/ *******************************************************************************/
public void setInputValues(Map<String, String> inputValues) public void setInputValues(Map<String, Serializable> inputValues)
{ {
this.inputValues = inputValues; this.inputValues = inputValues;
} }
@ -108,10 +111,44 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput
** Fluent setter for inputValues ** Fluent setter for inputValues
** **
*******************************************************************************/ *******************************************************************************/
public RunAssociatedScriptInput withInputValues(Map<String, String> inputValues) public RunAssociatedScriptInput withInputValues(Map<String, Serializable> inputValues)
{ {
this.inputValues = inputValues; this.inputValues = inputValues;
return (this); 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);
}
} }

View File

@ -22,8 +22,12 @@
package com.kingsrook.qqq.backend.core.model.metadata; 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.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QBackendMetaDataDeserializer; 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; import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
@ -38,6 +42,9 @@ public class QBackendMetaData
private String name; private String name;
private String backendType; private String backendType;
private Set<Capability> enabledCapabilities = new HashSet<>();
private Set<Capability> disabledCapabilities = new HashSet<>();
// todo - at some point, we may want to apply this to secret properties on subclasses? // todo - at some point, we may want to apply this to secret properties on subclasses?
// @JsonFilter("secretsFilter") // @JsonFilter("secretsFilter")
@ -157,4 +164,161 @@ public class QBackendMetaData
// noop in base class // // noop in base class //
//////////////////////// ////////////////////////
} }
/*******************************************************************************
** Getter for enabledCapabilities
**
*******************************************************************************/
public Set<Capability> getEnabledCapabilities()
{
return enabledCapabilities;
}
/*******************************************************************************
** Setter for enabledCapabilities
**
*******************************************************************************/
public void setEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
}
/*******************************************************************************
** Fluent setter for enabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for enabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withCapabilities(Set<Capability> 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<Capability> getDisabledCapabilities()
{
return disabledCapabilities;
}
/*******************************************************************************
** Setter for disabledCapabilities
**
*******************************************************************************/
public void setDisabledCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
}
/*******************************************************************************
** Fluent setter for disabledCapabilities
**
*******************************************************************************/
public QBackendMetaData withDisabledCapabilities(Set<Capability> 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<Capability> 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);
}
} }

View File

@ -22,6 +22,9 @@
package com.kingsrook.qqq.backend.core.model.metadata.dashboard; 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; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
@ -38,6 +41,8 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
protected Integer gridColumns; protected Integer gridColumns;
protected QCodeReference codeReference; protected QCodeReference codeReference;
protected Map<String, Serializable> defaultValues = new LinkedHashMap<>();
/******************************************************************************* /*******************************************************************************
@ -242,4 +247,56 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
return (this); return (this);
} }
/*******************************************************************************
** Getter for defaultValues
**
*******************************************************************************/
public Map<String, Serializable> getDefaultValues()
{
return defaultValues;
}
/*******************************************************************************
** Setter for defaultValues
**
*******************************************************************************/
public void setDefaultValues(Map<String, Serializable> defaultValues)
{
this.defaultValues = defaultValues;
}
/*******************************************************************************
** Fluent setter for defaultValues
**
*******************************************************************************/
public QWidgetMetaData withDefaultValues(Map<String, Serializable> 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);
}
} }

View File

@ -34,7 +34,11 @@ public enum AdornmentType
{ {
LINK, LINK,
CHIP, CHIP,
SIZE; SIZE,
CODE_EDITOR;
//////////////////////////////////////////////////////////////////////////
// keep these values in sync with AdornmentType.ts in qqq-frontend-core //
//////////////////////////////////////////////////////////////////////////

View File

@ -23,11 +23,16 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; 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;
import com.fasterxml.jackson.annotation.JsonInclude.Include; 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.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.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -53,6 +58,8 @@ public class QFrontendTableMetaData
private List<String> widgets; private List<String> widgets;
private Set<String> capabilities;
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! // // 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.name = tableMetaData.getName();
this.label = tableMetaData.getLabel(); this.label = tableMetaData.getLabel();
@ -89,6 +96,62 @@ public class QFrontendTableMetaData
{ {
this.widgets = tableMetaData.getWidgets(); this.widgets = tableMetaData.getWidgets();
} }
setCapabilities(backendForTable, tableMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
private void setCapabilities(QBackendMetaData backend, QTableMetaData table)
{
Set<Capability> 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; return widgets;
} }
/*******************************************************************************
** Getter for capabilities
**
*******************************************************************************/
public Set<String> getCapabilities()
{
return capabilities;
}
} }

View File

@ -24,6 +24,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -340,4 +343,38 @@ public class QAppMetaData implements QAppChildMetaData
this.addSection(section); this.addSection(section);
return (this); 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);
}
} }

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.model.metadata.layout; package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList;
import java.util.List; 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 ** 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 ** 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 ** Getter for icon
** **

View File

@ -289,6 +289,7 @@ public class QPossibleValueSource
*******************************************************************************/ *******************************************************************************/
public void setTableName(String tableName) public void setTableName(String tableName)
{ {
this.type = QPossibleValueSourceType.TABLE;
this.tableName = tableName; this.tableName = tableName;
} }
@ -300,7 +301,7 @@ public class QPossibleValueSource
*******************************************************************************/ *******************************************************************************/
public QPossibleValueSource withTableName(String tableName) public QPossibleValueSource withTableName(String tableName)
{ {
this.tableName = tableName; setTableName(tableName);
return (this); return (this);
} }
@ -446,6 +447,7 @@ public class QPossibleValueSource
public void setEnumValues(List<QPossibleValue<?>> enumValues) public void setEnumValues(List<QPossibleValue<?>> enumValues)
{ {
this.enumValues = enumValues; this.enumValues = enumValues;
setType(QPossibleValueSourceType.ENUM);
} }
@ -456,7 +458,7 @@ public class QPossibleValueSource
*******************************************************************************/ *******************************************************************************/
public QPossibleValueSource withEnumValues(List<QPossibleValue<?>> enumValues) public QPossibleValueSource withEnumValues(List<QPossibleValue<?>> enumValues)
{ {
this.enumValues = enumValues; setEnumValues(enumValues);
return this; return this;
} }
@ -472,6 +474,7 @@ public class QPossibleValueSource
this.enumValues = new ArrayList<>(); this.enumValues = new ArrayList<>();
} }
this.enumValues.add(possibleValue); this.enumValues.add(possibleValue);
setType(QPossibleValueSourceType.ENUM);
} }
@ -512,6 +515,7 @@ public class QPossibleValueSource
public void setCustomCodeReference(QCodeReference customCodeReference) public void setCustomCodeReference(QCodeReference customCodeReference)
{ {
this.customCodeReference = customCodeReference; this.customCodeReference = customCodeReference;
setType(QPossibleValueSourceType.CUSTOM);
} }
@ -522,7 +526,7 @@ public class QPossibleValueSource
*******************************************************************************/ *******************************************************************************/
public QPossibleValueSource withCustomCodeReference(QCodeReference customCodeReference) public QPossibleValueSource withCustomCodeReference(QCodeReference customCodeReference)
{ {
this.customCodeReference = customCodeReference; setCustomCodeReference(customCodeReference);
return (this); return (this);
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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 //
//////////////////////////////////////////////////////////////////////////
}

View File

@ -38,6 +38,8 @@ public class QFieldSection
private List<String> fieldNames; private List<String> fieldNames;
private QIcon icon; private QIcon icon;
private boolean isHidden = false;
/******************************************************************************* /*******************************************************************************
@ -244,4 +246,38 @@ public class QFieldSection
return (this); 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);
}
} }

View File

@ -26,10 +26,12 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer; import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizer;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
@ -81,6 +83,9 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
private List<String> widgets; private List<String> widgets;
private List<AssociatedScript> associatedScripts; private List<AssociatedScript> associatedScripts;
private Set<Capability> enabledCapabilities = new HashSet<>();
private Set<Capability> disabledCapabilities = new HashSet<>();
/******************************************************************************* /*******************************************************************************
@ -874,4 +879,160 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
return (this); return (this);
} }
/*******************************************************************************
** Getter for enabledCapabilities
**
*******************************************************************************/
public Set<Capability> getEnabledCapabilities()
{
return enabledCapabilities;
}
/*******************************************************************************
** Setter for enabledCapabilities
**
*******************************************************************************/
public void setEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
}
/*******************************************************************************
** Fluent setter for enabledCapabilities
**
*******************************************************************************/
public QTableMetaData withEnabledCapabilities(Set<Capability> enabledCapabilities)
{
this.enabledCapabilities = enabledCapabilities;
return (this);
}
/*******************************************************************************
** Alternative fluent setter for enabledCapabilities
**
*******************************************************************************/
public QTableMetaData withCapabilities(Set<Capability> 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<Capability> getDisabledCapabilities()
{
return disabledCapabilities;
}
/*******************************************************************************
** Setter for disabledCapabilities
**
*******************************************************************************/
public void setDisabledCapabilities(Set<Capability> disabledCapabilities)
{
this.disabledCapabilities = disabledCapabilities;
}
/*******************************************************************************
** Fluent setter for disabledCapabilities
**
*******************************************************************************/
public QTableMetaData withDisabledCapabilities(Set<Capability> 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<Capability> 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);
}
} }

View File

@ -48,10 +48,10 @@ public class Script extends QRecordEntity
@QField() @QField()
private String name; private String name;
@QField() @QField(possibleValueSourceName = "scriptType")
private Integer scriptTypeId; private Integer scriptTypeId;
@QField() @QField(possibleValueSourceName = "scriptRevision")
private Integer currentScriptRevisionId; private Integer currentScriptRevisionId;

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.scripts;
import java.time.Instant; import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField; 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.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
/******************************************************************************* /*******************************************************************************
@ -43,10 +44,10 @@ public class ScriptLog extends QRecordEntity
@QField() @QField()
private Instant modifyDate; private Instant modifyDate;
@QField() @QField(possibleValueSourceName = "script")
private Integer scriptId; private Integer scriptId;
@QField() @QField(possibleValueSourceName = "scriptRevision")
private Integer scriptRevisionId; private Integer scriptRevisionId;
@QField() @QField()
@ -55,7 +56,7 @@ public class ScriptLog extends QRecordEntity
@QField() @QField()
private Instant endTimestamp; private Instant endTimestamp;
@QField() @QField(displayFormat = DisplayFormat.COMMAS)
private Integer runTimeMillis; private Integer runTimeMillis;
@QField() @QField()

View File

@ -43,7 +43,7 @@ public class ScriptLogLine extends QRecordEntity
@QField() @QField()
private Instant modifyDate; private Instant modifyDate;
@QField() @QField(possibleValueSourceName = "scriptLog")
private Integer scriptLogId; private Integer scriptLogId;
@QField() @QField()

View File

@ -45,7 +45,7 @@ public class ScriptRevision extends QRecordEntity
@QField() @QField()
private Instant modifyDate; private Instant modifyDate;
@QField() @QField(possibleValueSourceName = "script")
private Integer scriptId; private Integer scriptId;
@QField() @QField()

View File

@ -28,7 +28,14 @@ import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity; 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.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.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 class ScriptsMetaDataProvider
{ {
/*******************************************************************************
**
*******************************************************************************/
public void defineAll(QInstance instance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{
defineStandardScriptsTables(instance, backendName, backendDetailEnricher);
defineStandardScriptsPossibleValueSources(instance);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -53,7 +71,35 @@ public class ScriptsMetaDataProvider
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public List<QTableMetaData> defineStandardScriptsTables(String backendName, Consumer<QTableMetaData> 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<QTableMetaData> defineStandardScriptsTables(String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
{ {
List<QTableMetaData> rs = new ArrayList<>(); List<QTableMetaData> rs = new ArrayList<>();
rs.add(enrich(backendDetailEnricher, defineScriptTypeTable(backendName))); rs.add(enrich(backendDetailEnricher, defineScriptTypeTable(backendName)));
@ -101,7 +147,9 @@ public class ScriptsMetaDataProvider
*******************************************************************************/ *******************************************************************************/
private QTableMetaData defineScriptTable(String backendName) throws QException 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 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 private QTableMetaData defineScriptRevisionTable(String backendName) throws QException
{ {
return (defineStandardTable(backendName, ScriptRevision.TABLE_NAME, ScriptRevision.class) QTableMetaData tableMetaData = defineStandardTable(backendName, ScriptRevision.TABLE_NAME, ScriptRevision.class)
.withRecordLabelFields(List.of("id"))); .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 private QTableMetaData defineScriptLogTable(String backendName) throws QException
{ {
return (defineStandardTable(backendName, ScriptLog.TABLE_NAME, ScriptLog.class) 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"))));
} }

View File

@ -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 public class EnumerationBackendModule implements QBackendModuleInterface
{ {

View File

@ -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 public class BackendQueryFilterUtils
{ {
/******************************************************************************* /*******************************************************************************
** ** Test if record matches filter.
*******************************************************************************/ *******************************************************************************/
@SuppressWarnings("checkstyle:indentation") @SuppressWarnings("checkstyle:indentation")
public static boolean doesRecordMatch(QQueryFilter filter, QRecord qRecord) 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<QRecord> recordList) public static void sortRecordList(QQueryFilter filter, List<QRecord> recordList)
{ {
@ -443,7 +447,7 @@ public class BackendQueryFilterUtils
/******************************************************************************* /*******************************************************************************
** ** Apply skip & limit attributes from queryInput to a list of records.
*******************************************************************************/ *******************************************************************************/
public static List<QRecord> applySkipAndLimit(QueryInput queryInput, List<QRecord> recordList) public static List<QRecord> applySkipAndLimit(QueryInput queryInput, List<QRecord> recordList)
{ {

View File

@ -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 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<Serializable, QRecord> getForeignRecordMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException public static Map<Serializable, QRecord> getForeignRecordMap(AbstractActionInput parentActionInput, List<QRecord> 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<Serializable, QRecord> getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException public static ListingHash<Serializable, QRecord> getForeignRecordListingHashMap(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
{ {
ListingHash<Serializable, QRecord> foreignRecordMap = new ListingHash<>(); ListingHash<Serializable, QRecord> foreignRecordMap = new ListingHash<>();
QueryInput queryInput = new QueryInput(parentActionInput.getInstance()); QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
@ -79,11 +90,11 @@ public class GeneralProcessUtils
queryInput.setTableName(foreignTableName); queryInput.setTableName(foreignTableName);
List<Serializable> foreignIds = new ArrayList<>(sourceRecords.stream().map(r -> r.getValue(sourceTableForeignKeyFieldName)).toList()); List<Serializable> 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); QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord foreignRecord : queryOutput.getRecords()) for(QRecord foreignRecord : queryOutput.getRecords())
{ {
foreignRecordMap.add(foreignRecord.getValue(foreignTablePrimaryKeyName), foreignRecord); foreignRecordMap.add(foreignRecord.getValue(foreignTableForeignKeyName), foreignRecord);
} }
return foreignRecordMap; 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<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException public static void addForeignRecordsToRecordList(AbstractActionInput parentActionInput, List<QRecord> 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<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTablePrimaryKeyName) throws QException public static void addForeignRecordsListToRecordList(AbstractActionInput parentActionInput, List<QRecord> sourceRecords, String sourceTableForeignKeyFieldName, String foreignTableName, String foreignTableForeignKeyName) throws QException
{ {
ListingHash<Serializable, QRecord> foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTablePrimaryKeyName); ListingHash<Serializable, QRecord> foreignRecordMap = getForeignRecordListingHashMap(parentActionInput, sourceRecords, sourceTableForeignKeyFieldName, foreignTableName, foreignTableForeignKeyName);
for(QRecord sourceRecord : sourceRecords) for(QRecord sourceRecord : sourceRecords)
{ {
List<QRecord> foreignRecordList = foreignRecordMap.get(sourceRecord.getValue(sourceTableForeignKeyFieldName)); List<QRecord> 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<QRecord> getRecordListByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException public static List<QRecord> 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<QRecord> getRecordById(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException public static Optional<QRecord> 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<QRecord> loadTable(AbstractActionInput parentActionInput, String tableName) throws QException public static List<QRecord> 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. ** 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<Serializable, QRecord> loadTableToMap(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException public static Map<Serializable, QRecord> 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. ** 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<Serializable, QRecord> loadTableToListingHash(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException public static ListingHash<Serializable, QRecord> loadTableToListingHash(AbstractActionInput parentActionInput, String tableName, String keyFieldName) throws QException
{ {

View File

@ -26,10 +26,10 @@ import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; 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.Log4jCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.NoopCodeExecutionLogger; 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.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.QCodeException;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.ExecuteCodeInput;
@ -120,9 +120,9 @@ class ExecuteCodeActionTest
void testTableLogger() throws QException void testTableLogger() throws QException
{ {
QInstance qInstance = TestUtils.defineInstance(); 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(); ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
assertEquals(16, executeCodeOutput.getOutput()); assertEquals(16, executeCodeOutput.getOutput());

View File

@ -25,11 +25,17 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map; 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.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.RunAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptOutput; 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.StoreAssociatedScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptOutput; 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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
@ -56,24 +62,7 @@ class RunAssociatedScriptActionTest
@Test @Test
void test() throws QException void test() throws QException
{ {
QInstance instance = TestUtils.defineInstance(); QInstance instance = setupInstance();
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")
));
insertScript(instance, 1, """ insertScript(instance, 1, """
return "Hello"; return "Hello";
@ -90,6 +79,9 @@ class RunAssociatedScriptActionTest
); );
RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); 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)) assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput))
.isInstanceOf(QException.class) .isInstanceOf(QException.class)
.hasRootCauseInstanceOf(ClassNotFoundException.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.*");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -24,15 +24,12 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; 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.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.StoreAssociatedScriptInput; 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.scripts.StoreAssociatedScriptOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
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.data.QRecord; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
/******************************************************************************* /*******************************************************************************
@ -85,7 +83,7 @@ class StoreAssociatedScriptActionTest
.withFieldName("otherScriptId") .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( TestUtils.insertRecords(instance, table, List.of(
new QRecord().withValue("id", 1), 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, "testScriptId", 1);
assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "otherScriptId", 3); assertValueInField(instance, TestUtils.TABLE_NAME_PERSON_MEMORY, 1, "otherScriptId", 3);
assertValueInField(instance, "script", 3, "currentScriptRevisionId", 4); 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); GetInput getInput = new GetInput(instance);
queryInput.setSession(new QSession()); getInput.setSession(new QSession());
queryInput.setTableName(tableName); getInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, List.of(recordId)))); getInput.setPrimaryKey(recordId);
QueryOutput queryOutput = new QueryAction().execute(queryInput); GetOutput getOutput = new GetAction().execute(getInput);
Serializable actual = queryOutput.getRecords().get(0).getValue(fieldName); if(getOutput.getRecord() == null)
assertEquals(value, actual); {
return (actual); 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 + "]");
} }
} }

View File

@ -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.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -39,6 +40,7 @@ class TestScriptActionTest
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@Disabled("Not yet done.")
void test() throws QException void test() throws QException
{ {
QInstance instance = TestUtils.defineInstance(); QInstance instance = TestUtils.defineInstance();

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<QRecord> 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"));
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<QRecord> 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<QRecord> 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"));
}
}

View File

@ -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.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; 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.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.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
@ -805,6 +806,7 @@ class QInstanceValidatorTest
possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id"))); possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id")));
possibleValueSource.setCustomCodeReference(new QCodeReference()); possibleValueSource.setCustomCodeReference(new QCodeReference());
possibleValueSource.setEnumValues(null); possibleValueSource.setEnumValues(null);
possibleValueSource.setType(QPossibleValueSourceType.ENUM);
}, },
"should not have a tableName", "should not have a tableName",
"should not have searchFields", "should not have searchFields",
@ -831,6 +833,7 @@ class QInstanceValidatorTest
possibleValueSource.setOrderByFields(new ArrayList<>()); possibleValueSource.setOrderByFields(new ArrayList<>());
possibleValueSource.setCustomCodeReference(new QCodeReference()); possibleValueSource.setCustomCodeReference(new QCodeReference());
possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test"))); possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test")));
possibleValueSource.setType(QPossibleValueSourceType.TABLE);
}, },
"should not have enum values", "should not have enum values",
"should not have a customCodeReference", "should not have a customCodeReference",
@ -860,6 +863,7 @@ class QInstanceValidatorTest
possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id"))); possibleValueSource.setOrderByFields(List.of(new QFilterOrderBy("id")));
possibleValueSource.setCustomCodeReference(null); possibleValueSource.setCustomCodeReference(null);
possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test"))); possibleValueSource.setEnumValues(List.of(new QPossibleValue<>("test")));
possibleValueSource.setType(QPossibleValueSourceType.CUSTOM);
}, },
"should not have enum values", "should not have enum values",
"should not have a tableName", "should not have a tableName",

View File

@ -2,6 +2,7 @@ qqq-backend-core
qqq-backend-module-api qqq-backend-module-api
qqq-backend-module-rdbms qqq-backend-module-rdbms
qqq-backend-module-filesystem qqq-backend-module-filesystem
qqq-language-support-javascript
qqq-middleware-javalin qqq-middleware-javalin
qqq-middleware-picocli qqq-middleware-picocli
qqq-middleware-lambda qqq-middleware-lambda

View File

@ -50,10 +50,10 @@ public class QJavaScriptExecutor implements QCodeExecutor
** **
*******************************************************************************/ *******************************************************************************/
@Override @Override
public Serializable execute(QCodeReference codeReference, Map<String, Serializable> context, QCodeExecutionLoggerInterface executionLogger) throws QCodeException public Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
{ {
String code = getCode(codeReference); String code = getCode(codeReference);
Serializable output = runInline(code, context, executionLogger); Serializable output = runInline(code, inputContext, executionLogger);
return (output); return (output);
} }
@ -62,13 +62,17 @@ public class QJavaScriptExecutor implements QCodeExecutor
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private Serializable runInline(String code, Map<String, Serializable> context, QCodeExecutionLoggerInterface executionLogger) throws QCodeException private Serializable runInline(String code, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException
{ {
new NashornScriptEngineFactory(); new NashornScriptEngineFactory();
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
//////////////////////////////////////////////
// setup the javascript environment/context //
//////////////////////////////////////////////
Bindings bindings = engine.createBindings(); Bindings bindings = engine.createBindings();
bindings.putAll(context); bindings.putAll(inputContext);
if(!bindings.containsKey("logger")) if(!bindings.containsKey("logger"))
{ {
bindings.put("logger", executionLogger); bindings.put("logger", executionLogger);

View File

@ -135,7 +135,7 @@ public class TestUtils
qInstance.addBackend(defineMemoryBackend()); qInstance.addBackend(defineMemoryBackend());
try try
{ {
new ScriptsMetaDataProvider().defineStandardScriptsTables(qInstance, defineMemoryBackend().getName(), null); new ScriptsMetaDataProvider().defineAll(qInstance, defineMemoryBackend().getName(), null);
} }
catch(Exception e) catch(Exception e)
{ {