Merge branch 'feature/ONE-68-frontend-fixes-spike' into feature/sprint-8

This commit is contained in:
2022-08-04 18:39:16 -05:00
27 changed files with 1591 additions and 183 deletions

View File

@ -22,14 +22,19 @@
package com.kingsrook.qqq.backend.core.actions.metadata;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.frontend.QFrontendAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -50,22 +55,87 @@ public class MetaDataAction
// todo pre-customization - just get to modify the request?
MetaDataOutput metaDataOutput = new MetaDataOutput();
Map<String, QFrontendAppMetaData> treeNodes = new LinkedHashMap<>();
/////////////////////////////////////
// map tables to frontend metadata //
/////////////////////////////////////
Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>();
for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet())
{
tables.put(entry.getKey(), new QFrontendTableMetaData(entry.getValue(), false));
treeNodes.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
}
metaDataOutput.setTables(tables);
////////////////////////////////////////
// map processes to frontend metadata //
////////////////////////////////////////
Map<String, QFrontendProcessMetaData> processes = new LinkedHashMap<>();
for(Map.Entry<String, QProcessMetaData> entry : metaDataInput.getInstance().getProcesses().entrySet())
{
processes.put(entry.getKey(), new QFrontendProcessMetaData(entry.getValue(), false));
treeNodes.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
}
metaDataOutput.setProcesses(processes);
// todo post-customization - can do whatever w/ the result if you want
///////////////////////////////////
// map apps to frontend metadata //
///////////////////////////////////
Map<String, QFrontendAppMetaData> apps = new LinkedHashMap<>();
for(Map.Entry<String, QAppMetaData> entry : metaDataInput.getInstance().getApps().entrySet())
{
apps.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
treeNodes.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
for(QAppChildMetaData child : entry.getValue().getChildren())
{
apps.get(entry.getKey()).addChild(new QFrontendAppMetaData(child));
}
}
metaDataOutput.setApps(apps);
////////////////////////////////////////////////
// organize app tree nodes by their hierarchy //
////////////////////////////////////////////////
List<QFrontendAppMetaData> appTree = new ArrayList<>();
for(QAppMetaData appMetaData : metaDataInput.getInstance().getApps().values())
{
if(appMetaData.getParentAppName() == null)
{
buildAppTree(treeNodes, appTree, appMetaData);
}
}
metaDataOutput.setAppTree(appTree);
// todo post-customization - can do whatever w/ the result if you want?
return metaDataOutput;
}
/*******************************************************************************
**
*******************************************************************************/
private void buildAppTree(Map<String, QFrontendAppMetaData> treeNodes, List<QFrontendAppMetaData> nodeList, QAppChildMetaData childMetaData)
{
QFrontendAppMetaData treeNode = treeNodes.get(childMetaData.getName());
if(treeNode == null)
{
return;
}
nodeList.add(treeNode);
if(childMetaData instanceof QAppMetaData app)
{
if(app.getChildren() != null)
{
for(QAppChildMetaData child : app.getChildren())
{
buildAppTree(treeNodes, treeNode.getChildren(), child);
}
}
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.values;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
** Utility to apply display formats to values for fields
*******************************************************************************/
public class QValueFormatter
{
private static final Logger LOG = LogManager.getLogger(QValueFormatter.class);
/*******************************************************************************
**
*******************************************************************************/
public static String formatValue(QFieldMetaData field, Serializable value)
{
//////////////////////////////////
// null values get null results //
//////////////////////////////////
if(value == null)
{
return (null);
}
////////////////////////////////////////////////////////
// if the field has a display format, try to apply it //
////////////////////////////////////////////////////////
if(StringUtils.hasContent(field.getDisplayFormat()))
{
try
{
return (field.getDisplayFormat().formatted(value));
}
catch(Exception e)
{
LOG.warn("Error formatting value [" + value + "] for field [" + field.getName() + "] with format [" + field.getDisplayFormat() + "]: " + e.getMessage());
}
}
////////////////////////////////////////
// by default, just get back a string //
////////////////////////////////////////
return (ValueUtils.getValueAsString(value));
}
}

View File

@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
@ -84,6 +85,11 @@ public class QInstanceEnricher
{
qInstance.getBackends().values().forEach(this::enrich);
}
if(qInstance.getApps() != null)
{
qInstance.getApps().values().forEach(this::enrich);
}
}
@ -179,6 +185,19 @@ public class QInstanceEnricher
/*******************************************************************************
**
*******************************************************************************/
private void enrich(QAppMetaData app)
{
if(!StringUtils.hasContent(app.getLabel()))
{
app.setLabel(nameToLabel(app.getName()));
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -23,10 +23,15 @@ package com.kingsrook.qqq.backend.core.instances;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -76,60 +81,10 @@ public class QInstanceValidator
List<String> errors = new ArrayList<>();
try
{
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getBackends()),
"At least 1 backend must be defined."))
{
qInstance.getBackends().forEach((backendName, backend) ->
{
assertCondition(errors, Objects.equals(backendName, backend.getName()),
"Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
});
}
/////////////////////////
// validate the tables //
/////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getTables()),
"At least 1 table must be defined."))
{
qInstance.getTables().forEach((tableName, table) ->
{
assertCondition(errors, Objects.equals(tableName, table.getName()),
"Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
////////////////////////////////////////
// validate the backend for the table //
////////////////////////////////////////
if(assertCondition(errors, StringUtils.hasContent(table.getBackendName()),
"Missing backend name for table " + tableName + "."))
{
if(CollectionUtils.nullSafeHasContents(qInstance.getBackends()))
{
assertCondition(errors, qInstance.getBackendForTable(tableName) != null,
"Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
}
}
//////////////////////////////////
// validate fields in the table //
//////////////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(table.getFields()),
"At least 1 field must be defined in table " + tableName + "."))
{
table.getFields().forEach((fieldName, field) ->
{
assertCondition(errors, Objects.equals(fieldName, field.getName()),
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
if(field.getPossibleValueSourceName() != null)
{
assertCondition(errors, qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
});
}
});
}
validateBackends(qInstance, errors);
validateTables(qInstance, errors);
validateProcesses(qInstance, errors);
validateApps(qInstance, errors);
}
catch(Exception e)
{
@ -149,6 +104,181 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void validateBackends(QInstance qInstance, List<String> errors)
{
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getBackends()), "At least 1 backend must be defined."))
{
qInstance.getBackends().forEach((backendName, backend) ->
{
assertCondition(errors, Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
});
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateTables(QInstance qInstance, List<String> errors)
{
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(qInstance.getTables()),
"At least 1 table must be defined."))
{
qInstance.getTables().forEach((tableName, table) ->
{
assertCondition(errors, Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, errors, table);
////////////////////////////////////////
// validate the backend for the table //
////////////////////////////////////////
if(assertCondition(errors, StringUtils.hasContent(table.getBackendName()),
"Missing backend name for table " + tableName + "."))
{
if(CollectionUtils.nullSafeHasContents(qInstance.getBackends()))
{
assertCondition(errors, qInstance.getBackendForTable(tableName) != null, "Unrecognized backend " + table.getBackendName() + " for table " + tableName + ".");
}
}
//////////////////////////////////
// validate fields in the table //
//////////////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(table.getFields()), "At least 1 field must be defined in table " + tableName + "."))
{
table.getFields().forEach((fieldName, field) ->
{
assertCondition(errors, Objects.equals(fieldName, field.getName()),
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
if(field.getPossibleValueSourceName() != null)
{
assertCondition(errors, qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
});
}
});
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateProcesses(QInstance qInstance, List<String> errors)
{
if(!CollectionUtils.nullSafeIsEmpty(qInstance.getProcesses()))
{
qInstance.getProcesses().forEach((processName, process) ->
{
assertCondition(errors, Objects.equals(processName, process.getName()), "Inconsistent naming for process: " + processName + "/" + process.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, errors, process);
/////////////////////////////////////////////
// validate the table name for the process //
/////////////////////////////////////////////
if(process.getTableName() != null)
{
assertCondition(errors, qInstance.getTable(process.getTableName()) != null, "Unrecognized table " + process.getTableName() + " for process " + processName + ".");
}
///////////////////////////////////
// validate steps in the process //
///////////////////////////////////
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(process.getStepList()), "At least 1 step must be defined in process " + processName + "."))
{
int index = 0;
for(QStepMetaData step : process.getStepList())
{
assertCondition(errors, StringUtils.hasContent(step.getName()), "Missing name for a step at index " + index + " in process " + processName);
index++;
}
}
});
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateApps(QInstance qInstance, List<String> errors)
{
if(!CollectionUtils.nullSafeIsEmpty(qInstance.getApps()))
{
qInstance.getApps().forEach((appName, app) ->
{
assertCondition(errors, Objects.equals(appName, app.getName()), "Inconsistent naming for app: " + appName + "/" + app.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, errors, app);
Set<String> appsVisited = new HashSet<>();
visitAppCheckingForCycles(app, appsVisited, errors);
if(app.getChildren() != null)
{
Set<String> childNames = new HashSet<>();
for(QAppChildMetaData child : app.getChildren())
{
assertCondition(errors, Objects.equals(appName, child.getParentAppName()), "Child " + child.getName() + " of app " + appName + " does not have its parent app properly set.");
assertCondition(errors, !childNames.contains(child.getName()), "App " + appName + " contains more than one child named " + child.getName());
childNames.add(child.getName());
}
}
});
}
}
/*******************************************************************************
** Check if an app's child list can recursively be traversed without finding a
** duplicate, which would indicate a cycle (e.g., an error)
*******************************************************************************/
private void visitAppCheckingForCycles(QAppMetaData app, Set<String> appsVisited, List<String> errors)
{
if(assertCondition(errors, !appsVisited.contains(app.getName()), "Circular app reference detected, involving " + app.getName()))
{
appsVisited.add(app.getName());
if(app.getChildren() != null)
{
for(QAppChildMetaData child : app.getChildren())
{
if(child instanceof QAppMetaData childApp)
{
visitAppCheckingForCycles(childApp, appsVisited, errors);
}
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateAppChildHasValidParentAppName(QInstance qInstance, List<String> errors, QAppChildMetaData appChild)
{
if(appChild.getParentAppName() != null)
{
assertCondition(errors, qInstance.getApp(appChild.getParentAppName()) != null, "Unrecognized parent app " + appChild.getParentAppName() + " for " + appChild.getName() + ".");
}
}
/*******************************************************************************
** For the given input condition, if it's true, then we're all good (and return true).
** But if it's false, add the provided message to the list of errors (and return false,
** e.g., in case you need to stop evaluating rules to avoid exceptions).
*******************************************************************************/
private boolean assertCondition(List<String> errors, boolean condition, String message)
{
if(!condition)

View File

@ -87,7 +87,7 @@ public abstract class AbstractActionInput
catch(QInstanceValidationException e)
{
LOG.warn(e);
throw (new IllegalArgumentException("QInstance failed validation" + e.getMessage()));
throw (new IllegalArgumentException("QInstance failed validation" + e.getMessage(), e));
}
}
}

View File

@ -22,8 +22,10 @@
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
@ -36,6 +38,9 @@ public class MetaDataOutput extends AbstractActionOutput
{
private Map<String, QFrontendTableMetaData> tables;
private Map<String, QFrontendProcessMetaData> processes;
private Map<String, QFrontendAppMetaData> apps;
private List<QFrontendAppMetaData> appTree;
@ -80,4 +85,49 @@ public class MetaDataOutput extends AbstractActionOutput
{
this.processes = processes;
}
/*******************************************************************************
** Getter for appTree
**
*******************************************************************************/
public List<QFrontendAppMetaData> getAppTree()
{
return appTree;
}
/*******************************************************************************
** Setter for appTree
**
*******************************************************************************/
public void setAppTree(List<QFrontendAppMetaData> appTree)
{
this.appTree = appTree;
}
/*******************************************************************************
** Getter for apps
**
*******************************************************************************/
public Map<String, QFrontendAppMetaData> getApps()
{
return apps;
}
/*******************************************************************************
** Setter for apps
**
*******************************************************************************/
public void setApps(Map<String, QFrontendAppMetaData> apps)
{
this.apps = apps;
}
}

View File

@ -29,7 +29,9 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -69,6 +71,7 @@ public class QRecord implements Serializable
}
/*******************************************************************************
**
*******************************************************************************/
@ -105,6 +108,16 @@ public class QRecord implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public void setValue(QFieldMetaData field, Serializable value)
{
values.put(field.getName(), value);
}
/*******************************************************************************
**
*******************************************************************************/
@ -126,6 +139,16 @@ public class QRecord implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public void setDisplayValue(QFieldMetaData field, Serializable rawValue)
{
displayValues.put(field.getName(), QValueFormatter.formatValue(field, rawValue));
}
/*******************************************************************************
**
*******************************************************************************/
@ -137,6 +160,17 @@ public class QRecord implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public QRecord withDisplayValue(QFieldMetaData field, Serializable rawValue)
{
setDisplayValue(field, rawValue);
return (this);
}
/*******************************************************************************
** Getter for tableName
**
@ -355,6 +389,7 @@ public class QRecord implements Serializable
}
/*******************************************************************************
** Getter for errors
**
@ -399,6 +434,7 @@ public class QRecord implements Serializable
}
/*******************************************************************************
** Convert this record to an QRecordEntity
*******************************************************************************/

View File

@ -24,10 +24,12 @@ package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
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.QStepMetaData;
@ -49,9 +51,13 @@ public class QInstance
private QAuthenticationMetaData authentication = null;
private Map<String, QTableMetaData> tables = new HashMap<>();
private Map<String, QPossibleValueSource<?>> possibleValueSources = new HashMap<>();
private Map<String, QProcessMetaData> processes = new HashMap<>();
////////////////////////////////////////////////////////////////////////////////////////////
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
////////////////////////////////////////////////////////////////////////////////////////////
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
private Map<String, QPossibleValueSource<?>> possibleValueSources = new LinkedHashMap<>();
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
// todo - lock down the object (no more changes allowed) after it's been validated?
@ -171,6 +177,11 @@ public class QInstance
*******************************************************************************/
public QTableMetaData getTable(String name)
{
if(this.tables == null)
{
return (null);
}
return (this.tables.get(name));
}
@ -260,6 +271,40 @@ public class QInstance
/*******************************************************************************
**
*******************************************************************************/
public void addApp(QAppMetaData app)
{
this.addApp(app.getName(), app);
}
/*******************************************************************************
**
*******************************************************************************/
public void addApp(String name, QAppMetaData app)
{
if(this.apps.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second app with name: " + name));
}
this.apps.put(name, app);
}
/*******************************************************************************
**
*******************************************************************************/
public QAppMetaData getApp(String name)
{
return (this.apps.get(name));
}
/*******************************************************************************
** Getter for backends
**
@ -348,6 +393,28 @@ public class QInstance
/*******************************************************************************
** Getter for apps
**
*******************************************************************************/
public Map<String, QAppMetaData> getApps()
{
return apps;
}
/*******************************************************************************
** Setter for apps
**
*******************************************************************************/
public void setApps(Map<String, QAppMetaData> apps)
{
this.apps = apps;
}
/*******************************************************************************
** Getter for hasBeenValidated
**

View File

@ -0,0 +1,40 @@
/*
* 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.fields;
/*******************************************************************************
**
*******************************************************************************/
public interface DisplayFormat
{
String DEFAULT = "%s";
String STRING = "%s";
String COMMAS = "%,d";
String DECIMAL1_COMMAS = "%,.1f";
String DECIMAL2_COMMAS = "%,.2f";
String DECIMAL3_COMMAS = "%,.3f";
String DECIMAL1 = "%.1f";
String DECIMAL2 = "%.2f";
String DECIMAL3 = "%.3f";
String CURRENCY = "$%,.2f";
}

View File

@ -51,6 +51,7 @@ public class QFieldMetaData
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
///////////////////////////////////////////////////////////////////////////////////
private String displayFormat = "%s";
private Serializable defaultValue;
private String possibleValueSourceName;
@ -395,4 +396,36 @@ public class QFieldMetaData
return (this);
}
/*******************************************************************************
** Getter for displayFormat
**
*******************************************************************************/
public String getDisplayFormat()
{
return displayFormat;
}
/*******************************************************************************
** Setter for displayFormat
**
*******************************************************************************/
public void setDisplayFormat(String displayFormat)
{
this.displayFormat = displayFormat;
}
/*******************************************************************************
** Fluent setter for displayFormat
**
*******************************************************************************/
public QFieldMetaData withDisplayFormat(String displayFormat)
{
this.displayFormat = displayFormat;
return (this);
}
}

View File

@ -65,6 +65,10 @@ public enum QFieldType
{
return (INTEGER);
}
if(c.equals(Boolean.class))
{
return (BOOLEAN);
}
if(c.equals(BigDecimal.class))
{
return (DECIMAL);

View File

@ -0,0 +1,33 @@
/*
* 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.frontend;
/*******************************************************************************
**
*******************************************************************************/
public enum AppTreeNodeType
{
TABLE,
PROCESS,
APP
}

View File

@ -0,0 +1,164 @@
/*
* 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.frontend;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
* Version of QAppMetaData that's meant for transmitting to a frontend.
*
*******************************************************************************/
@JsonInclude(Include.NON_NULL)
public class QFrontendAppMetaData
{
private AppTreeNodeType type;
private String name;
private String label;
private List<QFrontendAppMetaData> children = new ArrayList<>();
private String iconName;
/*******************************************************************************
**
*******************************************************************************/
public QFrontendAppMetaData(QAppChildMetaData appChildMetaData)
{
this.name = appChildMetaData.getName();
this.label = appChildMetaData.getLabel();
if(appChildMetaData.getClass().equals(QTableMetaData.class))
{
this.type = AppTreeNodeType.TABLE;
}
else if(appChildMetaData.getClass().equals(QProcessMetaData.class))
{
this.type = AppTreeNodeType.PROCESS;
}
else if(appChildMetaData.getClass().equals(QAppMetaData.class))
{
this.type = AppTreeNodeType.APP;
}
else
{
throw (new IllegalStateException("Unrecognized class for app child meta data: " + appChildMetaData.getClass()));
}
if(appChildMetaData.getIcon() != null && StringUtils.hasContent(appChildMetaData.getIcon().getName()))
{
this.iconName = appChildMetaData.getIcon().getName();
}
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Getter for type
**
*******************************************************************************/
public AppTreeNodeType getType()
{
return type;
}
/*******************************************************************************
** Getter for children
**
*******************************************************************************/
public List<QFrontendAppMetaData> getChildren()
{
return children;
}
/*******************************************************************************
** Getter for iconName
**
*******************************************************************************/
public String getIconName()
{
return iconName;
}
/*******************************************************************************
** Setter for iconName
**
*******************************************************************************/
public void setIconName(String iconName)
{
this.iconName = iconName;
}
/*******************************************************************************
**
*******************************************************************************/
public void addChild(QFrontendAppMetaData qFrontendAppMetaData)
{
if(children == null)
{
children = new ArrayList<>();
}
children.add(qFrontendAppMetaData);
}
}

View File

@ -0,0 +1,58 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.layout;
/*******************************************************************************
** Interface shared by meta-data objects which can be placed into an App.
*******************************************************************************/
public interface QAppChildMetaData
{
/*******************************************************************************
**
*******************************************************************************/
void setParentAppName(String parentAppName);
/*******************************************************************************
**
*******************************************************************************/
String getParentAppName();
/*******************************************************************************
**
*******************************************************************************/
String getName();
/*******************************************************************************
**
*******************************************************************************/
String getLabel();
/*******************************************************************************
**
*******************************************************************************/
default QIcon getIcon()
{
return (null);
}
}

View File

@ -0,0 +1,237 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList;
import java.util.List;
/*******************************************************************************
**
*******************************************************************************/
public class QAppMetaData implements QAppChildMetaData
{
private String name;
private String label;
private List<QAppChildMetaData> children;
private String parentAppName;
private QIcon icon;
/*******************************************************************************
**
*******************************************************************************/
public QAppMetaData()
{
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public QAppMetaData withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
** Setter for label
**
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label
**
*******************************************************************************/
public QAppMetaData withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for children
**
*******************************************************************************/
public List<QAppChildMetaData> getChildren()
{
return children;
}
/*******************************************************************************
** Setter for children
**
*******************************************************************************/
public void setChildren(List<QAppChildMetaData> children)
{
this.children = children;
}
/*******************************************************************************
** Add a child to this app.
**
*******************************************************************************/
public void addChild(QAppChildMetaData child)
{
if(this.children == null)
{
this.children = new ArrayList<>();
}
this.children.add(child);
child.setParentAppName(this.getName());
}
/*******************************************************************************
** Fluently add a child to this app.
**
*******************************************************************************/
public QAppMetaData withChild(QAppChildMetaData child)
{
addChild(child);
return (this);
}
/*******************************************************************************
** Fluent setter for children
**
*******************************************************************************/
public QAppMetaData withChildren(List<QAppChildMetaData> children)
{
this.children = children;
return (this);
}
/*******************************************************************************
** Getter for parentAppName
**
*******************************************************************************/
@Override
public String getParentAppName()
{
return parentAppName;
}
/*******************************************************************************
** Setter for parentAppName
**
*******************************************************************************/
@Override
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
/*******************************************************************************
** Setter for icon
**
*******************************************************************************/
public void setIcon(QIcon icon)
{
this.icon = icon;
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public QAppMetaData withIcon(QIcon icon)
{
this.icon = icon;
return (this);
}
}

View File

@ -0,0 +1,91 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.layout;
/*******************************************************************************
** Icon to show associated with an App, Table, Process, etc.
**
** Currently, name here must be a reference from https://fonts.google.com/icons
** e.g., local_shipping for https://fonts.google.com/icons?selected=Material+Symbols+Outlined:local_shipping
**
** Future may allow something like a "namespace", and/or multiple icons for
** use in different frontends, etc.
*******************************************************************************/
public class QIcon
{
private String name;
/*******************************************************************************
**
*******************************************************************************/
public QIcon()
{
}
/*******************************************************************************
**
*******************************************************************************/
public QIcon(String name)
{
this.name = name;
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public QIcon withName(String name)
{
this.name = name;
return (this);
}
}

View File

@ -26,13 +26,15 @@ import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
/*******************************************************************************
** Meta-Data to define a process in a QQQ instance.
**
*******************************************************************************/
public class QProcessMetaData
public class QProcessMetaData implements QAppChildMetaData
{
private String name;
private String label;
@ -41,6 +43,8 @@ public class QProcessMetaData
private List<QStepMetaData> stepList;
private String parentAppName;
/*******************************************************************************
@ -293,4 +297,28 @@ public class QProcessMetaData
return (this);
}
/*******************************************************************************
** Getter for parentAppName
**
*******************************************************************************/
@Override
public String getParentAppName()
{
return parentAppName;
}
/*******************************************************************************
** Setter for parentAppName
**
*******************************************************************************/
@Override
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
}

View File

@ -34,13 +34,15 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/*******************************************************************************
** Meta-Data to define a table in a QQQ instance.
**
*******************************************************************************/
public class QTableMetaData implements Serializable
public class QTableMetaData implements QAppChildMetaData, Serializable
{
private String name;
private String label;
@ -63,6 +65,8 @@ public class QTableMetaData implements Serializable
private Map<String, QCodeReference> customizers;
private String parentAppName;
private QIcon icon;
/*******************************************************************************
@ -465,4 +469,61 @@ public class QTableMetaData implements Serializable
return (this);
}
/*******************************************************************************
** Getter for parentAppName
**
*******************************************************************************/
@Override
public String getParentAppName()
{
return parentAppName;
}
/*******************************************************************************
** Setter for parentAppName
**
*******************************************************************************/
@Override
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
/*******************************************************************************
** Setter for icon
**
*******************************************************************************/
public void setIcon(QIcon icon)
{
this.icon = icon;
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public QTableMetaData withIcon(QIcon icon)
{
this.icon = icon;
return (this);
}
}

View File

@ -65,6 +65,7 @@ public class MockQueryAction implements QueryInterface
{
Serializable value = field.equals("id") ? (i + 1) : getValue(table, field);
record.setValue(field, value);
record.setDisplayValue(table.getField(field), value);
}
queryOutput.addRecord(record);