Merge remote-tracking branch 'origin/QQQ-41-v-2-of-app-home-pages-dashboards-etc' into feature/QQQ-42-reports

This commit is contained in:
2022-09-19 13:52:54 -05:00
23 changed files with 2228 additions and 70 deletions

View File

@ -40,4 +40,14 @@ public abstract class AbstractWidgetRenderer
*******************************************************************************/ *******************************************************************************/
public abstract Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface qWidgetMetaData) throws QException; public abstract Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface qWidgetMetaData) throws QException;
/*******************************************************************************
**
*******************************************************************************/
public String getWidgetName()
{
return this.getClass().getSimpleName();
}
} }

View File

@ -40,6 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData; import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/******************************************************************************* /*******************************************************************************
@ -102,11 +103,14 @@ public class MetaDataAction
apps.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue())); apps.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue())); treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
if(CollectionUtils.nullSafeHasContents(entry.getValue().getChildren()))
{
for(QAppChildMetaData child : entry.getValue().getChildren()) for(QAppChildMetaData child : entry.getValue().getChildren())
{ {
apps.get(entry.getKey()).addChild(new AppTreeNode(child)); apps.get(entry.getKey()).addChild(new AppTreeNode(child));
} }
} }
}
metaDataOutput.setApps(apps); metaDataOutput.setApps(apps);
//////////////////////////////////////////////// ////////////////////////////////////////////////

View File

@ -36,7 +36,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; 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.fields.QFieldType;
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.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType; import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
@ -217,6 +219,11 @@ public class QInstanceEnricher
{ {
app.setLabel(nameToLabel(app.getName())); app.setLabel(nameToLabel(app.getName()));
} }
if(CollectionUtils.nullSafeIsEmpty(app.getSections()))
{
generateAppSections(app);
}
} }
@ -535,6 +542,54 @@ public class QInstanceEnricher
/*******************************************************************************
** If a app didn't have any sections, generate "sensible defaults"
*******************************************************************************/
private void generateAppSections(QAppMetaData app)
{
if(CollectionUtils.nullSafeIsEmpty(app.getChildren()))
{
/////////////////////////////////////////////////////////////////////////////////////////////////
// assume this app is valid if it has no children, but surely it doesn't need any sections then. //
/////////////////////////////////////////////////////////////////////////////////////////////////
return;
}
//////////////////////////////////////////////////////////////////////////////
// create an identity section for the id and any fields in the record label //
//////////////////////////////////////////////////////////////////////////////
QAppSection defaultSection = new QAppSection(app.getName(), app.getLabel(), new QIcon("badge"), new ArrayList<>(), new ArrayList<>());
boolean foundNonAppChild = false;
if(CollectionUtils.nullSafeHasContents(app.getChildren()))
{
for(QAppChildMetaData child : app.getChildren())
{
////////////////////////////////////////////////////////////////////////////////
// only tables and processes are allowed to be in sections at this time, apps //
// might be children but not in sections so keep track if we find any non-app //
////////////////////////////////////////////////////////////////////////////////
if(child.getClass().equals(QTableMetaData.class))
{
defaultSection.getTables().add(child.getName());
foundNonAppChild = true;
}
else if(child.getClass().equals(QProcessMetaData.class))
{
defaultSection.getProcesses().add(child.getName());
foundNonAppChild = true;
}
}
}
if(foundNonAppChild)
{
app.addSection(defaultSection);
}
}
/******************************************************************************* /*******************************************************************************
** If a table didn't have any sections, generate "sensible defaults" ** If a table didn't have any sections, generate "sensible defaults"
*******************************************************************************/ *******************************************************************************/

View File

@ -40,6 +40,7 @@ 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.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData; 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.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
@ -213,7 +214,7 @@ public class QInstanceValidator
{ {
for(QFieldSection section : table.getSections()) for(QFieldSection section : table.getSections())
{ {
validateSection(table, section, fieldNamesInSections); validateFieldSection(table, section, fieldNamesInSections);
if(section.getTier().equals(Tier.T1)) if(section.getTier().equals(Tier.T1))
{ {
assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1"); assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
@ -479,7 +480,7 @@ public class QInstanceValidator
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private void validateSection(QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections) private void validateFieldSection(QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections)
{ {
assertCondition(StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + "."); assertCondition(StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + ".");
assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + "."); assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + ".");
@ -500,6 +501,42 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void validateAppSection(QAppMetaData app, QAppSection section, Set<String> childNamesInSections)
{
assertCondition(StringUtils.hasContent(section.getName()), "Missing a name for a section in app " + app.getName() + ".");
assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for a section in app " + app.getLabel() + ".");
boolean hasTables = CollectionUtils.nullSafeHasContents(section.getTables());
boolean hasProcesses = CollectionUtils.nullSafeHasContents(section.getProcesses());
if(assertCondition(hasTables || hasProcesses, "App " + app.getName() + " section " + section.getName() + " does not have any children."))
{
if(hasTables)
{
for(String tableName : section.getTables())
{
assertCondition(app.getChildren().stream().anyMatch(c -> c.getName().equals(tableName)), "App " + app.getName() + " section " + section.getName() + " specifies table " + tableName + ", which is not a child of this app.");
assertCondition(!childNamesInSections.contains(tableName), "App " + app.getName() + " has table " + tableName + " listed more than once in its sections.");
childNamesInSections.add(tableName);
}
}
if(hasProcesses)
{
for(String processName : section.getProcesses())
{
assertCondition(app.getChildren().stream().anyMatch(c -> c.getName().equals(processName)), "App " + app.getName() + " section " + section.getName() + " specifies process " + processName + ", which is not a child of this app.");
assertCondition(!childNamesInSections.contains(processName), "App " + app.getName() + " has process " + processName + " listed more than once in its sections.");
childNamesInSections.add(processName);
}
}
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -565,6 +602,29 @@ public class QInstanceValidator
childNames.add(child.getName()); childNames.add(child.getName());
} }
} }
//////////////////////////////////////////
// validate field sections in the table //
//////////////////////////////////////////
Set<String> childNamesInSections = new HashSet<>();
if(app.getSections() != null)
{
for(QAppSection section : app.getSections())
{
validateAppSection(app, section, childNamesInSections);
}
}
if(CollectionUtils.nullSafeHasContents(app.getChildren()))
{
for(QAppChildMetaData child : app.getChildren())
{
if(!child.getClass().equals(QAppMetaData.class))
{
assertCondition(childNamesInSections.contains(child.getName()), "App " + appName + " child " + child.getName() + " is not listed in any app sections.");
}
}
}
}); });
} }
} }

View File

@ -29,32 +29,34 @@ import java.util.List;
** Model containing datastructure expected by frontend bar chart widget ** Model containing datastructure expected by frontend bar chart widget
** **
*******************************************************************************/ *******************************************************************************/
public class BarChart implements QWidget public class ChartData implements QWidget
{ {
/* /*
type: "barChart", interface BarChartData{
title: "Parcel Invoice Lines per Month", labels: string[];
barChartData: { datasets: {
labels: ["Feb 22", "Mar 22", "Apr 22", "May 22", "Jun 22", "Jul 22", "Aug 22"], label: string;
datasets: {label: "Parcel Invoice Lines", data: [50000, 22000, 11111, 22333, 40404, 9876, 2355]}, data: number[];
}, }[];
}
*/ */
private String title; private String title;
private Data barChartData; private String description;
private Data chartData;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public BarChart(String title, String seriesLabel, List<String> labels, List<Number> data) public ChartData(String title, String description, String seriesLabel, List<String> labels, List<Number> data)
{ {
setTitle(title); setTitle(title);
setBarChartData(new BarChart.Data() setDescription(description);
setChartData(new ChartData.Data()
.withLabels(labels) .withLabels(labels)
.withDatasets(new BarChart.Data.DataSet() .withDatasets(new ChartData.Data.Dataset()
.withLabel(seriesLabel) .withLabel(seriesLabel)
.withData(data))); .withData(data)));
} }
@ -98,7 +100,7 @@ public class BarChart implements QWidget
** Fluent setter for title ** Fluent setter for title
** **
*******************************************************************************/ *******************************************************************************/
public BarChart withTitle(String title) public ChartData withTitle(String title)
{ {
this.title = title; this.title = title;
return (this); return (this);
@ -107,34 +109,68 @@ public class BarChart implements QWidget
/******************************************************************************* /*******************************************************************************
** Getter for barChartData ** Getter for description
** **
*******************************************************************************/ *******************************************************************************/
public Data getBarChartData() public String getDescription()
{ {
return barChartData; return description;
} }
/******************************************************************************* /*******************************************************************************
** Setter for barChartData ** Setter for description
** **
*******************************************************************************/ *******************************************************************************/
public void setBarChartData(Data barChartData) public void setDescription(String description)
{ {
this.barChartData = barChartData; this.description = description;
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for barChartData ** Fluent setter for description
** **
*******************************************************************************/ *******************************************************************************/
public BarChart withBarChartData(Data barChartData) public ChartData withDescription(String description)
{ {
this.barChartData = barChartData; this.description = description;
return (this);
}
/*******************************************************************************
** Getter for chartData
**
*******************************************************************************/
public Data getChartData()
{
return chartData;
}
/*******************************************************************************
** Setter for chartData
**
*******************************************************************************/
public void setChartData(Data chartData)
{
this.chartData = chartData;
}
/*******************************************************************************
** Fluent setter for chartData
**
*******************************************************************************/
public ChartData withChartData(Data chartData)
{
this.chartData = chartData;
return (this); return (this);
} }
@ -146,7 +182,7 @@ public class BarChart implements QWidget
public static class Data public static class Data
{ {
private List<String> labels; private List<String> labels;
private DataSet datasets; private Dataset dataset;
@ -188,9 +224,9 @@ public class BarChart implements QWidget
** Getter for datasets ** Getter for datasets
** **
*******************************************************************************/ *******************************************************************************/
public DataSet getDatasets() public Dataset getDataset()
{ {
return datasets; return dataset;
} }
@ -199,9 +235,9 @@ public class BarChart implements QWidget
** Setter for datasets ** Setter for datasets
** **
*******************************************************************************/ *******************************************************************************/
public void setDatasets(DataSet datasets) public void setDataset(Dataset dataset)
{ {
this.datasets = datasets; this.dataset = dataset;
} }
@ -210,9 +246,9 @@ public class BarChart implements QWidget
** Fluent setter for datasets ** Fluent setter for datasets
** **
*******************************************************************************/ *******************************************************************************/
public Data withDatasets(DataSet datasets) public Data withDatasets(Dataset datasets)
{ {
this.datasets = datasets; this.dataset = datasets;
return (this); return (this);
} }
@ -221,7 +257,7 @@ public class BarChart implements QWidget
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public static class DataSet public static class Dataset
{ {
private String label; private String label;
private List<Number> data; private List<Number> data;
@ -254,7 +290,7 @@ public class BarChart implements QWidget
** Fluent setter for label ** Fluent setter for label
** **
*******************************************************************************/ *******************************************************************************/
public DataSet withLabel(String label) public Dataset withLabel(String label)
{ {
this.label = label; this.label = label;
return (this); return (this);
@ -288,7 +324,7 @@ public class BarChart implements QWidget
** Fluent setter for data ** Fluent setter for data
** **
*******************************************************************************/ *******************************************************************************/
public DataSet withData(List<Number> data) public Dataset withData(List<Number> data)
{ {
this.data = data; this.data = data;
return (this); return (this);

View File

@ -0,0 +1,344 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
/*******************************************************************************
** Model containing datastructure expected by frontend bar chart widget
**
*******************************************************************************/
public class LineChartData implements QWidget
{
/*
export interface DefaultLineChartData
{
labels: string[]; // monday, tues...
lineLabels: string[]; // axle, cdl...
datasets: {
label: string; // axle, cdl...
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
data: number[];
}[];
};
*/
private String title;
private Data chartData;
/*******************************************************************************
**
*******************************************************************************/
public LineChartData()
{
}
/*******************************************************************************
**
*******************************************************************************/
public LineChartData(String title, List<String> labels, List<String> lineLabels, List<Data.Dataset> datasets)
{
setTitle(title);
setChartData(new LineChartData.Data()
.withLabels(labels)
.withLineLabels(lineLabels)
.withDatasets(datasets));
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return "lineChart";
}
/*******************************************************************************
** Getter for title
**
*******************************************************************************/
public String getTitle()
{
return title;
}
/*******************************************************************************
** Setter for title
**
*******************************************************************************/
public void setTitle(String title)
{
this.title = title;
}
/*******************************************************************************
** Fluent setter for title
**
*******************************************************************************/
public LineChartData withTitle(String title)
{
this.title = title;
return (this);
}
/*******************************************************************************
** Getter for chartData
**
*******************************************************************************/
public Data getChartData()
{
return chartData;
}
/*******************************************************************************
** Setter for chartData
**
*******************************************************************************/
public void setChartData(Data chartData)
{
this.chartData = chartData;
}
/*******************************************************************************
** Fluent setter for chartData
**
*******************************************************************************/
public LineChartData withChartData(Data chartData)
{
this.chartData = chartData;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public static class Data
{
private List<String> labels;
private List<String> lineLabels;
private List<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 lineLabels
**
*******************************************************************************/
public List<String> getLineLabels()
{
return lineLabels;
}
/*******************************************************************************
** Setter for lineLabels
**
*******************************************************************************/
public void setLineLabels(List<String> lineLabels)
{
this.lineLabels = lineLabels;
}
/*******************************************************************************
** Fluent setter for lineLabels
**
*******************************************************************************/
public Data withLineLabels(List<String> lineLabels)
{
this.lineLabels = lineLabels;
return (this);
}
/*******************************************************************************
** Getter for datasets
**
*******************************************************************************/
public List<Dataset> getDatasets()
{
return datasets;
}
/*******************************************************************************
** Setter for datasets
**
*******************************************************************************/
public void setDatasets(List<Dataset> datasets)
{
this.datasets = datasets;
}
/*******************************************************************************
** Fluent setter for datasets
**
*******************************************************************************/
public Data withDatasets(List<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,233 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
/*******************************************************************************
** Model containing datastructure expected by frontend location widget
**
*******************************************************************************/
public class LocationData implements QWidget
{
private String imageUrl;
private String title;
private String description;
private String footerText;
private String location;
/*******************************************************************************
**
*******************************************************************************/
public LocationData(String imageUrl, String title, String description, String location, String footerText)
{
this.imageUrl = imageUrl;
this.title = title;
this.description = description;
this.location = location;
this.footerText = footerText;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return "location";
}
/*******************************************************************************
** Getter for imageUrl
**
*******************************************************************************/
public String getImageUrl()
{
return imageUrl;
}
/*******************************************************************************
** Setter for imageUrl
**
*******************************************************************************/
public void setImageUrl(String imageUrl)
{
this.imageUrl = imageUrl;
}
/*******************************************************************************
** Fluent setter for imageUrl
**
*******************************************************************************/
public LocationData withImageUrl(String imageUrl)
{
this.imageUrl = imageUrl;
return (this);
}
/*******************************************************************************
** Getter for description
**
*******************************************************************************/
public String getDescription()
{
return description;
}
/*******************************************************************************
** Setter for description
**
*******************************************************************************/
public void setDescription(String description)
{
this.description = description;
}
/*******************************************************************************
** Fluent setter for description
**
*******************************************************************************/
public LocationData withDescription(String description)
{
this.description = description;
return (this);
}
/*******************************************************************************
** Getter for footerText
**
*******************************************************************************/
public String getFooterText()
{
return footerText;
}
/*******************************************************************************
** Setter for footerText
**
*******************************************************************************/
public void setFooterText(String footerText)
{
this.footerText = footerText;
}
/*******************************************************************************
** Fluent setter for footerText
**
*******************************************************************************/
public LocationData withFooterText(String footerText)
{
this.footerText = footerText;
return (this);
}
/*******************************************************************************
** Getter for title
**
*******************************************************************************/
public String getTitle()
{
return title;
}
/*******************************************************************************
** Setter for title
**
*******************************************************************************/
public void setTitle(String title)
{
this.title = title;
}
/*******************************************************************************
** Fluent setter for title
**
*******************************************************************************/
public LocationData withTitle(String title)
{
this.title = title;
return (this);
}
/*******************************************************************************
** Getter for location
**
*******************************************************************************/
public String getLocation()
{
return location;
}
/*******************************************************************************
** Setter for location
**
*******************************************************************************/
public void setLocation(String location)
{
this.location = location;
}
/*******************************************************************************
** Fluent setter for location
**
*******************************************************************************/
public LocationData withLocation(String location)
{
this.location = location;
return (this);
}
}

View File

@ -0,0 +1,206 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
/*******************************************************************************
** Model containing datastructure expected by frontend bar chart widget
**
*******************************************************************************/
public class StatisticsData implements QWidget
{
/*
interface BarChartData{
labels: string[];
datasets: {
label: string;
data: number[];
}[];
}
*/
private String title;
private int count;
private Number percentageAmount;
private String percentageLabel;
/*******************************************************************************
**
*******************************************************************************/
public StatisticsData(String title, int count, Number percentageAmount, String percentageLabel)
{
this.title = title;
this.count = count;
this.percentageLabel = percentageLabel;
this.percentageAmount = percentageAmount;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return "statistics";
}
/*******************************************************************************
** Getter for title
**
*******************************************************************************/
public String getTitle()
{
return title;
}
/*******************************************************************************
** Setter for title
**
*******************************************************************************/
public void setTitle(String title)
{
this.title = title;
}
/*******************************************************************************
** Fluent setter for title
**
*******************************************************************************/
public StatisticsData withTitle(String title)
{
this.title = title;
return (this);
}
/*******************************************************************************
** Getter for count
**
*******************************************************************************/
public int getCount()
{
return count;
}
/*******************************************************************************
** Setter for count
**
*******************************************************************************/
public void setCount(int count)
{
this.count = count;
}
/*******************************************************************************
** Fluent setter for count
**
*******************************************************************************/
public StatisticsData withCount(int count)
{
this.count = count;
return (this);
}
/*******************************************************************************
** Getter for percentageAmount
**
*******************************************************************************/
public Number getPercentageAmount()
{
return percentageAmount;
}
/*******************************************************************************
** Setter for percentageAmount
**
*******************************************************************************/
public void setPercentageAmount(Number percentageAmount)
{
this.percentageAmount = percentageAmount;
}
/*******************************************************************************
** Fluent setter for percentageAmount
**
*******************************************************************************/
public StatisticsData withPercentageAmount(Number percentageAmount)
{
this.percentageAmount = percentageAmount;
return (this);
}
/*******************************************************************************
** Getter for percentageLabel
**
*******************************************************************************/
public String getPercentageLabel()
{
return percentageLabel;
}
/*******************************************************************************
** Setter for percentageLabel
**
*******************************************************************************/
public void setPercentageLabel(String percentageLabel)
{
this.percentageLabel = percentageLabel;
}
/*******************************************************************************
** Fluent setter for percentageLabel
**
*******************************************************************************/
public StatisticsData withPercentageLabel(String percentageLabel)
{
this.percentageLabel = percentageLabel;
return (this);
}
}

View File

@ -0,0 +1,417 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
import java.util.Map;
/*******************************************************************************
** Model containing datastructure expected by frontend bar chart widget
**
*******************************************************************************/
public class TableData implements QWidget
{
/*
const carrierSpendData = {
columns: [
{Header: "carrier", accessor: "product", width: "55%"},
{Header: "total YTD", accessor: "value"},
{Header: "monthly average", accessor: "adsSpent", align: "center"},
{Header: "service failures", accessor: "refunds", align: "center"},
],
rows: [
{
product: <ProductCell image={axlehire} name="AxleHire" orders="921" />,
value: <DefaultCell>$140,925</DefaultCell>,
adsSpent: <DefaultCell>$24,531</DefaultCell>,
refunds: <RefundsCell value={121} icon={{color: "success", name: "keyboard_arrow_up"}} />,
},
]
}
*/
private String title;
private List<Column> columns;
private List<Map<String, Object>> rows;
private List<String> dropdownOptions;
/*******************************************************************************
**
*******************************************************************************/
public TableData(String title, List<Column> columns, List<Map<String, Object>> rows, List<String> dropdownOptions)
{
setTitle(title);
setColumns(columns);
setRows(rows);
setDropdownOptions(dropdownOptions);
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return "table";
}
/*******************************************************************************
** Getter for title
**
*******************************************************************************/
public String getTitle()
{
return title;
}
/*******************************************************************************
** Setter for title
**
*******************************************************************************/
public void setTitle(String title)
{
this.title = title;
}
/*******************************************************************************
** Fluent setter for title
**
*******************************************************************************/
public TableData withTitle(String title)
{
this.title = title;
return (this);
}
/*******************************************************************************
** Getter for columns
**
*******************************************************************************/
public List<Column> getColumns()
{
return columns;
}
/*******************************************************************************
** Setter for columns
**
*******************************************************************************/
public void setColumns(List<Column> columns)
{
this.columns = columns;
}
/*******************************************************************************
** Fluent setter for columns
**
*******************************************************************************/
public TableData withColumns(List<Column> columns)
{
this.columns = columns;
return (this);
}
/*******************************************************************************
** Getter for rows
**
*******************************************************************************/
public List<Map<String, Object>> getRows()
{
return rows;
}
/*******************************************************************************
** Setter for rows
**
*******************************************************************************/
public void setRows(List<Map<String, Object>> rows)
{
this.rows = rows;
}
/*******************************************************************************
** Fluent setter for rows
**
*******************************************************************************/
public TableData withRows(List<Map<String, Object>> rows)
{
this.rows = rows;
return (this);
}
/*******************************************************************************
** Getter for dropdownOptions
**
*******************************************************************************/
public List<String> getDropdownOptions()
{
return dropdownOptions;
}
/*******************************************************************************
** Setter for dropdownOptions
**
*******************************************************************************/
public void setDropdownOptions(List<String> dropdownOptions)
{
this.dropdownOptions = dropdownOptions;
}
/*******************************************************************************
** Fluent setter for dropdownOptions
**
*******************************************************************************/
public TableData withDropdownOptions(List<String> dropdownOptions)
{
this.dropdownOptions = dropdownOptions;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public static class Column
{
private String type;
private String header;
private String accessor;
private String width;
private String align;
/*******************************************************************************
**
*******************************************************************************/
public Column(String type, String header, String accessor, String width, String align)
{
this.type = type;
this.header = header;
this.accessor = accessor;
this.width = width;
this.align = align;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public String getType()
{
return type;
}
/*******************************************************************************
** Setter for type
**
*******************************************************************************/
public void setType(String type)
{
this.type = type;
}
/*******************************************************************************
** Getter for header
**
*******************************************************************************/
public String getHeader()
{
return header;
}
/*******************************************************************************
** Setter for header
**
*******************************************************************************/
public void setHeader(String header)
{
this.header = header;
}
/*******************************************************************************
** Getter for accessor
**
*******************************************************************************/
public String getAccessor()
{
return accessor;
}
/*******************************************************************************
** Setter for accessor
**
*******************************************************************************/
public void setAccessor(String accessor)
{
this.accessor = accessor;
}
/*******************************************************************************
** Getter for width
**
*******************************************************************************/
public String getWidth()
{
return width;
}
/*******************************************************************************
** Setter for width
**
*******************************************************************************/
public void setWidth(String width)
{
this.width = width;
}
/*******************************************************************************
** Getter for align
**
*******************************************************************************/
public String getAlign()
{
return align;
}
/*******************************************************************************
** Setter for align
**
*******************************************************************************/
public void setAlign(String align)
{
this.align = align;
}
/*******************************************************************************
** fluent setter for header
**
*******************************************************************************/
public Column withHeader(String header)
{
this.header = header;
return this;
}
/*******************************************************************************
** fluent setter for accessor
**
*******************************************************************************/
public Column withAccessor(String accessor)
{
this.accessor = accessor;
return this;
}
/*******************************************************************************
** fluent setter for width
**
*******************************************************************************/
public Column withWidth(String width)
{
this.width = width;
return this;
}
/*******************************************************************************
** fluent setter for align
**
*******************************************************************************/
public Column withAlign(String align)
{
this.align = align;
return this;
}
/*******************************************************************************
** fluent setter for type
**
*******************************************************************************/
public Column withType(String type)
{
this.type = type;
return this;
}
}
}

View File

@ -23,10 +23,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -39,12 +42,14 @@ public class QFrontendAppMetaData
{ {
private String name; private String name;
private String label; private String label;
private List<AppTreeNode> children = new ArrayList<>();
private List<String> widgets = new ArrayList<>();
private String iconName; private String iconName;
private List<String> widgets = new ArrayList<>();
private List<AppTreeNode> children = new ArrayList<>();
private Map<String, AppTreeNode> childMap = new HashMap<>();
private List<QAppSection> sections;
/******************************************************************************* /*******************************************************************************
@ -64,6 +69,11 @@ public class QFrontendAppMetaData
{ {
this.widgets = appMetaData.getWidgets(); this.widgets = appMetaData.getWidgets();
} }
if(CollectionUtils.nullSafeHasContents(appMetaData.getSections()))
{
this.sections = appMetaData.getSections();
}
} }
@ -101,6 +111,17 @@ public class QFrontendAppMetaData
/*******************************************************************************
** Getter for childMap
**
*******************************************************************************/
public Map<String, AppTreeNode> getChildMap()
{
return childMap;
}
/******************************************************************************* /*******************************************************************************
** Getter for iconName ** Getter for iconName
** **
@ -132,6 +153,7 @@ public class QFrontendAppMetaData
{ {
children = new ArrayList<>(); children = new ArrayList<>();
} }
childMap.put(childAppTreeNode.getName(), childAppTreeNode);
children.add(childAppTreeNode); children.add(childAppTreeNode);
} }
@ -145,4 +167,15 @@ public class QFrontendAppMetaData
{ {
return widgets; return widgets;
} }
/*******************************************************************************
** Getter for sections
**
*******************************************************************************/
public List<QAppSection> getSections()
{
return sections;
}
} }

View File

@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/******************************************************************************* /*******************************************************************************
@ -41,6 +42,8 @@ public class QAppMetaData implements QAppChildMetaData
private QIcon icon; private QIcon icon;
private List<String> widgets; private List<String> widgets;
private List<QAppSection> sections;
/******************************************************************************* /*******************************************************************************
@ -137,7 +140,13 @@ public class QAppMetaData implements QAppChildMetaData
*******************************************************************************/ *******************************************************************************/
public void setChildren(List<QAppChildMetaData> children) public void setChildren(List<QAppChildMetaData> children)
{ {
this.children = children; if(CollectionUtils.nullSafeHasContents(children))
{
for(QAppChildMetaData child : children)
{
addChild(child);
}
}
} }
@ -176,7 +185,7 @@ public class QAppMetaData implements QAppChildMetaData
*******************************************************************************/ *******************************************************************************/
public QAppMetaData withChildren(List<QAppChildMetaData> children) public QAppMetaData withChildren(List<QAppChildMetaData> children)
{ {
this.children = children; setChildren(children);
return (this); return (this);
} }
@ -205,6 +214,7 @@ public class QAppMetaData implements QAppChildMetaData
} }
/******************************************************************************* /*******************************************************************************
** Getter for icon ** Getter for icon
** **
@ -226,6 +236,7 @@ public class QAppMetaData implements QAppChildMetaData
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for icon ** Fluent setter for icon
** **
@ -238,7 +249,6 @@ public class QAppMetaData implements QAppChildMetaData
/******************************************************************************* /*******************************************************************************
** Getter for widgets ** Getter for widgets
** **
@ -260,6 +270,7 @@ public class QAppMetaData implements QAppChildMetaData
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for widgets ** Fluent setter for widgets
** **
@ -270,4 +281,63 @@ public class QAppMetaData implements QAppChildMetaData
return (this); return (this);
} }
/*******************************************************************************
** Getter for sections
**
*******************************************************************************/
public List<QAppSection> getSections()
{
return sections;
}
/*******************************************************************************
** Setter for sections
**
*******************************************************************************/
public void setSections(List<QAppSection> sections)
{
this.sections = sections;
}
/*******************************************************************************
** Fluent setter for sections
**
*******************************************************************************/
public QAppMetaData withSections(List<QAppSection> sections)
{
this.sections = sections;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void addSection(QAppSection section)
{
if(this.sections == null)
{
this.sections = new ArrayList<>();
}
this.sections.add(section);
}
/*******************************************************************************
** Fluent setter for sections
**
*******************************************************************************/
public QAppMetaData withSection(QAppSection section)
{
this.addSection(section);
return (this);
}
} }

View File

@ -0,0 +1,233 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.List;
/*******************************************************************************
** A section of apps/tables/processes - a logical grouping.
*******************************************************************************/
public class QAppSection
{
private String name;
private String label;
private QIcon icon;
private List<String> tables;
private List<String> processes;
/*******************************************************************************
**
*******************************************************************************/
public QAppSection()
{
}
/*******************************************************************************
**
*******************************************************************************/
public QAppSection(String name, String label, QIcon icon, List<String> tables, List<String> processes)
{
this.name = name;
this.label = label;
this.icon = icon;
this.tables = tables;
this.processes = processes;
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public QAppSection 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 QAppSection withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for tables
**
*******************************************************************************/
public List<String> getTables()
{
return tables;
}
/*******************************************************************************
** Setter for tables
**
*******************************************************************************/
public void setTables(List<String> tables)
{
this.tables = tables;
}
/*******************************************************************************
** Fluent setter for tables
**
*******************************************************************************/
public QAppSection withTables(List<String> tables)
{
this.tables = tables;
return (this);
}
/*******************************************************************************
** Getter for processes
**
*******************************************************************************/
public List<String> getProcesses()
{
return processes;
}
/*******************************************************************************
** Setter for processes
**
*******************************************************************************/
public void setProcesses(List<String> processes)
{
this.processes = processes;
}
/*******************************************************************************
** Fluent setter for processes
**
*******************************************************************************/
public QAppSection withProcesses(List<String> processes)
{
this.processes = processes;
return (this);
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
/*******************************************************************************
** Setter for icon
**
*******************************************************************************/
public void setIcon(QIcon icon)
{
this.icon = icon;
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public QAppSection withIcon(QIcon icon)
{
this.icon = icon;
return (this);
}
}

View File

@ -25,7 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.dashboard.widgets.ChartData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
@ -62,7 +62,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
labels.add("May 2022"); labels.add("May 2022");
data.add(64); data.add(64);
return (new BarChart("Persons created per Month", "Person records", labels, data)); return (new ChartData("Persons created per Month", null, "Person records", labels, data));
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -23,7 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.dashboard.widgets.ChartData;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -43,11 +43,11 @@ class WidgetDataLoaderTest
void test() throws QException void test() throws QException
{ {
Object widgetData = new WidgetDataLoader().execute(TestUtils.defineInstance(), TestUtils.getMockSession(), PersonsByCreateDateBarChart.class.getSimpleName()); Object widgetData = new WidgetDataLoader().execute(TestUtils.defineInstance(), TestUtils.getMockSession(), PersonsByCreateDateBarChart.class.getSimpleName());
assertThat(widgetData).isInstanceOf(BarChart.class); assertThat(widgetData).isInstanceOf(ChartData.class);
BarChart barChart = (BarChart) widgetData; ChartData chartData = (ChartData) widgetData;
assertEquals("barChart", barChart.getType()); assertEquals("barChart", chartData.getType());
assertThat(barChart.getTitle()).isNotBlank(); assertThat(chartData.getTitle()).isNotBlank();
assertNotNull(barChart.getBarChartData()); assertNotNull(chartData.getChartData());
} }
} }

View File

@ -30,7 +30,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_GREETINGS;
import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_MISCELLANEOUS;
import static com.kingsrook.qqq.backend.core.utils.TestUtils.APP_NAME_PEOPLE;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@ -163,6 +167,35 @@ class QInstanceEnricherTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGenerateAppSections()
{
QInstance qInstance = TestUtils.defineInstance();
new QInstanceEnricher().enrich(qInstance);
assertNotNull(qInstance.getApp(APP_NAME_GREETINGS).getSections());
assertEquals(1, qInstance.getApp(APP_NAME_GREETINGS).getSections().size(), "App should automatically have one section");
assertEquals(0, qInstance.getApp(APP_NAME_GREETINGS).getSections().get(0).getTables().size(), "Section should not have tables");
assertEquals(2, qInstance.getApp(APP_NAME_GREETINGS).getSections().get(0).getProcesses().size(), "Section should have two processes");
assertEquals(qInstance.getApp(APP_NAME_GREETINGS).getName(), qInstance.getApp(APP_NAME_GREETINGS).getSections().get(0).getName(), "Section name should default to app's");
assertNotNull(qInstance.getApp(APP_NAME_PEOPLE).getSections());
assertEquals(1, qInstance.getApp(APP_NAME_PEOPLE).getSections().size(), "App should automatically have one section");
assertEquals(2, qInstance.getApp(APP_NAME_PEOPLE).getSections().get(0).getTables().size(), "Section should have two tables");
assertEquals(0, qInstance.getApp(APP_NAME_PEOPLE).getSections().get(0).getProcesses().size(), "Section should not have processes");
assertEquals(qInstance.getApp(APP_NAME_PEOPLE).getName(), qInstance.getApp(APP_NAME_PEOPLE).getSections().get(0).getName(), "Section name should default to app's");
assertNotNull(qInstance.getApp(APP_NAME_MISCELLANEOUS).getSections());
assertEquals(1, qInstance.getApp(APP_NAME_MISCELLANEOUS).getSections().size(), "App should automatically have one section");
assertEquals(1, qInstance.getApp(APP_NAME_MISCELLANEOUS).getSections().get(0).getTables().size(), "Section should have one table");
assertEquals(1, qInstance.getApp(APP_NAME_MISCELLANEOUS).getSections().get(0).getProcesses().size(), "Section should have one process");
assertEquals(qInstance.getApp(APP_NAME_MISCELLANEOUS).getName(), qInstance.getApp(APP_NAME_MISCELLANEOUS).getSections().get(0).getName(), "Section name should default to app's");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -41,13 +41,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; 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.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier; import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -575,6 +576,123 @@ class QInstanceValidatorTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testAppSectionsMissingName()
{
QAppMetaData app = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection(null, "Section 1", new QIcon("person"), List.of("test"), null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "Missing a name");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testAppSectionsMissingLabel()
{
QAppMetaData app = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("Section 1", null, new QIcon("person"), List.of("test"), null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "Missing a label");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testAppSectionsNoFields()
{
QAppMetaData app1 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of(), List.of()));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "section1 does not have any children", "child test is not listed in any app sections");
QAppMetaData app2 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), null, null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "section1 does not have any children", "child test is not listed in any app sections");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testAppSectionsUnrecognizedFieldName()
{
QAppMetaData app1 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test", "tset"), null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "not a child of this app");
QAppMetaData app2 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), List.of("tset")));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "not a child of this app");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testAppSectionsDuplicatedFieldName()
{
QAppMetaData app1 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test", "test"), null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app1), "more than once");
QAppMetaData app2 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), null))
.withSection(new QAppSection("section2", "Section 2", new QIcon("person"), List.of("test"), null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app2), "more than once");
QAppMetaData app3 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), List.of("test")));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app3), "more than once");
QAppMetaData app4 = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), null, List.of("test", "test")));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app4), "more than once");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testChildNotInAnySections()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("name", QFieldType.STRING));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "not listed in any field sections");
QAppMetaData app = new QAppMetaData().withName("test")
.withChild(new QTableMetaData().withName("tset"))
.withChild(new QTableMetaData().withName("test"))
.withSection(new QAppSection("section1", "Section 1", new QIcon("person"), List.of("test"), null));
assertValidationFailureReasons((qInstance) -> qInstance.addApp(app), "not listed in any app sections");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -825,6 +943,7 @@ class QInstanceValidatorTest
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -0,0 +1,191 @@
<template name="addX" value="/*******************************************************************************&#10; **&#10; *******************************************************************************/&#10;public void add$TYPE$($TYPE$ $var$)&#10;{&#10; if(this.$list$ == null)&#10; {&#10; this.$list$ = new ArrayList&lt;&gt;();&#10; }&#10; this.$list$.add($var$);&#10;}" description="write a method to add an X to a list" toReformat="false" toShortenFQNames="true">
<variable name="TYPE" expression="className()" defaultValue="" alwaysStopAt="true" />
<variable name="var" expression="suggestVariableName()" defaultValue="" alwaysStopAt="true" />
<variable name="list" expression="suggestVariableName()" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="afterAllMethod" value="/*******************************************************************************&#10;** &#10;*******************************************************************************/&#10;@AfterAll&#10;static void afterAll()&#10;{&#10; $END$&#10;}&#10;" description="Write a junt afterAll method" toReformat="true" toShortenFQNames="true">
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="afterEachMethod" value="/*******************************************************************************&#10;** &#10;*******************************************************************************/&#10;@AfterEach&#10;void afterEach()&#10;{&#10; $END$&#10;}&#10;" description="Write a junt afterEach method" toReformat="true" toShortenFQNames="true">
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="beforeAllMethod" value="/*******************************************************************************&#10;** &#10;*******************************************************************************/&#10;@BeforeAll&#10;static void beforeAll()&#10;{&#10; $END$&#10;}&#10;" description="Write a junt beforeAll method" toReformat="true" toShortenFQNames="true">
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="beforeAndAfterEachMethod" value="/*******************************************************************************&#10;** &#10;*******************************************************************************/&#10;@BeforeEach&#10;@AfterEach&#10;void beforeAndAfterEach()&#10;{&#10; $END$&#10;}&#10;" description="Write a junt beforeAndAfterEach method" toReformat="true" toShortenFQNames="true">
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="beforeEachMethod" value="/*******************************************************************************&#10;** &#10;*******************************************************************************/&#10;@BeforeEach&#10;void beforeEach()&#10;{&#10; $END$&#10;}&#10;" description="Write a junt beforeEach method" toReformat="true" toShortenFQNames="true">
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="gs" value="&#10;/*******************************************************************************&#10; ** Getter for $field$&#10; **&#10; *******************************************************************************/&#10;public $fieldType$ get$fieldUcFirst$()&#10;{&#10; return $field$;&#10;}&#10;&#10;&#10;&#10;/*******************************************************************************&#10; ** Setter for $field$&#10; **&#10; *******************************************************************************/&#10;public void set$fieldUcFirst$($fieldType$ $field$)&#10;{&#10; this.$field$ = $field$;&#10;}&#10;&#10;&#10;" description="Write a getter and setter" toReformat="false" toShortenFQNames="true">
<variable name="field" expression="completeSmart()" defaultValue="" alwaysStopAt="true" />
<variable name="fieldUcFirst" expression="capitalize(field)" defaultValue="" alwaysStopAt="false" />
<variable name="fieldType" expression="typeOfVariable(field)" defaultValue="" alwaysStopAt="false" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="gsw" value="&#10;/*******************************************************************************&#10; ** Getter for $field$&#10; **&#10; *******************************************************************************/&#10;public $fieldType$ get$fieldUcFirst$()&#10;{&#10; return $field$;&#10;}&#10;&#10;&#10;&#10;/*******************************************************************************&#10; ** Setter for $field$&#10; **&#10; *******************************************************************************/&#10;public void set$fieldUcFirst$($fieldType$ $field$)&#10;{&#10; this.$field$ = $field$;&#10;}&#10;&#10;&#10;/*******************************************************************************&#10; ** Fluent setter for $field$&#10; **&#10; *******************************************************************************/&#10;public $thisClass$ with$fieldUcFirst$($fieldType$ $field$)&#10;{&#10; this.$field$ = $field$;&#10; return (this);&#10;}&#10;&#10;" description="Write a getter, setter, and fluent setter" toReformat="false" toShortenFQNames="true">
<variable name="field" expression="completeSmart()" defaultValue="" alwaysStopAt="true" />
<variable name="thisClass" expression="className()" defaultValue="" alwaysStopAt="false" />
<variable name="fieldUcFirst" expression="capitalize(field)" defaultValue="" alwaysStopAt="false" />
<variable name="fieldType" expression="typeOfVariable(field)" defaultValue="" alwaysStopAt="false" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="innerclass" value="/*******************************************************************************&#10; **&#10; *******************************************************************************/&#10;$ACCESS$ static class $NAME$&#10;{&#10; $END$&#10;}" description="Write an inner class" toReformat="true" toShortenFQNames="true">
<variable name="ACCESS" expression="enum(&quot;public&quot;, &quot;private&quot;, &quot;protected&quot;)" defaultValue="" alwaysStopAt="true" />
<variable name="NAME" expression="" defaultValue="&quot;Foo&quot;" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="javadoc" value="/*******************************************************************************&#10; ** $END$&#10; *******************************************************************************/" description="Write a method or class header javadoc comment" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="list" value="List&lt;$TYPE$&gt; $var$ = new ArrayList&lt;&gt;();" description="List&lt;T&gt; list = new ArrayList&lt;&gt;();" toReformat="false" toShortenFQNames="true">
<variable name="TYPE" expression="className()" defaultValue="" alwaysStopAt="true" />
<variable name="var" expression="completeSmart()" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="main" value="/*******************************************************************************&#10; **&#10; *******************************************************************************/&#10;public static void main(String[] args)&#10;{&#10; $CLASS$ instance = new $CLASS$();&#10; instance.run();&#10;}&#10;&#10;&#10;&#10;/*******************************************************************************&#10; **&#10; *******************************************************************************/&#10;public void run()&#10;{&#10; try&#10; {&#10; $END$&#10; }&#10; catch(Exception e)&#10; {&#10; e.printStackTrace();&#10; }&#10;}" description="Write a main method (which will instantiate the current class and call a new run() method)" toReformat="true" toShortenFQNames="true">
<variable name="CLASS" expression="fileNameWithoutExtension()" defaultValue="" alwaysStopAt="false" />
<context>
<option name="COMPLETION" value="false" />
<option name="JAVA_CODE" value="true" />
<option name="JAVA_COMMENT" value="false" />
<option name="JAVA_EXPRESSION" value="false" />
<option name="JAVA_STATEMENT" value="false" />
<option name="JAVA_STRING" value="false" />
</context>
</template>
<template name="map" value="Map&lt;$KEY$, $VALUE$&gt; $var$ = new HashMap&lt;&gt;();" description="Map&lt;K,V&gt; m = new HashMap&lt;&gt;();" toReformat="false" toShortenFQNames="true">
<variable name="KEY" expression="className()" defaultValue="" alwaysStopAt="true" />
<variable name="VALUE" expression="className()" defaultValue="" alwaysStopAt="true" />
<variable name="var" expression="completeSmart()" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="method" value="/*******************************************************************************&#10; **&#10; *******************************************************************************/&#10;$ACCESS$ $TYPE$ $NAME$($ARGS$)&#10;{&#10; $TYPE$ rs = new $TYPE$();&#10; $END$&#10; return(rs);&#10;}" description="Write a method (that returns something)" toReformat="true" toShortenFQNames="true">
<variable name="ACCESS" expression="enum(&quot;public&quot;, &quot;private&quot;, &quot;protected&quot;)" defaultValue="" alwaysStopAt="true" />
<variable name="TYPE" expression="" defaultValue="&quot;Object&quot;" alwaysStopAt="true" />
<variable name="NAME" expression="" defaultValue="&quot;foo&quot;" alwaysStopAt="true" />
<variable name="ARGS" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="methodVoid" value="/*******************************************************************************&#10; **&#10; *******************************************************************************/&#10;$ACCESS$ void $NAME$($ARGS$)&#10;{&#10; $END$&#10;}" description="Write a method (that returns void)" toReformat="false" toShortenFQNames="true">
<variable name="ACCESS" expression="enum(&quot;public&quot;, &quot;private&quot;, &quot;protected&quot;)" defaultValue="" alwaysStopAt="true" />
<variable name="NAME" expression="" defaultValue="&quot;foo&quot;" alwaysStopAt="true" />
<variable name="ARGS" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="new" value="$TYPE$ $INSTANCE$ = new $TYPE$($END$);" description="Create new instance" toReformat="false" toShortenFQNames="true">
<variable name="TYPE" expression="" defaultValue="&quot;Object&quot;" alwaysStopAt="true" />
<variable name="INSTANCE" expression="camelCase(TYPE)" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="newal" value="new ArrayList&lt;&gt;()" description="new ArrayList&lt;&gt;()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="newhm" value="new HashMap&lt;&gt;()" description="new HashMap&lt;&gt;()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="newhs" value="new HashSet&lt;&gt;()" description="new LinkedList&lt;&gt;()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="newlh" value="new ListingHash&lt;&gt;()" description="new ListingHash&lt;&gt;()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="newlhm" value="new LinkedHashMap&lt;&gt;()" description="new LinkedHashMap&lt;&gt;()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="newll" value="new LinkedList&lt;&gt;()" description="new LinkedList&lt;&gt;()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="set" value="Set&lt;$TYPE$&gt; $var$ = new HashSet&lt;&gt;();" description="Set&lt;T&gt; s = new HashSet&lt;&gt;();" toReformat="false" toShortenFQNames="true">
<variable name="TYPE" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="var" expression="completeSmart()" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="singleton" value="private static $CLASS_NAME$ $INSTANCE$ = null;&#10;&#10;&#10;/*******************************************************************************&#10; ** Singleton constructor&#10; *******************************************************************************/&#10;private $CLASS_NAME$()&#10;{&#10; &#10;}&#10;&#10;&#10;&#10;/*******************************************************************************&#10; ** Singleton accessor&#10; *******************************************************************************/&#10;public static $CLASS_NAME$ getInstance()&#10;{&#10; if($INSTANCE$ == null)&#10; {&#10; $INSTANCE$ = new $CLASS_NAME$();&#10; }&#10; return ($INSTANCE$);&#10;}&#10;&#10;" description="Create a singleton field, constructor, and accessor" toReformat="false" toShortenFQNames="true">
<variable name="CLASS_NAME" expression="className()" defaultValue="" alwaysStopAt="false" />
<variable name="INSTANCE" expression="decapitalize(className())" defaultValue="" alwaysStopAt="false" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="tcp" value="try&#10;{&#10; $END$&#10;}&#10;catch(Exception e)&#10;{&#10; e.printStackTrace();&#10;}" description="try { } catch(Exception e) { e.printStackTrace() }" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="tcpr" value="try&#10;{&#10; $END$&#10;}&#10;catch(Exception e)&#10;{&#10; e.printStackTrace();&#10; throw(e);&#10;}" description="try { } catch(Exception e) { e.printStackTrace(); throw(e); }" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="tct" value="try&#10;{&#10; $END$&#10;}&#10;catch(Exception e)&#10;{&#10; throw(new $THROW$(&quot;$MESSAGE$&quot;, e));&#10;}" description="try { } catch(Exception e) { throw(new Exception(&quot;&quot;, e); }" toReformat="false" toShortenFQNames="true">
<variable name="THROW" expression="expectedType()" defaultValue="&quot;Exception&quot;" alwaysStopAt="true" />
<variable name="MESSAGE" expression="" defaultValue="&quot;Error&quot;" alwaysStopAt="true" />
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="testMethod" value="/*******************************************************************************&#10;** &#10;*******************************************************************************/&#10;@Test&#10;void test$NAME_SUFFIX$()&#10;{&#10; $END$&#10;}&#10;" description="Write a test method" toReformat="true" toShortenFQNames="true">
<variable name="NAME_SUFFIX" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="with" value="/*******************************************************************************&#10; ** Fluent setter for $field$&#10; **&#10; *******************************************************************************/&#10;public $thisClass$ with$fieldUcFirst$($fieldType$ $field$)&#10;{&#10; set$fieldUcFirst$($field$);&#10; return (this);&#10;}&#10;" description="Write a fluent setter" toReformat="false" toShortenFQNames="true">
<variable name="field" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="thisClass" expression="className()" defaultValue="" alwaysStopAt="false" />
<variable name="fieldUcFirst" expression="capitalize(field)" defaultValue="" alwaysStopAt="false" />
<variable name="fieldType" expression="typeOfVariable(field)" defaultValue="" alwaysStopAt="false" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>

View File

@ -0,0 +1,54 @@
<template name="alterAddColumn" value="ALTER TABLE $TABLE$ ADD $COLUMN$ $TYPE$;" description="Write an ALTER TABLE SQL statement to add a column" toReformat="false" toShortenFQNames="true">
<variable name="TABLE" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="COLUMN" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="TYPE" expression="enum(&quot;INTEGER&quot;,&quot;VARCHAR(250)&quot;,&quot;BOOLEAN&quot;,&quot;DECIMAL(12,2)&quot;,&quot;DATE&quot;,&quot;TIMESTAMP&quot;)" defaultValue="INTEGER" alwaysStopAt="true" />
<context>
<option name="SQL" value="true" />
<option name="XML_TEXT" value="true" />
</context>
</template>
<template name="alterAddIndex" value="ALTER TABLE $TABLE$ ADD INDEX $INDEX_NAME$ ($COLUMN$);" description="Write an ALTER TABLE SQL statement to add an index" toReformat="false" toShortenFQNames="true">
<variable name="TABLE" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="COLUMN" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="INDEX_NAME" expression="concat(&quot;i_&quot;, regularExpression(regularExpression(COLUMN, &quot;,&quot;, &quot;_&quot;), &quot; &quot;, &quot;&quot;)" defaultValue="" alwaysStopAt="true" />
<context>
<option name="SQL" value="true" />
<option name="XML_TEXT" value="true" />
</context>
</template>
<template name="changeSetCreateTable" value="&lt;changeSet author=&quot;$USER$&quot; id=&quot;$STORY$-$INDEX$&quot;&gt;&#10; &lt;createTable tableName=&quot;$TABLE$&quot;&gt;&#10; &lt;column autoIncrement=&quot;true&quot; name=&quot;id&quot; type=&quot;INTEGER&quot;&gt;&#10; &lt;constraints nullable=&quot;false&quot; primaryKey=&quot;true&quot; primaryKeyName=&quot;$TABLE$_pkey&quot;/&gt;&#10; &lt;/column&gt;&#10;&#10; &lt;column defaultValueComputed=&quot;CURRENT_TIMESTAMP&quot; name=&quot;create_date&quot; type=&quot;TIMESTAMP&quot;/&gt;&#10; &lt;column defaultValueComputed=&quot;CURRENT_TIMESTAMP&quot; name=&quot;modify_date&quot; type=&quot;TIMESTAMP&quot;/&gt;&#10;&#10; &lt;column name=&quot;$END$&quot; type=&quot;&quot;/&gt;&#10; &lt;/createTable&gt;&#10;&lt;/changeSet&gt;" description="Write a Liquibase createTable changeset" toReformat="false" toShortenFQNames="true">
<variable name="USER" expression="user()" defaultValue="" alwaysStopAt="true" />
<variable name="STORY" expression="fileNameWithoutExtension()" defaultValue="" alwaysStopAt="true" />
<variable name="INDEX" expression="" defaultValue="&quot;1&quot;" alwaysStopAt="true" />
<variable name="TABLE" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="XML_TEXT" value="true" />
</context>
</template>
<template name="changeSetCreateTableAndLoadData" value="&lt;changeSet author=&quot;$USER$&quot; id=&quot;$STORY$-$INDEX$&quot;&gt;&#10; &lt;createTable tableName=&quot;$TABLE$&quot;&gt;&#10; &lt;column autoIncrement=&quot;true&quot; name=&quot;id&quot; type=&quot;INTEGER&quot;&gt;&#10; &lt;constraints nullable=&quot;false&quot; primaryKey=&quot;true&quot; primaryKeyName=&quot;$TABLE$_pkey&quot;/&gt;&#10; &lt;/column&gt;&#10;&#10; &lt;column defaultValueComputed=&quot;CURRENT_TIMESTAMP&quot; name=&quot;create_date&quot; type=&quot;TIMESTAMP&quot;/&gt;&#10; &lt;column defaultValueComputed=&quot;CURRENT_TIMESTAMP&quot; name=&quot;modify_date&quot; type=&quot;TIMESTAMP&quot;/&gt;&#10;&#10; &lt;column name=&quot;$END$&quot; type=&quot;&quot;/&gt;&#10; &lt;/createTable&gt;&#10;&lt;/changeSet&gt;&#10;&#10;&lt;changeSet author=&quot;$USER$&quot; id=&quot;$STORY$-$NEXT_INDEX$&quot;&gt;&#10; &lt;loadData file=&quot;src/main/resources/liquibase/data/$TABLE$.csv&quot; tableName=&quot;$TABLE$&quot; /&gt;&#10;&lt;/changeSet&gt;" description="Write 2 Liquibase changesets, to create a table and load its data" toReformat="false" toShortenFQNames="true">
<variable name="USER" expression="user()" defaultValue="" alwaysStopAt="true" />
<variable name="STORY" expression="fileNameWithoutExtension()" defaultValue="" alwaysStopAt="true" />
<variable name="INDEX" expression="" defaultValue="&quot;1&quot;" alwaysStopAt="true" />
<variable name="TABLE" expression="" defaultValue="" alwaysStopAt="true" />
<variable name="NEXT_INDEX" expression="groovyScript(&quot;try { return(Integer.parseInt(_1) + 1) } catch(Exception e) { return ''; }&quot;, INDEX)" defaultValue="" alwaysStopAt="false" />
<context>
<option name="XML_TEXT" value="true" />
</context>
</template>
<template name="changeSetLoadData" value="&lt;changeSet author=&quot;$USER$&quot; id=&quot;$STORY$-$INDEX$&quot;&gt;&#10; &lt;loadData file=&quot;src/main/resources/liquibase/data/$TABLE$.csv&quot; tableName=&quot;$TABLE$&quot; /&gt;&#10;&lt;/changeSet&gt;" description="Write a Liquibase loadData changeset" toReformat="false" toShortenFQNames="true">
<variable name="USER" expression="user()" defaultValue="" alwaysStopAt="true" />
<variable name="STORY" expression="fileNameWithoutExtension()" defaultValue="" alwaysStopAt="true" />
<variable name="INDEX" expression="" defaultValue="&quot;1&quot;" alwaysStopAt="true" />
<variable name="TABLE" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="XML_TEXT" value="true" />
</context>
</template>
<template name="changeSetSQL" value="&lt;changeSet author=&quot;$USER$&quot; id=&quot;$STORY$-$INDEX$&quot;&gt;&#10; &lt;sql&gt;&#10; $END$&#10; &lt;/sql&gt;&#10;&lt;/changeSet&gt;" description="Write a Liquibase SQL changeset" toReformat="false" toShortenFQNames="true">
<variable name="USER" expression="user()" defaultValue="" alwaysStopAt="true" />
<variable name="STORY" expression="fileNameWithoutExtension()" defaultValue="" alwaysStopAt="true" />
<variable name="INDEX" expression="" defaultValue="" alwaysStopAt="true" />
<context>
<option name="XML_TEXT" value="true" />
</context>
</template>

View File

@ -0,0 +1,46 @@
<template name="declog" value="private static final Logger LOG = LogManager.getLogger($THIS_CLASS$.class);" description="Declare a logger object (LOG) for a class without one" toReformat="false" toShortenFQNames="true">
<variable name="THIS_CLASS" expression="fileNameWithoutExtension()" defaultValue="" alwaysStopAt="false" />
<context>
<option name="JAVA_DECLARATION" value="true" />
</context>
</template>
<template name="qcount" value="CountInput countInput = new CountInput(qInstance);&#10;countInput.setSession(new QSession());&#10;countInput.setTableName(tableName);&#10;CountOutput countOutput = new CountAction().execute(countInput);&#10;" description="Write code to execute a QQQ CountAction" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="qdf" value=".withDisplayFormat(DisplayFormat.$END$)" description="QQQ field - .withDisplayFormat()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="qdfcom" value=".withDisplayFormat(DisplayFormat.COMMAS)" description="QQQ field - .withDisplayFormat(COMMAS)" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="qdfcur" value=".withDisplayFormat(DisplayFormat.CURRENCY)" description="QQQ field - .withDisplayFormat(CURRENCY)" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="qinsert" value="InsertInput insertInput = new InsertInput(qInstance);&#10;insertInput.setSession(new QSession());&#10;insertInput.setTableName(tableName);&#10;insertInput.setRecords($END$);&#10;InsertOutput insertOutput = new InsertAction().execute(insertInput);&#10;" description="Write code to execute a QQQ InsertAction" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="qlab" value=".withLabel(&quot;$END$&quot;)" description="QQQ meta data objects - .withLabel()" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_EXPRESSION" value="true" />
</context>
</template>
<template name="qquery" value="QueryInput queryInput = new QueryInput(qInstance);&#10;queryInput.setSession(new QSession());&#10;queryInput.setTableName(tableName);&#10;QueryOutput queryOutput = new QueryAction().execute(queryInput);&#10;" description="Write code to execute a QQQ QueryAction" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>
<template name="qupdate" value="UpdateInput updateInput = new UpdateInput($END$);&#10;updateInput.setSession();&#10;updateInput.setTableName();&#10;updateInput.setRecords();&#10;UpdateOutput updateOutput = new UpdateAction().execute(updateInput);&#10;" description="Write code to execute a QQQ UpdateAction" toReformat="false" toShortenFQNames="true">
<context>
<option name="JAVA_STATEMENT" value="true" />
</context>
</template>

View File

@ -0,0 +1,14 @@
## Usage
You can add these live templates to your IntelliJ as follows:
- Get the contents of one or more files on your clipboard
- e.g., `cat Kingsrook-SQL.xml | pbcopy`
- IntelliJ > Preferences > Editor > Live Templates
- Select the Template Group that you want to put them in
- Recommended that you use a group whose name matches the file name you're loading from
- You may need to create a new one group via the + button
- Right-click on the Template Group and choose Paste.
## Publishing
From IntelliJ > Preferences > Editor > Live Templates, right-click
on one or more Live Templates to copy it - paste it into an XML file.

View File

@ -26,7 +26,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.dashboard.widgets.ChartData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
@ -63,7 +63,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
labels.add("May 2022"); labels.add("May 2022");
data.add(64); data.add(64);
return (new BarChart("Persons created per Month", "Person records", labels, data)); return (new ChartData("Persons created per Month", null, "Person records", labels, data));
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -26,7 +26,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.dashboard.widgets.ChartData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; 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.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
@ -89,7 +89,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer
labels.add("May 2022"); labels.add("May 2022");
data.add(64); data.add(64);
return (new BarChart("Persons created per Month", "Person records", labels, data)); return (new ChartData("Persons created per Month", null, "Person records", labels, data));
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -23,7 +23,7 @@ package com.kingsrook.sampleapp.dashboard.widgets;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.dashboard.widgets.ChartData;
import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.sampleapp.SampleMetaDataProvider; import com.kingsrook.sampleapp.SampleMetaDataProvider;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
/******************************************************************************* /*******************************************************************************
** Unit test for PersonsByCreateDateBarChart ** Unit test for PersonsByCreateDateBarChart
*******************************************************************************/ *******************************************************************************/
class PersonsByCreateDateBarChartTest class PersonsByCreateDateChartTestData
{ {
/******************************************************************************* /*******************************************************************************
@ -45,11 +45,11 @@ class PersonsByCreateDateBarChartTest
void test() throws QException void test() throws QException
{ {
Object widgetData = new PersonsByCreateDateBarChart().render(SampleMetaDataProvider.defineInstance(), new QSession(), null); Object widgetData = new PersonsByCreateDateBarChart().render(SampleMetaDataProvider.defineInstance(), new QSession(), null);
assertThat(widgetData).isInstanceOf(BarChart.class); assertThat(widgetData).isInstanceOf(ChartData.class);
BarChart barChart = (BarChart) widgetData; ChartData chartData = (ChartData) widgetData;
assertEquals("barChart", barChart.getType()); assertEquals("chartData", chartData.getType());
assertThat(barChart.getTitle()).isNotBlank(); assertThat(chartData.getTitle()).isNotBlank();
assertNotNull(barChart.getBarChartData()); assertNotNull(chartData.getChartData());
} }
} }