QQQ-7 first version of actual execution of processes

This commit is contained in:
2022-06-24 08:12:21 -05:00
parent fcc831008f
commit ff83427483
6 changed files with 280 additions and 59 deletions

View File

@ -46,6 +46,7 @@
--> -->
<module name="TreeWalker"> <module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="OuterTypeFilename"/> <module name="OuterTypeFilename"/>
<module name="IllegalTokenText"> <module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/> <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
@ -171,7 +172,7 @@
<property name="caseIndent" value="3"/> <property name="caseIndent" value="3"/>
<property name="throwsIndent" value="6"/> <property name="throwsIndent" value="6"/>
<property name="lineWrappingIndentation" value="3"/> <property name="lineWrappingIndentation" value="3"/>
<property name="arrayInitIndent" value="2"/> <property name="arrayInitIndent" value="6"/>
</module> </module>
<!-- <!--
<module name="AbbreviationAsWordInName"> <module name="AbbreviationAsWordInName">
@ -260,4 +261,5 @@
<module name="MissingOverride"/> <module name="MissingOverride"/>
</module> </module>
<module name="SuppressWarningsFilter"/>
</module> </module>

View File

@ -0,0 +1,87 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.frontend.picocli;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback;
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import picocli.CommandLine;
/*******************************************************************************
** Define how a PicoCLI process gets data back to a QProcess.
*******************************************************************************/
public class PicoCliProcessCallback implements QProcessCallback
{
private final CommandLine commandLine;
/*******************************************************************************
** Constructor that takes the picocli CommandLine object
*******************************************************************************/
public PicoCliProcessCallback(CommandLine commandLine)
{
this.commandLine = commandLine;
}
/*******************************************************************************
** Get the filter query for this callback.
*******************************************************************************/
@Override
public QQueryFilter getQueryFilter()
{
return null;
}
/*******************************************************************************
** Get the field values for this callback.
*******************************************************************************/
@Override
public Map<String, Serializable> getFieldValues(List<QFieldMetaData> fields)
{
Map<String, Serializable> rs = new HashMap<>();
final Scanner scanner = new Scanner(System.in);
///////////////////////////////////
// todo - only if "interactive?" //
///////////////////////////////////
for(QFieldMetaData field : fields)
{
commandLine.getOut().println("Please supply a value for the field: [" + field.getLabel() + "]:");
rs.put(field.getName(), scanner.nextLine());
}
return (rs);
}
}

View File

@ -25,12 +25,16 @@ package com.kingsrook.qqq.frontend.picocli;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import picocli.CommandLine; import picocli.CommandLine;
@ -94,9 +98,27 @@ public class QCommandBuilder
List<QProcessMetaData> processes = qInstance.getProcessesForTable(tableName); List<QProcessMetaData> processes = qInstance.getProcessesForTable(tableName);
if(CollectionUtils.nullSafeHasContents(processes)) if(CollectionUtils.nullSafeHasContents(processes))
{ {
tableCommand.addSubcommand("process", defineTableProcessesCommand(table, processes)); tableCommand.addSubcommand("process", defineProcessesCommand(processes));
} }
}); });
///////////////////////////////////////////////////////////////////////////
// add all orphan processes (e.g., ones without tables) to the top-level //
///////////////////////////////////////////////////////////////////////////
List<QProcessMetaData> orphanProcesses = new ArrayList<>();
for(QProcessMetaData process : qInstance.getProcesses().values())
{
if(!StringUtils.hasContent(process.getTableName()))
{
orphanProcesses.add(process);
}
}
if(!orphanProcesses.isEmpty())
{
topCommandSpec.addSubcommand("processes", defineProcessesCommand(orphanProcesses));
}
return topCommandSpec; return topCommandSpec;
} }
@ -233,14 +255,34 @@ public class QCommandBuilder
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private CommandLine.Model.CommandSpec defineTableProcessesCommand(QTableMetaData table, List<QProcessMetaData> processes) private CommandLine.Model.CommandSpec defineProcessesCommand(List<QProcessMetaData> processes)
{ {
CommandLine.Model.CommandSpec processesCommand = CommandLine.Model.CommandSpec.create(); CommandLine.Model.CommandSpec processesCommand = CommandLine.Model.CommandSpec.create();
for(QProcessMetaData process : processes) for(QProcessMetaData process : processes)
{ {
///////////////////////////////////////////
// add the sub-command to run the proces //
///////////////////////////////////////////
CommandLine.Model.CommandSpec processCommand = CommandLine.Model.CommandSpec.create(); CommandLine.Model.CommandSpec processCommand = CommandLine.Model.CommandSpec.create();
processesCommand.addSubcommand(process.getName(), processCommand); processesCommand.addSubcommand(process.getName(), processCommand);
//////////////////////////////////////////////////////////////////////////////////
// add all (distinct, by name) input fields to the command as --field-* options //
//////////////////////////////////////////////////////////////////////////////////
Map<String, QFieldMetaData> inputFieldMap = new LinkedHashMap<>();
for(QFieldMetaData inputField : process.getInputFields())
{
inputFieldMap.put(inputField.getName(), inputField);
}
for(QFieldMetaData field : inputFieldMap.values())
{
processCommand.addOption(CommandLine.Model.OptionSpec.builder("--field-" + field.getName())
.type(getClassForField(field))
.build());
}
} }
return (processesCommand); return (processesCommand);

View File

@ -36,7 +36,7 @@ import com.kingsrook.qqq.backend.core.actions.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.InsertAction; import com.kingsrook.qqq.backend.core.actions.InsertAction;
import com.kingsrook.qqq.backend.core.actions.MetaDataAction; import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
import com.kingsrook.qqq.backend.core.actions.QueryAction; import com.kingsrook.qqq.backend.core.actions.QueryAction;
import com.kingsrook.qqq.backend.core.actions.RunFunctionAction; import com.kingsrook.qqq.backend.core.actions.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction; import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
import com.kingsrook.qqq.backend.core.actions.UpdateAction; import com.kingsrook.qqq.backend.core.actions.UpdateAction;
import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter; import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
@ -53,6 +53,8 @@ import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataRequest;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataResult; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataResult;
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest; import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest;
import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult; import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult;
import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
@ -62,6 +64,7 @@ import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFiel
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
@ -247,9 +250,23 @@ public class QPicoCliImplementation
} }
else else
{ {
String subCommandName = parseResult.subcommand().commandSpec().name(); ParseResult subParseResult = parseResult.subcommand();
String subCommandName = subParseResult.commandSpec().name();
CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName);
return runTableLevelCommand(subCommandLine, parseResult.subcommand()); switch(subCommandName)
{
case "processes":
{
return runTableProcess(subCommandLine, null, subParseResult);
}
default:
{
/////////////////////////////////////////////////////////
// by default, assume the command here is a table name //
/////////////////////////////////////////////////////////
return runTableLevelCommand(subCommandLine, subParseResult);
}
}
} }
} }
@ -336,10 +353,43 @@ public class QPicoCliImplementation
private int runTableProcessLevelCommand(CommandLine subCommandLine, String tableName, ParseResult processParseResult) private int runTableProcessLevelCommand(CommandLine subCommandLine, String tableName, ParseResult processParseResult)
{ {
String processName = processParseResult.commandSpec().name(); String processName = processParseResult.commandSpec().name();
QTableMetaData table = qInstance.getTable(tableName);
QProcessMetaData process = qInstance.getProcess(processName); QProcessMetaData process = qInstance.getProcess(processName);
RunFunctionAction runFunctionAction = new RunFunctionAction(); RunProcessRequest request = new RunProcessRequest(qInstance);
// todo!
request.setSession(session);
request.setProcessName(processName);
request.setCallback(new PicoCliProcessCallback(subCommandLine));
for(OptionSpec matchedOption : processParseResult.matchedOptions())
{
if(matchedOption.longestName().startsWith("--field-"))
{
String fieldName = matchedOption.longestName().substring(8);
request.addValue(fieldName, matchedOption.getValue());
}
}
try
{
RunProcessResult result = new RunProcessAction().execute(request);
subCommandLine.getOut().println("Process Results: "); // todo better!!
for(QFieldMetaData outputField : process.getOutputFields())
{
subCommandLine.getOut().format(" %s: %s\n", outputField.getLabel(), result.getValues().get(outputField.getName()));
}
if(result.getError() != null)
{
subCommandLine.getOut().println("Process Error message: " + result.getError());
}
}
catch(Exception e)
{
e.printStackTrace();
subCommandLine.getOut().println("Caught Exception running process: " + e); // todo better!
return 1;
}
return 0; return 0;
} }

View File

@ -22,10 +22,12 @@
package com.kingsrook.qqq.frontend.picocli; package com.kingsrook.qqq.frontend.picocli;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -37,7 +39,6 @@ import org.apache.commons.io.FileUtils;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -133,12 +134,20 @@ class QPicoCliImplementationTest
TestOutput testOutput = testCli("--meta-data"); TestOutput testOutput = testCli("--meta-data");
JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput()); JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(metaData); assertNotNull(metaData);
assertEquals(1, metaData.keySet().size(), "Number of top-level keys"); assertEquals(2, metaData.keySet().size(), "Number of top-level keys");
assertTrue(metaData.has("tables")); assertTrue(metaData.has("tables"));
JSONObject tables = metaData.getJSONObject("tables"); JSONObject tables = metaData.getJSONObject("tables");
JSONObject personTable = tables.getJSONObject("person"); JSONObject personTable = tables.getJSONObject("person");
assertEquals("person", personTable.getString("name")); assertEquals("person", personTable.getString("name"));
assertEquals("Person", personTable.getString("label")); assertEquals("Person", personTable.getString("label"));
assertTrue(metaData.has("processes"));
JSONObject processes = metaData.getJSONObject("processes");
JSONObject greetProcess = processes.getJSONObject("greet");
assertEquals("greet", greetProcess.getString("name"));
// todo - need label assertEquals("Person", greetProcess.getString("label"));
assertEquals("person", greetProcess.getString("tableName"));
} }
@ -472,12 +481,25 @@ class QPicoCliImplementationTest
** **
*******************************************************************************/ *******************************************************************************/
@Test @Test
@Disabled // not yet done. public void test_tableProcessGreetUsingCallbackForFields() throws Exception
public void test_tableProcessGreet() throws Exception
{ {
setStandardInputLines("Hi", "How are you?");
TestOutput testOutput = testCli("person", "process", "greet"); TestOutput testOutput = testCli("person", "process", "greet");
assertTestOutputContains(testOutput, "Please supply a value for the field.*Greeting Prefix");
assertTestOutputContains(testOutput, "Hi X How are you?");
}
fail("Assertion not written...");
/*******************************************************************************
** test running a process on a table
**
*******************************************************************************/
@Test
public void test_tableProcessGreetUsingOptionsForFields() throws Exception
{
TestOutput testOutput = testCli("person", "process", "greet", "--field-greetingPrefix=Hello", "--field-greetingSuffix=There");
assertTestOutputDoesNotContain(testOutput, "Please supply a value for the field");
assertTestOutputContains(testOutput, "Hello X There");
} }
@ -567,6 +589,26 @@ class QPicoCliImplementationTest
/*******************************************************************************
**
*******************************************************************************/
private void setStandardInputLines(String... lines)
{
StringBuilder stringBuilder = new StringBuilder();
for(String line : lines)
{
stringBuilder.append(line);
if(!line.endsWith("\n"))
{
stringBuilder.append("\n");
}
}
ByteArrayInputStream stdin = new ByteArrayInputStream(stringBuilder.toString().getBytes(Charset.defaultCharset()));
System.setIn(stdin);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -26,7 +26,6 @@ import java.io.InputStream;
import java.sql.Connection; import java.sql.Connection;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage; import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage;
@ -41,7 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QOutputView;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListView; import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListView;
import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -63,7 +62,7 @@ public class TestUtils
public static void primeTestDatabase() throws Exception public static void primeTestDatabase() throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(TestUtils.defineBackend())); Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql");
assertNotNull(primeTestDatabaseSqlStream); assertNotNull(primeTestDatabaseSqlStream);
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream); List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
@ -84,7 +83,7 @@ public class TestUtils
public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
{ {
ConnectionManager connectionManager = new ConnectionManager(); ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(defineBackend())); Connection connection = connectionManager.getConnection(defineBackend());
QueryManager.executeStatement(connection, sql, resultSetProcessor); QueryManager.executeStatement(connection, sql, resultSetProcessor);
} }
@ -123,16 +122,15 @@ public class TestUtils
** Define the h2 rdbms backend ** Define the h2 rdbms backend
** **
*******************************************************************************/ *******************************************************************************/
public static QBackendMetaData defineBackend() public static RDBMSBackendMetaData defineBackend()
{ {
return new QBackendMetaData() return (new RDBMSBackendMetaData()
.withName("default") .withVendor("h2")
.withType("rdbms") .withHostName("mem")
.withValue("vendor", "h2") .withDatabaseName("test_database")
.withValue("hostName", "mem") .withUsername("sa")
.withValue("databaseName", "test_database") .withPassword("")
.withValue("username", "sa") .withName("default"));
.withValue("password", "");
} }