diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java new file mode 100644 index 00000000..4ec61c9a --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptAction.java @@ -0,0 +1,198 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.actions.scripts; + + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Objects; +import com.kingsrook.qqq.backend.core.actions.ActionHelper; +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.GetAction; +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.RunAdHocRecordScriptInput; +import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.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; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RunAdHocRecordScriptAction +{ + // todo! private Map scriptRevisionCache = new HashMap<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void run(RunAdHocRecordScriptInput input, RunAdHocRecordScriptOutput output) throws QException + { + ActionHelper.validateSession(input); + + ScriptRevision scriptRevision = getScriptRevision(input); + + GetInput getInput = new GetInput(); + getInput.setTableName(input.getTableName()); + getInput.setPrimaryKey(input.getRecordPrimaryKey()); + GetOutput getOutput = new GetAction().execute(getInput); + QRecord record = getOutput.getRecord(); + // todo err if not found + + ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(); + executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new))); + executeCodeInput.getInput().put("record", record); + executeCodeInput.setContext(new HashMap<>()); + if(input.getOutputObject() != null) + { + executeCodeInput.getContext().put("output", input.getOutputObject()); + } + + if(input.getScriptUtils() != null) + { + executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils()); + } + else + { + executeCodeInput.getContext().put("scriptUtils", new ScriptApiUtils()); + } + + executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!! + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger // + ///////////////////////////////////////////////////////////////////////////////////////////////// + QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId())); + executeCodeInput.setExecutionLogger(executionLogger); + if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface) + { + //////////////////////////////////////////////////////////////////////////////////////////////////// + // if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. // + //////////////////////////////////////////////////////////////////////////////////////////////////// + scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId()); + scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId()); + } + + ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput(); + new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput); + + output.setOutput(executeCodeOutput.getOutput()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private ScriptRevision getScriptRevision(RunAdHocRecordScriptInput input) throws QException + { + // todo if(!scriptRevisionCache.containsKey(input.getCodeReference())) + { + Serializable scriptId = input.getCodeReference().getScriptId(); + /* + if(scriptId == null) + { + throw (new QNotFoundException("The input record [" + input.getCodeReference().getScriptId() + "][" + input.getCodeReference().getRecordPrimaryKey() + + "] does not have a script specified for [" + input.getCodeReference().getFieldName() + "]")); + } + + */ + + Script script = getScript(input, scriptId); + /* todo + if(script.getCurrentScriptRevisionId() == null) + { + throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + + "] (scriptId=" + scriptId + ") does not have a current version.")); + } + + */ + + ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId()); + // scriptRevisionCache.put(input.getCodeReference(), scriptRevision); + return scriptRevision; + } + + // return scriptRevisionCache.get(input.getCodeReference()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private ScriptRevision getCurrentScriptRevision(RunAdHocRecordScriptInput input, Serializable scriptRevisionId) throws QException + { + GetInput getInput = new GetInput(); + getInput.setTableName("scriptRevision"); + getInput.setPrimaryKey(scriptRevisionId); + GetOutput getOutput = new GetAction().execute(getInput); + if(getOutput.getRecord() == null) + { + /* todo + throw (new QNotFoundException("The current revision of the script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "][" + + input.getCodeReference().getFieldName() + "] (scriptRevisionId=" + scriptRevisionId + ") was not found.")); + + */ + throw (new IllegalStateException("todo")); + } + + return (new ScriptRevision(getOutput.getRecord())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private Script getScript(RunAdHocRecordScriptInput input, Serializable scriptId) throws QException + { + GetInput getInput = new GetInput(); + getInput.setTableName("script"); + getInput.setPrimaryKey(scriptId); + GetOutput getOutput = new GetAction().execute(getInput); + + if(getOutput.getRecord() == null) + { + /* + throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey() + "][" + + input.getCodeReference().getFieldName() + "] (script id=" + scriptId + ") was not found.")); + + */ + throw (new IllegalStateException("todo")); + } + + return (new Script(getOutput.getRecord())); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ScriptApiUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ScriptApiUtils.java new file mode 100644 index 00000000..d4b5631e --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/ScriptApiUtils.java @@ -0,0 +1,111 @@ +/* + * 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.io.Serializable; +import java.util.List; +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.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; + + +/******************************************************************************* + + $utils.query("order", null); + $utils.query($utils.newQueryInput().withTable("order").withLimit(1).withShouldGenerateDisplayValues()) + *******************************************************************************/ +public class ScriptApiUtils implements Serializable +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public QueryInput newQueryInput() + { + return (new QueryInput()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QRecord newQRecord() + { + return (new QRecord()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List query(String table, QQueryFilter filter) throws QException + { + QueryInput queryInput = new QueryInput(); + queryInput.setTableName(table); + queryInput.setFilter(filter); + return (new QueryAction().execute(queryInput).getRecords()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public List query(QueryInput queryInput) throws QException + { + return (new QueryAction().execute(queryInput).getRecords()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void update(String table, List recordList) throws QException + { + UpdateInput updateInput = new UpdateInput(); + updateInput.setTableName(table); + updateInput.setRecords(recordList); + new UpdateAction().execute(updateInput); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void update(String table, QRecord record) throws QException + { + UpdateInput updateInput = new UpdateInput(); + updateInput.setTableName(table); + updateInput.setRecords(List.of(record)); + new UpdateAction().execute(updateInput); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptInput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptInput.java new file mode 100644 index 00000000..971ff4c0 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptInput.java @@ -0,0 +1,270 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.actions.scripts; + + +import java.io.Serializable; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface; +import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput; +import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RunAdHocRecordScriptInput extends AbstractTableActionInput +{ + private AdHocScriptCodeReference codeReference; + private Map inputValues; + private Serializable recordPrimaryKey; + private String tableName; + private QCodeExecutionLoggerInterface logger; + + private Serializable outputObject; + + private Serializable scriptUtils; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public RunAdHocRecordScriptInput() + { + } + + + + /******************************************************************************* + ** Getter for inputValues + ** + *******************************************************************************/ + public Map getInputValues() + { + return inputValues; + } + + + + /******************************************************************************* + ** Setter for inputValues + ** + *******************************************************************************/ + public void setInputValues(Map inputValues) + { + this.inputValues = inputValues; + } + + + + /******************************************************************************* + ** Fluent setter for inputValues + ** + *******************************************************************************/ + public RunAdHocRecordScriptInput withInputValues(Map inputValues) + { + this.inputValues = inputValues; + return (this); + } + + + + /******************************************************************************* + ** Getter for outputObject + ** + *******************************************************************************/ + public Serializable getOutputObject() + { + return outputObject; + } + + + + /******************************************************************************* + ** Setter for outputObject + ** + *******************************************************************************/ + public void setOutputObject(Serializable outputObject) + { + this.outputObject = outputObject; + } + + + + /******************************************************************************* + ** Fluent setter for outputObject + ** + *******************************************************************************/ + public RunAdHocRecordScriptInput withOutputObject(Serializable outputObject) + { + this.outputObject = outputObject; + return (this); + } + + + + /******************************************************************************* + ** Getter for logger + *******************************************************************************/ + public QCodeExecutionLoggerInterface getLogger() + { + return (this.logger); + } + + + + /******************************************************************************* + ** Setter for logger + *******************************************************************************/ + public void setLogger(QCodeExecutionLoggerInterface logger) + { + this.logger = logger; + } + + + + /******************************************************************************* + ** Fluent setter for logger + *******************************************************************************/ + public RunAdHocRecordScriptInput withLogger(QCodeExecutionLoggerInterface logger) + { + this.logger = logger; + return (this); + } + + + + /******************************************************************************* + ** Getter for scriptUtils + ** + *******************************************************************************/ + public Serializable getScriptUtils() + { + return scriptUtils; + } + + + + /******************************************************************************* + ** Setter for scriptUtils + ** + *******************************************************************************/ + public void setScriptUtils(Serializable scriptUtils) + { + this.scriptUtils = scriptUtils; + } + + + + /******************************************************************************* + ** Getter for codeReference + *******************************************************************************/ + public AdHocScriptCodeReference getCodeReference() + { + return (this.codeReference); + } + + + + /******************************************************************************* + ** Setter for codeReference + *******************************************************************************/ + public void setCodeReference(AdHocScriptCodeReference codeReference) + { + this.codeReference = codeReference; + } + + + + /******************************************************************************* + ** Fluent setter for codeReference + *******************************************************************************/ + public RunAdHocRecordScriptInput withCodeReference(AdHocScriptCodeReference codeReference) + { + this.codeReference = codeReference; + return (this); + } + + + + /******************************************************************************* + ** Getter for recordPrimaryKey + *******************************************************************************/ + public Serializable getRecordPrimaryKey() + { + return (this.recordPrimaryKey); + } + + + + /******************************************************************************* + ** Setter for recordPrimaryKey + *******************************************************************************/ + public void setRecordPrimaryKey(Serializable recordPrimaryKey) + { + this.recordPrimaryKey = recordPrimaryKey; + } + + + + /******************************************************************************* + ** Fluent setter for recordPrimaryKey + *******************************************************************************/ + public RunAdHocRecordScriptInput withRecordPrimaryKey(Serializable recordPrimaryKey) + { + this.recordPrimaryKey = recordPrimaryKey; + return (this); + } + + + + /******************************************************************************* + ** Getter for tableName + *******************************************************************************/ + public String getTableName() + { + return (this.tableName); + } + + + + /******************************************************************************* + ** Setter for tableName + *******************************************************************************/ + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + + + /******************************************************************************* + ** Fluent setter for tableName + *******************************************************************************/ + public RunAdHocRecordScriptInput withTableName(String tableName) + { + this.tableName = tableName; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java new file mode 100644 index 00000000..d070916e --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/scripts/RunAdHocRecordScriptOutput.java @@ -0,0 +1,70 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.actions.scripts; + + +import java.io.Serializable; +import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class RunAdHocRecordScriptOutput extends AbstractActionOutput +{ + private Serializable output; + + + + /******************************************************************************* + ** Getter for output + ** + *******************************************************************************/ + public Serializable getOutput() + { + return output; + } + + + + /******************************************************************************* + ** Setter for output + ** + *******************************************************************************/ + public void setOutput(Serializable output) + { + this.output = output; + } + + + + /******************************************************************************* + ** Fluent setter for output + ** + *******************************************************************************/ + public RunAdHocRecordScriptOutput withOutput(Serializable output) + { + this.output = output; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AdHocScriptCodeReference.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AdHocScriptCodeReference.java new file mode 100644 index 00000000..bab3cc69 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/AdHocScriptCodeReference.java @@ -0,0 +1,63 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.model.metadata.code; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class AdHocScriptCodeReference extends QCodeReference +{ + private Integer scriptId; + + + + /******************************************************************************* + ** Getter for scriptId + *******************************************************************************/ + public Integer getScriptId() + { + return (this.scriptId); + } + + + + /******************************************************************************* + ** Setter for scriptId + *******************************************************************************/ + public void setScriptId(Integer scriptId) + { + this.scriptId = scriptId; + } + + + + /******************************************************************************* + ** Fluent setter for scriptId + *******************************************************************************/ + public AdHocScriptCodeReference withScriptId(Integer scriptId) + { + this.scriptId = scriptId; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java new file mode 100644 index 00000000..bb8f6a26 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/TablesPossibleValueSourceMetaDataProvider.java @@ -0,0 +1,63 @@ +/* + * 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.model.metadata.tables; + + +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; +import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TablesPossibleValueSourceMetaDataProvider +{ + public static final String NAME = "tables"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QPossibleValueSource defineTablesPossibleValueSource(QInstance qInstance) + { + QPossibleValueSource possibleValueSource = new QPossibleValueSource() + .withName(NAME) + .withType(QPossibleValueSourceType.ENUM) + .withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY); + + List> enumValues = new ArrayList<>(); + for(QTableMetaData table : qInstance.getTables().values()) + { + enumValues.add(new QPossibleValue<>(table.getName(), table.getLabel())); + } + + possibleValueSource.withEnumValues(enumValues); + return (possibleValueSource); + } + +} 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 new file mode 100644 index 00000000..f7589aae --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/scripts/StoreScriptRevisionProcessStep.java @@ -0,0 +1,178 @@ +/* + * 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.List; +import com.kingsrook.qqq.backend.core.actions.ActionHelper; +import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +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.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.actions.tables.insert.InsertInput; +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.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.utils.StringUtils; + + +/******************************************************************************* + ** Action to store a new version of a script, associated with a record. + ** + ** If there's never been a script assigned to the record (for the specified field), + ** then a new Script record is first inserted. + ** + ** The script referenced by the record is always updated to point at the new + ** scriptRevision record that is inserted. + ** + *******************************************************************************/ +public class StoreScriptRevisionProcessStep implements BackendStep +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException + { + ActionHelper.validateSession(input); + + /* + QTableMetaData table = input.getTable(); + Optional optAssociatedScript = table.getAssociatedScripts().stream().filter(as -> as.getFieldName().equals(input.getFieldName())).findFirst(); + if(optAssociatedScript.isEmpty()) + { + throw (new QException("Field to update associated script for is not an associated script field.")); + } + AssociatedScript associatedScript = optAssociatedScript.get(); + + ///////////////////////////////////////////////////////////// + // get the record that the script is to be associated with // + ///////////////////////////////////////////////////////////// + QRecord associatedRecord; + { + GetInput getInput = new GetInput(); + getInput.setTableName(input.getTableName()); + getInput.setPrimaryKey(input.getRecordPrimaryKey()); + getInput.setShouldGenerateDisplayValues(true); + GetOutput getOutput = new GetAction().execute(getInput); + associatedRecord = getOutput.getRecord(); + } + if(associatedRecord == null) + { + throw (new QException("Record to associated with script was not found.")); + } + */ + + ////////////////////////////////////////////////////////////////// + // check if there's currently a script referenced by the record // + ////////////////////////////////////////////////////////////////// + Integer scriptId = input.getValueInteger("scriptId"); + Integer nextSequenceNo = 1; + + //////////////////////////////////////// + // get the existing script, to update // + //////////////////////////////////////// + GetInput getInput = new GetInput(); + getInput.setTableName("script"); + getInput.setPrimaryKey(scriptId); + GetOutput getOutput = new GetAction().execute(getInput); + QRecord script = getOutput.getRecord(); + + QueryInput queryInput = new QueryInput(); + queryInput.setTableName("scriptRevision"); + queryInput.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, List.of(script.getValue("id")))) + .withOrderBy(new QFilterOrderBy("sequenceNo", false)) + ); + queryInput.setLimit(1); + QueryOutput queryOutput = new QueryAction().execute(queryInput); + if(!queryOutput.getRecords().isEmpty()) + { + nextSequenceNo = queryOutput.getRecords().get(0).getValueInteger("sequenceNo") + 1; + } + + ////////////////////////////////// + // insert a new script revision // + ////////////////////////////////// + String commitMessage = input.getValueString("commitMessage"); + if(!StringUtils.hasContent(commitMessage)) + { + if(nextSequenceNo == 1) + { + commitMessage = "Initial version"; + } + else + { + commitMessage = "No commit message given"; + } + } + + QRecord scriptRevision = new QRecord() + .withValue("scriptId", script.getValue("id")) + .withValue("contents", input.getValueString("contents")) + .withValue("commitMessage", commitMessage) + .withValue("sequenceNo", nextSequenceNo); + + try + { + scriptRevision.setValue("author", input.getSession().getUser().getFullName()); + } + catch(Exception e) + { + scriptRevision.setValue("author", "Unknown"); + } + + InsertInput insertInput = new InsertInput(); + insertInput.setTableName("scriptRevision"); + insertInput.setRecords(List.of(scriptRevision)); + InsertOutput insertOutput = new InsertAction().execute(insertInput); + scriptRevision = insertOutput.getRecords().get(0); + + //////////////////////////////////////////////////// + // update the script to point at the new revision // + //////////////////////////////////////////////////// + script.setValue("currentScriptRevisionId", scriptRevision.getValue("id")); + UpdateInput updateInput = new UpdateInput(); + updateInput.setTableName("script"); + updateInput.setRecords(List.of(script)); + new UpdateAction().execute(updateInput); + + output.addValue("scriptId", script.getValueInteger("id")); + output.addValue("scriptName", script.getValueString("name")); + output.addValue("scriptRevisionId", scriptRevision.getValueInteger("id")); + output.addValue("scriptRevisionSequenceNo", scriptRevision.getValueInteger("sequenceNo")); + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java new file mode 100644 index 00000000..b8edad0f --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/scripts/RunAdHocRecordScriptActionTest.java @@ -0,0 +1,157 @@ +/* + * 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.List; +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.scripts.logging.Log4jCodeExecutionLogger; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +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.tables.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference; +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.metadata.tables.AssociatedScript; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import org.junit.jupiter.api.Test; + + +/******************************************************************************* + ** Unit test for RunAdHocRecordScriptAction + *******************************************************************************/ +class RunAdHocRecordScriptActionTest extends BaseTest +{ + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() throws QException + { + setupInstance(); + + Integer scriptId = insertScript(""" + return "Hello"; + """); + + RunAdHocRecordScriptInput runAdHocRecordScriptInput = new RunAdHocRecordScriptInput(); + runAdHocRecordScriptInput.setRecordPrimaryKey(1); + runAdHocRecordScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAdHocRecordScriptInput.setCodeReference(new AdHocScriptCodeReference().withScriptId(scriptId)); + runAdHocRecordScriptInput.setLogger(new Log4jCodeExecutionLogger()); + + RunAdHocRecordScriptOutput runAdHocRecordScriptOutput = new RunAdHocRecordScriptOutput(); + new RunAdHocRecordScriptAction().run(runAdHocRecordScriptInput, runAdHocRecordScriptOutput); + + /* + RunAssociatedScriptInput runAssociatedScriptInput = new RunAssociatedScriptInput(); + runAssociatedScriptInput.setInputValues(Map.of()); + runAssociatedScriptInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY); + runAssociatedScriptInput.setCodeReference(new AssociatedScriptCodeReference() + .withRecordTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withRecordPrimaryKey(1) + .withFieldName("testScriptId") + ); + RunAssociatedScriptOutput runAssociatedScriptOutput = new RunAssociatedScriptOutput(); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ok - since the core module doesn't have the javascript language support module as a dep, this action will fail - but at least we can confirm it fails with this specific exception! // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + assertThatThrownBy(() -> new RunAssociatedScriptAction().run(runAssociatedScriptInput, runAssociatedScriptOutput)) + .isInstanceOf(QException.class) + .hasRootCauseInstanceOf(ClassNotFoundException.class) + .hasRootCauseMessage("com.kingsrook.qqq.languages.javascript.QJavaScriptExecutor"); + + ///////////////////////////////////// + // assert that a log was generated // + ///////////////////////////////////// + assertEquals(1, TestUtils.queryTable(ScriptLog.TABLE_NAME).size()); + */ + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void setupInstance() throws QException + { + QInstance instance = QContext.getQInstance(); + QTableMetaData personMemory = instance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY) + .withField(new QFieldMetaData("testScriptId", QFieldType.INTEGER)) + .withAssociatedScript(new AssociatedScript() + .withScriptTypeId(1) + .withFieldName("testScriptId") + ); + + new ScriptsMetaDataProvider().defineAll(instance, TestUtils.MEMORY_BACKEND_NAME, null); + + TestUtils.insertRecords(instance, personMemory, List.of( + new QRecord().withValue("id", 1), + new QRecord().withValue("id", 2) + )); + + TestUtils.insertRecords(instance, instance.getTable("scriptType"), List.of( + new QRecord().withValue("id", 1).withValue("name", "Test Script Type") + )); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private Integer insertScript(String code) throws QException + { + InsertInput insertInput = new InsertInput(); + insertInput.setTableName("script"); + insertInput.setRecords(List.of(new QRecord().withValue("name", "Test script"))); + InsertOutput insertOutput = new InsertAction().execute(insertInput); + Integer scriptId = insertOutput.getRecords().get(0).getValueInteger("id"); + + insertInput = new InsertInput(); + insertInput.setTableName("scriptRevision"); + insertInput.setRecords(List.of(new QRecord().withValue("scriptId", scriptId).withValue("code", code))); + insertOutput = new InsertAction().execute(insertInput); + Integer scriptRevisionId = insertOutput.getRecords().get(0).getValueInteger("id"); + + UpdateInput updateInput = new UpdateInput(); + updateInput.setTableName("script"); + updateInput.setRecords(List.of(new QRecord().withValue("id", scriptId).withValue("currentScriptRevisionId", scriptRevisionId))); + UpdateOutput updateOutput = new UpdateAction().execute(updateInput); + + return (scriptId); + } +} \ No newline at end of file