Script Tests and further enhancements

This commit is contained in:
2022-11-10 14:21:31 -06:00
parent 6d08afa4c2
commit a5ec33b51b
28 changed files with 1239 additions and 601 deletions

View File

@ -1,65 +0,0 @@
/*
* 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;
import java.util.HashMap;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Class for running a test of a script - e.g., maybe before it is saved.
*******************************************************************************/
public class TestScriptAction
{
/*******************************************************************************
**
*******************************************************************************/
public void run(TestScriptInput input, TestScriptOutput output) throws QException
{
QTableMetaData table = input.getTable();
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
executeCodeInput.setSession(input.getSession());
executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
executeCodeInput.setContext(new HashMap<>());
// todo! if(input.getOutputObject() != null)
// todo! {
// todo! executeCodeInput.getContext().put("output", input.getOutputObject());
// todo! }
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(input.getCode()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
executeCodeInput.setExecutionLogger(new BuildScriptLogAndScriptLogLineExecutionLogger());
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
// todo! output.setOutput(executeCodeOutput.getOutput());
}
}

View File

@ -0,0 +1,109 @@
/*
* 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;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
** Interface to be implemented by script-running actions, if they want to allow
** themselves to be used for user-testing of their script.
*******************************************************************************/
public interface TestScriptActionInterface
{
/*******************************************************************************
** Called to adapt or translate data from the TestScriptInput (which would just
** have a map of name-value pairs) to the actual input object(s) used by the script.
**
** Note - such a method may want or need to put an "output" object into the
** executeCodeInput's context map.
*******************************************************************************/
void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput);
/*******************************************************************************
** Called to adapt or translate the output object of the script execution to
** something suitable for returning to the caller.
**
** Default implementation may always be suitable?
*******************************************************************************/
default Serializable processTestScriptOutput(ExecuteCodeOutput executeCodeOutput)
{
return (executeCodeOutput.getOutput());
}
/*******************************************************************************
** Define the list of input fields for testing the script. The names of these
** fields will end up as keys in the setupTestScriptInput method's testScriptInput object.
*******************************************************************************/
List<QFieldMetaData> getTestInputFields();
/*******************************************************************************
** Define the list of output fields when testing the script. The output object
** returned from processTestScriptOutput should have keys that match these field names.
*******************************************************************************/
List<QFieldMetaData> getTestOutputFields();
/*******************************************************************************
** Execute a test script.
*******************************************************************************/
default void execute(TestScriptInput input, TestScriptOutput output) throws QException
{
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
executeCodeInput.setSession(input.getSession());
executeCodeInput.setContext(new HashMap<>());
executeCodeInput.setCodeReference(input.getCodeReference());
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
executeCodeInput.setExecutionLogger(executionLogger);
setupTestScriptInput(input, executeCodeInput);
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
try
{
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
output.setOutputObject(processTestScriptOutput(executeCodeOutput));
}
catch(Exception e)
{
output.setException(e);
}
output.setScriptLog(executionLogger.getScriptLog());
output.setScriptLogLines(executionLogger.getScriptLogLines());
}
}

View File

@ -230,7 +230,7 @@ public class QInstanceEnricher
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private void enrichField(QFieldMetaData field) public void enrichField(QFieldMetaData field)
{ {
if(!StringUtils.hasContent(field.getLabel())) if(!StringUtils.hasContent(field.getLabel()))
{ {

View File

@ -26,6 +26,7 @@ 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;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/******************************************************************************* /*******************************************************************************
@ -33,10 +34,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
*******************************************************************************/ *******************************************************************************/
public class TestScriptInput extends AbstractTableActionInput public class TestScriptInput extends AbstractTableActionInput
{ {
private Serializable recordPrimaryKey; private Map<String, Serializable> inputValues;
private String code; private QCodeReference codeReference;
private Serializable scriptTypeId;
private Map<String, String> inputValues;
@ -50,45 +49,11 @@ public class TestScriptInput extends AbstractTableActionInput
/*******************************************************************************
** Getter for recordPrimaryKey
**
*******************************************************************************/
public Serializable getRecordPrimaryKey()
{
return recordPrimaryKey;
}
/*******************************************************************************
** Setter for recordPrimaryKey
**
*******************************************************************************/
public void setRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
}
/*******************************************************************************
** Fluent setter for recordPrimaryKey
**
*******************************************************************************/
public TestScriptInput withRecordPrimaryKey(Serializable recordPrimaryKey)
{
this.recordPrimaryKey = recordPrimaryKey;
return (this);
}
/******************************************************************************* /*******************************************************************************
** Getter for inputValues ** Getter for inputValues
** **
*******************************************************************************/ *******************************************************************************/
public Map<String, String> getInputValues() public Map<String, Serializable> getInputValues()
{ {
return inputValues; return inputValues;
} }
@ -99,7 +64,7 @@ public class TestScriptInput 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;
} }
@ -110,7 +75,7 @@ public class TestScriptInput extends AbstractTableActionInput
** Fluent setter for inputValues ** Fluent setter for inputValues
** **
*******************************************************************************/ *******************************************************************************/
public TestScriptInput withInputValues(Map<String, String> inputValues) public TestScriptInput withInputValues(Map<String, Serializable> inputValues)
{ {
this.inputValues = inputValues; this.inputValues = inputValues;
return (this); return (this);
@ -119,68 +84,34 @@ public class TestScriptInput extends AbstractTableActionInput
/******************************************************************************* /*******************************************************************************
** Getter for code ** Getter for codeReference
** **
*******************************************************************************/ *******************************************************************************/
public String getCode() public QCodeReference getCodeReference()
{ {
return code; return codeReference;
} }
/******************************************************************************* /*******************************************************************************
** Setter for code ** Setter for codeReference
** **
*******************************************************************************/ *******************************************************************************/
public void setCode(String code) public void setCodeReference(QCodeReference codeReference)
{ {
this.code = code; this.codeReference = codeReference;
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for code ** Fluent setter for codeReference
** **
*******************************************************************************/ *******************************************************************************/
public TestScriptInput withCode(String code) public TestScriptInput withCodeReference(QCodeReference codeReference)
{ {
this.code = code; this.codeReference = codeReference;
return (this);
}
/*******************************************************************************
** Getter for scriptTypeId
**
*******************************************************************************/
public Serializable getScriptTypeId()
{
return scriptTypeId;
}
/*******************************************************************************
** Setter for scriptTypeId
**
*******************************************************************************/
public void setScriptTypeId(Serializable scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
}
/*******************************************************************************
** Fluent setter for scriptTypeId
**
*******************************************************************************/
public TestScriptInput withScriptTypeId(Serializable scriptTypeId)
{
this.scriptTypeId = scriptTypeId;
return (this); return (this);
} }

View File

@ -22,7 +22,10 @@
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.List;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/******************************************************************************* /*******************************************************************************
@ -30,4 +33,103 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
*******************************************************************************/ *******************************************************************************/
public class TestScriptOutput extends AbstractActionOutput public class TestScriptOutput extends AbstractActionOutput
{ {
private Serializable outputObject;
private Exception exception;
private QRecord scriptLog;
private List<QRecord> scriptLogLines;
/*******************************************************************************
** Getter for outputObject
**
*******************************************************************************/
public Serializable getOutputObject()
{
return outputObject;
}
/*******************************************************************************
** Setter for outputObject
**
*******************************************************************************/
public void setOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
}
/*******************************************************************************
** Fluent setter for outputObject
**
*******************************************************************************/
public TestScriptOutput withOutputObject(Serializable outputObject)
{
this.outputObject = outputObject;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void setException(Exception exception)
{
this.exception = exception;
}
/*******************************************************************************
**
*******************************************************************************/
public Exception getException()
{
return exception;
}
/*******************************************************************************
**
*******************************************************************************/
public void setScriptLog(QRecord scriptLog)
{
this.scriptLog = scriptLog;
}
/*******************************************************************************
**
*******************************************************************************/
public QRecord getScriptLog()
{
return scriptLog;
}
/*******************************************************************************
**
*******************************************************************************/
public void setScriptLogLines(List<QRecord> scriptLogLines)
{
this.scriptLogLines = scriptLogLines;
}
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> getScriptLogLines()
{
return scriptLogLines;
}
} }

View File

@ -32,5 +32,6 @@ public enum QCodeUsage
CUSTOMIZER, // a function to customize part of a QQQ table's behavior CUSTOMIZER, // a function to customize part of a QQQ table's behavior
POSSIBLE_VALUE_PROVIDER, // code that drives a custom possibleValueSource POSSIBLE_VALUE_PROVIDER, // code that drives a custom possibleValueSource
RECORD_AUTOMATION_HANDLER, // code that executes record automations RECORD_AUTOMATION_HANDLER, // code that executes record automations
REPORT_STATIC_DATA_SUPPLIER // code that supplies static data to a report REPORT_STATIC_DATA_SUPPLIER, // code that supplies static data to a report
SCRIPT_TESTER // class that is used to test scripts.
} }

View File

@ -98,4 +98,20 @@ public enum AdornmentType
String XLARGE = "xlarge"; String XLARGE = "xlarge";
} }
/*******************************************************************************
**
*******************************************************************************/
public interface CodeEditorValues
{
/*******************************************************************************
**
*******************************************************************************/
static Pair<String, Serializable> languageMode(String languageMode)
{
return (new Pair<>("languageMode", languageMode));
}
}
} }

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
import java.io.Serializable; import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/******************************************************************************* /*******************************************************************************
@ -30,8 +31,9 @@ import java.io.Serializable;
*******************************************************************************/ *******************************************************************************/
public class AssociatedScript implements Serializable public class AssociatedScript implements Serializable
{ {
private String fieldName; private String fieldName;
private Serializable scriptTypeId; private Serializable scriptTypeId;
private QCodeReference scriptTester;
@ -101,4 +103,38 @@ public class AssociatedScript implements Serializable
return (this); return (this);
} }
/*******************************************************************************
** Getter for scriptTester
**
*******************************************************************************/
public QCodeReference getScriptTester()
{
return scriptTester;
}
/*******************************************************************************
** Setter for scriptTester
**
*******************************************************************************/
public void setScriptTester(QCodeReference scriptTester)
{
this.scriptTester = scriptTester;
}
/*******************************************************************************
** Fluent setter for scriptTester
**
*******************************************************************************/
public AssociatedScript withScriptTester(QCodeReference scriptTester)
{
this.scriptTester = scriptTester;
return (this);
}
} }

View File

@ -36,13 +36,13 @@ public class Script extends QRecordEntity
{ {
public static final String TABLE_NAME = "script"; public static final String TABLE_NAME = "script";
@QField() @QField(isEditable = false)
private Integer id; private Integer id;
@QField() @QField(isEditable = false)
private Instant createDate; private Instant createDate;
@QField() @QField(isEditable = false)
private Instant modifyDate; private Instant modifyDate;
@QField() @QField()

View File

@ -35,13 +35,13 @@ public class ScriptLog extends QRecordEntity
{ {
public static final String TABLE_NAME = "scriptLog"; public static final String TABLE_NAME = "scriptLog";
@QField() @QField(isEditable = false)
private Integer id; private Integer id;
@QField() @QField(isEditable = false)
private Instant createDate; private Instant createDate;
@QField() @QField(isEditable = false)
private Instant modifyDate; private Instant modifyDate;
@QField(possibleValueSourceName = "script") @QField(possibleValueSourceName = "script")

View File

@ -34,13 +34,13 @@ public class ScriptLogLine extends QRecordEntity
{ {
public static final String TABLE_NAME = "scriptLogLine"; public static final String TABLE_NAME = "scriptLogLine";
@QField() @QField(isEditable = false)
private Integer id; private Integer id;
@QField() @QField(isEditable = false)
private Instant createDate; private Instant createDate;
@QField() @QField(isEditable = false)
private Instant modifyDate; private Instant modifyDate;
@QField(possibleValueSourceName = "scriptLog") @QField(possibleValueSourceName = "scriptLog")

View File

@ -36,13 +36,13 @@ public class ScriptRevision extends QRecordEntity
{ {
public static final String TABLE_NAME = "scriptRevision"; public static final String TABLE_NAME = "scriptRevision";
@QField() @QField(isEditable = false)
private Integer id; private Integer id;
@QField() @QField(isEditable = false)
private Instant createDate; private Instant createDate;
@QField() @QField(isEditable = false)
private Instant modifyDate; private Instant modifyDate;
@QField(possibleValueSourceName = "script") @QField(possibleValueSourceName = "script")

View File

@ -34,13 +34,13 @@ public class ScriptType extends QRecordEntity
{ {
public static final String TABLE_NAME = "scriptType"; public static final String TABLE_NAME = "scriptType";
@QField() @QField(isEditable = false)
private Integer id; private Integer id;
@QField() @QField(isEditable = false)
private Instant createDate; private Instant createDate;
@QField() @QField(isEditable = false)
private Instant modifyDate; private Instant modifyDate;
@QField() @QField()

View File

@ -163,7 +163,8 @@ public class ScriptsMetaDataProvider
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name"))) .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("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"))); .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
tableMetaData.getField("sampleCode").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR)); tableMetaData.getField("sampleCode").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("javascript")));
tableMetaData.getField("helpText").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("text")));
return (tableMetaData); return (tableMetaData);
} }

View File

@ -52,7 +52,7 @@ public class BasicETLExtractFunction implements BackendStep
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
String tableName = runBackendStepInput.getValueString(BasicETLProcess.FIELD_SOURCE_TABLE); String tableName = runBackendStepInput.getValueString(BasicETLProcess.FIELD_SOURCE_TABLE);
LOG.info("Start query on table: " + tableName); LOG.debug("Start query on table: " + tableName);
QueryInput queryInput = new QueryInput(runBackendStepInput.getInstance()); QueryInput queryInput = new QueryInput(runBackendStepInput.getInstance());
queryInput.setSession(runBackendStepInput.getSession()); queryInput.setSession(runBackendStepInput.getSession());
@ -70,7 +70,7 @@ public class BasicETLExtractFunction implements BackendStep
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// if the caller gave us a record pipe, pass it to the query action // // if the caller gave us a record pipe, pass it to the query action //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
if (recordPipe != null) if(recordPipe != null)
{ {
queryInput.setRecordPipe(recordPipe); queryInput.setRecordPipe(recordPipe);
} }
@ -78,7 +78,7 @@ public class BasicETLExtractFunction implements BackendStep
QueryAction queryAction = new QueryAction(); QueryAction queryAction = new QueryAction();
QueryOutput queryOutput = queryAction.execute(queryInput); QueryOutput queryOutput = queryAction.execute(queryInput);
if (recordPipe == null) if(recordPipe == null)
{ {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// only return the records (and log about them) if there's no record pipe // // only return the records (and log about them) if there's no record pipe //

View File

@ -60,13 +60,14 @@ public class BasicETLLoadFunction implements BackendStep
// exit early with no-op if no records made it here // // exit early with no-op if no records made it here //
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
List<QRecord> inputRecords = runBackendStepInput.getRecords(); List<QRecord> inputRecords = runBackendStepInput.getRecords();
LOG.info("Received [" + inputRecords.size() + "] records to load");
if(CollectionUtils.nullSafeIsEmpty(inputRecords)) if(CollectionUtils.nullSafeIsEmpty(inputRecords))
{ {
runBackendStepOutput.addValue(BasicETLProcess.FIELD_RECORD_COUNT, 0); runBackendStepOutput.addValue(BasicETLProcess.FIELD_RECORD_COUNT, 0);
return; return;
} }
LOG.info("Received [" + inputRecords.size() + "] records to load");
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
// put the destination table name in all records being inserted // // put the destination table name in all records being inserted //
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////

View File

@ -59,21 +59,21 @@ public class BasicETLTransformFunction implements BackendStep
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{ {
String tableName = runBackendStepInput.getValueString(BasicETLProcess.FIELD_DESTINATION_TABLE); String tableName = runBackendStepInput.getValueString(BasicETLProcess.FIELD_DESTINATION_TABLE);
LOG.info("Start transform for destination table: " + tableName); LOG.debug("Start transform for destination table: " + tableName);
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
// exit early with no-op if no records made it here, or if we don't have a mapping to use // // exit early with no-op if no records made it here, or if we don't have a mapping to use //
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords())) if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
{ {
LOG.info("Exiting early with no-op for empty input record list."); LOG.debug("Exiting early with no-op for empty input record list.");
return; return;
} }
String mappingJSON = runBackendStepInput.getValueString(BasicETLProcess.FIELD_MAPPING_JSON); String mappingJSON = runBackendStepInput.getValueString(BasicETLProcess.FIELD_MAPPING_JSON);
if(!StringUtils.hasContent(mappingJSON)) if(!StringUtils.hasContent(mappingJSON))
{ {
LOG.info("Exiting early with no-op for empty mappingJSON."); LOG.debug("Exiting early with no-op for empty mappingJSON.");
return; return;
} }

View File

@ -0,0 +1,222 @@
/*
* 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;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
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.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for TestScriptActionInterface
*******************************************************************************/
class TestScriptActionInterfaceTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
QInstance qInstance = TestUtils.defineInstance();
TestScriptInput testScriptInput = new TestScriptInput(qInstance);
testScriptInput.setSession(new QSession());
testScriptInput.setInputValues(Map.of("name", "Darin", "age", 42));
testScriptInput.setCodeReference(new QCodeReference(SampleScript.class, QCodeUsage.CUSTOMIZER));
TestScriptOutput testScriptOutput = new TestScriptOutput();
new SampleTestAction().execute(testScriptInput, testScriptOutput);
assertNull(testScriptOutput.getException());
assertTrue(testScriptOutput.getOutputObject() instanceof SampleTestOutput);
SampleTestOutput sampleTestOutput = (SampleTestOutput) testScriptOutput.getOutputObject();
assertEquals("Darin is 42 years old!", sampleTestOutput.getMessage());
}
/*******************************************************************************
**
*******************************************************************************/
public static class SampleScript implements Function<Map<String, Object>, Serializable>
{
@Override
public Serializable apply(Map<String, Object> context)
{
SampleTestInput input = (SampleTestInput) context.get("input");
SampleTestOutput output = (SampleTestOutput) context.get("output");
output.setMessage(input.getName() + " is " + input.getAge() + " years old!");
return (output);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class SampleTestAction implements TestScriptActionInterface
{
@Override
public void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput)
{
SampleTestInput sampleTestInput = new SampleTestInput();
sampleTestInput.setName(ValueUtils.getValueAsString(testScriptInput.getInputValues().get("name")));
sampleTestInput.setAge(ValueUtils.getValueAsInteger(testScriptInput.getInputValues().get("age")));
executeCodeInput.setInput(Map.of("input", sampleTestInput));
executeCodeInput.getContext().put("output", new SampleTestOutput());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QFieldMetaData> getTestInputFields()
{
return List.of(
new QFieldMetaData("name", QFieldType.STRING),
new QFieldMetaData("age", QFieldType.INTEGER)
);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QFieldMetaData> getTestOutputFields()
{
return List.of(new QFieldMetaData("message", QFieldType.STRING));
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class SampleTestInput implements Serializable
{
private String name;
private Integer age;
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Getter for age
**
*******************************************************************************/
public Integer getAge()
{
return age;
}
/*******************************************************************************
** Setter for age
**
*******************************************************************************/
public void setAge(Integer age)
{
this.age = age;
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class SampleTestOutput implements Serializable
{
private String message;
/*******************************************************************************
** Getter for message
**
*******************************************************************************/
public String getMessage()
{
return message;
}
/*******************************************************************************
** Setter for message
**
*******************************************************************************/
public void setMessage(String message)
{
this.message = message;
}
}
}

View File

@ -1,52 +0,0 @@
/*
* 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;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for TestScriptAction
*******************************************************************************/
class TestScriptActionTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
@Disabled("Not yet done.")
void test() throws QException
{
QInstance instance = TestUtils.defineInstance();
TestScriptInput input = new TestScriptInput(instance);
TestScriptOutput output = new TestScriptOutput();
new TestScriptAction().run(input, output);
}
}

View File

@ -57,7 +57,7 @@ public class BasicETLCleanupSourceFilesStep implements BackendStep
public static final String FIELD_MOVE_OR_DELETE = "moveOrDelete"; public static final String FIELD_MOVE_OR_DELETE = "moveOrDelete";
public static final String FIELD_DESTINATION_FOR_MOVES = "destinationForMoves"; public static final String FIELD_DESTINATION_FOR_MOVES = "destinationForMoves";
public static final String VALUE_MOVE = "move"; public static final String VALUE_MOVE = "move";
public static final String VALUE_DELETE = "delete"; public static final String VALUE_DELETE = "delete";
public static final String STEP_NAME = "cleanupSourceFiles"; public static final String STEP_NAME = "cleanupSourceFiles";
@ -84,7 +84,7 @@ public class BasicETLCleanupSourceFilesStep implements BackendStep
String sourceFilePaths = runBackendStepInput.getValueString(BasicETLCollectSourceFileNamesStep.FIELD_SOURCE_FILE_PATHS); String sourceFilePaths = runBackendStepInput.getValueString(BasicETLCollectSourceFileNamesStep.FIELD_SOURCE_FILE_PATHS);
if(!StringUtils.hasContent(sourceFilePaths)) if(!StringUtils.hasContent(sourceFilePaths))
{ {
LOG.info("No source file paths were specified in field [" + BasicETLCollectSourceFileNamesStep.FIELD_SOURCE_FILE_PATHS + "]"); LOG.debug("No source file paths were specified in field [" + BasicETLCollectSourceFileNamesStep.FIELD_SOURCE_FILE_PATHS + "]");
return; return;
} }

View File

@ -49,6 +49,12 @@
<version>${revision}</version> <version>${revision}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-language-support-javascript</artifactId>
<version>${revision}</version>
<scope>test</scope>
</dependency>
<!-- 3rd party deps specifically for this module --> <!-- 3rd party deps specifically for this module -->
<dependency> <dependency>

View File

@ -42,8 +42,6 @@ import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
import com.kingsrook.qqq.backend.core.actions.metadata.ProcessMetaDataAction; import com.kingsrook.qqq.backend.core.actions.metadata.ProcessMetaDataAction;
import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction; import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction;
import com.kingsrook.qqq.backend.core.actions.reporting.ExportAction; import com.kingsrook.qqq.backend.core.actions.reporting.ExportAction;
import com.kingsrook.qqq.backend.core.actions.scripts.StoreAssociatedScriptAction;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptAction;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction; import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
@ -68,10 +66,6 @@ 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.actions.reporting.ExportInput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
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.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput; import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
@ -80,9 +74,6 @@ 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.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; 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.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; 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.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
@ -95,13 +86,11 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
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;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule; import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
import com.kingsrook.qqq.backend.core.processes.utils.GeneralProcessUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils;
@ -109,7 +98,6 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.apibuilder.EndpointGroup; import io.javalin.apibuilder.EndpointGroup;
import io.javalin.http.ContentType;
import io.javalin.http.Context; import io.javalin.http.Context;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -308,10 +296,7 @@ public class QJavalinImplementation
put("", QJavalinImplementation::dataUpdate); // todo - want different semantics?? put("", QJavalinImplementation::dataUpdate); // todo - want different semantics??
delete("", QJavalinImplementation::dataDelete); delete("", QJavalinImplementation::dataDelete);
get("/developer", QJavalinImplementation::getRecordDeveloperMode); QJavalinScriptsHandler.defineRecordRoutes();
post("/developer/associatedScript/{fieldName}", QJavalinImplementation::storeRecordAssociatedScript);
get("/developer/associatedScript/{fieldName}/{scriptRevisionId}/logs", QJavalinImplementation::getAssociatedScriptLogs);
post("/developer/associatedScript/{fieldName}/test", QJavalinImplementation::testAssociatedScript);
}); });
}); });
@ -986,213 +971,6 @@ public class QJavalinImplementation
/*******************************************************************************
**
*******************************************************************************/
private static void getRecordDeveloperMode(Context context)
{
try
{
String tableName = context.pathParam("table");
QTableMetaData table = qInstance.getTable(tableName);
String primaryKey = context.pathParam("primaryKey");
GetInput getInput = new GetInput(qInstance);
setupSession(context, getInput);
getInput.setTableName(tableName);
getInput.setShouldGenerateDisplayValues(true);
getInput.setShouldTranslatePossibleValues(true);
// todo - validate that the primary key is of the proper type (e.g,. not a string for an id field)
// and throw a 400-series error (tell the user bad-request), rather than, we're doing a 500 (server error)
getInput.setPrimaryKey(primaryKey);
GetAction getAction = new GetAction();
GetOutput getOutput = getAction.execute(getInput);
///////////////////////////////////////////////////////
// throw a not found error if the record isn't found //
///////////////////////////////////////////////////////
QRecord record = getOutput.getRecord();
if(record == null)
{
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
}
Map<String, Serializable> rs = new HashMap<>();
rs.put("record", record);
ArrayList<HashMap<String, Serializable>> associatedScripts = new ArrayList<>();
rs.put("associatedScripts", associatedScripts);
///////////////////////////////////////////////////////
// process each associated script type for the table //
///////////////////////////////////////////////////////
for(AssociatedScript associatedScript : CollectionUtils.nonNullList(table.getAssociatedScripts()))
{
HashMap<String, Serializable> thisScriptData = new HashMap<>();
associatedScripts.add(thisScriptData);
thisScriptData.put("associatedScript", associatedScript);
String fieldName = associatedScript.getFieldName();
Serializable scriptId = record.getValue(fieldName);
if(scriptId != null)
{
GetInput getScriptInput = new GetInput(qInstance);
setupSession(context, getScriptInput);
getScriptInput.setTableName("script");
getScriptInput.setPrimaryKey(scriptId);
GetOutput getScriptOutput = new GetAction().execute(getScriptInput);
if(getScriptOutput.getRecord() != null)
{
thisScriptData.put("script", getScriptOutput.getRecord());
QueryInput queryInput = new QueryInput(qInstance);
setupSession(context, queryInput);
queryInput.setTableName("scriptRevision");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, List.of(getScriptOutput.getRecord().getValue("id"))))
.withOrderBy(new QFilterOrderBy("id", false))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
thisScriptData.put("scriptRevisions", new ArrayList<>(queryOutput.getRecords()));
}
}
}
context.result(JsonUtils.toJson(rs));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void getAssociatedScriptLogs(Context context)
{
try
{
String scriptRevisionId = context.pathParam("scriptRevisionId");
QueryInput queryInput = new QueryInput(qInstance);
setupSession(context, queryInput);
queryInput.setTableName("scriptLog");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("scriptRevisionId", QCriteriaOperator.EQUALS, List.of(scriptRevisionId)))
.withOrderBy(new QFilterOrderBy("id", false))
);
queryInput.setLimit(100);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
{
GeneralProcessUtils.addForeignRecordsListToRecordList(queryInput, queryOutput.getRecords(), "id", "scriptLogLine", "scriptLogId");
}
Map<String, Serializable> rs = new HashMap<>();
rs.put("scriptLogRecords", new ArrayList<>(queryOutput.getRecords()));
context.result(JsonUtils.toJson(rs));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void storeRecordAssociatedScript(Context context)
{
context.contentType(ContentType.APPLICATION_JSON);
try
{
StoreAssociatedScriptInput input = new StoreAssociatedScriptInput(qInstance);
setupSession(context, input);
input.setCode(context.formParam("contents"));
input.setCommitMessage(context.formParam("commitMessage"));
input.setFieldName(context.pathParam("fieldName"));
input.setTableName(context.pathParam("table"));
input.setRecordPrimaryKey(context.pathParam("primaryKey"));
StoreAssociatedScriptOutput output = new StoreAssociatedScriptOutput();
StoreAssociatedScriptAction storeAssociatedScriptAction = new StoreAssociatedScriptAction();
storeAssociatedScriptAction.run(input, output);
context.result(JsonUtils.toJson(output));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void testAssociatedScript(Context context)
{
try
{
TestScriptInput input = new TestScriptInput(qInstance);
setupSession(context, input);
input.setTableName(context.pathParam("table"));
input.setRecordPrimaryKey(context.pathParam("primaryKey"));
// context.pathParam("fieldName");
Map<String, String> inputValues = new HashMap<>();
input.setInputValues(inputValues);
for(Map.Entry<String, List<String>> entry : context.formParamMap().entrySet())
{
String key = entry.getKey();
String value = entry.getValue().get(0);
if(key.equals("code"))
{
input.setCode(value);
}
else if(key.equals("scriptTypeId"))
{
input.setScriptTypeId(ValueUtils.getValueAsInteger(value));
}
else
{
inputValues.put(key, value);
}
}
TestScriptOutput output = new TestScriptOutput();
new TestScriptAction().run(input, output);
//////////////////////////////////////////////////////////////
// todo - output to frontend - then add assertions in test. //
//////////////////////////////////////////////////////////////
context.result(JsonUtils.toJson("OK"));
}
catch(Exception e)
{
handleException(context, e);
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -174,10 +174,10 @@ public class QJavalinProcessHandler
return (new RunProcessAction().execute(runProcessInput)); return (new RunProcessAction().execute(runProcessInput));
}); });
LOG.info("Process result error? " + runProcessOutput.getException()); LOG.debug("Process result error? " + runProcessOutput.getException());
for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessInput.getProcessName()).getOutputFields()) for(QFieldMetaData outputField : QJavalinImplementation.qInstance.getProcess(runProcessInput.getProcessName()).getOutputFields())
{ {
LOG.info("Process result output value: " + outputField.getName() + ": " + runProcessOutput.getValues().get(outputField.getName())); LOG.debug("Process result output value: " + outputField.getName() + ": " + runProcessOutput.getValues().get(outputField.getName()));
} }
serializeRunProcessResultForCaller(resultForCaller, runProcessOutput); serializeRunProcessResultForCaller(resultForCaller, runProcessOutput);

View File

@ -0,0 +1,341 @@
/*
* 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.javalin;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.scripts.StoreAssociatedScriptAction;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
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.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptOutput;
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.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
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.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptType;
import com.kingsrook.qqq.backend.core.processes.utils.GeneralProcessUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import static io.javalin.apibuilder.ApiBuilder.get;
import static io.javalin.apibuilder.ApiBuilder.post;
/*******************************************************************************
** endpoints and handlers for deal with record scripts
*******************************************************************************/
public class QJavalinScriptsHandler
{
/*******************************************************************************
** Define routes under the basic /data/${table}/${primaryKey} path - e.g.,
** record-specific script routes.
*******************************************************************************/
public static void defineRecordRoutes()
{
get("/developer", QJavalinScriptsHandler::getRecordDeveloperMode);
post("/developer/associatedScript/{fieldName}", QJavalinScriptsHandler::storeRecordAssociatedScript);
get("/developer/associatedScript/{fieldName}/{scriptRevisionId}/logs", QJavalinScriptsHandler::getAssociatedScriptLogs);
post("/developer/associatedScript/{fieldName}/test", QJavalinScriptsHandler::testAssociatedScript);
}
/*******************************************************************************
**
*******************************************************************************/
private static void getRecordDeveloperMode(Context context)
{
try
{
String tableName = context.pathParam("table");
QTableMetaData table = QJavalinImplementation.qInstance.getTable(tableName);
String primaryKey = context.pathParam("primaryKey");
GetInput getInput = new GetInput(QJavalinImplementation.qInstance);
QJavalinImplementation.setupSession(context, getInput);
getInput.setTableName(tableName);
getInput.setShouldGenerateDisplayValues(true);
getInput.setShouldTranslatePossibleValues(true);
// todo - validate that the primary key is of the proper type (e.g,. not a string for an id field)
// and throw a 400-series error (tell the user bad-request), rather than, we're doing a 500 (server error)
getInput.setPrimaryKey(primaryKey);
GetAction getAction = new GetAction();
GetOutput getOutput = getAction.execute(getInput);
///////////////////////////////////////////////////////
// throw a not found error if the record isn't found //
///////////////////////////////////////////////////////
QRecord record = getOutput.getRecord();
if(record == null)
{
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
}
Map<String, Serializable> rs = new HashMap<>();
rs.put("record", record);
ArrayList<HashMap<String, Serializable>> associatedScripts = new ArrayList<>();
rs.put("associatedScripts", associatedScripts);
Map<Serializable, QRecord> scriptTypeMap = GeneralProcessUtils.loadTableToMap(getInput, ScriptType.TABLE_NAME, "id");
///////////////////////////////////////////////////////
// process each associated script type for the table //
///////////////////////////////////////////////////////
QInstanceEnricher qInstanceEnricher = new QInstanceEnricher(QJavalinImplementation.qInstance);
for(AssociatedScript associatedScript : CollectionUtils.nonNullList(table.getAssociatedScripts()))
{
HashMap<String, Serializable> thisScriptData = new HashMap<>();
associatedScripts.add(thisScriptData);
thisScriptData.put("associatedScript", associatedScript);
thisScriptData.put("scriptType", scriptTypeMap.get(associatedScript.getScriptTypeId()));
/////////////////////////////////////////////////////////////////////
// load the associated script and current revision from the record //
/////////////////////////////////////////////////////////////////////
String fieldName = associatedScript.getFieldName();
Serializable scriptId = record.getValue(fieldName);
if(scriptId != null)
{
GetInput getScriptInput = new GetInput(QJavalinImplementation.qInstance);
QJavalinImplementation.setupSession(context, getScriptInput);
getScriptInput.setTableName("script");
getScriptInput.setPrimaryKey(scriptId);
GetOutput getScriptOutput = new GetAction().execute(getScriptInput);
if(getScriptOutput.getRecord() != null)
{
thisScriptData.put("script", getScriptOutput.getRecord());
QueryInput queryInput = new QueryInput(QJavalinImplementation.qInstance);
QJavalinImplementation.setupSession(context, queryInput);
queryInput.setTableName("scriptRevision");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, List.of(getScriptOutput.getRecord().getValue("id"))))
.withOrderBy(new QFilterOrderBy("id", false))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
thisScriptData.put("scriptRevisions", new ArrayList<>(queryOutput.getRecords()));
}
}
///////////////////////////////////////////////////////////
// load testing info about the script type, if available //
///////////////////////////////////////////////////////////
QCodeReference scriptTesterCodeRef = associatedScript.getScriptTester();
if(scriptTesterCodeRef != null)
{
TestScriptActionInterface scriptTester = QCodeLoader.getAdHoc(TestScriptActionInterface.class, scriptTesterCodeRef);
thisScriptData.put("testInputFields", enrichFieldsToArrayList(qInstanceEnricher, scriptTester.getTestInputFields()));
thisScriptData.put("testOutputFields", enrichFieldsToArrayList(qInstanceEnricher, scriptTester.getTestOutputFields()));
}
}
context.result(JsonUtils.toJson(rs));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static Serializable enrichFieldsToArrayList(QInstanceEnricher qInstanceEnricher, List<QFieldMetaData> fields)
{
ArrayList<QFieldMetaData> rs = new ArrayList<>();
if(CollectionUtils.nullSafeIsEmpty(fields))
{
return (rs);
}
for(QFieldMetaData field : fields)
{
qInstanceEnricher.enrichField(field);
rs.add(field);
}
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/
public static void getAssociatedScriptLogs(Context context)
{
try
{
String scriptRevisionId = context.pathParam("scriptRevisionId");
QueryInput queryInput = new QueryInput(QJavalinImplementation.qInstance);
QJavalinImplementation.setupSession(context, queryInput);
queryInput.setTableName("scriptLog");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("scriptRevisionId", QCriteriaOperator.EQUALS, List.of(scriptRevisionId)))
.withOrderBy(new QFilterOrderBy("id", false))
);
queryInput.setLimit(100);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
{
GeneralProcessUtils.addForeignRecordsListToRecordList(queryInput, queryOutput.getRecords(), "id", "scriptLogLine", "scriptLogId");
}
Map<String, Serializable> rs = new HashMap<>();
rs.put("scriptLogRecords", new ArrayList<>(queryOutput.getRecords()));
context.result(JsonUtils.toJson(rs));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void storeRecordAssociatedScript(Context context)
{
context.contentType(ContentType.APPLICATION_JSON);
try
{
StoreAssociatedScriptInput input = new StoreAssociatedScriptInput(QJavalinImplementation.qInstance);
QJavalinImplementation.setupSession(context, input);
input.setCode(context.formParam("contents"));
input.setCommitMessage(context.formParam("commitMessage"));
input.setFieldName(context.pathParam("fieldName"));
input.setTableName(context.pathParam("table"));
input.setRecordPrimaryKey(context.pathParam("primaryKey"));
StoreAssociatedScriptOutput output = new StoreAssociatedScriptOutput();
StoreAssociatedScriptAction storeAssociatedScriptAction = new StoreAssociatedScriptAction();
storeAssociatedScriptAction.run(input, output);
context.result(JsonUtils.toJson(output));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void testAssociatedScript(Context context)
{
context.contentType(ContentType.APPLICATION_JSON);
try
{
TestScriptInput input = new TestScriptInput(QJavalinImplementation.qInstance);
QJavalinImplementation.setupSession(context, input);
// todo delete? input.setRecordPrimaryKey(context.pathParam("primaryKey"));
Map<String, Serializable> inputValues = new HashMap<>();
input.setInputValues(inputValues);
String tableName = context.pathParam("table");
String fieldName = context.pathParam("fieldName");
QTableMetaData table = QJavalinImplementation.qInstance.getTable(tableName);
Optional<AssociatedScript> optionalAssociatedScript = table.getAssociatedScripts().stream().filter(as -> as.getFieldName().equals(fieldName)).findFirst();
if(optionalAssociatedScript.isEmpty())
{
throw new IllegalArgumentException("No associated script was found for field " + fieldName + " on table " + tableName);
}
AssociatedScript associatedScript = optionalAssociatedScript.get();
QCodeReference scriptTesterCodeRef = associatedScript.getScriptTester();
if(scriptTesterCodeRef == null)
{
throw (new IllegalArgumentException("This scriptType cannot be tested, as it does not define a scriptTester codeReference."));
}
for(Map.Entry<String, List<String>> entry : context.formParamMap().entrySet())
{
String key = entry.getKey();
String value = entry.getValue().get(0);
if(key.equals("code"))
{
input.setCodeReference(new QCodeReference().withInlineCode(value).withCodeType(QCodeType.JAVA_SCRIPT));
}
else
{
inputValues.put(key, value);
}
}
TestScriptActionInterface scriptTester = QCodeLoader.getAdHoc(TestScriptActionInterface.class, scriptTesterCodeRef);
TestScriptOutput output = new TestScriptOutput();
scriptTester.execute(input, output);
context.result(JsonUtils.toJson(output));
}
catch(Exception e)
{
QJavalinImplementation.handleException(context, e);
}
}
}

View File

@ -26,23 +26,9 @@ import java.io.Serializable;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
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.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType; import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import kong.unirest.HttpResponse; import kong.unirest.HttpResponse;
import kong.unirest.Unirest; import kong.unirest.Unirest;
@ -630,135 +616,4 @@ class QJavalinImplementationTest extends QJavalinTestBase
assertEquals(5, jsonObject.getJSONArray("options").getJSONObject(1).getInt("id")); assertEquals(5, jsonObject.getJSONArray("options").getJSONObject(1).getInt("id"));
} }
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGetRecordDeveloperMode() throws QException
{
UpdateInput updateInput = new UpdateInput(TestUtils.defineInstance());
updateInput.setSession(new QSession());
updateInput.setTableName("person");
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("testScriptId", 47)));
new UpdateAction().execute(updateInput);
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("script");
insertInput.setRecords(List.of(new QRecord().withValue("id", 47).withValue("currentScriptRevisionId", 100)));
new InsertAction().execute(insertInput);
insertInput.setTableName("scriptRevision");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1000).withValue("scriptId", 47).withValue("content", "var i;")));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/1/developer").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
System.out.println(jsonObject.toString(3));
assertNotNull(jsonObject);
assertNotNull(jsonObject.getJSONObject("record"));
assertEquals("Darin", jsonObject.getJSONObject("record").getJSONObject("values").getString("firstName"));
assertEquals("Darin", jsonObject.getJSONObject("record").getJSONObject("displayValues").getString("firstName"));
assertNotNull(jsonObject.getJSONArray("associatedScripts"));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONArray("scriptRevisions"));
assertEquals("var i;", jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONArray("scriptRevisions").getJSONObject(0).getJSONObject("values").getString("content"));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("script"));
assertEquals(100, jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("script").getJSONObject("values").getInt("currentScriptRevisionId"));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("associatedScript"));
assertEquals("testScriptId", jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("associatedScript").getString("fieldName"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testStoreRecordAssociatedScript() throws QException
{
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("scriptType");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("name", "Test")));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.post(BASE_URL + "/data/person/1/developer/associatedScript/testScriptId")
.field("contents", "var j = 0;")
.field("commitMessage", "Javalin Commit")
.asString();
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
queryInput.setSession(new QSession());
queryInput.setTableName("scriptRevision");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("contents", QCriteriaOperator.EQUALS, List.of("var j = 0;")))
.withCriteria(new QFilterCriteria("commitMessage", QCriteriaOperator.EQUALS, List.of("Javalin Commit")))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
assertEquals(1, queryOutput.getRecords().size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTestAssociatedScript() throws QException
{
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("scriptType");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("name", "Test")));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.post(BASE_URL + "/data/person/1/developer/associatedScript/testScriptId/test")
.field("code", "var j = 0;")
.field("scriptTypeId", "1")
.field("x", "47")
.asString();
/////////////////////////////////////////
// todo - assertions after implemented //
/////////////////////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGetAssociatedScriptLogs() throws QException
{
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("scriptLog");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("output", "testOutput").withValue("scriptRevisionId", 100)));
new InsertAction().execute(insertInput);
insertInput.setTableName("scriptLogLine");
insertInput.setRecords(List.of(
new QRecord().withValue("scriptLogId", 1).withValue("text", "line one"),
new QRecord().withValue("scriptLogId", 1).withValue("text", "line two")
));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/1/developer/associatedScript/testScriptId/100/logs").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject.getJSONArray("scriptLogRecords"));
assertEquals(1, jsonObject.getJSONArray("scriptLogRecords").length());
assertNotNull(jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values"));
assertEquals("testOutput", jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getString("output"));
assertNotNull(jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine"));
assertEquals(2, jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine").length());
assertEquals("line one", jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine").getJSONObject(0).getJSONObject("values").getString("text"));
assertEquals("line two", jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine").getJSONObject(1).getJSONObject("values").getString("text"));
}
} }

View File

@ -0,0 +1,187 @@
/*
* 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.javalin;
import java.util.List;
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.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for the javalin scripts handler methods.
*******************************************************************************/
class QJavalinScriptsHandlerTest extends QJavalinTestBase
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGetRecordDeveloperMode() throws QException
{
UpdateInput updateInput = new UpdateInput(TestUtils.defineInstance());
updateInput.setSession(new QSession());
updateInput.setTableName("person");
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("testScriptId", 47)));
new UpdateAction().execute(updateInput);
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("script");
insertInput.setRecords(List.of(new QRecord().withValue("id", 47).withValue("currentScriptRevisionId", 100)));
new InsertAction().execute(insertInput);
insertInput.setTableName("scriptRevision");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1000).withValue("scriptId", 47).withValue("content", "var i;")));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/1/developer").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
System.out.println(jsonObject.toString(3));
assertNotNull(jsonObject);
assertNotNull(jsonObject.getJSONObject("record"));
assertEquals("Darin", jsonObject.getJSONObject("record").getJSONObject("values").getString("firstName"));
assertEquals("Darin", jsonObject.getJSONObject("record").getJSONObject("displayValues").getString("firstName"));
assertNotNull(jsonObject.getJSONArray("associatedScripts"));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONArray("scriptRevisions"));
assertEquals("var i;", jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONArray("scriptRevisions").getJSONObject(0).getJSONObject("values").getString("content"));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("script"));
assertEquals(100, jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("script").getJSONObject("values").getInt("currentScriptRevisionId"));
assertNotNull(jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("associatedScript"));
assertEquals("testScriptId", jsonObject.getJSONArray("associatedScripts").getJSONObject(0).getJSONObject("associatedScript").getString("fieldName"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testStoreRecordAssociatedScript() throws QException
{
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("scriptType");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("name", "Test")));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.post(BASE_URL + "/data/person/1/developer/associatedScript/testScriptId")
.field("contents", "var j = 0;")
.field("commitMessage", "Javalin Commit")
.asString();
QueryInput queryInput = new QueryInput(TestUtils.defineInstance());
queryInput.setSession(new QSession());
queryInput.setTableName("scriptRevision");
queryInput.setFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("contents", QCriteriaOperator.EQUALS, List.of("var j = 0;")))
.withCriteria(new QFilterCriteria("commitMessage", QCriteriaOperator.EQUALS, List.of("Javalin Commit")))
);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
assertEquals(1, queryOutput.getRecords().size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testTestAssociatedScript() throws QException
{
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("scriptType");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("name", "Test")));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.post(BASE_URL + "/data/person/1/developer/associatedScript/testScriptId/test")
.field("code", """
// output.setMessage(`${input.getName()} is ${input.getAge()} years old.`);
output.setMessage("I am " + input.getName());
return (output);
""")
.field("scriptTypeId", "1")
.field("name", "Tim")
.asString();
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertEquals("I am Tim", jsonObject.getJSONObject("outputObject").getString("message"));
assertNotNull(jsonObject.getJSONObject("scriptLog"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGetAssociatedScriptLogs() throws QException
{
InsertInput insertInput = new InsertInput(TestUtils.defineInstance());
insertInput.setSession(new QSession());
insertInput.setTableName("scriptLog");
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("output", "testOutput").withValue("scriptRevisionId", 100)));
new InsertAction().execute(insertInput);
insertInput.setTableName("scriptLogLine");
insertInput.setRecords(List.of(
new QRecord().withValue("scriptLogId", 1).withValue("text", "line one"),
new QRecord().withValue("scriptLogId", 1).withValue("text", "line two")
));
new InsertAction().execute(insertInput);
HttpResponse<String> response = Unirest.get(BASE_URL + "/data/person/1/developer/associatedScript/testScriptId/100/logs").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject.getJSONArray("scriptLogRecords"));
assertEquals(1, jsonObject.getJSONArray("scriptLogRecords").length());
assertNotNull(jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values"));
assertEquals("testOutput", jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getString("output"));
assertNotNull(jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine"));
assertEquals(2, jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine").length());
assertEquals("line one", jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine").getJSONObject(0).getJSONObject("values").getString("text"));
assertEquals("line two", jsonObject.getJSONArray("scriptLogRecords").getJSONObject(0).getJSONObject("values").getJSONArray("scriptLogLine").getJSONObject(1).getJSONObject("values").getString("text"));
}
}

View File

@ -0,0 +1,168 @@
/*
* 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.javalin;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.TestScriptInput;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
**
*******************************************************************************/
public class TestScriptAction implements TestScriptActionInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput)
{
SampleTestInput sampleTestInput = new SampleTestInput();
sampleTestInput.setName(ValueUtils.getValueAsString(testScriptInput.getInputValues().get("name")));
sampleTestInput.setAge(ValueUtils.getValueAsInteger(testScriptInput.getInputValues().get("age")));
executeCodeInput.setInput(Map.of("input", sampleTestInput));
executeCodeInput.getContext().put("output", new SampleTestOutput());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QFieldMetaData> getTestInputFields()
{
return List.of(
new QFieldMetaData("name", QFieldType.STRING),
new QFieldMetaData("age", QFieldType.INTEGER)
);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QFieldMetaData> getTestOutputFields()
{
return List.of(new QFieldMetaData("message", QFieldType.STRING));
}
/*******************************************************************************
**
*******************************************************************************/
public static class SampleTestInput implements Serializable
{
private String name;
private Integer age;
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Getter for age
**
*******************************************************************************/
public Integer getAge()
{
return age;
}
/*******************************************************************************
** Setter for age
**
*******************************************************************************/
public void setAge(Integer age)
{
this.age = age;
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class SampleTestOutput implements Serializable
{
private String message;
/*******************************************************************************
** Getter for message
**
*******************************************************************************/
public String getMessage()
{
return message;
}
/*******************************************************************************
** Setter for message
**
*******************************************************************************/
public void setMessage(String message)
{
this.message = message;
}
}
}

View File

@ -227,7 +227,8 @@ public class TestUtils
.withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER).withBackendName("test_script_id")) .withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER).withBackendName("test_script_id"))
.withAssociatedScript(new AssociatedScript() .withAssociatedScript(new AssociatedScript()
.withFieldName("testScriptId") .withFieldName("testScriptId")
.withScriptTypeId(1)); .withScriptTypeId(1)
.withScriptTester(new QCodeReference(TestScriptAction.class, QCodeUsage.SCRIPT_TESTER)));
} }