diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java index 875cf2f5..024305ec 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ExecuteCodeAction.java @@ -23,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 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()); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RecordScriptTestInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RecordScriptTestInterface.java new file mode 100644 index 00000000..71aece3d --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RecordScriptTestInterface.java @@ -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 . + */ + +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 getTestInputFields() + { + return (List.of(new QFieldMetaData("recordPrimaryKeyList", QFieldType.STRING).withLabel("Record Primary Key List"))); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public List getTestOutputFields() + { + return (Collections.emptyList()); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java index 53054839..78a8db65 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAssociatedScriptAction.java @@ -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) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java index 917ebfb2..f82e7032 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptRevision.java @@ -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 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 getFiles() + { + return (this.files); + } + + + + /******************************************************************************* + ** Setter for files + *******************************************************************************/ + public void setFiles(List files) + { + this.files = files; + } + + + + /******************************************************************************* + ** Fluent setter for files + *******************************************************************************/ + public ScriptRevision withFiles(List files) + { + this.files = files; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptType.java index e26908ef..7561b3e2 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptType.java @@ -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); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java index bd0c6f12..34de25db 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/scripts/ScriptsMetaDataProvider.java @@ -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 diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/LoadScriptTestDetailsProcessStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/LoadScriptTestDetailsProcessStep.java new file mode 100644 index 00000000..6be0ffd1 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/LoadScriptTestDetailsProcessStep.java @@ -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 . + */ + +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 inputFields = new ArrayList<>(); + for(QFieldMetaData testInputField : CollectionUtils.nonNullList(testScriptActionInterface.getTestInputFields())) + { + qInstanceEnricher.enrichField(testInputField); + inputFields.add(testInputField); + } + + ArrayList 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); + } + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStep.java index 7e14b2e2..73bc4e13 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStep.java @@ -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 fileContents = (List) input.getValue("fileContents"); - if(CollectionUtils.nullSafeHasContents(fileContents)) + ////////////////////////////////////////// + // Store the file(s) under the revision // + ////////////////////////////////////////// + List scriptRevisionFileRecords = null; + if(StringUtils.hasContent(input.getValueString("fileNames"))) { - List 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); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/TestScriptProcessStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/TestScriptProcessStep.java index a5a805f8..f66c1278 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/TestScriptProcessStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/TestScriptProcessStep.java @@ -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 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 inputValues = new HashMap<>(); + testScriptInput.setInputValues(inputValues); + + for(Map.Entry 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) { diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStepTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStepTest.java index 718ff0f2..524727b7 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStepTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStepTest.java @@ -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 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 scripts = TestUtils.queryTable(Script.TABLE_NAME); assertNull(scripts.get(0).getValueInteger("currentScriptRevisionId")); - ArrayList 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 = "

Hey, what's up

"; - 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()); diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 297cffcc..ff6298c1 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -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)