mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
QQQ-38 Initial build of app home page widgets
This commit is contained in:
@ -93,4 +93,42 @@ public class QCodeLoader
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
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.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractWidgetRenderer
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract Object render(QInstance qInstance, QSession session) throws QException;
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
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.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class WidgetDataLoader
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Object execute(QInstance qInstance, QSession session, String name) throws QException
|
||||
{
|
||||
QWidgetMetaData widget = qInstance.getWidget(name);
|
||||
AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, widget.getCodeReference());
|
||||
return (widgetRenderer.render(qInstance, session));
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.CustomizerLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.Customizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -62,7 +62,7 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
||||
storage = new QueryOutputList();
|
||||
}
|
||||
|
||||
postQueryRecordCustomizer = (Function<QRecord, QRecord>) CustomizerLoader.getTableCustomizerFunction(queryInput.getTable(), Customizers.POST_QUERY_RECORD);
|
||||
postQueryRecordCustomizer = (Function<QRecord, QRecord>) QCodeLoader.getTableCustomizerFunction(queryInput.getTable(), Customizers.POST_QUERY_RECORD);
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,299 @@
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BarChart
|
||||
{
|
||||
|
||||
/*
|
||||
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 type = "barChart";
|
||||
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("Parcel Invoice Lines")
|
||||
.withData(data)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BarChart withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ import java.util.List;
|
||||
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.dashboard.QWidgetMetaData;
|
||||
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;
|
||||
@ -59,6 +60,8 @@ public class QInstance
|
||||
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
|
||||
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, QWidgetMetaData> widgets = new LinkedHashMap<>();
|
||||
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated?
|
||||
|
||||
@JsonIgnore
|
||||
@ -445,4 +448,60 @@ public class QInstance
|
||||
{
|
||||
this.authentication = authentication;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for widgets
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QWidgetMetaData> getWidgets()
|
||||
{
|
||||
return widgets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for widgets
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setWidgets(Map<String, QWidgetMetaData> widgets)
|
||||
{
|
||||
this.widgets = widgets;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addWidget(QWidgetMetaData widget)
|
||||
{
|
||||
this.addWidget(widget.getName(), widget);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addWidget(String name, QWidgetMetaData widget)
|
||||
{
|
||||
if(this.widgets.containsKey(name))
|
||||
{
|
||||
throw (new IllegalArgumentException("Attempted to add a second widget with name: " + name));
|
||||
}
|
||||
this.widgets.put(name, widget);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QWidgetMetaData getWidget(String name)
|
||||
{
|
||||
return (this.widgets.get(name));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
private String name;
|
||||
private 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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
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.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Sample bar chart widget
|
||||
*******************************************************************************/
|
||||
public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Object render(QInstance qInstance, QSession session) 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
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.QueryAction;
|
||||
@ -38,6 +39,7 @@ 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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
@ -115,6 +117,7 @@ public class TestUtils
|
||||
qInstance.addProcess(new BasicETLProcess().defineProcessMetaData());
|
||||
qInstance.addProcess(new StreamedETLProcess().defineProcessMetaData());
|
||||
|
||||
defineWidgets(qInstance);
|
||||
defineApps(qInstance);
|
||||
|
||||
return (qInstance);
|
||||
@ -122,6 +125,19 @@ public class TestUtils
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void defineWidgets(QInstance qInstance)
|
||||
{
|
||||
qInstance.addWidget(new QWidgetMetaData()
|
||||
.withName(PersonsByCreateDateBarChart.class.getSimpleName())
|
||||
.withCodeReference(new QCodeReference(PersonsByCreateDateBarChart.class, null)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -136,7 +152,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)
|
||||
|
@ -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);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
@ -278,6 +279,8 @@ public class QJavalinImplementation
|
||||
});
|
||||
});
|
||||
|
||||
get("/widget/{name}", QJavalinImplementation::widget);
|
||||
|
||||
////////////////////
|
||||
// process routes //
|
||||
////////////////////
|
||||
@ -662,6 +665,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,56 @@
|
||||
package com.kingsrook.qqq.backend.javalin;
|
||||
|
||||
|
||||
import java.sql.Connection;
|
||||
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.session.QSession;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Sample bar chart widget
|
||||
*******************************************************************************/
|
||||
public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Object render(QInstance qInstance, QSession session) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
**
|
||||
|
@ -34,6 +34,7 @@ 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.fields.DisplayFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@ -56,6 +57,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 io.github.cdimascio.dotenv.Dotenv;
|
||||
|
||||
|
||||
@ -114,6 +116,8 @@ public class SampleMetaDataProvider
|
||||
qInstance.addProcess(defineProcessScreenThenSleep());
|
||||
qInstance.addProcess(defineProcessSimpleThrow());
|
||||
|
||||
defineWidgets(qInstance);
|
||||
|
||||
defineApps(qInstance);
|
||||
|
||||
return (qInstance);
|
||||
@ -121,6 +125,18 @@ public class SampleMetaDataProvider
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void defineWidgets(QInstance qInstance)
|
||||
{
|
||||
qInstance.addWidget(new QWidgetMetaData()
|
||||
.withName(PersonsByCreateDateBarChart.class.getSimpleName())
|
||||
.withCodeReference(new QCodeReference(PersonsByCreateDateBarChart.class, null)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -136,7 +152,9 @@ public class SampleMetaDataProvider
|
||||
.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")));
|
||||
.withIcon(new QIcon().withName("waving_hand"))
|
||||
.withWidgets(List.of(PersonsByCreateDateBarChart.class.getSimpleName()))
|
||||
);
|
||||
|
||||
qInstance.addApp(new QAppMetaData()
|
||||
.withName(APP_NAME_PEOPLE)
|
||||
|
@ -0,0 +1,66 @@
|
||||
package com.kingsrook.sampleapp.dashboard.widgets;
|
||||
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.sampleapp.SampleMetaDataProvider;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Object render(QInstance qInstance, QSession session) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
|
||||
List<String> labels = new ArrayList<>();
|
||||
List<Number> data = new ArrayList<>();
|
||||
|
||||
for(Map<String, Object> row : rows)
|
||||
{
|
||||
labels.add(ValueUtils.getValueAsString(row.get("month")));
|
||||
data.add(ValueUtils.getValueAsInteger(row.get("count")));
|
||||
}
|
||||
|
||||
return (new BarChart("Persons created per Month", "Person records", labels, data));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error rendering widget", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
assertThat(widgetData).isInstanceOf(BarChart.class);
|
||||
BarChart barChart = (BarChart) widgetData;
|
||||
assertEquals("barChart", barChart.getType());
|
||||
assertThat(barChart.getTitle()).isNotBlank();
|
||||
assertNotNull(barChart.getBarChartData());
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user