Merge branch 'feature/QQQ-38-app-home-widgets' into feature/sprint-10

This commit is contained in:
2022-08-31 15:59:32 -05:00
31 changed files with 1618 additions and 45 deletions

View File

@ -36,11 +36,26 @@
<!-- noe at this time -->
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.17.259</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- other qqq modules deps -->
<!-- none, this is core. -->
<!-- 3rd party deps specifically for this module -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>quicksight</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>

View File

@ -103,6 +103,7 @@ public class QCodeLoader
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
public static <T extends BackendStep> T getBackendStep(Class<T> expectedType, QCodeReference codeReference)
{
if(codeReference == null)
@ -121,9 +122,46 @@ public class QCodeLoader
try
{
Class<?> customizerClass = Class.forName(codeReference.getName());
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")
T t = (T) customizerClass.getConstructor().newInstance();
return t;
public static <T> T getAdHoc(Class<T> 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)
{

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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<String, String> customEnvironment;
private Map<String, String> environmentOverrides;
/*******************************************************************************
**
*******************************************************************************/
public QMetaDataVariableInterpreter()
{
environmentOverrides = new HashMap<>();
Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
for(DotenvEntry e : dotenv.entries())
{
environmentOverrides.put(e.getKey(), e.getValue());
}
}
@ -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<String, String> customEnvironment)
protected void setEnvironmentOverrides(Map<String, String> environmentOverrides)
{
this.customEnvironment = customEnvironment;
this.environmentOverrides = environmentOverrides;
}
@ -163,13 +181,13 @@ public class QMetaDataVariableInterpreter
/*******************************************************************************
**
*******************************************************************************/
private Map<String, String> 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);
}
}

View File

@ -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<String> labels, List<Number> 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<String> labels;
private DataSet datasets;
/*******************************************************************************
** Getter for labels
**
*******************************************************************************/
public List<String> getLabels()
{
return labels;
}
/*******************************************************************************
** Setter for labels
**
*******************************************************************************/
public void setLabels(List<String> labels)
{
this.labels = labels;
}
/*******************************************************************************
** Fluent setter for labels
**
*******************************************************************************/
public Data withLabels(List<String> 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<Number> 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<Number> getData()
{
return data;
}
/*******************************************************************************
** Setter for data
**
*******************************************************************************/
public void setData(List<Number> data)
{
this.data = data;
}
/*******************************************************************************
** Fluent setter for data
**
*******************************************************************************/
public DataSet withData(List<Number> data)
{
this.data = data;
return (this);
}
}
}
}

View File

@ -0,0 +1,14 @@
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
/*******************************************************************************
**
*******************************************************************************/
public interface QWidget
{
/*******************************************************************************
** Getter for type
*******************************************************************************/
String getType();
}

View File

@ -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);
}
}

View File

@ -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<String, QProcessMetaData> processes = new LinkedHashMap<>();
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
private Map<String, QWidgetMetaDataInterface> 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<String, QWidgetMetaDataInterface> getWidgets()
{
return widgets;
}
/*******************************************************************************
** Setter for widgets
**
*******************************************************************************/
public void setWidgets(Map<String, QWidgetMetaDataInterface> 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));
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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<String> 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<String> getAllowedDomains()
{
return allowedDomains;
}
/*******************************************************************************
** Setter for allowedDomains
**
*******************************************************************************/
public void setAllowedDomains(Collection<String> allowedDomains)
{
this.allowedDomains = allowedDomains;
}
/*******************************************************************************
** Fluent setter for allowedDomains
**
*******************************************************************************/
public QuickSightChartMetaData withAllowedDomains(Collection<String> allowedDomains)
{
this.allowedDomains = allowedDomains;
return this;
}
}

View File

@ -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<AppTreeNode> children = new ArrayList<>();
private List<String> 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<String> getWidgets()
{
return widgets;
}
}

View File

@ -40,6 +40,7 @@ public class QAppMetaData implements QAppChildMetaData
private String parentAppName;
private QIcon icon;
private List<String> widgets;
/*******************************************************************************
@ -235,4 +236,38 @@ public class QAppMetaData implements QAppChildMetaData
return (this);
}
/*******************************************************************************
** Getter for widgets
**
*******************************************************************************/
public List<String> getWidgets()
{
return widgets;
}
/*******************************************************************************
** Setter for widgets
**
*******************************************************************************/
public void setWidgets(List<String> widgets)
{
this.widgets = widgets;
}
/*******************************************************************************
** Fluent setter for widgets
**
*******************************************************************************/
public QAppMetaData withWidgets(List<String> widgets)
{
this.widgets = widgets;
return (this);
}
}

View File

@ -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<String> labels = new ArrayList<>();
List<Number> 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));
}
}
}

View File

@ -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());
}
}

View File

@ -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"));

View File

@ -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)

View File

@ -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
**

View File

@ -298,8 +298,6 @@ public class QueryManager
*******************************************************************************/
public static List<Map<String, Object>> executeStatementForRows(Connection connection, String sql, Object... params) throws SQLException
{
throw (new NotImplementedException());
/*
List<Map<String, Object>> rs = new ArrayList<>();
PreparedStatement statement = prepareStatementAndBindParams(connection, sql, params);
@ -318,7 +316,6 @@ public class QueryManager
}
return (rs);
*/
}

View File

@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -101,13 +102,21 @@ class ConnectionManagerTest
private RDBMSBackendMetaData getAuroraBacked()
{
QMetaDataVariableInterpreter interpreter = new QMetaDataVariableInterpreter();
String vendor = interpreter.interpret("${env.RDBMS_VENDOR}");
String hostname = interpreter.interpret("${env.RDBMS_HOSTNAME}");
Integer port = Integer.valueOf(interpreter.interpret("${env.RDBMS_PORT}"));
String databaseName = interpreter.interpret("${env.RDBMS_DATABASE_NAME}");
String username = interpreter.interpret("${env.RDBMS_USERNAME}");
String password= interpreter.interpret("${env.RDBMS_PASSWORD}");
return new RDBMSBackendMetaData()
.withName("aurora-test")
.withVendor("aurora")
.withHostName("nf-one-development-aurora.cwuhqcx1inwx.us-east-2.rds.amazonaws.com")
.withPort(3306)
.withDatabaseName("nutrifresh_one")
.withUsername("nf_admin")
.withPassword("%!2rwcH+fb#WgPg");
.withVendor(vendor)
.withHostName(hostname)
.withPort(port)
.withDatabaseName(databaseName)
.withUsername(username)
.withPassword(password);
}
}

View File

@ -36,6 +36,8 @@ import java.time.LocalTime;
import java.time.Month;
import java.time.OffsetDateTime;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -381,4 +383,25 @@ class QueryManagerTest
assertEquals("Q", simpleEntity.get("CHAR_COL"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testQueryForRows() throws SQLException
{
Connection connection = getConnection();
QueryManager.executeUpdate(connection, """
INSERT INTO test_table
( int_col, datetime_col, char_col, date_col, time_col )
VALUES
( 47, '2022-08-10 19:22:08', 'Q', '2022-08-10', '19:22:08')
""");
List<Map<String, Object>> rows = QueryManager.executeStatementForRows(connection, "SELECT * FROM test_table");
assertNotNull(rows);
assertEquals(47, rows.get(0).get("INT_COL"));
assertEquals("Q", rows.get(0).get("CHAR_COL"));
}
}

View File

@ -36,6 +36,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
import com.kingsrook.qqq.backend.core.actions.dashboard.WidgetDataLoader;
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
import com.kingsrook.qqq.backend.core.actions.metadata.ProcessMetaDataAction;
import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction;
@ -284,6 +285,8 @@ public class QJavalinImplementation
});
});
get("/widget/{name}", QJavalinImplementation::widget);
////////////////////
// process routes //
////////////////////
@ -668,6 +671,27 @@ public class QJavalinImplementation
/*******************************************************************************
** Load the data for a widget of a given name.
*******************************************************************************/
private static void widget(Context context)
{
try
{
InsertInput insertInput = new InsertInput(qInstance);
setupSession(context, insertInput);
Object widgetData = new WidgetDataLoader().execute(qInstance, insertInput.getSession(), context.pathParam("name"));
context.result(JsonUtils.toJson(widgetData));
}
catch(Exception e)
{
handleException(context, e);
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -0,0 +1,52 @@
package com.kingsrook.qqq.backend.javalin;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer;
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<String> labels = new ArrayList<>();
List<Number> 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));
}
}
}

View File

@ -470,4 +470,21 @@ class QJavalinImplementationTest extends QJavalinTestBase
assertThat(response.getBody()).contains("Unsupported report format");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testWidget()
{
HttpResponse<String> response = Unirest.get(BASE_URL + "/widget/" + PersonsByCreateDateBarChart.class.getSimpleName()).asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertEquals("barChart", jsonObject.getString("type"));
assertNotNull(jsonObject.getString("title"));
assertNotNull(jsonObject.getJSONObject("barChartData"));
}
}

View File

@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
@ -122,11 +123,24 @@ public class TestUtils
qInstance.addProcess(defineProcessSimpleSleep());
qInstance.addProcess(defineProcessScreenThenSleep());
qInstance.addProcess(defineProcessSimpleThrow());
defineWidgets(qInstance);
return (qInstance);
}
/*******************************************************************************
**
*******************************************************************************/
private static void defineWidgets(QInstance qInstance)
{
qInstance.addWidget(new QWidgetMetaData()
.withName(PersonsByCreateDateBarChart.class.getSimpleName())
.withCodeReference(new QCodeReference(PersonsByCreateDateBarChart.class, null)));
}
/*******************************************************************************
** Define the authentication used in standard tests - using 'mock' type.
**

View File

@ -128,9 +128,9 @@
<version>4.10.0</version>
<configuration>
<propertyFile>/src/main/resources/liquibase/liquibase.properties</propertyFile>
<url>${env.LB_DB_URL}</url>
<username>${env.LB_DB_USERNAME}</username>
<password>${env.LB_DB_PASSWORD}</password>
<url>${env.RDBMS_URL}</url>
<username>${env.RDBMS_USERNAME}</username>
<password>${env.RDBMS_PASSWORD}</password>
<contexts>${env.LB_CONTEXTS}</contexts>
</configuration>
</plugin>

View File

@ -26,10 +26,13 @@ import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.amazonaws.regions.Regions;
import com.kingsrook.qqq.backend.core.actions.dashboard.QuickSightChartRenderer;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
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.QAuthenticationType;
@ -37,6 +40,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
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.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QuickSightChartMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
@ -64,6 +70,7 @@ import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.RecordFor
import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemBackendMetaData;
import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemTableBackendDetails;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.sampleapp.dashboard.widgets.PersonsByCreateDateBarChart;
import com.kingsrook.sampleapp.processes.clonepeople.ClonePeopleTransformStep;
import io.github.cdimascio.dotenv.Dotenv;
@ -78,10 +85,6 @@ public class SampleMetaDataProvider
public static final String RDBMS_BACKEND_NAME = "rdbms";
public static final String FILESYSTEM_BACKEND_NAME = "filesystem";
public static final String AUTH0_AUTHENTICATION_MODULE_NAME = "auth0";
// public static final String AUTH0_BASE_URL = "https://kingsrook.us.auth0.com/";
public static final String AUTH0_BASE_URL = "https://nutrifresh-one-development.us.auth0.com/";
public static final String APP_NAME_GREETINGS = "greetingsApp";
public static final String APP_NAME_PEOPLE = "peopleApp";
public static final String APP_NAME_MISCELLANEOUS = "miscellaneous";
@ -125,6 +128,8 @@ public class SampleMetaDataProvider
qInstance.addProcess(defineProcessScreenThenSleep());
qInstance.addProcess(defineProcessSimpleThrow());
defineWidgets(qInstance);
defineApps(qInstance);
return (qInstance);
@ -132,6 +137,37 @@ public class SampleMetaDataProvider
/*******************************************************************************
**
*******************************************************************************/
private static void defineWidgets(QInstance qInstance)
{
qInstance.addWidget(new QWidgetMetaData()
.withName(PersonsByCreateDateBarChart.class.getSimpleName())
.withCodeReference(new QCodeReference(PersonsByCreateDateBarChart.class, null)));
QMetaDataVariableInterpreter interpreter = new QMetaDataVariableInterpreter();
String accountId = interpreter.interpret("${env.QUICKSIGHT_ACCOUNT_ID}");
String accessKey = interpreter.interpret("${env.QUICKSIGHT_ACCESS_KEY}");
String secretKey = interpreter.interpret("${env.QUICKSIGHT_SECRET_KEY}");
String userArn = interpreter.interpret("${env.QUICKSIGHT_USER_ARN}");
QWidgetMetaDataInterface quickSightChartMetaData = new QuickSightChartMetaData()
.withAccountId(accountId)
.withAccessKey(accessKey)
.withSecretKey(secretKey)
.withUserArn(userArn)
.withDashboardId("9e452e78-8509-4c81-bb7f-967abfc356da")
.withRegion(Regions.US_EAST_2.getName())
.withName(QuickSightChartRenderer.class.getSimpleName())
.withLabel("Example Quicksight Chart")
.withCodeReference(new QCodeReference(QuickSightChartRenderer.class, null));
qInstance.addWidget(quickSightChartMetaData);
}
/*******************************************************************************
**
*******************************************************************************/
@ -144,6 +180,7 @@ public class SampleMetaDataProvider
.withChild(qInstance.getTable(TABLE_NAME_PERSON).withIcon(new QIcon().withName("person")))
.withChild(qInstance.getTable(TABLE_NAME_CITY).withIcon(new QIcon().withName("location_city")))
.withChild(qInstance.getProcess(PROCESS_NAME_GREET_INTERACTIVE).withIcon(new QIcon().withName("waving_hand")))
.withWidgets(List.of(PersonsByCreateDateBarChart.class.getSimpleName(), QuickSightChartRenderer.class.getSimpleName()))
);
qInstance.addApp(new QAppMetaData()
@ -183,15 +220,22 @@ public class SampleMetaDataProvider
{
if(USE_MYSQL)
{
Dotenv dotenv = Dotenv.configure().load();
QMetaDataVariableInterpreter interpreter = new QMetaDataVariableInterpreter();
String vendor = interpreter.interpret("${env.RDBMS_VENDOR}");
String hostname = interpreter.interpret("${env.RDBMS_HOSTNAME}");
Integer port = Integer.valueOf(interpreter.interpret("${env.RDBMS_PORT}"));
String databaseName = interpreter.interpret("${env.RDBMS_DATABASE_NAME}");
String username = interpreter.interpret("${env.RDBMS_USERNAME}");
String password= interpreter.interpret("${env.RDBMS_PASSWORD}");
return new RDBMSBackendMetaData()
.withName(RDBMS_BACKEND_NAME)
.withVendor("mysql")
.withHostName("127.0.0.1")
.withPort(3306)
.withDatabaseName("qqq")
.withUsername("root")
.withPassword(dotenv.get("RDBMS_PASSWORD"));
.withVendor(vendor)
.withHostName(hostname)
.withPort(port)
.withDatabaseName(databaseName)
.withUsername(username)
.withPassword(password);
}
else
{

View File

@ -0,0 +1,79 @@
package com.kingsrook.sampleapp.dashboard.widgets;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer;
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;
/*******************************************************************************
**
*******************************************************************************/
public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface metaData) throws QException
{
try
{
/*
// todo - always do this as SQL... if we had database in CI...
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.getConnection(SampleMetaDataProvider.defineRdbmsBackend());
String sql = """
SELECT
COUNT(*) AS count,
DATE_FORMAT(create_date, '%m-%Y') AS month
FROM
person
GROUP BY
2
ORDER BY
2
""";
List<Map<String, Object>> rows = QueryManager.executeStatementForRows(connection, sql);
for(Map<String, Object> row : rows)
{
labels.add(ValueUtils.getValueAsString(row.get("month")));
data.add(ValueUtils.getValueAsInteger(row.get("count")));
}
*/
List<String> labels = new ArrayList<>();
List<Number> 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));
}
}
}

View File

@ -0,0 +1,34 @@
package com.kingsrook.sampleapp.dashboard.widgets;
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.session.QSession;
import com.kingsrook.sampleapp.SampleMetaDataProvider;
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 PersonsByCreateDateBarChart
*******************************************************************************/
class PersonsByCreateDateBarChartTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
Object widgetData = new PersonsByCreateDateBarChart().render(SampleMetaDataProvider.defineInstance(), new QSession(), null);
assertThat(widgetData).isInstanceOf(BarChart.class);
BarChart barChart = (BarChart) widgetData;
assertEquals("barChart", barChart.getType());
assertThat(barChart.getTitle()).isNotBlank();
assertNotNull(barChart.getBarChartData());
}
}