Add joins and ChildRecordList widget

This commit is contained in:
2022-11-14 11:39:47 -06:00
parent f0a464ce9e
commit 2a7e76b0f9
14 changed files with 1083 additions and 48 deletions

View File

@ -22,11 +22,15 @@
package com.kingsrook.qqq.backend.core.actions.dashboard; 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.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.exceptions.QException; 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.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput; 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); ActionHelper.validateSession(input);
AbstractWidgetRenderer widgetRenderer = QCodeLoader.getAdHoc(AbstractWidgetRenderer.class, input.getWidgetMetaData().getCodeReference()); 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<String, Serializable> entry : widgetMetaData.getDefaultValues().entrySet())
{
input.getQueryParams().putIfAbsent(entry.getKey(), ValueUtils.getValueAsString(entry.getValue()));
}
}
return (widgetRenderer.render(input)); return (widgetRenderer.render(input));
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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)));
}
}

View File

@ -285,7 +285,7 @@ public class QInstanceValidator
{ {
for(QFieldSection section : table.getSections()) for(QFieldSection section : table.getSections())
{ {
validateFieldSection(table, section, fieldNamesInSections); validateTableSection(qInstance, table, section, fieldNamesInSections);
if(section.getTier().equals(Tier.T1)) if(section.getTier().equals(Tier.T1))
{ {
assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1"); assertCondition(tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
@ -658,13 +658,17 @@ public class QInstanceValidator
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private void validateFieldSection(QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections) private void validateTableSection(QInstance qInstance, QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections)
{ {
assertCondition(StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + "."); assertCondition(StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + ".");
assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + "."); assertCondition(StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + ".");
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()) for(String fieldName : section.getFieldNames())
{ {
@ -674,6 +678,10 @@ public class QInstanceValidator
fieldNamesInSections.add(fieldName); 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.");
}
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -35,7 +35,8 @@ public enum WidgetType
QUICK_SIGHT_CHART("quickSightChart"), QUICK_SIGHT_CHART("quickSightChart"),
STATISTICS("statistics"), STATISTICS("statistics"),
STEPPER("stepper"), STEPPER("stepper"),
TABLE("table"); TABLE("table"),
CHILD_RECORD_LIST("childRecordList");
private final String type; private final String type;

View File

@ -28,10 +28,18 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore; 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.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.automation.QAutomationProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData; 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.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.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; 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. // // Important to use LinkedHashmap here, to preserve the order in which entries are added. //
//////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////
private Map<String, QTableMetaData> tables = new LinkedHashMap<>(); private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
private Map<String, QJoinMetaData> joins = new LinkedHashMap<>();
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>(); private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>(); private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
private Map<String, QAppMetaData> apps = new LinkedHashMap<>(); private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
@ -79,6 +88,9 @@ public class QInstance
@JsonIgnore @JsonIgnore
private boolean hasBeenValidated = false; private boolean hasBeenValidated = false;
private Map<String, String> memoizedTablePaths = new HashMap<>();
private Map<String, String> 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<AppTreeNode> 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<String, QJoinMetaData> getJoins()
{
return joins;
}
/*******************************************************************************
** Setter for joins
**
*******************************************************************************/
public void setJoins(Map<String, QJoinMetaData> joins)
{
this.joins = joins;
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -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.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection; import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/******************************************************************************* /*******************************************************************************
@ -92,11 +91,6 @@ public class QFrontendTableMetaData
this.iconName = tableMetaData.getIcon().getName(); this.iconName = tableMetaData.getIcon().getName();
} }
if(CollectionUtils.nullSafeHasContents(tableMetaData.getWidgets()))
{
this.widgets = tableMetaData.getWidgets();
}
setCapabilities(backendForTable, tableMetaData); setCapabilities(backendForTable, tableMetaData);
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<JoinOn> joinOns;
private List<QFilterOrderBy> 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<JoinOn> getJoinOns()
{
return joinOns;
}
/*******************************************************************************
** Setter for joinOns
**
*******************************************************************************/
public void setJoinOns(List<JoinOn> joinOns)
{
this.joinOns = joinOns;
}
/*******************************************************************************
** Fluent setter for joinOns
**
*******************************************************************************/
public QJoinMetaData withJoinOns(List<JoinOn> 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<QFilterOrderBy> getOrderBys()
{
return orderBys;
}
/*******************************************************************************
** Setter for orderBys
**
*******************************************************************************/
public void setOrderBys(List<QFilterOrderBy> orderBys)
{
this.orderBys = orderBys;
}
/*******************************************************************************
** Fluent setter for orderBys
**
*******************************************************************************/
public QJoinMetaData withOrderBys(List<QFilterOrderBy> 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()));
}
}

View File

@ -28,6 +28,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/******************************************************************************* /*******************************************************************************
** A section of fields - a logical grouping. ** A section of fields - a logical grouping.
** TODO - this class should be named QTableSection!
*******************************************************************************/ *******************************************************************************/
public class QFieldSection public class QFieldSection
{ {
@ -36,6 +37,7 @@ public class QFieldSection
private Tier tier; private Tier tier;
private List<String> fieldNames; private List<String> fieldNames;
private String widgetName;
private QIcon icon; private QIcon icon;
private boolean isHidden = false; 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 ** Getter for name
** **
@ -280,4 +294,38 @@ public class QFieldSection
return (this); 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);
}
} }

View File

@ -80,7 +80,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
private List<QFieldSection> sections; private List<QFieldSection> sections;
private List<String> widgets;
private List<AssociatedScript> associatedScripts; private List<AssociatedScript> associatedScripts;
private Set<Capability> enabledCapabilities = new HashSet<>(); private Set<Capability> enabledCapabilities = new HashSet<>();
@ -726,40 +725,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
/*******************************************************************************
** Getter for widgets
**
*******************************************************************************/
public List<String> getWidgets()
{
return widgets;
}
/*******************************************************************************
** Setter for widgets
**
*******************************************************************************/
public void setWidgets(List<String> widgets)
{
this.widgets = widgets;
}
/*******************************************************************************
** Fluent setter for widgets
**
*******************************************************************************/
public QTableMetaData withWidgets(List<String> widgets)
{
this.widgets = widgets;
return (this);
}
/******************************************************************************* /*******************************************************************************
** Getter for associatedScripts ** Getter for associatedScripts
** **

View File

@ -128,6 +128,8 @@ public class MemoryRecordStore
} }
} }
BackendQueryFilterUtils.sortRecordList(input.getFilter(), records);
return (records); return (records);
} }

View File

@ -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.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; 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.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.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; 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.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.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.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.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
@ -118,6 +122,8 @@ public class TestUtils
public static final String TABLE_NAME_PERSON = "person"; public static final String TABLE_NAME_PERSON = "person";
public static final String TABLE_NAME_SHAPE = "shape"; 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 = "greet";
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive"; public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
@ -161,6 +167,10 @@ public class TestUtils
qInstance.addTable(defineTableIdAndNameOnly()); qInstance.addTable(defineTableIdAndNameOnly());
qInstance.addTable(defineTableShape()); qInstance.addTable(defineTableShape());
qInstance.addTable(defineTableBasepull()); qInstance.addTable(defineTableBasepull());
qInstance.addTable(defineTableOrder());
qInstance.addTable(defineTableLineItem());
qInstance.addJoin(defineJoinOrderLineItem());
qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource()); qInstance.addPossibleValueSource(defineAutomationStatusPossibleValueSource());
qInstance.addPossibleValueSource(defineStatesPossibleValueSource()); 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"));
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/