diff --git a/qqq-backend-core/pom.xml b/qqq-backend-core/pom.xml
index 268945bc..8a952f04 100644
--- a/qqq-backend-core/pom.xml
+++ b/qqq-backend-core/pom.xml
@@ -36,11 +36,26 @@
+
+
+
+ software.amazon.awssdk
+ bom
+ 2.17.259
+ pom
+ import
+
+
+
+
+ software.amazon.awssdk
+ quicksight
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
index 4d478757..a0d3ec0a 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java
@@ -103,6 +103,7 @@ public class QCodeLoader
/*******************************************************************************
**
*******************************************************************************/
+ @SuppressWarnings("unchecked")
public static T getBackendStep(Class expectedType, QCodeReference codeReference)
{
if(codeReference == null)
@@ -121,9 +122,46 @@ public class QCodeLoader
try
{
Class> customizerClass = Class.forName(codeReference.getName());
- @SuppressWarnings("unchecked")
- T t = (T) customizerClass.getConstructor().newInstance();
- return t;
+ return ((T) customizerClass.getConstructor().newInstance());
+ }
+ catch(Exception e)
+ {
+ LOG.error("Error initializing customizer: " + codeReference);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // return null here - under the assumption that during normal run-time operations, we'll never hit here //
+ // as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
+ // if it finds an invalid code reference //
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////
+ return (null);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("unchecked")
+ public static T getAdHoc(Class expectedType, QCodeReference codeReference)
+ {
+ if(codeReference == null)
+ {
+ return (null);
+ }
+
+ if(!codeReference.getCodeType().equals(QCodeType.JAVA))
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
+ ///////////////////////////////////////////////////////////////////////////////////////
+ throw (new IllegalArgumentException("Only JAVA code references are supported at this time."));
+ }
+
+ try
+ {
+ Class> customizerClass = Class.forName(codeReference.getName());
+ return ((T) customizerClass.getConstructor().newInstance());
}
catch(Exception e)
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java
new file mode 100644
index 00000000..7919b928
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java
@@ -0,0 +1,21 @@
+package com.kingsrook.qqq.backend.core.actions.dashboard;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public abstract class AbstractWidgetRenderer
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public abstract Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface qWidgetMetaData) throws QException;
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java
new file mode 100644
index 00000000..74687c75
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/QuickSightChartRenderer.java
@@ -0,0 +1,77 @@
+package com.kingsrook.qqq.backend.core.actions.dashboard;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QuickSightChart;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QuickSightChartMetaData;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.quicksight.QuickSightClient;
+import software.amazon.awssdk.services.quicksight.model.GenerateEmbedUrlForRegisteredUserRequest;
+import software.amazon.awssdk.services.quicksight.model.GenerateEmbedUrlForRegisteredUserResponse;
+import software.amazon.awssdk.services.quicksight.model.RegisteredUserDashboardEmbeddingConfiguration;
+import software.amazon.awssdk.services.quicksight.model.RegisteredUserEmbeddingExperienceConfiguration;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QuickSightChartRenderer extends AbstractWidgetRenderer
+{
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface metaData) throws QException
+ {
+ try
+ {
+ QuickSightChartMetaData quickSightMetaData = (QuickSightChartMetaData) metaData;
+ QuickSightClient quickSightClient = getQuickSightClient(quickSightMetaData);
+
+ final RegisteredUserEmbeddingExperienceConfiguration experienceConfiguration = RegisteredUserEmbeddingExperienceConfiguration.builder()
+ .dashboard(
+ RegisteredUserDashboardEmbeddingConfiguration.builder()
+ .initialDashboardId(quickSightMetaData.getDashboardId())
+ .build())
+ .build();
+
+ final GenerateEmbedUrlForRegisteredUserRequest generateEmbedUrlForRegisteredUserRequest = GenerateEmbedUrlForRegisteredUserRequest.builder()
+ .awsAccountId(quickSightMetaData.getAccountId())
+ .userArn(quickSightMetaData.getUserArn())
+ .experienceConfiguration(experienceConfiguration)
+ .build();
+
+ final GenerateEmbedUrlForRegisteredUserResponse generateEmbedUrlForRegisteredUserResponse = quickSightClient.generateEmbedUrlForRegisteredUser(generateEmbedUrlForRegisteredUserRequest);
+
+ String embedUrl = generateEmbedUrlForRegisteredUserResponse.embedUrl();
+ return (new QuickSightChart(metaData.getName(), quickSightMetaData.getLabel(), embedUrl));
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error rendering widget", e));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private QuickSightClient getQuickSightClient(QuickSightChartMetaData metaData)
+ {
+ AwsBasicCredentials awsCredentials = AwsBasicCredentials.create(metaData.getAccessKey(), metaData.getSecretKey());
+
+ QuickSightClient amazonQuickSightClient = QuickSightClient.builder()
+ .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
+ .region(Region.of(metaData.getRegion()))
+ .build();
+
+ return (amazonQuickSightClient);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoader.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoader.java
new file mode 100644
index 00000000..de346886
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoader.java
@@ -0,0 +1,27 @@
+package com.kingsrook.qqq.backend.core.actions.dashboard;
+
+
+import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class WidgetDataLoader
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Object execute(QInstance qInstance, QSession session, String name) throws QException
+ {
+ QWidgetMetaDataInterface widget = qInstance.getWidget(name);
+ AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, widget.getCodeReference());
+ Object w = widgetRenderer.render(qInstance, session, widget);
+ return (widgetRenderer.render(qInstance, session, widget));
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java
index d3ae8884..7549d047 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreter.java
@@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.instances;
import java.lang.reflect.Method;
+import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException;
+import io.github.cdimascio.dotenv.Dotenv;
+import io.github.cdimascio.dotenv.DotenvEntry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -45,7 +48,22 @@ public class QMetaDataVariableInterpreter
{
private static final Logger LOG = LogManager.getLogger(QMetaDataVariableInterpreter.class);
- private Map customEnvironment;
+ private Map environmentOverrides;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QMetaDataVariableInterpreter()
+ {
+ environmentOverrides = new HashMap<>();
+ Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
+ for(DotenvEntry e : dotenv.entries())
+ {
+ environmentOverrides.put(e.getKey(), e.getValue());
+ }
+ }
@@ -86,7 +104,7 @@ public class QMetaDataVariableInterpreter
//////////////////////////////////////////////////////////////////////////////////////////////
// get the value - if it's null, move on, else, interpret it, and put it back in the object //
//////////////////////////////////////////////////////////////////////////////////////////////
- Object value = getter.invoke(o);
+ Object value = getter.invoke(o);
if(value == null)
{
continue;
@@ -124,7 +142,7 @@ public class QMetaDataVariableInterpreter
if(value.startsWith(envPrefix) && value.endsWith("}"))
{
String envVarName = value.substring(envPrefix.length()).replaceFirst("}$", "");
- String envValue = getEnvironment().get(envVarName);
+ String envValue = getEnvironmentVariable(envVarName);
return (envValue);
}
@@ -149,13 +167,13 @@ public class QMetaDataVariableInterpreter
/*******************************************************************************
- ** Setter for customEnvironment - protected - meant to be called (at least at this
+ ** Setter for environmentOverrides - protected - meant to be called (at least at this
** time), only in unit test
**
*******************************************************************************/
- protected void setCustomEnvironment(Map customEnvironment)
+ protected void setEnvironmentOverrides(Map environmentOverrides)
{
- this.customEnvironment = customEnvironment;
+ this.environmentOverrides = environmentOverrides;
}
@@ -163,13 +181,13 @@ public class QMetaDataVariableInterpreter
/*******************************************************************************
**
*******************************************************************************/
- private Map getEnvironment()
+ private String getEnvironmentVariable(String key)
{
- if(this.customEnvironment != null)
+ if(this.environmentOverrides.containsKey(key))
{
- return (this.customEnvironment);
+ return (this.environmentOverrides.get(key));
}
- return System.getenv();
+ return System.getenv(key);
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/BarChart.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/BarChart.java
new file mode 100644
index 00000000..2d8e8667
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/BarChart.java
@@ -0,0 +1,276 @@
+package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
+
+
+import java.util.List;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class BarChart implements QWidget
+{
+
+ /*
+ type: "barChart",
+ title: "Parcel Invoice Lines per Month",
+ barChartData: {
+ labels: ["Feb 22", "Mar 22", "Apr 22", "May 22", "Jun 22", "Jul 22", "Aug 22"],
+ datasets: {label: "Parcel Invoice Lines", data: [50000, 22000, 11111, 22333, 40404, 9876, 2355]},
+ },
+ */
+
+ private String title;
+ private Data barChartData;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public BarChart(String title, String seriesLabel, List labels, List data)
+ {
+ setTitle(title);
+ setBarChartData(new BarChart.Data()
+ .withLabels(labels)
+ .withDatasets(new BarChart.Data.DataSet()
+ .withLabel(seriesLabel)
+ .withData(data)));
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for type
+ **
+ *******************************************************************************/
+ public String getType()
+ {
+ return "barChart";
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for title
+ **
+ *******************************************************************************/
+ public String getTitle()
+ {
+ return title;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for title
+ **
+ *******************************************************************************/
+ public void setTitle(String title)
+ {
+ this.title = title;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for title
+ **
+ *******************************************************************************/
+ public BarChart withTitle(String title)
+ {
+ this.title = title;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for barChartData
+ **
+ *******************************************************************************/
+ public Data getBarChartData()
+ {
+ return barChartData;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for barChartData
+ **
+ *******************************************************************************/
+ public void setBarChartData(Data barChartData)
+ {
+ this.barChartData = barChartData;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for barChartData
+ **
+ *******************************************************************************/
+ public BarChart withBarChartData(Data barChartData)
+ {
+ this.barChartData = barChartData;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class Data
+ {
+ private List labels;
+ private DataSet datasets;
+
+
+
+ /*******************************************************************************
+ ** Getter for labels
+ **
+ *******************************************************************************/
+ public List getLabels()
+ {
+ return labels;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for labels
+ **
+ *******************************************************************************/
+ public void setLabels(List labels)
+ {
+ this.labels = labels;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for labels
+ **
+ *******************************************************************************/
+ public Data withLabels(List labels)
+ {
+ this.labels = labels;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for datasets
+ **
+ *******************************************************************************/
+ public DataSet getDatasets()
+ {
+ return datasets;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for datasets
+ **
+ *******************************************************************************/
+ public void setDatasets(DataSet datasets)
+ {
+ this.datasets = datasets;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for datasets
+ **
+ *******************************************************************************/
+ public Data withDatasets(DataSet datasets)
+ {
+ this.datasets = datasets;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public static class DataSet
+ {
+ private String label;
+ private List data;
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for label
+ **
+ *******************************************************************************/
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for label
+ **
+ *******************************************************************************/
+ public DataSet withLabel(String label)
+ {
+ this.label = label;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for data
+ **
+ *******************************************************************************/
+ public List getData()
+ {
+ return data;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for data
+ **
+ *******************************************************************************/
+ public void setData(List data)
+ {
+ this.data = data;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for data
+ **
+ *******************************************************************************/
+ public DataSet withData(List data)
+ {
+ this.data = data;
+ return (this);
+ }
+ }
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/QWidget.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/QWidget.java
new file mode 100644
index 00000000..8c76fc52
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/QWidget.java
@@ -0,0 +1,14 @@
+package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface QWidget
+{
+ /*******************************************************************************
+ ** Getter for type
+ *******************************************************************************/
+ String getType();
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/QuickSightChart.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/QuickSightChart.java
new file mode 100644
index 00000000..9cccd508
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/QuickSightChart.java
@@ -0,0 +1,139 @@
+package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QuickSightChart implements QWidget
+{
+ private String label;
+ private String name;
+ private String url;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QuickSightChart(String name, String label, String url)
+ {
+ this.url = url;
+ this.name = name;
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for type
+ **
+ *******************************************************************************/
+ public String getType()
+ {
+ return "quickSightChart";
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for url
+ **
+ *******************************************************************************/
+ public String getUrl()
+ {
+ return url;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for url
+ **
+ *******************************************************************************/
+ public void setUrl(String url)
+ {
+ this.url = url;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for url
+ **
+ *******************************************************************************/
+ public QuickSightChart withUrl(String url)
+ {
+ this.url = url;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for name
+ **
+ *******************************************************************************/
+ public String getName()
+ {
+ return name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for name
+ **
+ *******************************************************************************/
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ **
+ *******************************************************************************/
+ public QuickSightChart withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for label
+ **
+ *******************************************************************************/
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for label
+ **
+ *******************************************************************************/
+ public QuickSightChart withLabel(String label)
+ {
+ this.label = label;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
index c2b08d6b..c221d013 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java
@@ -30,6 +30,7 @@ import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
@@ -62,6 +63,8 @@ public class QInstance
private Map processes = new LinkedHashMap<>();
private Map apps = new LinkedHashMap<>();
+ private Map widgets = new LinkedHashMap<>();
+
// todo - lock down the object (no more changes allowed) after it's been validated?
@JsonIgnore
@@ -524,4 +527,60 @@ public class QInstance
{
this.authentication = authentication;
}
+
+
+
+ /*******************************************************************************
+ ** Getter for widgets
+ **
+ *******************************************************************************/
+ public Map getWidgets()
+ {
+ return widgets;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for widgets
+ **
+ *******************************************************************************/
+ public void setWidgets(Map widgets)
+ {
+ this.widgets = widgets;
+ }
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addWidget(QWidgetMetaDataInterface widget)
+ {
+ this.addWidget(widget.getName(), widget);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void addWidget(String name, QWidgetMetaDataInterface widget)
+ {
+ if(this.widgets.containsKey(name))
+ {
+ throw (new IllegalArgumentException("Attempted to add a second widget with name: " + name));
+ }
+ this.widgets.put(name, widget);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QWidgetMetaDataInterface getWidget(String name)
+ {
+ return (this.widgets.get(name));
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java
new file mode 100644
index 00000000..2a9594b7
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaData.java
@@ -0,0 +1,83 @@
+package com.kingsrook.qqq.backend.core.model.metadata.dashboard;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QWidgetMetaData implements QWidgetMetaDataInterface
+{
+ protected String name;
+ protected QCodeReference codeReference;
+
+
+
+ /*******************************************************************************
+ ** Getter for name
+ **
+ *******************************************************************************/
+ public String getName()
+ {
+ return name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for name
+ **
+ *******************************************************************************/
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ **
+ *******************************************************************************/
+ public QWidgetMetaData withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for codeReference
+ **
+ *******************************************************************************/
+ public QCodeReference getCodeReference()
+ {
+ return codeReference;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for codeReference
+ **
+ *******************************************************************************/
+ public void setCodeReference(QCodeReference codeReference)
+ {
+ this.codeReference = codeReference;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for codeReference
+ **
+ *******************************************************************************/
+ public QWidgetMetaData withCodeReference(QCodeReference codeReference)
+ {
+ this.codeReference = codeReference;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java
new file mode 100644
index 00000000..771c8d33
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QWidgetMetaDataInterface.java
@@ -0,0 +1,42 @@
+package com.kingsrook.qqq.backend.core.model.metadata.dashboard;
+
+
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public interface QWidgetMetaDataInterface
+{
+ /*******************************************************************************
+ ** Getter for name
+ *******************************************************************************/
+ String getName();
+
+ /*******************************************************************************
+ ** Setter for name
+ *******************************************************************************/
+ void setName(String name);
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ *******************************************************************************/
+ QWidgetMetaDataInterface withName(String name);
+
+ /*******************************************************************************
+ ** Getter for codeReference
+ *******************************************************************************/
+ QCodeReference getCodeReference();
+
+ /*******************************************************************************
+ ** Setter for codeReference
+ *******************************************************************************/
+ void setCodeReference(QCodeReference codeReference);
+
+ /*******************************************************************************
+ ** Fluent setter for codeReference
+ *******************************************************************************/
+ QWidgetMetaDataInterface withCodeReference(QCodeReference codeReference);
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QuickSightChartMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QuickSightChartMetaData.java
new file mode 100644
index 00000000..114804a4
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/QuickSightChartMetaData.java
@@ -0,0 +1,305 @@
+package com.kingsrook.qqq.backend.core.model.metadata.dashboard;
+
+
+import java.util.Collection;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class QuickSightChartMetaData extends QWidgetMetaData implements QWidgetMetaDataInterface
+{
+ private String label;
+ private String accessKey;
+ private String secretKey;
+ private String dashboardId;
+ private String accountId;
+ private String userArn;
+ private String region;
+ private Collection allowedDomains;
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for name
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withName(String name)
+ {
+ this.name = name;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for accessKey
+ **
+ *******************************************************************************/
+ public String getAccessKey()
+ {
+ return accessKey;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for accessKey
+ **
+ *******************************************************************************/
+ public void setAccessKey(String accessKey)
+ {
+ this.accessKey = accessKey;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for accessKey
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withAccessKey(String accessKey)
+ {
+ this.accessKey = accessKey;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for label
+ **
+ *******************************************************************************/
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for label
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withLabel(String label)
+ {
+ this.label = label;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for secretKey
+ **
+ *******************************************************************************/
+ public String getSecretKey()
+ {
+ return secretKey;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for secretKey
+ **
+ *******************************************************************************/
+ public void setSecretKey(String secretKey)
+ {
+ this.secretKey = secretKey;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for secretKey
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withSecretKey(String secretKey)
+ {
+ this.secretKey = secretKey;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for dashboardId
+ **
+ *******************************************************************************/
+ public String getDashboardId()
+ {
+ return dashboardId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for dashboardId
+ **
+ *******************************************************************************/
+ public void setDashboardId(String dashboardId)
+ {
+ this.dashboardId = dashboardId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for dashboardId
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withDashboardId(String dashboardId)
+ {
+ this.dashboardId = dashboardId;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for accountId
+ **
+ *******************************************************************************/
+ public String getAccountId()
+ {
+ return accountId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for accountId
+ **
+ *******************************************************************************/
+ public void setAccountId(String accountId)
+ {
+ this.accountId = accountId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for accountId
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withAccountId(String accountId)
+ {
+ this.accountId = accountId;
+ return this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for userArn
+ **
+ *******************************************************************************/
+ public String getUserArn()
+ {
+ return userArn;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for userArn
+ **
+ *******************************************************************************/
+ public void setUserArn(String userArn)
+ {
+ this.userArn = userArn;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for userArn
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withUserArn(String userArn)
+ {
+ this.userArn = userArn;
+ return this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for region
+ **
+ *******************************************************************************/
+ public String getRegion()
+ {
+ return region;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for region
+ **
+ *******************************************************************************/
+ public void setRegion(String region)
+ {
+ this.region = region;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for region
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withRegion(String region)
+ {
+ this.region = region;
+ return this;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for allowedDomains
+ **
+ *******************************************************************************/
+ public Collection getAllowedDomains()
+ {
+ return allowedDomains;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for allowedDomains
+ **
+ *******************************************************************************/
+ public void setAllowedDomains(Collection allowedDomains)
+ {
+ this.allowedDomains = allowedDomains;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for allowedDomains
+ **
+ *******************************************************************************/
+ public QuickSightChartMetaData withAllowedDomains(Collection allowedDomains)
+ {
+ this.allowedDomains = allowedDomains;
+ return this;
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java
index 6f792f97..a4bd3ba4 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java
@@ -26,7 +26,8 @@ import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
+import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
@@ -40,6 +41,7 @@ public class QFrontendAppMetaData
private String label;
private List children = new ArrayList<>();
+ private List widgets = new ArrayList<>();
private String iconName;
@@ -48,14 +50,19 @@ public class QFrontendAppMetaData
/*******************************************************************************
**
*******************************************************************************/
- public QFrontendAppMetaData(QAppChildMetaData appChildMetaData)
+ public QFrontendAppMetaData(QAppMetaData appMetaData)
{
- this.name = appChildMetaData.getName();
- this.label = appChildMetaData.getLabel();
+ this.name = appMetaData.getName();
+ this.label = appMetaData.getLabel();
- if(appChildMetaData.getIcon() != null)
+ if(appMetaData.getIcon() != null)
{
- this.iconName = appChildMetaData.getIcon().getName();
+ this.iconName = appMetaData.getIcon().getName();
+ }
+
+ if(CollectionUtils.nullSafeHasContents(appMetaData.getWidgets()))
+ {
+ this.widgets = appMetaData.getWidgets();
}
}
@@ -127,4 +134,15 @@ public class QFrontendAppMetaData
}
children.add(childAppTreeNode);
}
+
+
+
+ /*******************************************************************************
+ ** Getter for widgets
+ **
+ *******************************************************************************/
+ public List getWidgets()
+ {
+ return widgets;
+ }
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java
index 36ac02d6..be38cb87 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java
@@ -40,6 +40,7 @@ public class QAppMetaData implements QAppChildMetaData
private String parentAppName;
private QIcon icon;
+ private List widgets;
/*******************************************************************************
@@ -235,4 +236,38 @@ public class QAppMetaData implements QAppChildMetaData
return (this);
}
+
+
+
+ /*******************************************************************************
+ ** Getter for widgets
+ **
+ *******************************************************************************/
+ public List getWidgets()
+ {
+ return widgets;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for widgets
+ **
+ *******************************************************************************/
+ public void setWidgets(List widgets)
+ {
+ this.widgets = widgets;
+ }
+
+
+ /*******************************************************************************
+ ** Fluent setter for widgets
+ **
+ *******************************************************************************/
+ public QAppMetaData withWidgets(List widgets)
+ {
+ this.widgets = widgets;
+ return (this);
+ }
+
}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java
new file mode 100644
index 00000000..06997d0a
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java
@@ -0,0 +1,51 @@
+package com.kingsrook.qqq.backend.core.actions.dashboard;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.BarChart;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+
+
+/*******************************************************************************
+ ** Sample bar chart widget
+ *******************************************************************************/
+public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
+{
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Override
+ public Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface metaData) throws QException
+ {
+ try
+ {
+ List labels = new ArrayList<>();
+ List data = new ArrayList<>();
+
+ labels.add("Jan. 2022");
+ data.add(17);
+
+ labels.add("Feb. 2022");
+ data.add(42);
+
+ labels.add("Mar. 2022");
+ data.add(47);
+
+ labels.add("Apr. 2022");
+ data.add(0);
+
+ labels.add("May 2022");
+ data.add(64);
+
+ return (new BarChart("Persons created per Month", "Person records", labels, data));
+ }
+ catch(Exception e)
+ {
+ throw (new QException("Error rendering widget", e));
+ }
+ }
+}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java
new file mode 100644
index 00000000..c571e361
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java
@@ -0,0 +1,32 @@
+package com.kingsrook.qqq.backend.core.actions.dashboard;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.dashboard.widgets.BarChart;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+/*******************************************************************************
+ ** Unit test for WidgetDataLoader
+ *******************************************************************************/
+class WidgetDataLoaderTest
+{
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test() throws QException
+ {
+ Object widgetData = new WidgetDataLoader().execute(TestUtils.defineInstance(), TestUtils.getMockSession(), PersonsByCreateDateBarChart.class.getSimpleName());
+ assertThat(widgetData).isInstanceOf(BarChart.class);
+ BarChart barChart = (BarChart) widgetData;
+ assertEquals("barChart", barChart.getType());
+ assertThat(barChart.getTitle()).isNotBlank();
+ assertNotNull(barChart.getBarChartData());
+ }
+
+}
\ No newline at end of file
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java
index e3881b00..d40a82d9 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QMetaDataVariableInterpreterTest.java
@@ -102,7 +102,7 @@ class QMetaDataVariableInterpreterTest
QMetaDataVariableInterpreter secretReader = new QMetaDataVariableInterpreter();
String key = "CUSTOM_PROPERTY";
String value = "ABCD-9876";
- secretReader.setCustomEnvironment(Map.of(key, value));
+ secretReader.setEnvironmentOverrides(Map.of(key, value));
assertNull(secretReader.interpret(null));
assertEquals("foo", secretReader.interpret("foo"));
@@ -278,4 +278,4 @@ class QMetaDataVariableInterpreterTest
}
}
-}
\ No newline at end of file
+}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
index b0079f5b..b762d42f 100644
--- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
+import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarChart;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge;
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
@@ -52,6 +53,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProvi
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
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.layout.QAppMetaData;
@@ -147,6 +149,7 @@ public class TestUtils
qInstance.addAutomationProvider(definePollingAutomationProvider());
+ defineWidgets(qInstance);
defineApps(qInstance);
return (qInstance);
@@ -154,6 +157,19 @@ public class TestUtils
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private static void defineWidgets(QInstance qInstance)
+ {
+ qInstance.addWidget(new QWidgetMetaData()
+ .withName(PersonsByCreateDateBarChart.class.getSimpleName())
+ .withCodeReference(new QCodeReference(PersonsByCreateDateBarChart.class, null)));
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -180,7 +196,8 @@ public class TestUtils
.withName(APP_NAME_PEOPLE)
.withChild(qInstance.getTable(TABLE_NAME_PERSON))
.withChild(qInstance.getTable(TABLE_NAME_PERSON_FILE))
- .withChild(qInstance.getApp(APP_NAME_GREETINGS)));
+ .withChild(qInstance.getApp(APP_NAME_GREETINGS))
+ .withWidgets(List.of(PersonsByCreateDateBarChart.class.getSimpleName())));
qInstance.addApp(new QAppMetaData()
.withName(APP_NAME_MISCELLANEOUS)
diff --git a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaData.java b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaData.java
index 2475b6b5..652913f7 100644
--- a/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaData.java
+++ b/qqq-backend-module-filesystem/src/main/java/com/kingsrook/qqq/backend/module/filesystem/s3/model/metadata/S3BackendMetaData.java
@@ -50,6 +50,19 @@ public class S3BackendMetaData extends AbstractFilesystemBackendMetaData
+ /*******************************************************************************
+ ** Fluent setter for backendType
+ **
+ *******************************************************************************/
+ @Override
+ public S3BackendMetaData withBackendType(String backendType)
+ {
+ setBackendType(backendType);
+ return this;
+ }
+
+
+
/*******************************************************************************
** Getter for bucketName
**
diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java
index ce761755..653e8731 100644
--- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java
@@ -298,8 +298,6 @@ public class QueryManager
*******************************************************************************/
public static List