diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java
new file mode 100644
index 00000000..1efa034e
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractHTMLWidgetRenderer.java
@@ -0,0 +1,124 @@
+/*
+ * 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.dashboard;
+
+
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+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.widgets.RenderWidgetInput;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+
+
+/*******************************************************************************
+ ** Base class for rendering qqq HTML dashboard widgets
+ **
+ *******************************************************************************/
+public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String openTopLevelBulletList()
+ {
+ return ("""
+
+
""");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String closeTopLevelBulletList()
+ {
+ return ("""
+
+
""");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String bulletItalics(String text)
+ {
+ return ("" + text + "");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String bulletLink(String href, String text)
+ {
+ return ("" + text + "");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String bulletNameLink(String name, String href, String text)
+ {
+ return (bulletNameValue(name, "" + text + ""));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String bulletNameValue(String name, String value)
+ {
+ return ("" + name + " " + value + "");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String linkTableBulkLoad(RenderWidgetInput input, String tableName) throws QException
+ {
+ String tablePath = input.getInstance().getTablePath(input, tableName);
+ return (tablePath + "/" + tableName + ".bulkInsert");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected String linkTableFilter(RenderWidgetInput input, String tableName, QQueryFilter filter) throws QException
+ {
+ String tablePath = input.getInstance().getTablePath(input, tableName);
+ return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java
index 2490aefa..18fb4a73 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtils.java
@@ -28,9 +28,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+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;
@@ -39,6 +44,7 @@ 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.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@@ -174,7 +180,7 @@ public class GeneralProcessUtils
** key, or any other field on the table. Note, if multiple rows do match the value,
** only 1 (determined in an unspecified way) is returned.
*******************************************************************************/
- public static Optional getRecordById(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
+ public static Optional getRecordByField(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
{
QueryInput queryInput = new QueryInput(parentActionInput.getInstance());
queryInput.setSession(parentActionInput.getSession());
@@ -187,6 +193,43 @@ public class GeneralProcessUtils
+ /*******************************************************************************
+ ** Query to get one record by a unique key value.
+ *******************************************************************************/
+ public static QRecord getRecordByFieldOrElseThrow(AbstractActionInput parentActionInput, String tableName, String fieldName, Serializable fieldValue) throws QException
+ {
+ return getRecordByField(parentActionInput, tableName, fieldName, fieldValue)
+ .orElseThrow(() -> new QException(tableName + " with " + fieldName + " of " + fieldValue + " was not found."));
+ }
+
+
+
+ /*******************************************************************************
+ ** Query to get one record by its primary key value.
+ *******************************************************************************/
+ public static Optional getRecordByPrimaryKey(AbstractActionInput parentActionInput, String tableName, Serializable value) throws QException
+ {
+ GetInput getInput = new GetInput(parentActionInput.getInstance());
+ getInput.setSession(parentActionInput.getSession());
+ getInput.setTableName(tableName);
+ getInput.setPrimaryKey(value);
+ GetOutput getOutput = new GetAction().execute(getInput);
+ return (Optional.ofNullable(getOutput.getRecord()));
+ }
+
+
+
+ /*******************************************************************************
+ ** Query to get one record by its primary key value.
+ *******************************************************************************/
+ public static QRecord getRecordByPrimaryKeyOrElseThrow(AbstractActionInput parentActionInput, String tableName, Serializable value) throws QException
+ {
+ return getRecordByPrimaryKey(parentActionInput, tableName, value)
+ .orElseThrow(() -> new QException(tableName + " with primary key of " + value + " was not found."));
+ }
+
+
+
/*******************************************************************************
** Load all rows from a table.
**
@@ -316,4 +359,51 @@ public class GeneralProcessUtils
return (map);
}
+
+
+ /*******************************************************************************
+ ** Ensure that a process has been initiated with a single record as input - and
+ ** get that record id.
+ **
+ *******************************************************************************/
+ public static Integer validateSingleSelectedId(RunBackendStepInput runBackendStepInput, String tableLabel) throws QException
+ {
+ ////////////////////////////////////////////////////
+ // Get the selected recordId and verify we only 1 //
+ ////////////////////////////////////////////////////
+ String recordIds = (String) runBackendStepInput.getValue("recordIds");
+ if(!StringUtils.hasContent(recordIds))
+ {
+ throw new QUserFacingException("Select a " + tableLabel + " to process.");
+ }
+
+ String[] idStrings = recordIds.split(",");
+ if(idStrings.length > 1)
+ {
+ throw new QUserFacingException("Select a single " + tableLabel + " to process.");
+ }
+
+ return (Integer.parseInt(idStrings[0]));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static List recordsToEntities(Class recordEntityClass, List records) throws QException
+ {
+ if(records == null)
+ {
+ return (null);
+ }
+
+ List rs = new ArrayList<>();
+ for(QRecord record : records)
+ {
+ rs.add(QRecordEntity.fromQRecord(recordEntityClass, record));
+ }
+ return (rs);
+ }
+
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java
index ad98187a..f002552a 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/utils/GeneralProcessUtilsTest.java
@@ -241,11 +241,11 @@ class GeneralProcessUtilsTest
QueryInput queryInput = new QueryInput(instance);
queryInput.setSession(new QSession());
- Optional record = GeneralProcessUtils.getRecordById(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "James");
+ Optional record = GeneralProcessUtils.getRecordByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "James");
assertTrue(record.isPresent());
assertEquals(2, record.get().getValueInteger("id"));
- record = GeneralProcessUtils.getRecordById(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "Bobby");
+ record = GeneralProcessUtils.getRecordByField(queryInput, TestUtils.TABLE_NAME_PERSON_MEMORY, "firstName", "Bobby");
assertFalse(record.isPresent());
}