diff --git a/qqq-backend-core/pom.xml b/qqq-backend-core/pom.xml
index d6fc2da1..4bb8ca06 100644
--- a/qqq-backend-core/pom.xml
+++ b/qqq-backend-core/pom.xml
@@ -84,7 +84,7 @@
org.json
json
- 20230227
+ 20230618
org.apache.commons
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/AbstractPreInsertCustomizer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/AbstractPreInsertCustomizer.java
index d1e21dc4..196ea4b8 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/AbstractPreInsertCustomizer.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/AbstractPreInsertCustomizer.java
@@ -55,6 +55,21 @@ public abstract class AbstractPreInsertCustomizer
+ /////////////////////////////////////////////////////////////////////////////////
+ // allow the customizer to specify when it should be executed as part of the //
+ // insert action. default (per method in this class) is AFTER_ALL_VALIDATIONS //
+ /////////////////////////////////////////////////////////////////////////////////
+ public enum WhenToRun
+ {
+ BEFORE_ALL_VALIDATIONS,
+ BEFORE_UNIQUE_KEY_CHECKS,
+ BEFORE_REQUIRED_FIELD_CHECKS,
+ BEFORE_SECURITY_CHECKS,
+ AFTER_ALL_VALIDATIONS
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -62,6 +77,16 @@ public abstract class AbstractPreInsertCustomizer
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public WhenToRun getWhenToRun()
+ {
+ return (WhenToRun.AFTER_ALL_VALIDATIONS);
+ }
+
+
+
/*******************************************************************************
** Getter for insertInput
**
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/DateTimeGroupBy.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/DateTimeGroupBy.java
index bbd18e47..f69596af 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/DateTimeGroupBy.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/DateTimeGroupBy.java
@@ -99,7 +99,17 @@ public enum DateTimeGroupBy
public String getSqlExpression()
{
ZoneId sessionOrInstanceZoneId = ValueUtils.getSessionOrInstanceZoneId();
- String targetTimezone = sessionOrInstanceZoneId.toString();
+ return (getSqlExpression(sessionOrInstanceZoneId));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public String getSqlExpression(ZoneId targetZoneId)
+ {
+ String targetTimezone = targetZoneId.toString();
if("Z".equals(targetTimezone) || !StringUtils.hasContent(targetTimezone))
{
@@ -158,7 +168,18 @@ public enum DateTimeGroupBy
*******************************************************************************/
public String makeSelectedString(Instant time)
{
- ZonedDateTime zoned = time.atZone(ValueUtils.getSessionOrInstanceZoneId());
+ return (makeSelectedString(time, ValueUtils.getSessionOrInstanceZoneId()));
+ }
+
+
+
+ /*******************************************************************************
+ ** Make an Instant into a string that will match what came out of the database's
+ ** DATE_FORMAT() function
+ *******************************************************************************/
+ public String makeSelectedString(Instant time, ZoneId zoneId)
+ {
+ ZonedDateTime zoned = time.atZone(zoneId);
if(this == WEEK)
{
@@ -182,7 +203,17 @@ public enum DateTimeGroupBy
*******************************************************************************/
public String makeHumanString(Instant instant)
{
- ZonedDateTime zoned = instant.atZone(ValueUtils.getSessionOrInstanceZoneId());
+ return (makeHumanString(instant, ValueUtils.getSessionOrInstanceZoneId()));
+ }
+
+
+
+ /*******************************************************************************
+ ** Make a string to show to a user
+ *******************************************************************************/
+ public String makeHumanString(Instant instant, ZoneId zoneId)
+ {
+ ZonedDateTime zoned = instant.atZone(zoneId);
if(this.equals(WEEK))
{
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("M'/'d");
@@ -215,25 +246,35 @@ public enum DateTimeGroupBy
/*******************************************************************************
**
*******************************************************************************/
- @SuppressWarnings("checkstyle:indentation")
public Instant roundDown(Instant instant)
{
- ZonedDateTime zoned = instant.atZone(ValueUtils.getSessionOrInstanceZoneId());
+ return roundDown(instant, ValueUtils.getSessionOrInstanceZoneId());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @SuppressWarnings("checkstyle:indentation")
+ public Instant roundDown(Instant instant, ZoneId zoneId)
+ {
+ ZonedDateTime zoned = instant.atZone(zoneId);
return switch(this)
+ {
+ case YEAR -> zoned.with(TemporalAdjusters.firstDayOfYear()).truncatedTo(ChronoUnit.DAYS).toInstant();
+ case MONTH -> zoned.with(TemporalAdjusters.firstDayOfMonth()).truncatedTo(ChronoUnit.DAYS).toInstant();
+ case WEEK ->
{
- case YEAR -> zoned.with(TemporalAdjusters.firstDayOfYear()).truncatedTo(ChronoUnit.DAYS).toInstant();
- case MONTH -> zoned.with(TemporalAdjusters.firstDayOfMonth()).truncatedTo(ChronoUnit.DAYS).toInstant();
- case WEEK ->
+ while(zoned.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue())
{
- while(zoned.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue())
- {
- zoned = zoned.minusDays(1);
- }
- yield (zoned.truncatedTo(ChronoUnit.DAYS).toInstant());
+ zoned = zoned.minusDays(1);
}
- case DAY -> zoned.truncatedTo(ChronoUnit.DAYS).toInstant();
- case HOUR -> zoned.truncatedTo(ChronoUnit.HOURS).toInstant();
- };
+ yield (zoned.truncatedTo(ChronoUnit.DAYS).toInstant());
+ }
+ case DAY -> zoned.truncatedTo(ChronoUnit.DAYS).toInstant();
+ case HOUR -> zoned.truncatedTo(ChronoUnit.HOURS).toInstant();
+ };
}
@@ -243,7 +284,17 @@ public enum DateTimeGroupBy
*******************************************************************************/
public Instant increment(Instant instant)
{
- ZonedDateTime zoned = instant.atZone(ValueUtils.getSessionOrInstanceZoneId());
+ return (increment(instant, ValueUtils.getSessionOrInstanceZoneId()));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public Instant increment(Instant instant, ZoneId zoneId)
+ {
+ ZonedDateTime zoned = instant.atZone(zoneId);
return (zoned.plus(noOfChronoUnitsToAdd, chronoUnitToAdd).toInstant());
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ParentWidgetRenderer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ParentWidgetRenderer.java
index 6feb2105..f3937a15 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ParentWidgetRenderer.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/dashboard/widgets/ParentWidgetRenderer.java
@@ -60,6 +60,8 @@ public class ParentWidgetRenderer extends AbstractWidgetRenderer
widgetData.setChildWidgetNameList(metaData.getChildWidgetNameList());
}
+ widgetData.setLayoutType(metaData.getLayoutType());
+
return (new RenderWidgetOutput(widgetData));
}
catch(Exception e)
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
index 0b5adcb0..a537c883 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/InsertAction.java
@@ -193,25 +193,76 @@ public class InsertAction extends AbstractQActionFunction preInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPreInsertCustomizer.class, table, TableCustomizers.PRE_INSERT_RECORD.getRole());
if(preInsertCustomizer.isPresent())
{
preInsertCustomizer.get().setInsertInput(insertInput);
preInsertCustomizer.get().setIsPreview(isPreview);
- insertInput.setRecords(preInsertCustomizer.get().apply(insertInput.getRecords()));
+ runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
+ }
+
+ setDefaultValuesInRecords(table, insertInput.getRecords());
+
+ ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
+
+ runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
+ setErrorsIfUniqueKeyErrors(insertInput, table);
+
+ runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_REQUIRED_FIELD_CHECKS);
+ if(insertInput.getInputSource().shouldValidateRequiredFields())
+ {
+ validateRequiredFields(insertInput);
+ }
+
+ runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
+ ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
+
+ runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void setDefaultValuesInRecords(QTableMetaData table, List records)
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // for all fields in the table - if any have a default value, then look at all input records, //
+ // and if they have null value, then apply the default //
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ for(QFieldMetaData field : table.getFields().values())
+ {
+ if(field.getDefaultValue() != null)
+ {
+ for(QRecord record : records)
+ {
+ if(record.getValue(field.getName()) == null)
+ {
+ record.setValue(field.getName(), field.getDefaultValue());
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void runPreInsertCustomizerIfItIsTime(InsertInput insertInput, Optional preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun whenToRun) throws QException
+ {
+ if(preInsertCustomizer.isPresent())
+ {
+ if(whenToRun.equals(preInsertCustomizer.get().getWhenToRun()))
+ {
+ insertInput.setRecords(preInsertCustomizer.get().apply(insertInput.getRecords()));
+ }
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java
index 7dc51ca4..860a4279 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QValueFormatter.java
@@ -274,6 +274,14 @@ public class QValueFormatter
*******************************************************************************/
private static String formatRecordLabelExceptionalCases(QTableMetaData table, QRecord record)
{
+ //////////////////////////////////////////////////////////////////////////////////////
+ // if the record already has a label (say, from a query-customizer), then return it //
+ //////////////////////////////////////////////////////////////////////////////////////
+ if(record.getRecordLabel() != null)
+ {
+ return (record.getRecordLabel());
+ }
+
///////////////////////////////////////////////////////////////////////////////////////
// if there's no record label format, then just return the primary key display value //
///////////////////////////////////////////////////////////////////////////////////////
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java
index fcfd2fa1..7816dba4 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java
@@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
+import java.time.Instant;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -208,37 +210,50 @@ public class SearchPossibleValueSourceAction
}
else
{
- if(StringUtils.hasContent(input.getSearchTerm()))
+ String searchTerm = input.getSearchTerm();
+ if(StringUtils.hasContent(searchTerm))
{
for(String valueField : possibleValueSource.getSearchFields())
{
- QFieldMetaData field = table.getField(valueField);
- if(field.getType().equals(QFieldType.STRING))
+ try
{
- queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.STARTS_WITH, List.of(input.getSearchTerm())));
- }
- else if(field.getType().equals(QFieldType.DATE) || field.getType().equals(QFieldType.DATE_TIME))
- {
- LOG.debug("Not querying PVS [" + possibleValueSource.getName() + "] on date field [" + field.getName() + "]");
- // todo - what? queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.STARTS_WITH, List.of(input.getSearchTerm())));
- }
- else
- {
- try
+ QFieldMetaData field = table.getField(valueField);
+ if(field.getType().equals(QFieldType.STRING))
{
- Integer valueAsInteger = ValueUtils.getValueAsInteger(input.getSearchTerm());
+ queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.STARTS_WITH, List.of(searchTerm)));
+ }
+ else if(field.getType().equals(QFieldType.DATE))
+ {
+ LocalDate searchDate = ValueUtils.getValueAsLocalDate(searchTerm);
+ if(searchDate != null)
+ {
+ queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.EQUALS, searchDate));
+ }
+ }
+ else if(field.getType().equals(QFieldType.DATE_TIME))
+ {
+ Instant searchDate = ValueUtils.getValueAsInstant(searchTerm);
+ if(searchDate != null)
+ {
+ queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.EQUALS, searchDate));
+ }
+ }
+ else
+ {
+ Integer valueAsInteger = ValueUtils.getValueAsInteger(searchTerm);
if(valueAsInteger != null)
{
queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.EQUALS, List.of(valueAsInteger)));
}
}
- catch(Exception e)
- {
- ////////////////////////////////////////////////////////
- // write a FALSE criteria if the value isn't a number //
- ////////////////////////////////////////////////////////
- queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.IN, List.of()));
- }
+ }
+ catch(Exception e)
+ {
+ //////////////////////////////////////////////////////////////////////////////////////////
+ // write a FALSE criteria upon exceptions (e.g., type conversion fails) //
+ // Why are we doing this? so a single-field query finds nothing instead of everything. //
+ //////////////////////////////////////////////////////////////////////////////////////////
+ queryFilter.addCriteria(new QFilterCriteria(valueField, QCriteriaOperator.IN, List.of()));
}
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapter.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapter.java
index 46f51d58..925abf88 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapter.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/adapters/CsvToQRecordAdapter.java
@@ -36,7 +36,9 @@ import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFiel
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.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
@@ -156,14 +158,21 @@ public class CsvToQRecordAdapter
// now move values into the QRecord, using the mapping to get the 'header' corresponding to each QField //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
QRecord qRecord = new QRecord();
- for(QFieldMetaData field : table.getFields().values())
+ try
{
- String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
- fieldSource = adjustHeaderCase(fieldSource, inputWrapper);
- qRecord.setValue(field.getName(), csvValues.get(fieldSource));
- }
+ for(QFieldMetaData field : table.getFields().values())
+ {
+ String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
+ fieldSource = adjustHeaderCase(fieldSource, inputWrapper);
+ setValue(inputWrapper, qRecord, field, csvValues.get(fieldSource));
+ }
- runRecordCustomizer(recordCustomizer, qRecord);
+ runRecordCustomizer(recordCustomizer, qRecord);
+ }
+ catch(Exception e)
+ {
+ qRecord.addError(new BadInputStatusMessage("Error parsing line #" + (recordCount + 1) + ": " + e.getMessage()));
+ }
addRecord(qRecord);
recordCount++;
@@ -202,13 +211,20 @@ public class CsvToQRecordAdapter
// now move values into the QRecord, using the mapping to get the 'header' corresponding to each QField //
//////////////////////////////////////////////////////////////////////////////////////////////////////////
QRecord qRecord = new QRecord();
- for(QFieldMetaData field : table.getFields().values())
+ try
{
- Integer fieldIndex = (Integer) mapping.getFieldSource(field.getName());
- qRecord.setValue(field.getName(), csvValues.get(fieldIndex));
- }
+ for(QFieldMetaData field : table.getFields().values())
+ {
+ Integer fieldIndex = (Integer) mapping.getFieldSource(field.getName());
+ setValue(inputWrapper, qRecord, field, csvValues.get(fieldIndex));
+ }
- runRecordCustomizer(recordCustomizer, qRecord);
+ runRecordCustomizer(recordCustomizer, qRecord);
+ }
+ catch(Exception e)
+ {
+ qRecord.addError(new BadInputStatusMessage("Error parsing line #" + (recordCount + 1) + ": " + e.getMessage()));
+ }
addRecord(qRecord);
recordCount++;
@@ -231,6 +247,23 @@ public class CsvToQRecordAdapter
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void setValue(InputWrapper inputWrapper, QRecord qRecord, QFieldMetaData field, String valueString)
+ {
+ if(inputWrapper.doCorrectValueTypes)
+ {
+ qRecord.setValue(field.getName(), ValueUtils.getValueAsFieldType(field.getType(), valueString));
+ }
+ else
+ {
+ qRecord.setValue(field.getName(), valueString);
+ }
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -341,6 +374,7 @@ public class CsvToQRecordAdapter
private AbstractQFieldMapping> mapping;
private Consumer recordCustomizer;
private Integer limit;
+ private boolean doCorrectValueTypes = false;
private boolean caseSensitiveHeaders = false;
@@ -582,6 +616,40 @@ public class CsvToQRecordAdapter
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for doCorrectValueTypes
+ **
+ *******************************************************************************/
+ public boolean getDoCorrectValueTypes()
+ {
+ return doCorrectValueTypes;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for doCorrectValueTypes
+ **
+ *******************************************************************************/
+ public void setDoCorrectValueTypes(boolean doCorrectValueTypes)
+ {
+ this.doCorrectValueTypes = doCorrectValueTypes;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for doCorrectValueTypes
+ **
+ *******************************************************************************/
+ public InputWrapper withDoCorrectValueTypes(boolean doCorrectValueTypes)
+ {
+ this.doCorrectValueTypes = doCorrectValueTypes;
+ return (this);
+ }
+
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/logging/QLogger.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/logging/QLogger.java
index 55d58086..3683c62a 100755
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/logging/QLogger.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/logging/QLogger.java
@@ -124,7 +124,7 @@ public class QLogger
*******************************************************************************/
public void log(Level level, String message)
{
- logger.log(level, makeJsonString(message));
+ logger.log(level, () -> makeJsonString(message));
}
@@ -134,7 +134,7 @@ public class QLogger
*******************************************************************************/
public void log(Level level, String message, Throwable t)
{
- logger.log(level, makeJsonString(message, t));
+ logger.log(level, () -> makeJsonString(message, t));
}
@@ -144,7 +144,7 @@ public class QLogger
*******************************************************************************/
public void log(Level level, String message, Throwable t, LogPair... logPairs)
{
- logger.log(level, makeJsonString(message, t, logPairs));
+ logger.log(level, () -> makeJsonString(message, t, logPairs));
}
@@ -154,7 +154,7 @@ public class QLogger
*******************************************************************************/
public void log(Level level, Throwable t)
{
- logger.log(level, makeJsonString(null, t));
+ logger.log(level, () -> makeJsonString(null, t));
}
@@ -164,7 +164,7 @@ public class QLogger
*******************************************************************************/
public void trace(String message)
{
- logger.trace(makeJsonString(message));
+ logger.trace(() -> makeJsonString(message));
}
@@ -174,7 +174,7 @@ public class QLogger
*******************************************************************************/
public void trace(String message, LogPair... logPairs)
{
- logger.trace(makeJsonString(message, null, logPairs));
+ logger.trace(() -> makeJsonString(message, null, logPairs));
}
@@ -194,7 +194,7 @@ public class QLogger
*******************************************************************************/
public void trace(String message, Throwable t)
{
- logger.trace(makeJsonString(message, t));
+ logger.trace(() -> makeJsonString(message, t));
}
@@ -204,7 +204,7 @@ public class QLogger
*******************************************************************************/
public void trace(String message, Throwable t, LogPair... logPairs)
{
- logger.trace(makeJsonString(message, t, logPairs));
+ logger.trace(() -> makeJsonString(message, t, logPairs));
}
@@ -214,7 +214,7 @@ public class QLogger
*******************************************************************************/
public void trace(Throwable t)
{
- logger.trace(makeJsonString(null, t));
+ logger.trace(() -> makeJsonString(null, t));
}
@@ -224,7 +224,7 @@ public class QLogger
*******************************************************************************/
public void debug(String message)
{
- logger.debug(makeJsonString(message));
+ logger.debug(() -> makeJsonString(message));
}
@@ -234,7 +234,7 @@ public class QLogger
*******************************************************************************/
public void debug(String message, LogPair... logPairs)
{
- logger.debug(makeJsonString(message, null, logPairs));
+ logger.debug(() -> makeJsonString(message, null, logPairs));
}
@@ -254,7 +254,7 @@ public class QLogger
*******************************************************************************/
public void debug(String message, Throwable t)
{
- logger.debug(makeJsonString(message, t));
+ logger.debug(() -> makeJsonString(message, t));
}
@@ -264,7 +264,7 @@ public class QLogger
*******************************************************************************/
public void debug(String message, Throwable t, LogPair... logPairs)
{
- logger.debug(makeJsonString(message, t, logPairs));
+ logger.debug(() -> makeJsonString(message, t, logPairs));
}
@@ -274,7 +274,7 @@ public class QLogger
*******************************************************************************/
public void debug(Throwable t)
{
- logger.debug(makeJsonString(null, t));
+ logger.debug(() -> makeJsonString(null, t));
}
@@ -284,7 +284,7 @@ public class QLogger
*******************************************************************************/
public void info(String message)
{
- logger.info(makeJsonString(message));
+ logger.info(() -> makeJsonString(message));
}
@@ -294,7 +294,7 @@ public class QLogger
*******************************************************************************/
public void info(LogPair... logPairs)
{
- logger.info(makeJsonString(null, null, logPairs));
+ logger.info(() -> makeJsonString(null, null, logPairs));
}
@@ -304,7 +304,7 @@ public class QLogger
*******************************************************************************/
public void info(List logPairList)
{
- logger.info(makeJsonString(null, null, logPairList));
+ logger.info(() -> makeJsonString(null, null, logPairList));
}
@@ -314,7 +314,7 @@ public class QLogger
*******************************************************************************/
public void info(String message, LogPair... logPairs)
{
- logger.info(makeJsonString(message, null, logPairs));
+ logger.info(() -> makeJsonString(message, null, logPairs));
}
@@ -334,7 +334,7 @@ public class QLogger
*******************************************************************************/
public void info(String message, Throwable t)
{
- logger.info(makeJsonString(message, t));
+ logger.info(() -> makeJsonString(message, t));
}
@@ -344,7 +344,7 @@ public class QLogger
*******************************************************************************/
public void info(String message, Throwable t, LogPair... logPairs)
{
- logger.info(makeJsonString(message, t, logPairs));
+ logger.info(() -> makeJsonString(message, t, logPairs));
}
@@ -354,7 +354,7 @@ public class QLogger
*******************************************************************************/
public void info(Throwable t)
{
- logger.info(makeJsonString(null, t));
+ logger.info(() -> makeJsonString(null, t));
}
@@ -364,7 +364,7 @@ public class QLogger
*******************************************************************************/
public void warn(String message)
{
- logger.warn(makeJsonString(message));
+ logger.warn(() -> makeJsonString(message));
}
@@ -374,7 +374,7 @@ public class QLogger
*******************************************************************************/
public void warn(String message, LogPair... logPairs)
{
- logger.warn(makeJsonString(message, null, logPairs));
+ logger.warn(() -> makeJsonString(message, null, logPairs));
}
@@ -394,7 +394,7 @@ public class QLogger
*******************************************************************************/
public void warn(String message, Throwable t)
{
- logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(message, t));
+ logger.log(determineIfShouldDowngrade(t, Level.WARN), () -> makeJsonString(message, t));
}
@@ -404,7 +404,7 @@ public class QLogger
*******************************************************************************/
public void warn(String message, Throwable t, LogPair... logPairs)
{
- logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(message, t, logPairs));
+ logger.log(determineIfShouldDowngrade(t, Level.WARN), () -> makeJsonString(message, t, logPairs));
}
@@ -414,7 +414,7 @@ public class QLogger
*******************************************************************************/
public void warn(Throwable t)
{
- logger.log(determineIfShouldDowngrade(t, Level.WARN), makeJsonString(null, t));
+ logger.log(determineIfShouldDowngrade(t, Level.WARN), () -> makeJsonString(null, t));
}
@@ -424,7 +424,7 @@ public class QLogger
*******************************************************************************/
public void error(String message)
{
- logger.error(makeJsonString(message));
+ logger.error(() -> makeJsonString(message));
}
@@ -434,7 +434,7 @@ public class QLogger
*******************************************************************************/
public void error(String message, LogPair... logPairs)
{
- logger.error(makeJsonString(message, null, logPairs));
+ logger.error(() -> makeJsonString(message, null, logPairs));
}
@@ -454,7 +454,7 @@ public class QLogger
*******************************************************************************/
public void error(String message, Throwable t)
{
- logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(message, t));
+ logger.log(determineIfShouldDowngrade(t, Level.ERROR), () -> makeJsonString(message, t));
}
@@ -464,7 +464,7 @@ public class QLogger
*******************************************************************************/
public void error(String message, Throwable t, LogPair... logPairs)
{
- logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(message, t, logPairs));
+ logger.log(determineIfShouldDowngrade(t, Level.ERROR), () -> makeJsonString(message, t, logPairs));
}
@@ -474,7 +474,7 @@ public class QLogger
*******************************************************************************/
public void error(Throwable t)
{
- logger.log(determineIfShouldDowngrade(t, Level.ERROR), makeJsonString(null, t));
+ logger.log(determineIfShouldDowngrade(t, Level.ERROR), () -> makeJsonString(null, t));
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java
index a3866948..ac131938 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartData.java
@@ -48,6 +48,8 @@ public class ChartData extends QWidgetData
private boolean isCurrency = false;
private int height;
+ private ChartSubheaderData chartSubheaderData;
+
/*******************************************************************************
@@ -387,6 +389,7 @@ public class ChartData extends QWidgetData
private String color;
private String backgroundColor;
private List urls;
+ private List backgroundColors;
@@ -423,6 +426,17 @@ public class ChartData extends QWidgetData
+ /*******************************************************************************
+ ** Getter for backgroundColors
+ **
+ *******************************************************************************/
+ public List getBackgroundColors()
+ {
+ return backgroundColors;
+ }
+
+
+
/*******************************************************************************
** Setter for backgroundColor
**
@@ -434,6 +448,17 @@ public class ChartData extends QWidgetData
+ /*******************************************************************************
+ ** Setter for backgroundColor
+ **
+ *******************************************************************************/
+ public void setBackgroundColors(List backgroundColors)
+ {
+ this.backgroundColors = backgroundColors;
+ }
+
+
+
/*******************************************************************************
** Fluent setter for backgroundColor
**
@@ -446,6 +471,18 @@ public class ChartData extends QWidgetData
+ /*******************************************************************************
+ ** Fluent setter for backgroundColor
+ **
+ *******************************************************************************/
+ public Dataset withBackgroundColors(List backgroundColors)
+ {
+ this.backgroundColors = backgroundColors;
+ return (this);
+ }
+
+
+
/*******************************************************************************
** Getter for color
**
@@ -559,4 +596,36 @@ public class ChartData extends QWidgetData
}
}
}
+
+
+
+ /*******************************************************************************
+ ** Getter for chartSubheaderData
+ *******************************************************************************/
+ public ChartSubheaderData getChartSubheaderData()
+ {
+ return (this.chartSubheaderData);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for chartSubheaderData
+ *******************************************************************************/
+ public void setChartSubheaderData(ChartSubheaderData chartSubheaderData)
+ {
+ this.chartSubheaderData = chartSubheaderData;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for chartSubheaderData
+ *******************************************************************************/
+ public ChartData withChartSubheaderData(ChartSubheaderData chartSubheaderData)
+ {
+ this.chartSubheaderData = chartSubheaderData;
+ return (this);
+ }
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartSubheaderData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartSubheaderData.java
new file mode 100644
index 00000000..6e52dfb4
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ChartSubheaderData.java
@@ -0,0 +1,331 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2023. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
+
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class ChartSubheaderData
+{
+ private Number mainNumber;
+ private Number vsPreviousPercent;
+ private Number vsPreviousNumber;
+ private Boolean isUpVsPrevious;
+ private Boolean isGoodVsPrevious;
+ private String vsDescription = "vs prev period";
+
+ private String mainNumberUrl;
+ private String previousNumberUrl;
+
+
+
+ /*******************************************************************************
+ ** Getter for mainNumber
+ *******************************************************************************/
+ public Number getMainNumber()
+ {
+ return (this.mainNumber);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for mainNumber
+ *******************************************************************************/
+ public void setMainNumber(Number mainNumber)
+ {
+ this.mainNumber = mainNumber;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for mainNumber
+ *******************************************************************************/
+ public ChartSubheaderData withMainNumber(Number mainNumber)
+ {
+ this.mainNumber = mainNumber;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for vsPreviousNumber
+ *******************************************************************************/
+ public Number getVsPreviousNumber()
+ {
+ return (this.vsPreviousNumber);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for vsPreviousNumber
+ *******************************************************************************/
+ public void setVsPreviousNumber(Number vsPreviousNumber)
+ {
+ this.vsPreviousNumber = vsPreviousNumber;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for vsPreviousNumber
+ *******************************************************************************/
+ public ChartSubheaderData withVsPreviousNumber(Number vsPreviousNumber)
+ {
+ this.vsPreviousNumber = vsPreviousNumber;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for vsDescription
+ *******************************************************************************/
+ public String getVsDescription()
+ {
+ return (this.vsDescription);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for vsDescription
+ *******************************************************************************/
+ public void setVsDescription(String vsDescription)
+ {
+ this.vsDescription = vsDescription;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for vsDescription
+ *******************************************************************************/
+ public ChartSubheaderData withVsDescription(String vsDescription)
+ {
+ this.vsDescription = vsDescription;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for vsPreviousPercent
+ *******************************************************************************/
+ public Number getVsPreviousPercent()
+ {
+ return (this.vsPreviousPercent);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for vsPreviousPercent
+ *******************************************************************************/
+ public void setVsPreviousPercent(Number vsPreviousPercent)
+ {
+ this.vsPreviousPercent = vsPreviousPercent;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for vsPreviousPercent
+ *******************************************************************************/
+ public ChartSubheaderData withVsPreviousPercent(Number vsPreviousPercent)
+ {
+ this.vsPreviousPercent = vsPreviousPercent;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for isUpVsPrevious
+ *******************************************************************************/
+ public Boolean getIsUpVsPrevious()
+ {
+ return (this.isUpVsPrevious);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for isUpVsPrevious
+ *******************************************************************************/
+ public void setIsUpVsPrevious(Boolean isUpVsPrevious)
+ {
+ this.isUpVsPrevious = isUpVsPrevious;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for isUpVsPrevious
+ *******************************************************************************/
+ public ChartSubheaderData withIsUpVsPrevious(Boolean isUpVsPrevious)
+ {
+ this.isUpVsPrevious = isUpVsPrevious;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for isGoodVsPrevious
+ *******************************************************************************/
+ public Boolean getIsGoodVsPrevious()
+ {
+ return (this.isGoodVsPrevious);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for isGoodVsPrevious
+ *******************************************************************************/
+ public void setIsGoodVsPrevious(Boolean isGoodVsPrevious)
+ {
+ this.isGoodVsPrevious = isGoodVsPrevious;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for isGoodVsPrevious
+ *******************************************************************************/
+ public ChartSubheaderData withIsGoodVsPrevious(Boolean isGoodVsPrevious)
+ {
+ this.isGoodVsPrevious = isGoodVsPrevious;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for mainNumberUrl
+ *******************************************************************************/
+ public String getMainNumberUrl()
+ {
+ return (this.mainNumberUrl);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for mainNumberUrl
+ *******************************************************************************/
+ public void setMainNumberUrl(String mainNumberUrl)
+ {
+ this.mainNumberUrl = mainNumberUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for mainNumberUrl
+ *******************************************************************************/
+ public ChartSubheaderData withMainNumberUrl(String mainNumberUrl)
+ {
+ this.mainNumberUrl = mainNumberUrl;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for previousNumberUrl
+ *******************************************************************************/
+ public String getPreviousNumberUrl()
+ {
+ return (this.previousNumberUrl);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for previousNumberUrl
+ *******************************************************************************/
+ public void setPreviousNumberUrl(String previousNumberUrl)
+ {
+ this.previousNumberUrl = previousNumberUrl;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for previousNumberUrl
+ *******************************************************************************/
+ public ChartSubheaderData withPreviousNumberUrl(String previousNumberUrl)
+ {
+ this.previousNumberUrl = previousNumberUrl;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void calculatePercentsEtc(boolean isUpGood)
+ {
+ if(mainNumber != null && vsPreviousNumber != null && vsPreviousNumber.doubleValue() > 0)
+ {
+ /////////////////////////////////////////////////////////////////
+ // these are the results we're going for: //
+ // current: 10, previous: 20 = -50% //
+ // current: 15, previous: 20 = -25% //
+ // current: 20, previous: 10 = +100% //
+ // current: 15, previous: 10 = +50% //
+ // this formula gets us that: (current - previous) / previous //
+ // (with a *100 in there to make it a percent-looking value) //
+ /////////////////////////////////////////////////////////////////
+ BigDecimal current = new BigDecimal(String.valueOf(mainNumber));
+ BigDecimal previous = new BigDecimal(String.valueOf(vsPreviousNumber));
+ BigDecimal difference = current.subtract(previous);
+ BigDecimal ratio = difference.divide(previous, new MathContext(2, RoundingMode.HALF_UP));
+ BigDecimal percentBD = ratio.multiply(new BigDecimal(100));
+ Integer percent = Math.abs(percentBD.intValue());
+ if(mainNumber.doubleValue() < vsPreviousNumber.doubleValue())
+ {
+ setIsUpVsPrevious(false);
+ setIsGoodVsPrevious(isUpGood ? false : true);
+ setVsPreviousPercent(percent);
+ }
+ else // note - equal is being considered here in the good.
+ {
+ setIsUpVsPrevious(true);
+ setIsGoodVsPrevious(isUpGood ? true : false);
+ setVsPreviousPercent(percent);
+ }
+ }
+ }
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ParentWidgetData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ParentWidgetData.java
index f75f032e..87338413 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ParentWidgetData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/ParentWidgetData.java
@@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
+import com.kingsrook.qqq.backend.core.model.metadata.dashboard.ParentWidgetMetaData;
/*******************************************************************************
@@ -32,6 +33,7 @@ import java.util.List;
public class ParentWidgetData extends QWidgetData
{
private List childWidgetNameList;
+ private ParentWidgetMetaData.LayoutType layoutType = ParentWidgetMetaData.LayoutType.GRID;
@@ -87,4 +89,36 @@ public class ParentWidgetData extends QWidgetData
return (this);
}
+
+
+ /*******************************************************************************
+ ** Getter for layoutType
+ *******************************************************************************/
+ public ParentWidgetMetaData.LayoutType getLayoutType()
+ {
+ return (this.layoutType);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for layoutType
+ *******************************************************************************/
+ public void setLayoutType(ParentWidgetMetaData.LayoutType layoutType)
+ {
+ this.layoutType = layoutType;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for layoutType
+ *******************************************************************************/
+ public ParentWidgetData withLayoutType(ParentWidgetMetaData.LayoutType layoutType)
+ {
+ this.layoutType = layoutType;
+ return (this);
+ }
+
+
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java
index d8330878..630e1cc5 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/dashboard/widgets/TableData.java
@@ -39,6 +39,8 @@ public class TableData extends QWidgetData
private List