From 4bf29807e3f49489533713e9a0a11e6b37798df8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 12 Feb 2024 15:00:19 -0600 Subject: [PATCH] CE-847 New process to manually run automations (the same code that scheduler runs - so that'll only process records that are pending). --- .../RunTableAutomationsProcessStep.java | 152 ++++++++++++++++++ .../RunTableAutomationsProcessStepTest.java | 97 +++++++++++ 2 files changed, 249 insertions(+) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStep.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStepTest.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStep.java new file mode 100644 index 00000000..29f1c2f1 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStep.java @@ -0,0 +1,152 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.automation; + + +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.automation.polling.PollingAutomationPerTableRunner; +import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface; +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.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +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.processes.QBackendStepMetaData; +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.QFunctionInputMetaData; +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.StringUtils; + + +/******************************************************************************* + ** Process to manually run table automations, for a table. + ** + ** Useful, maybe, for an e2e test. Or, if you don't want jobs to be running, + ** but want to run automations by-hand, for some reason. + ** + ** In the future, this class could take a param to only do inserts or updates. + ** + ** Also, right now, only records that are Pending automations will be run - + ** again, that could be changed, presumably (take a list of records, always run, etc...) + *******************************************************************************/ +public class RunTableAutomationsProcessStep implements BackendStep, MetaDataProducerInterface +{ + public static final String NAME = "RunTableAutomationsProcess"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QProcessMetaData produce(QInstance qInstance) throws QException + { + QProcessMetaData processMetaData = new QProcessMetaData() + .withName(NAME) + .withStepList(List.of( + new QFrontendStepMetaData() + .withName("input") + .withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM)) + .withFormField(new QFieldMetaData("tableName", QFieldType.STRING).withIsRequired(true)) + .withFormField(new QFieldMetaData("automationProviderName", QFieldType.STRING)), + new QBackendStepMetaData() + .withName("run") + .withCode(new QCodeReference(getClass())) + .withInputData(new QFunctionInputMetaData() + .withField(new QFieldMetaData("tableName", QFieldType.STRING)) + .withField(new QFieldMetaData("automationProviderName", QFieldType.STRING))), + new QFrontendStepMetaData() + .withName("output") + .withComponent(new QFrontendComponentMetaData().withType(QComponentType.VIEW_FORM)) + .withFormField(new QFieldMetaData("ok", QFieldType.STRING)) + )); + + return (processMetaData); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException + { + QInstance qInstance = QContext.getQInstance(); + + //////////////////////////////////////////////////////////////////// + // get tableName param (since this process is not table-specific) // + //////////////////////////////////////////////////////////////////// + String tableName = runBackendStepInput.getValueString("tableName"); + if(!StringUtils.hasContent(tableName)) + { + throw (new QException("Missing required input value: tableName")); + } + + if(!QContext.getQInstance().getTables().containsKey(tableName)) + { + throw (new QException("Unrecognized table name: " + tableName)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // get the automation provider name to use - either as the only-one-in-instance, or via param // + //////////////////////////////////////////////////////////////////////////////////////////////// + String automationProviderName = runBackendStepInput.getValueString("automationProviderName"); + if(!StringUtils.hasContent(automationProviderName)) + { + Map automationProviders = CollectionUtils.nonNullMap(qInstance.getAutomationProviders()); + if(automationProviders.size() == 1) + { + automationProviderName = automationProviders.keySet().iterator().next(); + } + else + { + throw (new QException("Missing required input value: automationProviderName (and there is not exactly 1 in the active instance)")); + } + } + + ///////////////////////////////////////////// + // run automations for the requested table // + ///////////////////////////////////////////// + List tableActions = PollingAutomationPerTableRunner.getTableActions(qInstance, automationProviderName); + for(PollingAutomationPerTableRunner.TableActionsInterface tableAction : tableActions) + { + if(tableName.equals(tableAction.tableName())) + { + PollingAutomationPerTableRunner pollingAutomationPerTableRunner = new PollingAutomationPerTableRunner(qInstance, automationProviderName, () -> QContext.getQSession(), tableAction); + pollingAutomationPerTableRunner.processTableInsertOrUpdate(qInstance.getTable(tableAction.tableName()), tableAction.status()); + } + } + + runBackendStepOutput.addValue("ok", "true"); + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStepTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStepTest.java new file mode 100644 index 00000000..0a5bd9f0 --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/automation/RunTableAutomationsProcessStepTest.java @@ -0,0 +1,97 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2024. 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.automation; + + +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +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.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.TestUtils; +import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/******************************************************************************* + ** Unit test for RunTableAutomationsProcessStep + *******************************************************************************/ +class RunTableAutomationsProcessStepTest extends BaseTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() throws Exception + { + UnsafeSupplier getAutomationStatus = () -> new GetAction().executeForRecord(new GetInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withPrimaryKey(1)).getValueInteger("qqqAutomationStatus"); + + new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecord(new QRecord())); + assertEquals(AutomationStatus.PENDING_INSERT_AUTOMATIONS.getId(), getAutomationStatus.get()); + + RunBackendStepInput input = new RunBackendStepInput(); + input.addValue("tableName", TestUtils.TABLE_NAME_PERSON_MEMORY); + RunBackendStepOutput output = new RunBackendStepOutput(); + new RunTableAutomationsProcessStep().run(input, output); + assertEquals("true", output.getValue("ok")); + + assertEquals(AutomationStatus.OK.getId(), getAutomationStatus.get()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testThrowsWithoutTableName() throws QException + { + RunBackendStepInput input = new RunBackendStepInput(); + RunBackendStepOutput output = new RunBackendStepOutput(); + assertThatThrownBy(() -> new RunTableAutomationsProcessStep().run(input, output)) + .hasMessageContaining("Missing required input value: tableName"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testThrowsWithInvalidTableName() throws QException + { + RunBackendStepInput input = new RunBackendStepInput(); + RunBackendStepOutput output = new RunBackendStepOutput(); + input.addValue("tableName", "asdf"); + assertThatThrownBy(() -> new RunTableAutomationsProcessStep().run(input, output)) + .hasMessageContaining("Unrecognized table name: asdf"); + } + +} \ No newline at end of file