From 8891b3e7ea8c95fc114097b2c24a8973bd781052 Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Fri, 16 Sep 2022 10:19:06 -0500 Subject: [PATCH] QQQ-41: added app sections, new widgets, ability to have no tables under app, etc. --- .../dashboard/AbstractWidgetRenderer.java | 10 + .../core/actions/metadata/MetaDataAction.java | 8 +- .../core/instances/QInstanceEnricher.java | 55 +++ .../core/instances/QInstanceValidator.java | 64 ++- .../widgets/{BarChart.java => ChartData.java} | 102 +++-- .../dashboard/widgets/LineChartData.java | 344 +++++++++++++++ .../model/dashboard/widgets/LocationData.java | 233 ++++++++++ .../dashboard/widgets/StatisticsData.java | 206 +++++++++ .../model/dashboard/widgets/TableData.java | 417 ++++++++++++++++++ .../frontend/QFrontendAppMetaData.java | 41 +- .../model/metadata/layout/QAppMetaData.java | 78 +++- .../model/metadata/layout/QAppSection.java | 233 ++++++++++ .../PersonsByCreateDateBarChart.java | 6 +- .../dashboard/WidgetDataLoaderTest.java | 14 +- .../core/instances/QInstanceEnricherTest.java | 37 +- .../instances/QInstanceValidatorTest.java | 121 ++++- .../javalin/PersonsByCreateDateBarChart.java | 6 +- .../widgets/PersonsByCreateDateBarChart.java | 4 +- ... => PersonsByCreateDateChartTestData.java} | 14 +- 19 files changed, 1923 insertions(+), 70 deletions(-) rename qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/{BarChart.java => ChartData.java} (76%) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LineChartData.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LocationData.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/StatisticsData.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java rename qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/{PersonsByCreateDateBarChartTest.java => PersonsByCreateDateChartTestData.java} (83%) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java index ba65c767..f7920eceb 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/AbstractWidgetRenderer.java @@ -40,4 +40,14 @@ public abstract class AbstractWidgetRenderer *******************************************************************************/ public abstract Object render(QInstance qInstance, QSession session, QWidgetMetaDataInterface qWidgetMetaData) throws QException; + + + /******************************************************************************* + ** + *******************************************************************************/ + public String getWidgetName() + { + return this.getClass().getSimpleName(); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java index 97d460ab..f1b5c58e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataAction.java @@ -38,6 +38,7 @@ 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.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; /******************************************************************************* @@ -89,9 +90,12 @@ public class MetaDataAction apps.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue())); treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue())); - for(QAppChildMetaData child : entry.getValue().getChildren()) + if(CollectionUtils.nullSafeHasContents(entry.getValue().getChildren())) { - apps.get(entry.getKey()).addChild(new AppTreeNode(child)); + for(QAppChildMetaData child : entry.getValue().getChildren()) + { + apps.get(entry.getKey()).addChild(new AppTreeNode(child)); + } } } metaDataOutput.setApps(apps); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java index d56f830a..b7e0d2b1 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java @@ -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.fields.QFieldMetaData; 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.QAppSection; 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.QFrontendComponentMetaData; @@ -211,6 +213,11 @@ public class QInstanceEnricher { app.setLabel(nameToLabel(app.getName())); } + + if(CollectionUtils.nullSafeIsEmpty(app.getSections())) + { + generateAppSections(app); + } } @@ -511,6 +518,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" *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index f1148d43..92c15190 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -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.layout.QAppChildMetaData; 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.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; @@ -213,7 +214,7 @@ public class QInstanceValidator { for(QFieldSection section : table.getSections()) { - validateSection(table, section, fieldNamesInSections); + validateFieldSection(table, section, fieldNamesInSections); if(section.getTier().equals(Tier.T1)) { 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 fieldNamesInSections) + private void validateFieldSection(QTableMetaData table, QFieldSection section, Set fieldNamesInSections) { 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() + "."); @@ -500,6 +501,42 @@ public class QInstanceValidator + /******************************************************************************* + ** + *******************************************************************************/ + private void validateAppSection(QAppMetaData app, QAppSection section, Set 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()); } } + + ////////////////////////////////////////// + // validate field sections in the table // + ////////////////////////////////////////// + Set 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."); + } + } + } }); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/BarChart.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java similarity index 76% rename from qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/BarChart.java rename to qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java index 27f6689b..33e483cb 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/BarChart.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java @@ -29,32 +29,34 @@ import java.util.List; ** Model containing datastructure expected by frontend bar chart widget ** *******************************************************************************/ -public class BarChart implements QWidget +public class ChartData implements QWidget { - /* - type: "barChart", - title: "Parcel Invoice Lines per Month", - barChartData: { - labels: ["Feb 22", "Mar 22", "Apr 22", "May 22", "Jun 22", "Jul 22", "Aug 22"], - datasets: {label: "Parcel Invoice Lines", data: [50000, 22000, 11111, 22333, 40404, 9876, 2355]}, - }, + interface BarChartData{ + labels: string[]; + datasets: { + label: string; + data: number[]; + }[]; + } */ private String title; - private Data barChartData; + private String description; + private Data chartData; /******************************************************************************* ** *******************************************************************************/ - public BarChart(String title, String seriesLabel, List labels, List data) + public ChartData(String title, String description, String seriesLabel, List labels, List data) { setTitle(title); - setBarChartData(new BarChart.Data() + setDescription(description); + setChartData(new ChartData.Data() .withLabels(labels) - .withDatasets(new BarChart.Data.DataSet() + .withDatasets(new ChartData.Data.Dataset() .withLabel(seriesLabel) .withData(data))); } @@ -98,7 +100,7 @@ public class BarChart implements QWidget ** Fluent setter for title ** *******************************************************************************/ - public BarChart withTitle(String title) + public ChartData withTitle(String title) { this.title = title; 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); } @@ -146,7 +182,7 @@ public class BarChart implements QWidget public static class Data { private List labels; - private DataSet datasets; + private Dataset dataset; @@ -188,9 +224,9 @@ public class BarChart implements QWidget ** 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 ** *******************************************************************************/ - 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 ** *******************************************************************************/ - public Data withDatasets(DataSet datasets) + public Data withDatasets(Dataset datasets) { - this.datasets = datasets; + this.dataset = datasets; return (this); } @@ -221,9 +257,9 @@ public class BarChart implements QWidget /******************************************************************************* ** *******************************************************************************/ - public static class DataSet + public static class Dataset { - private String label; + private String label; private List data; @@ -254,7 +290,7 @@ public class BarChart implements QWidget ** Fluent setter for label ** *******************************************************************************/ - public DataSet withLabel(String label) + public Dataset withLabel(String label) { this.label = label; return (this); @@ -288,7 +324,7 @@ public class BarChart implements QWidget ** Fluent setter for data ** *******************************************************************************/ - public DataSet withData(List data) + public Dataset withData(List data) { this.data = data; return (this); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LineChartData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LineChartData.java new file mode 100644 index 00000000..50818aaa --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LineChartData.java @@ -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 . + */ + +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 labels, List lineLabels, List 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 labels; + private List lineLabels; + private List datasets; + + + + /******************************************************************************* + ** Getter for labels + ** + *******************************************************************************/ + public List getLabels() + { + return labels; + } + + + + /******************************************************************************* + ** Setter for labels + ** + *******************************************************************************/ + public void setLabels(List labels) + { + this.labels = labels; + } + + + + /******************************************************************************* + ** Fluent setter for labels + ** + *******************************************************************************/ + public Data withLabels(List labels) + { + this.labels = labels; + return (this); + } + + + + /******************************************************************************* + ** Getter for lineLabels + ** + *******************************************************************************/ + public List getLineLabels() + { + return lineLabels; + } + + + + /******************************************************************************* + ** Setter for lineLabels + ** + *******************************************************************************/ + public void setLineLabels(List lineLabels) + { + this.lineLabels = lineLabels; + } + + + + /******************************************************************************* + ** Fluent setter for lineLabels + ** + *******************************************************************************/ + public Data withLineLabels(List lineLabels) + { + this.lineLabels = lineLabels; + return (this); + } + + + + /******************************************************************************* + ** Getter for datasets + ** + *******************************************************************************/ + public List getDatasets() + { + return datasets; + } + + + + /******************************************************************************* + ** Setter for datasets + ** + *******************************************************************************/ + public void setDatasets(List datasets) + { + this.datasets = datasets; + } + + + + /******************************************************************************* + ** Fluent setter for datasets + ** + *******************************************************************************/ + public Data withDatasets(List datasets) + { + this.datasets = datasets; + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static class Dataset + { + private String label; + private List data; + + + + /******************************************************************************* + ** Getter for label + ** + *******************************************************************************/ + public String getLabel() + { + return label; + } + + + + /******************************************************************************* + ** Setter for label + ** + *******************************************************************************/ + public void setLabel(String label) + { + this.label = label; + } + + + + /******************************************************************************* + ** Fluent setter for label + ** + *******************************************************************************/ + public Dataset withLabel(String label) + { + this.label = label; + return (this); + } + + + + /******************************************************************************* + ** Getter for data + ** + *******************************************************************************/ + public List getData() + { + return data; + } + + + + /******************************************************************************* + ** Setter for data + ** + *******************************************************************************/ + public void setData(List data) + { + this.data = data; + } + + + + /******************************************************************************* + ** Fluent setter for data + ** + *******************************************************************************/ + public Dataset withData(List data) + { + this.data = data; + return (this); + } + } + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LocationData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LocationData.java new file mode 100644 index 00000000..439466ea --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/LocationData.java @@ -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 . + */ + +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); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/StatisticsData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/StatisticsData.java new file mode 100644 index 00000000..5e62e0d0 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/StatisticsData.java @@ -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 . + */ + +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); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java new file mode 100644 index 00000000..2d3ec96e --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java @@ -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 . + */ + +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: , + value: $140,925, + adsSpent: $24,531, + refunds: , + }, + ] + } + */ + + private String title; + private List columns; + private List> rows; + private List dropdownOptions; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public TableData(String title, List columns, List> rows, List 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 getColumns() + { + return columns; + } + + + + /******************************************************************************* + ** Setter for columns + ** + *******************************************************************************/ + public void setColumns(List columns) + { + this.columns = columns; + } + + + + /******************************************************************************* + ** Fluent setter for columns + ** + *******************************************************************************/ + public TableData withColumns(List columns) + { + this.columns = columns; + return (this); + } + + + + /******************************************************************************* + ** Getter for rows + ** + *******************************************************************************/ + public List> getRows() + { + return rows; + } + + + + /******************************************************************************* + ** Setter for rows + ** + *******************************************************************************/ + public void setRows(List> rows) + { + this.rows = rows; + } + + + + /******************************************************************************* + ** Fluent setter for rows + ** + *******************************************************************************/ + public TableData withRows(List> rows) + { + this.rows = rows; + return (this); + } + + + + /******************************************************************************* + ** Getter for dropdownOptions + ** + *******************************************************************************/ + public List getDropdownOptions() + { + return dropdownOptions; + } + + + + /******************************************************************************* + ** Setter for dropdownOptions + ** + *******************************************************************************/ + public void setDropdownOptions(List dropdownOptions) + { + this.dropdownOptions = dropdownOptions; + } + + + + /******************************************************************************* + ** Fluent setter for dropdownOptions + ** + *******************************************************************************/ + public TableData withDropdownOptions(List 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; + } + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java index a4bd3ba4..9471c95c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendAppMetaData.java @@ -23,10 +23,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonInclude; 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.QAppSection; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -39,12 +42,14 @@ public class QFrontendAppMetaData { private String name; private String label; - - private List children = new ArrayList<>(); - private List widgets = new ArrayList<>(); - private String iconName; + private List widgets = new ArrayList<>(); + private List children = new ArrayList<>(); + private Map childMap = new HashMap<>(); + + private List sections; + /******************************************************************************* @@ -64,6 +69,11 @@ public class QFrontendAppMetaData { 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 getChildMap() + { + return childMap; + } + + + /******************************************************************************* ** Getter for iconName ** @@ -132,6 +153,7 @@ public class QFrontendAppMetaData { children = new ArrayList<>(); } + childMap.put(childAppTreeNode.getName(), childAppTreeNode); children.add(childAppTreeNode); } @@ -145,4 +167,15 @@ public class QFrontendAppMetaData { return widgets; } + + + + /******************************************************************************* + ** Getter for sections + ** + *******************************************************************************/ + public List getSections() + { + return sections; + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java index be38cb87..36f2a0a2 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java @@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout; import java.util.ArrayList; import java.util.List; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; /******************************************************************************* @@ -40,7 +41,9 @@ public class QAppMetaData implements QAppChildMetaData private String parentAppName; private QIcon icon; - private List widgets; + private List widgets; + private List sections; + /******************************************************************************* @@ -137,7 +140,13 @@ public class QAppMetaData implements QAppChildMetaData *******************************************************************************/ public void setChildren(List 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 children) { - this.children = children; + setChildren(children); return (this); } @@ -205,6 +214,7 @@ public class QAppMetaData implements QAppChildMetaData } + /******************************************************************************* ** Getter for icon ** @@ -226,6 +236,7 @@ public class QAppMetaData implements QAppChildMetaData } + /******************************************************************************* ** Fluent setter for icon ** @@ -238,7 +249,6 @@ public class QAppMetaData implements QAppChildMetaData - /******************************************************************************* ** Getter for widgets ** @@ -260,6 +270,7 @@ public class QAppMetaData implements QAppChildMetaData } + /******************************************************************************* ** Fluent setter for widgets ** @@ -270,4 +281,63 @@ public class QAppMetaData implements QAppChildMetaData return (this); } + + + /******************************************************************************* + ** Getter for sections + ** + *******************************************************************************/ + public List getSections() + { + return sections; + } + + + + /******************************************************************************* + ** Setter for sections + ** + *******************************************************************************/ + public void setSections(List sections) + { + this.sections = sections; + } + + + + /******************************************************************************* + ** Fluent setter for sections + ** + *******************************************************************************/ + public QAppMetaData withSections(List 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); + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java new file mode 100644 index 00000000..d2d80340 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppSection.java @@ -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 . + */ + +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 tables; + private List processes; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QAppSection() + { + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QAppSection(String name, String label, QIcon icon, List tables, List 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 getTables() + { + return tables; + } + + + + /******************************************************************************* + ** Setter for tables + ** + *******************************************************************************/ + public void setTables(List tables) + { + this.tables = tables; + } + + + + /******************************************************************************* + ** Fluent setter for tables + ** + *******************************************************************************/ + public QAppSection withTables(List tables) + { + this.tables = tables; + return (this); + } + + + + /******************************************************************************* + ** Getter for processes + ** + *******************************************************************************/ + public List getProcesses() + { + return processes; + } + + + + /******************************************************************************* + ** Setter for processes + ** + *******************************************************************************/ + public void setProcesses(List processes) + { + this.processes = processes; + } + + + + /******************************************************************************* + ** Fluent setter for processes + ** + *******************************************************************************/ + public QAppSection withProcesses(List 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); + } + +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java index 8ba67623..cc84b799 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/PersonsByCreateDateBarChart.java @@ -25,7 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.dashboard; import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.model.dashboard.widgets.BarChart; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChartData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.session.QSession; @@ -45,7 +45,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer try { List labels = new ArrayList<>(); - List data = new ArrayList<>(); + List data = new ArrayList<>(); labels.add("Jan. 2022"); data.add(17); @@ -62,7 +62,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer labels.add("May 2022"); 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) { diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java index fbc6412f..ad7fb9b5 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/dashboard/WidgetDataLoaderTest.java @@ -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.model.dashboard.widgets.BarChart; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChartData; import com.kingsrook.qqq.backend.core.utils.TestUtils; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -43,11 +43,11 @@ class WidgetDataLoaderTest void test() throws QException { Object widgetData = new WidgetDataLoader().execute(TestUtils.defineInstance(), TestUtils.getMockSession(), PersonsByCreateDateBarChart.class.getSimpleName()); - assertThat(widgetData).isInstanceOf(BarChart.class); - BarChart barChart = (BarChart) widgetData; - assertEquals("barChart", barChart.getType()); - assertThat(barChart.getTitle()).isNotBlank(); - assertNotNull(barChart.getBarChartData()); + assertThat(widgetData).isInstanceOf(ChartData.class); + ChartData chartData = (ChartData) widgetData; + assertEquals("chartData", chartData.getType()); + assertThat(chartData.getTitle()).isNotBlank(); + assertNotNull(chartData.getChartData()); } -} \ No newline at end of file +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java index c86e9fca..d4669c30 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricherTest.java @@ -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.utils.TestUtils; 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.assertNotNull; 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"); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -175,12 +208,12 @@ class QInstanceEnricherTest assertNull(table.getRecordLabelFormat()); qInstance = TestUtils.defineInstance(); - table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName"); + table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName"); new QInstanceEnricher().enrich(qInstance); assertEquals("%s", table.getRecordLabelFormat()); qInstance = TestUtils.defineInstance(); - table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName", "lastName"); + table = qInstance.getTable("person").withRecordLabelFormat(null).withRecordLabelFields("firstName", "lastName"); new QInstanceEnricher().enrich(qInstance); assertEquals("%s %s", table.getRecordLabelFormat()); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index 9a4e2f9e..d3397d5f 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -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.QFieldType; 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.possiblevalues.QPossibleValue; 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.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.automation.TableAutomationAction; import com.kingsrook.qqq.backend.core.utils.TestUtils; import org.junit.jupiter.api.Test; 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 } + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/PersonsByCreateDateBarChart.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/PersonsByCreateDateBarChart.java index 18277f6f..268d5c8b 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/PersonsByCreateDateBarChart.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/PersonsByCreateDateBarChart.java @@ -26,7 +26,7 @@ import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.model.dashboard.widgets.BarChart; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChartData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.session.QSession; @@ -46,7 +46,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer try { List labels = new ArrayList<>(); - List data = new ArrayList<>(); + List data = new ArrayList<>(); labels.add("Jan. 2022"); data.add(17); @@ -63,7 +63,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer labels.add("May 2022"); 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) { diff --git a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChart.java b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChart.java index decad446..b6e38fb2 100644 --- a/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChart.java +++ b/qqq-sample-project/src/main/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChart.java @@ -26,7 +26,7 @@ import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.model.dashboard.widgets.BarChart; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChartData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; import com.kingsrook.qqq.backend.core.model.session.QSession; @@ -89,7 +89,7 @@ public class PersonsByCreateDateBarChart extends AbstractWidgetRenderer labels.add("May 2022"); 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) { diff --git a/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChartTest.java b/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateChartTestData.java similarity index 83% rename from qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChartTest.java rename to qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateChartTestData.java index 9cfb1d47..f70ca51d 100644 --- a/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateBarChartTest.java +++ b/qqq-sample-project/src/test/java/com/kingsrook/sampleapp/dashboard/widgets/PersonsByCreateDateChartTestData.java @@ -23,7 +23,7 @@ package com.kingsrook.sampleapp.dashboard.widgets; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.model.dashboard.widgets.BarChart; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChartData; import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.sampleapp.SampleMetaDataProvider; import org.junit.jupiter.api.Test; @@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; /******************************************************************************* ** Unit test for PersonsByCreateDateBarChart *******************************************************************************/ -class PersonsByCreateDateBarChartTest +class PersonsByCreateDateChartTestData { /******************************************************************************* @@ -45,11 +45,11 @@ class PersonsByCreateDateBarChartTest void test() throws QException { Object widgetData = new PersonsByCreateDateBarChart().render(SampleMetaDataProvider.defineInstance(), new QSession(), null); - assertThat(widgetData).isInstanceOf(BarChart.class); - BarChart barChart = (BarChart) widgetData; - assertEquals("barChart", barChart.getType()); - assertThat(barChart.getTitle()).isNotBlank(); - assertNotNull(barChart.getBarChartData()); + assertThat(widgetData).isInstanceOf(ChartData.class); + ChartData chartData = (ChartData) widgetData; + assertEquals("chartData", chartData.getType()); + assertThat(chartData.getTitle()).isNotBlank(); + assertNotNull(chartData.getChartData()); } }