diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/RenderWidgetAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/RenderWidgetAction.java index fe2ed2c1..24f81162 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/RenderWidgetAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/RenderWidgetAction.java @@ -22,11 +22,15 @@ package com.kingsrook.qqq.backend.core.actions.dashboard; +import java.io.Serializable; +import java.util.Map; import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; /******************************************************************************* @@ -44,6 +48,18 @@ public class RenderWidgetAction ActionHelper.validateSession(input); AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, input.getWidgetMetaData().getCodeReference()); + + /////////////////////////////////////////////////////////////// + // move default values from meta data into this render input // + /////////////////////////////////////////////////////////////// + if(input.getWidgetMetaData() instanceof QWidgetMetaData widgetMetaData) + { + for(Map.Entry entry : widgetMetaData.getDefaultValues().entrySet()) + { + input.getQueryParams().putIfAbsent(entry.getKey(), ValueUtils.getValueAsString(entry.getValue())); + } + } + return (widgetRenderer.render(input)); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java new file mode 100644 index 00000000..6119ddd8 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ChildRecordListRenderer.java @@ -0,0 +1,126 @@ +/* + * 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.actions.dashboard.widgets; + + +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractWidgetRenderer; +import com.kingsrook.qqq.backend.core.actions.tables.GetAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException; +import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; +import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; +import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChildRecordListData; +import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; + + +/******************************************************************************* + ** Generic widget for display a list of child records. + *******************************************************************************/ +public class ChildRecordListRenderer extends AbstractWidgetRenderer +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public static QWidgetMetaData defineWidgetFromJoin(QJoinMetaData join) + { + return (new QWidgetMetaData() + .withName(join.getName()) + .withCodeReference(new QCodeReference(ChildRecordListRenderer.class, null)) + .withType(WidgetType.CHILD_RECORD_LIST.getType()) + .withDefaultValue("joinName", join.getName())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public RenderWidgetOutput render(RenderWidgetInput input) throws QException + { + String widgetLabel = input.getQueryParams().get("widgetLabel"); + String joinName = input.getQueryParams().get("joinName"); + QJoinMetaData join = input.getInstance().getJoin(joinName); + String id = input.getQueryParams().get("id"); + + //////////////////////////////////////////////////////// + // fetch the record that we're getting children for. // + // e.g., the left-side of the join, with the input id // + //////////////////////////////////////////////////////// + GetInput getInput = new GetInput(input.getInstance()); + getInput.setSession(input.getSession()); + getInput.setTableName(join.getLeftTable()); + getInput.setPrimaryKey(id); + GetOutput getOutput = new GetAction().execute(getInput); + QRecord record = getOutput.getRecord(); + + if(record == null) + { + QTableMetaData table = input.getInstance().getTable(join.getLeftTable()); + throw (new QNotFoundException("Could not find " + (table == null ? "" : table.getLabel()) + " with primary key " + id)); + } + + //////////////////////////////////////////////////////////////////// + // set up the query - for the table on the right side of the join // + //////////////////////////////////////////////////////////////////// + QQueryFilter filter = new QQueryFilter(); + for(JoinOn joinOn : join.getJoinOns()) + { + filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(record.getValue(joinOn.getLeftField())))); + } + filter.setOrderBys(join.getOrderBys()); + + QueryInput queryInput = new QueryInput(input.getInstance()); + queryInput.setSession(input.getSession()); + queryInput.setTableName(join.getRightTable()); + queryInput.setShouldTranslatePossibleValues(true); + queryInput.setShouldGenerateDisplayValues(true); + queryInput.setFilter(filter); + QueryOutput queryOutput = new QueryAction().execute(queryInput); + + QTableMetaData table = input.getInstance().getTable(join.getRightTable()); + String tablePath = input.getInstance().getTablePath(input, table.getName()); + String viewAllLink = tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()); + + return (new RenderWidgetOutput(new ChildRecordListData(widgetLabel, queryOutput, table, tablePath, viewAllLink))); + } + +} 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 59e65887..7c2b5a0f 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 @@ -285,7 +285,7 @@ public class QInstanceValidator { for(QFieldSection section : table.getSections()) { - validateFieldSection(table, section, fieldNamesInSections); + validateTableSection(qInstance, table, section, fieldNamesInSections); if(section.getTier().equals(Tier.T1)) { assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1"); @@ -658,13 +658,17 @@ public class QInstanceValidator /******************************************************************************* ** *******************************************************************************/ - private void validateFieldSection(QTableMetaData table, QFieldSection section, Set fieldNamesInSections) + private void validateTableSection(QInstance qInstance, 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() + "."); - if(assertCondition(CollectionUtils.nullSafeHasContents(section.getFieldNames()), "Table " + table.getName() + " section " + section.getName() + " does not have any fields.")) + + boolean hasFields = CollectionUtils.nullSafeHasContents(section.getFieldNames()); + boolean hasWidget = StringUtils.hasContent(section.getWidgetName()); + + if(assertCondition(hasFields || hasWidget, "Table " + table.getName() + " section " + section.getName() + " does not have any fields or a widget.")) { - if(table.getFields() != null) + if(table.getFields() != null && hasFields) { for(String fieldName : section.getFieldNames()) { @@ -674,6 +678,10 @@ public class QInstanceValidator fieldNamesInSections.add(fieldName); } } + else if(hasWidget) + { + assertCondition(qInstance.getWidget(section.getWidgetName()) != null, "Table " + table.getName() + " section " + section.getName() + " specifies widget " + section.getWidgetName() + ", which is not a widget in this instance."); + } } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChildRecordListData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChildRecordListData.java new file mode 100644 index 00000000..75ddbf4d --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChildRecordListData.java @@ -0,0 +1,212 @@ +/* + * 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 com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; + + +/******************************************************************************* + ** Model containing data structure expected by frontend ChildRecordList widget + ** + *******************************************************************************/ +public class ChildRecordListData implements QWidget +{ + private String title; + private QueryOutput queryOutput; + private QTableMetaData childTableMetaData; + + private String tablePath; + private String viewAllLink; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public ChildRecordListData(String title, QueryOutput queryOutput, QTableMetaData childTableMetaData, String tablePath, String viewAllLink) + { + this.title = title; + this.queryOutput = queryOutput; + this.childTableMetaData = childTableMetaData; + this.tablePath = tablePath; + this.viewAllLink = viewAllLink; + } + + + + /******************************************************************************* + ** Getter for type + ** + *******************************************************************************/ + public String getType() + { + return WidgetType.CHILD_RECORD_LIST.getType(); + } + + + + /******************************************************************************* + ** Getter for title + ** + *******************************************************************************/ + public String getTitle() + { + return title; + } + + + + /******************************************************************************* + ** Setter for title + ** + *******************************************************************************/ + public void setTitle(String title) + { + this.title = title; + } + + + + /******************************************************************************* + ** Fluent setter for title + ** + *******************************************************************************/ + public ChildRecordListData withTitle(String title) + { + this.title = title; + return (this); + } + + + + /******************************************************************************* + ** Getter for queryOutput + ** + *******************************************************************************/ + public QueryOutput getQueryOutput() + { + return queryOutput; + } + + + + /******************************************************************************* + ** Setter for queryOutput + ** + *******************************************************************************/ + public void setQueryOutput(QueryOutput queryOutput) + { + this.queryOutput = queryOutput; + } + + + + /******************************************************************************* + ** Fluent setter for queryOutput + ** + *******************************************************************************/ + public ChildRecordListData withQueryOutput(QueryOutput queryOutput) + { + this.queryOutput = queryOutput; + return (this); + } + + + + /******************************************************************************* + ** Getter for childTableMetaData + ** + *******************************************************************************/ + public QTableMetaData getChildTableMetaData() + { + return childTableMetaData; + } + + + + /******************************************************************************* + ** Setter for childTableMetaData + ** + *******************************************************************************/ + public void setChildTableMetaData(QTableMetaData childTableMetaData) + { + this.childTableMetaData = childTableMetaData; + } + + + + /******************************************************************************* + ** Fluent setter for childTableMetaData + ** + *******************************************************************************/ + public ChildRecordListData withChildTableMetaData(QTableMetaData childTableMetaData) + { + this.childTableMetaData = childTableMetaData; + return (this); + } + + + + /******************************************************************************* + ** Getter for tablePath + ** + *******************************************************************************/ + public String getTablePath() + { + return tablePath; + } + + + + /******************************************************************************* + ** Setter for tablePath + ** + *******************************************************************************/ + public void setTablePath(String tablePath) + { + this.tablePath = tablePath; + } + + + + /******************************************************************************* + ** Getter for viewAllLink + ** + *******************************************************************************/ + public String getViewAllLink() + { + return viewAllLink; + } + + + + /******************************************************************************* + ** Setter for viewAllLink + ** + *******************************************************************************/ + public void setViewAllLink(String viewAllLink) + { + this.viewAllLink = viewAllLink; + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java index 3e6e4ec3..843441f8 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/WidgetType.java @@ -35,7 +35,8 @@ public enum WidgetType QUICK_SIGHT_CHART("quickSightChart"), STATISTICS("statistics"), STEPPER("stepper"), - TABLE("table"); + TABLE("table"), + CHILD_RECORD_LIST("childRecordList"); private final String type; diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java index 9fc3859e..b3fa4650 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/QInstance.java @@ -28,10 +28,18 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction; +import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey; +import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData; import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface; +import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode; +import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; @@ -64,6 +72,7 @@ public class QInstance // Important to use LinkedHashmap here, to preserve the order in which entries are added. // //////////////////////////////////////////////////////////////////////////////////////////// private Map tables = new LinkedHashMap<>(); + private Map joins = new LinkedHashMap<>(); private Map possibleValueSources = new LinkedHashMap<>(); private Map processes = new LinkedHashMap<>(); private Map apps = new LinkedHashMap<>(); @@ -79,6 +88,9 @@ public class QInstance @JsonIgnore private boolean hasBeenValidated = false; + private Map memoizedTablePaths = new HashMap<>(); + private Map memoizedProcessPaths = new HashMap<>(); + /******************************************************************************* @@ -118,6 +130,71 @@ public class QInstance + /******************************************************************************* + ** Get the full path to a table + *******************************************************************************/ + public String getTablePath(AbstractActionInput actionInput, String tableName) throws QException + { + if(!memoizedTablePaths.containsKey(tableName)) + { + MetaDataInput input = new MetaDataInput(this); + input.setSession(actionInput.getSession()); + MetaDataOutput output = new MetaDataAction().execute(input); + memoizedTablePaths.put(tableName, searchAppTree(output.getAppTree(), tableName, AppTreeNodeType.TABLE, "")); + } + return (memoizedTablePaths.get(tableName)); + } + + + + /******************************************************************************* + ** Get the full path to a process + *******************************************************************************/ + public String getProcessPath(AbstractActionInput actionInput, String processName) throws QException + { + if(!memoizedProcessPaths.containsKey(processName)) + { + MetaDataInput input = new MetaDataInput(this); + input.setSession(actionInput.getSession()); + MetaDataOutput output = new MetaDataAction().execute(input); + return searchAppTree(output.getAppTree(), processName, AppTreeNodeType.PROCESS, ""); + } + return (memoizedProcessPaths.get(processName)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private String searchAppTree(List appTree, String tableName, AppTreeNodeType treeNodeType, String path) + { + if(appTree == null) + { + return (null); + } + + for(AppTreeNode appTreeNode : appTree) + { + if(appTreeNode.getType().equals(treeNodeType) && appTreeNode.getName().equals(tableName)) + { + return (path + "/" + tableName); + } + else if(appTreeNode.getType().equals(AppTreeNodeType.APP)) + { + String subResult = searchAppTree(appTreeNode.getChildren(), tableName, treeNodeType, path + "/" + appTreeNode.getName()); + if(subResult != null) + { + return (subResult); + } + } + } + + return (null); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -243,6 +320,71 @@ public class QInstance + /******************************************************************************* + ** + *******************************************************************************/ + public void addJoin(QJoinMetaData join) + { + addJoin(join.getName(), join); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addJoin(String name, QJoinMetaData join) + { + if(!StringUtils.hasContent(name)) + { + throw (new IllegalArgumentException("Attempted to add a join without a name.")); + } + if(this.joins.containsKey(name)) + { + throw (new IllegalArgumentException("Attempted to add a second join with name: " + name)); + } + this.joins.put(name, join); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QJoinMetaData getJoin(String name) + { + if(this.joins == null) + { + return (null); + } + + return (this.joins.get(name)); + } + + + + /******************************************************************************* + ** Getter for joins + ** + *******************************************************************************/ + public Map getJoins() + { + return joins; + } + + + + /******************************************************************************* + ** Setter for joins + ** + *******************************************************************************/ + public void setJoins(Map joins) + { + this.joins = joins; + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java index b52ed802..e0df08c6 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/frontend/QFrontendTableMetaData.java @@ -35,7 +35,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; 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.utils.CollectionUtils; /******************************************************************************* @@ -92,11 +91,6 @@ public class QFrontendTableMetaData this.iconName = tableMetaData.getIcon().getName(); } - if(CollectionUtils.nullSafeHasContents(tableMetaData.getWidgets())) - { - this.widgets = tableMetaData.getWidgets(); - } - setCapabilities(backendForTable, tableMetaData); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/JoinOn.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/JoinOn.java new file mode 100644 index 00000000..13f5ee76 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/JoinOn.java @@ -0,0 +1,123 @@ +/* + * 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.joins; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class JoinOn +{ + private String leftField; + private String rightField; + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public JoinOn() + { + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public JoinOn(String leftField, String rightField) + { + this.leftField = leftField; + this.rightField = rightField; + } + + + + /******************************************************************************* + ** Getter for leftField + ** + *******************************************************************************/ + public String getLeftField() + { + return leftField; + } + + + + /******************************************************************************* + ** Setter for leftField + ** + *******************************************************************************/ + public void setLeftField(String leftField) + { + this.leftField = leftField; + } + + + + /******************************************************************************* + ** Fluent setter for leftField + ** + *******************************************************************************/ + public JoinOn withLeftField(String leftField) + { + this.leftField = leftField; + return (this); + } + + + + /******************************************************************************* + ** Getter for rightField + ** + *******************************************************************************/ + public String getRightField() + { + return rightField; + } + + + + /******************************************************************************* + ** Setter for rightField + ** + *******************************************************************************/ + public void setRightField(String rightField) + { + this.rightField = rightField; + } + + + + /******************************************************************************* + ** Fluent setter for rightField + ** + *******************************************************************************/ + public JoinOn withRightField(String rightField) + { + this.rightField = rightField; + return (this); + } + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/JoinType.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/JoinType.java new file mode 100644 index 00000000..1e575b08 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/JoinType.java @@ -0,0 +1,41 @@ +/* + * 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.joins; + + +/******************************************************************************* + ** Type for a QJoin. + ** + ** - One to One - what about zero?? + ** - One to Many - e.g., where the parent record really "owns" all of the child + ** records. Like Order -> OrderLine. + ** - Many to One - e.g., where a child references a parent, but we'd never really + ** view or manage all of the children under the parent. + ** - Many to Many - e.g., through an intersection table... ? Needs more thought. + *******************************************************************************/ +public enum JoinType +{ + ONE_TO_ONE, + ONE_TO_MANY, + MANY_TO_ONE, + MANY_TO_MANY +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java new file mode 100644 index 00000000..68b0b1ee --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java @@ -0,0 +1,293 @@ +/* + * 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.joins; + + +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; +import com.kingsrook.qqq.backend.core.utils.StringUtils; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class QJoinMetaData +{ + private String name; + private JoinType type; + private String leftTable; + private String rightTable; + + private List joinOns; + private List orderBys; + + + + /******************************************************************************* + ** Getter for name + ** + *******************************************************************************/ + public String getName() + { + return name; + } + + + + /******************************************************************************* + ** Setter for name + ** + *******************************************************************************/ + public void setName(String name) + { + this.name = name; + } + + + + /******************************************************************************* + ** Fluent setter for name + ** + *******************************************************************************/ + public QJoinMetaData withName(String name) + { + this.name = name; + return (this); + } + + + + /******************************************************************************* + ** Getter for type + ** + *******************************************************************************/ + public JoinType getType() + { + return type; + } + + + + /******************************************************************************* + ** Setter for type + ** + *******************************************************************************/ + public void setType(JoinType type) + { + this.type = type; + } + + + + /******************************************************************************* + ** Fluent setter for type + ** + *******************************************************************************/ + public QJoinMetaData withType(JoinType type) + { + this.type = type; + return (this); + } + + + + /******************************************************************************* + ** Getter for leftTable + ** + *******************************************************************************/ + public String getLeftTable() + { + return leftTable; + } + + + + /******************************************************************************* + ** Setter for leftTable + ** + *******************************************************************************/ + public void setLeftTable(String leftTable) + { + this.leftTable = leftTable; + } + + + + /******************************************************************************* + ** Fluent setter for leftTable + ** + *******************************************************************************/ + public QJoinMetaData withLeftTable(String leftTable) + { + this.leftTable = leftTable; + return (this); + } + + + + /******************************************************************************* + ** Getter for rightTable + ** + *******************************************************************************/ + public String getRightTable() + { + return rightTable; + } + + + + /******************************************************************************* + ** Setter for rightTable + ** + *******************************************************************************/ + public void setRightTable(String rightTable) + { + this.rightTable = rightTable; + } + + + + /******************************************************************************* + ** Fluent setter for rightTable + ** + *******************************************************************************/ + public QJoinMetaData withRightTable(String rightTable) + { + this.rightTable = rightTable; + return (this); + } + + + + /******************************************************************************* + ** Getter for joinOns + ** + *******************************************************************************/ + public List getJoinOns() + { + return joinOns; + } + + + + /******************************************************************************* + ** Setter for joinOns + ** + *******************************************************************************/ + public void setJoinOns(List joinOns) + { + this.joinOns = joinOns; + } + + + + /******************************************************************************* + ** Fluent setter for joinOns + ** + *******************************************************************************/ + public QJoinMetaData withJoinOns(List joinOns) + { + this.joinOns = joinOns; + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for joinOns + ** + *******************************************************************************/ + public QJoinMetaData withJoinOn(JoinOn joinOn) + { + if(this.joinOns == null) + { + this.joinOns = new ArrayList<>(); + } + this.joinOns.add(joinOn); + return (this); + } + + + + /******************************************************************************* + ** Getter for orderBys + ** + *******************************************************************************/ + public List getOrderBys() + { + return orderBys; + } + + + + /******************************************************************************* + ** Setter for orderBys + ** + *******************************************************************************/ + public void setOrderBys(List orderBys) + { + this.orderBys = orderBys; + } + + + + /******************************************************************************* + ** Fluent setter for orderBys + ** + *******************************************************************************/ + public QJoinMetaData withOrderBys(List orderBys) + { + this.orderBys = orderBys; + return (this); + } + + + + /******************************************************************************* + ** Fluent setter for orderBys + ** + *******************************************************************************/ + public QJoinMetaData withOrderBy(QFilterOrderBy orderBy) + { + if(this.orderBys == null) + { + this.orderBys = new ArrayList<>(); + } + this.orderBys.add(orderBy); + return (this); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QJoinMetaData withInferredName() + { + if(!StringUtils.hasContent(getLeftTable()) || !StringUtils.hasContent(getRightTable())) + { + throw (new IllegalStateException("Missing either a left or right table name when trying to set inferred name for join")); + } + return (withName(getLeftTable() + "Join" + getRightTable())); + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java index f62140f4..caeff6e4 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QFieldSection.java @@ -28,6 +28,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon; /******************************************************************************* ** A section of fields - a logical grouping. + ** TODO - this class should be named QTableSection! *******************************************************************************/ public class QFieldSection { @@ -36,6 +37,7 @@ public class QFieldSection private Tier tier; private List fieldNames; + private String widgetName; private QIcon icon; private boolean isHidden = false; @@ -78,6 +80,18 @@ public class QFieldSection + /******************************************************************************* + ** + *******************************************************************************/ + public QFieldSection(String name, QIcon icon, Tier tier) + { + this.name = name; + this.icon = icon; + this.tier = tier; + } + + + /******************************************************************************* ** Getter for name ** @@ -280,4 +294,38 @@ public class QFieldSection return (this); } + + + /******************************************************************************* + ** Getter for widgetName + ** + *******************************************************************************/ + public String getWidgetName() + { + return widgetName; + } + + + + /******************************************************************************* + ** Setter for widgetName + ** + *******************************************************************************/ + public void setWidgetName(String widgetName) + { + this.widgetName = widgetName; + } + + + + /******************************************************************************* + ** Fluent setter for widgetName + ** + *******************************************************************************/ + public QFieldSection withWidgetName(String widgetName) + { + this.widgetName = widgetName; + return (this); + } + } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java index c7f20b48..61a007cf 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java @@ -80,7 +80,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable private List sections; - private List widgets; private List associatedScripts; private Set enabledCapabilities = new HashSet<>(); @@ -726,40 +725,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable - /******************************************************************************* - ** Getter for widgets - ** - *******************************************************************************/ - public List getWidgets() - { - return widgets; - } - - - - /******************************************************************************* - ** Setter for widgets - ** - *******************************************************************************/ - public void setWidgets(List widgets) - { - this.widgets = widgets; - } - - - - /******************************************************************************* - ** Fluent setter for widgets - ** - *******************************************************************************/ - public QTableMetaData withWidgets(List widgets) - { - this.widgets = widgets; - return (this); - } - - - /******************************************************************************* ** Getter for associatedScripts ** diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java index 153699a2..bc3702dd 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/memory/MemoryRecordStore.java @@ -128,6 +128,8 @@ public class MemoryRecordStore } } + BackendQueryFilterUtils.sortRecordList(input.getFilter(), records); + return (records); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java index b22f1f00..92acf60b 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/TestUtils.java @@ -44,6 +44,7 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutp import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; @@ -62,6 +63,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; @@ -116,8 +120,10 @@ public class TestUtils public static final String APP_NAME_PEOPLE = "peopleApp"; public static final String APP_NAME_MISCELLANEOUS = "miscellaneous"; - public static final String TABLE_NAME_PERSON = "person"; - public static final String TABLE_NAME_SHAPE = "shape"; + public static final String TABLE_NAME_PERSON = "person"; + public static final String TABLE_NAME_SHAPE = "shape"; + public static final String TABLE_NAME_ORDER = "order"; + public static final String TABLE_NAME_LINE_ITEM = "orderLine"; public static final String PROCESS_NAME_GREET_PEOPLE = "greet"; public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive"; @@ -161,6 +167,10 @@ public class TestUtils qInstance.addTable(defineTableIdAndNameOnly()); qInstance.addTable(defineTableShape()); qInstance.addTable(defineTableBasepull()); + qInstance.addTable(defineTableOrder()); + qInstance.addTable(defineTableLineItem()); + + qInstance.addJoin(defineJoinOrderLineItem()); qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource()); qInstance.addPossibleValueSource(defineStatesPossibleValueSource()); @@ -444,6 +454,60 @@ public class TestUtils + /******************************************************************************* + ** Define the order table used in standard tests. + *******************************************************************************/ + public static QTableMetaData defineTableOrder() + { + return new QTableMetaData() + .withName(TABLE_NAME_ORDER) + .withBackendName(MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) + .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false)) + .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false)) + .withField(new QFieldMetaData("orderDate", QFieldType.DATE)) + .withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY)); + } + + + + /******************************************************************************* + ** Define the lineItem table used in standard tests. + *******************************************************************************/ + public static QTableMetaData defineTableLineItem() + { + return new QTableMetaData() + .withName(TABLE_NAME_LINE_ITEM) + .withBackendName(MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false)) + .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false)) + .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false)) + .withField(new QFieldMetaData("orderId", QFieldType.INTEGER)) + .withField(new QFieldMetaData("lineNumber", QFieldType.STRING)) + .withField(new QFieldMetaData("sku", QFieldType.STRING)) + .withField(new QFieldMetaData("quantity", QFieldType.INTEGER)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QJoinMetaData defineJoinOrderLineItem() + { + return new QJoinMetaData() + .withName("orderLineItem") + .withType(JoinType.ONE_TO_MANY) + .withLeftTable(TABLE_NAME_ORDER) + .withRightTable(TABLE_NAME_LINE_ITEM) + .withJoinOn(new JoinOn("id", "orderId")) + .withOrderBy(new QFilterOrderBy("lineNumber")); + } + + + /******************************************************************************* ** *******************************************************************************/