mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Merged dev into feature/quartz-scheduler
This commit is contained in:
@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -30,12 +33,26 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
** part of a transaction.
|
||||
**
|
||||
** Most obvious use-case would be a JDBC Connection. See subclass in rdbms module.
|
||||
** Ditto MongoDB.
|
||||
**
|
||||
** Note: One would imagine that this class shouldn't ever implement Serializable...
|
||||
*******************************************************************************/
|
||||
public class QBackendTransaction
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QBackendTransaction openFor(AbstractTableActionInput input) throws QException
|
||||
{
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(input.getBackend());
|
||||
QBackendTransaction transaction = qModule.openTransaction(input);
|
||||
return (transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Commit the transaction.
|
||||
*******************************************************************************/
|
||||
|
@ -83,6 +83,15 @@ public class AsyncRecordPipeLoop
|
||||
long jobStartTime = System.currentTimeMillis();
|
||||
boolean everCalledConsumer = false;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// in case the pipe capacity has been made very small (useful in tests!), //
|
||||
// then make the minRecordsToConsume match it. //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(recordPipe.getCapacity() < minRecordsToConsume)
|
||||
{
|
||||
minRecordsToConsume = recordPipe.getCapacity();
|
||||
}
|
||||
|
||||
while(jobState.equals(AsyncJobState.RUNNING))
|
||||
{
|
||||
if(recordPipe.countAvailableRecords() < minRecordsToConsume)
|
||||
|
@ -295,7 +295,7 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error performing an audit", e);
|
||||
LOG.warn("Error performing an audit", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,10 +30,12 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
@ -73,6 +75,8 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
|
||||
public static final String AUDIT_CONTEXT_FIELD_NAME = "auditContext";
|
||||
|
||||
private static Set<String> loggedUnauditableTableNames = new HashSet<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -102,6 +106,21 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
return (output);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// currently, the table's primary key must be integer... so, log (once) and return early if not that //
|
||||
// (or, if no primary key!) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QFieldMetaData field = table.getFields().get(table.getPrimaryKeyField());
|
||||
if(field == null || !QFieldType.INTEGER.equals(field.getType()))
|
||||
{
|
||||
if(!loggedUnauditableTableNames.contains(table.getName()))
|
||||
{
|
||||
LOG.info("Cannot audit table without integer as its primary key", logPair("tableName", table.getName()));
|
||||
loggedUnauditableTableNames.add(table.getName());
|
||||
}
|
||||
return (output);
|
||||
}
|
||||
|
||||
String contextSuffix = getContentSuffix(input);
|
||||
|
||||
AuditInput auditInput = new AuditInput();
|
||||
@ -168,7 +187,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error performing DML audit", e, logPair("type", String.valueOf(dmlType)), logPair("table", table.getName()));
|
||||
LOG.warn("Error performing DML audit", e, logPair("type", String.valueOf(dmlType)), logPair("table", table.getName()));
|
||||
}
|
||||
|
||||
return (output);
|
||||
@ -192,6 +211,19 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
contextSuffix.append(" ").append(input.getAuditContext());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// look for a context value place directly into the session //
|
||||
//////////////////////////////////////////////////////////////
|
||||
QSession qSession = QContext.getQSession();
|
||||
if(qSession != null)
|
||||
{
|
||||
String sessionContext = qSession.getValue(AUDIT_CONTEXT_FIELD_NAME);
|
||||
if(StringUtils.hasContent(sessionContext))
|
||||
{
|
||||
contextSuffix.append(" ").append(sessionContext);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// note process label (and a possible context from the process's state) if present //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -215,17 +247,20 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
///////////////////////////////////////////////////
|
||||
// use api label & version if present in session //
|
||||
///////////////////////////////////////////////////
|
||||
QSession qSession = QContext.getQSession();
|
||||
String apiVersion = qSession.getValue("apiVersion");
|
||||
if(apiVersion != null)
|
||||
if(qSession != null)
|
||||
{
|
||||
String apiLabel = qSession.getValue("apiLabel");
|
||||
if(!StringUtils.hasContent(apiLabel))
|
||||
String apiVersion = qSession.getValue("apiVersion");
|
||||
if(apiVersion != null)
|
||||
{
|
||||
apiLabel = "API";
|
||||
String apiLabel = qSession.getValue("apiLabel");
|
||||
if(!StringUtils.hasContent(apiLabel))
|
||||
{
|
||||
apiLabel = "API";
|
||||
}
|
||||
contextSuffix.append(" via ").append(apiLabel).append(" Version: ").append(apiVersion);
|
||||
}
|
||||
contextSuffix.append(" via ").append(apiLabel).append(" Version: ").append(apiVersion);
|
||||
}
|
||||
|
||||
return (contextSuffix.toString());
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
|
||||
|
||||
|
||||
@ -55,6 +56,30 @@ public enum AutomationStatus implements PossibleValueEnum<Integer>
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get instance by id
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static AutomationStatus getById(Integer id)
|
||||
{
|
||||
if(id == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
for(AutomationStatus value : AutomationStatus.values())
|
||||
{
|
||||
if(Objects.equals(value.id, id))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for id
|
||||
**
|
||||
@ -106,10 +131,10 @@ public enum AutomationStatus implements PossibleValueEnum<Integer>
|
||||
public String getInsertOrUpdate()
|
||||
{
|
||||
return switch(this)
|
||||
{
|
||||
case PENDING_INSERT_AUTOMATIONS, RUNNING_INSERT_AUTOMATIONS, FAILED_INSERT_AUTOMATIONS -> "Insert";
|
||||
case PENDING_UPDATE_AUTOMATIONS, RUNNING_UPDATE_AUTOMATIONS, FAILED_UPDATE_AUTOMATIONS -> "Update";
|
||||
case OK -> "";
|
||||
};
|
||||
{
|
||||
case PENDING_INSERT_AUTOMATIONS, RUNNING_INSERT_AUTOMATIONS, FAILED_INSERT_AUTOMATIONS -> "Insert";
|
||||
case PENDING_UPDATE_AUTOMATIONS, RUNNING_UPDATE_AUTOMATIONS, FAILED_UPDATE_AUTOMATIONS -> "Update";
|
||||
case OK -> "";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -22,30 +22,40 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.TableTrigger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.automation.AutomationStatusTrackingType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -55,19 +65,37 @@ public class RecordAutomationStatusUpdater
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RecordAutomationStatusUpdater.class);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// feature flag - by default, will be true - before setting records to PENDING_UPDATE_AUTOMATIONS, //
|
||||
// we will fetch them (if we didn't take them in from the caller, which, UpdateAction does if its //
|
||||
// backend supports it), to check their current automationStatus - and if they are currently PENDING //
|
||||
// or RUNNING inserts or updates, we won't update them. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static boolean allowPreUpdateFetch = new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.recordAutomationStatusUpdater.allowPreUpdateFetch", "QQQ_RECORD_AUTOMATION_STATUS_UPDATER_ALLOW_PRE_UPDATE_FETCH", true);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// feature flag - by default, we'll memoize the check for triggers - but we can turn it off. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static boolean memoizeCheckForTriggers = new QMetaDataVariableInterpreter().getBooleanFromPropertyOrEnvironment("qqq.recordAutomationStatusUpdater.memoizeCheckForTriggers", "QQQ_RECORD_AUTOMATION_STATUS_UPDATER_MEMOIZE_CHECK_FOR_TRIGGERS", true);
|
||||
|
||||
private static Memoization<Key, Boolean> areThereTableTriggersForTableMemoization = new Memoization<Key, Boolean>().withTimeout(Duration.of(60, ChronoUnit.SECONDS));
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** for a list of records from a table, set their automation status - based on
|
||||
** how the table is configured.
|
||||
*******************************************************************************/
|
||||
public static boolean setAutomationStatusInRecords(QSession session, QTableMetaData table, List<QRecord> records, AutomationStatus automationStatus)
|
||||
public static boolean setAutomationStatusInRecords(QTableMetaData table, List<QRecord> records, AutomationStatus automationStatus, QBackendTransaction transaction, List<QRecord> oldRecordList)
|
||||
{
|
||||
if(table == null || table.getAutomationDetails() == null || CollectionUtils.nullSafeIsEmpty(records))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
Set<Serializable> pkeysWeMayNotUpdate = new HashSet<>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// In case an automation is running, and it updates records - don't let those records be marked //
|
||||
// as PENDING_UPDATE_AUTOMATIONS... this is meant to avoid having a record's automation update //
|
||||
@ -81,12 +109,60 @@ public class RecordAutomationStatusUpdater
|
||||
for(StackTraceElement stackTraceElement : e.getStackTrace())
|
||||
{
|
||||
String className = stackTraceElement.getClassName();
|
||||
if(className.contains("com.kingsrook.qqq.backend.core.actions.automation") && !className.equals(RecordAutomationStatusUpdater.class.getName()) && !className.endsWith("Test"))
|
||||
if(className.contains(RecordAutomationStatusUpdater.class.getPackageName()) && !className.equals(RecordAutomationStatusUpdater.class.getName()) && !className.endsWith("Test") && !className.contains("Test$"))
|
||||
{
|
||||
LOG.debug("Avoiding re-setting automation status to PENDING_UPDATE while running an automation");
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// if table uses field-in-table status tracking, then check the old records, //
|
||||
// before we set them to pending-updates, to avoid losing other pending or //
|
||||
// running status information. We will allow moving from OK or the 2 //
|
||||
// failed statuses into pending-updates - which seems right. //
|
||||
// This is added to fix cases where an update that comes in before insert //
|
||||
// -automations have run, will cause the pending-insert status to be missed. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
if(automationDetails.getStatusTracking() != null && AutomationStatusTrackingType.FIELD_IN_TABLE.equals(automationDetails.getStatusTracking().getType()))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(oldRecordList))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we didn't get the oldRecordList as input (though UpdateAction should usually pass it?) //
|
||||
// then check feature-flag if we're allowed to do a lookup here & now. If so, then do. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(allowPreUpdateFetch)
|
||||
{
|
||||
List<Serializable> pkeysToLookup = records.stream().map(r -> r.getValue(table.getPrimaryKeyField())).toList();
|
||||
oldRecordList = new QueryAction().execute(new QueryInput(table.getName())
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, pkeysToLookup)))
|
||||
.withTransaction(transaction)
|
||||
).getRecords();
|
||||
}
|
||||
}
|
||||
|
||||
for(QRecord freshRecord : CollectionUtils.nonNullList(oldRecordList))
|
||||
{
|
||||
Serializable recordStatus = freshRecord.getValue(automationDetails.getStatusTracking().getFieldName());
|
||||
if(AutomationStatus.PENDING_INSERT_AUTOMATIONS.getId().equals(recordStatus)
|
||||
|| AutomationStatus.PENDING_UPDATE_AUTOMATIONS.getId().equals(recordStatus)
|
||||
|| AutomationStatus.RUNNING_INSERT_AUTOMATIONS.getId().equals(recordStatus)
|
||||
|| AutomationStatus.RUNNING_UPDATE_AUTOMATIONS.getId().equals(recordStatus))
|
||||
{
|
||||
Serializable primaryKey = freshRecord.getValue(table.getPrimaryKeyField());
|
||||
LOG.debug("May not update automation status", logPair("table", table.getName()), logPair("id", primaryKey), logPair("currentStatus", recordStatus), logPair("requestedStatus", automationStatus.getId()));
|
||||
pkeysWeMayNotUpdate.add(primaryKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
LOG.error("Error checking existing automation status before setting new automation status - more records will be updated than maybe should be...", qe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -98,19 +174,15 @@ public class RecordAutomationStatusUpdater
|
||||
automationStatus = AutomationStatus.OK;
|
||||
}
|
||||
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails.getStatusTracking() != null && AutomationStatusTrackingType.FIELD_IN_TABLE.equals(automationDetails.getStatusTracking().getType()))
|
||||
{
|
||||
for(QRecord record : records)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - seems like there's some case here, where if an order was in PENDING_INSERT, but then some other job updated the record, that we'd //
|
||||
// lose that pending status, which would be a Bad Thing™... //
|
||||
// problem is - we may not have the full record in here, so we can't necessarily check the record to see what status it's currently in... //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
record.setValue(automationDetails.getStatusTracking().getFieldName(), automationStatus.getId());
|
||||
// todo - another field - for the automation timestamp??
|
||||
if(!pkeysWeMayNotUpdate.contains(record.getValue(table.getPrimaryKeyField())))
|
||||
{
|
||||
record.setValue(automationDetails.getStatusTracking().getFieldName(), automationStatus.getId());
|
||||
// todo - another field - for the automation timestamp??
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,11 +260,29 @@ public class RecordAutomationStatusUpdater
|
||||
return (false);
|
||||
}
|
||||
|
||||
if(memoizeCheckForTriggers)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// as within the lookup method, error on the side of "yes, maybe there are triggers" //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<Boolean> result = areThereTableTriggersForTableMemoization.getResult(new Key(table, triggerEvent), key -> lookupIfThereAreTriggersForTable(table, triggerEvent));
|
||||
return result.orElse(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return lookupIfThereAreTriggersForTable(table, triggerEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Boolean lookupIfThereAreTriggersForTable(QTableMetaData table, TriggerEvent triggerEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
///////////////////
|
||||
// todo - cache? //
|
||||
///////////////////
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TableTrigger.TABLE_NAME);
|
||||
countInput.setFilter(new QQueryFilter(
|
||||
@ -207,6 +297,7 @@ public class RecordAutomationStatusUpdater
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the count query failed, we're a bit safer to err on the side of "yeah, there might be automations" //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
LOG.warn("Error looking if there are triggers for table", e, logPair("tableName", table.getName()));
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
@ -217,12 +308,12 @@ public class RecordAutomationStatusUpdater
|
||||
** for a list of records, update their automation status and actually Update the
|
||||
** backend as well.
|
||||
*******************************************************************************/
|
||||
public static void setAutomationStatusInRecordsAndUpdate(QInstance instance, QSession session, QTableMetaData table, List<QRecord> records, AutomationStatus automationStatus) throws QException
|
||||
public static void setAutomationStatusInRecordsAndUpdate(QTableMetaData table, List<QRecord> records, AutomationStatus automationStatus, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails != null && AutomationStatusTrackingType.FIELD_IN_TABLE.equals(automationDetails.getStatusTracking().getType()))
|
||||
{
|
||||
boolean didSetStatusField = setAutomationStatusInRecords(session, table, records, automationStatus);
|
||||
boolean didSetStatusField = setAutomationStatusInRecords(table, records, automationStatus, transaction, null);
|
||||
if(didSetStatusField)
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
@ -237,6 +328,7 @@ public class RecordAutomationStatusUpdater
|
||||
.withValue(table.getPrimaryKeyField(), r.getValue(table.getPrimaryKeyField()))
|
||||
.withValue(automationDetails.getStatusTracking().getFieldName(), r.getValue(automationDetails.getStatusTracking().getFieldName()))).toList());
|
||||
updateInput.setAreAllValuesBeingUpdatedTheSame(true);
|
||||
updateInput.setTransaction(transaction);
|
||||
updateInput.setOmitDmlAudit(true);
|
||||
|
||||
new UpdateAction().execute(updateInput);
|
||||
@ -250,4 +342,8 @@ public class RecordAutomationStatusUpdater
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private record Key(QTableMetaData table, TriggerEvent triggerEvent) {}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||
@ -43,6 +44,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
@ -59,18 +61,21 @@ import com.kingsrook.qqq.backend.core.model.automation.TableTrigger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
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.automation.AutomationStatusTrackingType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.json.JSONObject;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -87,8 +92,9 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(PollingAutomationPerTableRunner.class);
|
||||
|
||||
private final TableActions tableActions;
|
||||
private final String name;
|
||||
private final TableActionsInterface tableActions;
|
||||
|
||||
private String name;
|
||||
|
||||
private QInstance instance;
|
||||
private Supplier<QSession> sessionSupplier;
|
||||
@ -116,10 +122,51 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Interface to be used by 2 records in this class - normal TableActions, and
|
||||
** ShardedTableActions.
|
||||
*******************************************************************************/
|
||||
public record TableActions(String tableName, AutomationStatus status)
|
||||
public interface TableActionsInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String tableName();
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
AutomationStatus status();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Wrapper for a pair of (tableName, automationStatus)
|
||||
*******************************************************************************/
|
||||
public record TableActions(String tableName, AutomationStatus status) implements TableActionsInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void noopToFakeTestCoverage()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** extended version of TableAction, for sharding use-case - adds the shard
|
||||
** details.
|
||||
*******************************************************************************/
|
||||
public record ShardedTableActions(String tableName, AutomationStatus status, String shardByFieldName, Serializable shardValue, String shardLabel) implements TableActionsInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void noopToFakeTestCoverage()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -128,16 +175,46 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
** basically just get a list of tables which at least *could* have automations
|
||||
** run - either meta-data automations, or table-triggers (data/user defined).
|
||||
*******************************************************************************/
|
||||
public static List<TableActions> getTableActions(QInstance instance, String providerName)
|
||||
public static List<TableActionsInterface> getTableActions(QInstance instance, String providerName)
|
||||
{
|
||||
List<TableActions> tableActionList = new ArrayList<>();
|
||||
List<TableActionsInterface> tableActionList = new ArrayList<>();
|
||||
|
||||
for(QTableMetaData table : instance.getTables().values())
|
||||
{
|
||||
if(table.getAutomationDetails() != null && providerName.equals(table.getAutomationDetails().getProviderName()))
|
||||
QTableAutomationDetails automationDetails = table.getAutomationDetails();
|
||||
if(automationDetails != null && providerName.equals(automationDetails.getProviderName()))
|
||||
{
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_INSERT_AUTOMATIONS));
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS));
|
||||
if(StringUtils.hasContent(automationDetails.getShardByFieldName()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for sharded automations, add a tableAction (of the sharded subtype) for each shard-value //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(automationDetails.getShardSourceTableName());
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
Serializable shardId = record.getValue(automationDetails.getShardIdFieldName());
|
||||
String label = record.getValueString(automationDetails.getShardLabelFieldName());
|
||||
tableActionList.add(new ShardedTableActions(table.getName(), AutomationStatus.PENDING_INSERT_AUTOMATIONS, automationDetails.getShardByFieldName(), shardId, label));
|
||||
tableActionList.add(new ShardedTableActions(table.getName(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS, automationDetails.getShardByFieldName(), shardId, label));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error getting sharded table automation actions for a table", e, new LogPair("tableName", table.getName()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// for non-sharded, we just need tabler name & automation status //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_INSERT_AUTOMATIONS));
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,12 +226,17 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public PollingAutomationPerTableRunner(QInstance instance, String providerName, Supplier<QSession> sessionSupplier, TableActions tableActions)
|
||||
public PollingAutomationPerTableRunner(QInstance instance, String providerName, Supplier<QSession> sessionSupplier, TableActionsInterface tableActions)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.sessionSupplier = sessionSupplier;
|
||||
this.tableActions = tableActions;
|
||||
this.name = providerName + ">" + tableActions.tableName() + ">" + tableActions.status().getInsertOrUpdate();
|
||||
|
||||
if(tableActions instanceof ShardedTableActions shardedTableActions)
|
||||
{
|
||||
this.name += ":" + shardedTableActions.shardLabel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -173,12 +255,11 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
|
||||
try
|
||||
{
|
||||
QSession session = sessionSupplier != null ? sessionSupplier.get() : new QSession();
|
||||
processTableInsertOrUpdate(instance.getTable(tableActions.tableName()), session, tableActions.status());
|
||||
processTableInsertOrUpdate(instance.getTable(tableActions.tableName()), tableActions.status());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error running automations", e);
|
||||
LOG.warn("Error running automations", e, logPair("tableName", tableActions.tableName()), logPair("status", tableActions.status()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -192,7 +273,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
/*******************************************************************************
|
||||
** Query for and process records that have a PENDING_INSERT or PENDING_UPDATE status on a given table.
|
||||
*******************************************************************************/
|
||||
public void processTableInsertOrUpdate(QTableMetaData table, QSession session, AutomationStatus automationStatus) throws QException
|
||||
public void processTableInsertOrUpdate(QTableMetaData table, AutomationStatus automationStatus) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// get the actions to run against this table in this automation status //
|
||||
@ -222,19 +303,30 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
AutomationStatusTrackingType statusTrackingType = automationDetails.getStatusTracking().getType();
|
||||
if(AutomationStatusTrackingType.FIELD_IN_TABLE.equals(statusTrackingType))
|
||||
{
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(automationDetails.getStatusTracking().getFieldName(), QCriteriaOperator.EQUALS, List.of(automationStatus.getId()))));
|
||||
QQueryFilter filter = new QQueryFilter().withCriteria(new QFilterCriteria(automationDetails.getStatusTracking().getFieldName(), QCriteriaOperator.EQUALS, List.of(automationStatus.getId())));
|
||||
addOrderByToQueryFilter(table, automationStatus, filter);
|
||||
queryInput.setFilter(filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new NotImplementedException("Automation Status Tracking type [" + statusTrackingType + "] is not yet implemented in here."));
|
||||
}
|
||||
|
||||
if(tableActions instanceof ShardedTableActions shardedTableActions)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////
|
||||
// for sharded actions, add the shardBy field as a criteria //
|
||||
//////////////////////////////////////////////////////////////
|
||||
QQueryFilter filter = queryInput.getFilter();
|
||||
filter.addCriteria(new QFilterCriteria(shardedTableActions.shardByFieldName(), QCriteriaOperator.EQUALS, shardedTableActions.shardValue()));
|
||||
}
|
||||
|
||||
queryInput.setRecordPipe(recordPipe);
|
||||
return (new QueryAction().execute(queryInput));
|
||||
}, () ->
|
||||
{
|
||||
List<QRecord> records = recordPipe.consumeAvailableRecords();
|
||||
applyActionsToRecords(session, table, records, actions, automationStatus);
|
||||
applyActionsToRecords(table, records, actions, automationStatus);
|
||||
return (records.size());
|
||||
}
|
||||
);
|
||||
@ -242,6 +334,38 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static void addOrderByToQueryFilter(QTableMetaData table, AutomationStatus automationStatus, QQueryFilter filter)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// look for a field in the table with either create-date or modify-date behavior, //
|
||||
// based on if doing insert or update automations //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
DynamicDefaultValueBehavior dynamicDefaultValueBehavior = automationStatus.equals(AutomationStatus.PENDING_INSERT_AUTOMATIONS) ? DynamicDefaultValueBehavior.CREATE_DATE : DynamicDefaultValueBehavior.MODIFY_DATE;
|
||||
Optional<QFieldMetaData> field = table.getFields().values().stream()
|
||||
.filter(f -> dynamicDefaultValueBehavior.equals(f.getBehaviorOrDefault(QContext.getQInstance(), DynamicDefaultValueBehavior.class)))
|
||||
.findFirst();
|
||||
|
||||
if(field.isPresent())
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// if a create/modify date field was found, order by it (ascending) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
filter.addOrderBy(new QFilterOrderBy(field.get().getName()));
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////
|
||||
// else, order by the primary key //
|
||||
////////////////////////////////////
|
||||
filter.addOrderBy(new QFilterOrderBy(table.getPrimaryKeyField()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get the actions to run against a table in an automation status. both from
|
||||
** metaData and tableTriggers/data.
|
||||
@ -258,7 +382,23 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
{
|
||||
if(action.getTriggerEvent().equals(triggerEvent))
|
||||
{
|
||||
rs.add(action);
|
||||
///////////////////////////////////////////////////////////
|
||||
// for sharded configs, only run if the shard id matches //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(tableActions instanceof ShardedTableActions shardedTableActions)
|
||||
{
|
||||
if(shardedTableActions.shardValue().equals(action.getShardId()))
|
||||
{
|
||||
rs.add(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////
|
||||
// for non-sharded, always add the action //
|
||||
////////////////////////////////////////////
|
||||
rs.add(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,13 +425,15 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
if(filterId != null)
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(SavedFilter.TABLE_NAME);
|
||||
getInput.setTableName(SavedView.TABLE_NAME);
|
||||
getInput.setPrimaryKey(filterId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
SavedFilter savedFilter = new SavedFilter(getOutput.getRecord());
|
||||
filter = JsonUtils.toObject(savedFilter.getFilterJson(), QQueryFilter.class);
|
||||
SavedView savedView = new SavedView(getOutput.getRecord());
|
||||
JSONObject viewJson = new JSONObject(savedView.getViewJson());
|
||||
JSONObject queryFilter = viewJson.getJSONObject("queryFilter");
|
||||
filter = JsonUtils.toObject(queryFilter.toString(), QQueryFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +466,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
** table's actions against them - IF they are found to match the action's filter
|
||||
** (assuming it has one - if it doesn't, then all records match).
|
||||
*******************************************************************************/
|
||||
private void applyActionsToRecords(QSession session, QTableMetaData table, List<QRecord> records, List<TableAutomationAction> actions, AutomationStatus automationStatus) throws QException
|
||||
private void applyActionsToRecords(QTableMetaData table, List<QRecord> records, List<TableAutomationAction> actions, AutomationStatus automationStatus) throws QException
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(records))
|
||||
{
|
||||
@ -334,7 +476,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
///////////////////////////////////////////////////
|
||||
// mark the records as RUNNING their automations //
|
||||
///////////////////////////////////////////////////
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecordsAndUpdate(instance, session, table, records, pendingToRunningStatusMap.get(automationStatus));
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecordsAndUpdate(table, records, pendingToRunningStatusMap.get(automationStatus), null);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// foreach action - run it against the records (but only if they match the action's filter, if there is one) //
|
||||
@ -352,13 +494,15 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
////////////////////////////////////////
|
||||
// update status on all these records //
|
||||
////////////////////////////////////////
|
||||
if(anyActionsFailed)
|
||||
AutomationStatus statusToUpdateTo = anyActionsFailed ? pendingToFailedStatusMap.get(automationStatus) : AutomationStatus.OK;
|
||||
try
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecordsAndUpdate(instance, session, table, records, pendingToFailedStatusMap.get(automationStatus));
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecordsAndUpdate(table, records, statusToUpdateTo, null);
|
||||
}
|
||||
else
|
||||
catch(Exception e)
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecordsAndUpdate(instance, session, table, records, AutomationStatus.OK);
|
||||
LOG.warn("Error updating automationStatus after running automations", logPair("tableName", table), logPair("count", records.size()), logPair("status", statusToUpdateTo));
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,7 +532,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Caught exception processing records on " + table + " for action " + action, e);
|
||||
LOG.warn("Caught exception processing automations", e, logPair("tableName", table), logPair("action", action.getName()));
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
@ -471,7 +615,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
@Override
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
List<Serializable> recordIds = records.stream().map(r -> r.getValueInteger(table.getPrimaryKeyField())).collect(Collectors.toList());
|
||||
List<Serializable> recordIds = records.stream().map(r -> r.getValue(table.getPrimaryKeyField())).collect(Collectors.toList());
|
||||
return (new QQueryFilter().withCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordIds)));
|
||||
}
|
||||
});
|
||||
|
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.QQueryFilterDeduper;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
@ -176,11 +177,13 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
return (totalString);
|
||||
}
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return ("<a href='" + tablePath + "?filter=" + JsonUtils.toJson(filter) + "'>" + totalString + "</a>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -192,6 +195,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
return;
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
urls.add(tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||
}
|
||||
|
||||
@ -208,6 +212,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
return (null);
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||
}
|
||||
|
||||
@ -224,6 +229,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
return (null);
|
||||
}
|
||||
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
@ -326,6 +332,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
filter = QQueryFilterDeduper.dedupeFilter(filter);
|
||||
return (tablePath + "/" + processName + "?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
@ -297,4 +297,21 @@ public enum DateTimeGroupBy
|
||||
ZonedDateTime zoned = instant.atZone(zoneId);
|
||||
return (zoned.plus(noOfChronoUnitsToAdd, chronoUnitToAdd).toInstant());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static DateTimeFormatter sqlDateFormatToSelectedDateTimeFormatter(String sqlDateFormat)
|
||||
{
|
||||
for(DateTimeGroupBy value : values())
|
||||
{
|
||||
if(value.sqlDateFormat.equals(sqlDateFormat))
|
||||
{
|
||||
return (value.selectedStringFormatter);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
** Interface for the Insert action.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface InsertInterface extends QActionInterface
|
||||
public interface InsertInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -150,14 +151,21 @@ public class MetaDataAction
|
||||
}
|
||||
metaDataOutput.setWidgets(widgets);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// sort apps - by sortOrder (integer), then by label //
|
||||
///////////////////////////////////////////////////////
|
||||
List<QAppMetaData> sortedApps = metaDataInput.getInstance().getApps().values().stream()
|
||||
.sorted(Comparator.comparing((QAppMetaData a) -> a.getSortOrder())
|
||||
.thenComparing((QAppMetaData a) -> a.getLabel()))
|
||||
.toList();
|
||||
|
||||
///////////////////////////////////
|
||||
// map apps to frontend metadata //
|
||||
///////////////////////////////////
|
||||
Map<String, QFrontendAppMetaData> apps = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, QAppMetaData> entry : metaDataInput.getInstance().getApps().entrySet())
|
||||
for(QAppMetaData app : sortedApps)
|
||||
{
|
||||
String appName = entry.getKey();
|
||||
QAppMetaData app = entry.getValue();
|
||||
String appName = app.getName();
|
||||
|
||||
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, app);
|
||||
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
|
||||
@ -191,7 +199,7 @@ public class MetaDataAction
|
||||
// organize app tree nodes by their hierarchy //
|
||||
////////////////////////////////////////////////
|
||||
List<AppTreeNode> appTree = new ArrayList<>();
|
||||
for(QAppMetaData appMetaData : metaDataInput.getInstance().getApps().values())
|
||||
for(QAppMetaData appMetaData : sortedApps)
|
||||
{
|
||||
if(appMetaData.getParentAppName() == null)
|
||||
{
|
||||
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor for commonly used QProcessCallback's
|
||||
*******************************************************************************/
|
||||
public class QProcessCallbackFactory
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QProcessCallback forFilter(QQueryFilter filter)
|
||||
{
|
||||
return new QProcessCallback()
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
return (filter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Map<String, Serializable> getFieldValues(List<QFieldMetaData> fields)
|
||||
{
|
||||
return (Collections.emptyMap());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -72,7 +72,7 @@ import org.apache.commons.lang.BooleanUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Action handler for running q-processes (which are a sequence of q-functions).
|
||||
** Action handler for running q-processes (which are a sequence of q-steps).
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class RunProcessAction
|
||||
@ -82,6 +82,7 @@ public class RunProcessAction
|
||||
public static final String BASEPULL_THIS_RUNTIME_KEY = "basepullThisRuntimeKey";
|
||||
public static final String BASEPULL_LAST_RUNTIME_KEY = "basepullLastRuntimeKey";
|
||||
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
|
||||
public static final String BASEPULL_CONFIGURATION = "basepullConfiguration";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// indicator that the timestamp field should be updated - e.g., the execute step is finished. //
|
||||
@ -633,5 +634,6 @@ public class RunProcessAction
|
||||
|
||||
runProcessInput.getValues().put(BASEPULL_LAST_RUNTIME_KEY, lastRunTime);
|
||||
runProcessInput.getValues().put(BASEPULL_TIMESTAMP_FIELD, basepullConfiguration.getTimestampField());
|
||||
runProcessInput.getValues().put(BASEPULL_CONFIGURATION, basepullConfiguration);
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ public class GenerateReportAction
|
||||
RunBackendStepOutput transformStepOutput = null;
|
||||
if(tableView != null && tableView.getRecordTransformStep() != null)
|
||||
{
|
||||
transformStep = QCodeLoader.getBackendStep(AbstractTransformStep.class, tableView.getRecordTransformStep());
|
||||
transformStep = QCodeLoader.getAdHoc(AbstractTransformStep.class, tableView.getRecordTransformStep());
|
||||
|
||||
transformStepInput = new RunBackendStepInput();
|
||||
transformStepInput.setValues(reportInput.getInputValues());
|
||||
|
@ -44,7 +44,8 @@ public class RecordPipe
|
||||
private static final long BLOCKING_SLEEP_MILLIS = 100;
|
||||
private static final long MAX_SLEEP_LOOP_MILLIS = 300_000; // 5 minutes
|
||||
|
||||
private ArrayBlockingQueue<QRecord> queue = new ArrayBlockingQueue<>(1_000);
|
||||
private int capacity = 1_000;
|
||||
private ArrayBlockingQueue<QRecord> queue = new ArrayBlockingQueue<>(capacity);
|
||||
|
||||
private boolean isTerminated = false;
|
||||
|
||||
@ -72,6 +73,7 @@ public class RecordPipe
|
||||
*******************************************************************************/
|
||||
public RecordPipe(Integer overrideCapacity)
|
||||
{
|
||||
this.capacity = overrideCapacity;
|
||||
queue = new ArrayBlockingQueue<>(overrideCapacity);
|
||||
}
|
||||
|
||||
@ -213,4 +215,14 @@ public class RecordPipe
|
||||
this.postRecordActions = postRecordActions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for capacity
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int getCapacity()
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
@ -47,12 +48,14 @@ 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.QueryJoin;
|
||||
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.metadata.code.AdHocScriptCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptsMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -65,6 +68,8 @@ public class RunAdHocRecordScriptAction
|
||||
private Map<Integer, ScriptRevision> scriptRevisionCacheByScriptRevisionId = new HashMap<>();
|
||||
private Map<Integer, ScriptRevision> scriptRevisionCacheByScriptId = new HashMap<>();
|
||||
|
||||
private static Memoization<Integer, Script> scriptMemoizationById = new Memoization<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -85,6 +90,12 @@ public class RunAdHocRecordScriptAction
|
||||
throw (new QException("Script revision was not found."));
|
||||
}
|
||||
|
||||
Optional<Script> script = getScript(scriptRevision);
|
||||
|
||||
QContext.getQSession().setValue(DMLAuditAction.AUDIT_CONTEXT_FIELD_NAME, script.isPresent()
|
||||
? "via Script \"%s\"".formatted(script.get().getName())
|
||||
: "via Script id " + scriptRevision.getScriptId());
|
||||
|
||||
////////////////////////////
|
||||
// figure out the records //
|
||||
////////////////////////////
|
||||
@ -124,6 +135,10 @@ public class RunAdHocRecordScriptAction
|
||||
{
|
||||
output.setException(Optional.of(e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.getQSession().removeValue(DMLAuditAction.AUDIT_CONTEXT_FIELD_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -217,4 +232,44 @@ public class RunAdHocRecordScriptAction
|
||||
throw (new QException("Code reference did not contain a scriptRevision, scriptRevisionId, or scriptId"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Optional<Script> getScript(ScriptRevision scriptRevision)
|
||||
{
|
||||
if(scriptRevision == null || scriptRevision.getScriptId() == null)
|
||||
{
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return scriptMemoizationById.getResult(scriptRevision.getScriptId(), scriptId ->
|
||||
{
|
||||
try
|
||||
{
|
||||
QRecord scriptRecord = new GetAction().executeForRecord(new GetInput(Script.TABLE_NAME).withPrimaryKey(scriptRevision.getScriptId()));
|
||||
if(scriptRecord != null)
|
||||
{
|
||||
Script script = new Script(scriptRecord);
|
||||
scriptMemoizationById.storeResult(scriptRevision.getScriptId(), script);
|
||||
return (script);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("");
|
||||
}
|
||||
|
||||
return (null);
|
||||
});
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return (Optional.empty());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -53,6 +53,7 @@ import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
@ -117,21 +118,24 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
setAutomationStatusField(insertInput);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// load the backend module and its insert interface //
|
||||
//////////////////////////////////////////////////////
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
InsertInterface insertInterface = qModule.getInsertInterface();
|
||||
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
performValidations(insertInput, false);
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the insert //
|
||||
////////////////////////////////////
|
||||
InsertOutput insertOutput = insertInterface.execute(insertInput);
|
||||
//////////////////////////////////////////////////////
|
||||
// use the backend module to actually do the insert //
|
||||
//////////////////////////////////////////////////////
|
||||
InsertOutput insertOutput = runInsertInBackend(insertInput);
|
||||
|
||||
if(insertOutput.getRecords() == null)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// in case the module failed to set record in the output, put an empty list there //
|
||||
// to avoid so many downstream NPE's //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
insertOutput.setRecords(new ArrayList<>());
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// log if there were errors //
|
||||
@ -187,6 +191,37 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private InsertOutput runInsertInBackend(InsertInput insertInput) throws QException
|
||||
{
|
||||
///////////////////////////////////
|
||||
// exit early if 0 input records //
|
||||
///////////////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(insertInput.getRecords()))
|
||||
{
|
||||
LOG.debug("Insert request called with 0 records. Returning with no-op", logPair("tableName", insertInput.getTableName()));
|
||||
InsertOutput rs = new InsertOutput();
|
||||
rs.setRecords(new ArrayList<>());
|
||||
return (rs);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// load the backend module and its insert interface //
|
||||
//////////////////////////////////////////////////////
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput.getBackend());
|
||||
InsertInterface insertInterface = qModule.getInsertInterface();
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the insert //
|
||||
////////////////////////////////////
|
||||
InsertOutput insertOutput = insertInterface.execute(insertInput);
|
||||
return insertOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -208,7 +243,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
setDefaultValuesInRecords(table, insertInput.getRecords());
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.INSERT, insertInput.getInstance(), table, insertInput.getRecords(), null);
|
||||
|
||||
runPreInsertCustomizerIfItIsTime(insertInput, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_UNIQUE_KEY_CHECKS);
|
||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||
@ -410,7 +445,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
*******************************************************************************/
|
||||
private void setAutomationStatusField(InsertInput insertInput)
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecords(insertInput.getSession(), insertInput.getTable(), insertInput.getRecords(), AutomationStatus.PENDING_INSERT_AUTOMATIONS);
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecords(insertInput.getTable(), insertInput.getRecords(), AutomationStatus.PENDING_INSERT_AUTOMATIONS, insertInput.getTransaction(), null);
|
||||
}
|
||||
|
||||
|
||||
@ -418,23 +453,11 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QBackendModuleInterface getBackendModuleInterface(InsertInput insertInput) throws QException
|
||||
private QBackendModuleInterface getBackendModuleInterface(QBackendMetaData backend) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(insertInput);
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(insertInput.getBackend());
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
|
||||
return (qModule);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QBackendTransaction openTransaction(InsertInput insertInput) throws QException
|
||||
{
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
return (qModule.getInsertInterface().openTransaction(insertInput));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -84,9 +84,7 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
if(transaction == null)
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(input.getTableName());
|
||||
transaction = new InsertAction().openTransaction(insertInput);
|
||||
transaction = QBackendTransaction.openFor(new InsertInput(input.getTableName()));
|
||||
weOwnTheTransaction = true;
|
||||
}
|
||||
|
||||
@ -138,19 +136,22 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
output.setUpdateOutput(updateOutput);
|
||||
|
||||
QQueryFilter deleteFilter = new QQueryFilter(new QFilterCriteria(primaryKeyField, QCriteriaOperator.NOT_IN, primaryKeysToKeep));
|
||||
if(input.getFilter() != null)
|
||||
if(input.getPerformDeletes())
|
||||
{
|
||||
deleteFilter.addSubFilter(input.getFilter());
|
||||
}
|
||||
QQueryFilter deleteFilter = new QQueryFilter(new QFilterCriteria(primaryKeyField, QCriteriaOperator.NOT_IN, primaryKeysToKeep));
|
||||
if(input.getFilter() != null)
|
||||
{
|
||||
deleteFilter.addSubFilter(input.getFilter());
|
||||
}
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(table.getName());
|
||||
deleteInput.setQueryFilter(deleteFilter);
|
||||
deleteInput.setTransaction(transaction);
|
||||
deleteInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
output.setDeleteOutput(deleteOutput);
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(table.getName());
|
||||
deleteInput.setQueryFilter(deleteFilter);
|
||||
deleteInput.setTransaction(transaction);
|
||||
deleteInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
output.setDeleteOutput(deleteOutput);
|
||||
}
|
||||
|
||||
if(weOwnTheTransaction)
|
||||
{
|
||||
|
@ -57,6 +57,8 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
@ -72,6 +74,7 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -113,7 +116,6 @@ public class UpdateAction
|
||||
public UpdateOutput execute(UpdateInput updateInput) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(updateInput);
|
||||
setAutomationStatusField(updateInput);
|
||||
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
|
||||
@ -130,12 +132,31 @@ public class UpdateAction
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Optional<List<QRecord>> oldRecordList = fetchOldRecords(updateInput, updateInterface);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// allow caller to specify that we don't want to trigger automations. this isn't //
|
||||
// isn't expected to be used much - by design, only for the process that is meant to //
|
||||
// heal automation status, so that it can force us into status=Pending-inserts //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
if(!updateInput.getOmitTriggeringAutomations())
|
||||
{
|
||||
setAutomationStatusField(updateInput, oldRecordList);
|
||||
}
|
||||
|
||||
performValidations(updateInput, oldRecordList, false);
|
||||
|
||||
////////////////////////////////////
|
||||
// have the backend do the update //
|
||||
////////////////////////////////////
|
||||
UpdateOutput updateOutput = updateInterface.execute(updateInput);
|
||||
UpdateOutput updateOutput = runUpdateInBackend(updateInput, updateInterface);
|
||||
|
||||
if(updateOutput.getRecords() == null)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// in case the module failed to set record in the output, put an empty list there //
|
||||
// to avoid so many downstream NPE's //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
updateOutput.setRecords(new ArrayList<>());
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// log if there were errors //
|
||||
@ -194,6 +215,28 @@ public class UpdateAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private UpdateOutput runUpdateInBackend(UpdateInput updateInput, UpdateInterface updateInterface) throws QException
|
||||
{
|
||||
///////////////////////////////////
|
||||
// exit early if 0 input records //
|
||||
///////////////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(updateInput.getRecords()))
|
||||
{
|
||||
LOG.debug("Update request called with 0 records. Returning with no-op", logPair("tableName", updateInput.getTableName()));
|
||||
UpdateOutput rs = new UpdateOutput();
|
||||
rs.setRecords(new ArrayList<>());
|
||||
return (rs);
|
||||
}
|
||||
|
||||
UpdateOutput updateOutput = updateInterface.execute(updateInput);
|
||||
return updateOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -204,7 +247,13 @@ public class UpdateAction
|
||||
/////////////////////////////
|
||||
// run standard validators //
|
||||
/////////////////////////////
|
||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), table, updateInput.getRecords());
|
||||
Set<FieldBehavior<?>> behaviorsToOmit = null;
|
||||
if(BooleanUtils.isTrue(updateInput.getOmitModifyDateUpdate()))
|
||||
{
|
||||
behaviorsToOmit = Set.of(DynamicDefaultValueBehavior.MODIFY_DATE);
|
||||
}
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.UPDATE, updateInput.getInstance(), table, updateInput.getRecords(), behaviorsToOmit);
|
||||
validatePrimaryKeysAreGiven(updateInput);
|
||||
|
||||
if(oldRecordList.isPresent())
|
||||
@ -530,9 +579,9 @@ public class UpdateAction
|
||||
/*******************************************************************************
|
||||
** If the table being updated uses an automation-status field, populate it now.
|
||||
*******************************************************************************/
|
||||
private void setAutomationStatusField(UpdateInput updateInput)
|
||||
private void setAutomationStatusField(UpdateInput updateInput, Optional<List<QRecord>> oldRecordList)
|
||||
{
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecords(updateInput.getSession(), updateInput.getTable(), updateInput.getRecords(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS);
|
||||
RecordAutomationStatusUpdater.setAutomationStatusInRecords(updateInput.getTable(), updateInput.getRecords(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS, updateInput.getTransaction(), oldRecordList.orElse(null));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.tables.helpers;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
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.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Helper for backends that want to do their updates on records grouped by the
|
||||
** set of fields that are being changed, and/or by the values those fields are
|
||||
** being set to.
|
||||
**
|
||||
** e.g., RDBMS, for n records where some sub-set of fields are all having values
|
||||
** set the same (say, a status=x), we can do that as 1 query where id in (?,?,...,?).
|
||||
*******************************************************************************/
|
||||
public class UpdateActionRecordSplitHelper
|
||||
{
|
||||
private ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = new ListingHash<>();
|
||||
private boolean haveAnyWithoutErrors = false;
|
||||
private List<QRecord> outputRecords = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void init(UpdateInput updateInput)
|
||||
{
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
Instant now = Instant.now();
|
||||
|
||||
for(QRecord record : updateInput.getRecords())
|
||||
{
|
||||
List<String> updatableFields = table.getFields().values().stream()
|
||||
.map(QFieldMetaData::getName)
|
||||
// todo - intent here is to avoid non-updateable fields - but this
|
||||
// should be like based on field.isUpdatable once that attribute exists
|
||||
.filter(name -> !name.equals("id"))
|
||||
.filter(name -> record.getValues().containsKey(name))
|
||||
.toList();
|
||||
recordsByFieldBeingUpdated.add(updatableFields, record);
|
||||
|
||||
if(CollectionUtils.nullSafeIsEmpty(record.getErrors()))
|
||||
{
|
||||
haveAnyWithoutErrors = true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// go ahead and put the record into the output list at this point in time, //
|
||||
// so that the output list's order matches the input list order //
|
||||
// note that if we want to capture updated values (like modify dates), then //
|
||||
// we may want a map of primary key to output record, for easy updating. //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
QRecord outputRecord = new QRecord(record);
|
||||
outputRecords.add(outputRecord);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static boolean areAllValuesBeingUpdatedTheSame(UpdateInput updateInput, List<QRecord> recordList, List<String> fieldsBeingUpdated)
|
||||
{
|
||||
if(updateInput.getAreAllValuesBeingUpdatedTheSame() != null)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// if input told us what value to use here, then trust it //
|
||||
////////////////////////////////////////////////////////////
|
||||
return (updateInput.getAreAllValuesBeingUpdatedTheSame());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(recordList.size() == 1)
|
||||
{
|
||||
//////////////////////////////////////////////////////
|
||||
// if a single record, then yes, that always counts //
|
||||
//////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// else iterate over the records, comparing them to the first record //
|
||||
// return a false if any diffs are found. if no diffs, return true. //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
QRecord firstRecord = recordList.get(0);
|
||||
for(int i = 1; i < recordList.size(); i++)
|
||||
{
|
||||
QRecord record = recordList.get(i);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// skip records w/ errors (that we won't be updating //
|
||||
///////////////////////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
for(String fieldName : fieldsBeingUpdated)
|
||||
{
|
||||
if(!Objects.equals(firstRecord.getValue(fieldName), record.getValue(fieldName)))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for haveAnyWithoutErrors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getHaveAnyWithoutErrors()
|
||||
{
|
||||
return haveAnyWithoutErrors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordsByFieldBeingUpdated
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ListingHash<List<String>, QRecord> getRecordsByFieldBeingUpdated()
|
||||
{
|
||||
return recordsByFieldBeingUpdated;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for outputRecords
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getOutputRecords()
|
||||
{
|
||||
return outputRecords;
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.NullValueBehaviorUtil;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
||||
@ -230,7 +231,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
{
|
||||
for(QRecord inputRecord : inputRecords)
|
||||
{
|
||||
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
|
||||
if(RecordSecurityLock.NullValueBehavior.DENY.equals(NullValueBehaviorUtil.getEffectiveNullValueBehavior(recordSecurityLock)))
|
||||
{
|
||||
inputRecord.addError(new PermissionDeniedMessage("You do not have permission to " + action.name().toLowerCase() + " this record - the referenced " + leftMostJoinTable.getLabel() + " was not found."));
|
||||
}
|
||||
@ -298,7 +299,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// handle null values - error if the NullValueBehavior is DENY //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
|
||||
if(RecordSecurityLock.NullValueBehavior.DENY.equals(NullValueBehaviorUtil.getEffectiveNullValueBehavior(recordSecurityLock)))
|
||||
{
|
||||
String lockLabel = CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()) ? recordSecurityLock.getSecurityKeyType() : table.getField(recordSecurityLock.getFieldName()).getLabel();
|
||||
record.addError(new PermissionDeniedMessage("You do not have permission to " + action.name().toLowerCase() + " a record without a value in the field: " + lockLabel));
|
||||
|
@ -34,7 +34,6 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
@ -104,7 +103,7 @@ public class QPossibleValueTranslator
|
||||
{
|
||||
if(!transactionsPerTable.containsKey(tableName))
|
||||
{
|
||||
transactionsPerTable.put(tableName, new InsertAction().openTransaction(new InsertInput(tableName)));
|
||||
transactionsPerTable.put(tableName, QBackendTransaction.openFor(new InsertInput(tableName)));
|
||||
}
|
||||
|
||||
return (transactionsPerTable.get(tableName));
|
||||
|
@ -334,24 +334,34 @@ public class QValueFormatter
|
||||
if(exposedJoin.getJoinTable().equals(nameParts[0]))
|
||||
{
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(nameParts[0]);
|
||||
fieldMap.put(fieldName, joinTable.getField(nameParts[1]));
|
||||
if(joinTable.getFields().containsKey(nameParts[1]))
|
||||
{
|
||||
fieldMap.put(fieldName, joinTable.getField(nameParts[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldMap.put(fieldName, table.getField(fieldName));
|
||||
if(table.getFields().containsKey(fieldName))
|
||||
{
|
||||
fieldMap.put(fieldName, table.getField(fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
///////////////////////////////////////////////////////////
|
||||
// put an empty field in - so no formatting will be done //
|
||||
///////////////////////////////////////////////////////////
|
||||
LOG.info("Error getting field for setting display value", e, logPair("fieldName", fieldName), logPair("tableName", table.getName()));
|
||||
fieldMap.put(fieldName, new QFieldMetaData());
|
||||
LOG.warn("Error getting field for setting display value", e, logPair("fieldName", fieldName), logPair("tableName", table.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we didn't find the field definition, put an empty field in the map, so no formatting will be done //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(!fieldMap.containsKey(fieldName))
|
||||
{
|
||||
fieldMap.put(fieldName, new QFieldMetaData());
|
||||
}
|
||||
}
|
||||
|
||||
setDisplayValuesInRecord(fieldMap, record);
|
||||
|
@ -23,18 +23,17 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
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.ValueTooLongBehavior;
|
||||
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.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class to apply value behaviors to records.
|
||||
** Utility class to apply value behaviors to records.
|
||||
*******************************************************************************/
|
||||
public class ValueBehaviorApplier
|
||||
{
|
||||
@ -42,16 +41,10 @@ public class ValueBehaviorApplier
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void applyFieldBehaviors(QInstance instance, QTableMetaData table, List<QRecord> recordList)
|
||||
public enum Action
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
String fieldName = field.getName();
|
||||
if(field.getType().equals(QFieldType.STRING) && field.getMaxLength() != null)
|
||||
{
|
||||
applyValueTooLongBehavior(instance, recordList, field, fieldName);
|
||||
}
|
||||
}
|
||||
INSERT,
|
||||
UPDATE
|
||||
}
|
||||
|
||||
|
||||
@ -59,31 +52,18 @@ public class ValueBehaviorApplier
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void applyValueTooLongBehavior(QInstance instance, List<QRecord> recordList, QFieldMetaData field, String fieldName)
|
||||
public static void applyFieldBehaviors(Action action, QInstance instance, QTableMetaData table, List<QRecord> recordList, Set<FieldBehavior<?>> behaviorsToOmit)
|
||||
{
|
||||
ValueTooLongBehavior valueTooLongBehavior = field.getBehavior(instance, ValueTooLongBehavior.class);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// don't process PASS_THROUGH - so we don't have to iterate over the whole record list to do noop //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(valueTooLongBehavior != null && !valueTooLongBehavior.equals(ValueTooLongBehavior.PASS_THROUGH))
|
||||
if(CollectionUtils.nullSafeIsEmpty(recordList))
|
||||
{
|
||||
for(QRecord record : recordList)
|
||||
return;
|
||||
}
|
||||
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(field.getBehaviors()))
|
||||
{
|
||||
String value = record.getValueString(fieldName);
|
||||
if(value != null && value.length() > field.getMaxLength())
|
||||
{
|
||||
switch(valueTooLongBehavior)
|
||||
{
|
||||
case TRUNCATE -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength()));
|
||||
case TRUNCATE_ELLIPSIS -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength(), "..."));
|
||||
case ERROR -> record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too long (max allowed length=" + field.getMaxLength() + ")"));
|
||||
case PASS_THROUGH ->
|
||||
{
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected valueTooLongBehavior: " + valueTooLongBehavior);
|
||||
}
|
||||
}
|
||||
fieldBehavior.apply(action, recordList, instance, table, field, behaviorsToOmit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
|
||||
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.QFieldType;
|
||||
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;
|
||||
@ -133,11 +134,17 @@ public class CsvToQRecordAdapter
|
||||
CSVFormat.DEFAULT
|
||||
.withFirstRecordAsHeader()
|
||||
.withIgnoreHeaderCase()
|
||||
.withIgnoreEmptyLines()
|
||||
.withTrim());
|
||||
|
||||
List<String> headers = csvParser.getHeaderNames();
|
||||
headers = makeHeadersUnique(headers);
|
||||
|
||||
////////////////////////////////////////
|
||||
// used by csv-headers-as-field-names //
|
||||
////////////////////////////////////////
|
||||
Map<String, QFieldMetaData> csvHeaderFieldMapping = buildCsvHeaderFieldMappingIfNeeded(inputWrapper, headers);
|
||||
|
||||
Iterator<CSVRecord> csvIterator = csvParser.iterator();
|
||||
int recordCount = 0;
|
||||
while(csvIterator.hasNext())
|
||||
@ -160,11 +167,27 @@ public class CsvToQRecordAdapter
|
||||
QRecord qRecord = new QRecord();
|
||||
try
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
if(inputWrapper.getCsvHeadersAsFieldNames())
|
||||
{
|
||||
String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
|
||||
fieldSource = adjustHeaderCase(fieldSource, inputWrapper);
|
||||
setValue(inputWrapper, qRecord, field, csvValues.get(fieldSource));
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// in csv-headers-as-field-names mode, don't mess with table, and don't do any mapping //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(Map.Entry<String, String> entry : csvValues.entrySet())
|
||||
{
|
||||
setValue(inputWrapper, qRecord, csvHeaderFieldMapping.get(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// otherwise, fields come from table //
|
||||
///////////////////////////////////////
|
||||
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);
|
||||
@ -247,6 +270,26 @@ public class CsvToQRecordAdapter
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<String, QFieldMetaData> buildCsvHeaderFieldMappingIfNeeded(InputWrapper inputWrapper, List<String> headers)
|
||||
{
|
||||
Map<String, QFieldMetaData> csvHeaderFieldMapping = null;
|
||||
if(inputWrapper.getCsvHeadersAsFieldNames())
|
||||
{
|
||||
csvHeaderFieldMapping = new HashMap<>();
|
||||
for(String header : headers)
|
||||
{
|
||||
header = adjustHeaderCase(header, inputWrapper);
|
||||
csvHeaderFieldMapping.put(header, new QFieldMetaData(header, QFieldType.STRING));
|
||||
}
|
||||
}
|
||||
return csvHeaderFieldMapping;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -376,7 +419,8 @@ public class CsvToQRecordAdapter
|
||||
private Integer limit;
|
||||
private boolean doCorrectValueTypes = false;
|
||||
|
||||
private boolean caseSensitiveHeaders = false;
|
||||
private boolean caseSensitiveHeaders = false;
|
||||
private boolean csvHeadersAsFieldNames = false;
|
||||
|
||||
|
||||
|
||||
@ -618,6 +662,40 @@ public class CsvToQRecordAdapter
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for csvHeadersAsFieldNames
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getCsvHeadersAsFieldNames()
|
||||
{
|
||||
return csvHeadersAsFieldNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for csvHeadersAsFieldNames
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setCsvHeadersAsFieldNames(boolean csvHeadersAsFieldNames)
|
||||
{
|
||||
this.csvHeadersAsFieldNames = csvHeadersAsFieldNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for csvHeadersAsFieldNames
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InputWrapper withCsvHeadersAsFieldNames(boolean csvHeadersAsFieldNames)
|
||||
{
|
||||
this.csvHeadersAsFieldNames = csvHeadersAsFieldNames;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for doCorrectValueTypes
|
||||
**
|
||||
|
@ -103,13 +103,23 @@ public class JsonToQRecordAdapter
|
||||
{
|
||||
QRecord record = new QRecord();
|
||||
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
if(table == null)
|
||||
{
|
||||
String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
|
||||
// todo - so if the mapping didn't say how to map this field, does that mean we should use the default name for the field?
|
||||
if(jsonObject.has(fieldSource))
|
||||
jsonObject.keys().forEachRemaining(key ->
|
||||
{
|
||||
record.setValue(field.getName(), (Serializable) jsonObject.get(fieldSource));
|
||||
record.setValue(key, jsonObject.optString(key));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
String fieldSource = mapping == null ? field.getName() : String.valueOf(mapping.getFieldSource(field.getName()));
|
||||
// todo - so if the mapping didn't say how to map this field, does that mean we should use the default name for the field?
|
||||
if(jsonObject.has(fieldSource))
|
||||
{
|
||||
record.setValue(field.getName(), (Serializable) jsonObject.get(fieldSource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.context;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
@ -31,6 +34,7 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -47,6 +51,7 @@ public class QContext
|
||||
private static ThreadLocal<QBackendTransaction> qBackendTransactionThreadLocal = new ThreadLocal<>();
|
||||
private static ThreadLocal<Stack<AbstractActionInput>> actionStackThreadLocal = new ThreadLocal<>();
|
||||
|
||||
private static ThreadLocal<Map<String, Serializable>> objectsThreadLocal = new ThreadLocal<>();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -132,6 +137,7 @@ public class QContext
|
||||
qSessionThreadLocal.remove();
|
||||
qBackendTransactionThreadLocal.remove();
|
||||
actionStackThreadLocal.remove();
|
||||
objectsThreadLocal.remove();
|
||||
}
|
||||
|
||||
|
||||
@ -259,4 +265,92 @@ public class QContext
|
||||
|
||||
return (Optional.of(actionStackThreadLocal.get().get(0)));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get one named object from the Context for the current thread. may return null.
|
||||
*******************************************************************************/
|
||||
public static Serializable getObject(String key)
|
||||
{
|
||||
if(objectsThreadLocal.get() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return objectsThreadLocal.get().get(key);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get one named object from the Context for the current thread, cast to the
|
||||
** specified type if possible. if not found, or wrong type, empty is returned.
|
||||
*******************************************************************************/
|
||||
public static <T extends Serializable> Optional<T> getObject(String key, Class<T> type)
|
||||
{
|
||||
Serializable object = getObject(key);
|
||||
|
||||
if(type.isInstance(object))
|
||||
{
|
||||
return Optional.of(type.cast(object));
|
||||
}
|
||||
else if(object == null)
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unexpected type of object found in session under key [" + key + "]",
|
||||
logPair("expectedType", type.getName()),
|
||||
logPair("actualType", object.getClass().getName())
|
||||
);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** put a named object into the Context for the current thread.
|
||||
*******************************************************************************/
|
||||
public static void setObject(String key, Serializable object)
|
||||
{
|
||||
if(objectsThreadLocal.get() == null)
|
||||
{
|
||||
objectsThreadLocal.set(new HashMap<>());
|
||||
}
|
||||
objectsThreadLocal.get().put(key, object);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** remove a named object from the Context of the current thread.
|
||||
*******************************************************************************/
|
||||
public static void removeObject(String key)
|
||||
{
|
||||
if(objectsThreadLocal.get() != null)
|
||||
{
|
||||
objectsThreadLocal.get().remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get the full map of named objects for the current thread (possibly null).
|
||||
*******************************************************************************/
|
||||
public static Map<String, Serializable> getObjects()
|
||||
{
|
||||
return objectsThreadLocal.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fully replace the map of named objets for the current thread.
|
||||
*******************************************************************************/
|
||||
public static void setObjects(Map<String, Serializable> objects)
|
||||
{
|
||||
objectsThreadLocal.set(objects);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
@ -94,10 +95,8 @@ public class QInstanceEnricher
|
||||
|
||||
private JoinGraph joinGraph;
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// todo - come up w/ a way for app devs to set configs! //
|
||||
//////////////////////////////////////////////////////////
|
||||
private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
|
||||
private boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = true;
|
||||
private boolean configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate = true;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// let an instance define mappings to be applied during name-to-label enrichments, //
|
||||
@ -464,6 +463,22 @@ public class QInstanceEnricher
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// add field behaviors for create date & modify date, if so configured //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
if(configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate)
|
||||
{
|
||||
if("createDate".equals(field.getName()) && field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class) == null)
|
||||
{
|
||||
field.withBehavior(DynamicDefaultValueBehavior.CREATE_DATE);
|
||||
}
|
||||
|
||||
if("modifyDate".equals(field.getName()) && field.getBehaviorOnlyIfSet(DynamicDefaultValueBehavior.class) == null)
|
||||
{
|
||||
field.withBehavior(DynamicDefaultValueBehavior.MODIFY_DATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1220,4 +1235,66 @@ public class QInstanceEnricher
|
||||
labelMappings.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels
|
||||
*******************************************************************************/
|
||||
public boolean getConfigRemoveIdFromNameWhenCreatingPossibleValueFieldLabels()
|
||||
{
|
||||
return (this.configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels
|
||||
*******************************************************************************/
|
||||
public void setConfigRemoveIdFromNameWhenCreatingPossibleValueFieldLabels(boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels)
|
||||
{
|
||||
this.configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels
|
||||
*******************************************************************************/
|
||||
public QInstanceEnricher withConfigRemoveIdFromNameWhenCreatingPossibleValueFieldLabels(boolean configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels)
|
||||
{
|
||||
this.configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels = configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate
|
||||
*******************************************************************************/
|
||||
public boolean getConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate()
|
||||
{
|
||||
return (this.configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate
|
||||
*******************************************************************************/
|
||||
public void setConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate(boolean configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate)
|
||||
{
|
||||
this.configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate = configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate
|
||||
*******************************************************************************/
|
||||
public QInstanceEnricher withConfigAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate(boolean configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate)
|
||||
{
|
||||
this.configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate = configAddDynamicDefaultValuesToFieldsNamedCreateDateAndModifyDate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,12 +30,14 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
|
||||
@ -109,6 +111,8 @@ public class QInstanceHelpContentManager
|
||||
String processName = nameValuePairs.get("process");
|
||||
String fieldName = nameValuePairs.get("field");
|
||||
String sectionName = nameValuePairs.get("section");
|
||||
String widgetName = nameValuePairs.get("widget");
|
||||
String slotName = nameValuePairs.get("slot");
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// build a help content meta-data object from the record //
|
||||
@ -137,89 +141,16 @@ public class QInstanceHelpContentManager
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
if(StringUtils.hasContent(tableName))
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
LOG.info("Unrecognized table in help content", logPair("key", key));
|
||||
return;
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(fieldName))
|
||||
{
|
||||
//////////////////////////
|
||||
// handle a table field //
|
||||
//////////////////////////
|
||||
QFieldMetaData field = table.getFields().get(fieldName);
|
||||
if(field == null)
|
||||
{
|
||||
LOG.info("Unrecognized table field in help content", logPair("key", key));
|
||||
return;
|
||||
}
|
||||
|
||||
if(helpContent != null)
|
||||
{
|
||||
field.withHelpContent(helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.removeHelpContent(roles);
|
||||
}
|
||||
}
|
||||
else if(StringUtils.hasContent(sectionName))
|
||||
{
|
||||
////////////////////////////
|
||||
// handle a table section //
|
||||
////////////////////////////
|
||||
Optional<QFieldSection> optionalSection = table.getSections().stream().filter(s -> sectionName.equals(s.getName())).findFirst();
|
||||
if(optionalSection.isEmpty())
|
||||
{
|
||||
LOG.info("Unrecognized table section in help content", logPair("key", key));
|
||||
return;
|
||||
}
|
||||
|
||||
if(helpContent != null)
|
||||
{
|
||||
optionalSection.get().withHelpContent(helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
optionalSection.get().removeHelpContent(roles);
|
||||
}
|
||||
}
|
||||
processHelpContentForTable(key, tableName, sectionName, fieldName, roles, helpContent);
|
||||
}
|
||||
else if(StringUtils.hasContent(processName))
|
||||
{
|
||||
QProcessMetaData process = qInstance.getProcess(processName);
|
||||
if(process == null)
|
||||
{
|
||||
LOG.info("Unrecognized process in help content", logPair("key", key));
|
||||
return;
|
||||
}
|
||||
processHelpContentForProcess(key, processName, fieldName, roles, helpContent);
|
||||
}
|
||||
else if(StringUtils.hasContent(widgetName))
|
||||
{
|
||||
processHelpContentForWidget(key, widgetName, slotName, helpContent);
|
||||
|
||||
if(StringUtils.hasContent(fieldName))
|
||||
{
|
||||
////////////////////////////
|
||||
// handle a process field //
|
||||
////////////////////////////
|
||||
Optional<QFieldMetaData> optionalField = CollectionUtils.mergeLists(process.getInputFields(), process.getOutputFields())
|
||||
.stream().filter(f -> fieldName.equals(f.getName()))
|
||||
.findFirst();
|
||||
|
||||
if(optionalField.isEmpty())
|
||||
{
|
||||
LOG.info("Unrecognized process field in help content", logPair("key", key));
|
||||
return;
|
||||
}
|
||||
|
||||
if(helpContent != null)
|
||||
{
|
||||
optionalField.get().withHelpContent(helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
optionalField.get().removeHelpContent(roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -230,6 +161,131 @@ public class QInstanceHelpContentManager
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForTable(String key, String tableName, String sectionName, String fieldName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
if(table == null)
|
||||
{
|
||||
LOG.info("Unrecognized table in help content", logPair("key", key));
|
||||
}
|
||||
else if(StringUtils.hasContent(fieldName))
|
||||
{
|
||||
//////////////////////////
|
||||
// handle a table field //
|
||||
//////////////////////////
|
||||
QFieldMetaData field = table.getFields().get(fieldName);
|
||||
if(field == null)
|
||||
{
|
||||
LOG.info("Unrecognized table field in help content", logPair("key", key));
|
||||
}
|
||||
else if(helpContent != null)
|
||||
{
|
||||
field.withHelpContent(helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.removeHelpContent(roles);
|
||||
}
|
||||
}
|
||||
else if(StringUtils.hasContent(sectionName))
|
||||
{
|
||||
////////////////////////////
|
||||
// handle a table section //
|
||||
////////////////////////////
|
||||
Optional<QFieldSection> optionalSection = table.getSections().stream().filter(s -> sectionName.equals(s.getName())).findFirst();
|
||||
if(optionalSection.isEmpty())
|
||||
{
|
||||
LOG.info("Unrecognized table section in help content", logPair("key", key));
|
||||
}
|
||||
else if(helpContent != null)
|
||||
{
|
||||
optionalSection.get().withHelpContent(helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
optionalSection.get().removeHelpContent(roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForProcess(String key, String processName, String fieldName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(process == null)
|
||||
{
|
||||
LOG.info("Unrecognized process in help content", logPair("key", key));
|
||||
}
|
||||
else if(StringUtils.hasContent(fieldName))
|
||||
{
|
||||
////////////////////////////
|
||||
// handle a process field //
|
||||
////////////////////////////
|
||||
Optional<QFieldMetaData> optionalField = CollectionUtils.mergeLists(process.getInputFields(), process.getOutputFields())
|
||||
.stream().filter(f -> fieldName.equals(f.getName()))
|
||||
.findFirst();
|
||||
|
||||
if(optionalField.isEmpty())
|
||||
{
|
||||
LOG.info("Unrecognized process field in help content", logPair("key", key));
|
||||
}
|
||||
else if(helpContent != null)
|
||||
{
|
||||
optionalField.get().withHelpContent(helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
optionalField.get().removeHelpContent(roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForWidget(String key, String widgetName, String slotName, QHelpContent helpContent)
|
||||
{
|
||||
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
|
||||
if(!StringUtils.hasContent(slotName))
|
||||
{
|
||||
LOG.info("Missing slot name in help content", logPair("key", key));
|
||||
}
|
||||
else if(widget == null)
|
||||
{
|
||||
LOG.info("Unrecognized widget in help content", logPair("key", key));
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<String, QHelpContent> widgetHelpContent = widget.getHelpContent();
|
||||
if(widgetHelpContent == null)
|
||||
{
|
||||
widgetHelpContent = new HashMap<>();
|
||||
}
|
||||
|
||||
if(helpContent != null)
|
||||
{
|
||||
widgetHelpContent.put(slotName, helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
widgetHelpContent.remove(slotName);
|
||||
}
|
||||
|
||||
widget.setHelpContent(widgetHelpContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** add a help content object to a list - replacing an entry in the list with the
|
||||
** same roles if one is found.
|
||||
|
@ -31,11 +31,13 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
|
||||
@ -49,8 +51,10 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
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.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
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.dashboard.ParentWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@ -75,6 +79,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
|
||||
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.model.metadata.tables.UniqueKey;
|
||||
@ -83,9 +88,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.Automatio
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeLambda;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -150,11 +157,13 @@ public class QInstanceValidator
|
||||
try
|
||||
{
|
||||
validateBackends(qInstance);
|
||||
validateAuthentication(qInstance);
|
||||
validateAutomationProviders(qInstance);
|
||||
validateTables(qInstance, joinGraph);
|
||||
validateProcesses(qInstance);
|
||||
validateReports(qInstance);
|
||||
validateApps(qInstance);
|
||||
validateWidgets(qInstance);
|
||||
validatePossibleValueSources(qInstance);
|
||||
validateQueuesAndProviders(qInstance);
|
||||
validateJoins(qInstance);
|
||||
@ -204,14 +213,23 @@ public class QInstanceValidator
|
||||
if(assertCondition(StringUtils.hasContent(securityKeyType.getName()), "Missing name for a securityKeyType"))
|
||||
{
|
||||
assertCondition(Objects.equals(name, securityKeyType.getName()), "Inconsistent naming for securityKeyType: " + name + "/" + securityKeyType.getName() + ".");
|
||||
assertCondition(!usedNames.contains(name), "More than one SecurityKeyType with name (or allAccessKeyName) of: " + name);
|
||||
|
||||
String duplicateNameMessagePrefix = "More than one SecurityKeyType with name (or allAccessKeyName or nullValueBehaviorKeyName) of: ";
|
||||
assertCondition(!usedNames.contains(name), duplicateNameMessagePrefix + name);
|
||||
usedNames.add(name);
|
||||
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
||||
{
|
||||
assertCondition(!usedNames.contains(securityKeyType.getAllAccessKeyName()), "More than one SecurityKeyType with name (or allAccessKeyName) of: " + securityKeyType.getAllAccessKeyName());
|
||||
assertCondition(!usedNames.contains(securityKeyType.getAllAccessKeyName()), duplicateNameMessagePrefix + securityKeyType.getAllAccessKeyName());
|
||||
usedNames.add(securityKeyType.getAllAccessKeyName());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(securityKeyType.getNullValueBehaviorKeyName()))
|
||||
{
|
||||
assertCondition(!usedNames.contains(securityKeyType.getNullValueBehaviorKeyName()), duplicateNameMessagePrefix + securityKeyType.getNullValueBehaviorKeyName());
|
||||
usedNames.add(securityKeyType.getNullValueBehaviorKeyName());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(securityKeyType.getPossibleValueSourceName()))
|
||||
{
|
||||
assertCondition(qInstance.getPossibleValueSource(securityKeyType.getPossibleValueSourceName()) != null, "Unrecognized possibleValueSourceName in securityKeyType: " + name);
|
||||
@ -380,6 +398,23 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateAuthentication(QInstance qInstance)
|
||||
{
|
||||
QAuthenticationMetaData authentication = qInstance.getAuthentication();
|
||||
if(authentication != null)
|
||||
{
|
||||
if(authentication.getCustomizer() != null)
|
||||
{
|
||||
validateSimpleCodeReference("Instance Authentication meta data customizer ", authentication.getCustomizer(), QAuthenticationModuleCustomizerInterface.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -480,6 +515,11 @@ public class QInstanceValidator
|
||||
validateTableCustomizer(tableName, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if(table.getBackendDetails() != null)
|
||||
{
|
||||
table.getBackendDetails().validate(qInstance, table, this);
|
||||
}
|
||||
|
||||
validateTableAutomationDetails(qInstance, table);
|
||||
validateTableUniqueKeys(table);
|
||||
validateAssociatedScripts(table);
|
||||
@ -487,6 +527,11 @@ public class QInstanceValidator
|
||||
validateTableRecordSecurityLocks(qInstance, table);
|
||||
validateTableAssociations(qInstance, table);
|
||||
validateExposedJoins(qInstance, joinGraph, table);
|
||||
|
||||
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
|
||||
{
|
||||
supplementalTableMetaData.validate(qInstance, table, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -686,7 +731,7 @@ public class QInstanceValidator
|
||||
|
||||
String prefix = "Field " + fieldName + " in table " + tableName + " ";
|
||||
|
||||
ValueTooLongBehavior behavior = field.getBehavior(qInstance, ValueTooLongBehavior.class);
|
||||
ValueTooLongBehavior behavior = field.getBehaviorOrDefault(qInstance, ValueTooLongBehavior.class);
|
||||
if(behavior != null && !behavior.equals(ValueTooLongBehavior.PASS_THROUGH))
|
||||
{
|
||||
assertCondition(field.getMaxLength() != null, prefix + "specifies a ValueTooLongBehavior, but not a maxLength.");
|
||||
@ -1239,7 +1284,25 @@ public class QInstanceValidator
|
||||
{
|
||||
if(fieldMetaData.getDefaultValue() != null && fieldMetaData.getDefaultValue() instanceof QCodeReference codeReference)
|
||||
{
|
||||
validateSimpleCodeReference("Process " + processName + " backend step code reference: ", codeReference, BackendStep.class);
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// by default, assume that any process field which is a QCodeReference should be a reference to a BackendStep... //
|
||||
// but... allow a secondary field name to be set, to tell us what class to *actually* expect here... //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Class<?> expectedClass = BackendStep.class;
|
||||
try
|
||||
{
|
||||
Optional<QFieldMetaData> expectedTypeField = backendStepMetaData.getInputMetaData().getField(fieldMetaData.getName() + "_expectedType");
|
||||
if(expectedTypeField.isPresent() && expectedTypeField.get().getDefaultValue() != null)
|
||||
{
|
||||
expectedClass = Class.forName(ValueUtils.getValueAsString(expectedTypeField.get().getDefaultValue()));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
warn("Error loading expectedType for field [" + fieldMetaData.getName() + "] in process [" + processName + "]: " + e.getMessage());
|
||||
}
|
||||
|
||||
validateSimpleCodeReference("Process " + processName + " code reference: ", codeReference, expectedClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1515,12 +1578,53 @@ public class QInstanceValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// validate widgets //
|
||||
//////////////////////
|
||||
for(String widgetName : CollectionUtils.nonNullList(app.getWidgets()))
|
||||
{
|
||||
assertCondition(qInstance.getWidget(widgetName) != null, "App " + appName + " widget " + widgetName + " is not a recognized widget.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateWidgets(QInstance qInstance)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(qInstance.getWidgets()))
|
||||
{
|
||||
qInstance.getWidgets().forEach((widgetName, widget) ->
|
||||
{
|
||||
assertCondition(Objects.equals(widgetName, widget.getName()), "Inconsistent naming for widget: " + widgetName + "/" + widget.getName() + ".");
|
||||
|
||||
if(assertCondition(widget.getCodeReference() != null, "Missing codeReference for widget: " + widgetName))
|
||||
{
|
||||
validateSimpleCodeReference("Widget " + widgetName + " code reference: ", widget.getCodeReference(), AbstractWidgetRenderer.class);
|
||||
}
|
||||
|
||||
if(widget instanceof ParentWidgetMetaData parentWidgetMetaData)
|
||||
{
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(parentWidgetMetaData.getChildWidgetNameList()), "Missing child widgets for parent widget: " + widget.getName()))
|
||||
{
|
||||
for(String childWidgetName : parentWidgetMetaData.getChildWidgetNameList())
|
||||
{
|
||||
assertCondition(qInstance.getWidget(childWidgetName) != null, "Unrecognized child widget name [" + childWidgetName + "] in parent widget: " + widget.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -1760,20 +1864,6 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
interface UnsafeLambda
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Object to accumulate multiple audit-details to be recorded under a single
|
||||
** audit per-record, within a process step. Especially useful if/when the
|
||||
** process step spreads its work out through multiple classes.
|
||||
**
|
||||
** Pattern of usage looks like:
|
||||
**
|
||||
** <pre>
|
||||
** // declare as a field (or local) w/ message for the audit headers
|
||||
** private AuditDetailAccumulator auditDetailAccumulator = new AuditDetailAccumulator("Audit header message");
|
||||
**
|
||||
** // put into thread context
|
||||
** AuditDetailAccumulator.setInContext(auditDetailAccumulator);
|
||||
**
|
||||
** // add a detail message for a record
|
||||
** auditDetailAccumulator.addAuditDetail(tableName, record, "Detail message");
|
||||
**
|
||||
** // in another class, get the accumulator from context and safely add a detail message
|
||||
** AuditDetailAccumulator.getFromContext().ifPresent(ada -> ada.addAuditDetail(tableName, record, "More Details"));
|
||||
**
|
||||
** // at the end of a step run/runOnePage method, add the accumulated audit details to step output
|
||||
** auditDetailAccumulator.getAccumulatedAuditSingleInputs().forEach(runBackendStepOutput::addAuditSingleInput);
|
||||
** auditDetailAccumulator.clear();
|
||||
** </pre>
|
||||
*******************************************************************************/
|
||||
public class AuditDetailAccumulator implements Serializable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AuditDetailAccumulator.class);
|
||||
|
||||
private static final String objectKey = AuditDetailAccumulator.class.getSimpleName();
|
||||
|
||||
private String header;
|
||||
|
||||
private Map<TableNameAndPrimaryKey, AuditSingleInput> recordAuditInputMap = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AuditDetailAccumulator(String header)
|
||||
{
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setInContext()
|
||||
{
|
||||
QContext.setObject(objectKey, this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<AuditDetailAccumulator> getFromContext()
|
||||
{
|
||||
return QContext.getObject(objectKey, AuditDetailAccumulator.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addAuditDetail(String tableName, QRecordEntity entity, String message)
|
||||
{
|
||||
if(entity != null)
|
||||
{
|
||||
addAuditDetail(tableName, entity.toQRecord(), message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addAuditDetail(String tableName, QRecord record, String message)
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
Serializable primaryKey = record.getValue(table.getPrimaryKeyField());
|
||||
if(primaryKey == null)
|
||||
{
|
||||
LOG.info("Missing primary key in input record - audit detail message will not be recorded.", logPair("message", message));
|
||||
return;
|
||||
}
|
||||
|
||||
AuditSingleInput auditSingleInput = recordAuditInputMap.computeIfAbsent(new TableNameAndPrimaryKey(tableName, primaryKey), (key) -> new AuditSingleInput(table, record, header));
|
||||
auditSingleInput.addDetail(message);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Collection<AuditSingleInput> getAccumulatedAuditSingleInputs()
|
||||
{
|
||||
return (recordAuditInputMap.values());
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void clear()
|
||||
{
|
||||
recordAuditInputMap.clear();
|
||||
}
|
||||
|
||||
|
||||
private record TableNameAndPrimaryKey(String tableName, Serializable primaryKey) {}
|
||||
}
|
@ -41,7 +41,7 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
/*******************************************************************************
|
||||
** Input data to insert a single audit record (with optional child record)..
|
||||
*******************************************************************************/
|
||||
public class AuditSingleInput
|
||||
public class AuditSingleInput implements Serializable
|
||||
{
|
||||
private String auditTableName;
|
||||
private String auditUserName;
|
||||
|
@ -25,7 +25,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||
@ -42,7 +44,7 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);
|
||||
|
||||
private String fieldName;
|
||||
private String fieldName;
|
||||
private QCriteriaOperator operator;
|
||||
private List<Serializable> values;
|
||||
|
||||
@ -93,11 +95,37 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria(String fieldName, QCriteriaOperator operator, List<Serializable> values)
|
||||
@SuppressWarnings("unchecked")
|
||||
public QFilterCriteria(String fieldName, QCriteriaOperator operator, List<? extends Serializable> values)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.operator = operator;
|
||||
this.values = values == null ? new ArrayList<>() : values;
|
||||
this.values = values == null ? new ArrayList<>() : (List<Serializable>) values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public QFilterCriteria(String fieldName, QCriteriaOperator operator, Collection<? extends Serializable> values)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.operator = operator;
|
||||
|
||||
if(values == null)
|
||||
{
|
||||
this.values = new ArrayList<>();
|
||||
}
|
||||
else if(values instanceof List list)
|
||||
{
|
||||
this.values = list;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.values = new ArrayList<>(values);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -319,4 +347,37 @@ public class QFilterCriteria implements Serializable, Cloneable
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QFilterCriteria that = (QFilterCriteria) o;
|
||||
return Objects.equals(fieldName, that.fieldName) && operator == that.operator && Objects.equals(values, that.values) && Objects.equals(otherFieldName, that.otherFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(fieldName, operator, values, otherFieldName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -323,6 +324,18 @@ public class QQueryFilter implements Serializable, Cloneable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for adding a single subFilter
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QQueryFilter withSubFilter(QQueryFilter subFilter)
|
||||
{
|
||||
addSubFilter(subFilter);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -467,4 +480,36 @@ public class QQueryFilter implements Serializable, Cloneable
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QQueryFilter that = (QQueryFilter) o;
|
||||
return Objects.equals(criteria, that.criteria) && Objects.equals(orderBys, that.orderBys) && booleanOperator == that.booleanOperator && Objects.equals(subFilters, that.subFilters) && Objects.equals(skip, that.skip) && Objects.equals(limit, that.limit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(criteria, orderBys, booleanOperator, subFilters, skip, limit);
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ public class ReplaceInput extends AbstractTableActionInput
|
||||
private UniqueKey key;
|
||||
private List<QRecord> records;
|
||||
private QQueryFilter filter;
|
||||
private boolean performDeletes = true;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
|
||||
@ -207,4 +208,35 @@ public class ReplaceInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for performDeletes
|
||||
*******************************************************************************/
|
||||
public boolean getPerformDeletes()
|
||||
{
|
||||
return (this.performDeletes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for performDeletes
|
||||
*******************************************************************************/
|
||||
public void setPerformDeletes(boolean performDeletes)
|
||||
{
|
||||
this.performDeletes = performDeletes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for performDeletes
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withPerformDeletes(boolean performDeletes)
|
||||
{
|
||||
this.performDeletes = performDeletes;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -51,8 +51,10 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private Boolean areAllValuesBeingUpdatedTheSame = null;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
private boolean omitTriggeringAutomations = false;
|
||||
private boolean omitDmlAudit = false;
|
||||
private boolean omitModifyDateUpdate = false;
|
||||
private String auditContext = null;
|
||||
|
||||
|
||||
|
||||
@ -321,4 +323,66 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for omitTriggeringAutomations
|
||||
*******************************************************************************/
|
||||
public boolean getOmitTriggeringAutomations()
|
||||
{
|
||||
return (this.omitTriggeringAutomations);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for omitTriggeringAutomations
|
||||
*******************************************************************************/
|
||||
public void setOmitTriggeringAutomations(boolean omitTriggeringAutomations)
|
||||
{
|
||||
this.omitTriggeringAutomations = omitTriggeringAutomations;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for omitTriggeringAutomations
|
||||
*******************************************************************************/
|
||||
public UpdateInput withOmitTriggeringAutomations(boolean omitTriggeringAutomations)
|
||||
{
|
||||
this.omitTriggeringAutomations = omitTriggeringAutomations;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for omitModifyDateUpdate
|
||||
*******************************************************************************/
|
||||
public boolean getOmitModifyDateUpdate()
|
||||
{
|
||||
return (this.omitModifyDateUpdate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for omitModifyDateUpdate
|
||||
*******************************************************************************/
|
||||
public void setOmitModifyDateUpdate(boolean omitModifyDateUpdate)
|
||||
{
|
||||
this.omitModifyDateUpdate = omitModifyDateUpdate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for omitModifyDateUpdate
|
||||
*******************************************************************************/
|
||||
public UpdateInput withOmitModifyDateUpdate(boolean omitModifyDateUpdate)
|
||||
{
|
||||
this.omitModifyDateUpdate = omitModifyDateUpdate;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.savedfilters.SavedFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ public class TableTrigger extends QRecordEntity
|
||||
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME)
|
||||
private String tableName;
|
||||
|
||||
@QField(possibleValueSourceName = SavedFilter.TABLE_NAME)
|
||||
@QField(possibleValueSourceName = SavedView.TABLE_NAME)
|
||||
private Integer filterId;
|
||||
|
||||
@QField(possibleValueSourceName = Script.TABLE_NAME)
|
||||
|
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseValues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Data used to render a Composite Widget - e.g., a collection of blocks
|
||||
*******************************************************************************/
|
||||
public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidgetData, BaseValues, BaseSlots, BaseStyles>
|
||||
{
|
||||
private List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks = new ArrayList<>();
|
||||
|
||||
private Map<String, Serializable> styleOverrides = new HashMap<>();
|
||||
|
||||
private Layout layout;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum Layout
|
||||
{
|
||||
/////////////////////////////////////////////////////////////
|
||||
// note, these are used in QQQ FMD CompositeWidgetData.tsx //
|
||||
/////////////////////////////////////////////////////////////
|
||||
FLEX_ROW_WRAPPED,
|
||||
FLEX_ROW_SPACE_BETWEEN,
|
||||
TABLE_SUB_ROW_DETAILS,
|
||||
BADGES_WRAPPER
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "COMPOSITE";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for blocks
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<AbstractBlockWidgetData<?, ?, ?, ?>> getBlocks()
|
||||
{
|
||||
return blocks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for blocks
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBlocks(List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks)
|
||||
{
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for blocks
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CompositeWidgetData withBlocks(List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks)
|
||||
{
|
||||
this.blocks = blocks;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CompositeWidgetData withBlock(AbstractBlockWidgetData<?, ?, ?, ?> block)
|
||||
{
|
||||
addBlock(block);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addBlock(AbstractBlockWidgetData<?, ?, ?, ?> block)
|
||||
{
|
||||
if(this.blocks == null)
|
||||
{
|
||||
this.blocks = new ArrayList<>();
|
||||
}
|
||||
this.blocks.add(block);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for styleOverrides
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getStyleOverrides()
|
||||
{
|
||||
return (this.styleOverrides);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for styleOverrides
|
||||
*******************************************************************************/
|
||||
public void setStyleOverrides(Map<String, Serializable> styleOverrides)
|
||||
{
|
||||
this.styleOverrides = styleOverrides;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for styleOverrides
|
||||
*******************************************************************************/
|
||||
public CompositeWidgetData withStyleOverrides(Map<String, Serializable> styleOverrides)
|
||||
{
|
||||
this.styleOverrides = styleOverrides;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CompositeWidgetData withStyleOverride(String key, Serializable value)
|
||||
{
|
||||
addStyleOverride(key, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addStyleOverride(String key, Serializable value)
|
||||
{
|
||||
if(this.styleOverrides == null)
|
||||
{
|
||||
this.styleOverrides = new HashMap<>();
|
||||
}
|
||||
this.styleOverrides.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for layout
|
||||
*******************************************************************************/
|
||||
public Layout getLayout()
|
||||
{
|
||||
return (this.layout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for layout
|
||||
*******************************************************************************/
|
||||
public void setLayout(Layout layout)
|
||||
{
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for layout
|
||||
*******************************************************************************/
|
||||
public CompositeWidgetData withLayout(Layout layout)
|
||||
{
|
||||
this.layout = layout;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -35,6 +35,8 @@ public class ParentWidgetData extends QWidgetData
|
||||
private List<String> childWidgetNameList;
|
||||
private ParentWidgetMetaData.LayoutType layoutType = ParentWidgetMetaData.LayoutType.GRID;
|
||||
|
||||
private boolean isLabelPageTitle = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -121,4 +123,34 @@ public class ParentWidgetData extends QWidgetData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isLabelPageTitle
|
||||
*******************************************************************************/
|
||||
public boolean getIsLabelPageTitle()
|
||||
{
|
||||
return (this.isLabelPageTitle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isLabelPageTitle
|
||||
*******************************************************************************/
|
||||
public void setIsLabelPageTitle(boolean isLabelPageTitle)
|
||||
{
|
||||
this.isLabelPageTitle = isLabelPageTitle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isLabelPageTitle
|
||||
*******************************************************************************/
|
||||
public ParentWidgetData withIsLabelPageTitle(boolean isLabelPageTitle)
|
||||
{
|
||||
this.isLabelPageTitle = isLabelPageTitle;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -33,6 +34,7 @@ import java.util.Map;
|
||||
public abstract class QWidgetData
|
||||
{
|
||||
private String label;
|
||||
private String sublabel;
|
||||
private String footerHTML;
|
||||
private List<String> dropdownNameList;
|
||||
private List<String> dropdownLabelList;
|
||||
@ -47,6 +49,8 @@ public abstract class QWidgetData
|
||||
private List<List<Map<String, String>>> dropdownDataList;
|
||||
private String dropdownNeedsSelectedText;
|
||||
|
||||
private List<List<Serializable>> csvData;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -324,4 +328,65 @@ public abstract class QWidgetData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for csvData
|
||||
*******************************************************************************/
|
||||
public List<List<Serializable>> getCsvData()
|
||||
{
|
||||
return (this.csvData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for csvData
|
||||
*******************************************************************************/
|
||||
public void setCsvData(List<List<Serializable>> csvData)
|
||||
{
|
||||
this.csvData = csvData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for csvData
|
||||
*******************************************************************************/
|
||||
public QWidgetData withCsvData(List<List<Serializable>> csvData)
|
||||
{
|
||||
this.csvData = csvData;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sublabel
|
||||
*******************************************************************************/
|
||||
public String getSublabel()
|
||||
{
|
||||
return (this.sublabel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sublabel
|
||||
*******************************************************************************/
|
||||
public void setSublabel(String sublabel)
|
||||
{
|
||||
this.sublabel = sublabel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sublabel
|
||||
*******************************************************************************/
|
||||
public QWidgetData withSublabel(String sublabel)
|
||||
{
|
||||
this.sublabel = sublabel;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,19 +22,24 @@
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Model containing datastructure expected by frontend statistics widget
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class StatisticsData extends QWidgetData
|
||||
{
|
||||
private Number count;
|
||||
private String countFontSize;
|
||||
private String countURL;
|
||||
private Number percentageAmount;
|
||||
private String percentageLabel;
|
||||
private boolean isCurrency = false;
|
||||
private boolean increaseIsGood = true;
|
||||
private Serializable count;
|
||||
private String countFontSize;
|
||||
private String countURL;
|
||||
private String countContext;
|
||||
private Number percentageAmount;
|
||||
private String percentageLabel;
|
||||
private String percentageURL;
|
||||
private boolean isCurrency = false;
|
||||
private boolean increaseIsGood = true;
|
||||
|
||||
|
||||
|
||||
@ -50,7 +55,7 @@ public class StatisticsData extends QWidgetData
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public StatisticsData(Number count, Number percentageAmount, String percentageLabel)
|
||||
public StatisticsData(Serializable count, Number percentageAmount, String percentageLabel)
|
||||
{
|
||||
this.count = count;
|
||||
this.percentageLabel = percentageLabel;
|
||||
@ -142,7 +147,7 @@ public class StatisticsData extends QWidgetData
|
||||
** Getter for count
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Number getCount()
|
||||
public Serializable getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
@ -153,7 +158,7 @@ public class StatisticsData extends QWidgetData
|
||||
** Setter for count
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setCount(Number count)
|
||||
public void setCount(Serializable count)
|
||||
{
|
||||
this.count = count;
|
||||
}
|
||||
@ -164,7 +169,7 @@ public class StatisticsData extends QWidgetData
|
||||
** Fluent setter for count
|
||||
**
|
||||
*******************************************************************************/
|
||||
public StatisticsData withCount(Number count)
|
||||
public StatisticsData withCount(Serializable count)
|
||||
{
|
||||
this.count = count;
|
||||
return (this);
|
||||
@ -306,4 +311,66 @@ public class StatisticsData extends QWidgetData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for countContext
|
||||
*******************************************************************************/
|
||||
public String getCountContext()
|
||||
{
|
||||
return (this.countContext);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for countContext
|
||||
*******************************************************************************/
|
||||
public void setCountContext(String countContext)
|
||||
{
|
||||
this.countContext = countContext;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for countContext
|
||||
*******************************************************************************/
|
||||
public StatisticsData withCountContext(String countContext)
|
||||
{
|
||||
this.countContext = countContext;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for percentageURL
|
||||
*******************************************************************************/
|
||||
public String getPercentageURL()
|
||||
{
|
||||
return (this.percentageURL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for percentageURL
|
||||
*******************************************************************************/
|
||||
public void setPercentageURL(String percentageURL)
|
||||
{
|
||||
this.percentageURL = percentageURL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for percentageURL
|
||||
*******************************************************************************/
|
||||
public StatisticsData withPercentageURL(String percentageURL)
|
||||
{
|
||||
this.percentageURL = percentageURL;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ public enum WidgetType
|
||||
STEPPER("stepper"),
|
||||
TABLE("table"),
|
||||
USA_MAP("usaMap"),
|
||||
COMPOSITE("composite"),
|
||||
DATA_BAG_VIEWER("dataBagViewer"),
|
||||
SCRIPT_VIEWER("scriptViewer");
|
||||
|
||||
|
@ -0,0 +1,419 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.QWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base class for the data returned in rendering a block of a specific type.
|
||||
**
|
||||
** The type parameters define the structure of the block's data, and should
|
||||
** generally be defined along with a sub-class of this class, in a block-specific
|
||||
** sub-package.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractBlockWidgetData<
|
||||
T extends AbstractBlockWidgetData<T, V, S, SX>,
|
||||
V extends BlockValuesInterface,
|
||||
S extends BlockSlotsInterface,
|
||||
SX extends BlockStylesInterface> extends QWidgetData
|
||||
{
|
||||
private String blockId;
|
||||
|
||||
private BlockTooltip tooltip;
|
||||
private BlockLink link;
|
||||
|
||||
private Map<S, BlockTooltip> tooltipMap;
|
||||
private Map<S, BlockLink> linkMap;
|
||||
|
||||
private V values;
|
||||
private SX styles;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public final String getType()
|
||||
{
|
||||
return "block";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public abstract String getBlockTypeName();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withTooltip(S key, String value)
|
||||
{
|
||||
addTooltip(key, value);
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addTooltip(S key, String value)
|
||||
{
|
||||
if(this.tooltipMap == null)
|
||||
{
|
||||
this.tooltipMap = new HashMap<>();
|
||||
}
|
||||
this.tooltipMap.put(key, new BlockTooltip().withTitle(value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withTooltip(S key, BlockTooltip value)
|
||||
{
|
||||
addTooltip(key, value);
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addTooltip(S key, BlockTooltip value)
|
||||
{
|
||||
if(this.tooltipMap == null)
|
||||
{
|
||||
this.tooltipMap = new HashMap<>();
|
||||
}
|
||||
this.tooltipMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tooltipMap
|
||||
*******************************************************************************/
|
||||
public Map<S, BlockTooltip> getTooltipMap()
|
||||
{
|
||||
return (this.tooltipMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tooltipMap
|
||||
*******************************************************************************/
|
||||
public void setTooltipMap(Map<S, BlockTooltip> tooltipMap)
|
||||
{
|
||||
this.tooltipMap = tooltipMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tooltipMap
|
||||
*******************************************************************************/
|
||||
public T withTooltipMap(Map<S, BlockTooltip> tooltipMap)
|
||||
{
|
||||
this.tooltipMap = tooltipMap;
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tooltip
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BlockTooltip getTooltip()
|
||||
{
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tooltip
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTooltip(BlockTooltip tooltip)
|
||||
{
|
||||
this.tooltip = tooltip;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tooltip
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withTooltip(String tooltip)
|
||||
{
|
||||
this.tooltip = new BlockTooltip(tooltip);
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tooltip
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withTooltip(BlockTooltip tooltip)
|
||||
{
|
||||
this.tooltip = tooltip;
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withLink(S key, String value)
|
||||
{
|
||||
addLink(key, value);
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addLink(S key, String value)
|
||||
{
|
||||
if(this.linkMap == null)
|
||||
{
|
||||
this.linkMap = new HashMap<>();
|
||||
}
|
||||
this.linkMap.put(key, new BlockLink(value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withLink(S key, BlockLink value)
|
||||
{
|
||||
addLink(key, value);
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addLink(S key, BlockLink value)
|
||||
{
|
||||
if(this.linkMap == null)
|
||||
{
|
||||
this.linkMap = new HashMap<>();
|
||||
}
|
||||
this.linkMap.put(key, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for linkMap
|
||||
*******************************************************************************/
|
||||
public Map<S, BlockLink> getLinkMap()
|
||||
{
|
||||
return (this.linkMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for linkMap
|
||||
*******************************************************************************/
|
||||
public void setLinkMap(Map<S, BlockLink> linkMap)
|
||||
{
|
||||
this.linkMap = linkMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for linkMap
|
||||
*******************************************************************************/
|
||||
public T withLinkMap(Map<S, BlockLink> linkMap)
|
||||
{
|
||||
this.linkMap = linkMap;
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for link
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BlockLink getLink()
|
||||
{
|
||||
return link;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for link
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setLink(BlockLink link)
|
||||
{
|
||||
this.link = link;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for link
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withLink(String link)
|
||||
{
|
||||
this.link = new BlockLink(link);
|
||||
return (T) (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for link
|
||||
**
|
||||
*******************************************************************************/
|
||||
public T withLink(BlockLink link)
|
||||
{
|
||||
this.link = link;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for values
|
||||
*******************************************************************************/
|
||||
public V getValues()
|
||||
{
|
||||
return (this.values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for values
|
||||
*******************************************************************************/
|
||||
public void setValues(V values)
|
||||
{
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for values
|
||||
*******************************************************************************/
|
||||
public T withValues(V values)
|
||||
{
|
||||
this.values = values;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for styles
|
||||
*******************************************************************************/
|
||||
public SX getStyles()
|
||||
{
|
||||
return (this.styles);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for styles
|
||||
*******************************************************************************/
|
||||
public void setStyles(SX styles)
|
||||
{
|
||||
this.styles = styles;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for styles
|
||||
*******************************************************************************/
|
||||
public T withStyles(SX styles)
|
||||
{
|
||||
this.styles = styles;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for blockId
|
||||
*******************************************************************************/
|
||||
public String getBlockId()
|
||||
{
|
||||
return (this.blockId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for blockId
|
||||
*******************************************************************************/
|
||||
public void setBlockId(String blockId)
|
||||
{
|
||||
this.blockId = blockId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for blockId
|
||||
*******************************************************************************/
|
||||
public T withBlockId(String blockId)
|
||||
{
|
||||
this.blockId = blockId;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A link used within a (widget) block.
|
||||
**
|
||||
** Right now, just a href - but target is an obvious next-thing to add here.
|
||||
*******************************************************************************/
|
||||
public class BlockLink
|
||||
{
|
||||
private String href;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BlockLink()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BlockLink(String href)
|
||||
{
|
||||
this.href = href;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for href
|
||||
*******************************************************************************/
|
||||
public String getHref()
|
||||
{
|
||||
return (this.href);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for href
|
||||
*******************************************************************************/
|
||||
public void setHref(String href)
|
||||
{
|
||||
this.href = href;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for href
|
||||
*******************************************************************************/
|
||||
public BlockLink withHref(String href)
|
||||
{
|
||||
this.href = href;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** marker-interface for classes (enums, actually) used to define the "slots"
|
||||
** within a widget-block that can have links or tooltips applied to them.
|
||||
*******************************************************************************/
|
||||
public interface BlockSlotsInterface
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Marker interface for classes that define the "styles" that can be customized
|
||||
** within a particular widget-block type.
|
||||
*******************************************************************************/
|
||||
public interface BlockStylesInterface
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A tooltip used within a (widget) block.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BlockTooltip
|
||||
{
|
||||
private String title;
|
||||
private Placement placement = Placement.BOTTOM;
|
||||
|
||||
|
||||
|
||||
public enum Placement
|
||||
{BOTTOM, LEFT, RIGHT, TOP}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BlockTooltip()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public BlockTooltip(String title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for title
|
||||
*******************************************************************************/
|
||||
public String getTitle()
|
||||
{
|
||||
return (this.title);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for title
|
||||
*******************************************************************************/
|
||||
public void setTitle(String title)
|
||||
{
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for title
|
||||
*******************************************************************************/
|
||||
public BlockTooltip withTitle(String title)
|
||||
{
|
||||
this.title = title;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for placement
|
||||
*******************************************************************************/
|
||||
public Placement getPlacement()
|
||||
{
|
||||
return (this.placement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for placement
|
||||
*******************************************************************************/
|
||||
public void setPlacement(Placement placement)
|
||||
{
|
||||
this.placement = placement;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for placement
|
||||
*******************************************************************************/
|
||||
public BlockTooltip withPlacement(Placement placement)
|
||||
{
|
||||
this.placement = placement;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Marker interface for classes that define the values that can be returned for
|
||||
** a particular widget-block type.
|
||||
*******************************************************************************/
|
||||
public interface BlockValuesInterface
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum BaseSlots implements BlockSlotsInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BaseStyles implements BlockStylesInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BaseValues implements BlockValuesInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.bignumberblock;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BigNumberBlockData extends AbstractBlockWidgetData<BigNumberBlockData, BigNumberValues, BigNumberSlots, BigNumberStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "BIG_NUMBER";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.bignumberblock;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum BigNumberSlots implements BlockSlotsInterface
|
||||
{
|
||||
HEADING,
|
||||
NUMBER,
|
||||
CONTEXT
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.bignumberblock;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BigNumberStyles implements BlockStylesInterface
|
||||
{
|
||||
private String numberColor;
|
||||
private String width;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for numberColor
|
||||
*******************************************************************************/
|
||||
public String getNumberColor()
|
||||
{
|
||||
return (this.numberColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for numberColor
|
||||
*******************************************************************************/
|
||||
public void setNumberColor(String numberColor)
|
||||
{
|
||||
this.numberColor = numberColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for numberColor
|
||||
*******************************************************************************/
|
||||
public BigNumberStyles withNumberColor(String numberColor)
|
||||
{
|
||||
this.numberColor = numberColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for width
|
||||
*******************************************************************************/
|
||||
public String getWidth()
|
||||
{
|
||||
return (this.width);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for width
|
||||
*******************************************************************************/
|
||||
public void setWidth(String width)
|
||||
{
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for width
|
||||
*******************************************************************************/
|
||||
public BigNumberStyles withWidth(String width)
|
||||
{
|
||||
this.width = width;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.bignumberblock;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BigNumberValues implements BlockValuesInterface
|
||||
{
|
||||
private String heading;
|
||||
private Serializable number;
|
||||
private String context;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for heading
|
||||
*******************************************************************************/
|
||||
public String getHeading()
|
||||
{
|
||||
return (this.heading);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for heading
|
||||
*******************************************************************************/
|
||||
public void setHeading(String heading)
|
||||
{
|
||||
this.heading = heading;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for heading
|
||||
*******************************************************************************/
|
||||
public BigNumberValues withHeading(String heading)
|
||||
{
|
||||
this.heading = heading;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for number
|
||||
*******************************************************************************/
|
||||
public Serializable getNumber()
|
||||
{
|
||||
return (this.number);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for number
|
||||
*******************************************************************************/
|
||||
public void setNumber(Serializable number)
|
||||
{
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for number
|
||||
*******************************************************************************/
|
||||
public BigNumberValues withNumber(Serializable number)
|
||||
{
|
||||
this.number = number;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for context
|
||||
*******************************************************************************/
|
||||
public String getContext()
|
||||
{
|
||||
return (this.context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for context
|
||||
*******************************************************************************/
|
||||
public void setContext(String context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for context
|
||||
*******************************************************************************/
|
||||
public BigNumberValues withContext(String context)
|
||||
{
|
||||
this.context = context;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.divider;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseValues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class DividerBlockData extends AbstractBlockWidgetData<DividerBlockData, BaseValues, BaseSlots, BaseStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "DIVIDER";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.numbericonbadge;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class NumberIconBadgeBlockData extends AbstractBlockWidgetData<NumberIconBadgeBlockData, NumberIconBadgeValues, NumberIconBadgeSlots, NumberIconBadgeStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "NUMBER_ICON_BADGE";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.numbericonbadge;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum NumberIconBadgeSlots implements BlockSlotsInterface
|
||||
{
|
||||
NUMBER,
|
||||
ICON
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.numbericonbadge;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class NumberIconBadgeStyles implements BlockStylesInterface
|
||||
{
|
||||
private String color;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for color
|
||||
*******************************************************************************/
|
||||
public String getColor()
|
||||
{
|
||||
return (this.color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for color
|
||||
*******************************************************************************/
|
||||
public void setColor(String color)
|
||||
{
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for color
|
||||
*******************************************************************************/
|
||||
public NumberIconBadgeStyles withColor(String color)
|
||||
{
|
||||
this.color = color;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.numbericonbadge;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class NumberIconBadgeValues implements BlockValuesInterface
|
||||
{
|
||||
private Serializable number;
|
||||
private String iconName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for number
|
||||
*******************************************************************************/
|
||||
public Serializable getNumber()
|
||||
{
|
||||
return (this.number);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for number
|
||||
*******************************************************************************/
|
||||
public void setNumber(Serializable number)
|
||||
{
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for number
|
||||
*******************************************************************************/
|
||||
public NumberIconBadgeValues withNumber(Serializable number)
|
||||
{
|
||||
this.number = number;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for iconName
|
||||
*******************************************************************************/
|
||||
public String getIconName()
|
||||
{
|
||||
return (this.iconName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for iconName
|
||||
*******************************************************************************/
|
||||
public void setIconName(String iconName)
|
||||
{
|
||||
this.iconName = iconName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for iconName
|
||||
*******************************************************************************/
|
||||
public NumberIconBadgeValues withIconName(String iconName)
|
||||
{
|
||||
this.iconName = iconName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.progressbar;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ProgressBarBlockData extends AbstractBlockWidgetData<ProgressBarBlockData, ProgressBarValues, ProgressBarSlots, ProgressBarStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "PROGRESS_BAR";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.progressbar;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum ProgressBarSlots implements BlockSlotsInterface
|
||||
{
|
||||
HEADING,
|
||||
BAR,
|
||||
VALUE
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.progressbar;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ProgressBarStyles implements BlockStylesInterface
|
||||
{
|
||||
private String barColor;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for barColor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getBarColor()
|
||||
{
|
||||
return barColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for barColor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBarColor(String barColor)
|
||||
{
|
||||
this.barColor = barColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for barColor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProgressBarStyles withBarColor(String barColor)
|
||||
{
|
||||
this.barColor = barColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.progressbar;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ProgressBarValues implements BlockValuesInterface
|
||||
{
|
||||
private String heading;
|
||||
private BigDecimal percent;
|
||||
private Serializable value;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for heading
|
||||
*******************************************************************************/
|
||||
public String getHeading()
|
||||
{
|
||||
return (this.heading);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for heading
|
||||
*******************************************************************************/
|
||||
public void setHeading(String heading)
|
||||
{
|
||||
this.heading = heading;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for heading
|
||||
*******************************************************************************/
|
||||
public ProgressBarValues withHeading(String heading)
|
||||
{
|
||||
this.heading = heading;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for percent
|
||||
*******************************************************************************/
|
||||
public BigDecimal getPercent()
|
||||
{
|
||||
return (this.percent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for percent
|
||||
*******************************************************************************/
|
||||
public void setPercent(BigDecimal percent)
|
||||
{
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for percent
|
||||
*******************************************************************************/
|
||||
public ProgressBarValues withPercent(BigDecimal percent)
|
||||
{
|
||||
this.percent = percent;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for value
|
||||
*******************************************************************************/
|
||||
public Serializable getValue()
|
||||
{
|
||||
return (this.value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for value
|
||||
*******************************************************************************/
|
||||
public void setValue(Serializable value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for value
|
||||
*******************************************************************************/
|
||||
public ProgressBarValues withValue(Serializable value)
|
||||
{
|
||||
this.value = value;
|
||||
return (this);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.tablesubrowdetailrow;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TableSubRowDetailRowBlockData extends AbstractBlockWidgetData<TableSubRowDetailRowBlockData, TableSubRowDetailRowValues, TableSubRowDetailRowSlots, TableSubRowDetailRowStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "TABLE_SUB_ROW_DETAIL_ROW";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.tablesubrowdetailrow;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum TableSubRowDetailRowSlots implements BlockSlotsInterface
|
||||
{
|
||||
LABEL,
|
||||
VALUE
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.tablesubrowdetailrow;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TableSubRowDetailRowStyles implements BlockStylesInterface
|
||||
{
|
||||
private String labelColor;
|
||||
private String valueColor;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for labelColor
|
||||
*******************************************************************************/
|
||||
public String getLabelColor()
|
||||
{
|
||||
return (this.labelColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for labelColor
|
||||
*******************************************************************************/
|
||||
public void setLabelColor(String labelColor)
|
||||
{
|
||||
this.labelColor = labelColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for labelColor
|
||||
*******************************************************************************/
|
||||
public TableSubRowDetailRowStyles withLabelColor(String labelColor)
|
||||
{
|
||||
this.labelColor = labelColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for valueColor
|
||||
*******************************************************************************/
|
||||
public String getValueColor()
|
||||
{
|
||||
return (this.valueColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for valueColor
|
||||
*******************************************************************************/
|
||||
public void setValueColor(String valueColor)
|
||||
{
|
||||
this.valueColor = valueColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for valueColor
|
||||
*******************************************************************************/
|
||||
public TableSubRowDetailRowStyles withValueColor(String valueColor)
|
||||
{
|
||||
this.valueColor = valueColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.tablesubrowdetailrow;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TableSubRowDetailRowValues implements BlockValuesInterface
|
||||
{
|
||||
private String label;
|
||||
private Serializable value;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableSubRowDetailRowValues()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableSubRowDetailRowValues(String label, Serializable value)
|
||||
{
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for label
|
||||
*******************************************************************************/
|
||||
public String getLabel()
|
||||
{
|
||||
return (this.label);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for label
|
||||
*******************************************************************************/
|
||||
public void setLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for label
|
||||
*******************************************************************************/
|
||||
public TableSubRowDetailRowValues withLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for value
|
||||
*******************************************************************************/
|
||||
public Serializable getValue()
|
||||
{
|
||||
return (this.value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for value
|
||||
*******************************************************************************/
|
||||
public void setValue(Serializable value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for value
|
||||
*******************************************************************************/
|
||||
public TableSubRowDetailRowValues withValue(Serializable value)
|
||||
{
|
||||
this.value = value;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TextBlockData extends AbstractBlockWidgetData<TextBlockData, TextValues, TextSlots, TextStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "TEXT";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum TextSlots implements BlockSlotsInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TextStyles implements BlockStylesInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TextValues implements BlockValuesInterface
|
||||
{
|
||||
private String text;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TextValues()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TextValues(String text)
|
||||
{
|
||||
setText(text);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for text
|
||||
*******************************************************************************/
|
||||
public String getText()
|
||||
{
|
||||
return (this.text);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for text
|
||||
*******************************************************************************/
|
||||
public void setText(String text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for text
|
||||
*******************************************************************************/
|
||||
public TextValues withText(String text)
|
||||
{
|
||||
this.text = text;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.upordownnumber;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class UpOrDownNumberBlockData extends AbstractBlockWidgetData<UpOrDownNumberBlockData, UpOrDownNumberValues, UpOrDownNumberSlots, UpOrDownNumberStyles>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getBlockTypeName()
|
||||
{
|
||||
return "UP_OR_DOWN_NUMBER";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.upordownnumber;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockSlotsInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum UpOrDownNumberSlots implements BlockSlotsInterface
|
||||
{
|
||||
NUMBER,
|
||||
CONTEXT
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.upordownnumber;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class UpOrDownNumberStyles implements BlockStylesInterface
|
||||
{
|
||||
private String colorOverride;
|
||||
private boolean isStacked = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for colorOverride
|
||||
*******************************************************************************/
|
||||
public String getColorOverride()
|
||||
{
|
||||
return (this.colorOverride);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for colorOverride
|
||||
*******************************************************************************/
|
||||
public void setColorOverride(String colorOverride)
|
||||
{
|
||||
this.colorOverride = colorOverride;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for colorOverride
|
||||
*******************************************************************************/
|
||||
public UpOrDownNumberStyles withColorOverride(String colorOverride)
|
||||
{
|
||||
this.colorOverride = colorOverride;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isStacked
|
||||
*******************************************************************************/
|
||||
public boolean getIsStacked()
|
||||
{
|
||||
return (this.isStacked);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isStacked
|
||||
*******************************************************************************/
|
||||
public void setIsStacked(boolean isStacked)
|
||||
{
|
||||
this.isStacked = isStacked;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isStacked
|
||||
*******************************************************************************/
|
||||
public UpOrDownNumberStyles withIsStacked(boolean isStacked)
|
||||
{
|
||||
this.isStacked = isStacked;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.upordownnumber;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class UpOrDownNumberValues implements BlockValuesInterface
|
||||
{
|
||||
private boolean isUp = false;
|
||||
private boolean isGood = false;
|
||||
private Serializable number;
|
||||
private String context;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isUp
|
||||
*******************************************************************************/
|
||||
public boolean getIsUp()
|
||||
{
|
||||
return (this.isUp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isUp
|
||||
*******************************************************************************/
|
||||
public void setIsUp(boolean isUp)
|
||||
{
|
||||
this.isUp = isUp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isUp
|
||||
*******************************************************************************/
|
||||
public UpOrDownNumberValues withIsUp(boolean isUp)
|
||||
{
|
||||
this.isUp = isUp;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for isGood
|
||||
*******************************************************************************/
|
||||
public boolean getIsGood()
|
||||
{
|
||||
return (this.isGood);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for isGood
|
||||
*******************************************************************************/
|
||||
public void setIsGood(boolean isGood)
|
||||
{
|
||||
this.isGood = isGood;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for isGood
|
||||
*******************************************************************************/
|
||||
public UpOrDownNumberValues withIsGood(boolean isGood)
|
||||
{
|
||||
this.isGood = isGood;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for number
|
||||
*******************************************************************************/
|
||||
public Serializable getNumber()
|
||||
{
|
||||
return (this.number);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for number
|
||||
*******************************************************************************/
|
||||
public void setNumber(Serializable number)
|
||||
{
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for number
|
||||
*******************************************************************************/
|
||||
public UpOrDownNumberValues withNumber(Serializable number)
|
||||
{
|
||||
this.number = number;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for context
|
||||
*******************************************************************************/
|
||||
public String getContext()
|
||||
{
|
||||
return (this.context);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for context
|
||||
*******************************************************************************/
|
||||
public void setContext(String context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for context
|
||||
*******************************************************************************/
|
||||
public UpOrDownNumberValues withContext(String context)
|
||||
{
|
||||
this.context = context;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -63,6 +63,11 @@ public @interface QField
|
||||
*******************************************************************************/
|
||||
boolean isHidden() default false;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String defaultValue() default "";
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -32,6 +32,7 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -108,7 +109,7 @@ public class QRecord implements Serializable
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Copy constructor.
|
||||
** Copy constructor. Makes a deep clone.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord(QRecord record)
|
||||
@ -120,10 +121,10 @@ public class QRecord implements Serializable
|
||||
this.displayValues = deepCopySimpleMap(record.displayValues);
|
||||
this.backendDetails = deepCopySimpleMap(record.backendDetails);
|
||||
|
||||
this.associatedRecords = deepCopyAssociatedRecords(record.associatedRecords);
|
||||
|
||||
this.errors = record.errors == null ? null : new ArrayList<>(record.errors);
|
||||
this.warnings = record.warnings == null ? null : new ArrayList<>(record.warnings);
|
||||
|
||||
this.associatedRecords = deepCopyAssociatedRecords(record.associatedRecords);
|
||||
}
|
||||
|
||||
|
||||
@ -143,17 +144,17 @@ public class QRecord implements Serializable
|
||||
** todo - move to a cloning utils maybe?
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
private <K, V> Map<K, V> deepCopySimpleMap(Map<K, V> map)
|
||||
private <V extends Serializable> Map<String, V> deepCopySimpleMap(Map<String, V> map)
|
||||
{
|
||||
if(map == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
Map<K, V> clone = new LinkedHashMap<>();
|
||||
for(Map.Entry<K, V> entry : map.entrySet())
|
||||
Map<String, V> clone = new LinkedHashMap<>();
|
||||
for(Map.Entry<String, V> entry : map.entrySet())
|
||||
{
|
||||
V value = entry.getValue();
|
||||
Serializable value = entry.getValue();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// not sure from where/how java.sql.Date objects are getting in here... //
|
||||
@ -167,15 +168,32 @@ public class QRecord implements Serializable
|
||||
ArrayList<?> cloneList = new ArrayList<>(arrayList);
|
||||
clone.put(entry.getKey(), (V) cloneList);
|
||||
}
|
||||
else if(entry.getValue() instanceof Serializable serializableValue)
|
||||
else if(entry.getValue() instanceof LinkedList<?> linkedList)
|
||||
{
|
||||
LOG.info("Non-primitive serializable value in QRecord - calling SerializationUtils.clone...", logPair("key", entry.getKey()), logPair("type", value.getClass()));
|
||||
clone.put(entry.getKey(), (V) SerializationUtils.clone(serializableValue));
|
||||
LinkedList<?> cloneList = new LinkedList<>(linkedList);
|
||||
clone.put(entry.getKey(), (V) cloneList);
|
||||
}
|
||||
else if(entry.getValue() instanceof LinkedHashMap<?, ?> linkedHashMap)
|
||||
{
|
||||
LinkedHashMap<?, ?> cloneMap = new LinkedHashMap<>(linkedHashMap);
|
||||
clone.put(entry.getKey(), (V) cloneMap);
|
||||
}
|
||||
else if(entry.getValue() instanceof HashMap<?, ?> hashMap)
|
||||
{
|
||||
HashMap<?, ?> cloneMap = new HashMap<>(hashMap);
|
||||
clone.put(entry.getKey(), (V) cloneMap);
|
||||
}
|
||||
else if(entry.getValue() instanceof QRecord otherQRecord)
|
||||
{
|
||||
clone.put(entry.getKey(), (V) new QRecord(otherQRecord));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Non-serializable value in QRecord...", logPair("key", entry.getKey()), logPair("type", value.getClass()));
|
||||
clone.put(entry.getKey(), entry.getValue());
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// we know entry is serializable at this point, based on type param's bound //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
LOG.debug("Non-primitive serializable value in QRecord - calling SerializationUtils.clone...", logPair("key", entry.getKey()), logPair("type", value.getClass()));
|
||||
clone.put(entry.getKey(), (V) SerializationUtils.clone(entry.getValue()));
|
||||
}
|
||||
}
|
||||
return (clone);
|
||||
|
@ -29,6 +29,7 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import com.google.common.reflect.ClassPath;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
@ -50,13 +51,22 @@ public class MetaDataProducerHelper
|
||||
**
|
||||
** Note - they'll be sorted by the sortOrder they provide.
|
||||
*******************************************************************************/
|
||||
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) throws IOException
|
||||
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName) throws QException
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// find all the meta data producer classes in (and under) the package //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
List<Class<?>> classesInPackage = getClassesInPackage(packageName);
|
||||
List<MetaDataProducerInterface<?>> producers = new ArrayList<>();
|
||||
List<Class<?>> classesInPackage;
|
||||
try
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// find all the meta data producer classes in (and under) the package //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
classesInPackage = getClassesInPackage(packageName);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting classes in package [" + packageName + "]", e));
|
||||
}
|
||||
List<MetaDataProducerInterface<?>> producers = new ArrayList<>();
|
||||
|
||||
for(Class<?> aClass : classesInPackage)
|
||||
{
|
||||
try
|
||||
@ -95,6 +105,7 @@ public class MetaDataProducerHelper
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// sort them by sort order, then by the type that they return - specifically - doing apps //
|
||||
// after all other types (as apps often try to get other types from the instance) //
|
||||
// also - do backends earlier than others (e.g., tables may expect backends to exist) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
producers.sort(Comparator
|
||||
.comparing((MetaDataProducerInterface<?> p) -> p.getSortOrder())
|
||||
@ -105,11 +116,15 @@ public class MetaDataProducerHelper
|
||||
Class<?> outputType = p.getClass().getMethod("produce", QInstance.class).getReturnType();
|
||||
if(outputType.equals(QAppMetaData.class))
|
||||
{
|
||||
return (1);
|
||||
return (2);
|
||||
}
|
||||
else if(outputType.equals(QBackendMetaData.class))
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -746,12 +746,22 @@ public class QInstance
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hasBeenValidated
|
||||
** If pass a QInstanceValidationKey (which can only be instantiated by the validator),
|
||||
** then the hasBeenValidated field will be set to true.
|
||||
**
|
||||
** Else, if passed a null, hasBeenValidated will be reset to false - e.g., to
|
||||
** re-trigger validation (can be useful in tests).
|
||||
*******************************************************************************/
|
||||
public void setHasBeenValidated(QInstanceValidationKey key)
|
||||
{
|
||||
this.hasBeenValidated = true;
|
||||
if(key == null)
|
||||
{
|
||||
this.hasBeenValidated = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.hasBeenValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1042,10 +1052,16 @@ public class QInstance
|
||||
for(QSecurityKeyType securityKeyType : CollectionUtils.nonNullMap(getSecurityKeyTypes()).values())
|
||||
{
|
||||
rs.add(securityKeyType.getName());
|
||||
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
||||
{
|
||||
rs.add(securityKeyType.getAllAccessKeyName());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(securityKeyType.getNullValueBehaviorKeyName()))
|
||||
{
|
||||
rs.add(securityKeyType.getNullValueBehaviorKeyName());
|
||||
}
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
@ -1198,4 +1214,14 @@ public class QInstance
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void add(TopLevelMetaDataInterface metaData)
|
||||
{
|
||||
metaData.addSelfToInstance(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import com.fasterxml.jackson.annotation.JsonFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -43,6 +44,7 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
|
||||
@JsonFilter("secretsFilter")
|
||||
private Map<String, String> values;
|
||||
|
||||
private QCodeReference customizer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -192,4 +194,35 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
|
||||
qInstance.setAuthentication(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for customizer
|
||||
*******************************************************************************/
|
||||
public QCodeReference getCustomizer()
|
||||
{
|
||||
return (this.customizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for customizer
|
||||
*******************************************************************************/
|
||||
public void setCustomizer(QCodeReference customizer)
|
||||
{
|
||||
this.customizer = customizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for customizer
|
||||
*******************************************************************************/
|
||||
public QAuthenticationMetaData withCustomizer(QCodeReference customizer)
|
||||
{
|
||||
this.customizer = customizer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,9 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
|
||||
@ -55,10 +57,12 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
|
||||
private boolean storeDropdownSelections;
|
||||
|
||||
private boolean showReloadButton = true;
|
||||
private boolean showExportButton = true;
|
||||
private boolean showExportButton = false;
|
||||
|
||||
protected Map<String, QIcon> icons;
|
||||
|
||||
protected Map<String, QHelpContent> helpContent;
|
||||
|
||||
protected Map<String, Serializable> defaultValues = new LinkedHashMap<>();
|
||||
|
||||
|
||||
@ -217,6 +221,17 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// originally, showExportButton defaulted to true, and only a few frontend components knew how to render it. //
|
||||
// but, with the advent of csvData that any widget type can export, then the generic frontend widget code //
|
||||
// became aware of the export button, so we wanted to flip the default for showExportButton to false, but //
|
||||
// still have it by-default be true for these 2 types //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(WidgetType.TABLE.getType().equals(type) || WidgetType.CHILD_RECORD_LIST.getType().equals(type))
|
||||
{
|
||||
setShowExportButton(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -227,7 +242,7 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
|
||||
*******************************************************************************/
|
||||
public QWidgetMetaData withType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
setType(type);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -675,4 +690,35 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for helpContent
|
||||
*******************************************************************************/
|
||||
public Map<String, QHelpContent> getHelpContent()
|
||||
{
|
||||
return (this.helpContent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for helpContent
|
||||
*******************************************************************************/
|
||||
public void setHelpContent(Map<String, QHelpContent> helpContent)
|
||||
{
|
||||
this.helpContent = helpContent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for helpContent
|
||||
*******************************************************************************/
|
||||
public QWidgetMetaData withHelpContent(Map<String, QHelpContent> helpContent)
|
||||
{
|
||||
this.helpContent = helpContent;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -25,9 +25,11 @@ package com.kingsrook.qqq.backend.core.model.metadata.dashboard;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
||||
|
||||
@ -38,6 +40,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRule
|
||||
*******************************************************************************/
|
||||
public interface QWidgetMetaDataInterface extends MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(QWidgetMetaDataInterface.class);
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
@ -228,6 +232,23 @@ public interface QWidgetMetaDataInterface extends MetaDataWithPermissionRules, T
|
||||
return (null);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default Map<String, QHelpContent> getHelpContent()
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default void setHelpContent(Map<String, QHelpContent> helpContent)
|
||||
{
|
||||
LOG.debug("Setting help content in a widgetMetaData type that doesn't support it (because it didn't override the getter/setter)");
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -51,6 +51,7 @@ public class HtmlWrapper implements Serializable
|
||||
public static final String STYLE_INDENT_2 = "padding-left: 2rem; ";
|
||||
public static final String STYLE_FLOAT_RIGHT = "float: right; ";
|
||||
public static final String STYLE_RED = "color: red; ";
|
||||
public static final String STYLE_YELLOW = "color: #bfb743; ";
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.utils.CollectionUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Field behavior that sets a default value for a field dynamically.
|
||||
** e.g., create-date fields get set to 'now' on insert.
|
||||
** e.g., modify-date fields get set to 'now' on insert and on update.
|
||||
*******************************************************************************/
|
||||
public enum DynamicDefaultValueBehavior implements FieldBehavior<DynamicDefaultValueBehavior>
|
||||
{
|
||||
CREATE_DATE,
|
||||
MODIFY_DATE,
|
||||
NONE;
|
||||
|
||||
private static final QLogger LOG = QLogger.getLogger(ValueTooLongBehavior.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public DynamicDefaultValueBehavior getDefault()
|
||||
{
|
||||
return (NONE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field, Set<FieldBehavior<?>> behaviorsToOmit)
|
||||
{
|
||||
if(this.equals(NONE))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(behaviorsToOmit != null && behaviorsToOmit.contains(this))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(this)
|
||||
{
|
||||
case CREATE_DATE -> applyCreateDate(action, recordList, table, field);
|
||||
case MODIFY_DATE -> applyModifyDate(action, recordList, table, field);
|
||||
default -> throw new IllegalStateException("Unexpected enum value: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void applyCreateDate(ValueBehaviorApplier.Action action, List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
if(!ValueBehaviorApplier.Action.INSERT.equals(action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setCreateDateOrModifyDateOnList(recordList, table, field);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void applyModifyDate(ValueBehaviorApplier.Action action, List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// check both of these (even though they're the only 2 values at the time of this writing), just in case more enum values are added in the future //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(!ValueBehaviorApplier.Action.INSERT.equals(action) && !ValueBehaviorApplier.Action.UPDATE.equals(action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setCreateDateOrModifyDateOnList(recordList, table, field);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void setCreateDateOrModifyDateOnList(List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
String fieldName = field.getName();
|
||||
Serializable value = getNow(table, field);
|
||||
|
||||
for(QRecord record : CollectionUtils.nonNullList(recordList))
|
||||
{
|
||||
record.setValue(fieldName, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Serializable getNow(QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
if(QFieldType.DATE_TIME.equals(field.getType()))
|
||||
{
|
||||
return (Instant.now());
|
||||
}
|
||||
else if(QFieldType.DATE.equals(field.getType()))
|
||||
{
|
||||
return (LocalDate.now());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Request to apply a " + this.name() + " DynamicDefaultValueBehavior to a non-date or date-time field", logPair("table", table.getName()), logPair("field", field.getName()));
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void noop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -22,10 +22,42 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for (expected to be?) enums which define behaviors that get applied
|
||||
** to fields.
|
||||
**
|
||||
** At the present, these behaviors get applied before a field is stored (insert
|
||||
** or update), through the ValueBehaviorApplier class.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface FieldBehavior
|
||||
public interface FieldBehavior<T extends FieldBehavior<T>>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** In case a behavior of this type wasn't set on the field, what should the
|
||||
** default of this type be?
|
||||
*******************************************************************************/
|
||||
T getDefault();
|
||||
|
||||
/*******************************************************************************
|
||||
** Apply this behavior to a list of records
|
||||
*******************************************************************************/
|
||||
void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field, Set<FieldBehavior<?>> behaviorsToOmit);
|
||||
|
||||
/*******************************************************************************
|
||||
** control if multiple behaviors of this type should be allowed together on a field.
|
||||
*******************************************************************************/
|
||||
default boolean allowMultipleBehaviorsOfThisType()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.github.hervian.reflection.Fun;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
@ -44,6 +45,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -52,6 +55,8 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class QFieldMetaData implements Cloneable
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFieldMetaData.class);
|
||||
|
||||
private String name;
|
||||
private String label;
|
||||
private String backendName;
|
||||
@ -73,8 +78,8 @@ public class QFieldMetaData implements Cloneable
|
||||
private String possibleValueSourceName;
|
||||
private QQueryFilter possibleValueSourceFilter;
|
||||
|
||||
private Integer maxLength;
|
||||
private Set<FieldBehavior> behaviors;
|
||||
private Integer maxLength;
|
||||
private Set<FieldBehavior<?>> behaviors;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// w/ longer-term vision for FieldBehaviors //
|
||||
@ -220,6 +225,11 @@ public class QFieldMetaData implements Cloneable
|
||||
{
|
||||
withBehavior(fieldAnnotation.valueTooLongBehavior());
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(fieldAnnotation.defaultValue()))
|
||||
{
|
||||
ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
@ -674,7 +684,7 @@ public class QFieldMetaData implements Cloneable
|
||||
** Getter for behaviors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Set<FieldBehavior> getBehaviors()
|
||||
public Set<FieldBehavior<?>> getBehaviors()
|
||||
{
|
||||
return behaviors;
|
||||
}
|
||||
@ -682,11 +692,12 @@ public class QFieldMetaData implements Cloneable
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Get the FieldBehavior object of a given behaviorType (class) - but - if one
|
||||
** isn't set, then use the default from that type.
|
||||
*******************************************************************************/
|
||||
public <T extends FieldBehavior> T getBehavior(QInstance instance, Class<T> behaviorType)
|
||||
public <T extends FieldBehavior<T>> T getBehaviorOrDefault(QInstance instance, Class<T> behaviorType)
|
||||
{
|
||||
for(FieldBehavior fieldBehavior : CollectionUtils.nonNullCollection(behaviors))
|
||||
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(behaviors))
|
||||
{
|
||||
if(behaviorType.isInstance(fieldBehavior))
|
||||
{
|
||||
@ -701,9 +712,33 @@ public class QFieldMetaData implements Cloneable
|
||||
///////////////////////////////////////////
|
||||
// return default behavior for this type //
|
||||
///////////////////////////////////////////
|
||||
if(behaviorType.equals(ValueTooLongBehavior.class))
|
||||
if(behaviorType.isEnum())
|
||||
{
|
||||
return behaviorType.cast(ValueTooLongBehavior.getDefault());
|
||||
return (behaviorType.getEnumConstants()[0].getDefault());
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the FieldBehavior object of a given behaviorType (class) - and if one
|
||||
** isn't set, then return null.
|
||||
*******************************************************************************/
|
||||
public <T extends FieldBehavior<T>> T getBehaviorOnlyIfSet(Class<T> behaviorType)
|
||||
{
|
||||
if(behaviors == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(behaviors))
|
||||
{
|
||||
if(behaviorType.isInstance(fieldBehavior))
|
||||
{
|
||||
return (behaviorType.cast(fieldBehavior));
|
||||
}
|
||||
}
|
||||
|
||||
return (null);
|
||||
@ -715,7 +750,7 @@ public class QFieldMetaData implements Cloneable
|
||||
** Setter for behaviors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBehaviors(Set<FieldBehavior> behaviors)
|
||||
public void setBehaviors(Set<FieldBehavior<?>> behaviors)
|
||||
{
|
||||
this.behaviors = behaviors;
|
||||
}
|
||||
@ -726,7 +761,7 @@ public class QFieldMetaData implements Cloneable
|
||||
** Fluent setter for behaviors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFieldMetaData withBehaviors(Set<FieldBehavior> behaviors)
|
||||
public QFieldMetaData withBehaviors(Set<FieldBehavior<?>> behaviors)
|
||||
{
|
||||
this.behaviors = behaviors;
|
||||
return (this);
|
||||
@ -738,12 +773,30 @@ public class QFieldMetaData implements Cloneable
|
||||
** Fluent setter for behaviors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFieldMetaData withBehavior(FieldBehavior behavior)
|
||||
public QFieldMetaData withBehavior(FieldBehavior<?> behavior)
|
||||
{
|
||||
if(behavior == null)
|
||||
{
|
||||
LOG.debug("Skipping request to add null behavior", logPair("fieldName", getName()));
|
||||
return (this);
|
||||
}
|
||||
|
||||
if(behaviors == null)
|
||||
{
|
||||
behaviors = new HashSet<>();
|
||||
}
|
||||
|
||||
if(!behavior.allowMultipleBehaviorsOfThisType())
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
FieldBehavior<?> existingBehaviorOfThisType = getBehaviorOnlyIfSet(behavior.getClass());
|
||||
if(existingBehaviorOfThisType != null)
|
||||
{
|
||||
LOG.debug("Replacing a field behavior", logPair("fieldName", getName()), logPair("oldBehavior", existingBehaviorOfThisType), logPair("newBehavior", behavior));
|
||||
this.behaviors.remove(existingBehaviorOfThisType);
|
||||
}
|
||||
}
|
||||
|
||||
this.behaviors.add(behavior);
|
||||
return (this);
|
||||
}
|
||||
|
@ -22,23 +22,90 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.statusmessages.BadInputStatusMessage;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Behaviors for string fields, if their value is too long.
|
||||
**
|
||||
** Note: This was the first implementation of a FieldBehavior, so its test
|
||||
** coverage is provided in ValueBehaviorApplierTest.
|
||||
*******************************************************************************/
|
||||
public enum ValueTooLongBehavior implements FieldBehavior
|
||||
public enum ValueTooLongBehavior implements FieldBehavior<ValueTooLongBehavior>
|
||||
{
|
||||
TRUNCATE,
|
||||
TRUNCATE_ELLIPSIS,
|
||||
ERROR,
|
||||
PASS_THROUGH;
|
||||
|
||||
private static final QLogger LOG = QLogger.getLogger(ValueTooLongBehavior.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static FieldBehavior getDefault()
|
||||
@Override
|
||||
public ValueTooLongBehavior getDefault()
|
||||
{
|
||||
return PASS_THROUGH;
|
||||
return (PASS_THROUGH);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field, Set<FieldBehavior<?>> behaviorsToOmit)
|
||||
{
|
||||
if(this.equals(PASS_THROUGH))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(behaviorsToOmit != null && behaviorsToOmit.contains(this))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String fieldName = field.getName();
|
||||
if(!QFieldType.STRING.equals(field.getType()))
|
||||
{
|
||||
LOG.debug("Request to apply a ValueTooLongBehavior to a non-string field", logPair("table", table.getName()), logPair("field", fieldName));
|
||||
return;
|
||||
}
|
||||
|
||||
if(field.getMaxLength() == null)
|
||||
{
|
||||
LOG.debug("Request to apply a ValueTooLongBehavior to string field without a maxLength", logPair("table", table.getName()), logPair("field", fieldName));
|
||||
return;
|
||||
}
|
||||
|
||||
for(QRecord record : recordList)
|
||||
{
|
||||
String value = record.getValueString(fieldName);
|
||||
if(value != null && value.length() > field.getMaxLength())
|
||||
{
|
||||
switch(this)
|
||||
{
|
||||
case TRUNCATE -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength()));
|
||||
case TRUNCATE_ELLIPSIS -> record.setValue(fieldName, StringUtils.safeTruncate(value, field.getMaxLength(), "..."));
|
||||
case ERROR -> record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too long (max allowed length=" + field.getMaxLength() + ")"));
|
||||
///////////////////////////////////
|
||||
// PASS_THROUGH is handled above //
|
||||
///////////////////////////////////
|
||||
default -> throw new IllegalStateException("Unexpected enum value: " + this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,13 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
@ -51,6 +53,7 @@ public class QFrontendAppMetaData
|
||||
|
||||
private List<QAppSection> sections;
|
||||
|
||||
private Map<String, QSupplementalAppMetaData> supplementalAppMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -92,6 +95,31 @@ public class QFrontendAppMetaData
|
||||
{
|
||||
this.sections = filteredSections;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// include supplemental meta data, based on if it's meant for full or partial frontend meta-data requests //
|
||||
// todo - take includeFullMetaData as a param? //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
boolean includeFullMetaData = true;
|
||||
for(QSupplementalAppMetaData supplementalAppMetaData : CollectionUtils.nonNullMap(appMetaData.getSupplementalMetaData()).values())
|
||||
{
|
||||
boolean include;
|
||||
if(includeFullMetaData)
|
||||
{
|
||||
include = supplementalAppMetaData.includeInFullFrontendMetaData();
|
||||
}
|
||||
else
|
||||
{
|
||||
include = supplementalAppMetaData.includeInPartialFrontendMetaData();
|
||||
}
|
||||
|
||||
if(include)
|
||||
{
|
||||
this.supplementalAppMetaData = Objects.requireNonNullElseGet(this.supplementalAppMetaData, HashMap::new);
|
||||
this.supplementalAppMetaData.put(supplementalAppMetaData.getType(), supplementalAppMetaData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -196,4 +224,15 @@ public class QFrontendAppMetaData
|
||||
{
|
||||
return sections;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalAppMetaData
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QSupplementalAppMetaData> getSupplementalAppMetaData()
|
||||
{
|
||||
return supplementalAppMetaData;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.WidgetDropdownData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
|
||||
|
||||
@ -57,7 +58,8 @@ public class QFrontendWidgetMetaData
|
||||
private boolean showReloadButton = false;
|
||||
private boolean showExportButton = false;
|
||||
|
||||
protected Map<String, QIcon> icons;
|
||||
protected Map<String, QIcon> icons;
|
||||
protected Map<String, QHelpContent> helpContent;
|
||||
|
||||
private final boolean hasPermission;
|
||||
|
||||
@ -92,6 +94,8 @@ public class QFrontendWidgetMetaData
|
||||
this.icons = qWidgetMetaData.getIcons();
|
||||
}
|
||||
|
||||
this.helpContent = widgetMetaData.getHelpContent();
|
||||
|
||||
hasPermission = PermissionsHelper.hasWidgetPermission(actionInput, name);
|
||||
}
|
||||
|
||||
@ -259,4 +263,15 @@ public class QFrontendWidgetMetaData
|
||||
{
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for helpContent
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, QHelpContent> getHelpContent()
|
||||
{
|
||||
return helpContent;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
@ -45,6 +47,8 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
|
||||
private String name;
|
||||
private String label;
|
||||
|
||||
private Integer sortOrder = 500;
|
||||
|
||||
private QPermissionRules permissionRules;
|
||||
|
||||
private List<QAppChildMetaData> children;
|
||||
@ -55,6 +59,8 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
|
||||
private List<String> widgets;
|
||||
private List<QAppSection> sections;
|
||||
|
||||
private Map<String, QSupplementalAppMetaData> supplementalMetaData;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -437,4 +443,82 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
|
||||
{
|
||||
qInstance.addApp(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sortOrder
|
||||
*******************************************************************************/
|
||||
public Integer getSortOrder()
|
||||
{
|
||||
return (this.sortOrder);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for sortOrder
|
||||
*******************************************************************************/
|
||||
public void setSortOrder(Integer sortOrder)
|
||||
{
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for sortOrder
|
||||
*******************************************************************************/
|
||||
public QAppMetaData withSortOrder(Integer sortOrder)
|
||||
{
|
||||
this.sortOrder = sortOrder;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public Map<String, QSupplementalAppMetaData> getSupplementalMetaData()
|
||||
{
|
||||
return (this.supplementalMetaData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public void setSupplementalMetaData(Map<String, QSupplementalAppMetaData> supplementalMetaData)
|
||||
{
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QAppMetaData withSupplementalMetaData(QSupplementalAppMetaData supplementalMetaData)
|
||||
{
|
||||
if(this.supplementalMetaData == null)
|
||||
{
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for supplementalMetaData
|
||||
*******************************************************************************/
|
||||
public QAppMetaData withSupplementalMetaData(Map<String, QSupplementalAppMetaData> supplementalMetaData)
|
||||
{
|
||||
this.supplementalMetaData = supplementalMetaData;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for app-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QSupplementalAppMetaData
|
||||
{
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean includeInPartialFrontendMetaData()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean includeInFullFrontendMetaData()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void enrich(QInstance qInstance, QTableMetaData table)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QTableMetaData tableMetaData, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
}
|
@ -23,8 +23,11 @@ package com.kingsrook.qqq.backend.core.model.metadata.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -32,6 +35,8 @@ import com.kingsrook.qqq.backend.core.processes.implementations.basepull.Basepul
|
||||
*******************************************************************************/
|
||||
public class AbstractProcessMetaDataBuilder
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractProcessMetaDataBuilder.class);
|
||||
|
||||
protected QProcessMetaData processMetaData;
|
||||
|
||||
|
||||
@ -58,6 +63,54 @@ public class AbstractProcessMetaDataBuilder
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractProcessMetaDataBuilder withName(String name)
|
||||
{
|
||||
processMetaData.setName(name);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for label
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractProcessMetaDataBuilder withLabel(String name)
|
||||
{
|
||||
processMetaData.setLabel(name);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractProcessMetaDataBuilder withTableName(String tableName)
|
||||
{
|
||||
processMetaData.setTableName(tableName);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for icon
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractProcessMetaDataBuilder withIcon(QIcon icon)
|
||||
{
|
||||
processMetaData.setIcon(icon);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -65,7 +118,8 @@ public class AbstractProcessMetaDataBuilder
|
||||
{
|
||||
processMetaData.getInputFields().stream()
|
||||
.filter(f -> f.getName().equals(fieldName)).findFirst()
|
||||
.ifPresent(f -> f.setDefaultValue(value));
|
||||
.ifPresentOrElse(f -> f.setDefaultValue(value),
|
||||
() -> LOG.warn("Could not find process input field for setting default value", logPair("processName", () -> processMetaData.getName()), logPair("fieldName", fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. 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.security;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock.NullValueBehavior;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility for working with security key, nullValueBehaviors.
|
||||
*******************************************************************************/
|
||||
public class NullValueBehaviorUtil
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(NullValueBehaviorUtil.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Look at a RecordSecurityLock, but also the active session - and if the session
|
||||
** has a null-value-behavior key for the lock's key-type, then allow that behavior
|
||||
** to override the lock's default.
|
||||
*******************************************************************************/
|
||||
public static NullValueBehavior getEffectiveNullValueBehavior(RecordSecurityLock recordSecurityLock)
|
||||
{
|
||||
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
if(StringUtils.hasContent(securityKeyType.getNullValueBehaviorKeyName()))
|
||||
{
|
||||
List<Serializable> nullValueSessionValueList = QContext.getQSession().getSecurityKeyValues(securityKeyType.getNullValueBehaviorKeyName());
|
||||
if(CollectionUtils.nullSafeHasContents(nullValueSessionValueList))
|
||||
{
|
||||
NullValueBehavior nullValueBehavior = NullValueBehavior.tryToGetFromString(ValueUtils.getValueAsString(nullValueSessionValueList.get(0)));
|
||||
if(nullValueBehavior != null)
|
||||
{
|
||||
return nullValueBehavior;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Unexpected value in nullValueBehavior security key. Will use recordSecurityLock's nullValueBehavior",
|
||||
logPair("nullValueBehaviorKeyName", securityKeyType.getNullValueBehaviorKeyName()),
|
||||
logPair("value", nullValueSessionValueList.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (recordSecurityLock.getNullValueBehavior());
|
||||
}
|
||||
|
||||
}
|
@ -34,6 +34,7 @@ public class QSecurityKeyType implements TopLevelMetaDataInterface
|
||||
{
|
||||
private String name;
|
||||
private String allAccessKeyName;
|
||||
private String nullValueBehaviorKeyName;
|
||||
private String possibleValueSourceName;
|
||||
|
||||
|
||||
@ -149,4 +150,35 @@ public class QSecurityKeyType implements TopLevelMetaDataInterface
|
||||
qInstance.addSecurityKeyType(this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for nullValueBehaviorKeyName
|
||||
*******************************************************************************/
|
||||
public String getNullValueBehaviorKeyName()
|
||||
{
|
||||
return (this.nullValueBehaviorKeyName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for nullValueBehaviorKeyName
|
||||
*******************************************************************************/
|
||||
public void setNullValueBehaviorKeyName(String nullValueBehaviorKeyName)
|
||||
{
|
||||
this.nullValueBehaviorKeyName = nullValueBehaviorKeyName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for nullValueBehaviorKeyName
|
||||
*******************************************************************************/
|
||||
public QSecurityKeyType withNullValueBehaviorKeyName(String nullValueBehaviorKeyName)
|
||||
{
|
||||
this.nullValueBehaviorKeyName = nullValueBehaviorKeyName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.security;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -67,7 +69,34 @@ public class RecordSecurityLock
|
||||
{
|
||||
ALLOW,
|
||||
ALLOW_WRITE_ONLY, // not common - but see Audit, where you can do a thing that inserts them into a generic table, even though you can't later read them yourself...
|
||||
DENY
|
||||
DENY;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// for use in tryToGetFromString, where we'll lowercase the input //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
private static final Map<String, NullValueBehavior> stringMapping = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
stringMapping.put("allow", ALLOW);
|
||||
stringMapping.put("allow_write_only", ALLOW_WRITE_ONLY);
|
||||
stringMapping.put("allowwriteonly", ALLOW_WRITE_ONLY);
|
||||
stringMapping.put("deny", DENY);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static NullValueBehavior tryToGetFromString(String string)
|
||||
{
|
||||
if(string == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return stringMapping.get(string.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
@ -69,4 +70,16 @@ public abstract class QSupplementalTableMetaData
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QTableMetaData tableMetaData, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.serialization.QTableBackendDetailsDeserializer;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
|
||||
@ -100,4 +103,16 @@ public abstract class QTableBackendDetails
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QTableMetaData table, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
////////////////////////
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
@ -49,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRule
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -57,6 +59,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
*******************************************************************************/
|
||||
public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules, TopLevelMetaDataInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QTableMetaData.class);
|
||||
|
||||
private String name;
|
||||
private String label;
|
||||
|
||||
@ -813,6 +817,15 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withUniqueKey(UniqueKey uniqueKey)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// you can't add a null key, so, if someone tried, just gracefully return w/ noop //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
if(uniqueKey == null)
|
||||
{
|
||||
LOG.debug("Skipping request to add null uniqueKey", logPair("tableName", name));
|
||||
return (this);
|
||||
}
|
||||
|
||||
if(this.uniqueKeys == null)
|
||||
{
|
||||
this.uniqueKeys = new ArrayList<>();
|
||||
@ -1130,6 +1143,15 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withRecordSecurityLock(RecordSecurityLock recordSecurityLock)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// you can't add a null lock, so, if someone tried, just gracefully return w/ noop //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
if(recordSecurityLock == null)
|
||||
{
|
||||
LOG.debug("Skipping request to add null recordSecurityLock", logPair("tableName", name));
|
||||
return (this);
|
||||
}
|
||||
|
||||
if(this.recordSecurityLocks == null)
|
||||
{
|
||||
this.recordSecurityLocks = new ArrayList<>();
|
||||
|
@ -37,6 +37,11 @@ public class QTableAutomationDetails
|
||||
|
||||
private Integer overrideBatchSize;
|
||||
|
||||
private String shardByFieldName; // field in "this" table, to use for sharding
|
||||
private String shardSourceTableName; // name of the table where the shards are defined as rows
|
||||
private String shardLabelFieldName; // field in shard-source-table to use for labeling shards
|
||||
private String shardIdFieldName; // field in shard-source-table to identify shards (e.g., joins to this table's shardByFieldName)
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -188,4 +193,128 @@ public class QTableAutomationDetails
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shardByFieldName
|
||||
*******************************************************************************/
|
||||
public String getShardByFieldName()
|
||||
{
|
||||
return (this.shardByFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shardByFieldName
|
||||
*******************************************************************************/
|
||||
public void setShardByFieldName(String shardByFieldName)
|
||||
{
|
||||
this.shardByFieldName = shardByFieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for shardByFieldName
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withShardByFieldName(String shardByFieldName)
|
||||
{
|
||||
this.shardByFieldName = shardByFieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shardSourceTableName
|
||||
*******************************************************************************/
|
||||
public String getShardSourceTableName()
|
||||
{
|
||||
return (this.shardSourceTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shardSourceTableName
|
||||
*******************************************************************************/
|
||||
public void setShardSourceTableName(String shardSourceTableName)
|
||||
{
|
||||
this.shardSourceTableName = shardSourceTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for shardSourceTableName
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withShardSourceTableName(String shardSourceTableName)
|
||||
{
|
||||
this.shardSourceTableName = shardSourceTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shardLabelFieldName
|
||||
*******************************************************************************/
|
||||
public String getShardLabelFieldName()
|
||||
{
|
||||
return (this.shardLabelFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shardLabelFieldName
|
||||
*******************************************************************************/
|
||||
public void setShardLabelFieldName(String shardLabelFieldName)
|
||||
{
|
||||
this.shardLabelFieldName = shardLabelFieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for shardLabelFieldName
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withShardLabelFieldName(String shardLabelFieldName)
|
||||
{
|
||||
this.shardLabelFieldName = shardLabelFieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shardIdFieldName
|
||||
*******************************************************************************/
|
||||
public String getShardIdFieldName()
|
||||
{
|
||||
return (this.shardIdFieldName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for shardIdFieldName
|
||||
*******************************************************************************/
|
||||
public void setShardIdFieldName(String shardIdFieldName)
|
||||
{
|
||||
this.shardIdFieldName = shardIdFieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for shardIdFieldName
|
||||
*******************************************************************************/
|
||||
public QTableAutomationDetails withShardIdFieldName(String shardIdFieldName)
|
||||
{
|
||||
this.shardIdFieldName = shardIdFieldName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user