Add field sections, record labels, display values being populated

This commit is contained in:
2022-08-09 14:23:51 -05:00
parent 19afc0fc10
commit 3b8b45ecea
19 changed files with 1140 additions and 56 deletions

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
import com.kingsrook.qqq.backend.core.actions.ActionHelper; import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
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;
@ -48,6 +49,10 @@ public class QueryAction
// todo pre-customization - just get to modify the request? // todo pre-customization - just get to modify the request?
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput); QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
// todo post-customization - can do whatever w/ the result if you want // todo post-customization - can do whatever w/ the result if you want
QValueFormatter.setDisplayValuesInRecords(queryInput.getTable(), queryOutput.getRecords());
return queryOutput; return queryOutput;
} }
} }

View File

@ -23,7 +23,10 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -62,14 +65,114 @@ public class QValueFormatter
return (field.getDisplayFormat().formatted(value)); return (field.getDisplayFormat().formatted(value));
} }
catch(Exception e) catch(Exception e)
{
try
{
if(e.getMessage().equals("f != java.lang.Integer"))
{
return formatValue(field, ValueUtils.getValueAsBigDecimal(value));
}
else if(e.getMessage().equals("d != java.math.BigDecimal"))
{
return formatValue(field, ValueUtils.getValueAsInteger(value));
}
else
{ {
LOG.warn("Error formatting value [" + value + "] for field [" + field.getName() + "] with format [" + field.getDisplayFormat() + "]: " + e.getMessage()); LOG.warn("Error formatting value [" + value + "] for field [" + field.getName() + "] with format [" + field.getDisplayFormat() + "]: " + e.getMessage());
} }
} }
catch(Exception e2)
{
LOG.warn("Caught secondary exception trying to convert type on field [" + field.getName() + "] for formatting", e);
}
}
}
//////////////////////////////////////// ////////////////////////////////////////
// by default, just get back a string // // by default, just get back a string //
//////////////////////////////////////// ////////////////////////////////////////
return (ValueUtils.getValueAsString(value)); return (ValueUtils.getValueAsString(value));
} }
/*******************************************************************************
** Make a string from a table's recordLabelFormat and fields, for a given record.
*******************************************************************************/
public static String formatRecordLabel(QTableMetaData table, QRecord record)
{
if(!StringUtils.hasContent(table.getRecordLabelFormat()))
{
return (formatRecordLabelExceptionalCases(table, record));
}
///////////////////////////////////////////////////////////////////////
// get list of values, then pass them to the string formatter method //
///////////////////////////////////////////////////////////////////////
try
{
List<Serializable> values = table.getRecordLabelFields().stream()
.map(record::getValue)
.map(v -> v == null ? "" : v)
.toList();
return (table.getRecordLabelFormat().formatted(values.toArray()));
}
catch(Exception e)
{
return (formatRecordLabelExceptionalCases(table, record));
}
}
/*******************************************************************************
** Deal with non-happy-path cases for making a record label.
*******************************************************************************/
private static String formatRecordLabelExceptionalCases(QTableMetaData table, QRecord record)
{
///////////////////////////////////////////////////////////////////////////////////////
// if there's no record label format, then just return the primary key display value //
///////////////////////////////////////////////////////////////////////////////////////
String pkeyDisplayValue = record.getDisplayValue(table.getPrimaryKeyField());
if(StringUtils.hasContent(pkeyDisplayValue))
{
return (pkeyDisplayValue);
}
String pkeyRawValue = ValueUtils.getValueAsString(record.getValue(table.getPrimaryKeyField()));
if(StringUtils.hasContent(pkeyRawValue))
{
return (pkeyRawValue);
}
///////////////////////////////////////////////////////////////////////////////
// worst case scenario, return empty string, but never null from this method //
///////////////////////////////////////////////////////////////////////////////
return ("");
}
/*******************************************************************************
** For a list of records, set their recordLabels and display values
*******************************************************************************/
public static void setDisplayValuesInRecords(QTableMetaData table, List<QRecord> records)
{
if(records == null)
{
return;
}
for(QRecord record : records)
{
for(QFieldMetaData field : table.getFields().values())
{
String formattedValue = QValueFormatter.formatValue(field, record.getValue(field.getName()));
record.setDisplayValue(field.getName(), formattedValue);
}
record.setRecordLabel(QValueFormatter.formatRecordLabel(table, record));
}
}
} }

View File

@ -23,8 +23,10 @@ package com.kingsrook.qqq.backend.core.instances;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -32,6 +34,7 @@ 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.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; 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.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
@ -41,13 +44,16 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMe
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteStoreStep; import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteStoreStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditReceiveValuesStep; import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditReceiveValuesStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditStoreRecordsStep; import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditStoreRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep; import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep; import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep; import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -111,6 +117,11 @@ public class QInstanceEnricher
{ {
table.getFields().values().forEach(this::enrich); table.getFields().values().forEach(this::enrich);
} }
if(CollectionUtils.nullSafeIsEmpty(table.getSections()))
{
generateTableFieldSections(table);
}
} }
@ -196,13 +207,20 @@ public class QInstanceEnricher
*******************************************************************************/ *******************************************************************************/
private String nameToLabel(String name) private String nameToLabel(String name)
{ {
if(name == null) if(!StringUtils.hasContent(name))
{ {
return (null); return (name);
} }
if(name.length() == 1)
{
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
}
else
{
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1")); return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
} }
}
@ -422,4 +440,71 @@ public class QInstanceEnricher
))); )));
} }
/*******************************************************************************
** If a table didn't have any sections, generate "sensible defaults"
*******************************************************************************/
private void generateTableFieldSections(QTableMetaData table)
{
if(CollectionUtils.nullSafeIsEmpty(table.getFields()))
{
/////////////////////////////////////////////////////////////////////////////////////////////////////
// assume this table is invalid if it has no fields, but surely it doesn't need any sections then. //
/////////////////////////////////////////////////////////////////////////////////////////////////////
return;
}
//////////////////////////////////////////////////////////////////////////////
// create an identity section for the id and any fields in the record label //
//////////////////////////////////////////////////////////////////////////////
QFieldSection identitySection = new QFieldSection("identity", "Identity", new QIcon("badge"), Tier.T1, new ArrayList<>());
Set<String> usedFieldNames = new HashSet<>();
if(StringUtils.hasContent(table.getPrimaryKeyField()))
{
identitySection.getFieldNames().add(table.getPrimaryKeyField());
usedFieldNames.add(table.getPrimaryKeyField());
}
if(CollectionUtils.nullSafeHasContents(table.getRecordLabelFields()))
{
for(String fieldName : table.getRecordLabelFields())
{
if(!usedFieldNames.contains(fieldName))
{
identitySection.getFieldNames().add(fieldName);
usedFieldNames.add(fieldName);
}
}
}
if(!identitySection.getFieldNames().isEmpty())
{
table.addSection(identitySection);
}
///////////////////////////////////////////////////////////////////////////////
// if there are more fields, then add them in a default/Other Fields section //
///////////////////////////////////////////////////////////////////////////////
QFieldSection otherSection = new QFieldSection("otherFields", "Other Fields", new QIcon("dataset"), Tier.T2, new ArrayList<>());
if(CollectionUtils.nullSafeHasContents(table.getFields()))
{
for(String fieldName : table.getFields().keySet())
{
if(!usedFieldNames.contains(fieldName))
{
otherSection.getFieldNames().add(fieldName);
usedFieldNames.add(fieldName);
}
}
}
if(!otherSection.getFieldNames().isEmpty())
{
table.addSection(otherSection);
}
}
} }

View File

@ -32,6 +32,9 @@ 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.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -160,12 +163,63 @@ public class QInstanceValidator
} }
}); });
} }
//////////////////////////////////////////
// validate field sections in the table //
//////////////////////////////////////////
Set<String> fieldNamesInSections = new HashSet<>();
QFieldSection tier1Section = null;
if(table.getSections() != null)
{
for(QFieldSection section : table.getSections())
{
validateSection(errors, table, section, fieldNamesInSections);
if(section.getTier().equals(Tier.T1))
{
assertCondition(errors, tier1Section == null, "Table " + tableName + " has more than 1 section listed as Tier 1");
tier1Section = section;
}
}
}
if(CollectionUtils.nullSafeHasContents(table.getFields()))
{
for(String fieldName : table.getFields().keySet())
{
assertCondition(errors, fieldNamesInSections.contains(fieldName), "Table " + tableName + " field " + fieldName + " is not listed in any field sections.");
}
}
}); });
} }
} }
/*******************************************************************************
**
*******************************************************************************/
private void validateSection(List<String> errors, QTableMetaData table, QFieldSection section, Set<String> fieldNamesInSections)
{
assertCondition(errors, StringUtils.hasContent(section.getName()), "Missing a name for field section in table " + table.getName() + ".");
assertCondition(errors, StringUtils.hasContent(section.getLabel()), "Missing a label for field section in table " + table.getLabel() + ".");
if(assertCondition(errors, CollectionUtils.nullSafeHasContents(section.getFieldNames()), "Table " + table.getName() + " section " + section.getName() + " does not have any fields."))
{
if(table.getFields() != null)
{
for(String fieldName : section.getFieldNames())
{
assertCondition(errors, table.getFields().containsKey(fieldName), "Table " + table.getName() + " section " + section.getName() + " specifies fieldName " + fieldName + ", which is not a field on this table.");
assertCondition(errors, !fieldNamesInSections.contains(fieldName), "Table " + table.getName() + " has field " + fieldName + " listed more than once in its field sections.");
fieldNamesInSections.add(fieldName);
}
}
}
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -29,7 +29,6 @@ import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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.exceptions.QException;
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.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -56,6 +55,8 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
public class QRecord implements Serializable public class QRecord implements Serializable
{ {
private String tableName; private String tableName;
private String recordLabel;
private Map<String, Serializable> values = new LinkedHashMap<>(); private Map<String, Serializable> values = new LinkedHashMap<>();
private Map<String, String> displayValues = new LinkedHashMap<>(); private Map<String, String> displayValues = new LinkedHashMap<>();
private Map<String, Serializable> backendDetails = new LinkedHashMap<>(); private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
@ -90,6 +91,7 @@ public class QRecord implements Serializable
public QRecord(QRecord record) public QRecord(QRecord record)
{ {
this.tableName = record.tableName; this.tableName = record.tableName;
this.recordLabel = record.recordLabel;
this.values = record.values; this.values = record.values;
this.displayValues = record.displayValues; this.displayValues = record.displayValues;
this.backendDetails = record.backendDetails; this.backendDetails = record.backendDetails;
@ -139,15 +141,6 @@ public class QRecord implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public void setDisplayValue(QFieldMetaData field, Serializable rawValue)
{
displayValues.put(field.getName(), QValueFormatter.formatValue(field, rawValue));
}
/******************************************************************************* /*******************************************************************************
** **
@ -160,17 +153,6 @@ public class QRecord implements Serializable
/*******************************************************************************
**
*******************************************************************************/
public QRecord withDisplayValue(QFieldMetaData field, Serializable rawValue)
{
setDisplayValue(field, rawValue);
return (this);
}
/******************************************************************************* /*******************************************************************************
** Getter for tableName ** Getter for tableName
** **
@ -205,6 +187,39 @@ public class QRecord implements Serializable
/*******************************************************************************
** Getter for recordLabel
**
*******************************************************************************/
public String getRecordLabel()
{
return recordLabel;
}
/*******************************************************************************
** Setter for recordLabel
**
*******************************************************************************/
public void setRecordLabel(String recordLabel)
{
this.recordLabel = recordLabel;
}
/*******************************************************************************
** Fluent setter for recordLabel
**
*******************************************************************************/
public QRecord withRecordLabel(String recordLabel)
{
this.recordLabel = recordLabel;
return (this);
}
/******************************************************************************* /*******************************************************************************
** Getter for values ** Getter for values
** **

View File

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

View File

@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/******************************************************************************* /*******************************************************************************
@ -45,6 +46,8 @@ public class QFrontendProcessMetaData
private String tableName; private String tableName;
private boolean isHidden; private boolean isHidden;
private String iconName;
private List<QFrontendStepMetaData> frontendSteps; private List<QFrontendStepMetaData> frontendSteps;
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
@ -77,6 +80,11 @@ public class QFrontendProcessMetaData
frontendSteps = new ArrayList<>(); frontendSteps = new ArrayList<>();
} }
} }
if(processMetaData.getIcon() != null && StringUtils.hasContent(processMetaData.getIcon().getName()))
{
this.iconName = processMetaData.getIcon().getName();
}
} }
@ -148,12 +156,12 @@ public class QFrontendProcessMetaData
/******************************************************************************* /*******************************************************************************
** Setter for isHidden ** Getter for iconName
** **
*******************************************************************************/ *******************************************************************************/
public void setIsHidden(boolean isHidden) public String getIconName()
{ {
this.isHidden = isHidden; return iconName;
} }
} }

View File

@ -23,11 +23,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
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.StringUtils;
/******************************************************************************* /*******************************************************************************
@ -43,7 +46,10 @@ public class QFrontendTableMetaData
private boolean isHidden; private boolean isHidden;
private String primaryKeyField; private String primaryKeyField;
private String iconName;
private Map<String, QFrontendFieldMetaData> fields; private Map<String, QFrontendFieldMetaData> fields;
private List<QFieldSection> sections;
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! // // do not add setters. take values from the source-object in the constructor!! //
@ -68,6 +74,13 @@ public class QFrontendTableMetaData
{ {
this.fields.put(entry.getKey(), new QFrontendFieldMetaData(entry.getValue())); this.fields.put(entry.getKey(), new QFrontendFieldMetaData(entry.getValue()));
} }
this.sections = tableMetaData.getSections();
}
if(tableMetaData.getIcon() != null && StringUtils.hasContent(tableMetaData.getIcon().getName()))
{
this.iconName = tableMetaData.getIcon().getName();
} }
} }
@ -117,6 +130,17 @@ public class QFrontendTableMetaData
/*******************************************************************************
** Getter for sections
**
*******************************************************************************/
public List<QFieldSection> getSections()
{
return sections;
}
/******************************************************************************* /*******************************************************************************
** Getter for isHidden ** Getter for isHidden
** **
@ -129,11 +153,11 @@ public class QFrontendTableMetaData
/******************************************************************************* /*******************************************************************************
** Setter for isHidden ** Getter for iconName
** **
*******************************************************************************/ *******************************************************************************/
public void setIsHidden(boolean isHidden) public String getIconName()
{ {
this.isHidden = isHidden; return iconName;
} }
} }

View File

@ -27,7 +27,7 @@ import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
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.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/******************************************************************************* /*******************************************************************************
@ -44,6 +44,7 @@ public class QProcessMetaData implements QAppChildMetaData
private List<QStepMetaData> stepList; private List<QStepMetaData> stepList;
private String parentAppName; private String parentAppName;
private QIcon icon;
@ -321,4 +322,38 @@ public class QProcessMetaData implements QAppChildMetaData
this.parentAppName = 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 QProcessMetaData withIcon(QIcon icon)
{
this.icon = icon;
return (this);
}
} }

View File

@ -0,0 +1,234 @@
/*
* 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.tables;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/*******************************************************************************
** A section of fields - a logical grouping.
*******************************************************************************/
public class QFieldSection
{
private String name;
private String label;
private Tier tier;
private List<String> fieldNames;
private QIcon icon;
/*******************************************************************************
**
*******************************************************************************/
public QFieldSection()
{
}
/*******************************************************************************
**
*******************************************************************************/
public QFieldSection(String name, String label, QIcon icon, Tier tier, List<String> fieldNames)
{
this.name = name;
this.label = label;
this.icon = icon;
this.tier = tier;
this.fieldNames = fieldNames;
}
/*******************************************************************************
** Getter for name
**
*******************************************************************************/
public String getName()
{
return name;
}
/*******************************************************************************
** Setter for name
**
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
**
*******************************************************************************/
public QFieldSection 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 QFieldSection withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for tier
**
*******************************************************************************/
public Tier getTier()
{
return tier;
}
/*******************************************************************************
** Setter for tier
**
*******************************************************************************/
public void setTier(Tier tier)
{
this.tier = tier;
}
/*******************************************************************************
** Fluent setter for tier
**
*******************************************************************************/
public QFieldSection withTier(Tier tier)
{
this.tier = tier;
return (this);
}
/*******************************************************************************
** Getter for fieldNames
**
*******************************************************************************/
public List<String> getFieldNames()
{
return fieldNames;
}
/*******************************************************************************
** Setter for fieldNames
**
*******************************************************************************/
public void setFieldNames(List<String> fieldNames)
{
this.fieldNames = fieldNames;
}
/*******************************************************************************
** Fluent setter for fieldNames
**
*******************************************************************************/
public QFieldSection withFieldNames(List<String> fieldNames)
{
this.fieldNames = fieldNames;
return (this);
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
/*******************************************************************************
** Setter for icon
**
*******************************************************************************/
public void setIcon(QIcon icon)
{
this.icon = icon;
}
/*******************************************************************************
** Fluent setter for icon
**
*******************************************************************************/
public QFieldSection withIcon(QIcon icon)
{
this.icon = icon;
return (this);
}
}

View File

@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -64,6 +65,12 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
private String parentAppName; private String parentAppName;
private QIcon icon; private QIcon icon;
private String recordLabelFormat;
private List<String> recordLabelFields;
private List<QFieldSection> sections;
/******************************************************************************* /*******************************************************************************
** Default constructor. ** Default constructor.
@ -496,6 +503,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for icon ** Fluent setter for icon
** **
@ -506,4 +514,131 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
return (this); return (this);
} }
/*******************************************************************************
** Getter for recordLabelFormat
**
*******************************************************************************/
public String getRecordLabelFormat()
{
return recordLabelFormat;
}
/*******************************************************************************
** Setter for recordLabelFormat
**
*******************************************************************************/
public void setRecordLabelFormat(String recordLabelFormat)
{
this.recordLabelFormat = recordLabelFormat;
}
/*******************************************************************************
** Fluent setter for recordLabelFormat
**
*******************************************************************************/
public QTableMetaData withRecordLabelFormat(String recordLabelFormat)
{
this.recordLabelFormat = recordLabelFormat;
return (this);
}
/*******************************************************************************
** Getter for recordLabelFields
**
*******************************************************************************/
public List<String> getRecordLabelFields()
{
return recordLabelFields;
}
/*******************************************************************************
** Setter for recordLabelFields
**
*******************************************************************************/
public void setRecordLabelFields(List<String> recordLabelFields)
{
this.recordLabelFields = recordLabelFields;
}
/*******************************************************************************
** Fluent setter for recordLabelFields
**
*******************************************************************************/
public QTableMetaData withRecordLabelFields(List<String> recordLabelFields)
{
this.recordLabelFields = recordLabelFields;
return (this);
}
/*******************************************************************************
** Getter for sections
**
*******************************************************************************/
public List<QFieldSection> getSections()
{
return sections;
}
/*******************************************************************************
** Setter for sections
**
*******************************************************************************/
public void setSections(List<QFieldSection> sections)
{
this.sections = sections;
}
/*******************************************************************************
** Fluent setter for sections
**
*******************************************************************************/
public QTableMetaData withSections(List<QFieldSection> fieldSections)
{
this.sections = fieldSections;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public void addSection(QFieldSection fieldSection)
{
if(this.sections == null)
{
this.sections = new ArrayList<>();
}
this.sections.add(fieldSection);
}
/*******************************************************************************
**
*******************************************************************************/
public QTableMetaData withSection(QFieldSection fieldSection)
{
addSection(fieldSection);
return (this);
}
} }

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.tables;
/*******************************************************************************
**
*******************************************************************************/
public enum Tier
{
T1,
T2,
T3
}

View File

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

View File

@ -0,0 +1,160 @@
/*
* 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.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/*******************************************************************************
** Unit test for QValueFormatter
*******************************************************************************/
class QValueFormatterTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFormatValue()
{
assertNull(QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), null));
assertEquals("1", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), 1));
assertEquals("1,000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), 1000));
assertEquals("1000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(null), 1000));
assertEquals("$1,000.00", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.CURRENCY), 1000));
assertEquals("1,000.00", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.DECIMAL2_COMMAS), 1000));
assertEquals("1000.00", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.DECIMAL2), 1000));
assertEquals("1", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1")));
assertEquals("1,000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1000")));
assertEquals("1000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), new BigDecimal("1000")));
assertEquals("1000", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.STRING), 1000));
//////////////////////////////////////////////////
// this one flows through the exceptional cases //
//////////////////////////////////////////////////
assertEquals("1000.01", QValueFormatter.formatValue(new QFieldMetaData().withDisplayFormat(DisplayFormat.COMMAS), new BigDecimal("1000.01")));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFormatRecordLabel()
{
QTableMetaData table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("firstName", "lastName"));
assertEquals("Darin Kelkhoff", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("lastName", "Kelkhoff")));
assertEquals("Darin ", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin")));
assertEquals("Darin ", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("lastName", null)));
table = new QTableMetaData().withRecordLabelFormat("%s " + DisplayFormat.CURRENCY).withRecordLabelFields(List.of("firstName", "price"));
assertEquals("Darin $10,000.00", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("firstName", "Darin").withValue("price", new BigDecimal(10000))));
table = new QTableMetaData().withRecordLabelFormat(DisplayFormat.DEFAULT).withRecordLabelFields(List.of("id"));
assertEquals("123456", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", "123456")));
///////////////////////////////////////////////////////
// exceptional flow: no recordLabelFormat specified //
///////////////////////////////////////////////////////
table = new QTableMetaData().withPrimaryKeyField("id");
assertEquals("42", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", 42)));
/////////////////////////////////////////////////
// exceptional flow: no fields for the format //
/////////////////////////////////////////////////
table = new QTableMetaData().withRecordLabelFormat("%s %s").withPrimaryKeyField("id");
assertEquals("128", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("id", 128)));
/////////////////////////////////////////////////////////
// exceptional flow: not enough fields for the format //
/////////////////////////////////////////////////////////
table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("a")).withPrimaryKeyField("id");
assertEquals("256", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("a", 47).withValue("id", 256)));
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// exceptional flow (kinda): too many fields for the format (just get the ones that are in the format) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
table = new QTableMetaData().withRecordLabelFormat("%s %s").withRecordLabelFields(List.of("a", "b", "c")).withPrimaryKeyField("id");
assertEquals("47 48", QValueFormatter.formatRecordLabel(table, new QRecord().withValue("a", 47).withValue("b", 48).withValue("c", 49).withValue("id", 256)));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSetDisplayValuesInRecords()
{
QTableMetaData table = new QTableMetaData()
.withRecordLabelFormat("%s %s")
.withRecordLabelFields(List.of("firstName", "lastName"))
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
.withField(new QFieldMetaData("price", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY))
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER).withDisplayFormat(DisplayFormat.COMMAS));
/////////////////////////////////////////////////////////////////
// first, make sure it doesn't crash with null or empty inputs //
/////////////////////////////////////////////////////////////////
QValueFormatter.setDisplayValuesInRecords(table, null);
QValueFormatter.setDisplayValuesInRecords(table, Collections.emptyList());
List<QRecord> records = List.of(
new QRecord()
.withValue("firstName", "Tim")
.withValue("lastName", "Chamberlain")
.withValue("price", new BigDecimal("3.50"))
.withValue("quantity", 1701),
new QRecord()
.withValue("firstName", "Tyler")
.withValue("lastName", "Samples")
.withValue("price", new BigDecimal("174999.99"))
.withValue("quantity", 47)
);
QValueFormatter.setDisplayValuesInRecords(table, records);
assertEquals("Tim Chamberlain", records.get(0).getRecordLabel());
assertEquals("$3.50", records.get(0).getDisplayValue("price"));
assertEquals("1,701", records.get(0).getDisplayValue("quantity"));
assertEquals("Tyler Samples", records.get(1).getRecordLabel());
assertEquals("$174,999.99", records.get(1).getDisplayValue("price"));
assertEquals("47", records.get(1).getDisplayValue("quantity"));
}
}

View File

@ -24,10 +24,17 @@ package com.kingsrook.qqq.backend.core.instances;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; 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.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
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.Tier;
import com.kingsrook.qqq.backend.core.utils.TestUtils; import com.kingsrook.qqq.backend.core.utils.TestUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -283,6 +290,124 @@ class QInstanceValidatorTest
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionsMissingName()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection(null, "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "Missing a name");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionsMissingLabel()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", null, new QIcon("person"), Tier.T1, List.of("id")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "Missing a label");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionsNoFields()
{
QTableMetaData table1 = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of()))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table1), "section1 does not have any fields", "field id is not listed in any field sections");
QTableMetaData table2 = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, null))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table2), "section1 does not have any fields", "field id is not listed in any field sections");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionsUnrecognizedFieldName()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id", "od")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "not a field on this table");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionsDuplicatedFieldName()
{
QTableMetaData table1 = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id", "id")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table1), "more than once");
QTableMetaData table2 = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withSection(new QFieldSection("section2", "Section 2", new QIcon("person"), Tier.T2, List.of("id")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table2), "more than once");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldNotInAnySections()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("name", QFieldType.STRING));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "not listed in any field sections");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldSectionsMultipleTier1()
{
QTableMetaData table = new QTableMetaData().withName("test")
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
.withSection(new QFieldSection("section1", "Section 1", new QIcon("person"), Tier.T1, List.of("id")))
.withSection(new QFieldSection("section2", "Section 2", new QIcon("person"), Tier.T1, List.of("name")))
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
.withField(new QFieldMetaData("name", QFieldType.STRING));
assertValidationFailureReasons((qInstance) -> qInstance.addTable(table), "more than 1 section listed as Tier 1");
}
/******************************************************************************* /*******************************************************************************
** Run a little setup code on a qInstance; then validate it, and assert that it ** Run a little setup code on a qInstance; then validate it, and assert that it
** failed validation with reasons that match the supplied vararg-reasons (but allow ** failed validation with reasons that match the supplied vararg-reasons (but allow

View File

@ -22,10 +22,12 @@
package com.kingsrook.qqq.backend.module.rdbms; package com.kingsrook.qqq.backend.module.rdbms;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
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.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
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.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails; import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
@ -48,11 +50,25 @@ public class TestUtils
QInstance qInstance = new QInstance(); QInstance qInstance = new QInstance();
qInstance.addBackend(defineBackend()); qInstance.addBackend(defineBackend());
qInstance.addTable(defineTablePerson()); qInstance.addTable(defineTablePerson());
qInstance.setAuthentication(defineAuthentication());
return (qInstance); return (qInstance);
} }
/*******************************************************************************
** Define the authentication used in standard tests - using 'mock' type.
**
*******************************************************************************/
public static QAuthenticationMetaData defineAuthentication()
{
return new QAuthenticationMetaData()
.withName("mock")
.withType(QAuthenticationType.MOCK);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -23,16 +23,20 @@ package com.kingsrook.qqq.backend.module.rdbms.actions;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
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.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;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import com.kingsrook.qqq.backend.module.rdbms.TestUtils;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/******************************************************************************* /*******************************************************************************
@ -416,7 +420,29 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
QueryInput queryInput = new QueryInput(); QueryInput queryInput = new QueryInput();
queryInput.setInstance(TestUtils.defineInstance()); queryInput.setInstance(TestUtils.defineInstance());
queryInput.setTableName(TestUtils.defineTablePerson().getName()); queryInput.setTableName(TestUtils.defineTablePerson().getName());
queryInput.setSession(new QSession());
return queryInput; return queryInput;
} }
/*******************************************************************************
** This doesn't really test any RDBMS code, but is a checkpoint that the core
** module is populating displayValues when it performs the system-level query action.
*******************************************************************************/
@Test
public void testThatDisplayValuesGetSetGoingThroughQueryAction() throws QException
{
QueryInput queryInput = initQueryRequest();
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Assertions.assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
for(QRecord record : queryOutput.getRecords())
{
assertThat(record.getValues()).isNotEmpty();
assertThat(record.getDisplayValues()).isNotEmpty();
assertThat(record.getErrors()).isEmpty();
}
}
} }

View File

@ -33,16 +33,20 @@ 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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.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.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep; import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep; import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
@ -123,18 +127,25 @@ public class SampleMetaDataProvider
{ {
qInstance.addApp(new QAppMetaData() qInstance.addApp(new QAppMetaData()
.withName(APP_NAME_GREETINGS) .withName(APP_NAME_GREETINGS)
.withChild(qInstance.getProcess(PROCESS_NAME_GREET)) .withIcon(new QIcon().withName("emoji_people"))
.withChild(qInstance.getProcess(PROCESS_NAME_GREET_INTERACTIVE))); .withChild(qInstance.getProcess(PROCESS_NAME_GREET)
.withIcon(new QIcon().withName("emoji_people")))
.withChild(qInstance.getTable(TABLE_NAME_PERSON)
.withIcon(new QIcon().withName("person")))
.withChild(qInstance.getTable(TABLE_NAME_CITY)
.withIcon(new QIcon().withName("location_city")))
.withChild(qInstance.getProcess(PROCESS_NAME_GREET_INTERACTIVE))
.withIcon(new QIcon().withName("waving_hand")));
qInstance.addApp(new QAppMetaData() qInstance.addApp(new QAppMetaData()
.withName(APP_NAME_PEOPLE) .withName(APP_NAME_PEOPLE)
.withChild(qInstance.getTable(TABLE_NAME_PERSON)) .withIcon(new QIcon().withName("person"))
.withChild(qInstance.getTable(TABLE_NAME_CITY))
.withChild(qInstance.getApp(APP_NAME_GREETINGS))); .withChild(qInstance.getApp(APP_NAME_GREETINGS)));
qInstance.addApp(new QAppMetaData() qInstance.addApp(new QAppMetaData()
.withName(APP_NAME_MISCELLANEOUS) .withName(APP_NAME_MISCELLANEOUS)
.withChild(qInstance.getTable(TABLE_NAME_CARRIER)) .withIcon(new QIcon().withName("stars"))
.withChild(qInstance.getTable(TABLE_NAME_CARRIER).withIcon(new QIcon("local_shipping")))
.withChild(qInstance.getProcess(PROCESS_NAME_SIMPLE_SLEEP)) .withChild(qInstance.getProcess(PROCESS_NAME_SIMPLE_SLEEP))
.withChild(qInstance.getProcess(PROCESS_NAME_SLEEP_INTERACTIVE)) .withChild(qInstance.getProcess(PROCESS_NAME_SLEEP_INTERACTIVE))
.withChild(qInstance.getProcess(PROCESS_NAME_SIMPLE_THROW))); .withChild(qInstance.getProcess(PROCESS_NAME_SIMPLE_THROW)));
@ -205,19 +216,25 @@ public class SampleMetaDataProvider
table.setName(TABLE_NAME_CARRIER); table.setName(TABLE_NAME_CARRIER);
table.setBackendName(RDBMS_BACKEND_NAME); table.setBackendName(RDBMS_BACKEND_NAME);
table.setPrimaryKeyField("id"); table.setPrimaryKeyField("id");
table.setRecordLabelFormat("%s");
table.setRecordLabelFields(List.of("name"));
table.addField(new QFieldMetaData("id", QFieldType.INTEGER)); table.addField(new QFieldMetaData("id", QFieldType.INTEGER));
table.addField(new QFieldMetaData("name", QFieldType.STRING) table.addField(new QFieldMetaData("name", QFieldType.STRING)
.withIsRequired(true)); .withIsRequired(true));
table.addField(new QFieldMetaData("company_code", QFieldType.STRING) // todo enum table.addField(new QFieldMetaData("company_code", QFieldType.STRING) // todo PVS
.withLabel("Company") .withLabel("Company")
.withIsRequired(true) .withIsRequired(true)
.withBackendName("comp_code")); .withBackendName("comp_code"));
table.addField(new QFieldMetaData("service_level", QFieldType.STRING) table.addField(new QFieldMetaData("service_level", QFieldType.STRING) // todo PVS
.withIsRequired(true)); // todo enum .withLabel("Service Level")
.withIsRequired(true));
table.addSection(new QFieldSection("identity", "Identity", new QIcon("badge"), Tier.T1, List.of("id", "name")));
table.addSection(new QFieldSection("basicInfo", "Basic Info", new QIcon("dataset"), Tier.T2, List.of("company_code", "service_level")));
return (table); return (table);
} }
@ -234,13 +251,24 @@ public class SampleMetaDataProvider
.withLabel("Person") .withLabel("Person")
.withBackendName(RDBMS_BACKEND_NAME) .withBackendName(RDBMS_BACKEND_NAME)
.withPrimaryKeyField("id") .withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)) .withRecordLabelFormat("%s %s")
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date")) .withRecordLabelFields(List.of("firstName", "lastName"))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date")) .withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date").withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date").withIsEditable(false))
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name").withIsRequired(true)) .withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name").withIsRequired(true))
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name").withIsRequired(true)) .withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name").withIsRequired(true))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date")) .withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date"))
.withField(new QFieldMetaData("email", QFieldType.STRING)); .withField(new QFieldMetaData("email", QFieldType.STRING))
.withField(new QFieldMetaData("annualSalary", QFieldType.DECIMAL).withBackendName("annual_salary").withDisplayFormat(DisplayFormat.CURRENCY))
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER).withBackendName("days_worked").withDisplayFormat(DisplayFormat.COMMAS))
.withSection(new QFieldSection("identity", "Identity", new QIcon("badge"), Tier.T1, List.of("id", "firstName", "lastName")))
.withSection(new QFieldSection("basicInfo", "Basic Info", new QIcon("dataset"), Tier.T2, List.of("email", "birthDate")))
.withSection(new QFieldSection("employmentInfo", "Employment Info", new QIcon("work"), Tier.T2, List.of("annualSalary", "daysWorked")))
.withSection(new QFieldSection("dates", "Dates", new QIcon("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))
.withInferredFieldBackendNames();
} }

View File

@ -29,14 +29,17 @@ CREATE TABLE person
first_name VARCHAR(80) NOT NULL, first_name VARCHAR(80) NOT NULL,
last_name VARCHAR(80) NOT NULL, last_name VARCHAR(80) NOT NULL,
birth_date DATE, birth_date DATE,
email VARCHAR(250) NOT NULL email VARCHAR(250) NOT NULL,
annual_salary DECIMAL(12, 2),
days_worked INTEGER
); );
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com'); INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 75003.50, 1001);
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 150000, 10100);
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 300000, 100100);
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 950000, 75);
INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email, annual_salary, days_worked) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1500000, 1);
DROP TABLE IF EXISTS carrier; DROP TABLE IF EXISTS carrier;
CREATE TABLE carrier CREATE TABLE carrier