Switch to store all script contents in scriptRevisionFile sub-table; make test interface for all scripts work the same

This commit is contained in:
2023-06-26 20:18:57 -05:00
parent 6bc543fff7
commit b53d1823df
11 changed files with 456 additions and 137 deletions

View File

@ -23,22 +23,33 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.scripts.AbstractRunScriptInput;
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.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.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.scripts.ScriptRevision;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevisionFile;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -133,7 +144,17 @@ public class ExecuteCodeAction
/*******************************************************************************
**
*******************************************************************************/
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision)
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision) throws QException
{
return setupExecuteCodeInput(input, scriptRevision, null);
}
/*******************************************************************************
**
*******************************************************************************/
public static ExecuteCodeInput setupExecuteCodeInput(AbstractRunScriptInput<?> input, ScriptRevision scriptRevision, String fileName) throws QException
{
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
@ -150,7 +171,49 @@ public class ExecuteCodeAction
context.put("scriptUtils", input.getScriptUtils());
}
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
if(CollectionUtils.nullSafeIsEmpty(scriptRevision.getFiles()))
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(ScriptRevisionFile.TABLE_NAME);
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("scriptRevisionId", QCriteriaOperator.EQUALS, scriptRevision.getId())));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
scriptRevision.setFiles(new ArrayList<>());
for(QRecord record : queryOutput.getRecords())
{
scriptRevision.getFiles().add(new ScriptRevisionFile(record));
}
}
List<ScriptRevisionFile> files = scriptRevision.getFiles();
if(files == null || files.isEmpty())
{
throw (new QException("Script Revision " + scriptRevision.getId() + " had more than 1 associated ScriptRevisionFile (and the name to use was not specified)."));
}
else
{
String contents = null;
if(fileName == null || files.size() == 1)
{
contents = files.get(0).getContents();
}
else
{
for(ScriptRevisionFile file : files)
{
if(file.getFileName().equals(fileName))
{
contents = file.getContents();
}
}
if(contents == null)
{
throw (new QException("Could not find file named " + fileName + " for Script Revision " + scriptRevision.getId()));
}
}
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(contents).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
}
ExecuteCodeAction.addApiUtilityToContext(context, scriptRevision);
context.put("qqq", new QqqScriptUtils());

View File

@ -0,0 +1,71 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.scripts;
import java.util.Collections;
import java.util.List;
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.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
/*******************************************************************************
**
*******************************************************************************/
public class RecordScriptTestInterface implements TestScriptActionInterface
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void setupTestScriptInput(TestScriptInput testScriptInput, ExecuteCodeInput executeCodeInput) throws QException
{
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QFieldMetaData> getTestInputFields()
{
return (List.of(new QFieldMetaData("recordPrimaryKeyList", QFieldType.STRING).withLabel("Record Primary Key List")));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<QFieldMetaData> getTestOutputFields()
{
return (Collections.emptyList());
}
}

View File

@ -110,6 +110,7 @@ public class RunAssociatedScriptAction
GetInput getInput = new GetInput();
getInput.setTableName("scriptRevision");
getInput.setPrimaryKey(scriptRevisionId);
getInput.setIncludeAssociations(true);
GetOutput getOutput = new GetAction().execute(getInput);
if(getOutput.getRecord() == null)
{

View File

@ -23,7 +23,9 @@ package com.kingsrook.qqq.backend.core.model.scripts;
import java.time.Instant;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
@ -55,9 +57,6 @@ public class ScriptRevision extends QRecordEntity
@QField(possibleValueSourceName = "apiName", label = "API Name")
private String apiName;
@QField()
private String contents;
@QField()
private Integer sequenceNo;
@ -67,6 +66,9 @@ public class ScriptRevision extends QRecordEntity
@QField(maxLength = 100, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS)
private String author;
@QAssociation(name = "files")
private List<ScriptRevisionFile> files;
/*******************************************************************************
@ -226,40 +228,6 @@ public class ScriptRevision extends QRecordEntity
/*******************************************************************************
** Getter for contents
**
*******************************************************************************/
public String getContents()
{
return contents;
}
/*******************************************************************************
** Setter for contents
**
*******************************************************************************/
public void setContents(String contents)
{
this.contents = contents;
}
/*******************************************************************************
** Fluent setter for contents
**
*******************************************************************************/
public ScriptRevision withContents(String contents)
{
this.contents = contents;
return (this);
}
/*******************************************************************************
** Getter for sequenceNo
**
@ -422,4 +390,35 @@ public class ScriptRevision extends QRecordEntity
return (this);
}
/*******************************************************************************
** Getter for files
*******************************************************************************/
public List<ScriptRevisionFile> getFiles()
{
return (this.files);
}
/*******************************************************************************
** Setter for files
*******************************************************************************/
public void setFiles(List<ScriptRevisionFile> files)
{
this.files = files;
}
/*******************************************************************************
** Fluent setter for files
*******************************************************************************/
public ScriptRevision withFiles(List<ScriptRevisionFile> files)
{
this.files = files;
return (this);
}
}

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.scripts;
import java.time.Instant;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
@ -55,6 +56,30 @@ public class ScriptType extends QRecordEntity
@QField(possibleValueSourceName = ScriptTypeFileMode.NAME)
private Integer fileMode;
@QField()
private String testScriptInterfaceName;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ScriptType()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ScriptType(QRecord qRecord)
{
populateFromQRecord(qRecord);
}
/*******************************************************************************
@ -290,4 +315,35 @@ public class ScriptType extends QRecordEntity
return (this);
}
/*******************************************************************************
** Getter for testScriptInterfaceName
*******************************************************************************/
public String getTestScriptInterfaceName()
{
return (this.testScriptInterfaceName);
}
/*******************************************************************************
** Setter for testScriptInterfaceName
*******************************************************************************/
public void setTestScriptInterfaceName(String testScriptInterfaceName)
{
this.testScriptInterfaceName = testScriptInterfaceName;
}
/*******************************************************************************
** Fluent setter for testScriptInterfaceName
*******************************************************************************/
public ScriptType withTestScriptInterfaceName(String testScriptInterfaceName)
{
this.testScriptInterfaceName = testScriptInterfaceName;
return (this);
}
}

View File

@ -54,12 +54,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
import com.kingsrook.qqq.backend.core.processes.implementations.scripts.LoadScriptTestDetailsProcessStep;
import com.kingsrook.qqq.backend.core.processes.implementations.scripts.RunRecordScriptExtractStep;
import com.kingsrook.qqq.backend.core.processes.implementations.scripts.RunRecordScriptLoadStep;
import com.kingsrook.qqq.backend.core.processes.implementations.scripts.RunRecordScriptTransformStep;
@ -72,9 +74,10 @@ import com.kingsrook.qqq.backend.core.processes.implementations.scripts.TestScri
*******************************************************************************/
public class ScriptsMetaDataProvider
{
public static final String RUN_RECORD_SCRIPT_PROCESS_NAME = "runRecordScript";
public static final String STORE_SCRIPT_REVISION_PROCESS_NAME = "storeScriptRevision";
public static final String TEST_SCRIPT_PROCESS_NAME = "testScript";
public static final String RUN_RECORD_SCRIPT_PROCESS_NAME = "runRecordScript";
public static final String STORE_SCRIPT_REVISION_PROCESS_NAME = "storeScriptRevision";
public static final String TEST_SCRIPT_PROCESS_NAME = "testScript";
public static final String LOAD_SCRIPT_TEST_DETAILS_PROCESS_NAME = "loadScriptTestDetails";
public static final String SCRIPT_TYPE_NAME_RECORD = "Record Script";
@ -94,11 +97,30 @@ public class ScriptsMetaDataProvider
instance.addPossibleValueSource(TablesPossibleValueSourceMetaDataProvider.defineTablesPossibleValueSource(instance));
instance.addProcess(defineStoreScriptRevisionProcess());
instance.addProcess(defineTestScriptProcess());
instance.addProcess(defineLoadScriptTestDetailsProcess());
instance.addProcess(defineRunRecordScriptProcess());
}
/*******************************************************************************
**
*******************************************************************************/
private QProcessMetaData defineLoadScriptTestDetailsProcess()
{
return (new QProcessMetaData()
.withName(LOAD_SCRIPT_TEST_DETAILS_PROCESS_NAME)
.withTableName(Script.TABLE_NAME)
.withIsHidden(true)
//? .withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
.withStepList(List.of(
new QBackendStepMetaData()
.withName("main")
.withCode(new QCodeReference(LoadScriptTestDetailsProcessStep.class)))));
}
/*******************************************************************************
**
*******************************************************************************/
@ -404,7 +426,7 @@ public class ScriptsMetaDataProvider
{
QTableMetaData tableMetaData = defineStandardTable(backendName, ScriptType.TABLE_NAME, ScriptType.class)
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name")))
.withSection(new QFieldSection("details", new QIcon().withName("dataset"), Tier.T2, List.of("helpText", "sampleCode", "fileMode")))
.withSection(new QFieldSection("details", new QIcon().withName("dataset"), Tier.T2, List.of("helpText", "sampleCode", "fileMode", "testScriptInterfaceName")))
.withSection(new QFieldSection("files", new QIcon().withName("description"), Tier.T2).withWidgetName(QJoinMetaData.makeInferredJoinName(ScriptType.TABLE_NAME, ScriptTypeFileSchema.TABLE_NAME)))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
tableMetaData.getField("sampleCode").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("javascript")));
@ -440,12 +462,15 @@ public class ScriptsMetaDataProvider
.withRecordLabelFormat("%s v%s")
.withRecordLabelFields(List.of("scriptId", "sequenceNo"))
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "scriptId", "sequenceNo")))
.withSection(new QFieldSection("code", new QIcon().withName("data_object"), Tier.T2, List.of("contents")))
.withSection(new QFieldSection("files", new QIcon().withName("description"), Tier.T2).withWidgetName(QJoinMetaData.makeInferredJoinName(ScriptRevision.TABLE_NAME, ScriptRevisionFile.TABLE_NAME)))
.withSection(new QFieldSection("changeManagement", new QIcon().withName("history"), Tier.T2, List.of("commitMessage", "author")))
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))
.withAssociation(new Association()
.withName("files")
.withAssociatedTableName(ScriptRevisionFile.TABLE_NAME)
.withJoinName(QJoinMetaData.makeInferredJoinName(ScriptRevision.TABLE_NAME, ScriptRevisionFile.TABLE_NAME)));
tableMetaData.getField("contents").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("javascript")));
tableMetaData.getField("scriptId").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
try

View File

@ -0,0 +1,96 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.processes.implementations.scripts;
import java.util.ArrayList;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
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.scripts.ScriptType;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Action to load the details necessary to test a script.
**
*******************************************************************************/
public class LoadScriptTestDetailsProcessStep implements BackendStep
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
try
{
ActionHelper.validateSession(input);
Integer scriptTypeId = input.getValueInteger("scriptTypeId");
GetInput getInput = new GetInput();
getInput.setTableName(ScriptType.TABLE_NAME);
getInput.setPrimaryKey(scriptTypeId);
GetOutput getOutput = new GetAction().execute(getInput);
ScriptType scriptType = new ScriptType(getOutput.getRecord());
TestScriptActionInterface testScriptActionInterface = QCodeLoader.getAdHoc(TestScriptActionInterface.class, new QCodeReference(scriptType.getTestScriptInterfaceName(), QCodeType.JAVA));
QInstanceEnricher qInstanceEnricher = new QInstanceEnricher(new QInstance());
ArrayList<QFieldMetaData> inputFields = new ArrayList<>();
for(QFieldMetaData testInputField : CollectionUtils.nonNullList(testScriptActionInterface.getTestInputFields()))
{
qInstanceEnricher.enrichField(testInputField);
inputFields.add(testInputField);
}
ArrayList<QFieldMetaData> outputFields = new ArrayList<>();
for(QFieldMetaData testOutputField : CollectionUtils.nonNullList(testScriptActionInterface.getTestOutputFields()))
{
qInstanceEnricher.enrichField(testOutputField);
outputFields.add(testOutputField);
}
output.addValue("testInputFields", inputFields);
output.addValue("testOutputFields", outputFields);
}
catch(Exception e)
{
output.addValue("exception", e);
}
}
}

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.processes.implementations.scripts;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
@ -128,11 +129,6 @@ public class StoreScriptRevisionProcessStep implements BackendStep
.withValue("commitMessage", commitMessage)
.withValue("sequenceNo", nextSequenceNo);
if(input.getValue("contents") != null)
{
scriptRevision.withValue("contents", input.getValueString("contents"));
}
try
{
scriptRevision.setValue("author", input.getSession().getUser().getFullName());
@ -147,22 +143,28 @@ public class StoreScriptRevisionProcessStep implements BackendStep
scriptRevision = insertOutput.getRecords().get(0);
Integer scriptRevisionId = scriptRevision.getValueInteger("id");
//////////////////////////////////////////////////////////////////////////////////////////
// if there's a list of file contents (instead of just a single string), store them all //
//////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("unchecked")
List<QRecord> fileContents = (List<QRecord>) input.getValue("fileContents");
if(CollectionUtils.nullSafeHasContents(fileContents))
//////////////////////////////////////////
// Store the file(s) under the revision //
//////////////////////////////////////////
List<QRecord> scriptRevisionFileRecords = null;
if(StringUtils.hasContent(input.getValueString("fileNames")))
{
List<QRecord> scriptRevisionRecords = fileContents.stream().map(r -> new ScriptRevisionFile()
.withScriptRevisionId(scriptRevisionId)
.withFileName(r.getValueString("fileName"))
.withContents(r.getValueString("contents"))
.toQRecord()).toList();
scriptRevisionFileRecords = new ArrayList<>();
for(String fileName : input.getValueString("fileNames").split(","))
{
scriptRevisionFileRecords.add(new ScriptRevisionFile()
.withScriptRevisionId(scriptRevisionId)
.withFileName(fileName)
.withContents(input.getValueString("fileContents:" + fileName))
.toQRecord());
}
}
if(CollectionUtils.nullSafeHasContents(scriptRevisionFileRecords))
{
InsertInput scriptRevisionFileInsertInput = new InsertInput();
scriptRevisionFileInsertInput.setTableName(ScriptRevisionFile.TABLE_NAME);
scriptRevisionFileInsertInput.setRecords(scriptRevisionRecords);
scriptRevisionFileInsertInput.setRecords(scriptRevisionFileRecords);
scriptRevisionFileInsertInput.setTransaction(transaction);
new InsertAction().execute(scriptRevisionFileInsertInput);
}

View File

@ -22,35 +22,33 @@
package com.kingsrook.qqq.backend.core.processes.implementations.scripts;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.scripts.RunAdHocRecordScriptAction;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
import com.kingsrook.qqq.backend.core.actions.scripts.logging.BuildScriptLogAndScriptLogLineExecutionLogger;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptInput;
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptOutput;
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.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.AdHocScriptCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.scripts.Script;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevisionFile;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptType;
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
@ -77,62 +75,60 @@ public class TestScriptProcessStep implements BackendStep
ScriptRevision scriptRevision = new ScriptRevision();
scriptRevision.setScriptId(scriptId);
scriptRevision.setContents(input.getValueString("code"));
ArrayList<ScriptRevisionFile> files = new ArrayList<>();
if(StringUtils.hasContent(input.getValueString("fileNames")))
{
for(String fileName : input.getValueString("fileNames").split(","))
{
files.add(new ScriptRevisionFile()
.withFileName(fileName)
.withContents(input.getValueString("fileContents:" + fileName)));
}
}
scriptRevision.setFiles(files);
scriptRevision.setApiName(input.getValueString("apiName"));
scriptRevision.setApiVersion(input.getValueString("apiVersion"));
AdHocScriptCodeReference adHocScriptCodeReference = new AdHocScriptCodeReference().withScriptRevisionRecord(scriptRevision.toQRecord());
adHocScriptCodeReference.setCodeType(QCodeType.JAVA_SCRIPT); // todo - load dynamically?
adHocScriptCodeReference.setInlineCode(scriptRevision.getFiles().get(0).getContents()); // todo - ugh.
BuildScriptLogAndScriptLogLineExecutionLogger executionLogger = new BuildScriptLogAndScriptLogLineExecutionLogger(null, null);
/////////////////////////////////////////////////////////////////
// lookup the script - figure out how to proceed based on type //
/////////////////////////////////////////////////////////////////
QRecord script = getScript(scriptId);
String scriptTypeName = getScriptTypeName(script);
//////////////////////////////////////////////////////
// load the script & its type & its test interface. //
//////////////////////////////////////////////////////
QRecord script = getScript(scriptId);
Integer scriptTypeId = script.getValueInteger("scriptTypeId");
GetInput getInput = new GetInput();
getInput.setTableName(ScriptType.TABLE_NAME);
getInput.setPrimaryKey(scriptTypeId);
GetOutput getOutput = new GetAction().execute(getInput);
ScriptType scriptType = new ScriptType(getOutput.getRecord());
if(ScriptsMetaDataProvider.SCRIPT_TYPE_NAME_RECORD.equals(scriptTypeName))
TestScriptActionInterface testScriptActionInterface = QCodeLoader.getAdHoc(TestScriptActionInterface.class, new QCodeReference(scriptType.getTestScriptInterfaceName(), QCodeType.JAVA));
TestScriptInput testScriptInput = new TestScriptInput();
testScriptInput.setApiName(input.getValueString("apiName"));
testScriptInput.setApiVersion(input.getValueString("apiVersion"));
testScriptInput.setCodeReference(adHocScriptCodeReference);
Map<String, Serializable> inputValues = new HashMap<>();
testScriptInput.setInputValues(inputValues);
for(Map.Entry<String, Serializable> entry : input.getValues().entrySet())
{
String tableName = script.getValueString("tableName");
QTableMetaData table = QContext.getQInstance().getTable(tableName);
if(table == null)
{
throw (new QException("Could not find table [" + tableName + "] for script"));
}
String recordPrimaryKeyList = input.getValueString("recordPrimaryKeyList");
if(!StringUtils.hasContent(recordPrimaryKeyList))
{
throw (new QException("Record primary key list was not given."));
}
QueryInput queryInput = new QueryInput();
queryInput.setTableName(tableName);
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordPrimaryKeyList.split(","))));
queryInput.setIncludeAssociations(true);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
{
throw (new QException("No records were found by the given primary keys."));
}
RunAdHocRecordScriptInput runAdHocRecordScriptInput = new RunAdHocRecordScriptInput();
runAdHocRecordScriptInput.setRecordList(queryOutput.getRecords());
runAdHocRecordScriptInput.setLogger(executionLogger);
runAdHocRecordScriptInput.setTableName(tableName);
runAdHocRecordScriptInput.setCodeReference(new AdHocScriptCodeReference().withScriptRevisionRecord(scriptRevision.toQRecord()));
RunAdHocRecordScriptOutput runAdHocRecordScriptOutput = new RunAdHocRecordScriptOutput();
new RunAdHocRecordScriptAction().run(runAdHocRecordScriptInput, runAdHocRecordScriptOutput);
/////////////////////////////////////////////
// if there was an exception, send it back //
/////////////////////////////////////////////
runAdHocRecordScriptOutput.getException().ifPresent(e -> output.addValue("exception", e));
}
else
{
throw new QException("This process does not know how to test a script of type: " + scriptTypeName);
String key = entry.getKey();
String value = ValueUtils.getValueAsString(entry.getValue());
inputValues.put(key, value);
}
TestScriptOutput testScriptOutput = new TestScriptOutput();
testScriptActionInterface.execute(testScriptInput, testScriptOutput);
output.addValue("scriptLogLines", new ArrayList<>(executionLogger.getScriptLogLines()));
output.addValue("outputObject", testScriptOutput.getOutputObject());
}
catch(Exception e)
{

View File

@ -22,7 +22,6 @@
package com.kingsrook.qqq.backend.core.processes.implementations.scripts;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.BaseTest;
@ -68,7 +67,8 @@ class StoreScriptRevisionProcessStepTest extends BaseTest
new StoreScriptRevisionProcessStep().run(new RunBackendStepInput().withValues(MapBuilder.of(
"scriptId", scriptId,
"contents", scriptContents
"fileNames", "script",
"fileContents:script", scriptContents
)), new RunBackendStepOutput());
scripts = TestUtils.queryTable(Script.TABLE_NAME);
@ -79,11 +79,16 @@ class StoreScriptRevisionProcessStepTest extends BaseTest
assertEquals(scriptId, scriptRevision.getValueInteger("scriptId"));
assertEquals(1, scriptRevision.getValueInteger("sequenceNo"));
assertEquals("Initial version", scriptRevision.getValueString("commitMessage"));
assertEquals(scriptContents, scriptRevision.getValueString("contents"));
List<QRecord> scriptRevisionFiles = TestUtils.queryTable(ScriptRevisionFile.TABLE_NAME);
QRecord scriptRevisionFile = scriptRevisionFiles.get(0);
assertEquals(scriptContents, scriptRevisionFile.getValueString("contents"));
String updatedScriptContents = "logger.log('Really, Hi');";
new StoreScriptRevisionProcessStep().run(new RunBackendStepInput().withValues(MapBuilder.of(
"scriptId", scriptId,
"contents", scriptContents
"fileNames", "script",
"fileContents:script", updatedScriptContents
)), new RunBackendStepOutput());
scripts = TestUtils.queryTable(Script.TABLE_NAME);
@ -91,10 +96,14 @@ class StoreScriptRevisionProcessStepTest extends BaseTest
scriptRevisions = TestUtils.queryTable(ScriptRevision.TABLE_NAME).stream().filter(r -> r.getValueInteger("id").equals(2)).collect(Collectors.toList());
scriptRevision = scriptRevisions.get(0);
Integer newScriptRevisionId = scriptRevision.getValueInteger("id");
assertEquals(scriptId, scriptRevision.getValueInteger("scriptId"));
assertEquals(2, scriptRevision.getValueInteger("sequenceNo"));
assertEquals("No commit message given", scriptRevision.getValueString("commitMessage"));
assertEquals(scriptContents, scriptRevision.getValueString("contents"));
scriptRevisionFiles = TestUtils.queryTable(ScriptRevisionFile.TABLE_NAME);
scriptRevisionFile = scriptRevisionFiles.stream().filter(r -> r.getValueInteger("scriptRevisionId").equals(newScriptRevisionId)).findFirst().get();
assertEquals(updatedScriptContents, scriptRevisionFile.getValueString("contents"));
}
@ -116,13 +125,11 @@ class StoreScriptRevisionProcessStepTest extends BaseTest
List<QRecord> scripts = TestUtils.queryTable(Script.TABLE_NAME);
assertNull(scripts.get(0).getValueInteger("currentScriptRevisionId"));
ArrayList<QRecord> fileContents = new ArrayList<>();
fileContents.add(new QRecord().withValue("fileName", "script").withValue("contents", scriptContents));
fileContents.add(new QRecord().withValue("fileName", "template").withValue("contents", templateContents));
RunBackendStepInput runBackendStepInput = new RunBackendStepInput();
runBackendStepInput.addValue("scriptId", scriptId);
runBackendStepInput.addValue("fileContents", fileContents);
runBackendStepInput.addValue("fileNames", "script,template");
runBackendStepInput.addValue("fileContents:script", scriptContents);
runBackendStepInput.addValue("fileContents:template", templateContents);
new StoreScriptRevisionProcessStep().run(runBackendStepInput, new RunBackendStepOutput());
scripts = TestUtils.queryTable(Script.TABLE_NAME);
@ -150,13 +157,11 @@ class StoreScriptRevisionProcessStepTest extends BaseTest
String updatedScriptContents = "logger.log('Really, Hi');";
String updatedTemplateContents = "<h1>Hey, what's up</h1>";
fileContents = new ArrayList<>();
fileContents.add(new QRecord().withValue("fileName", "script").withValue("contents", updatedScriptContents));
fileContents.add(new QRecord().withValue("fileName", "template").withValue("contents", updatedTemplateContents));
runBackendStepInput = new RunBackendStepInput();
runBackendStepInput.addValue("scriptId", scriptId);
runBackendStepInput.addValue("fileContents", fileContents);
runBackendStepInput.addValue("fileNames", "script,template");
runBackendStepInput.addValue("fileContents:script", updatedScriptContents);
runBackendStepInput.addValue("fileContents:template", updatedTemplateContents);
runBackendStepInput.addValue("commitMessage", "Updated files");
new StoreScriptRevisionProcessStep().run(runBackendStepInput, new RunBackendStepOutput());

View File

@ -855,6 +855,11 @@ public class QJavalinImplementation
getInput.setShouldTranslatePossibleValues(true);
getInput.setShouldFetchHeavyFields(true);
if("true".equals(context.queryParam("includeAssociations")))
{
getInput.setIncludeAssociations(true);
}
PermissionsHelper.checkTablePermissionThrowing(getInput, TablePermissionSubType.READ);
// todo - validate that the primary key is of the proper type (e.g,. not a string for an id field)